@kaitranntt/ccs 4.4.0 → 5.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/README.md +98 -7
- package/VERSION +1 -1
- package/config/base-agy.settings.json +10 -0
- package/config/base-codex.settings.json +10 -0
- package/config/base-gemini.settings.json +10 -0
- package/dist/auth/auth-commands.d.ts +52 -0
- package/dist/auth/auth-commands.d.ts.map +1 -0
- package/dist/auth/auth-commands.js +479 -0
- package/dist/auth/auth-commands.js.map +1 -0
- package/dist/auth/profile-detector.d.ts +68 -0
- package/dist/auth/profile-detector.d.ts.map +1 -0
- package/dist/auth/profile-detector.js +209 -0
- package/dist/auth/profile-detector.js.map +1 -0
- package/dist/auth/profile-registry.d.ts +60 -0
- package/dist/auth/profile-registry.d.ts.map +1 -0
- package/dist/auth/profile-registry.js +188 -0
- package/dist/auth/profile-registry.js.map +1 -0
- package/dist/ccs.d.ts +10 -0
- package/dist/ccs.d.ts.map +1 -0
- package/dist/ccs.js +320 -0
- package/dist/ccs.js.map +1 -0
- package/dist/cliproxy/auth-handler.d.ts +95 -0
- package/dist/cliproxy/auth-handler.d.ts.map +1 -0
- package/dist/cliproxy/auth-handler.js +443 -0
- package/dist/cliproxy/auth-handler.js.map +1 -0
- package/dist/cliproxy/base-config-loader.d.ts +42 -0
- package/dist/cliproxy/base-config-loader.d.ts.map +1 -0
- package/dist/cliproxy/base-config-loader.js +123 -0
- package/dist/cliproxy/base-config-loader.js.map +1 -0
- package/dist/cliproxy/binary-manager.d.ts +104 -0
- package/dist/cliproxy/binary-manager.d.ts.map +1 -0
- package/dist/cliproxy/binary-manager.js +567 -0
- package/dist/cliproxy/binary-manager.js.map +1 -0
- package/dist/cliproxy/cliproxy-executor.d.ts +33 -0
- package/dist/cliproxy/cliproxy-executor.d.ts.map +1 -0
- package/dist/cliproxy/cliproxy-executor.js +297 -0
- package/dist/cliproxy/cliproxy-executor.js.map +1 -0
- package/dist/cliproxy/config-generator.d.ts +89 -0
- package/dist/cliproxy/config-generator.d.ts.map +1 -0
- package/dist/cliproxy/config-generator.js +263 -0
- package/dist/cliproxy/config-generator.js.map +1 -0
- package/dist/cliproxy/index.d.ts +13 -0
- package/dist/cliproxy/index.d.ts.map +1 -0
- package/dist/cliproxy/index.js +62 -0
- package/dist/cliproxy/index.js.map +1 -0
- package/dist/cliproxy/platform-detector.d.ts +48 -0
- package/dist/cliproxy/platform-detector.d.ts.map +1 -0
- package/dist/cliproxy/platform-detector.js +118 -0
- package/dist/cliproxy/platform-detector.js.map +1 -0
- package/dist/cliproxy/types.d.ts +169 -0
- package/dist/cliproxy/types.d.ts.map +1 -0
- package/dist/cliproxy/types.js +7 -0
- package/dist/cliproxy/types.js.map +1 -0
- package/dist/commands/doctor-command.d.ts +10 -0
- package/dist/commands/doctor-command.d.ts.map +1 -0
- package/dist/commands/doctor-command.js +44 -0
- package/dist/commands/doctor-command.js.map +1 -0
- package/dist/commands/help-command.d.ts +5 -0
- package/dist/commands/help-command.d.ts.map +1 -0
- package/dist/commands/help-command.js +104 -0
- package/dist/commands/help-command.js.map +1 -0
- package/dist/commands/install-command.d.ts +14 -0
- package/dist/commands/install-command.d.ts.map +1 -0
- package/dist/commands/install-command.js +39 -0
- package/dist/commands/install-command.js.map +1 -0
- package/dist/commands/shell-completion-command.d.ts +10 -0
- package/dist/commands/shell-completion-command.d.ts.map +1 -0
- package/dist/commands/shell-completion-command.js +85 -0
- package/dist/commands/shell-completion-command.js.map +1 -0
- package/dist/commands/sync-command.d.ts +10 -0
- package/dist/commands/sync-command.d.ts.map +1 -0
- package/dist/commands/sync-command.js +59 -0
- package/dist/commands/sync-command.js.map +1 -0
- package/dist/commands/update-command.d.ts +12 -0
- package/dist/commands/update-command.d.ts.map +1 -0
- package/dist/commands/update-command.js +295 -0
- package/dist/commands/update-command.js.map +1 -0
- package/dist/commands/version-command.d.ts +10 -0
- package/dist/commands/version-command.d.ts.map +1 -0
- package/dist/commands/version-command.js +100 -0
- package/dist/commands/version-command.js.map +1 -0
- package/dist/delegation/delegation-handler.d.ts +60 -0
- package/dist/delegation/delegation-handler.d.ts.map +1 -0
- package/dist/delegation/delegation-handler.js +174 -0
- package/dist/delegation/delegation-handler.js.map +1 -0
- package/dist/delegation/headless-executor.d.ts +114 -0
- package/dist/delegation/headless-executor.d.ts.map +1 -0
- package/dist/delegation/headless-executor.js +562 -0
- package/dist/delegation/headless-executor.js.map +1 -0
- package/dist/delegation/result-formatter.d.ts +108 -0
- package/dist/delegation/result-formatter.d.ts.map +1 -0
- package/dist/delegation/result-formatter.js +391 -0
- package/dist/delegation/result-formatter.js.map +1 -0
- package/dist/delegation/session-manager.d.ts +58 -0
- package/dist/delegation/session-manager.d.ts.map +1 -0
- package/dist/delegation/session-manager.js +153 -0
- package/dist/delegation/session-manager.js.map +1 -0
- package/dist/delegation/settings-parser.d.ts +31 -0
- package/dist/delegation/settings-parser.d.ts.map +1 -0
- package/dist/delegation/settings-parser.js +107 -0
- package/dist/delegation/settings-parser.js.map +1 -0
- package/dist/glmt/delta-accumulator.d.ts +210 -0
- package/dist/glmt/delta-accumulator.d.ts.map +1 -0
- package/dist/glmt/delta-accumulator.js +351 -0
- package/dist/glmt/delta-accumulator.js.map +1 -0
- package/dist/glmt/glmt-proxy.d.ts +72 -0
- package/dist/glmt/glmt-proxy.d.ts.map +1 -0
- package/dist/glmt/glmt-proxy.js +427 -0
- package/dist/glmt/glmt-proxy.js.map +1 -0
- package/dist/glmt/glmt-transformer.d.ts +265 -0
- package/dist/glmt/glmt-transformer.d.ts.map +1 -0
- package/dist/glmt/glmt-transformer.js +832 -0
- package/dist/glmt/glmt-transformer.js.map +1 -0
- package/dist/glmt/locale-enforcer.d.ts +38 -0
- package/dist/glmt/locale-enforcer.d.ts.map +1 -0
- package/dist/glmt/locale-enforcer.js +69 -0
- package/dist/glmt/locale-enforcer.js.map +1 -0
- package/dist/glmt/reasoning-enforcer.d.ts +52 -0
- package/dist/glmt/reasoning-enforcer.d.ts.map +1 -0
- package/dist/glmt/reasoning-enforcer.js +151 -0
- package/dist/glmt/reasoning-enforcer.js.map +1 -0
- package/dist/glmt/sse-parser.d.ts +47 -0
- package/dist/glmt/sse-parser.d.ts.map +1 -0
- package/dist/glmt/sse-parser.js +93 -0
- package/dist/glmt/sse-parser.js.map +1 -0
- package/dist/management/doctor.d.ts +104 -0
- package/dist/management/doctor.d.ts.map +1 -0
- package/dist/management/doctor.js +673 -0
- package/dist/management/doctor.js.map +1 -0
- package/dist/management/instance-manager.d.ts +57 -0
- package/dist/management/instance-manager.d.ts.map +1 -0
- package/dist/management/instance-manager.js +195 -0
- package/dist/management/instance-manager.js.map +1 -0
- package/dist/management/recovery-manager.d.ts +39 -0
- package/dist/management/recovery-manager.d.ts.map +1 -0
- package/dist/management/recovery-manager.js +141 -0
- package/dist/management/recovery-manager.js.map +1 -0
- package/dist/management/shared-manager.d.ts +47 -0
- package/dist/management/shared-manager.d.ts.map +1 -0
- package/dist/management/shared-manager.js +388 -0
- package/dist/management/shared-manager.js.map +1 -0
- package/dist/types/cli.d.ts +50 -0
- package/dist/types/cli.d.ts.map +1 -0
- package/dist/types/cli.js +16 -0
- package/dist/types/cli.js.map +1 -0
- package/dist/types/config.d.ts +51 -0
- package/dist/types/config.d.ts.map +1 -0
- package/dist/types/config.js +26 -0
- package/dist/types/config.js.map +1 -0
- package/dist/types/delegation.d.ts +61 -0
- package/dist/types/delegation.d.ts.map +1 -0
- package/dist/types/delegation.js +6 -0
- package/dist/types/delegation.js.map +1 -0
- package/dist/types/glmt.d.ts +95 -0
- package/dist/types/glmt.d.ts.map +1 -0
- package/dist/types/glmt.js +7 -0
- package/dist/types/glmt.js.map +1 -0
- package/dist/types/index.d.ts +13 -0
- package/dist/types/index.d.ts.map +1 -0
- package/dist/types/index.js +16 -0
- package/dist/types/index.js.map +1 -0
- package/dist/types/utils.d.ts +36 -0
- package/dist/types/utils.d.ts.map +1 -0
- package/dist/types/utils.js +22 -0
- package/dist/types/utils.js.map +1 -0
- package/dist/utils/claude-detector.d.ts +14 -0
- package/dist/utils/claude-detector.d.ts.map +1 -0
- package/dist/utils/claude-detector.js +112 -0
- package/dist/utils/claude-detector.js.map +1 -0
- package/dist/utils/claude-dir-installer.d.ts +46 -0
- package/dist/utils/claude-dir-installer.d.ts.map +1 -0
- package/dist/utils/claude-dir-installer.js +289 -0
- package/dist/utils/claude-dir-installer.js.map +1 -0
- package/dist/utils/claude-symlink-manager.d.ts +61 -0
- package/dist/utils/claude-symlink-manager.d.ts.map +1 -0
- package/dist/utils/claude-symlink-manager.js +291 -0
- package/dist/utils/claude-symlink-manager.js.map +1 -0
- package/dist/utils/config-manager.d.ts +32 -0
- package/dist/utils/config-manager.d.ts.map +1 -0
- package/dist/utils/config-manager.js +143 -0
- package/dist/utils/config-manager.js.map +1 -0
- package/dist/utils/delegation-validator.d.ts +39 -0
- package/dist/utils/delegation-validator.d.ts.map +1 -0
- package/dist/utils/delegation-validator.js +161 -0
- package/dist/utils/delegation-validator.js.map +1 -0
- package/dist/utils/error-codes.d.ts +36 -0
- package/dist/utils/error-codes.d.ts.map +1 -0
- package/dist/utils/error-codes.js +63 -0
- package/dist/utils/error-codes.js.map +1 -0
- package/dist/utils/error-manager.d.ts +59 -0
- package/dist/utils/error-manager.d.ts.map +1 -0
- package/dist/utils/error-manager.js +228 -0
- package/dist/utils/error-manager.js.map +1 -0
- package/dist/utils/helpers.d.ts +27 -0
- package/dist/utils/helpers.d.ts.map +1 -0
- package/dist/utils/helpers.js +150 -0
- package/dist/utils/helpers.js.map +1 -0
- package/dist/utils/package-manager-detector.d.ts +14 -0
- package/dist/utils/package-manager-detector.d.ts.map +1 -0
- package/dist/utils/package-manager-detector.js +162 -0
- package/dist/utils/package-manager-detector.js.map +1 -0
- package/dist/utils/progress-indicator.d.ts +52 -0
- package/dist/utils/progress-indicator.d.ts.map +1 -0
- package/dist/utils/progress-indicator.js +102 -0
- package/dist/utils/progress-indicator.js.map +1 -0
- package/dist/utils/prompt.d.ts +29 -0
- package/dist/utils/prompt.d.ts.map +1 -0
- package/dist/utils/prompt.js +116 -0
- package/dist/utils/prompt.js.map +1 -0
- package/dist/utils/shell-completion.d.ts +52 -0
- package/dist/utils/shell-completion.d.ts.map +1 -0
- package/dist/utils/shell-completion.js +231 -0
- package/dist/utils/shell-completion.js.map +1 -0
- package/dist/utils/shell-executor.d.ts +15 -0
- package/dist/utils/shell-executor.d.ts.map +1 -0
- package/dist/utils/shell-executor.js +57 -0
- package/dist/utils/shell-executor.js.map +1 -0
- package/dist/utils/update-checker.d.ts +48 -0
- package/dist/utils/update-checker.d.ts.map +1 -0
- package/dist/utils/update-checker.js +241 -0
- package/dist/utils/update-checker.js.map +1 -0
- package/lib/ccs +21 -1907
- package/lib/ccs.ps1 +26 -1800
- package/lib/error-codes.ps1 +2 -1
- package/lib/prompt.ps1 +2 -2
- package/package.json +31 -11
- package/scripts/add-shebang.js +39 -0
- package/scripts/bump-version.sh +25 -37
- package/scripts/dev-install.sh +32 -11
- package/scripts/postinstall.js +29 -29
- package/bin/auth/auth-commands.js +0 -499
- package/bin/auth/profile-detector.js +0 -204
- package/bin/auth/profile-registry.js +0 -225
- package/bin/ccs.js +0 -1034
- package/bin/delegation/README.md +0 -191
- package/bin/delegation/delegation-handler.js +0 -212
- package/bin/delegation/headless-executor.js +0 -618
- package/bin/delegation/result-formatter.js +0 -485
- package/bin/delegation/session-manager.js +0 -157
- package/bin/delegation/settings-parser.js +0 -109
- package/bin/glmt/delta-accumulator.js +0 -276
- package/bin/glmt/glmt-proxy.js +0 -495
- package/bin/glmt/glmt-transformer.js +0 -999
- package/bin/glmt/locale-enforcer.js +0 -72
- package/bin/glmt/reasoning-enforcer.js +0 -173
- package/bin/glmt/sse-parser.js +0 -96
- package/bin/management/doctor.js +0 -721
- package/bin/management/instance-manager.js +0 -202
- package/bin/management/recovery-manager.js +0 -135
- package/bin/management/shared-manager.js +0 -402
- package/bin/utils/claude-detector.js +0 -73
- package/bin/utils/claude-dir-installer.js +0 -283
- package/bin/utils/claude-symlink-manager.js +0 -289
- package/bin/utils/config-manager.js +0 -103
- package/bin/utils/delegation-validator.js +0 -154
- package/bin/utils/error-codes.js +0 -59
- package/bin/utils/error-manager.js +0 -165
- package/bin/utils/helpers.js +0 -136
- package/bin/utils/progress-indicator.js +0 -111
- package/bin/utils/prompt.js +0 -134
- package/bin/utils/shell-completion.js +0 -256
- package/bin/utils/update-checker.js +0 -243
package/lib/ccs
CHANGED
|
@@ -1,1918 +1,32 @@
|
|
|
1
1
|
#!/usr/bin/env bash
|
|
2
|
+
# CCS - Claude Code Switch (Bootstrap)
|
|
3
|
+
# Delegates to Node.js implementation via npx
|
|
4
|
+
# https://github.com/kaitranntt/ccs
|
|
2
5
|
set -euo pipefail
|
|
3
6
|
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
|
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"
|
|
7
|
+
readonly PACKAGE="@kaitranntt/ccs"
|
|
8
|
+
readonly MIN_NODE_VERSION=14
|
|
10
9
|
|
|
11
|
-
#
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
DEP_DIR="$SCRIPT_DIR"
|
|
17
|
-
else
|
|
18
|
-
# Standalone install - files in ~/.ccs/lib/
|
|
19
|
-
DEP_DIR="$HOME/.ccs/lib"
|
|
10
|
+
# Check Node.js installed
|
|
11
|
+
if ! command -v node &>/dev/null; then
|
|
12
|
+
echo "[X] Node.js not found" >&2
|
|
13
|
+
echo " Install: https://nodejs.org (LTS recommended)" >&2
|
|
14
|
+
exit 127
|
|
20
15
|
fi
|
|
21
16
|
|
|
22
|
-
#
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
# Source interactive prompts
|
|
29
|
-
source "$DEP_DIR/prompt.sh"
|
|
30
|
-
|
|
31
|
-
# --- Color/Format Functions ---
|
|
32
|
-
setup_colors() {
|
|
33
|
-
# Enable colors if: FORCE_COLOR set OR (TTY detected AND NO_COLOR not set) OR (TERM supports colors AND NO_COLOR not set)
|
|
34
|
-
if [[ -n "${FORCE_COLOR:-}" ]] || \
|
|
35
|
-
([[ -t 1 || -t 2 ]] && [[ -z "${NO_COLOR:-}" ]]) || \
|
|
36
|
-
([[ -n "${TERM:-}" && "${TERM}" != "dumb" ]] && [[ -z "${NO_COLOR:-}" ]]); then
|
|
37
|
-
RED='\033[0;31m'
|
|
38
|
-
GREEN='\033[0;32m'
|
|
39
|
-
YELLOW='\033[1;33m'
|
|
40
|
-
CYAN='\033[0;36m'
|
|
41
|
-
BOLD='\033[1m'
|
|
42
|
-
RESET='\033[0m'
|
|
43
|
-
else
|
|
44
|
-
RED='' GREEN='' YELLOW='' CYAN='' BOLD='' RESET=''
|
|
45
|
-
fi
|
|
46
|
-
}
|
|
47
|
-
|
|
48
|
-
msg_error() {
|
|
49
|
-
echo "" >&2
|
|
50
|
-
echo -e "${RED}${BOLD}=============================================${RESET}" >&2
|
|
51
|
-
echo -e "${RED}${BOLD} ERROR${RESET}" >&2
|
|
52
|
-
echo -e "${RED}${BOLD}=============================================${RESET}" >&2
|
|
53
|
-
echo "" >&2
|
|
54
|
-
echo -e "${RED}$1${RESET}" >&2
|
|
55
|
-
echo "" >&2
|
|
56
|
-
}
|
|
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
|
-
|
|
165
|
-
show_help() {
|
|
166
|
-
echo -e "${BOLD}CCS (Claude Code Switch) - Instant profile switching for Claude CLI${RESET}"
|
|
167
|
-
echo ""
|
|
168
|
-
|
|
169
|
-
echo -e "${CYAN}Usage:${RESET}"
|
|
170
|
-
echo -e " ${YELLOW}ccs${RESET} [profile] [claude-args...]"
|
|
171
|
-
echo -e " ${YELLOW}ccs${RESET} [flags]"
|
|
172
|
-
echo ""
|
|
173
|
-
|
|
174
|
-
echo -e "${CYAN}Description:${RESET}"
|
|
175
|
-
echo -e " Switch between multiple Claude accounts and alternative models"
|
|
176
|
-
echo -e " (GLM, Kimi) instantly. Run different Claude CLI sessions concurrently"
|
|
177
|
-
echo -e " with auto-recovery. Zero downtime."
|
|
178
|
-
echo ""
|
|
179
|
-
|
|
180
|
-
echo -e "${CYAN}Model Switching:${RESET}"
|
|
181
|
-
echo -e " ${YELLOW}ccs${RESET} Use default Claude account"
|
|
182
|
-
echo -e " ${YELLOW}ccs glm${RESET} Switch to GLM 4.6 model"
|
|
183
|
-
echo -e " ${YELLOW}ccs glmt${RESET} Switch to GLM with thinking mode"
|
|
184
|
-
echo -e " ${YELLOW}ccs glmt --verbose${RESET} Enable debug logging"
|
|
185
|
-
echo -e " ${YELLOW}ccs kimi${RESET} Switch to Kimi for Coding"
|
|
186
|
-
echo -e " ${YELLOW}ccs glm${RESET} \"debug this code\" Use GLM and run command"
|
|
187
|
-
echo ""
|
|
188
|
-
|
|
189
|
-
echo -e "${CYAN}Account Management:${RESET}"
|
|
190
|
-
echo -e " ${YELLOW}ccs auth --help${RESET} Run multiple Claude accounts concurrently"
|
|
191
|
-
echo ""
|
|
192
|
-
|
|
193
|
-
echo -e "${CYAN}Delegation (inside Claude Code CLI):${RESET}"
|
|
194
|
-
echo -e " ${YELLOW}/ccs \"task\"${RESET} Delegate task (auto-selects best profile)"
|
|
195
|
-
echo -e " ${YELLOW}/ccs --glm \"task\"${RESET} Force GLM-4.6 for simple tasks"
|
|
196
|
-
echo -e " ${YELLOW}/ccs --kimi \"task\"${RESET} Force Kimi for long context"
|
|
197
|
-
echo -e " ${YELLOW}/ccs:continue \"follow-up\"${RESET} Continue last delegation session"
|
|
198
|
-
echo -e " Save tokens by delegating simple tasks to cost-optimized models"
|
|
199
|
-
echo ""
|
|
200
|
-
|
|
201
|
-
echo -e "${CYAN}Diagnostics:${RESET}"
|
|
202
|
-
echo -e " ${YELLOW}ccs doctor${RESET} Run health check and diagnostics"
|
|
203
|
-
echo -e " ${YELLOW}ccs sync${RESET} Sync delegation commands and skills"
|
|
204
|
-
echo -e " ${YELLOW}ccs update${RESET} Update CCS to latest version"
|
|
205
|
-
echo ""
|
|
206
|
-
|
|
207
|
-
echo -e "${CYAN}Flags:${RESET}"
|
|
208
|
-
echo -e " ${YELLOW}-h, --help${RESET} Show this help message"
|
|
209
|
-
echo -e " ${YELLOW}-v, --version${RESET} Show version and installation info"
|
|
210
|
-
echo -e " ${YELLOW}-sc, --shell-completion${RESET} Install shell auto-completion"
|
|
211
|
-
echo ""
|
|
212
|
-
|
|
213
|
-
echo -e "${CYAN}Configuration:${RESET}"
|
|
214
|
-
echo -e " Config File: ~/.ccs/config.json"
|
|
215
|
-
echo -e " Profiles: ~/.ccs/profiles.json"
|
|
216
|
-
echo -e " Instances: ~/.ccs/instances/"
|
|
217
|
-
echo -e " Settings: ~/.ccs/*.settings.json"
|
|
218
|
-
echo -e " Environment: CCS_CONFIG (override config path)"
|
|
219
|
-
echo ""
|
|
220
|
-
|
|
221
|
-
echo -e "${CYAN}Shared Data:${RESET}"
|
|
222
|
-
echo -e " Commands: ~/.ccs/shared/commands/"
|
|
223
|
-
echo -e " Skills: ~/.ccs/shared/skills/"
|
|
224
|
-
echo -e " Agents: ~/.ccs/shared/agents/"
|
|
225
|
-
echo -e " Plugins: ~/.ccs/shared/plugins/"
|
|
226
|
-
echo -e " Note: Commands, skills, agents, and plugins are symlinked across all profiles"
|
|
227
|
-
echo ""
|
|
228
|
-
|
|
229
|
-
echo -e "${CYAN}Examples:${RESET}"
|
|
230
|
-
echo -e " ${YELLOW}\$ ccs${RESET} # Use default account"
|
|
231
|
-
echo -e " ${YELLOW}\$ ccs glm \"implement API\"${RESET} # Cost-optimized model"
|
|
232
|
-
echo ""
|
|
233
|
-
echo -e " For more: ${CYAN}https://github.com/kaitranntt/ccs/blob/main/README.md${RESET}"
|
|
234
|
-
echo ""
|
|
235
|
-
|
|
236
|
-
echo -e "${YELLOW}Uninstall:${RESET}"
|
|
237
|
-
echo -e " npm: npm uninstall -g @kaitranntt/ccs"
|
|
238
|
-
echo -e " macOS/Linux: curl -fsSL ccs.kaitran.ca/uninstall | bash"
|
|
239
|
-
echo -e " Windows: irm ccs.kaitran.ca/uninstall | iex"
|
|
240
|
-
echo ""
|
|
241
|
-
|
|
242
|
-
echo -e "${CYAN}Documentation:${RESET}"
|
|
243
|
-
echo -e " GitHub: ${CYAN}https://github.com/kaitranntt/ccs${RESET}"
|
|
244
|
-
echo -e " Docs: https://github.com/kaitranntt/ccs/blob/main/README.md"
|
|
245
|
-
echo -e " Issues: https://github.com/kaitranntt/ccs/issues"
|
|
246
|
-
echo ""
|
|
247
|
-
|
|
248
|
-
echo -e "${CYAN}License:${RESET} MIT"
|
|
249
|
-
}
|
|
250
|
-
|
|
251
|
-
setup_colors
|
|
252
|
-
|
|
253
|
-
# Check dependencies early
|
|
254
|
-
command -v jq &>/dev/null || {
|
|
255
|
-
msg_error "jq required but not installed. Install: brew install jq (macOS) or apt install jq (Ubuntu)"
|
|
256
|
-
exit 1
|
|
257
|
-
}
|
|
258
|
-
|
|
259
|
-
# --- Auto-Recovery Functions ---
|
|
260
|
-
|
|
261
|
-
ensure_ccs_directory() {
|
|
262
|
-
[[ -d "$HOME/.ccs" ]] && return 0
|
|
263
|
-
|
|
264
|
-
mkdir -p "$HOME/.ccs" 2>/dev/null || {
|
|
265
|
-
msg_error "Cannot create ~/.ccs/ directory. Check permissions."
|
|
266
|
-
return 1
|
|
267
|
-
}
|
|
268
|
-
|
|
269
|
-
echo "[i] Auto-recovery: Created ~/.ccs/ directory"
|
|
270
|
-
return 0
|
|
271
|
-
}
|
|
272
|
-
|
|
273
|
-
ensure_config_json() {
|
|
274
|
-
local config_file="$HOME/.ccs/config.json"
|
|
275
|
-
|
|
276
|
-
# Check if exists and valid
|
|
277
|
-
if [[ -f "$config_file" ]]; then
|
|
278
|
-
jq empty "$config_file" 2>/dev/null && return 0
|
|
279
|
-
|
|
280
|
-
# Corrupted - backup and recreate
|
|
281
|
-
local backup_file="${config_file}.backup.$(date +%s)"
|
|
282
|
-
mv "$config_file" "$backup_file" 2>/dev/null
|
|
283
|
-
echo "[i] Auto-recovery: Backed up corrupted config.json"
|
|
284
|
-
fi
|
|
285
|
-
|
|
286
|
-
# Create default config
|
|
287
|
-
cat > "$config_file" <<'EOF'
|
|
288
|
-
{
|
|
289
|
-
"profiles": {
|
|
290
|
-
"glm": "~/.ccs/glm.settings.json",
|
|
291
|
-
"kimi": "~/.ccs/kimi.settings.json",
|
|
292
|
-
"default": "~/.claude/settings.json"
|
|
293
|
-
}
|
|
294
|
-
}
|
|
295
|
-
EOF
|
|
296
|
-
|
|
297
|
-
echo "[i] Auto-recovery: Created ~/.ccs/config.json"
|
|
298
|
-
return 0
|
|
299
|
-
}
|
|
300
|
-
|
|
301
|
-
ensure_claude_settings() {
|
|
302
|
-
local claude_dir="$HOME/.claude"
|
|
303
|
-
local settings_file="$claude_dir/settings.json"
|
|
304
|
-
|
|
305
|
-
# Create ~/.claude/ if missing
|
|
306
|
-
if [[ ! -d "$claude_dir" ]]; then
|
|
307
|
-
mkdir -p "$claude_dir" 2>/dev/null || return 1
|
|
308
|
-
echo "[i] Auto-recovery: Created ~/.claude/ directory"
|
|
309
|
-
fi
|
|
310
|
-
|
|
311
|
-
# Create settings.json if missing
|
|
312
|
-
if [[ ! -f "$settings_file" ]]; then
|
|
313
|
-
echo '{}' > "$settings_file" 2>/dev/null || return 1
|
|
314
|
-
echo "[i] Auto-recovery: Created ~/.claude/settings.json"
|
|
315
|
-
echo "[i] Next step: Run 'claude /login' to authenticate"
|
|
316
|
-
return 0
|
|
317
|
-
fi
|
|
318
|
-
|
|
319
|
-
return 0
|
|
320
|
-
}
|
|
321
|
-
|
|
322
|
-
# Run auto-recovery
|
|
323
|
-
auto_recover() {
|
|
324
|
-
ensure_ccs_directory || return 1
|
|
325
|
-
ensure_config_json || return 1
|
|
326
|
-
ensure_claude_settings || return 1
|
|
327
|
-
return 0
|
|
328
|
-
}
|
|
329
|
-
|
|
330
|
-
# --- Doctor Command ---
|
|
331
|
-
|
|
332
|
-
doctor_check() {
|
|
333
|
-
local check_name="$1"
|
|
334
|
-
local status="$2" # success, warning, error
|
|
335
|
-
local message="${3:-}"
|
|
336
|
-
|
|
337
|
-
case "$status" in
|
|
338
|
-
success)
|
|
339
|
-
echo -e "${GREEN}[OK]${RESET} $check_name"
|
|
340
|
-
;;
|
|
341
|
-
warning)
|
|
342
|
-
echo -e "${YELLOW}[!]${RESET} $check_name${message:+: $message}"
|
|
343
|
-
;;
|
|
344
|
-
error)
|
|
345
|
-
echo -e "${RED}[X]${RESET} $check_name: $message"
|
|
346
|
-
;;
|
|
347
|
-
esac
|
|
348
|
-
}
|
|
349
|
-
|
|
350
|
-
doctor_run() {
|
|
351
|
-
echo -e "${CYAN}Running CCS Health Check...${RESET}"
|
|
352
|
-
echo ""
|
|
353
|
-
|
|
354
|
-
local has_errors=false
|
|
355
|
-
local total_checks=9
|
|
356
|
-
local current_check=0
|
|
357
|
-
|
|
358
|
-
# Check Claude CLI
|
|
359
|
-
current_check=$((current_check + 1))
|
|
360
|
-
show_progress_step $current_check $total_checks "Checking Claude CLI"
|
|
361
|
-
if command -v "$(detect_claude_cli)" &>/dev/null; then
|
|
362
|
-
clear_progress
|
|
363
|
-
doctor_check "Claude CLI" "success"
|
|
364
|
-
else
|
|
365
|
-
clear_progress
|
|
366
|
-
doctor_check "Claude CLI" "error" "Not found in PATH"
|
|
367
|
-
has_errors=true
|
|
368
|
-
fi
|
|
369
|
-
|
|
370
|
-
# Check ~/.ccs/
|
|
371
|
-
current_check=$((current_check + 1))
|
|
372
|
-
show_progress_step $current_check $total_checks "Checking CCS directory"
|
|
373
|
-
if [[ -d "$HOME/.ccs" ]]; then
|
|
374
|
-
clear_progress
|
|
375
|
-
doctor_check "CCS Directory" "success"
|
|
376
|
-
else
|
|
377
|
-
clear_progress
|
|
378
|
-
doctor_check "CCS Directory" "error" "~/.ccs/ not found"
|
|
379
|
-
has_errors=true
|
|
380
|
-
fi
|
|
381
|
-
|
|
382
|
-
# Check config.json
|
|
383
|
-
current_check=$((current_check + 1))
|
|
384
|
-
show_progress_step $current_check $total_checks "Checking config.json"
|
|
385
|
-
if [[ -f "$CONFIG_FILE" ]]; then
|
|
386
|
-
if jq empty "$CONFIG_FILE" 2>/dev/null; then
|
|
387
|
-
clear_progress
|
|
388
|
-
doctor_check "config.json" "success"
|
|
389
|
-
else
|
|
390
|
-
clear_progress
|
|
391
|
-
doctor_check "config.json" "error" "Invalid JSON"
|
|
392
|
-
has_errors=true
|
|
393
|
-
fi
|
|
394
|
-
else
|
|
395
|
-
clear_progress
|
|
396
|
-
doctor_check "config.json" "error" "Not found"
|
|
397
|
-
has_errors=true
|
|
398
|
-
fi
|
|
399
|
-
|
|
400
|
-
# Check glm.settings.json
|
|
401
|
-
current_check=$((current_check + 1))
|
|
402
|
-
show_progress_step $current_check $total_checks "Checking glm.settings.json"
|
|
403
|
-
local glm_file="$HOME/.ccs/glm.settings.json"
|
|
404
|
-
if [[ -f "$glm_file" ]]; then
|
|
405
|
-
if jq empty "$glm_file" 2>/dev/null; then
|
|
406
|
-
clear_progress
|
|
407
|
-
doctor_check "glm.settings.json" "success"
|
|
408
|
-
else
|
|
409
|
-
clear_progress
|
|
410
|
-
doctor_check "glm.settings.json" "error" "Invalid JSON"
|
|
411
|
-
has_errors=true
|
|
412
|
-
fi
|
|
413
|
-
else
|
|
414
|
-
clear_progress
|
|
415
|
-
doctor_check "glm.settings.json" "error" "Not found"
|
|
416
|
-
has_errors=true
|
|
417
|
-
fi
|
|
418
|
-
|
|
419
|
-
# Check kimi.settings.json
|
|
420
|
-
current_check=$((current_check + 1))
|
|
421
|
-
show_progress_step $current_check $total_checks "Checking kimi.settings.json"
|
|
422
|
-
local kimi_file="$HOME/.ccs/kimi.settings.json"
|
|
423
|
-
if [[ -f "$kimi_file" ]]; then
|
|
424
|
-
if jq empty "$kimi_file" 2>/dev/null; then
|
|
425
|
-
clear_progress
|
|
426
|
-
doctor_check "kimi.settings.json" "success"
|
|
427
|
-
else
|
|
428
|
-
clear_progress
|
|
429
|
-
doctor_check "kimi.settings.json" "error" "Invalid JSON"
|
|
430
|
-
has_errors=true
|
|
431
|
-
fi
|
|
432
|
-
else
|
|
433
|
-
clear_progress
|
|
434
|
-
doctor_check "kimi.settings.json" "error" "Not found"
|
|
435
|
-
has_errors=true
|
|
436
|
-
fi
|
|
437
|
-
|
|
438
|
-
# Check ~/.claude/settings.json
|
|
439
|
-
current_check=$((current_check + 1))
|
|
440
|
-
show_progress_step $current_check $total_checks "Checking Claude settings"
|
|
441
|
-
if [[ -f "$HOME/.claude/settings.json" ]]; then
|
|
442
|
-
if jq empty "$HOME/.claude/settings.json" 2>/dev/null; then
|
|
443
|
-
clear_progress
|
|
444
|
-
doctor_check "Claude Settings" "success"
|
|
445
|
-
else
|
|
446
|
-
clear_progress
|
|
447
|
-
doctor_check "Claude Settings" "warning" "Invalid JSON"
|
|
448
|
-
fi
|
|
449
|
-
else
|
|
450
|
-
clear_progress
|
|
451
|
-
doctor_check "Claude Settings" "warning" "Not found - run 'claude /login'"
|
|
452
|
-
fi
|
|
453
|
-
|
|
454
|
-
# Check profiles
|
|
455
|
-
current_check=$((current_check + 1))
|
|
456
|
-
show_progress_step $current_check $total_checks "Checking profiles"
|
|
457
|
-
if [[ -f "$CONFIG_FILE" ]]; then
|
|
458
|
-
local profile_count=$(jq -r '.profiles | length' "$CONFIG_FILE" 2>/dev/null || echo "0")
|
|
459
|
-
clear_progress
|
|
460
|
-
doctor_check "Profiles" "success" "($profile_count configured)"
|
|
461
|
-
fi
|
|
462
|
-
|
|
463
|
-
# Check instances
|
|
464
|
-
current_check=$((current_check + 1))
|
|
465
|
-
show_progress_step $current_check $total_checks "Checking instances"
|
|
466
|
-
if [[ -d "$INSTANCES_DIR" ]]; then
|
|
467
|
-
local instance_count=$(find "$INSTANCES_DIR" -maxdepth 1 -type d 2>/dev/null | wc -l)
|
|
468
|
-
instance_count=$((instance_count - 1)) # Exclude parent dir
|
|
469
|
-
clear_progress
|
|
470
|
-
doctor_check "Instances" "success" "($instance_count account profiles)"
|
|
471
|
-
else
|
|
472
|
-
clear_progress
|
|
473
|
-
doctor_check "Instances" "success" "(no account profiles)"
|
|
474
|
-
fi
|
|
475
|
-
|
|
476
|
-
# Check permissions
|
|
477
|
-
current_check=$((current_check + 1))
|
|
478
|
-
show_progress_step $current_check $total_checks "Checking permissions"
|
|
479
|
-
local test_file="$HOME/.ccs/.permission-test"
|
|
480
|
-
if echo "test" > "$test_file" 2>/dev/null; then
|
|
481
|
-
rm -f "$test_file" 2>/dev/null
|
|
482
|
-
clear_progress
|
|
483
|
-
doctor_check "Permissions" "success"
|
|
484
|
-
else
|
|
485
|
-
clear_progress
|
|
486
|
-
doctor_check "Permissions" "error" "Cannot write to ~/.ccs/"
|
|
487
|
-
has_errors=true
|
|
488
|
-
fi
|
|
489
|
-
|
|
490
|
-
# Summary
|
|
491
|
-
echo ""
|
|
492
|
-
echo -e "${CYAN}═══════════════════════════════════════════${RESET}"
|
|
493
|
-
if $has_errors; then
|
|
494
|
-
echo -e "${RED}Status: Installation has errors${RESET}"
|
|
495
|
-
echo "Run: npm install -g @kaitranntt/ccs --force"
|
|
496
|
-
else
|
|
497
|
-
echo -e "${GREEN}✓ All checks passed!${RESET}"
|
|
498
|
-
fi
|
|
499
|
-
echo ""
|
|
500
|
-
|
|
501
|
-
$has_errors && exit 1 || exit 0
|
|
502
|
-
}
|
|
503
|
-
|
|
504
|
-
# --- Sync Command ---
|
|
505
|
-
|
|
506
|
-
sync_run() {
|
|
507
|
-
local ccs_claude_dir="$HOME/.ccs/.claude"
|
|
508
|
-
local user_claude_dir="$HOME/.claude"
|
|
509
|
-
|
|
510
|
-
echo -e "${CYAN}Syncing delegation commands and skills to ~/.claude/...${RESET}"
|
|
511
|
-
echo ""
|
|
512
|
-
|
|
513
|
-
# Check if source directory exists
|
|
514
|
-
if [[ ! -d "$ccs_claude_dir" ]]; then
|
|
515
|
-
msg_error "CCS .claude/ directory not found at $ccs_claude_dir"
|
|
516
|
-
echo "Reinstall CCS: npm install -g @kaitranntt/ccs --force"
|
|
17
|
+
# Check Node.js version (major only)
|
|
18
|
+
node_version=$(node -v | cut -d'v' -f2 | cut -d'.' -f1)
|
|
19
|
+
if [[ "$node_version" -lt "$MIN_NODE_VERSION" ]]; then
|
|
20
|
+
echo "[X] Node.js $MIN_NODE_VERSION+ required (found: $(node -v))" >&2
|
|
21
|
+
echo " Update: https://nodejs.org" >&2
|
|
517
22
|
exit 1
|
|
518
|
-
fi
|
|
519
|
-
|
|
520
|
-
# Create ~/.claude/ if missing
|
|
521
|
-
if [[ ! -d "$user_claude_dir" ]]; then
|
|
522
|
-
echo -e "${CYAN}[i]${RESET} Creating ~/.claude/ directory"
|
|
523
|
-
mkdir -p "$user_claude_dir"
|
|
524
|
-
chmod 700 "$user_claude_dir"
|
|
525
|
-
fi
|
|
526
|
-
|
|
527
|
-
# Items to symlink (source:target:type)
|
|
528
|
-
local items=(
|
|
529
|
-
"commands/ccs:commands/ccs:dir"
|
|
530
|
-
"skills/ccs-delegation:skills/ccs-delegation:dir"
|
|
531
|
-
"agents/ccs-delegator.md:agents/ccs-delegator.md:file"
|
|
532
|
-
)
|
|
533
|
-
|
|
534
|
-
local installed=0
|
|
535
|
-
local skipped=0
|
|
536
|
-
|
|
537
|
-
for item in "${items[@]}"; do
|
|
538
|
-
IFS=':' read -r source target type <<< "$item"
|
|
539
|
-
local source_path="$ccs_claude_dir/$source"
|
|
540
|
-
local target_path="$user_claude_dir/$target"
|
|
541
|
-
local target_dir="$(dirname "$target_path")"
|
|
542
|
-
|
|
543
|
-
# Check source exists
|
|
544
|
-
if [[ ! -e "$source_path" ]]; then
|
|
545
|
-
echo -e "${YELLOW}[!]${RESET} Source not found: $source, skipping"
|
|
546
|
-
continue
|
|
547
|
-
fi
|
|
548
|
-
|
|
549
|
-
# Create parent directory if needed
|
|
550
|
-
if [[ ! -d "$target_dir" ]]; then
|
|
551
|
-
mkdir -p "$target_dir"
|
|
552
|
-
chmod 700 "$target_dir"
|
|
553
|
-
fi
|
|
554
|
-
|
|
555
|
-
# Check if already correct symlink
|
|
556
|
-
if [[ -L "$target_path" ]]; then
|
|
557
|
-
local link_target="$(readlink "$target_path")"
|
|
558
|
-
local resolved_target="$(cd "$(dirname "$target_path")" && cd "$(dirname "$link_target")" && pwd)/$(basename "$link_target")"
|
|
559
|
-
|
|
560
|
-
if [[ "$resolved_target" == "$source_path" ]]; then
|
|
561
|
-
((skipped++))
|
|
562
|
-
continue
|
|
563
|
-
fi
|
|
564
|
-
fi
|
|
565
|
-
|
|
566
|
-
# Backup existing file/directory
|
|
567
|
-
if [[ -e "$target_path" ]]; then
|
|
568
|
-
local timestamp="$(date +%Y-%m-%d)"
|
|
569
|
-
local backup_path="${target_path}.backup-${timestamp}"
|
|
570
|
-
local counter=1
|
|
571
|
-
|
|
572
|
-
while [[ -e "$backup_path" ]]; do
|
|
573
|
-
backup_path="${target_path}.backup-${timestamp}-${counter}"
|
|
574
|
-
((counter++))
|
|
575
|
-
done
|
|
576
|
-
|
|
577
|
-
mv "$target_path" "$backup_path"
|
|
578
|
-
echo -e "${CYAN}[i]${RESET} Backed up existing to $(basename "$backup_path")"
|
|
579
|
-
fi
|
|
580
|
-
|
|
581
|
-
# Create symlink
|
|
582
|
-
ln -s "$source_path" "$target_path" 2>/dev/null
|
|
583
|
-
if [[ $? -eq 0 ]]; then
|
|
584
|
-
echo -e "${GREEN}[OK]${RESET} Installed $target"
|
|
585
|
-
((installed++))
|
|
586
|
-
else
|
|
587
|
-
echo -e "${RED}[X]${RESET} Failed to install $target"
|
|
588
|
-
fi
|
|
589
|
-
done
|
|
590
|
-
|
|
591
|
-
echo ""
|
|
592
|
-
echo -e "${GREEN}✓ Update complete${RESET}"
|
|
593
|
-
echo " Installed: $installed"
|
|
594
|
-
echo " Already up-to-date: $skipped"
|
|
595
|
-
echo ""
|
|
596
|
-
}
|
|
597
|
-
|
|
598
|
-
# --- Update Command ---
|
|
599
|
-
|
|
600
|
-
update_run() {
|
|
601
|
-
echo ""
|
|
602
|
-
echo -e "${CYAN}Checking for updates...${RESET}"
|
|
603
|
-
echo ""
|
|
604
|
-
|
|
605
|
-
# Detect installation method
|
|
606
|
-
local install_method="direct"
|
|
607
|
-
if command -v npm &>/dev/null && npm list -g @kaitranntt/ccs &>/dev/null 2>&1; then
|
|
608
|
-
install_method="npm"
|
|
609
|
-
fi
|
|
610
|
-
|
|
611
|
-
# Fetch latest version from appropriate source
|
|
612
|
-
local latest_version=""
|
|
613
|
-
if command -v curl &>/dev/null; then
|
|
614
|
-
if [[ "$install_method" == "npm" ]]; then
|
|
615
|
-
# Check npm registry for npm installations
|
|
616
|
-
latest_version=$(curl -fsSL https://registry.npmjs.org/@kaitranntt/ccs/latest 2>/dev/null | \
|
|
617
|
-
grep '"version"' | head -1 | sed -E 's/.*"version"[[:space:]]*:[[:space:]]*"([0-9.]+)".*/\1/')
|
|
618
|
-
else
|
|
619
|
-
# Check GitHub releases for direct installations
|
|
620
|
-
latest_version=$(curl -fsSL https://api.github.com/repos/kaitranntt/ccs/releases/latest 2>/dev/null | \
|
|
621
|
-
grep '"tag_name"' | sed -E 's/.*"v?([0-9.]+)".*/\1/')
|
|
622
|
-
fi
|
|
623
|
-
fi
|
|
624
|
-
|
|
625
|
-
if [[ -z "$latest_version" ]]; then
|
|
626
|
-
echo -e "${YELLOW}[!] Unable to check for updates${RESET}"
|
|
627
|
-
echo ""
|
|
628
|
-
echo "Try manually:"
|
|
629
|
-
if [[ "$install_method" == "npm" ]]; then
|
|
630
|
-
echo -e " ${YELLOW}npm install -g @kaitranntt/ccs@latest${RESET}"
|
|
631
|
-
else
|
|
632
|
-
echo -e " ${YELLOW}curl -fsSL ccs.kaitran.ca/install | bash${RESET}"
|
|
633
|
-
fi
|
|
634
|
-
echo ""
|
|
635
|
-
exit 1
|
|
636
|
-
fi
|
|
637
|
-
|
|
638
|
-
# Compare versions
|
|
639
|
-
if [[ "$latest_version" == "$CCS_VERSION" ]]; then
|
|
640
|
-
echo -e "${GREEN}[OK] You are already on the latest version (${CCS_VERSION})${RESET}"
|
|
641
|
-
echo ""
|
|
642
|
-
exit 0
|
|
643
|
-
fi
|
|
644
|
-
|
|
645
|
-
# Check if update available
|
|
646
|
-
local current_major=$(echo "$CCS_VERSION" | cut -d. -f1)
|
|
647
|
-
local current_minor=$(echo "$CCS_VERSION" | cut -d. -f2)
|
|
648
|
-
local current_patch=$(echo "$CCS_VERSION" | cut -d. -f3)
|
|
649
|
-
|
|
650
|
-
local latest_major=$(echo "$latest_version" | cut -d. -f1)
|
|
651
|
-
local latest_minor=$(echo "$latest_version" | cut -d. -f2)
|
|
652
|
-
local latest_patch=$(echo "$latest_version" | cut -d. -f3)
|
|
653
|
-
|
|
654
|
-
local is_newer=0
|
|
655
|
-
if [[ $latest_major -gt $current_major ]]; then
|
|
656
|
-
is_newer=1
|
|
657
|
-
elif [[ $latest_major -eq $current_major ]] && [[ $latest_minor -gt $current_minor ]]; then
|
|
658
|
-
is_newer=1
|
|
659
|
-
elif [[ $latest_major -eq $current_major ]] && [[ $latest_minor -eq $current_minor ]] && [[ $latest_patch -gt $current_patch ]]; then
|
|
660
|
-
is_newer=1
|
|
661
|
-
fi
|
|
662
|
-
|
|
663
|
-
if [[ $is_newer -eq 0 ]]; then
|
|
664
|
-
echo -e "${GREEN}[OK] You are on version ${CCS_VERSION} (latest is ${latest_version})${RESET}"
|
|
665
|
-
echo ""
|
|
666
|
-
exit 0
|
|
667
|
-
fi
|
|
668
|
-
|
|
669
|
-
echo -e "${YELLOW}[i] Update available: ${CCS_VERSION} → ${latest_version}${RESET}"
|
|
670
|
-
echo ""
|
|
671
|
-
|
|
672
|
-
# Perform update based on installation method
|
|
673
|
-
if [[ "$install_method" == "npm" ]]; then
|
|
674
|
-
echo -e "${CYAN}Updating via npm...${RESET}"
|
|
675
|
-
echo ""
|
|
676
|
-
|
|
677
|
-
# Clear npm cache to ensure fresh download
|
|
678
|
-
echo -e "${CYAN}Clearing package cache...${RESET}"
|
|
679
|
-
if ! npm cache clean --force 2>/dev/null; then
|
|
680
|
-
echo -e "${YELLOW}[!] Cache clearing failed, proceeding anyway...${RESET}"
|
|
681
|
-
fi
|
|
682
|
-
echo ""
|
|
683
|
-
|
|
684
|
-
if npm install -g @kaitranntt/ccs@latest; then
|
|
685
|
-
echo ""
|
|
686
|
-
echo -e "${GREEN}[OK] Update successful!${RESET}"
|
|
687
|
-
echo ""
|
|
688
|
-
echo -e "Run ${YELLOW}ccs --version${RESET} to verify"
|
|
689
|
-
echo ""
|
|
690
|
-
exit 0
|
|
691
|
-
else
|
|
692
|
-
echo ""
|
|
693
|
-
echo -e "${RED}[X] Update failed${RESET}"
|
|
694
|
-
echo ""
|
|
695
|
-
echo "Try manually:"
|
|
696
|
-
echo -e " ${YELLOW}npm cache clean --force && npm install -g @kaitranntt/ccs@latest${RESET}"
|
|
697
|
-
echo ""
|
|
698
|
-
exit 1
|
|
699
|
-
fi
|
|
700
|
-
else
|
|
701
|
-
echo -e "${CYAN}Updating via installer...${RESET}"
|
|
702
|
-
echo ""
|
|
703
|
-
|
|
704
|
-
if curl -fsSL ccs.kaitran.ca/install | bash; then
|
|
705
|
-
echo ""
|
|
706
|
-
echo -e "${GREEN}[OK] Update successful!${RESET}"
|
|
707
|
-
echo ""
|
|
708
|
-
echo -e "Run ${YELLOW}ccs --version${RESET} to verify"
|
|
709
|
-
echo ""
|
|
710
|
-
exit 0
|
|
711
|
-
else
|
|
712
|
-
echo ""
|
|
713
|
-
echo -e "${RED}[X] Update failed${RESET}"
|
|
714
|
-
echo ""
|
|
715
|
-
echo "Try manually:"
|
|
716
|
-
echo -e " ${YELLOW}curl -fsSL ccs.kaitran.ca/install | bash${RESET}"
|
|
717
|
-
echo ""
|
|
718
|
-
exit 1
|
|
719
|
-
fi
|
|
720
|
-
fi
|
|
721
|
-
}
|
|
722
|
-
|
|
723
|
-
# --- Claude CLI Detection Logic ---
|
|
724
|
-
|
|
725
|
-
detect_claude_cli() {
|
|
726
|
-
echo "${CCS_CLAUDE_PATH:-claude}"
|
|
727
|
-
}
|
|
728
|
-
|
|
729
|
-
show_claude_not_found_error() {
|
|
730
|
-
msg_error "Claude CLI not found in PATH
|
|
731
|
-
|
|
732
|
-
CCS requires Claude CLI to be installed and available in your PATH.
|
|
733
|
-
|
|
734
|
-
Solutions:
|
|
735
|
-
1. Install Claude CLI:
|
|
736
|
-
https://docs.claude.com/en/docs/claude-code/installation
|
|
737
|
-
|
|
738
|
-
2. Verify installation:
|
|
739
|
-
command -v claude
|
|
740
|
-
|
|
741
|
-
3. If installed but not in PATH, add it:
|
|
742
|
-
# Find Claude installation
|
|
743
|
-
which claude
|
|
744
|
-
|
|
745
|
-
# Or set custom path
|
|
746
|
-
export CCS_CLAUDE_PATH='/path/to/claude'
|
|
747
|
-
|
|
748
|
-
Restart your terminal after installation."
|
|
749
|
-
}
|
|
750
|
-
|
|
751
|
-
show_version() {
|
|
752
|
-
# Title
|
|
753
|
-
echo -e "${BOLD}CCS (Claude Code Switch) v${CCS_VERSION}${RESET}"
|
|
754
|
-
echo ""
|
|
755
|
-
|
|
756
|
-
# Installation section with table-like formatting
|
|
757
|
-
echo -e "${CYAN}Installation:${RESET}"
|
|
758
|
-
|
|
759
|
-
# Location - prioritize script location over command location
|
|
760
|
-
local script_location="$(readlink -f "${BASH_SOURCE[0]}")"
|
|
761
|
-
local command_location=$(command -v ccs 2>/dev/null || echo "(not found)")
|
|
762
|
-
|
|
763
|
-
# Show script location if running from source
|
|
764
|
-
if [[ "$script_location" == *"ccs"* ]]; then
|
|
765
|
-
printf " ${CYAN}%-16s${RESET} %s\n" "Location:" "${script_location}"
|
|
766
|
-
else
|
|
767
|
-
printf " ${CYAN}%-16s${RESET} %s\n" "Location:" "${command_location}"
|
|
768
|
-
fi
|
|
769
|
-
|
|
770
|
-
# .ccs/ directory location
|
|
771
|
-
printf " ${CYAN}%-16s${RESET} %s\n" "CCS Directory:" "$HOME/.ccs/"
|
|
772
|
-
|
|
773
|
-
# Config path
|
|
774
|
-
printf " ${CYAN}%-16s${RESET} %s\n" "Config:" "${CONFIG_FILE}"
|
|
775
|
-
|
|
776
|
-
# Profiles.json location
|
|
777
|
-
printf " ${CYAN}%-16s${RESET} %s\n" "Profiles:" "${PROFILES_JSON}"
|
|
778
|
-
|
|
779
|
-
# Delegation status - check multiple indicators
|
|
780
|
-
local delegation_configured=false
|
|
781
|
-
local ready_profiles=()
|
|
782
|
-
|
|
783
|
-
# Check for delegation-sessions.json (primary indicator)
|
|
784
|
-
local delegation_sessions="$HOME/.ccs/delegation-sessions.json"
|
|
785
|
-
if [[ -f "$delegation_sessions" ]]; then
|
|
786
|
-
delegation_configured=true
|
|
787
|
-
fi
|
|
788
|
-
|
|
789
|
-
# Check for profiles with valid API keys (secondary indicator)
|
|
790
|
-
for profile in glm kimi; do
|
|
791
|
-
local settings_file="$HOME/.ccs/$profile.settings.json"
|
|
792
|
-
if [[ -f "$settings_file" ]]; then
|
|
793
|
-
# Check if API key is configured (not a placeholder)
|
|
794
|
-
local api_key=$(jq -r '.env.ANTHROPIC_AUTH_TOKEN // empty' "$settings_file" 2>/dev/null)
|
|
795
|
-
if [[ -n "$api_key" ]] && [[ ! "$api_key" =~ YOUR_.*_API_KEY_HERE ]] && [[ ! "$api_key" =~ sk-test.* ]]; then
|
|
796
|
-
ready_profiles+=("$profile")
|
|
797
|
-
delegation_configured=true
|
|
798
|
-
fi
|
|
799
|
-
fi
|
|
800
|
-
done
|
|
801
|
-
|
|
802
|
-
if $delegation_configured; then
|
|
803
|
-
printf " ${CYAN}%-16s${RESET} %s\n" "Delegation:" "Enabled"
|
|
804
|
-
else
|
|
805
|
-
printf " ${CYAN}%-16s${RESET} %s\n" "Delegation:" "Not configured"
|
|
806
|
-
fi
|
|
807
|
-
|
|
808
|
-
echo ""
|
|
809
|
-
|
|
810
|
-
# Ready Profiles section - make it more prominent
|
|
811
|
-
if [[ ${#ready_profiles[@]} -gt 0 ]]; then
|
|
812
|
-
echo -e "${CYAN}Delegation Ready:${RESET}"
|
|
813
|
-
echo " ${YELLOW}✓${RESET} ${ready_profiles[*]} profiles are ready for delegation"
|
|
814
|
-
echo ""
|
|
815
|
-
elif $delegation_configured; then
|
|
816
|
-
echo -e "${CYAN}Delegation Ready:${RESET}"
|
|
817
|
-
echo " ${YELLOW}!${RESET} Delegation configured but no valid API keys found"
|
|
818
|
-
echo ""
|
|
819
|
-
fi
|
|
820
|
-
|
|
821
|
-
# Documentation
|
|
822
|
-
echo -e "${CYAN}Documentation:${RESET} https://github.com/kaitranntt/ccs"
|
|
823
|
-
echo -e "${CYAN}License:${RESET} MIT"
|
|
824
|
-
echo ""
|
|
825
|
-
|
|
826
|
-
# Help hint
|
|
827
|
-
echo -e "${YELLOW}Run 'ccs --help' for usage information${RESET}"
|
|
828
|
-
}
|
|
829
|
-
|
|
830
|
-
# --- Profile Registry Functions (Phase 4) ---
|
|
831
|
-
|
|
832
|
-
# Initialize empty registry if missing
|
|
833
|
-
init_profiles_json() {
|
|
834
|
-
[[ -f "$PROFILES_JSON" ]] && return 0
|
|
835
|
-
|
|
836
|
-
local init_data='{
|
|
837
|
-
"version": "2.0.0",
|
|
838
|
-
"profiles": {},
|
|
839
|
-
"default": null
|
|
840
|
-
}'
|
|
841
|
-
|
|
842
|
-
echo "$init_data" > "$PROFILES_JSON"
|
|
843
|
-
chmod 0600 "$PROFILES_JSON"
|
|
844
|
-
}
|
|
845
|
-
|
|
846
|
-
# Read entire profiles.json
|
|
847
|
-
read_profiles_json() {
|
|
848
|
-
init_profiles_json
|
|
849
|
-
cat "$PROFILES_JSON"
|
|
850
|
-
}
|
|
851
|
-
|
|
852
|
-
# Write entire profiles.json (atomic)
|
|
853
|
-
write_profiles_json() {
|
|
854
|
-
local content="$1"
|
|
855
|
-
local temp_file="$PROFILES_JSON.tmp"
|
|
856
|
-
|
|
857
|
-
echo "$content" > "$temp_file" || {
|
|
858
|
-
msg_error "Failed to write profiles registry"
|
|
859
|
-
return 1
|
|
860
|
-
}
|
|
861
|
-
|
|
862
|
-
chmod 0600 "$temp_file"
|
|
863
|
-
mv "$temp_file" "$PROFILES_JSON" || {
|
|
864
|
-
rm -f "$temp_file"
|
|
865
|
-
msg_error "Failed to update profiles registry"
|
|
866
|
-
return 1
|
|
867
|
-
}
|
|
868
|
-
}
|
|
869
|
-
|
|
870
|
-
# Check if profile exists
|
|
871
|
-
profile_exists() {
|
|
872
|
-
local profile_name="$1"
|
|
873
|
-
init_profiles_json
|
|
874
|
-
|
|
875
|
-
local exists=$(jq -r ".profiles.\"$profile_name\" // empty" "$PROFILES_JSON")
|
|
876
|
-
[[ -n "$exists" ]]
|
|
877
|
-
}
|
|
878
|
-
|
|
879
|
-
# Create new profile
|
|
880
|
-
register_profile() {
|
|
881
|
-
local profile_name="$1"
|
|
882
|
-
local timestamp=$(date -u +"%Y-%m-%dT%H:%M:%S.000Z")
|
|
883
|
-
|
|
884
|
-
init_profiles_json
|
|
885
|
-
|
|
886
|
-
# Check if exists
|
|
887
|
-
profile_exists "$profile_name" && {
|
|
888
|
-
msg_error "Profile already exists: $profile_name"
|
|
889
|
-
return 1
|
|
890
|
-
}
|
|
891
|
-
|
|
892
|
-
# Read current data
|
|
893
|
-
local data=$(read_profiles_json)
|
|
894
|
-
|
|
895
|
-
# Add new profile
|
|
896
|
-
data=$(echo "$data" | jq \
|
|
897
|
-
--arg name "$profile_name" \
|
|
898
|
-
--arg timestamp "$timestamp" \
|
|
899
|
-
'.profiles[$name] = {
|
|
900
|
-
"type": "account",
|
|
901
|
-
"created": $timestamp,
|
|
902
|
-
"last_used": null
|
|
903
|
-
}')
|
|
904
|
-
|
|
905
|
-
# Note: No longer auto-set as default
|
|
906
|
-
# Users must explicitly run: ccs auth default <profile>
|
|
907
|
-
# Default always stays on implicit 'default' profile (uses ~/.claude/)
|
|
908
|
-
|
|
909
|
-
write_profiles_json "$data"
|
|
910
|
-
}
|
|
911
|
-
|
|
912
|
-
# Delete profile
|
|
913
|
-
unregister_profile() {
|
|
914
|
-
local profile_name="$1"
|
|
915
|
-
|
|
916
|
-
init_profiles_json
|
|
917
|
-
|
|
918
|
-
profile_exists "$profile_name" || return 0 # Idempotent
|
|
919
|
-
|
|
920
|
-
local data=$(read_profiles_json)
|
|
921
|
-
|
|
922
|
-
# Remove profile
|
|
923
|
-
data=$(echo "$data" | jq --arg name "$profile_name" 'del(.profiles[$name])')
|
|
924
|
-
|
|
925
|
-
# Update default if it was the deleted profile
|
|
926
|
-
local current_default=$(echo "$data" | jq -r '.default // empty')
|
|
927
|
-
if [[ "$current_default" == "$profile_name" ]]; then
|
|
928
|
-
# Set to first remaining profile or null
|
|
929
|
-
local first_profile=$(echo "$data" | jq -r '.profiles | keys[0] // empty')
|
|
930
|
-
data=$(echo "$data" | jq --arg first "$first_profile" '
|
|
931
|
-
.default = if $first != "" then $first else null end
|
|
932
|
-
')
|
|
933
|
-
fi
|
|
934
|
-
|
|
935
|
-
write_profiles_json "$data"
|
|
936
|
-
}
|
|
937
|
-
|
|
938
|
-
# Update last_used timestamp
|
|
939
|
-
touch_profile() {
|
|
940
|
-
local profile_name="$1"
|
|
941
|
-
local timestamp=$(date -u +"%Y-%m-%dT%H:%M:%S.000Z")
|
|
942
|
-
|
|
943
|
-
profile_exists "$profile_name" || return 0 # Silent fail if not exists
|
|
944
|
-
|
|
945
|
-
local data=$(read_profiles_json)
|
|
946
|
-
|
|
947
|
-
data=$(echo "$data" | jq \
|
|
948
|
-
--arg name "$profile_name" \
|
|
949
|
-
--arg timestamp "$timestamp" \
|
|
950
|
-
'.profiles[$name].last_used = $timestamp')
|
|
951
|
-
|
|
952
|
-
write_profiles_json "$data"
|
|
953
|
-
}
|
|
954
|
-
|
|
955
|
-
# Get default profile
|
|
956
|
-
get_default_profile() {
|
|
957
|
-
init_profiles_json
|
|
958
|
-
jq -r '.default // empty' "$PROFILES_JSON"
|
|
959
|
-
}
|
|
960
|
-
|
|
961
|
-
# Set default profile
|
|
962
|
-
set_default_profile() {
|
|
963
|
-
local profile_name="$1"
|
|
964
|
-
|
|
965
|
-
profile_exists "$profile_name" || {
|
|
966
|
-
msg_error "Profile not found: $profile_name"
|
|
967
|
-
return 1
|
|
968
|
-
}
|
|
969
|
-
|
|
970
|
-
local data=$(read_profiles_json)
|
|
971
|
-
data=$(echo "$data" | jq --arg name "$profile_name" '.default = $name')
|
|
972
|
-
|
|
973
|
-
write_profiles_json "$data"
|
|
974
|
-
}
|
|
975
|
-
|
|
976
|
-
# --- Instance Management Functions (Phase 2) ---
|
|
977
|
-
|
|
978
|
-
# Sanitize profile name for filesystem
|
|
979
|
-
sanitize_profile_name() {
|
|
980
|
-
local name="$1"
|
|
981
|
-
# Replace unsafe chars with dash, lowercase
|
|
982
|
-
echo "$name" | tr '[:upper:]' '[:lower:]' | sed 's/[^a-z0-9_-]/-/g'
|
|
983
|
-
}
|
|
984
|
-
|
|
985
|
-
# Link shared directories (Phase 1: Shared Global Data)
|
|
986
|
-
link_shared_directories() {
|
|
987
|
-
local instance_path="$1"
|
|
988
|
-
local shared_dir="$HOME/.ccs/shared"
|
|
989
|
-
|
|
990
|
-
# Ensure shared directories exist
|
|
991
|
-
mkdir -p "$shared_dir"/{commands,skills,agents,plugins}
|
|
992
|
-
|
|
993
|
-
# Create symlinks (remove existing first if present)
|
|
994
|
-
for dir in commands skills agents plugins; do
|
|
995
|
-
local link_path="$instance_path/$dir"
|
|
996
|
-
local target_path="$shared_dir/$dir"
|
|
997
|
-
|
|
998
|
-
# Remove existing directory/link
|
|
999
|
-
[[ -e "$link_path" ]] && rm -rf "$link_path"
|
|
1000
|
-
|
|
1001
|
-
# Create symlink
|
|
1002
|
-
ln -sf "$target_path" "$link_path"
|
|
1003
|
-
done
|
|
1004
|
-
}
|
|
1005
|
-
|
|
1006
|
-
# Migrate to shared structure (Phase 1: Auto-migration)
|
|
1007
|
-
migrate_to_shared_structure() {
|
|
1008
|
-
local shared_dir="$HOME/.ccs/shared"
|
|
1009
|
-
|
|
1010
|
-
# Check if migration is needed (shared dirs exist but are empty)
|
|
1011
|
-
if [[ -d "$shared_dir" ]]; then
|
|
1012
|
-
local needs_migration=false
|
|
1013
|
-
for dir in commands skills agents plugins; do
|
|
1014
|
-
if [[ ! -d "$shared_dir/$dir" ]] || [[ -z "$(ls -A "$shared_dir/$dir" 2>/dev/null)" ]]; then
|
|
1015
|
-
needs_migration=true
|
|
1016
|
-
break
|
|
1017
|
-
fi
|
|
1018
|
-
done
|
|
1019
|
-
|
|
1020
|
-
[[ "$needs_migration" == "false" ]] && return 0
|
|
1021
|
-
fi
|
|
1022
|
-
|
|
1023
|
-
# Create shared directory
|
|
1024
|
-
mkdir -p "$shared_dir"/{commands,skills,agents,plugins}
|
|
1025
|
-
|
|
1026
|
-
# Copy from ~/.claude/ (actual Claude CLI directory)
|
|
1027
|
-
local claude_dir="$HOME/.claude"
|
|
1028
|
-
|
|
1029
|
-
if [[ -d "$claude_dir" ]]; then
|
|
1030
|
-
# Copy commands to shared (if exists)
|
|
1031
|
-
[[ -d "$claude_dir/commands" ]] && \
|
|
1032
|
-
cp -r "$claude_dir/commands"/* "$shared_dir/commands/" 2>/dev/null || true
|
|
1033
|
-
|
|
1034
|
-
# Copy skills to shared (if exists)
|
|
1035
|
-
[[ -d "$claude_dir/skills" ]] && \
|
|
1036
|
-
cp -r "$claude_dir/skills"/* "$shared_dir/skills/" 2>/dev/null || true
|
|
1037
|
-
|
|
1038
|
-
# Copy agents to shared (if exists)
|
|
1039
|
-
[[ -d "$claude_dir/agents" ]] && \
|
|
1040
|
-
cp -r "$claude_dir/agents"/* "$shared_dir/agents/" 2>/dev/null || true
|
|
1041
|
-
|
|
1042
|
-
# Copy plugins to shared (if exists)
|
|
1043
|
-
[[ -d "$claude_dir/plugins" ]] && \
|
|
1044
|
-
cp -r "$claude_dir/plugins"/* "$shared_dir/plugins/" 2>/dev/null || true
|
|
1045
|
-
fi
|
|
1046
|
-
|
|
1047
|
-
# Update all instances to use symlinks
|
|
1048
|
-
for instance_path in "$INSTANCES_DIR"/*; do
|
|
1049
|
-
[[ -d "$instance_path" ]] || continue
|
|
1050
|
-
link_shared_directories "$instance_path"
|
|
1051
|
-
done
|
|
1052
|
-
|
|
1053
|
-
echo "[OK] Migrated to shared structure"
|
|
1054
|
-
}
|
|
1055
|
-
|
|
1056
|
-
# Initialize new instance directory
|
|
1057
|
-
initialize_instance() {
|
|
1058
|
-
local instance_path="$1"
|
|
1059
|
-
|
|
1060
|
-
# Create base directory
|
|
1061
|
-
mkdir -m 0700 -p "$instance_path"
|
|
1062
|
-
|
|
1063
|
-
# Create subdirectories (profile-specific only)
|
|
1064
|
-
local subdirs=(session-env todos logs file-history shell-snapshots debug .anthropic)
|
|
1065
|
-
for dir in "${subdirs[@]}"; do
|
|
1066
|
-
mkdir -m 0700 -p "$instance_path/$dir"
|
|
1067
|
-
done
|
|
1068
|
-
|
|
1069
|
-
# Symlink shared directories
|
|
1070
|
-
link_shared_directories "$instance_path"
|
|
1071
|
-
|
|
1072
|
-
# Copy global configs (optional)
|
|
1073
|
-
copy_global_configs "$instance_path"
|
|
1074
|
-
}
|
|
1075
|
-
|
|
1076
|
-
# Validate instance structure (auto-repair)
|
|
1077
|
-
validate_instance() {
|
|
1078
|
-
local instance_path="$1"
|
|
1079
|
-
local required_dirs=(session-env todos logs file-history shell-snapshots debug .anthropic)
|
|
1080
|
-
|
|
1081
|
-
for dir in "${required_dirs[@]}"; do
|
|
1082
|
-
if [[ ! -d "$instance_path/$dir" ]]; then
|
|
1083
|
-
mkdir -m 0700 -p "$instance_path/$dir"
|
|
1084
|
-
fi
|
|
1085
|
-
done
|
|
1086
|
-
}
|
|
1087
|
-
|
|
1088
|
-
# Copy global Claude configs to instance
|
|
1089
|
-
copy_global_configs() {
|
|
1090
|
-
local instance_path="$1"
|
|
1091
|
-
local global_claude="$HOME/.claude"
|
|
1092
|
-
|
|
1093
|
-
# Copy settings.json only (commands/skills are now symlinked to shared/)
|
|
1094
|
-
[[ -f "$global_claude/settings.json" ]] && \
|
|
1095
|
-
cp "$global_claude/settings.json" "$instance_path/settings.json" 2>/dev/null || true
|
|
1096
|
-
}
|
|
1097
|
-
|
|
1098
|
-
# Ensure instance exists (lazy initialization)
|
|
1099
|
-
ensure_instance() {
|
|
1100
|
-
local profile_name="$1"
|
|
1101
|
-
local safe_name=$(sanitize_profile_name "$profile_name")
|
|
1102
|
-
local instance_path="$INSTANCES_DIR/$safe_name"
|
|
1103
|
-
|
|
1104
|
-
# Create if missing
|
|
1105
|
-
if [[ ! -d "$instance_path" ]]; then
|
|
1106
|
-
initialize_instance "$instance_path"
|
|
1107
|
-
fi
|
|
1108
|
-
|
|
1109
|
-
# Validate structure
|
|
1110
|
-
validate_instance "$instance_path"
|
|
1111
|
-
|
|
1112
|
-
echo "$instance_path"
|
|
1113
|
-
}
|
|
1114
|
-
|
|
1115
|
-
# --- Profile Detection Logic (Phase 1) ---
|
|
1116
|
-
|
|
1117
|
-
# List available profiles for error messages
|
|
1118
|
-
# Get all profile names (for fuzzy matching)
|
|
1119
|
-
get_all_profile_names() {
|
|
1120
|
-
local names=()
|
|
1121
|
-
|
|
1122
|
-
# Settings-based profiles
|
|
1123
|
-
if [[ -f "$CONFIG_FILE" ]]; then
|
|
1124
|
-
while IFS= read -r name; do
|
|
1125
|
-
names+=("$name")
|
|
1126
|
-
done < <(jq -r '.profiles | keys[]' "$CONFIG_FILE" 2>/dev/null || true)
|
|
1127
|
-
fi
|
|
1128
|
-
|
|
1129
|
-
# Account-based profiles
|
|
1130
|
-
if [[ -f "$PROFILES_JSON" ]]; then
|
|
1131
|
-
while IFS= read -r name; do
|
|
1132
|
-
names+=("$name")
|
|
1133
|
-
done < <(jq -r '.profiles | keys[]' "$PROFILES_JSON" 2>/dev/null || true)
|
|
1134
|
-
fi
|
|
1135
|
-
|
|
1136
|
-
printf '%s\n' "${names[@]}"
|
|
1137
|
-
}
|
|
1138
|
-
|
|
1139
|
-
list_available_profiles() {
|
|
1140
|
-
local lines=()
|
|
1141
|
-
|
|
1142
|
-
# Settings-based profiles
|
|
1143
|
-
if [[ -f "$CONFIG_FILE" ]]; then
|
|
1144
|
-
local settings_profiles=$(jq -r '.profiles | keys[]' "$CONFIG_FILE" 2>/dev/null || true)
|
|
1145
|
-
if [[ -n "$settings_profiles" ]]; then
|
|
1146
|
-
lines+=("Settings-based profiles (GLM, Kimi, etc.):")
|
|
1147
|
-
while IFS= read -r name; do
|
|
1148
|
-
lines+=(" - $name")
|
|
1149
|
-
done <<< "$settings_profiles"
|
|
1150
|
-
fi
|
|
1151
|
-
fi
|
|
1152
|
-
|
|
1153
|
-
# Account-based profiles
|
|
1154
|
-
if [[ -f "$PROFILES_JSON" ]]; then
|
|
1155
|
-
local account_profiles=$(jq -r '.profiles | keys[]' "$PROFILES_JSON" 2>/dev/null || true)
|
|
1156
|
-
local default_profile=$(jq -r '.default // empty' "$PROFILES_JSON" 2>/dev/null || true)
|
|
1157
|
-
|
|
1158
|
-
if [[ -n "$account_profiles" ]]; then
|
|
1159
|
-
lines+=("Account-based profiles:")
|
|
1160
|
-
while IFS= read -r name; do
|
|
1161
|
-
local is_default=""
|
|
1162
|
-
[[ "$name" == "$default_profile" ]] && is_default=" [DEFAULT]"
|
|
1163
|
-
lines+=(" - $name$is_default")
|
|
1164
|
-
done <<< "$account_profiles"
|
|
1165
|
-
fi
|
|
1166
|
-
fi
|
|
1167
|
-
|
|
1168
|
-
if [[ ${#lines[@]} -eq 0 ]]; then
|
|
1169
|
-
echo " (no profiles configured)"
|
|
1170
|
-
echo " Run \"ccs auth create <profile>\" to create your first account profile."
|
|
1171
|
-
else
|
|
1172
|
-
printf '%s\n' "${lines[@]}"
|
|
1173
|
-
fi
|
|
1174
|
-
}
|
|
1175
|
-
|
|
1176
|
-
# Detect profile type and return info
|
|
1177
|
-
# Sets global variables: PROFILE_TYPE, PROFILE_PATH/INSTANCE_PATH
|
|
1178
|
-
detect_profile_type() {
|
|
1179
|
-
local profile_name="$1"
|
|
1180
|
-
|
|
1181
|
-
# Special case: 'default' resolves to default profile
|
|
1182
|
-
if [[ "$profile_name" == "default" ]]; then
|
|
1183
|
-
# Check account-based default first
|
|
1184
|
-
if [[ -f "$PROFILES_JSON" ]]; then
|
|
1185
|
-
local default_account=$(jq -r '.default // empty' "$PROFILES_JSON" 2>/dev/null || true)
|
|
1186
|
-
if [[ -n "$default_account" ]] && profile_exists "$default_account"; then
|
|
1187
|
-
PROFILE_TYPE="account"
|
|
1188
|
-
PROFILE_NAME="$default_account"
|
|
1189
|
-
return 0
|
|
1190
|
-
fi
|
|
1191
|
-
fi
|
|
1192
|
-
|
|
1193
|
-
# Check settings-based default
|
|
1194
|
-
if [[ -f "$CONFIG_FILE" ]]; then
|
|
1195
|
-
local default_settings=$(jq -r '.profiles.default // empty' "$CONFIG_FILE" 2>/dev/null || true)
|
|
1196
|
-
if [[ -n "$default_settings" ]]; then
|
|
1197
|
-
PROFILE_TYPE="settings"
|
|
1198
|
-
PROFILE_PATH="$default_settings"
|
|
1199
|
-
PROFILE_NAME="default"
|
|
1200
|
-
return 0
|
|
1201
|
-
fi
|
|
1202
|
-
fi
|
|
1203
|
-
|
|
1204
|
-
# No default configured, use Claude's defaults
|
|
1205
|
-
PROFILE_TYPE="default"
|
|
1206
|
-
PROFILE_NAME="default"
|
|
1207
|
-
return 0
|
|
1208
|
-
fi
|
|
1209
|
-
|
|
1210
|
-
# Priority 1: Check settings-based profiles (backward compatibility)
|
|
1211
|
-
if [[ -f "$CONFIG_FILE" ]]; then
|
|
1212
|
-
local settings_path=$(jq -r ".profiles.\"$profile_name\" // empty" "$CONFIG_FILE" 2>/dev/null || true)
|
|
1213
|
-
if [[ -n "$settings_path" ]]; then
|
|
1214
|
-
PROFILE_TYPE="settings"
|
|
1215
|
-
PROFILE_PATH="$settings_path"
|
|
1216
|
-
PROFILE_NAME="$profile_name"
|
|
1217
|
-
return 0
|
|
1218
|
-
fi
|
|
1219
|
-
fi
|
|
1220
|
-
|
|
1221
|
-
# Priority 2: Check account-based profiles
|
|
1222
|
-
if [[ -f "$PROFILES_JSON" ]] && profile_exists "$profile_name"; then
|
|
1223
|
-
PROFILE_TYPE="account"
|
|
1224
|
-
PROFILE_NAME="$profile_name"
|
|
1225
|
-
return 0
|
|
1226
|
-
fi
|
|
1227
|
-
|
|
1228
|
-
# Not found
|
|
1229
|
-
PROFILE_TYPE="error"
|
|
1230
|
-
return 1
|
|
1231
|
-
}
|
|
1232
|
-
|
|
1233
|
-
# --- Auth Commands (Phase 3) ---
|
|
1234
|
-
|
|
1235
|
-
auth_help() {
|
|
1236
|
-
echo -e "${BOLD}CCS Concurrent Account Management${RESET}"
|
|
1237
|
-
echo ""
|
|
1238
|
-
echo -e "${CYAN}Usage:${RESET}"
|
|
1239
|
-
echo -e " ${YELLOW}ccs auth${RESET} <command> [options]"
|
|
1240
|
-
echo ""
|
|
1241
|
-
echo -e "${CYAN}Commands:${RESET}"
|
|
1242
|
-
echo -e " ${YELLOW}create <profile>${RESET} Create new profile and login"
|
|
1243
|
-
echo -e " ${YELLOW}list${RESET} List all saved profiles"
|
|
1244
|
-
echo -e " ${YELLOW}show <profile>${RESET} Show profile details"
|
|
1245
|
-
echo -e " ${YELLOW}remove <profile>${RESET} Remove saved profile"
|
|
1246
|
-
echo -e " ${YELLOW}default <profile>${RESET} Set default profile"
|
|
1247
|
-
echo ""
|
|
1248
|
-
echo -e "${CYAN}Examples:${RESET}"
|
|
1249
|
-
echo -e " ${YELLOW}ccs auth create work${RESET} # Create & login to work profile"
|
|
1250
|
-
echo -e " ${YELLOW}ccs auth default work${RESET} # Set work as default"
|
|
1251
|
-
echo -e " ${YELLOW}ccs auth list${RESET} # List all profiles"
|
|
1252
|
-
echo -e " ${YELLOW}ccs work \"review code\"${RESET} # Use work profile"
|
|
1253
|
-
echo -e " ${YELLOW}ccs \"review code\"${RESET} # Use default profile"
|
|
1254
|
-
echo ""
|
|
1255
|
-
echo -e "${CYAN}Options:${RESET}"
|
|
1256
|
-
echo -e " ${YELLOW}--force${RESET} Allow overwriting existing profile (create)"
|
|
1257
|
-
echo -e " ${YELLOW}--yes, -y${RESET} Skip confirmation prompts (remove)"
|
|
1258
|
-
echo -e " ${YELLOW}--json${RESET} Output in JSON format (list, show)"
|
|
1259
|
-
echo -e " ${YELLOW}--verbose${RESET} Show additional details (list)"
|
|
1260
|
-
echo ""
|
|
1261
|
-
echo -e "${CYAN}Note:${RESET}"
|
|
1262
|
-
echo -e " By default, ${YELLOW}ccs${RESET} uses Claude CLI defaults from ~/.claude/"
|
|
1263
|
-
echo -e " Use ${YELLOW}ccs auth default <profile>${RESET} to change the default profile."
|
|
1264
|
-
echo ""
|
|
1265
|
-
}
|
|
1266
|
-
|
|
1267
|
-
auth_create() {
|
|
1268
|
-
local profile_name=""
|
|
1269
|
-
local force=false
|
|
1270
|
-
|
|
1271
|
-
# Parse arguments
|
|
1272
|
-
while [[ $# -gt 0 ]]; do
|
|
1273
|
-
case "$1" in
|
|
1274
|
-
--force) force=true ;;
|
|
1275
|
-
-*) msg_error "Unknown option: $1"; return 1 ;;
|
|
1276
|
-
*) profile_name="$1" ;;
|
|
1277
|
-
esac
|
|
1278
|
-
shift
|
|
1279
|
-
done
|
|
1280
|
-
|
|
1281
|
-
# Validate profile name
|
|
1282
|
-
[[ -z "$profile_name" ]] && {
|
|
1283
|
-
msg_error "Profile name is required"
|
|
1284
|
-
echo ""
|
|
1285
|
-
echo "Usage: ${YELLOW}ccs auth create <profile> [--force]${RESET}"
|
|
1286
|
-
return 1
|
|
1287
|
-
}
|
|
1288
|
-
|
|
1289
|
-
# Check if exists
|
|
1290
|
-
if ! $force && profile_exists "$profile_name"; then
|
|
1291
|
-
msg_error "Profile already exists: $profile_name"
|
|
1292
|
-
echo "Use ${YELLOW}--force${RESET} to overwrite"
|
|
1293
|
-
return 1
|
|
1294
|
-
fi
|
|
1295
|
-
|
|
1296
|
-
# Create instance
|
|
1297
|
-
echo "[i] Creating profile: $profile_name"
|
|
1298
|
-
local instance_path=$(ensure_instance "$profile_name")
|
|
1299
|
-
echo "[i] Instance directory: $instance_path"
|
|
1300
|
-
echo ""
|
|
1301
|
-
|
|
1302
|
-
# Register profile
|
|
1303
|
-
register_profile "$profile_name"
|
|
1304
|
-
|
|
1305
|
-
# Launch Claude for login
|
|
1306
|
-
echo -e "${YELLOW}[i] Starting Claude in isolated instance...${RESET}"
|
|
1307
|
-
echo -e "${YELLOW}[i] You will be prompted to login with your account.${RESET}"
|
|
1308
|
-
echo ""
|
|
1309
|
-
|
|
1310
|
-
CLAUDE_CONFIG_DIR="$instance_path" $(detect_claude_cli) || {
|
|
1311
|
-
msg_error "Login failed or cancelled"
|
|
1312
|
-
echo "To retry: ${YELLOW}ccs auth create $profile_name --force${RESET}"
|
|
1313
|
-
return 1
|
|
1314
|
-
}
|
|
1315
|
-
|
|
1316
|
-
echo ""
|
|
1317
|
-
echo -e "${GREEN}[OK] Profile created successfully${RESET}"
|
|
1318
|
-
echo ""
|
|
1319
|
-
echo " Profile: $profile_name"
|
|
1320
|
-
echo " Instance: $instance_path"
|
|
1321
|
-
echo ""
|
|
1322
|
-
echo "Usage:"
|
|
1323
|
-
echo " ${YELLOW}ccs $profile_name \"your prompt here\"${RESET} # Use this specific profile"
|
|
1324
|
-
echo ""
|
|
1325
|
-
echo "To set as default (so you can use just \"ccs\"):"
|
|
1326
|
-
echo " ${YELLOW}ccs auth default $profile_name${RESET}"
|
|
1327
|
-
echo ""
|
|
1328
|
-
}
|
|
1329
|
-
|
|
1330
|
-
auth_list() {
|
|
1331
|
-
local verbose=false
|
|
1332
|
-
local json=false
|
|
1333
|
-
|
|
1334
|
-
# Parse arguments
|
|
1335
|
-
while [[ $# -gt 0 ]]; do
|
|
1336
|
-
case "$1" in
|
|
1337
|
-
--verbose) verbose=true ;;
|
|
1338
|
-
--json) json=true ;;
|
|
1339
|
-
*) ;;
|
|
1340
|
-
esac
|
|
1341
|
-
shift
|
|
1342
|
-
done
|
|
1343
|
-
|
|
1344
|
-
# Read profiles.json
|
|
1345
|
-
[[ ! -f "$PROFILES_JSON" ]] && {
|
|
1346
|
-
if $json; then
|
|
1347
|
-
echo "{\"version\":\"$CCS_VERSION\",\"profiles\":[]}"
|
|
1348
|
-
return 0
|
|
1349
|
-
fi
|
|
1350
|
-
echo -e "${YELLOW}No account profiles found${RESET}"
|
|
1351
|
-
echo ""
|
|
1352
|
-
echo "To create your first profile:"
|
|
1353
|
-
echo " ${YELLOW}ccs auth create <profile>${RESET}"
|
|
1354
|
-
return 0
|
|
1355
|
-
}
|
|
1356
|
-
|
|
1357
|
-
local default_profile=$(jq -r '.default // empty' "$PROFILES_JSON" 2>/dev/null || true)
|
|
1358
|
-
|
|
1359
|
-
# JSON output mode
|
|
1360
|
-
if $json; then
|
|
1361
|
-
jq -n \
|
|
1362
|
-
--arg version "$CCS_VERSION" \
|
|
1363
|
-
--arg default "$default_profile" \
|
|
1364
|
-
--argjson data "$(cat "$PROFILES_JSON")" \
|
|
1365
|
-
'{
|
|
1366
|
-
version: $version,
|
|
1367
|
-
profiles: [
|
|
1368
|
-
$data.profiles | to_entries[] | {
|
|
1369
|
-
name: .key,
|
|
1370
|
-
type: (.value.type // "account"),
|
|
1371
|
-
is_default: (.key == $default),
|
|
1372
|
-
created: .value.created,
|
|
1373
|
-
last_used: (.value.last_used // null),
|
|
1374
|
-
instance_path: ($ENV.INSTANCES_DIR + "/" + .key)
|
|
1375
|
-
}
|
|
1376
|
-
]
|
|
1377
|
-
}'
|
|
1378
|
-
return 0
|
|
1379
|
-
fi
|
|
1380
|
-
|
|
1381
|
-
# Human-readable output
|
|
1382
|
-
local profiles=$(jq -r '.profiles | keys[]' "$PROFILES_JSON" 2>/dev/null || true)
|
|
1383
|
-
|
|
1384
|
-
[[ -z "$profiles" ]] && {
|
|
1385
|
-
echo -e "${YELLOW}No account profiles found${RESET}"
|
|
1386
|
-
return 0
|
|
1387
|
-
}
|
|
1388
|
-
|
|
1389
|
-
echo -e "${BOLD}Saved Account Profiles:${RESET}"
|
|
1390
|
-
echo ""
|
|
1391
|
-
|
|
1392
|
-
# Display profiles
|
|
1393
|
-
while IFS= read -r profile; do
|
|
1394
|
-
local is_default=false
|
|
1395
|
-
[[ "$profile" == "$default_profile" ]] && is_default=true
|
|
1396
|
-
|
|
1397
|
-
if $is_default; then
|
|
1398
|
-
echo -e "${GREEN}[*] ${CYAN}$profile${GREEN} (default)${RESET}"
|
|
1399
|
-
else
|
|
1400
|
-
echo -e "[ ] ${CYAN}$profile${RESET}"
|
|
1401
|
-
fi
|
|
1402
|
-
|
|
1403
|
-
local type=$(jq -r ".profiles.\"$profile\".type // \"account\"" "$PROFILES_JSON" 2>/dev/null || true)
|
|
1404
|
-
echo " Type: $type"
|
|
1405
|
-
|
|
1406
|
-
if $verbose; then
|
|
1407
|
-
local created=$(jq -r ".profiles.\"$profile\".created" "$PROFILES_JSON" 2>/dev/null || true)
|
|
1408
|
-
local last_used=$(jq -r ".profiles.\"$profile\".last_used // \"Never\"" "$PROFILES_JSON" 2>/dev/null || true)
|
|
1409
|
-
echo " Created: $created"
|
|
1410
|
-
echo " Last used: $last_used"
|
|
1411
|
-
fi
|
|
1412
|
-
|
|
1413
|
-
echo ""
|
|
1414
|
-
done <<< "$profiles"
|
|
1415
|
-
}
|
|
1416
|
-
|
|
1417
|
-
auth_show() {
|
|
1418
|
-
local profile_name=""
|
|
1419
|
-
local json=false
|
|
1420
|
-
|
|
1421
|
-
# Parse arguments
|
|
1422
|
-
while [[ $# -gt 0 ]]; do
|
|
1423
|
-
case "$1" in
|
|
1424
|
-
--json) json=true ;;
|
|
1425
|
-
-*) msg_error "Unknown option: $1"; return 1 ;;
|
|
1426
|
-
*) profile_name="$1" ;;
|
|
1427
|
-
esac
|
|
1428
|
-
shift
|
|
1429
|
-
done
|
|
1430
|
-
|
|
1431
|
-
[[ -z "$profile_name" ]] && {
|
|
1432
|
-
msg_error "Profile name is required"
|
|
1433
|
-
echo "Usage: ${YELLOW}ccs auth show <profile> [--json]${RESET}"
|
|
1434
|
-
return 1
|
|
1435
|
-
}
|
|
1436
|
-
|
|
1437
|
-
# Check if exists
|
|
1438
|
-
profile_exists "$profile_name" || {
|
|
1439
|
-
msg_error "Profile not found: $profile_name"
|
|
1440
|
-
return 1
|
|
1441
|
-
}
|
|
1442
|
-
|
|
1443
|
-
local default_profile=$(jq -r '.default // empty' "$PROFILES_JSON" 2>/dev/null || true)
|
|
1444
|
-
local is_default=false
|
|
1445
|
-
[[ "$profile_name" == "$default_profile" ]] && is_default=true
|
|
1446
|
-
|
|
1447
|
-
local type=$(jq -r ".profiles.\"$profile_name\".type // \"account\"" "$PROFILES_JSON" 2>/dev/null || true)
|
|
1448
|
-
local created=$(jq -r ".profiles.\"$profile_name\".created" "$PROFILES_JSON" 2>/dev/null || true)
|
|
1449
|
-
local last_used=$(jq -r ".profiles.\"$profile_name\".last_used // null" "$PROFILES_JSON" 2>/dev/null || true)
|
|
1450
|
-
local instance_path="$INSTANCES_DIR/$(sanitize_profile_name "$profile_name")"
|
|
1451
|
-
|
|
1452
|
-
# Count sessions
|
|
1453
|
-
local session_count=0
|
|
1454
|
-
if [[ -d "$instance_path/session-env" ]]; then
|
|
1455
|
-
session_count=$(find "$instance_path/session-env" -name "*.json" 2>/dev/null | wc -l | tr -d ' ')
|
|
1456
|
-
fi
|
|
1457
|
-
|
|
1458
|
-
# JSON output mode
|
|
1459
|
-
if $json; then
|
|
1460
|
-
jq -n \
|
|
1461
|
-
--arg name "$profile_name" \
|
|
1462
|
-
--arg type "$type" \
|
|
1463
|
-
--argjson is_default "$is_default" \
|
|
1464
|
-
--arg created "$created" \
|
|
1465
|
-
--arg last_used "$last_used" \
|
|
1466
|
-
--arg instance_path "$instance_path" \
|
|
1467
|
-
--argjson session_count "$session_count" \
|
|
1468
|
-
'{
|
|
1469
|
-
name: $name,
|
|
1470
|
-
type: $type,
|
|
1471
|
-
is_default: $is_default,
|
|
1472
|
-
created: $created,
|
|
1473
|
-
last_used: $last_used,
|
|
1474
|
-
instance_path: $instance_path,
|
|
1475
|
-
session_count: $session_count
|
|
1476
|
-
}'
|
|
1477
|
-
return 0
|
|
1478
|
-
fi
|
|
1479
|
-
|
|
1480
|
-
# Human-readable output
|
|
1481
|
-
echo -e "${BOLD}Profile: $profile_name${RESET}"
|
|
1482
|
-
echo ""
|
|
1483
|
-
|
|
1484
|
-
echo " Type: $type"
|
|
1485
|
-
echo " Default: $($is_default && echo "Yes" || echo "No")"
|
|
1486
|
-
echo " Instance: $instance_path"
|
|
1487
|
-
echo " Created: $created"
|
|
1488
|
-
[[ "$last_used" != "null" ]] && echo " Last used: $last_used" || echo " Last used: Never"
|
|
1489
|
-
echo ""
|
|
1490
|
-
}
|
|
1491
|
-
|
|
1492
|
-
auth_remove() {
|
|
1493
|
-
local profile_name=""
|
|
1494
|
-
|
|
1495
|
-
# Parse arguments
|
|
1496
|
-
while [[ $# -gt 0 ]]; do
|
|
1497
|
-
case "$1" in
|
|
1498
|
-
--yes|-y) export CCS_YES=1 ;; # Auto-confirm (export for confirm_action)
|
|
1499
|
-
-*) msg_error "Unknown option: $1"; return 1 ;;
|
|
1500
|
-
*) profile_name="$1" ;;
|
|
1501
|
-
esac
|
|
1502
|
-
shift
|
|
1503
|
-
done
|
|
1504
|
-
|
|
1505
|
-
[[ -z "$profile_name" ]] && {
|
|
1506
|
-
msg_error "Profile name is required"
|
|
1507
|
-
echo ""
|
|
1508
|
-
echo "Usage: ${YELLOW}ccs auth remove <profile> [--yes]${RESET}"
|
|
1509
|
-
return 1
|
|
1510
|
-
}
|
|
1511
|
-
|
|
1512
|
-
profile_exists "$profile_name" || {
|
|
1513
|
-
msg_error "Profile not found: $profile_name"
|
|
1514
|
-
return 1
|
|
1515
|
-
}
|
|
1516
|
-
|
|
1517
|
-
# Get instance path and session count for impact display
|
|
1518
|
-
local instance_path="$INSTANCES_DIR/$(sanitize_profile_name "$profile_name")"
|
|
1519
|
-
local session_count=0
|
|
1520
|
-
|
|
1521
|
-
if [[ -d "$instance_path/session-env" ]]; then
|
|
1522
|
-
session_count=$(find "$instance_path/session-env" -name "*.json" 2>/dev/null | wc -l | tr -d ' ')
|
|
1523
|
-
fi
|
|
1524
|
-
|
|
1525
|
-
# Display impact
|
|
1526
|
-
echo ""
|
|
1527
|
-
echo "Profile '${CYAN}$profile_name${RESET}' will be permanently deleted."
|
|
1528
|
-
echo " Instance path: $instance_path"
|
|
1529
|
-
echo " Sessions: $session_count conversation$([ "$session_count" -ne 1 ] && echo "s" || echo "")"
|
|
1530
|
-
echo ""
|
|
1531
|
-
|
|
1532
|
-
# Interactive confirmation (or --yes flag)
|
|
1533
|
-
if ! confirm_action "Delete this profile?" "no"; then
|
|
1534
|
-
echo "[i] Cancelled"
|
|
1535
|
-
return 0
|
|
1536
|
-
fi
|
|
1537
|
-
|
|
1538
|
-
# Delete instance directory
|
|
1539
|
-
rm -rf "$instance_path"
|
|
1540
|
-
|
|
1541
|
-
# Remove from registry
|
|
1542
|
-
unregister_profile "$profile_name"
|
|
1543
|
-
|
|
1544
|
-
echo -e "${GREEN}[OK] Profile removed successfully${RESET}"
|
|
1545
|
-
echo " Profile: $profile_name"
|
|
1546
|
-
echo ""
|
|
1547
|
-
}
|
|
1548
|
-
|
|
1549
|
-
auth_default() {
|
|
1550
|
-
local profile_name="${1:-}"
|
|
1551
|
-
|
|
1552
|
-
[[ -z "$profile_name" ]] && {
|
|
1553
|
-
msg_error "Profile name is required"
|
|
1554
|
-
echo "Usage: ${YELLOW}ccs auth default <profile>${RESET}"
|
|
1555
|
-
return 1
|
|
1556
|
-
}
|
|
1557
|
-
|
|
1558
|
-
profile_exists "$profile_name" || {
|
|
1559
|
-
msg_error "Profile not found: $profile_name"
|
|
1560
|
-
return 1
|
|
1561
|
-
}
|
|
1562
|
-
|
|
1563
|
-
set_default_profile "$profile_name"
|
|
1564
|
-
|
|
1565
|
-
echo -e "${GREEN}[OK] Default profile set${RESET}"
|
|
1566
|
-
echo " Profile: $profile_name"
|
|
1567
|
-
echo ""
|
|
1568
|
-
echo "Now you can use:"
|
|
1569
|
-
echo " ${YELLOW}ccs \"your prompt\"${RESET} # Uses $profile_name profile"
|
|
1570
|
-
echo ""
|
|
1571
|
-
}
|
|
1572
|
-
|
|
1573
|
-
handle_auth_commands() {
|
|
1574
|
-
shift # Remove 'auth'
|
|
1575
|
-
|
|
1576
|
-
local subcommand="${1:-}"
|
|
1577
|
-
shift || true
|
|
1578
|
-
|
|
1579
|
-
case "$subcommand" in
|
|
1580
|
-
create) auth_create "$@" ;;
|
|
1581
|
-
list) auth_list "$@" ;;
|
|
1582
|
-
show) auth_show "$@" ;;
|
|
1583
|
-
remove) auth_remove "$@" ;;
|
|
1584
|
-
default) auth_default "$@" ;;
|
|
1585
|
-
*) auth_help ;;
|
|
1586
|
-
esac
|
|
1587
|
-
}
|
|
1588
|
-
|
|
1589
|
-
# --- Shell Completion Installer ---
|
|
1590
|
-
|
|
1591
|
-
install_shell_completion() {
|
|
1592
|
-
shift # Remove --shell-completion
|
|
1593
|
-
|
|
1594
|
-
echo -e "${BOLD}Shell Completion Installer${RESET}"
|
|
1595
|
-
echo ""
|
|
1596
|
-
|
|
1597
|
-
# Parse flags for manual shell selection
|
|
1598
|
-
local target_shell=""
|
|
1599
|
-
for arg in "$@"; do
|
|
1600
|
-
case "$arg" in
|
|
1601
|
-
--bash) target_shell="bash" ;;
|
|
1602
|
-
--zsh) target_shell="zsh" ;;
|
|
1603
|
-
--fish) target_shell="fish" ;;
|
|
1604
|
-
*) ;;
|
|
1605
|
-
esac
|
|
1606
|
-
done
|
|
1607
|
-
|
|
1608
|
-
# Auto-detect shell if not specified
|
|
1609
|
-
if [[ -z "$target_shell" ]]; then
|
|
1610
|
-
if [[ -n "$BASH_VERSION" ]]; then
|
|
1611
|
-
target_shell="bash"
|
|
1612
|
-
elif [[ -n "$ZSH_VERSION" ]]; then
|
|
1613
|
-
target_shell="zsh"
|
|
1614
|
-
elif [[ -n "$FISH_VERSION" ]]; then
|
|
1615
|
-
target_shell="fish"
|
|
1616
|
-
else
|
|
1617
|
-
echo -e "${RED}[X] Could not detect shell${RESET}" >&2
|
|
1618
|
-
echo "" >&2
|
|
1619
|
-
echo -e "${YELLOW}Usage:${RESET}" >&2
|
|
1620
|
-
echo " ccs --shell-completion # Auto-detect shell" >&2
|
|
1621
|
-
echo " ccs --shell-completion --bash # Install for bash" >&2
|
|
1622
|
-
echo " ccs --shell-completion --zsh # Install for zsh" >&2
|
|
1623
|
-
echo " ccs --shell-completion --fish # Install for fish" >&2
|
|
1624
|
-
echo "" >&2
|
|
1625
|
-
return 1
|
|
1626
|
-
fi
|
|
1627
|
-
fi
|
|
1628
|
-
|
|
1629
|
-
# Ensure completion files exist in ~/.ccs/completions/
|
|
1630
|
-
local completions_dir="$HOME/.ccs/completions"
|
|
1631
|
-
if [[ ! -d "$completions_dir" ]]; then
|
|
1632
|
-
mkdir -p "$completions_dir"
|
|
1633
|
-
fi
|
|
1634
|
-
|
|
1635
|
-
# Copy from scripts if not present
|
|
1636
|
-
local script_dir="$(dirname "$0")"
|
|
1637
|
-
if [[ -f "$script_dir/../scripts/completion/ccs.bash" ]]; then
|
|
1638
|
-
cp "$script_dir/../scripts/completion/ccs.bash" "$completions_dir/" 2>/dev/null || true
|
|
1639
|
-
cp "$script_dir/../scripts/completion/ccs.zsh" "$completions_dir/" 2>/dev/null || true
|
|
1640
|
-
cp "$script_dir/../scripts/completion/ccs.fish" "$completions_dir/" 2>/dev/null || true
|
|
1641
|
-
fi
|
|
1642
|
-
|
|
1643
|
-
# Install based on target shell
|
|
1644
|
-
case "$target_shell" in
|
|
1645
|
-
bash)
|
|
1646
|
-
local rc_file="$HOME/.bashrc"
|
|
1647
|
-
local completion_file="$completions_dir/ccs.bash"
|
|
1648
|
-
local marker="# CCS shell completion"
|
|
1649
|
-
|
|
1650
|
-
if [[ ! -f "$completion_file" ]]; then
|
|
1651
|
-
echo -e "${RED}[X] Completion file not found: $completion_file${RESET}" >&2
|
|
1652
|
-
echo " Please reinstall CCS." >&2
|
|
1653
|
-
return 1
|
|
1654
|
-
fi
|
|
1655
|
-
|
|
1656
|
-
# Check if already installed
|
|
1657
|
-
if grep -q "$marker" "$rc_file" 2>/dev/null; then
|
|
1658
|
-
echo -e "${GREEN}[OK] Shell completion already installed${RESET}"
|
|
1659
|
-
echo ""
|
|
1660
|
-
return 0
|
|
1661
|
-
fi
|
|
1662
|
-
|
|
1663
|
-
# Append to .bashrc
|
|
1664
|
-
{
|
|
1665
|
-
echo ""
|
|
1666
|
-
echo "$marker"
|
|
1667
|
-
echo "source \"$completion_file\""
|
|
1668
|
-
} >> "$rc_file"
|
|
1669
|
-
|
|
1670
|
-
echo -e "${GREEN}[OK] Shell completion installed successfully!${RESET}"
|
|
1671
|
-
echo ""
|
|
1672
|
-
echo "Added to $rc_file"
|
|
1673
|
-
echo ""
|
|
1674
|
-
echo -e "${CYAN}To activate:${RESET}"
|
|
1675
|
-
echo " source ~/.bashrc"
|
|
1676
|
-
echo ""
|
|
1677
|
-
echo -e "${CYAN}Then test:${RESET}"
|
|
1678
|
-
echo " ccs <TAB> # See available profiles"
|
|
1679
|
-
echo " ccs auth <TAB> # See auth subcommands"
|
|
1680
|
-
echo ""
|
|
1681
|
-
;;
|
|
1682
|
-
|
|
1683
|
-
zsh)
|
|
1684
|
-
local rc_file="$HOME/.zshrc"
|
|
1685
|
-
local completion_dir="$HOME/.zsh/completion"
|
|
1686
|
-
local completion_file="$completions_dir/ccs.zsh"
|
|
1687
|
-
local marker="# CCS shell completion"
|
|
1688
|
-
|
|
1689
|
-
if [[ ! -f "$completion_file" ]]; then
|
|
1690
|
-
echo -e "${RED}[X] Completion file not found: $completion_file${RESET}" >&2
|
|
1691
|
-
echo " Please reinstall CCS." >&2
|
|
1692
|
-
return 1
|
|
1693
|
-
fi
|
|
1694
|
-
|
|
1695
|
-
# Create zsh completion directory
|
|
1696
|
-
mkdir -p "$completion_dir"
|
|
1697
|
-
|
|
1698
|
-
# Copy to zsh completion directory
|
|
1699
|
-
cp "$completion_file" "$completion_dir/_ccs"
|
|
1700
|
-
|
|
1701
|
-
# Check if already installed
|
|
1702
|
-
if grep -q "$marker" "$rc_file" 2>/dev/null; then
|
|
1703
|
-
echo -e "${GREEN}[OK] Shell completion already installed${RESET}"
|
|
1704
|
-
echo ""
|
|
1705
|
-
return 0
|
|
1706
|
-
fi
|
|
1707
|
-
|
|
1708
|
-
# Append to .zshrc
|
|
1709
|
-
{
|
|
1710
|
-
echo ""
|
|
1711
|
-
echo "$marker"
|
|
1712
|
-
echo "fpath=(~/.zsh/completion \$fpath)"
|
|
1713
|
-
echo "autoload -Uz compinit && compinit"
|
|
1714
|
-
} >> "$rc_file"
|
|
1715
|
-
|
|
1716
|
-
echo -e "${GREEN}[OK] Shell completion installed successfully!${RESET}"
|
|
1717
|
-
echo ""
|
|
1718
|
-
echo "Added to $rc_file"
|
|
1719
|
-
echo ""
|
|
1720
|
-
echo -e "${CYAN}To activate:${RESET}"
|
|
1721
|
-
echo " source ~/.zshrc"
|
|
1722
|
-
echo ""
|
|
1723
|
-
echo -e "${CYAN}Then test:${RESET}"
|
|
1724
|
-
echo " ccs <TAB> # See available profiles"
|
|
1725
|
-
echo " ccs auth <TAB> # See auth subcommands"
|
|
1726
|
-
echo ""
|
|
1727
|
-
;;
|
|
1728
|
-
|
|
1729
|
-
fish)
|
|
1730
|
-
local fish_dir="$HOME/.config/fish/completions"
|
|
1731
|
-
local completion_file="$completions_dir/ccs.fish"
|
|
1732
|
-
|
|
1733
|
-
if [[ ! -f "$completion_file" ]]; then
|
|
1734
|
-
echo -e "${RED}[X] Completion file not found: $completion_file${RESET}" >&2
|
|
1735
|
-
echo " Please reinstall CCS." >&2
|
|
1736
|
-
return 1
|
|
1737
|
-
fi
|
|
1738
|
-
|
|
1739
|
-
# Create fish completion directory
|
|
1740
|
-
mkdir -p "$fish_dir"
|
|
1741
|
-
|
|
1742
|
-
# Copy to fish completion directory (fish auto-loads from here)
|
|
1743
|
-
cp "$completion_file" "$fish_dir/ccs.fish"
|
|
1744
|
-
|
|
1745
|
-
echo -e "${GREEN}[OK] Shell completion installed successfully!${RESET}"
|
|
1746
|
-
echo ""
|
|
1747
|
-
echo "Installed to $fish_dir/ccs.fish"
|
|
1748
|
-
echo ""
|
|
1749
|
-
echo -e "${CYAN}To activate:${RESET}"
|
|
1750
|
-
echo " Fish auto-loads completions (no reload needed)"
|
|
1751
|
-
echo ""
|
|
1752
|
-
echo -e "${CYAN}Then test:${RESET}"
|
|
1753
|
-
echo " ccs <TAB> # See available profiles"
|
|
1754
|
-
echo " ccs auth <TAB> # See auth subcommands"
|
|
1755
|
-
echo ""
|
|
1756
|
-
;;
|
|
1757
|
-
|
|
1758
|
-
*)
|
|
1759
|
-
echo -e "${RED}[X] Unsupported shell: $target_shell${RESET}" >&2
|
|
1760
|
-
return 1
|
|
1761
|
-
;;
|
|
1762
|
-
esac
|
|
1763
|
-
|
|
1764
|
-
return 0
|
|
1765
|
-
}
|
|
1766
|
-
|
|
1767
|
-
# --- Main Execution Logic ---
|
|
1768
|
-
|
|
1769
|
-
# Special case: version command (check BEFORE profile detection)
|
|
1770
|
-
if [[ $# -gt 0 ]] && [[ "${1}" == "version" || "${1}" == "--version" || "${1}" == "-v" ]]; then
|
|
1771
|
-
show_version
|
|
1772
|
-
exit 0
|
|
1773
|
-
fi
|
|
1774
|
-
|
|
1775
|
-
# Special case: help command (check BEFORE profile detection)
|
|
1776
|
-
if [[ $# -gt 0 ]] && [[ "${1}" == "--help" || "${1}" == "-h" || "${1}" == "help" ]]; then
|
|
1777
|
-
show_help
|
|
1778
|
-
exit 0
|
|
1779
23
|
fi
|
|
1780
24
|
|
|
1781
|
-
#
|
|
1782
|
-
if
|
|
1783
|
-
|
|
1784
|
-
|
|
25
|
+
# Check npm/npx available
|
|
26
|
+
if ! command -v npx &>/dev/null; then
|
|
27
|
+
echo "[X] npx not found (requires npm 5.2+)" >&2
|
|
28
|
+
exit 127
|
|
1785
29
|
fi
|
|
1786
30
|
|
|
1787
|
-
#
|
|
1788
|
-
|
|
1789
|
-
install_shell_completion "$@"
|
|
1790
|
-
exit $?
|
|
1791
|
-
fi
|
|
1792
|
-
|
|
1793
|
-
# Special case: doctor command
|
|
1794
|
-
if [[ $# -gt 0 ]] && [[ "${1}" == "doctor" || "${1}" == "--doctor" ]]; then
|
|
1795
|
-
doctor_run
|
|
1796
|
-
exit $?
|
|
1797
|
-
fi
|
|
1798
|
-
|
|
1799
|
-
# Special case: sync command
|
|
1800
|
-
if [[ $# -gt 0 ]] && [[ "${1}" == "sync" || "${1}" == "--sync" ]]; then
|
|
1801
|
-
sync_run
|
|
1802
|
-
exit $?
|
|
1803
|
-
fi
|
|
1804
|
-
|
|
1805
|
-
# Special case: update command
|
|
1806
|
-
if [[ $# -gt 0 ]] && [[ "${1}" == "update" || "${1}" == "--update" ]]; then
|
|
1807
|
-
update_run
|
|
1808
|
-
exit $?
|
|
1809
|
-
fi
|
|
1810
|
-
|
|
1811
|
-
# Run auto-recovery before main logic
|
|
1812
|
-
auto_recover || {
|
|
1813
|
-
msg_error "Auto-recovery failed. Check permissions."
|
|
1814
|
-
exit 1
|
|
1815
|
-
}
|
|
1816
|
-
|
|
1817
|
-
# Smart profile detection: if first arg starts with '-', it's a flag not a profile
|
|
1818
|
-
if [[ $# -eq 0 ]] || [[ "${1}" =~ ^- ]]; then
|
|
1819
|
-
# No args or first arg is a flag → use default profile
|
|
1820
|
-
PROFILE="default"
|
|
1821
|
-
else
|
|
1822
|
-
# First arg doesn't start with '-' → treat as profile name
|
|
1823
|
-
PROFILE="${1}"
|
|
1824
|
-
fi
|
|
1825
|
-
|
|
1826
|
-
# Validate profile name (alphanumeric, dash, underscore only)
|
|
1827
|
-
if [[ "$PROFILE" =~ [^a-zA-Z0-9_-] ]]; then
|
|
1828
|
-
msg_error "Invalid profile name: $PROFILE
|
|
1829
|
-
|
|
1830
|
-
Use only alphanumeric characters, dash, or underscore."
|
|
1831
|
-
exit 1
|
|
1832
|
-
fi
|
|
1833
|
-
|
|
1834
|
-
# Detect profile type
|
|
1835
|
-
if ! detect_profile_type "$PROFILE"; then
|
|
1836
|
-
# Get suggestions using fuzzy matching
|
|
1837
|
-
mapfile -t all_profiles < <(get_all_profile_names)
|
|
1838
|
-
mapfile -t suggestions < <(find_similar_strings "$PROFILE" "${all_profiles[@]}")
|
|
1839
|
-
|
|
1840
|
-
echo "" >&2
|
|
1841
|
-
echo -e "${RED}[X] Profile '$PROFILE' not found${RESET}" >&2
|
|
1842
|
-
echo "" >&2
|
|
1843
|
-
|
|
1844
|
-
# Show suggestions if any
|
|
1845
|
-
if [[ ${#suggestions[@]} -gt 0 ]]; then
|
|
1846
|
-
echo -e "${YELLOW}Did you mean:${RESET}" >&2
|
|
1847
|
-
for suggestion in "${suggestions[@]}"; do
|
|
1848
|
-
echo " $suggestion" >&2
|
|
1849
|
-
done
|
|
1850
|
-
echo "" >&2
|
|
1851
|
-
fi
|
|
1852
|
-
|
|
1853
|
-
echo -e "${CYAN}Available profiles:${RESET}" >&2
|
|
1854
|
-
list_available_profiles >&2
|
|
1855
|
-
echo "" >&2
|
|
1856
|
-
echo -e "${YELLOW}Solutions:${RESET}" >&2
|
|
1857
|
-
echo " # Use existing profile" >&2
|
|
1858
|
-
echo " ccs <profile> \"your prompt\"" >&2
|
|
1859
|
-
echo "" >&2
|
|
1860
|
-
echo " # Create new account profile" >&2
|
|
1861
|
-
echo " ccs auth create <name>" >&2
|
|
1862
|
-
echo "" >&2
|
|
1863
|
-
echo -e "${YELLOW}Error: $E_PROFILE_NOT_FOUND${RESET}" >&2
|
|
1864
|
-
echo -e "${YELLOW}$(get_error_doc_url "$E_PROFILE_NOT_FOUND")${RESET}" >&2
|
|
1865
|
-
echo "" >&2
|
|
1866
|
-
exit 1
|
|
1867
|
-
fi
|
|
1868
|
-
|
|
1869
|
-
# Shift profile arg only if first arg was NOT a flag
|
|
1870
|
-
if [[ $# -gt 0 ]] && [[ ! "${1}" =~ ^- ]]; then
|
|
1871
|
-
shift
|
|
1872
|
-
fi
|
|
1873
|
-
|
|
1874
|
-
# Detect Claude CLI executable
|
|
1875
|
-
CLAUDE_CLI=$(detect_claude_cli)
|
|
1876
|
-
|
|
1877
|
-
# Execute based on profile type (Phase 5)
|
|
1878
|
-
case "$PROFILE_TYPE" in
|
|
1879
|
-
account)
|
|
1880
|
-
# Account-based profile: use CLAUDE_CONFIG_DIR
|
|
1881
|
-
INSTANCE_PATH=$(ensure_instance "$PROFILE_NAME")
|
|
1882
|
-
touch_profile "$PROFILE_NAME" # Update last_used
|
|
1883
|
-
|
|
1884
|
-
# Execute Claude with isolated config
|
|
1885
|
-
CLAUDE_CONFIG_DIR="$INSTANCE_PATH" exec "$CLAUDE_CLI" "$@" || {
|
|
1886
|
-
show_claude_not_found_error
|
|
1887
|
-
exit 1
|
|
1888
|
-
}
|
|
1889
|
-
;;
|
|
1890
|
-
|
|
1891
|
-
settings)
|
|
1892
|
-
# Settings-based profile: use --settings flag
|
|
1893
|
-
SETTINGS_PATH="${PROFILE_PATH/#\~/$HOME}"
|
|
1894
|
-
|
|
1895
|
-
[[ ! -f "$SETTINGS_PATH" ]] && {
|
|
1896
|
-
msg_error "Settings file not found: $SETTINGS_PATH"
|
|
1897
|
-
exit 1
|
|
1898
|
-
}
|
|
1899
|
-
|
|
1900
|
-
exec "$CLAUDE_CLI" --settings "$SETTINGS_PATH" "$@" || {
|
|
1901
|
-
show_claude_not_found_error
|
|
1902
|
-
exit 1
|
|
1903
|
-
}
|
|
1904
|
-
;;
|
|
1905
|
-
|
|
1906
|
-
default)
|
|
1907
|
-
# Default: no special handling
|
|
1908
|
-
exec "$CLAUDE_CLI" "$@" || {
|
|
1909
|
-
show_claude_not_found_error
|
|
1910
|
-
exit 1
|
|
1911
|
-
}
|
|
1912
|
-
;;
|
|
1913
|
-
|
|
1914
|
-
*)
|
|
1915
|
-
msg_error "Unknown profile type: $PROFILE_TYPE"
|
|
1916
|
-
exit 1
|
|
1917
|
-
;;
|
|
1918
|
-
esac
|
|
31
|
+
# Execute via npx (auto-installs if needed)
|
|
32
|
+
exec npx "$PACKAGE" "$@"
|