@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.
Files changed (262) hide show
  1. package/README.md +98 -7
  2. package/VERSION +1 -1
  3. package/config/base-agy.settings.json +10 -0
  4. package/config/base-codex.settings.json +10 -0
  5. package/config/base-gemini.settings.json +10 -0
  6. package/dist/auth/auth-commands.d.ts +52 -0
  7. package/dist/auth/auth-commands.d.ts.map +1 -0
  8. package/dist/auth/auth-commands.js +479 -0
  9. package/dist/auth/auth-commands.js.map +1 -0
  10. package/dist/auth/profile-detector.d.ts +68 -0
  11. package/dist/auth/profile-detector.d.ts.map +1 -0
  12. package/dist/auth/profile-detector.js +209 -0
  13. package/dist/auth/profile-detector.js.map +1 -0
  14. package/dist/auth/profile-registry.d.ts +60 -0
  15. package/dist/auth/profile-registry.d.ts.map +1 -0
  16. package/dist/auth/profile-registry.js +188 -0
  17. package/dist/auth/profile-registry.js.map +1 -0
  18. package/dist/ccs.d.ts +10 -0
  19. package/dist/ccs.d.ts.map +1 -0
  20. package/dist/ccs.js +320 -0
  21. package/dist/ccs.js.map +1 -0
  22. package/dist/cliproxy/auth-handler.d.ts +95 -0
  23. package/dist/cliproxy/auth-handler.d.ts.map +1 -0
  24. package/dist/cliproxy/auth-handler.js +443 -0
  25. package/dist/cliproxy/auth-handler.js.map +1 -0
  26. package/dist/cliproxy/base-config-loader.d.ts +42 -0
  27. package/dist/cliproxy/base-config-loader.d.ts.map +1 -0
  28. package/dist/cliproxy/base-config-loader.js +123 -0
  29. package/dist/cliproxy/base-config-loader.js.map +1 -0
  30. package/dist/cliproxy/binary-manager.d.ts +104 -0
  31. package/dist/cliproxy/binary-manager.d.ts.map +1 -0
  32. package/dist/cliproxy/binary-manager.js +567 -0
  33. package/dist/cliproxy/binary-manager.js.map +1 -0
  34. package/dist/cliproxy/cliproxy-executor.d.ts +33 -0
  35. package/dist/cliproxy/cliproxy-executor.d.ts.map +1 -0
  36. package/dist/cliproxy/cliproxy-executor.js +297 -0
  37. package/dist/cliproxy/cliproxy-executor.js.map +1 -0
  38. package/dist/cliproxy/config-generator.d.ts +89 -0
  39. package/dist/cliproxy/config-generator.d.ts.map +1 -0
  40. package/dist/cliproxy/config-generator.js +263 -0
  41. package/dist/cliproxy/config-generator.js.map +1 -0
  42. package/dist/cliproxy/index.d.ts +13 -0
  43. package/dist/cliproxy/index.d.ts.map +1 -0
  44. package/dist/cliproxy/index.js +62 -0
  45. package/dist/cliproxy/index.js.map +1 -0
  46. package/dist/cliproxy/platform-detector.d.ts +48 -0
  47. package/dist/cliproxy/platform-detector.d.ts.map +1 -0
  48. package/dist/cliproxy/platform-detector.js +118 -0
  49. package/dist/cliproxy/platform-detector.js.map +1 -0
  50. package/dist/cliproxy/types.d.ts +169 -0
  51. package/dist/cliproxy/types.d.ts.map +1 -0
  52. package/dist/cliproxy/types.js +7 -0
  53. package/dist/cliproxy/types.js.map +1 -0
  54. package/dist/commands/doctor-command.d.ts +10 -0
  55. package/dist/commands/doctor-command.d.ts.map +1 -0
  56. package/dist/commands/doctor-command.js +44 -0
  57. package/dist/commands/doctor-command.js.map +1 -0
  58. package/dist/commands/help-command.d.ts +5 -0
  59. package/dist/commands/help-command.d.ts.map +1 -0
  60. package/dist/commands/help-command.js +104 -0
  61. package/dist/commands/help-command.js.map +1 -0
  62. package/dist/commands/install-command.d.ts +14 -0
  63. package/dist/commands/install-command.d.ts.map +1 -0
  64. package/dist/commands/install-command.js +39 -0
  65. package/dist/commands/install-command.js.map +1 -0
  66. package/dist/commands/shell-completion-command.d.ts +10 -0
  67. package/dist/commands/shell-completion-command.d.ts.map +1 -0
  68. package/dist/commands/shell-completion-command.js +85 -0
  69. package/dist/commands/shell-completion-command.js.map +1 -0
  70. package/dist/commands/sync-command.d.ts +10 -0
  71. package/dist/commands/sync-command.d.ts.map +1 -0
  72. package/dist/commands/sync-command.js +59 -0
  73. package/dist/commands/sync-command.js.map +1 -0
  74. package/dist/commands/update-command.d.ts +12 -0
  75. package/dist/commands/update-command.d.ts.map +1 -0
  76. package/dist/commands/update-command.js +295 -0
  77. package/dist/commands/update-command.js.map +1 -0
  78. package/dist/commands/version-command.d.ts +10 -0
  79. package/dist/commands/version-command.d.ts.map +1 -0
  80. package/dist/commands/version-command.js +100 -0
  81. package/dist/commands/version-command.js.map +1 -0
  82. package/dist/delegation/delegation-handler.d.ts +60 -0
  83. package/dist/delegation/delegation-handler.d.ts.map +1 -0
  84. package/dist/delegation/delegation-handler.js +174 -0
  85. package/dist/delegation/delegation-handler.js.map +1 -0
  86. package/dist/delegation/headless-executor.d.ts +114 -0
  87. package/dist/delegation/headless-executor.d.ts.map +1 -0
  88. package/dist/delegation/headless-executor.js +562 -0
  89. package/dist/delegation/headless-executor.js.map +1 -0
  90. package/dist/delegation/result-formatter.d.ts +108 -0
  91. package/dist/delegation/result-formatter.d.ts.map +1 -0
  92. package/dist/delegation/result-formatter.js +391 -0
  93. package/dist/delegation/result-formatter.js.map +1 -0
  94. package/dist/delegation/session-manager.d.ts +58 -0
  95. package/dist/delegation/session-manager.d.ts.map +1 -0
  96. package/dist/delegation/session-manager.js +153 -0
  97. package/dist/delegation/session-manager.js.map +1 -0
  98. package/dist/delegation/settings-parser.d.ts +31 -0
  99. package/dist/delegation/settings-parser.d.ts.map +1 -0
  100. package/dist/delegation/settings-parser.js +107 -0
  101. package/dist/delegation/settings-parser.js.map +1 -0
  102. package/dist/glmt/delta-accumulator.d.ts +210 -0
  103. package/dist/glmt/delta-accumulator.d.ts.map +1 -0
  104. package/dist/glmt/delta-accumulator.js +351 -0
  105. package/dist/glmt/delta-accumulator.js.map +1 -0
  106. package/dist/glmt/glmt-proxy.d.ts +72 -0
  107. package/dist/glmt/glmt-proxy.d.ts.map +1 -0
  108. package/dist/glmt/glmt-proxy.js +427 -0
  109. package/dist/glmt/glmt-proxy.js.map +1 -0
  110. package/dist/glmt/glmt-transformer.d.ts +265 -0
  111. package/dist/glmt/glmt-transformer.d.ts.map +1 -0
  112. package/dist/glmt/glmt-transformer.js +832 -0
  113. package/dist/glmt/glmt-transformer.js.map +1 -0
  114. package/dist/glmt/locale-enforcer.d.ts +38 -0
  115. package/dist/glmt/locale-enforcer.d.ts.map +1 -0
  116. package/dist/glmt/locale-enforcer.js +69 -0
  117. package/dist/glmt/locale-enforcer.js.map +1 -0
  118. package/dist/glmt/reasoning-enforcer.d.ts +52 -0
  119. package/dist/glmt/reasoning-enforcer.d.ts.map +1 -0
  120. package/dist/glmt/reasoning-enforcer.js +151 -0
  121. package/dist/glmt/reasoning-enforcer.js.map +1 -0
  122. package/dist/glmt/sse-parser.d.ts +47 -0
  123. package/dist/glmt/sse-parser.d.ts.map +1 -0
  124. package/dist/glmt/sse-parser.js +93 -0
  125. package/dist/glmt/sse-parser.js.map +1 -0
  126. package/dist/management/doctor.d.ts +104 -0
  127. package/dist/management/doctor.d.ts.map +1 -0
  128. package/dist/management/doctor.js +673 -0
  129. package/dist/management/doctor.js.map +1 -0
  130. package/dist/management/instance-manager.d.ts +57 -0
  131. package/dist/management/instance-manager.d.ts.map +1 -0
  132. package/dist/management/instance-manager.js +195 -0
  133. package/dist/management/instance-manager.js.map +1 -0
  134. package/dist/management/recovery-manager.d.ts +39 -0
  135. package/dist/management/recovery-manager.d.ts.map +1 -0
  136. package/dist/management/recovery-manager.js +141 -0
  137. package/dist/management/recovery-manager.js.map +1 -0
  138. package/dist/management/shared-manager.d.ts +47 -0
  139. package/dist/management/shared-manager.d.ts.map +1 -0
  140. package/dist/management/shared-manager.js +388 -0
  141. package/dist/management/shared-manager.js.map +1 -0
  142. package/dist/types/cli.d.ts +50 -0
  143. package/dist/types/cli.d.ts.map +1 -0
  144. package/dist/types/cli.js +16 -0
  145. package/dist/types/cli.js.map +1 -0
  146. package/dist/types/config.d.ts +51 -0
  147. package/dist/types/config.d.ts.map +1 -0
  148. package/dist/types/config.js +26 -0
  149. package/dist/types/config.js.map +1 -0
  150. package/dist/types/delegation.d.ts +61 -0
  151. package/dist/types/delegation.d.ts.map +1 -0
  152. package/dist/types/delegation.js +6 -0
  153. package/dist/types/delegation.js.map +1 -0
  154. package/dist/types/glmt.d.ts +95 -0
  155. package/dist/types/glmt.d.ts.map +1 -0
  156. package/dist/types/glmt.js +7 -0
  157. package/dist/types/glmt.js.map +1 -0
  158. package/dist/types/index.d.ts +13 -0
  159. package/dist/types/index.d.ts.map +1 -0
  160. package/dist/types/index.js +16 -0
  161. package/dist/types/index.js.map +1 -0
  162. package/dist/types/utils.d.ts +36 -0
  163. package/dist/types/utils.d.ts.map +1 -0
  164. package/dist/types/utils.js +22 -0
  165. package/dist/types/utils.js.map +1 -0
  166. package/dist/utils/claude-detector.d.ts +14 -0
  167. package/dist/utils/claude-detector.d.ts.map +1 -0
  168. package/dist/utils/claude-detector.js +112 -0
  169. package/dist/utils/claude-detector.js.map +1 -0
  170. package/dist/utils/claude-dir-installer.d.ts +46 -0
  171. package/dist/utils/claude-dir-installer.d.ts.map +1 -0
  172. package/dist/utils/claude-dir-installer.js +289 -0
  173. package/dist/utils/claude-dir-installer.js.map +1 -0
  174. package/dist/utils/claude-symlink-manager.d.ts +61 -0
  175. package/dist/utils/claude-symlink-manager.d.ts.map +1 -0
  176. package/dist/utils/claude-symlink-manager.js +291 -0
  177. package/dist/utils/claude-symlink-manager.js.map +1 -0
  178. package/dist/utils/config-manager.d.ts +32 -0
  179. package/dist/utils/config-manager.d.ts.map +1 -0
  180. package/dist/utils/config-manager.js +143 -0
  181. package/dist/utils/config-manager.js.map +1 -0
  182. package/dist/utils/delegation-validator.d.ts +39 -0
  183. package/dist/utils/delegation-validator.d.ts.map +1 -0
  184. package/dist/utils/delegation-validator.js +161 -0
  185. package/dist/utils/delegation-validator.js.map +1 -0
  186. package/dist/utils/error-codes.d.ts +36 -0
  187. package/dist/utils/error-codes.d.ts.map +1 -0
  188. package/dist/utils/error-codes.js +63 -0
  189. package/dist/utils/error-codes.js.map +1 -0
  190. package/dist/utils/error-manager.d.ts +59 -0
  191. package/dist/utils/error-manager.d.ts.map +1 -0
  192. package/dist/utils/error-manager.js +228 -0
  193. package/dist/utils/error-manager.js.map +1 -0
  194. package/dist/utils/helpers.d.ts +27 -0
  195. package/dist/utils/helpers.d.ts.map +1 -0
  196. package/dist/utils/helpers.js +150 -0
  197. package/dist/utils/helpers.js.map +1 -0
  198. package/dist/utils/package-manager-detector.d.ts +14 -0
  199. package/dist/utils/package-manager-detector.d.ts.map +1 -0
  200. package/dist/utils/package-manager-detector.js +162 -0
  201. package/dist/utils/package-manager-detector.js.map +1 -0
  202. package/dist/utils/progress-indicator.d.ts +52 -0
  203. package/dist/utils/progress-indicator.d.ts.map +1 -0
  204. package/dist/utils/progress-indicator.js +102 -0
  205. package/dist/utils/progress-indicator.js.map +1 -0
  206. package/dist/utils/prompt.d.ts +29 -0
  207. package/dist/utils/prompt.d.ts.map +1 -0
  208. package/dist/utils/prompt.js +116 -0
  209. package/dist/utils/prompt.js.map +1 -0
  210. package/dist/utils/shell-completion.d.ts +52 -0
  211. package/dist/utils/shell-completion.d.ts.map +1 -0
  212. package/dist/utils/shell-completion.js +231 -0
  213. package/dist/utils/shell-completion.js.map +1 -0
  214. package/dist/utils/shell-executor.d.ts +15 -0
  215. package/dist/utils/shell-executor.d.ts.map +1 -0
  216. package/dist/utils/shell-executor.js +57 -0
  217. package/dist/utils/shell-executor.js.map +1 -0
  218. package/dist/utils/update-checker.d.ts +48 -0
  219. package/dist/utils/update-checker.d.ts.map +1 -0
  220. package/dist/utils/update-checker.js +241 -0
  221. package/dist/utils/update-checker.js.map +1 -0
  222. package/lib/ccs +21 -1907
  223. package/lib/ccs.ps1 +26 -1800
  224. package/lib/error-codes.ps1 +2 -1
  225. package/lib/prompt.ps1 +2 -2
  226. package/package.json +31 -11
  227. package/scripts/add-shebang.js +39 -0
  228. package/scripts/bump-version.sh +25 -37
  229. package/scripts/dev-install.sh +32 -11
  230. package/scripts/postinstall.js +29 -29
  231. package/bin/auth/auth-commands.js +0 -499
  232. package/bin/auth/profile-detector.js +0 -204
  233. package/bin/auth/profile-registry.js +0 -225
  234. package/bin/ccs.js +0 -1034
  235. package/bin/delegation/README.md +0 -191
  236. package/bin/delegation/delegation-handler.js +0 -212
  237. package/bin/delegation/headless-executor.js +0 -618
  238. package/bin/delegation/result-formatter.js +0 -485
  239. package/bin/delegation/session-manager.js +0 -157
  240. package/bin/delegation/settings-parser.js +0 -109
  241. package/bin/glmt/delta-accumulator.js +0 -276
  242. package/bin/glmt/glmt-proxy.js +0 -495
  243. package/bin/glmt/glmt-transformer.js +0 -999
  244. package/bin/glmt/locale-enforcer.js +0 -72
  245. package/bin/glmt/reasoning-enforcer.js +0 -173
  246. package/bin/glmt/sse-parser.js +0 -96
  247. package/bin/management/doctor.js +0 -721
  248. package/bin/management/instance-manager.js +0 -202
  249. package/bin/management/recovery-manager.js +0 -135
  250. package/bin/management/shared-manager.js +0 -402
  251. package/bin/utils/claude-detector.js +0 -73
  252. package/bin/utils/claude-dir-installer.js +0 -283
  253. package/bin/utils/claude-symlink-manager.js +0 -289
  254. package/bin/utils/config-manager.js +0 -103
  255. package/bin/utils/delegation-validator.js +0 -154
  256. package/bin/utils/error-codes.js +0 -59
  257. package/bin/utils/error-manager.js +0 -165
  258. package/bin/utils/helpers.js +0 -136
  259. package/bin/utils/progress-indicator.js +0 -111
  260. package/bin/utils/prompt.js +0 -134
  261. package/bin/utils/shell-completion.js +0 -256
  262. 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
- # Version (updated by scripts/bump-version.sh)
5
- CCS_VERSION="4.4.0"
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
- # 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"
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
- # 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
-
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
- # Special case: auth commands
1782
- if [[ $# -gt 0 ]] && [[ "${1}" == "auth" ]]; then
1783
- handle_auth_commands "$@"
1784
- exit $?
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
- # Special case: shell completion installer
1788
- if [[ $# -gt 0 ]] && [[ "${1}" == "--shell-completion" || "${1}" == "-sc" ]]; then
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" "$@"