@kaitranntt/ccs 4.4.0 → 5.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (262) hide show
  1. package/README.md +14 -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 +93 -0
  23. package/dist/cliproxy/auth-handler.d.ts.map +1 -0
  24. package/dist/cliproxy/auth-handler.js +402 -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.ps1 CHANGED
@@ -1,1813 +1,39 @@
1
- # CCS - Claude Code Switch (Windows PowerShell)
2
- # Cross-platform Claude CLI profile switcher
1
+ # CCS - Claude Code Switch (Bootstrap)
2
+ # Delegates to Node.js implementation via npx
3
3
  # https://github.com/kaitranntt/ccs
4
4
 
5
5
  param(
6
- [switch]$Help,
7
- [switch]$Version,
8
6
  [Parameter(ValueFromRemainingArguments=$true)]
9
7
  [string[]]$RemainingArgs
10
8
  )
11
9
 
12
10
  $ErrorActionPreference = "Stop"
13
-
14
- # Version (updated by scripts/bump-version.sh)
15
- $CcsVersion = "4.4.0"
16
- $ScriptDir = Split-Path -Parent $MyInvocation.MyCommand.Path
17
- $ConfigFile = if ($env:CCS_CONFIG) { $env:CCS_CONFIG } else { "$env:USERPROFILE\.ccs\config.json" }
18
- $ProfilesJson = "$env:USERPROFILE\.ccs\profiles.json"
19
- $InstancesDir = "$env:USERPROFILE\.ccs\instances"
20
-
21
- # Determine dependency location (git vs installed)
22
- # Git: lib/ccs.ps1 and dependencies are in same dir (lib/)
23
- # Installed: lib/ccs.ps1 is in ~/.ccs/, dependencies in ~/.ccs/lib/
24
- $DepDir = if (Test-Path "$ScriptDir\error-codes.ps1") {
25
- # Git install - files in same directory
26
- $ScriptDir
27
- } else {
28
- # Standalone install - files in ~/.ccs/lib/
29
- "$env:USERPROFILE\.ccs\lib"
30
- }
31
-
32
- # Source error codes
33
- . "$DepDir\error-codes.ps1"
34
-
35
- # Source progress indicators
36
- . "$DepDir\progress-indicator.ps1"
37
-
38
- # Source interactive prompts
39
- . "$DepDir\prompt.ps1"
40
-
41
- # --- Color/Format Functions ---
42
- function Write-ErrorMsg {
43
- param([string]$Message)
44
- Write-Host ""
45
- Write-Host "=============================================" -ForegroundColor Red
46
- Write-Host " ERROR" -ForegroundColor Red
47
- Write-Host "=============================================" -ForegroundColor Red
48
- Write-Host ""
49
- Write-Host $Message -ForegroundColor Red
50
- Write-Host ""
51
- }
52
-
53
- # Calculate Levenshtein distance between two strings
54
- function Get-LevenshteinDistance {
55
- param(
56
- [string]$a,
57
- [string]$b
58
- )
59
-
60
- $lenA = $a.Length
61
- $lenB = $b.Length
62
-
63
- if ($lenA -eq 0) { return $lenB }
64
- if ($lenB -eq 0) { return $lenA }
65
-
66
- # Initialize matrix
67
- $matrix = New-Object 'int[,]' ($lenB + 1), ($lenA + 1)
68
-
69
- # Initialize first row and column
70
- for ($i = 0; $i -le $lenB; $i++) {
71
- $matrix[$i, 0] = $i
72
- }
73
- for ($j = 0; $j -le $lenA; $j++) {
74
- $matrix[0, $j] = $j
75
- }
76
-
77
- # Fill matrix
78
- for ($i = 1; $i -le $lenB; $i++) {
79
- for ($j = 1; $j -le $lenA; $j++) {
80
- if ($a[$j - 1] -eq $b[$i - 1]) {
81
- $matrix[$i, $j] = $matrix[$i - 1, $j - 1]
82
- } else {
83
- $sub = $matrix[$i - 1, $j - 1]
84
- $ins = $matrix[$i, $j - 1]
85
- $del = $matrix[$i - 1, $j]
86
- $min = [Math]::Min([Math]::Min($sub, $ins), $del)
87
- $matrix[$i, $j] = $min + 1
88
- }
89
- }
90
- }
91
-
92
- return $matrix[$lenB, $lenA]
93
- }
94
-
95
- # Find similar strings using fuzzy matching
96
- function Find-SimilarStrings {
97
- param(
98
- [string]$Target,
99
- [string[]]$Candidates,
100
- [int]$MaxDistance = 2
101
- )
102
-
103
- $targetLower = $Target.ToLower()
104
- $matches = @()
105
-
106
- foreach ($candidate in $Candidates) {
107
- $candidateLower = $candidate.ToLower()
108
- $distance = Get-LevenshteinDistance $targetLower $candidateLower
109
-
110
- if ($distance -le $MaxDistance -and $distance -gt 0) {
111
- $matches += [PSCustomObject]@{
112
- Name = $candidate
113
- Distance = $distance
114
- }
115
- }
116
- }
117
-
118
- # Sort by distance and return top 3
119
- return $matches | Sort-Object Distance | Select-Object -First 3 | ForEach-Object { $_.Name }
120
- }
121
-
122
- # Enhanced error message with error codes
123
- function Show-EnhancedError {
124
- param(
125
- [string]$ErrorCode,
126
- [string]$ShortMsg,
127
- [string]$Context = "",
128
- [string]$Suggestions = ""
129
- )
130
-
131
- Write-Host ""
132
- Write-Host "[X] $ShortMsg" -ForegroundColor Red
133
- Write-Host ""
134
-
135
- if ($Context) {
136
- Write-Host $Context
137
- Write-Host ""
138
- }
139
-
140
- if ($Suggestions) {
141
- Write-Host "Solutions:" -ForegroundColor Yellow
142
- Write-Host $Suggestions
143
- Write-Host ""
144
- }
145
-
146
- Write-Host "Error: $ErrorCode" -ForegroundColor Yellow
147
- Write-Host (Get-ErrorDocUrl $ErrorCode) -ForegroundColor Yellow
148
- Write-Host ""
149
- }
150
-
151
- function Write-ColoredText {
152
- param(
153
- [string]$Text,
154
- [string]$Color = "White",
155
- [switch]$NoNewline
156
- )
157
-
158
- $UseColors = $env:FORCE_COLOR -or ([Console]::IsOutputRedirected -eq $false -and -not $env:NO_COLOR)
159
-
160
- if ($UseColors -and $Color) {
161
- if ($NoNewline) {
162
- Write-Host $Text -ForegroundColor $Color -NoNewline
163
- } else {
164
- Write-Host $Text -ForegroundColor $Color
165
- }
166
- } else {
167
- if ($NoNewline) {
168
- Write-Host $Text -NoNewline
169
- } else {
170
- Write-Host $Text
171
- }
172
- }
173
- }
174
-
175
- # --- Claude CLI Detection Logic ---
176
-
177
- function Find-ClaudeCli {
178
- if ($env:CCS_CLAUDE_PATH) {
179
- return $env:CCS_CLAUDE_PATH
180
- } else {
181
- return "claude"
182
- }
183
- }
184
-
185
- function Show-ClaudeNotFoundError {
186
- $Message = "Claude CLI not found in PATH" + "`n`n" +
187
- "CCS requires Claude CLI to be installed and available in your PATH." + "`n`n" +
188
- "Solutions:" + "`n" +
189
- " 1. Install Claude CLI:" + "`n" +
190
- " https://docs.claude.com/en/docs/claude-code/installation" + "`n`n" +
191
- " 2. Verify installation:" + "`n" +
192
- " Get-Command claude" + "`n`n" +
193
- " 3. If installed but not in PATH, add it:" + "`n" +
194
- " # Find Claude installation" + "`n" +
195
- " where.exe claude" + "`n`n" +
196
- " # Or set custom path" + "`n" +
197
- " `$env:CCS_CLAUDE_PATH = 'C:\path\to\claude.exe'" + "`n`n" +
198
- "Restart your terminal after installation."
199
-
200
- Write-ErrorMsg $Message
201
- }
202
-
203
- function Show-Help {
204
- $UseColors = $env:FORCE_COLOR -or ([Console]::IsOutputRedirected -eq $false -and -not $env:NO_COLOR)
205
-
206
- # Helper for colored output
207
- function Write-ColorLine {
208
- param([string]$Text, [string]$Color = "White")
209
- if ($UseColors) { Write-Host $Text -ForegroundColor $Color }
210
- else { Write-Host $Text }
211
- }
212
-
213
- Write-ColorLine "CCS (Claude Code Switch) - Instant profile switching for Claude CLI" "White"
214
- Write-Host ""
215
-
216
- Write-ColorLine "Usage:" "Cyan"
217
- Write-ColorLine " ccs [profile] [claude-args...]" "Yellow"
218
- Write-ColorLine " ccs [flags]" "Yellow"
219
- Write-Host ""
220
-
221
- Write-ColorLine "Description:" "Cyan"
222
- Write-Host " Switch between multiple Claude accounts and alternative models"
223
- Write-Host " (GLM, Kimi) instantly. Run different Claude CLI sessions concurrently"
224
- Write-Host " with auto-recovery. Zero downtime."
225
- Write-Host ""
226
-
227
- Write-ColorLine "Model Switching:" "Cyan"
228
- Write-ColorLine " ccs Use default Claude account" "Yellow"
229
- Write-ColorLine " ccs glm Switch to GLM 4.6 model" "Yellow"
230
- Write-ColorLine " ccs glmt Switch to GLM with thinking mode" "Yellow"
231
- Write-ColorLine " ccs glmt --verbose Enable debug logging" "Yellow"
232
- Write-ColorLine " ccs kimi Switch to Kimi for Coding" "Yellow"
233
- Write-ColorLine " ccs glm 'debug this code' Use GLM and run command" "Yellow"
234
- Write-Host ""
235
-
236
- Write-ColorLine "Account Management:" "Cyan"
237
- Write-ColorLine " ccs auth --help Run multiple Claude accounts concurrently" "Yellow"
238
- Write-Host ""
239
-
240
- Write-ColorLine "Delegation (inside Claude Code CLI):" "Cyan"
241
- Write-ColorLine " /ccs `"task`" Delegate task (auto-selects best profile)" "Yellow"
242
- Write-ColorLine " /ccs --glm `"task`" Force GLM-4.6 for simple tasks" "Yellow"
243
- Write-ColorLine " /ccs --kimi `"task`" Force Kimi for long context" "Yellow"
244
- Write-ColorLine " /ccs:continue `"follow-up`" Continue last delegation session" "Yellow"
245
- Write-Host " Save tokens by delegating simple tasks to cost-optimized models"
246
- Write-Host ""
247
-
248
- Write-ColorLine "Diagnostics:" "Cyan"
249
- Write-ColorLine " ccs doctor Run health check and diagnostics" "Yellow"
250
- Write-ColorLine " ccs sync Sync delegation commands and skills" "Yellow"
251
- Write-ColorLine " ccs update Update CCS to latest version" "Yellow"
252
- Write-Host ""
253
-
254
- Write-ColorLine "Flags:" "Cyan"
255
- Write-ColorLine " -h, --help Show this help message" "Yellow"
256
- Write-ColorLine " -v, --version Show version and installation info" "Yellow"
257
- Write-ColorLine " -sc, --shell-completion Install shell auto-completion" "Yellow"
258
- Write-Host ""
259
-
260
- Write-ColorLine "Configuration:" "Cyan"
261
- Write-Host " Config File: ~/.ccs/config.json"
262
- Write-Host " Profiles: ~/.ccs/profiles.json"
263
- Write-Host " Instances: ~/.ccs/instances/"
264
- Write-Host " Settings: ~/.ccs/*.settings.json"
265
- Write-Host " Environment: CCS_CONFIG (override config path)"
266
- Write-Host ""
267
-
268
- Write-ColorLine "Shared Data:" "Cyan"
269
- Write-Host " Commands: ~/.ccs/shared/commands/"
270
- Write-Host " Skills: ~/.ccs/shared/skills/"
271
- Write-Host " Agents: ~/.ccs/shared/agents/"
272
- Write-Host " Plugins: ~/.ccs/shared/plugins/"
273
- Write-Host " Note: Commands, skills, agents, and plugins are symlinked across all profiles"
274
- Write-Host ""
275
-
276
- Write-ColorLine "Examples:" "Cyan"
277
- Write-ColorLine " `$ ccs # Use default account" "Yellow"
278
- Write-ColorLine " `$ ccs glm `"implement API`" # Cost-optimized model" "Yellow"
279
- Write-Host ""
280
- Write-ColorLine " For more: https://github.com/kaitranntt/ccs/blob/main/README.md" "Cyan"
281
- Write-Host ""
282
-
283
- Write-ColorLine "Uninstall:" "Yellow"
284
- Write-Host " npm: npm uninstall -g @kaitranntt/ccs"
285
- Write-Host " macOS/Linux: curl -fsSL ccs.kaitran.ca/uninstall | bash"
286
- Write-Host " Windows: irm ccs.kaitran.ca/uninstall | iex"
287
- Write-Host ""
288
-
289
- Write-ColorLine "Documentation:" "Cyan"
290
- Write-Host " GitHub: https://github.com/kaitranntt/ccs"
291
- Write-Host " Docs: https://github.com/kaitranntt/ccs/blob/main/README.md"
292
- Write-Host " Issues: https://github.com/kaitranntt/ccs/issues"
293
- Write-Host ""
294
-
295
- Write-ColorLine "License: MIT" "Cyan"
296
- }
297
-
298
- function Show-Version {
299
- $UseColors = $env:FORCE_COLOR -or ([Console]::IsOutputRedirected -eq $false -and -not $env:NO_COLOR)
300
-
301
- # Helper for aligned output
302
- function Write-TableLine {
303
- param(
304
- [string]$Label,
305
- [string]$Value,
306
- [string]$Color = "Cyan"
307
- )
308
- if ($UseColors) {
309
- $PaddedLabel = $Label.PadRight(17)
310
- Write-Host " $PaddedLabel " -ForegroundColor $Color -NoNewline
311
- Write-Host $Value
312
- } else {
313
- $PaddedLabel = $Label.PadRight(17)
314
- Write-Host " $PaddedLabel $Value"
315
- }
316
- }
317
-
318
- # Title
319
- if ($UseColors) {
320
- Write-Host "CCS (Claude Code Switch) v$CcsVersion" -ForegroundColor White
321
- } else {
322
- Write-Host "CCS (Claude Code Switch) v$CcsVersion"
323
- }
324
- Write-Host ""
325
-
326
- # Installation section with table-like formatting
327
- if ($UseColors) { Write-Host "Installation:" -ForegroundColor Cyan }
328
- else { Write-Host "Installation:" }
329
-
330
- # Location - prioritize script location over command location
331
- $ScriptLocation = $MyInvocation.MyCommand.Path
332
- $InstallLocation = (Get-Command ccs -ErrorAction SilentlyContinue).Source
333
-
334
- # Show script location if running from source
335
- if ($ScriptLocation -and (Test-Path $ScriptLocation)) {
336
- Write-TableLine "Location:" $ScriptLocation
337
- } elseif ($InstallLocation) {
338
- Write-TableLine "Location:" $InstallLocation
339
- } else {
340
- Write-TableLine "Location:" "(not found - run from current directory)"
341
- }
342
-
343
- # .ccs/ directory location
344
- Write-TableLine "CCS Directory:" "$env:USERPROFILE\.ccs\"
345
-
346
- # Config path
347
- Write-TableLine "Config:" $ConfigFile
348
-
349
- # Profiles.json location
350
- Write-TableLine "Profiles:" $ProfilesJson
351
-
352
- # Delegation status - check multiple indicators
353
- $DelegationConfigured = $false
354
- $ReadyProfiles = @()
355
-
356
- # Check for delegation-sessions.json (primary indicator)
357
- $DelegationSessions = "$env:USERPROFILE\.ccs\delegation-sessions.json"
358
- if (Test-Path $DelegationSessions) {
359
- $DelegationConfigured = $true
360
- }
361
-
362
- # Check for profiles with valid API keys (secondary indicator)
363
- foreach ($profile in @("glm", "kimi")) {
364
- $SettingsFile = "$env:USERPROFILE\.ccs\$profile.settings.json"
365
- if (Test-Path $SettingsFile) {
366
- try {
367
- $Settings = Get-Content $SettingsFile -Raw | ConvertFrom-Json
368
- $ApiKey = $Settings.env.ANTHROPIC_AUTH_TOKEN
369
- if ($ApiKey -and $ApiKey -notmatch "YOUR_.*_API_KEY_HERE" -and $ApiKey -notmatch "sk-test.*") {
370
- $ReadyProfiles += $profile
371
- $DelegationConfigured = $true
372
- }
373
- } catch { }
374
- }
375
- }
376
-
377
- if ($DelegationConfigured) {
378
- Write-TableLine "Delegation:" "Enabled"
379
- } else {
380
- Write-TableLine "Delegation:" "Not configured"
381
- }
382
-
383
- Write-Host ""
384
-
385
- # Ready Profiles section - make it more prominent
386
- if ($ReadyProfiles.Count -gt 0) {
387
- if ($UseColors) { Write-Host "Delegation Ready:" -ForegroundColor Cyan }
388
- else { Write-Host "Delegation Ready:" }
389
-
390
- $ReadyProfilesStr = $ReadyProfiles -join ", "
391
- if ($UseColors) {
392
- Write-Host " ✓ " -ForegroundColor Yellow -NoNewline
393
- Write-Host "$ReadyProfilesStr profiles are ready for delegation"
394
- } else {
395
- Write-Host " ! $ReadyProfilesStr profiles are ready for delegation"
396
- }
397
- Write-Host ""
398
- } elseif ($DelegationConfigured) {
399
- if ($UseColors) { Write-Host "Delegation Ready:" -ForegroundColor Cyan }
400
- else { Write-Host "Delegation Ready:" }
401
-
402
- if ($UseColors) {
403
- Write-Host " ! " -ForegroundColor Yellow -NoNewline
404
- Write-Host "Delegation configured but no valid API keys found"
405
- } else {
406
- Write-Host " ! Delegation configured but no valid API keys found"
407
- }
408
- Write-Host ""
409
- }
410
-
411
- # Documentation
412
- if ($UseColors) {
413
- Write-Host "Documentation: https://github.com/kaitranntt/ccs" -ForegroundColor Cyan
414
- Write-Host "License: MIT" -ForegroundColor Cyan
415
- } else {
416
- Write-Host "Documentation: https://github.com/kaitranntt/ccs"
417
- Write-Host "License: MIT"
418
- }
419
- Write-Host ""
420
-
421
- if ($UseColors) {
422
- Write-Host "Run 'ccs --help' for usage information" -ForegroundColor Yellow
423
- } else {
424
- Write-Host "Run 'ccs --help' for usage information"
425
- }
426
- }
427
-
428
- # --- Auto-Recovery Functions ---
429
-
430
- function Ensure-CcsDirectory {
431
- if (Test-Path "$env:USERPROFILE\.ccs") { return $true }
432
-
433
- try {
434
- New-Item -ItemType Directory -Path "$env:USERPROFILE\.ccs" -Force | Out-Null
435
- Write-Host "[i] Auto-recovery: Created ~/.ccs/ directory"
436
- return $true
437
- } catch {
438
- Write-ErrorMsg "Cannot create ~/.ccs/ directory. Check permissions."
439
- return $false
440
- }
441
- }
442
-
443
- function Ensure-ConfigJson {
444
- $ConfigFile = "$env:USERPROFILE\.ccs\config.json"
445
-
446
- # Check if exists and valid
447
- if (Test-Path $ConfigFile) {
448
- try {
449
- Get-Content $ConfigFile -Raw | ConvertFrom-Json | Out-Null
450
- return $true
451
- } catch {
452
- # Corrupted - backup and recreate
453
- $BackupFile = "$ConfigFile.backup.$(Get-Date -Format 'yyyyMMddHHmmss')"
454
- Move-Item $ConfigFile $BackupFile -Force
455
- Write-Host "[i] Auto-recovery: Backed up corrupted config.json"
456
- }
457
- }
458
-
459
- # Create default config
460
- $DefaultConfig = @{
461
- profiles = @{
462
- glm = "~/.ccs/glm.settings.json"
463
- kimi = "~/.ccs/kimi.settings.json"
464
- default = "~/.claude/settings.json"
465
- }
466
- }
467
-
468
- $DefaultConfig | ConvertTo-Json -Depth 10 | Set-Content $ConfigFile
469
- Write-Host "[i] Auto-recovery: Created ~/.ccs/config.json"
470
- return $true
471
- }
472
-
473
- function Ensure-ClaudeSettings {
474
- $ClaudeDir = "$env:USERPROFILE\.claude"
475
- $SettingsFile = "$ClaudeDir\settings.json"
476
-
477
- # Create ~/.claude/ if missing
478
- if (-not (Test-Path $ClaudeDir)) {
479
- New-Item -ItemType Directory -Path $ClaudeDir -Force | Out-Null
480
- Write-Host "[i] Auto-recovery: Created ~/.claude/ directory"
481
- }
482
-
483
- # Create settings.json if missing
484
- if (-not (Test-Path $SettingsFile)) {
485
- '{}' | Set-Content $SettingsFile
486
- Write-Host "[i] Auto-recovery: Created ~/.claude/settings.json"
487
- Write-Host "[i] Next step: Run 'claude /login' to authenticate"
488
- return $true
489
- }
490
-
491
- return $false
492
- }
493
-
494
- function Invoke-AutoRecovery {
495
- if (-not (Ensure-CcsDirectory)) { return $false }
496
- if (-not (Ensure-ConfigJson)) { return $false }
497
- Ensure-ClaudeSettings | Out-Null
498
- return $true
499
- }
500
-
501
- # --- Profile Registry Functions (Phase 4) ---
502
-
503
- function Initialize-ProfilesJson {
504
- if (Test-Path $ProfilesJson) { return }
505
-
506
- $InitData = @{
507
- version = "2.0.0"
508
- profiles = @{}
509
- default = $null
510
- }
511
-
512
- $InitData | ConvertTo-Json -Depth 10 | Set-Content $ProfilesJson
513
-
514
- # Set file permissions (user-only)
515
- $Acl = Get-Acl $ProfilesJson
516
- $Acl.SetAccessRuleProtection($true, $false)
517
- $Acl.Access | ForEach-Object { $Acl.RemoveAccessRule($_) | Out-Null }
518
- $CurrentUser = [System.Security.Principal.WindowsIdentity]::GetCurrent().Name
519
- $Rule = New-Object System.Security.AccessControl.FileSystemAccessRule(
520
- $CurrentUser, "FullControl", "Allow"
521
- )
522
- $Acl.AddAccessRule($Rule)
523
- Set-Acl -Path $ProfilesJson -AclObject $Acl
524
- }
525
-
526
- function Read-ProfilesJson {
527
- Initialize-ProfilesJson
528
- Get-Content $ProfilesJson -Raw | ConvertFrom-Json
529
- }
530
-
531
- function Write-ProfilesJson {
532
- param([PSCustomObject]$Data)
533
-
534
- $TempFile = "$ProfilesJson.tmp"
535
-
536
- try {
537
- $Data | ConvertTo-Json -Depth 10 | Set-Content $TempFile
538
- Move-Item $TempFile $ProfilesJson -Force
539
- } catch {
540
- if (Test-Path $TempFile) { Remove-Item $TempFile -Force }
541
- throw "Failed to write profiles registry: $_"
542
- }
543
- }
544
-
545
- function Test-ProfileExists {
546
- param([string]$ProfileName)
547
-
548
- Initialize-ProfilesJson
549
- $Data = Read-ProfilesJson
550
- return $Data.profiles.PSObject.Properties.Name -contains $ProfileName
551
- }
552
-
553
- function Register-Profile {
554
- param([string]$ProfileName)
555
-
556
- if (Test-ProfileExists $ProfileName) {
557
- throw "Profile already exists: $ProfileName"
558
- }
559
-
560
- $Data = Read-ProfilesJson
561
- $Timestamp = (Get-Date).ToUniversalTime().ToString("o")
562
-
563
- $Data.profiles | Add-Member -NotePropertyName $ProfileName -NotePropertyValue ([PSCustomObject]@{
564
- type = "account"
565
- created = $Timestamp
566
- last_used = $null
567
- })
568
-
569
- # Note: No longer auto-set as default
570
- # Users must explicitly run: ccs auth default <profile>
571
- # Default always stays on implicit 'default' profile (uses ~/.claude/)
572
-
573
- Write-ProfilesJson $Data
574
- }
575
-
576
- function Unregister-Profile {
577
- param([string]$ProfileName)
578
-
579
- if (-not (Test-ProfileExists $ProfileName)) { return } # Idempotent
580
-
581
- $Data = Read-ProfilesJson
582
-
583
- # Remove profile
584
- $Data.profiles.PSObject.Properties.Remove($ProfileName)
585
-
586
- # Update default if it was the deleted profile
587
- if ($Data.default -eq $ProfileName) {
588
- $Remaining = $Data.profiles.PSObject.Properties.Name
589
- $Data.default = if ($Remaining.Count -gt 0) { $Remaining[0] } else { $null }
590
- }
591
-
592
- Write-ProfilesJson $Data
593
- }
594
-
595
- function Update-ProfileTimestamp {
596
- param([string]$ProfileName)
597
-
598
- if (-not (Test-ProfileExists $ProfileName)) { return } # Silent fail
599
-
600
- $Data = Read-ProfilesJson
601
- $Timestamp = (Get-Date).ToUniversalTime().ToString("o")
602
-
603
- $Data.profiles.$ProfileName.last_used = $Timestamp
604
-
605
- Write-ProfilesJson $Data
606
- }
607
-
608
- function Get-DefaultProfile {
609
- Initialize-ProfilesJson
610
- $Data = Read-ProfilesJson
611
- return $Data.default
612
- }
613
-
614
- function Set-DefaultProfile {
615
- param([string]$ProfileName)
616
-
617
- if (-not (Test-ProfileExists $ProfileName)) {
618
- throw "Profile not found: $ProfileName"
619
- }
620
-
621
- $Data = Read-ProfilesJson
622
- $Data.default = $ProfileName
623
-
624
- Write-ProfilesJson $Data
625
- }
626
-
627
- # --- Instance Management Functions (Phase 2) ---
628
-
629
- function Get-SanitizedProfileName {
630
- param([string]$Name)
631
-
632
- # Replace unsafe chars, lowercase
633
- return $Name.ToLower() -replace '[^a-z0-9_-]', '-'
634
- }
635
-
636
- function Set-InstancePermissions {
637
- param([string]$Path)
638
-
639
- # Get current user
640
- $CurrentUser = [System.Security.Principal.WindowsIdentity]::GetCurrent().Name
641
-
642
- # Create new ACL with inheritance disabled
643
- $Acl = Get-Acl $Path
644
- $Acl.SetAccessRuleProtection($true, $false) # Disable inheritance
645
- $Acl.Access | ForEach-Object { $Acl.RemoveAccessRule($_) | Out-Null }
646
-
647
- # Grant full control to current user only
648
- $Rule = New-Object System.Security.AccessControl.FileSystemAccessRule(
649
- $CurrentUser, "FullControl", "ContainerInherit,ObjectInherit", "None", "Allow"
650
- )
651
- $Acl.AddAccessRule($Rule)
652
-
653
- Set-Acl -Path $Path -AclObject $Acl
654
- }
655
-
656
- function Link-SharedDirectories {
657
- param([string]$InstancePath)
658
-
659
- $SharedDir = "$env:USERPROFILE\.ccs\shared"
660
-
661
- # Ensure shared directories exist
662
- @('commands', 'skills', 'agents', 'plugins') | ForEach-Object {
663
- $Dir = Join-Path $SharedDir $_
664
- if (-not (Test-Path $Dir)) {
665
- New-Item -ItemType Directory -Path $Dir -Force | Out-Null
666
- }
667
- }
668
-
669
- # Create symlinks (requires Windows Developer Mode or admin)
670
- @('commands', 'skills', 'agents', 'plugins') | ForEach-Object {
671
- $LinkPath = Join-Path $InstancePath $_
672
- $TargetPath = Join-Path $SharedDir $_
673
-
674
- # Remove existing directory/link
675
- if (Test-Path $LinkPath) {
676
- Remove-Item $LinkPath -Recurse -Force
677
- }
678
-
679
- # Try creating symlink (requires privileges)
680
- try {
681
- New-Item -ItemType SymbolicLink -Path $LinkPath -Target $TargetPath -Force | Out-Null
682
- } catch {
683
- # Fallback: Copy directory instead (suboptimal but functional)
684
- Copy-Item $TargetPath -Destination $LinkPath -Recurse -Force
685
- Write-Host "[!] Symlink failed for $_, copied instead (enable Developer Mode)" -ForegroundColor Yellow
686
- }
687
- }
688
- }
689
-
690
- function Migrate-SharedStructure {
691
- $SharedDir = "$env:USERPROFILE\.ccs\shared"
692
-
693
- # Check if migration is needed (shared dirs exist but are empty)
694
- if (Test-Path $SharedDir) {
695
- $NeedsMigration = $false
696
- foreach ($Dir in @('commands', 'skills', 'agents', 'plugins')) {
697
- $DirPath = Join-Path $SharedDir $Dir
698
- if (-not (Test-Path $DirPath) -or (Get-ChildItem $DirPath -ErrorAction SilentlyContinue).Count -eq 0) {
699
- $NeedsMigration = $true
700
- break
701
- }
702
- }
703
-
704
- if (-not $NeedsMigration) { return }
705
- }
706
-
707
- # Create shared directory
708
- @('commands', 'skills', 'agents', 'plugins') | ForEach-Object {
709
- $Dir = Join-Path $SharedDir $_
710
- New-Item -ItemType Directory -Path $Dir -Force | Out-Null
711
- }
712
-
713
- # Copy from ~/.claude/ (actual Claude CLI directory)
714
- $ClaudeDir = "$env:USERPROFILE\.claude"
715
-
716
- if (Test-Path $ClaudeDir) {
717
- # Copy commands to shared (if exists)
718
- $CommandsPath = Join-Path $ClaudeDir "commands"
719
- if (Test-Path $CommandsPath) {
720
- Copy-Item "$CommandsPath\*" -Destination "$SharedDir\commands\" -Recurse -ErrorAction SilentlyContinue
721
- }
722
-
723
- # Copy skills to shared (if exists)
724
- $SkillsPath = Join-Path $ClaudeDir "skills"
725
- if (Test-Path $SkillsPath) {
726
- Copy-Item "$SkillsPath\*" -Destination "$SharedDir\skills\" -Recurse -ErrorAction SilentlyContinue
727
- }
728
-
729
- # Copy agents to shared (if exists)
730
- $AgentsPath = Join-Path $ClaudeDir "agents"
731
- if (Test-Path $AgentsPath) {
732
- Copy-Item "$AgentsPath\*" -Destination "$SharedDir\agents\" -Recurse -ErrorAction SilentlyContinue
733
- }
734
-
735
- # Copy plugins to shared (if exists)
736
- $PluginsPath = Join-Path $ClaudeDir "plugins"
737
- if (Test-Path $PluginsPath) {
738
- Copy-Item "$PluginsPath\*" -Destination "$SharedDir\plugins\" -Recurse -ErrorAction SilentlyContinue
739
- }
740
- }
741
-
742
- # Update all instances to use symlinks
743
- if (Test-Path $InstancesDir) {
744
- Get-ChildItem $InstancesDir -Directory | ForEach-Object {
745
- Link-SharedDirectories $_.FullName
746
- }
747
- }
748
-
749
- Write-Host "[OK] Migrated to shared structure"
750
- }
751
-
752
- function Copy-GlobalConfigs {
753
- param([string]$InstancePath)
754
-
755
- $GlobalClaude = "$env:USERPROFILE\.claude"
756
-
757
- # Copy settings.json only (commands/skills are now symlinked to shared/)
758
- $GlobalSettings = Join-Path $GlobalClaude "settings.json"
759
- if (Test-Path $GlobalSettings) {
760
- Copy-Item $GlobalSettings -Destination (Join-Path $InstancePath "settings.json") -ErrorAction SilentlyContinue
761
- }
762
- }
763
-
764
- function Initialize-Instance {
765
- param([string]$InstancePath)
766
-
767
- # Create base directory with user-only ACL
768
- New-Item -ItemType Directory -Path $InstancePath -Force | Out-Null
769
- Set-InstancePermissions $InstancePath
770
-
771
- # Create subdirectories (profile-specific only)
772
- $Subdirs = @('session-env', 'todos', 'logs', 'file-history',
773
- 'shell-snapshots', 'debug', '.anthropic')
774
-
775
- foreach ($Dir in $Subdirs) {
776
- $DirPath = Join-Path $InstancePath $Dir
777
- New-Item -ItemType Directory -Path $DirPath -Force | Out-Null
778
- }
779
-
780
- # Symlink shared directories
781
- Link-SharedDirectories $InstancePath
782
-
783
- # Copy global configs
784
- Copy-GlobalConfigs $InstancePath
785
- }
786
-
787
- function Validate-Instance {
788
- param([string]$InstancePath)
789
-
790
- $RequiredDirs = @('session-env', 'todos', 'logs', 'file-history',
791
- 'shell-snapshots', 'debug', '.anthropic')
792
-
793
- foreach ($Dir in $RequiredDirs) {
794
- $DirPath = Join-Path $InstancePath $Dir
795
- if (-not (Test-Path $DirPath)) {
796
- New-Item -ItemType Directory -Path $DirPath -Force | Out-Null
797
- }
798
- }
799
- }
800
-
801
- function Ensure-Instance {
802
- param([string]$ProfileName)
803
-
804
- $SafeName = Get-SanitizedProfileName $ProfileName
805
- $InstancePath = "$InstancesDir\$SafeName"
806
-
807
- if (-not (Test-Path $InstancePath)) {
808
- Initialize-Instance $InstancePath
809
- }
810
-
811
- Validate-Instance $InstancePath
812
-
813
- return $InstancePath
814
- }
815
-
816
- # --- Profile Detection Logic (Phase 1) ---
817
-
818
- function Get-AllProfileNames {
819
- $names = @()
820
-
821
- # Settings-based profiles
822
- if (Test-Path $ConfigFile) {
823
- try {
824
- $Config = Get-Content $ConfigFile -Raw | ConvertFrom-Json
825
- $names += $Config.profiles.PSObject.Properties.Name
826
- } catch {}
827
- }
828
-
829
- # Account-based profiles
830
- if (Test-Path $ProfilesJson) {
831
- try {
832
- $Profiles = Read-ProfilesJson
833
- $names += $Profiles.profiles.PSObject.Properties.Name
834
- } catch {}
835
- }
836
-
837
- return $names
838
- }
839
-
840
- function Get-AvailableProfiles {
841
- $lines = @()
842
-
843
- # Settings-based profiles
844
- if (Test-Path $ConfigFile) {
845
- try {
846
- $Config = Get-Content $ConfigFile -Raw | ConvertFrom-Json
847
- $SettingsProfiles = $Config.profiles.PSObject.Properties.Name
848
-
849
- if ($SettingsProfiles.Count -gt 0) {
850
- $lines += "Settings-based profiles (GLM, Kimi, etc.):"
851
- foreach ($name in $SettingsProfiles) {
852
- $lines += " - $name"
853
- }
854
- }
855
- } catch {}
856
- }
857
-
858
- # Account-based profiles
859
- if (Test-Path $ProfilesJson) {
860
- try {
861
- $Profiles = Read-ProfilesJson
862
- $AccountProfiles = $Profiles.profiles.PSObject.Properties.Name
863
-
864
- if ($AccountProfiles.Count -gt 0) {
865
- $lines += "Account-based profiles:"
866
- foreach ($name in $AccountProfiles) {
867
- $IsDefault = if ($name -eq $Profiles.default) { " [DEFAULT]" } else { "" }
868
- $lines += " - $name$IsDefault"
869
- }
870
- }
871
- } catch {}
872
- }
873
-
874
- if ($lines.Count -eq 0) {
875
- return " (no profiles configured)`n Run `"ccs auth create <profile>`" to create your first account profile."
876
- } else {
877
- return $lines -join "`n"
878
- }
879
- }
880
-
881
- function Get-ProfileType {
882
- param([string]$ProfileName)
883
-
884
- # Special case: 'default' resolves to default profile
885
- if ($ProfileName -eq "default") {
886
- # Check account-based default first
887
- if (Test-Path $ProfilesJson) {
888
- $Profiles = Read-ProfilesJson
889
- $DefaultAccount = $Profiles.default
890
-
891
- if ($DefaultAccount -and (Test-ProfileExists $DefaultAccount)) {
892
- return @{
893
- Type = "account"
894
- Name = $DefaultAccount
895
- }
896
- }
897
- }
898
-
899
- # Check settings-based default
900
- if (Test-Path $ConfigFile) {
901
- $Config = Get-Content $ConfigFile -Raw | ConvertFrom-Json
902
- $DefaultSettings = $Config.profiles.default
903
-
904
- if ($DefaultSettings) {
905
- return @{
906
- Type = "settings"
907
- Path = $DefaultSettings
908
- Name = "default"
909
- }
910
- }
911
- }
912
-
913
- # No default configured, use Claude's defaults
914
- return @{
915
- Type = "default"
916
- Name = "default"
917
- }
918
- }
919
-
920
- # Priority 1: Check settings-based profiles (backward compatibility)
921
- if (Test-Path $ConfigFile) {
922
- try {
923
- $Config = Get-Content $ConfigFile -Raw | ConvertFrom-Json
924
- $SettingsPath = $Config.profiles.$ProfileName
925
-
926
- if ($SettingsPath) {
927
- return @{
928
- Type = "settings"
929
- Path = $SettingsPath
930
- Name = $ProfileName
931
- }
932
- }
933
- } catch {}
934
- }
935
-
936
- # Priority 2: Check account-based profiles
937
- if ((Test-Path $ProfilesJson) -and (Test-ProfileExists $ProfileName)) {
938
- return @{
939
- Type = "account"
940
- Name = $ProfileName
941
- }
942
- }
943
-
944
- # Not found
945
- return @{
946
- Type = "error"
947
- }
948
- }
949
-
950
- # --- Sync Command ---
951
-
952
- function Sync-Run {
953
- $CcsClaudeDir = "$env:USERPROFILE\.ccs\.claude"
954
- $UserClaudeDir = "$env:USERPROFILE\.claude"
955
-
956
- Write-Host "Syncing delegation commands and skills to ~/.claude/..." -ForegroundColor Cyan
957
- Write-Host ""
958
-
959
- # Check if source directory exists
960
- if (-not (Test-Path $CcsClaudeDir)) {
961
- Write-Host "[X] CCS .claude/ directory not found at $CcsClaudeDir" -ForegroundColor Red
962
- Write-Host "Reinstall CCS: npm install -g @kaitranntt/ccs --force"
963
- exit 1
964
- }
965
-
966
- # Create ~/.claude/ if missing
967
- if (-not (Test-Path $UserClaudeDir)) {
968
- Write-Host "[i] Creating ~/.claude/ directory" -ForegroundColor Cyan
969
- New-Item -ItemType Directory -Path $UserClaudeDir -Force | Out-Null
970
- }
971
-
972
- # Items to symlink
973
- $Items = @(
974
- @{ Source = "commands\ccs"; Target = "commands\ccs"; Type = "Directory" }
975
- @{ Source = "skills\ccs-delegation"; Target = "skills\ccs-delegation"; Type = "Directory" }
976
- @{ Source = "agents\ccs-delegator.md"; Target = "agents\ccs-delegator.md"; Type = "File" }
977
- )
978
-
979
- $Installed = 0
980
- $Skipped = 0
981
-
982
- foreach ($Item in $Items) {
983
- $SourcePath = Join-Path $CcsClaudeDir $Item.Source
984
- $TargetPath = Join-Path $UserClaudeDir $Item.Target
985
- $TargetDir = Split-Path -Parent $TargetPath
986
-
987
- # Check source exists
988
- if (-not (Test-Path $SourcePath)) {
989
- Write-Host "[!] Source not found: $($Item.Source), skipping" -ForegroundColor Yellow
990
- continue
991
- }
992
-
993
- # Create parent directory if needed
994
- if (-not (Test-Path $TargetDir)) {
995
- New-Item -ItemType Directory -Path $TargetDir -Force | Out-Null
996
- }
997
-
998
- # Check if already correct symlink
999
- if (Test-Path $TargetPath) {
1000
- $ItemInfo = Get-Item $TargetPath -Force
1001
- if ($ItemInfo.LinkType -eq "SymbolicLink") {
1002
- $LinkTarget = $ItemInfo.Target
1003
- if ($LinkTarget -eq $SourcePath) {
1004
- $Skipped++
1005
- continue
1006
- }
1007
- }
1008
-
1009
- # Backup existing file/directory
1010
- $Timestamp = Get-Date -Format "yyyy-MM-dd"
1011
- $BackupPath = "$TargetPath.backup-$Timestamp"
1012
- $Counter = 1
1013
-
1014
- while (Test-Path $BackupPath) {
1015
- $BackupPath = "$TargetPath.backup-$Timestamp-$Counter"
1016
- $Counter++
1017
- }
1018
-
1019
- Move-Item -Path $TargetPath -Destination $BackupPath -Force
1020
- Write-Host "[i] Backed up existing to $(Split-Path -Leaf $BackupPath)" -ForegroundColor Cyan
1021
- }
1022
-
1023
- # Create symlink
1024
- try {
1025
- $SymlinkType = if ($Item.Type -eq "Directory") { "Junction" } else { "SymbolicLink" }
1026
- New-Item -ItemType $SymlinkType -Path $TargetPath -Target $SourcePath -Force -ErrorAction Stop | Out-Null
1027
- Write-Host "[OK] Installed $($Item.Target)" -ForegroundColor Green
1028
- $Installed++
1029
- } catch {
1030
- Write-Host "[X] Failed to install $($Item.Target): $($_.Exception.Message)" -ForegroundColor Red
1031
- if ($_.Exception.Message -match "privilege") {
1032
- Write-Host "[i] Run PowerShell as Administrator or enable Developer Mode" -ForegroundColor Yellow
1033
- }
1034
- }
1035
- }
1036
-
1037
- Write-Host ""
1038
- Write-Host "✓ Update complete" -ForegroundColor Green
1039
- Write-Host " Installed: $Installed"
1040
- Write-Host " Already up-to-date: $Skipped"
1041
- Write-Host ""
1042
- }
1043
-
1044
- # --- Update Command ---
1045
-
1046
- function Update-Run {
1047
- Write-Host ""
1048
- Write-Host "Checking for updates..." -ForegroundColor Cyan
1049
- Write-Host ""
1050
-
1051
- # Detect installation method
1052
- $InstallMethod = "direct"
1053
- try {
1054
- $NpmList = npm list -g @kaitranntt/ccs 2>&1
1055
- if ($LASTEXITCODE -eq 0) {
1056
- $InstallMethod = "npm"
1057
- }
1058
- } catch {
1059
- # npm not available or not installed via npm
1060
- }
1061
-
1062
- # Fetch latest version from appropriate source
1063
- $LatestVersion = ""
1064
- try {
1065
- if ($InstallMethod -eq "npm") {
1066
- # Check npm registry for npm installations
1067
- $Response = Invoke-RestMethod -Uri "https://registry.npmjs.org/@kaitranntt/ccs/latest" -TimeoutSec 5
1068
- $LatestVersion = $Response.version
1069
- } else {
1070
- # Check GitHub releases for direct installations
1071
- $Response = Invoke-RestMethod -Uri "https://api.github.com/repos/kaitranntt/ccs/releases/latest" -TimeoutSec 5
1072
- $LatestVersion = $Response.tag_name -replace '^v', ''
1073
- }
1074
- } catch {
1075
- Write-Host "[!] Unable to check for updates" -ForegroundColor Yellow
1076
- Write-Host ""
1077
- Write-Host "Try manually:"
1078
- if ($InstallMethod -eq "npm") {
1079
- Write-Host " npm install -g @kaitranntt/ccs@latest" -ForegroundColor Yellow
1080
- } else {
1081
- Write-Host " irm ccs.kaitran.ca/install | iex" -ForegroundColor Yellow
1082
- }
1083
- Write-Host ""
1084
- exit 1
1085
- }
1086
-
1087
- # Compare versions
1088
- if ($LatestVersion -eq $CcsVersion) {
1089
- Write-Host "[OK] You are already on the latest version ($CcsVersion)" -ForegroundColor Green
1090
- Write-Host ""
1091
- exit 0
1092
- }
1093
-
1094
- # Check if update available
1095
- $CurrentParts = $CcsVersion.Split('.')
1096
- $LatestParts = $LatestVersion.Split('.')
1097
-
1098
- $IsNewer = $false
1099
- for ($i = 0; $i -lt 3; $i++) {
1100
- $Current = [int]$CurrentParts[$i]
1101
- $Latest = [int]$LatestParts[$i]
1102
-
1103
- if ($Latest -gt $Current) {
1104
- $IsNewer = $true
1105
- break
1106
- } elseif ($Latest -lt $Current) {
1107
- break
1108
- }
1109
- }
1110
-
1111
- if (-not $IsNewer) {
1112
- Write-Host "[OK] You are on version $CcsVersion (latest is $LatestVersion)" -ForegroundColor Green
1113
- Write-Host ""
1114
- exit 0
1115
- }
1116
-
1117
- Write-Host "[i] Update available: $CcsVersion → $LatestVersion" -ForegroundColor Yellow
1118
- Write-Host ""
1119
-
1120
- # Perform update based on installation method
1121
- if ($InstallMethod -eq "npm") {
1122
- Write-Host "Updating via npm..." -ForegroundColor Cyan
1123
- Write-Host ""
1124
-
1125
- # Clear npm cache to ensure fresh download
1126
- Write-Host "Clearing package cache..." -ForegroundColor Cyan
1127
- try {
1128
- npm cache clean --force 2>$null
1129
- if ($LASTEXITCODE -ne 0) {
1130
- Write-Host "[!] Cache clearing failed, proceeding anyway..." -ForegroundColor Yellow
1131
- }
1132
- } catch {
1133
- Write-Host "[!] Cache clearing failed, proceeding anyway..." -ForegroundColor Yellow
1134
- }
1135
- Write-Host ""
1136
-
1137
- try {
1138
- npm install -g @kaitranntt/ccs@latest
1139
- if ($LASTEXITCODE -eq 0) {
1140
- Write-Host ""
1141
- Write-Host "[OK] Update successful!" -ForegroundColor Green
1142
- Write-Host ""
1143
- Write-Host "Run ccs --version to verify" -ForegroundColor Yellow
1144
- Write-Host ""
1145
- exit 0
1146
- } else {
1147
- throw "npm install failed"
1148
- }
1149
- } catch {
1150
- Write-Host ""
1151
- Write-Host "[X] Update failed" -ForegroundColor Red
1152
- Write-Host ""
1153
- Write-Host "Try manually:"
1154
- Write-Host " npm cache clean --force; npm install -g @kaitranntt/ccs@latest" -ForegroundColor Yellow
1155
- Write-Host ""
1156
- exit 1
1157
- }
1158
- } else {
1159
- Write-Host "Updating via installer..." -ForegroundColor Cyan
1160
- Write-Host ""
1161
-
1162
- try {
1163
- irm ccs.kaitran.ca/install | iex
1164
- Write-Host ""
1165
- Write-Host "[OK] Update successful!" -ForegroundColor Green
1166
- Write-Host ""
1167
- Write-Host "Run ccs --version to verify" -ForegroundColor Yellow
1168
- Write-Host ""
1169
- exit 0
1170
- } catch {
1171
- Write-Host ""
1172
- Write-Host "[X] Update failed" -ForegroundColor Red
1173
- Write-Host ""
1174
- Write-Host "Try manually:"
1175
- Write-Host " irm ccs.kaitran.ca/install | iex" -ForegroundColor Yellow
1176
- Write-Host ""
1177
- exit 1
1178
- }
1179
- }
1180
- }
1181
-
1182
- # --- Auth Commands (Phase 3) ---
1183
-
1184
- function Show-AuthHelp {
1185
- Write-Host ""
1186
- Write-Host "CCS Concurrent Account Management" -ForegroundColor White
1187
- Write-Host ""
1188
- Write-Host "Usage:" -ForegroundColor Cyan
1189
- Write-Host " ccs auth <command> [options]" -ForegroundColor Yellow
1190
- Write-Host ""
1191
- Write-Host "Commands:" -ForegroundColor Cyan
1192
- Write-Host " create <profile> Create new profile and login" -ForegroundColor Yellow
1193
- Write-Host " list List all saved profiles" -ForegroundColor Yellow
1194
- Write-Host " show <profile> Show profile details" -ForegroundColor Yellow
1195
- Write-Host " remove <profile> Remove saved profile" -ForegroundColor Yellow
1196
- Write-Host " default <profile> Set default profile" -ForegroundColor Yellow
1197
- Write-Host ""
1198
- Write-Host "Examples:" -ForegroundColor Cyan
1199
- Write-Host " ccs auth create work # Create & login to work profile" -ForegroundColor Yellow
1200
- Write-Host " ccs auth default work # Set work as default" -ForegroundColor Yellow
1201
- Write-Host " ccs auth list # List all profiles" -ForegroundColor Yellow
1202
- Write-Host ' ccs work "review code" # Use work profile' -ForegroundColor Yellow
1203
- Write-Host ' ccs "review code" # Use default profile' -ForegroundColor Yellow
1204
- Write-Host ""
1205
- Write-Host "Options:" -ForegroundColor Cyan
1206
- Write-Host " --force Allow overwriting existing profile (create)" -ForegroundColor Yellow
1207
- Write-Host " --yes, -y Skip confirmation prompts (remove)" -ForegroundColor Yellow
1208
- Write-Host " --json Output in JSON format (list, show)" -ForegroundColor Yellow
1209
- Write-Host " --verbose Show additional details (list)" -ForegroundColor Yellow
1210
- Write-Host ""
1211
- Write-Host "Note:" -ForegroundColor Cyan
1212
- Write-Host " By default, " -NoNewline
1213
- Write-Host "ccs" -ForegroundColor Yellow -NoNewline
1214
- Write-Host " uses Claude CLI defaults from ~/.claude/"
1215
- Write-Host " Use " -NoNewline
1216
- Write-Host "ccs auth default <profile>" -ForegroundColor Yellow -NoNewline
1217
- Write-Host " to change the default profile."
1218
- Write-Host ""
1219
- }
1220
-
1221
- function Invoke-AuthCreate {
1222
- param([string[]]$Args)
1223
-
1224
- $ProfileName = ""
1225
- $Force = $false
1226
-
1227
- foreach ($arg in $Args) {
1228
- if ($arg -eq "--force") {
1229
- $Force = $true
1230
- } elseif ($arg -match '^-') {
1231
- Write-ErrorMsg "Unknown option: $arg"
1232
- return 1
1233
- } else {
1234
- $ProfileName = $arg
1235
- }
1236
- }
1237
-
1238
- if (-not $ProfileName) {
1239
- Write-ErrorMsg "Profile name is required`n`nUsage: ccs auth create <profile> [--force]"
1240
- return 1
1241
- }
1242
-
1243
- if (-not $Force -and (Test-ProfileExists $ProfileName)) {
1244
- Write-ErrorMsg "Profile already exists: $ProfileName`nUse --force to overwrite"
1245
- return 1
1246
- }
1247
-
1248
- # Create instance
1249
- Write-Host "[i] Creating profile: $ProfileName"
1250
- $InstancePath = Ensure-Instance $ProfileName
1251
- Write-Host "[i] Instance directory: $InstancePath"
1252
- Write-Host ""
1253
-
1254
- # Register profile
1255
- Register-Profile $ProfileName
1256
-
1257
- # Launch Claude for login
1258
- Write-Host "[i] Starting Claude in isolated instance..." -ForegroundColor Yellow
1259
- Write-Host "[i] You will be prompted to login with your account." -ForegroundColor Yellow
1260
- Write-Host ""
1261
-
1262
- $env:CLAUDE_CONFIG_DIR = $InstancePath
1263
- $ClaudeCli = Find-ClaudeCli
1264
- & $ClaudeCli
1265
-
1266
- if ($LASTEXITCODE -ne 0) {
1267
- Write-ErrorMsg "Login failed or cancelled`nTo retry: ccs auth create $ProfileName --force"
1268
- return 1
1269
- }
1270
-
1271
- Write-Host ""
1272
- Write-Host "[OK] Profile created successfully" -ForegroundColor Green
1273
- Write-Host ""
1274
- Write-Host " Profile: $ProfileName"
1275
- Write-Host " Instance: $InstancePath"
1276
- Write-Host ""
1277
- Write-Host "Usage:"
1278
- Write-Host " ccs $ProfileName `"your prompt here`" # Use this specific profile" -ForegroundColor Yellow
1279
- Write-Host ""
1280
- Write-Host 'To set as default (so you can use just "ccs"):'
1281
- Write-Host " ccs auth default $ProfileName" -ForegroundColor Yellow
1282
- Write-Host ""
1283
- }
1284
-
1285
- function Invoke-AuthList {
1286
- param([string[]]$Args)
1287
-
1288
- $Verbose = $Args -contains "--verbose"
1289
- $Json = $Args -contains "--json"
1290
-
1291
- if (-not (Test-Path $ProfilesJson)) {
1292
- if ($Json) {
1293
- Write-Output "{`"version`":`"$CcsVersion`",`"profiles`":[]}"
1294
- return
1295
- }
1296
- Write-Host "No account profiles found" -ForegroundColor Yellow
1297
- Write-Host ""
1298
- Write-Host "To create your first profile:"
1299
- Write-Host " ccs auth create <profile>" -ForegroundColor Yellow
1300
- return
1301
- }
1302
-
1303
- $Data = Read-ProfilesJson
1304
- $Profiles = $Data.profiles.PSObject.Properties.Name
1305
-
1306
- if ($Profiles.Count -eq 0) {
1307
- if ($Json) {
1308
- Write-Output "{`"version`":`"$CcsVersion`",`"profiles`":[]}"
1309
- return
1310
- }
1311
- Write-Host "No account profiles found" -ForegroundColor Yellow
1312
- return
1313
- }
1314
-
1315
- # JSON output mode
1316
- if ($Json) {
1317
- $ProfilesList = @()
1318
- foreach ($profile in $Profiles) {
1319
- $IsDefault = $profile -eq $Data.default
1320
- $Type = $Data.profiles.$profile.type
1321
- if (-not $Type) { $Type = "account" }
1322
- $Created = $Data.profiles.$profile.created
1323
- $LastUsed = $Data.profiles.$profile.last_used
1324
- $InstancePath = "$InstancesDir\$(Get-SanitizedProfileName $profile)"
1325
-
1326
- $ProfilesList += @{
1327
- name = $profile
1328
- type = $Type
1329
- is_default = $IsDefault
1330
- created = $Created
1331
- last_used = $LastUsed
1332
- instance_path = $InstancePath
1333
- }
1334
- }
1335
-
1336
- $Output = @{
1337
- version = $CcsVersion
1338
- profiles = $ProfilesList
1339
- }
1340
-
1341
- Write-Output ($Output | ConvertTo-Json -Depth 10)
1342
- return
1343
- }
1344
-
1345
- # Human-readable output
1346
- Write-Host "Saved Account Profiles:" -ForegroundColor White
1347
- Write-Host ""
1348
-
1349
- foreach ($profile in $Profiles) {
1350
- $IsDefault = $profile -eq $Data.default
1351
-
1352
- if ($IsDefault) {
1353
- Write-Host "[*] " -ForegroundColor Green -NoNewline
1354
- Write-Host $profile -ForegroundColor Cyan -NoNewline
1355
- Write-Host " (default)" -ForegroundColor Green
1356
- } else {
1357
- Write-Host "[ ] " -NoNewline
1358
- Write-Host $profile -ForegroundColor Cyan
1359
- }
1360
-
1361
- $Type = $Data.profiles.$profile.type
1362
- Write-Host " Type: $Type"
1363
-
1364
- if ($Verbose) {
1365
- $Created = $Data.profiles.$profile.created
1366
- $LastUsed = $Data.profiles.$profile.last_used
1367
- if (-not $LastUsed) { $LastUsed = "Never" }
1368
- Write-Host " Created: $Created"
1369
- Write-Host " Last used: $LastUsed"
1370
- }
1371
-
1372
- Write-Host ""
1373
- }
1374
- }
1375
-
1376
- function Invoke-AuthShow {
1377
- param([string[]]$Args)
1378
-
1379
- $ProfileName = ""
1380
- $Json = $false
1381
-
1382
- # Parse arguments
1383
- foreach ($arg in $Args) {
1384
- if ($arg -eq "--json") {
1385
- $Json = $true
1386
- } elseif ($arg -like "-*") {
1387
- Write-ErrorMsg "Unknown option: $arg"
1388
- return 1
1389
- } else {
1390
- $ProfileName = $arg
1391
- }
1392
- }
1393
-
1394
- if (-not $ProfileName) {
1395
- Write-ErrorMsg "Profile name is required"
1396
- Write-Host ""
1397
- Write-Host "Usage: " -NoNewline
1398
- Write-Host "ccs auth show <profile> [--json]" -ForegroundColor Yellow
1399
- return 1
1400
- }
1401
-
1402
- if (-not (Test-ProfileExists $ProfileName)) {
1403
- Write-ErrorMsg "Profile not found: $ProfileName"
1404
- return 1
1405
- }
1406
-
1407
- $Data = Read-ProfilesJson
1408
- $IsDefault = $ProfileName -eq $Data.default
1409
-
1410
- $Type = $Data.profiles.$ProfileName.type
1411
- if (-not $Type) { $Type = "account" }
1412
- $Created = $Data.profiles.$ProfileName.created
1413
- $LastUsed = $Data.profiles.$ProfileName.last_used
1414
- $InstancePath = "$InstancesDir\$(Get-SanitizedProfileName $ProfileName)"
1415
-
1416
- # Count sessions
1417
- $SessionCount = 0
1418
- if (Test-Path "$InstancePath\session-env") {
1419
- $SessionFiles = Get-ChildItem "$InstancePath\session-env" -Filter "*.json" -ErrorAction SilentlyContinue
1420
- $SessionCount = $SessionFiles.Count
1421
- }
1422
-
1423
- # JSON output mode
1424
- if ($Json) {
1425
- $Output = @{
1426
- name = $ProfileName
1427
- type = $Type
1428
- is_default = $IsDefault
1429
- created = $Created
1430
- last_used = $LastUsed
1431
- instance_path = $InstancePath
1432
- session_count = $SessionCount
1433
- }
1434
-
1435
- Write-Output ($Output | ConvertTo-Json -Depth 10)
1436
- return
1437
- }
1438
-
1439
- # Human-readable output
1440
- Write-Host "Profile: $ProfileName" -ForegroundColor White
1441
- Write-Host ""
1442
-
1443
- Write-Host " Type: $Type"
1444
- Write-Host " Default: $(if ($IsDefault) { 'Yes' } else { 'No' })"
1445
- Write-Host " Instance: $InstancePath"
1446
- Write-Host " Created: $Created"
1447
- if ($LastUsed) {
1448
- Write-Host " Last used: $LastUsed"
1449
- } else {
1450
- Write-Host " Last used: Never"
1451
- }
1452
- Write-Host ""
1453
- }
1454
-
1455
- function Invoke-AuthRemove {
1456
- param([string[]]$Args)
1457
-
1458
- $ProfileName = ""
1459
-
1460
- # Parse arguments
1461
- foreach ($arg in $Args) {
1462
- if ($arg -eq "--yes" -or $arg -eq "-y") {
1463
- $env:CCS_YES = "1" # Auto-confirm
1464
- } elseif ($arg -like "-*") {
1465
- Write-ErrorMsg "Unknown option: $arg"
1466
- return 1
1467
- } else {
1468
- $ProfileName = $arg
1469
- }
1470
- }
1471
-
1472
- if (-not $ProfileName) {
1473
- Write-ErrorMsg "Profile name is required"
1474
- Write-Host ""
1475
- Write-Host "Usage: " -NoNewline
1476
- Write-Host "ccs auth remove <profile> [--yes]" -ForegroundColor Yellow
1477
- return 1
1478
- }
1479
-
1480
- if (-not (Test-ProfileExists $ProfileName)) {
1481
- Write-ErrorMsg "Profile not found: $ProfileName"
1482
- return 1
1483
- }
1484
-
1485
- # Get instance path and session count for impact display
1486
- $InstancePath = "$InstancesDir\$(Get-SanitizedProfileName $ProfileName)"
1487
- $SessionCount = 0
1488
-
1489
- if (Test-Path "$InstancePath\session-env") {
1490
- $SessionFiles = Get-ChildItem "$InstancePath\session-env" -Filter "*.json" -ErrorAction SilentlyContinue
1491
- $SessionCount = $SessionFiles.Count
1492
- }
1493
-
1494
- # Display impact
1495
- Write-Host ""
1496
- Write-Host "Profile '" -NoNewline
1497
- Write-Host $ProfileName -ForegroundColor Cyan -NoNewline
1498
- Write-Host "' will be permanently deleted."
1499
- Write-Host " Instance path: $InstancePath"
1500
- Write-Host " Sessions: $SessionCount conversation$(if ($SessionCount -ne 1) { 's' } else { '' })"
1501
- Write-Host ""
1502
-
1503
- # Interactive confirmation (or --yes flag)
1504
- $Confirmed = Confirm-Action "Delete this profile?" "No"
1505
-
1506
- if (-not $Confirmed) {
1507
- Write-Host "[i] Cancelled"
1508
- return 0
1509
- }
1510
-
1511
- # Delete instance directory
1512
- if (Test-Path $InstancePath) {
1513
- Remove-Item $InstancePath -Recurse -Force
1514
- }
1515
-
1516
- # Remove from registry
1517
- Unregister-Profile $ProfileName
1518
-
1519
- Write-Host "[OK] Profile removed successfully" -ForegroundColor Green
1520
- Write-Host " Profile: $ProfileName"
1521
- Write-Host ""
1522
- }
1523
-
1524
- function Invoke-AuthDefault {
1525
- param([string[]]$Args)
1526
-
1527
- $ProfileName = $Args[0]
1528
-
1529
- if (-not $ProfileName) {
1530
- Write-ErrorMsg "Profile name is required`nUsage: ccs auth default <profile>"
1531
- return 1
1532
- }
1533
-
1534
- if (-not (Test-ProfileExists $ProfileName)) {
1535
- Write-ErrorMsg "Profile not found: $ProfileName"
1536
- return 1
1537
- }
1538
-
1539
- Set-DefaultProfile $ProfileName
1540
-
1541
- Write-Host "[OK] Default profile set" -ForegroundColor Green
1542
- Write-Host " Profile: $ProfileName"
1543
- Write-Host ""
1544
- Write-Host "Now you can use:"
1545
- Write-Host " ccs `"your prompt`" # Uses $ProfileName profile" -ForegroundColor Yellow
1546
- Write-Host ""
1547
- }
1548
-
1549
- function Invoke-AuthCommands {
1550
- param([string[]]$Args)
1551
-
1552
- $Subcommand = $Args[0]
1553
- $SubArgs = if ($Args.Count -gt 1) { $Args[1..($Args.Count-1)] } else { @() }
1554
-
1555
- switch ($Subcommand) {
1556
- "create" { Invoke-AuthCreate $SubArgs }
1557
- "list" { Invoke-AuthList $SubArgs }
1558
- "show" { Invoke-AuthShow $SubArgs }
1559
- "remove" { Invoke-AuthRemove $SubArgs }
1560
- "default" { Invoke-AuthDefault $SubArgs }
1561
- default { Show-AuthHelp }
1562
- }
1563
- }
1564
-
1565
- function Install-ShellCompletion {
1566
- param([string[]]$Args)
1567
-
1568
- Write-Host ""
1569
- Write-Host "Shell Completion Installer" -ForegroundColor Yellow
1570
- Write-Host ""
1571
-
1572
- # Ensure completion directory exists
1573
- $CompletionsDir = Join-Path $env:USERPROFILE ".ccs\completions"
1574
- if (-not (Test-Path $CompletionsDir)) {
1575
- New-Item -ItemType Directory -Path $CompletionsDir -Force | Out-Null
1576
- }
1577
-
1578
- # Ensure completion file exists
1579
- $CompletionFile = Join-Path $CompletionsDir "ccs.ps1"
1580
- if (-not (Test-Path $CompletionFile)) {
1581
- Write-Host "[X] Completion file not found. Please reinstall CCS." -ForegroundColor Red
1582
- Write-Host ""
1583
- exit 1
1584
- }
1585
-
1586
- # Get PowerShell profile path
1587
- $ProfilePath = $PROFILE
1588
- $ProfileDir = Split-Path $ProfilePath -Parent
1589
-
1590
- # Create profile directory if it doesn't exist
1591
- if (-not (Test-Path $ProfileDir)) {
1592
- New-Item -ItemType Directory -Path $ProfileDir -Force | Out-Null
1593
- }
1594
-
1595
- # Comment marker for easy identification
1596
- $Marker = "# CCS shell completion"
1597
- $SourceCmd = ". `"$CompletionFile`""
1598
-
1599
- # Check if already installed
1600
- if (Test-Path $ProfilePath) {
1601
- $Content = Get-Content $ProfilePath -Raw -ErrorAction SilentlyContinue
1602
- if ($Content -and $Content.Contains($Marker)) {
1603
- Write-Host "[OK] Shell completion already installed" -ForegroundColor Green
1604
- Write-Host ""
1605
- return 0
1606
- }
1607
- }
1608
-
1609
- # Append to PowerShell profile
1610
- $Block = "`n$Marker`n$SourceCmd`n"
1611
- Add-Content -Path $ProfilePath -Value $Block -NoNewline
1612
-
1613
- Write-Host "[OK] Shell completion installed successfully!" -ForegroundColor Green
1614
- Write-Host ""
1615
- Write-Host "Added to $ProfilePath"
1616
- Write-Host ""
1617
- Write-Host "To activate:" -ForegroundColor Cyan
1618
- Write-Host " . `$PROFILE"
1619
- Write-Host ""
1620
-
1621
- return 0
1622
- }
1623
-
1624
- # --- Main Execution Logic ---
1625
-
1626
- # Special case: version command (check BEFORE profile detection)
1627
- if ($Version) {
1628
- Show-Version
1629
- exit 0
1630
- } elseif ($RemainingArgs.Count -gt 0) {
1631
- $FirstArg = $RemainingArgs[0]
1632
- if ($FirstArg -eq "version" -or $FirstArg -eq "--version" -or $FirstArg -eq "-v") {
1633
- Show-Version
1634
- exit 0
1635
- }
1636
- }
1637
-
1638
- # Special case: help command (check BEFORE profile detection)
1639
- if ($Help) {
1640
- Show-Help
1641
- exit 0
1642
- } elseif ($RemainingArgs.Count -gt 0) {
1643
- $FirstArg = $RemainingArgs[0]
1644
- if ($FirstArg -eq "--help" -or $FirstArg -eq "-h" -or $FirstArg -eq "help") {
1645
- Show-Help
1646
- exit 0
1647
- }
1648
- }
1649
-
1650
- # Special case: shell completion installer
1651
- if ($RemainingArgs.Count -gt 0 -and ($RemainingArgs[0] -eq "--shell-completion" -or $RemainingArgs[0] -eq "-sc")) {
1652
- $CompletionArgs = if ($RemainingArgs.Count -gt 1) { $RemainingArgs[1..($RemainingArgs.Count-1)] } else { @() }
1653
- $Result = Install-ShellCompletion $CompletionArgs
1654
- exit $Result
1655
- }
1656
-
1657
- # Special case: auth commands
1658
- if ($RemainingArgs.Count -gt 0 -and $RemainingArgs[0] -eq "auth") {
1659
- $AuthArgs = if ($RemainingArgs.Count -gt 1) { $RemainingArgs[1..($RemainingArgs.Count-1)] } else { @() }
1660
- Invoke-AuthCommands $AuthArgs
1661
- exit $LASTEXITCODE
1662
- }
1663
-
1664
- # Special case: sync command
1665
- if ($RemainingArgs.Count -gt 0 -and ($RemainingArgs[0] -eq "sync" -or $RemainingArgs[0] -eq "--sync")) {
1666
- Sync-Run
1667
- exit 0
1668
- }
1669
-
1670
- # Special case: update command
1671
- if ($RemainingArgs.Count -gt 0 -and ($RemainingArgs[0] -eq "update" -or $RemainingArgs[0] -eq "--update")) {
1672
- Update-Run
1673
- exit 0
1674
- }
1675
-
1676
- # Run auto-recovery before main logic
1677
- if (-not (Invoke-AutoRecovery)) {
1678
- Write-ErrorMsg "Auto-recovery failed. Check permissions."
11
+ $PACKAGE = "@kaitranntt/ccs"
12
+ $MIN_NODE_VERSION = 14
13
+
14
+ # Check Node.js installed
15
+ $NodeCmd = Get-Command node -ErrorAction SilentlyContinue
16
+ if (-not $NodeCmd) {
17
+ Write-Host "[X] Node.js not found"
18
+ Write-Host " Install: https://nodejs.org (LTS recommended)"
19
+ exit 127
20
+ }
21
+
22
+ # Check Node.js version (major only)
23
+ $NodeVersion = (node -v) -replace '^v', '' -split '\.' | Select-Object -First 1
24
+ if ([int]$NodeVersion -lt $MIN_NODE_VERSION) {
25
+ Write-Host "[X] Node.js $MIN_NODE_VERSION+ required (found: $(node -v))"
26
+ Write-Host " Update: https://nodejs.org"
1679
27
  exit 1
1680
28
  }
1681
29
 
1682
- # Smart profile detection: if first arg starts with '-', it's a flag not a profile
1683
- if ($RemainingArgs.Count -eq 0 -or $RemainingArgs[0] -match '^-') {
1684
- # No args or first arg is a flag → use default profile
1685
- $Profile = "default"
1686
- # $RemainingArgs already contains the remaining args correctly
1687
- } else {
1688
- # First arg is a profile name
1689
- $Profile = $RemainingArgs[0]
1690
- $RemainingArgs = if ($RemainingArgs.Count -gt 1) { $RemainingArgs | Select-Object -Skip 1 } else { @() }
1691
- }
1692
-
1693
- # Validate profile name (alphanumeric, dash, underscore only)
1694
- if ($Profile -notmatch '^[a-zA-Z0-9_-]+$') {
1695
- $ErrorMessage = "Invalid profile name: $Profile" + "`n`n" +
1696
- "Use only alphanumeric characters, dash, or underscore."
1697
-
1698
- Write-ErrorMsg $ErrorMessage
1699
- exit 1
1700
- }
1701
-
1702
- # Detect profile type
1703
- $ProfileInfo = Get-ProfileType $Profile
1704
-
1705
- if ($ProfileInfo.Type -eq "error") {
1706
- # Get suggestions using fuzzy matching
1707
- $AllProfiles = Get-AllProfileNames
1708
- $Suggestions = Find-SimilarStrings -Target $Profile -Candidates $AllProfiles
1709
-
1710
- Write-Host ""
1711
- Write-Host "[X] Profile '$Profile' not found" -ForegroundColor Red
1712
- Write-Host ""
1713
-
1714
- # Show suggestions if any
1715
- if ($Suggestions -and $Suggestions.Count -gt 0) {
1716
- Write-Host "Did you mean:" -ForegroundColor Yellow
1717
- foreach ($suggestion in $Suggestions) {
1718
- Write-Host " $suggestion"
1719
- }
1720
- Write-Host ""
1721
- }
1722
-
1723
- Write-Host "Available profiles:" -ForegroundColor Cyan
1724
- Get-AvailableProfiles | ForEach-Object { Write-Host $_ }
1725
- Write-Host ""
1726
- Write-Host "Solutions:" -ForegroundColor Yellow
1727
- Write-Host " # Use existing profile"
1728
- Write-Host " ccs <profile> `"your prompt`""
1729
- Write-Host ""
1730
- Write-Host " # Create new account profile"
1731
- Write-Host " ccs auth create <name>"
1732
- Write-Host ""
1733
- Write-Host "Error: $script:E_PROFILE_NOT_FOUND" -ForegroundColor Yellow
1734
- Write-Host (Get-ErrorDocUrl $script:E_PROFILE_NOT_FOUND) -ForegroundColor Yellow
1735
- Write-Host ""
1736
- exit 1
30
+ # Check npm/npx available
31
+ $NpxCmd = Get-Command npx -ErrorAction SilentlyContinue
32
+ if (-not $NpxCmd) {
33
+ Write-Host "[X] npx not found (requires npm 5.2+)"
34
+ exit 127
1737
35
  }
1738
36
 
1739
- # Detect Claude CLI executable
1740
- $ClaudeCli = Find-ClaudeCli
1741
-
1742
- # Execute based on profile type (Phase 5)
1743
- switch ($ProfileInfo.Type) {
1744
- "account" {
1745
- # Account-based profile: use CLAUDE_CONFIG_DIR
1746
- $InstancePath = Ensure-Instance $ProfileInfo.Name
1747
- Update-ProfileTimestamp $ProfileInfo.Name # Update last_used
1748
-
1749
- # Execute Claude with isolated config
1750
- $env:CLAUDE_CONFIG_DIR = $InstancePath
1751
-
1752
- try {
1753
- if ($RemainingArgs) {
1754
- & $ClaudeCli @RemainingArgs
1755
- } else {
1756
- & $ClaudeCli
1757
- }
1758
- exit $LASTEXITCODE
1759
- } catch {
1760
- Show-ClaudeNotFoundError
1761
- exit 1
1762
- }
1763
- }
1764
-
1765
- "settings" {
1766
- # Settings-based profile: use --settings flag
1767
- $SettingsPath = $ProfileInfo.Path
1768
-
1769
- # Path expansion and normalization
1770
- if ($SettingsPath -match '^~[/\\]') {
1771
- $SettingsPath = $SettingsPath -replace '^~', $env:USERPROFILE
1772
- }
1773
- $SettingsPath = [System.Environment]::ExpandEnvironmentVariables($SettingsPath)
1774
- $SettingsPath = $SettingsPath -replace '/', '\'
1775
-
1776
- if (-not (Test-Path $SettingsPath)) {
1777
- Write-ErrorMsg "Settings file not found: $SettingsPath"
1778
- exit 1
1779
- }
1780
-
1781
- try {
1782
- if ($RemainingArgs) {
1783
- & $ClaudeCli --settings $SettingsPath @RemainingArgs
1784
- } else {
1785
- & $ClaudeCli --settings $SettingsPath
1786
- }
1787
- exit $LASTEXITCODE
1788
- } catch {
1789
- Show-ClaudeNotFoundError
1790
- exit 1
1791
- }
1792
- }
1793
-
1794
- "default" {
1795
- # Default: no special handling
1796
- try {
1797
- if ($RemainingArgs) {
1798
- & $ClaudeCli @RemainingArgs
1799
- } else {
1800
- & $ClaudeCli
1801
- }
1802
- exit $LASTEXITCODE
1803
- } catch {
1804
- Show-ClaudeNotFoundError
1805
- exit 1
1806
- }
1807
- }
1808
-
1809
- default {
1810
- Write-ErrorMsg "Unknown profile type: $($ProfileInfo.Type)"
1811
- exit 1
1812
- }
1813
- }
37
+ # Execute via npx (auto-installs if needed)
38
+ & npx $PACKAGE @RemainingArgs
39
+ exit $LASTEXITCODE