@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/README.ja.md +470 -146
- package/README.md +338 -151
- package/README.vi.md +484 -157
- package/VERSION +1 -1
- package/bin/auth/auth-commands.js +98 -13
- package/bin/auth/profile-detector.js +11 -6
- package/bin/ccs.js +87 -2
- package/bin/utils/error-codes.js +59 -0
- package/bin/utils/error-manager.js +38 -32
- package/bin/utils/helpers.js +65 -1
- package/bin/utils/progress-indicator.js +111 -0
- package/bin/utils/prompt.js +134 -0
- package/bin/utils/shell-completion.js +234 -0
- package/lib/ccs +541 -25
- package/lib/ccs.ps1 +381 -20
- package/lib/error-codes.ps1 +55 -0
- package/lib/error-codes.sh +63 -0
- package/lib/progress-indicator.ps1 +120 -0
- package/lib/progress-indicator.sh +117 -0
- package/lib/prompt.ps1 +109 -0
- package/lib/prompt.sh +99 -0
- package/package.json +1 -1
- package/scripts/completion/README.md +308 -0
- package/scripts/completion/ccs.bash +81 -0
- package/scripts/completion/ccs.fish +92 -0
- package/scripts/completion/ccs.ps1 +157 -0
- package/scripts/completion/ccs.zsh +130 -0
- package/scripts/postinstall.js +24 -0
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.
|
|
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}
|
|
31
|
-
echo -e "${RED}${BOLD}
|
|
32
|
-
echo -e "${RED}${BOLD}
|
|
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
|
-
|
|
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="
|
|
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
|
|
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 //
|
|
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
|
-
--
|
|
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 "
|
|
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
|
-
|
|
925
|
-
|
|
926
|
-
|
|
927
|
-
|
|
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
|
-
|
|
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
|
-
|
|
1036
|
-
$
|
|
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
|
|