@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.
- package/README.md +14 -7
- package/VERSION +1 -1
- package/config/base-agy.settings.json +10 -0
- package/config/base-codex.settings.json +10 -0
- package/config/base-gemini.settings.json +10 -0
- package/dist/auth/auth-commands.d.ts +52 -0
- package/dist/auth/auth-commands.d.ts.map +1 -0
- package/dist/auth/auth-commands.js +479 -0
- package/dist/auth/auth-commands.js.map +1 -0
- package/dist/auth/profile-detector.d.ts +68 -0
- package/dist/auth/profile-detector.d.ts.map +1 -0
- package/dist/auth/profile-detector.js +209 -0
- package/dist/auth/profile-detector.js.map +1 -0
- package/dist/auth/profile-registry.d.ts +60 -0
- package/dist/auth/profile-registry.d.ts.map +1 -0
- package/dist/auth/profile-registry.js +188 -0
- package/dist/auth/profile-registry.js.map +1 -0
- package/dist/ccs.d.ts +10 -0
- package/dist/ccs.d.ts.map +1 -0
- package/dist/ccs.js +320 -0
- package/dist/ccs.js.map +1 -0
- package/dist/cliproxy/auth-handler.d.ts +93 -0
- package/dist/cliproxy/auth-handler.d.ts.map +1 -0
- package/dist/cliproxy/auth-handler.js +402 -0
- package/dist/cliproxy/auth-handler.js.map +1 -0
- package/dist/cliproxy/base-config-loader.d.ts +42 -0
- package/dist/cliproxy/base-config-loader.d.ts.map +1 -0
- package/dist/cliproxy/base-config-loader.js +123 -0
- package/dist/cliproxy/base-config-loader.js.map +1 -0
- package/dist/cliproxy/binary-manager.d.ts +104 -0
- package/dist/cliproxy/binary-manager.d.ts.map +1 -0
- package/dist/cliproxy/binary-manager.js +567 -0
- package/dist/cliproxy/binary-manager.js.map +1 -0
- package/dist/cliproxy/cliproxy-executor.d.ts +33 -0
- package/dist/cliproxy/cliproxy-executor.d.ts.map +1 -0
- package/dist/cliproxy/cliproxy-executor.js +297 -0
- package/dist/cliproxy/cliproxy-executor.js.map +1 -0
- package/dist/cliproxy/config-generator.d.ts +89 -0
- package/dist/cliproxy/config-generator.d.ts.map +1 -0
- package/dist/cliproxy/config-generator.js +263 -0
- package/dist/cliproxy/config-generator.js.map +1 -0
- package/dist/cliproxy/index.d.ts +13 -0
- package/dist/cliproxy/index.d.ts.map +1 -0
- package/dist/cliproxy/index.js +62 -0
- package/dist/cliproxy/index.js.map +1 -0
- package/dist/cliproxy/platform-detector.d.ts +48 -0
- package/dist/cliproxy/platform-detector.d.ts.map +1 -0
- package/dist/cliproxy/platform-detector.js +118 -0
- package/dist/cliproxy/platform-detector.js.map +1 -0
- package/dist/cliproxy/types.d.ts +169 -0
- package/dist/cliproxy/types.d.ts.map +1 -0
- package/dist/cliproxy/types.js +7 -0
- package/dist/cliproxy/types.js.map +1 -0
- package/dist/commands/doctor-command.d.ts +10 -0
- package/dist/commands/doctor-command.d.ts.map +1 -0
- package/dist/commands/doctor-command.js +44 -0
- package/dist/commands/doctor-command.js.map +1 -0
- package/dist/commands/help-command.d.ts +5 -0
- package/dist/commands/help-command.d.ts.map +1 -0
- package/dist/commands/help-command.js +104 -0
- package/dist/commands/help-command.js.map +1 -0
- package/dist/commands/install-command.d.ts +14 -0
- package/dist/commands/install-command.d.ts.map +1 -0
- package/dist/commands/install-command.js +39 -0
- package/dist/commands/install-command.js.map +1 -0
- package/dist/commands/shell-completion-command.d.ts +10 -0
- package/dist/commands/shell-completion-command.d.ts.map +1 -0
- package/dist/commands/shell-completion-command.js +85 -0
- package/dist/commands/shell-completion-command.js.map +1 -0
- package/dist/commands/sync-command.d.ts +10 -0
- package/dist/commands/sync-command.d.ts.map +1 -0
- package/dist/commands/sync-command.js +59 -0
- package/dist/commands/sync-command.js.map +1 -0
- package/dist/commands/update-command.d.ts +12 -0
- package/dist/commands/update-command.d.ts.map +1 -0
- package/dist/commands/update-command.js +295 -0
- package/dist/commands/update-command.js.map +1 -0
- package/dist/commands/version-command.d.ts +10 -0
- package/dist/commands/version-command.d.ts.map +1 -0
- package/dist/commands/version-command.js +100 -0
- package/dist/commands/version-command.js.map +1 -0
- package/dist/delegation/delegation-handler.d.ts +60 -0
- package/dist/delegation/delegation-handler.d.ts.map +1 -0
- package/dist/delegation/delegation-handler.js +174 -0
- package/dist/delegation/delegation-handler.js.map +1 -0
- package/dist/delegation/headless-executor.d.ts +114 -0
- package/dist/delegation/headless-executor.d.ts.map +1 -0
- package/dist/delegation/headless-executor.js +562 -0
- package/dist/delegation/headless-executor.js.map +1 -0
- package/dist/delegation/result-formatter.d.ts +108 -0
- package/dist/delegation/result-formatter.d.ts.map +1 -0
- package/dist/delegation/result-formatter.js +391 -0
- package/dist/delegation/result-formatter.js.map +1 -0
- package/dist/delegation/session-manager.d.ts +58 -0
- package/dist/delegation/session-manager.d.ts.map +1 -0
- package/dist/delegation/session-manager.js +153 -0
- package/dist/delegation/session-manager.js.map +1 -0
- package/dist/delegation/settings-parser.d.ts +31 -0
- package/dist/delegation/settings-parser.d.ts.map +1 -0
- package/dist/delegation/settings-parser.js +107 -0
- package/dist/delegation/settings-parser.js.map +1 -0
- package/dist/glmt/delta-accumulator.d.ts +210 -0
- package/dist/glmt/delta-accumulator.d.ts.map +1 -0
- package/dist/glmt/delta-accumulator.js +351 -0
- package/dist/glmt/delta-accumulator.js.map +1 -0
- package/dist/glmt/glmt-proxy.d.ts +72 -0
- package/dist/glmt/glmt-proxy.d.ts.map +1 -0
- package/dist/glmt/glmt-proxy.js +427 -0
- package/dist/glmt/glmt-proxy.js.map +1 -0
- package/dist/glmt/glmt-transformer.d.ts +265 -0
- package/dist/glmt/glmt-transformer.d.ts.map +1 -0
- package/dist/glmt/glmt-transformer.js +832 -0
- package/dist/glmt/glmt-transformer.js.map +1 -0
- package/dist/glmt/locale-enforcer.d.ts +38 -0
- package/dist/glmt/locale-enforcer.d.ts.map +1 -0
- package/dist/glmt/locale-enforcer.js +69 -0
- package/dist/glmt/locale-enforcer.js.map +1 -0
- package/dist/glmt/reasoning-enforcer.d.ts +52 -0
- package/dist/glmt/reasoning-enforcer.d.ts.map +1 -0
- package/dist/glmt/reasoning-enforcer.js +151 -0
- package/dist/glmt/reasoning-enforcer.js.map +1 -0
- package/dist/glmt/sse-parser.d.ts +47 -0
- package/dist/glmt/sse-parser.d.ts.map +1 -0
- package/dist/glmt/sse-parser.js +93 -0
- package/dist/glmt/sse-parser.js.map +1 -0
- package/dist/management/doctor.d.ts +104 -0
- package/dist/management/doctor.d.ts.map +1 -0
- package/dist/management/doctor.js +673 -0
- package/dist/management/doctor.js.map +1 -0
- package/dist/management/instance-manager.d.ts +57 -0
- package/dist/management/instance-manager.d.ts.map +1 -0
- package/dist/management/instance-manager.js +195 -0
- package/dist/management/instance-manager.js.map +1 -0
- package/dist/management/recovery-manager.d.ts +39 -0
- package/dist/management/recovery-manager.d.ts.map +1 -0
- package/dist/management/recovery-manager.js +141 -0
- package/dist/management/recovery-manager.js.map +1 -0
- package/dist/management/shared-manager.d.ts +47 -0
- package/dist/management/shared-manager.d.ts.map +1 -0
- package/dist/management/shared-manager.js +388 -0
- package/dist/management/shared-manager.js.map +1 -0
- package/dist/types/cli.d.ts +50 -0
- package/dist/types/cli.d.ts.map +1 -0
- package/dist/types/cli.js +16 -0
- package/dist/types/cli.js.map +1 -0
- package/dist/types/config.d.ts +51 -0
- package/dist/types/config.d.ts.map +1 -0
- package/dist/types/config.js +26 -0
- package/dist/types/config.js.map +1 -0
- package/dist/types/delegation.d.ts +61 -0
- package/dist/types/delegation.d.ts.map +1 -0
- package/dist/types/delegation.js +6 -0
- package/dist/types/delegation.js.map +1 -0
- package/dist/types/glmt.d.ts +95 -0
- package/dist/types/glmt.d.ts.map +1 -0
- package/dist/types/glmt.js +7 -0
- package/dist/types/glmt.js.map +1 -0
- package/dist/types/index.d.ts +13 -0
- package/dist/types/index.d.ts.map +1 -0
- package/dist/types/index.js +16 -0
- package/dist/types/index.js.map +1 -0
- package/dist/types/utils.d.ts +36 -0
- package/dist/types/utils.d.ts.map +1 -0
- package/dist/types/utils.js +22 -0
- package/dist/types/utils.js.map +1 -0
- package/dist/utils/claude-detector.d.ts +14 -0
- package/dist/utils/claude-detector.d.ts.map +1 -0
- package/dist/utils/claude-detector.js +112 -0
- package/dist/utils/claude-detector.js.map +1 -0
- package/dist/utils/claude-dir-installer.d.ts +46 -0
- package/dist/utils/claude-dir-installer.d.ts.map +1 -0
- package/dist/utils/claude-dir-installer.js +289 -0
- package/dist/utils/claude-dir-installer.js.map +1 -0
- package/dist/utils/claude-symlink-manager.d.ts +61 -0
- package/dist/utils/claude-symlink-manager.d.ts.map +1 -0
- package/dist/utils/claude-symlink-manager.js +291 -0
- package/dist/utils/claude-symlink-manager.js.map +1 -0
- package/dist/utils/config-manager.d.ts +32 -0
- package/dist/utils/config-manager.d.ts.map +1 -0
- package/dist/utils/config-manager.js +143 -0
- package/dist/utils/config-manager.js.map +1 -0
- package/dist/utils/delegation-validator.d.ts +39 -0
- package/dist/utils/delegation-validator.d.ts.map +1 -0
- package/dist/utils/delegation-validator.js +161 -0
- package/dist/utils/delegation-validator.js.map +1 -0
- package/dist/utils/error-codes.d.ts +36 -0
- package/dist/utils/error-codes.d.ts.map +1 -0
- package/dist/utils/error-codes.js +63 -0
- package/dist/utils/error-codes.js.map +1 -0
- package/dist/utils/error-manager.d.ts +59 -0
- package/dist/utils/error-manager.d.ts.map +1 -0
- package/dist/utils/error-manager.js +228 -0
- package/dist/utils/error-manager.js.map +1 -0
- package/dist/utils/helpers.d.ts +27 -0
- package/dist/utils/helpers.d.ts.map +1 -0
- package/dist/utils/helpers.js +150 -0
- package/dist/utils/helpers.js.map +1 -0
- package/dist/utils/package-manager-detector.d.ts +14 -0
- package/dist/utils/package-manager-detector.d.ts.map +1 -0
- package/dist/utils/package-manager-detector.js +162 -0
- package/dist/utils/package-manager-detector.js.map +1 -0
- package/dist/utils/progress-indicator.d.ts +52 -0
- package/dist/utils/progress-indicator.d.ts.map +1 -0
- package/dist/utils/progress-indicator.js +102 -0
- package/dist/utils/progress-indicator.js.map +1 -0
- package/dist/utils/prompt.d.ts +29 -0
- package/dist/utils/prompt.d.ts.map +1 -0
- package/dist/utils/prompt.js +116 -0
- package/dist/utils/prompt.js.map +1 -0
- package/dist/utils/shell-completion.d.ts +52 -0
- package/dist/utils/shell-completion.d.ts.map +1 -0
- package/dist/utils/shell-completion.js +231 -0
- package/dist/utils/shell-completion.js.map +1 -0
- package/dist/utils/shell-executor.d.ts +15 -0
- package/dist/utils/shell-executor.d.ts.map +1 -0
- package/dist/utils/shell-executor.js +57 -0
- package/dist/utils/shell-executor.js.map +1 -0
- package/dist/utils/update-checker.d.ts +48 -0
- package/dist/utils/update-checker.d.ts.map +1 -0
- package/dist/utils/update-checker.js +241 -0
- package/dist/utils/update-checker.js.map +1 -0
- package/lib/ccs +21 -1907
- package/lib/ccs.ps1 +26 -1800
- package/lib/error-codes.ps1 +2 -1
- package/lib/prompt.ps1 +2 -2
- package/package.json +31 -11
- package/scripts/add-shebang.js +39 -0
- package/scripts/bump-version.sh +25 -37
- package/scripts/dev-install.sh +32 -11
- package/scripts/postinstall.js +29 -29
- package/bin/auth/auth-commands.js +0 -499
- package/bin/auth/profile-detector.js +0 -204
- package/bin/auth/profile-registry.js +0 -225
- package/bin/ccs.js +0 -1034
- package/bin/delegation/README.md +0 -191
- package/bin/delegation/delegation-handler.js +0 -212
- package/bin/delegation/headless-executor.js +0 -618
- package/bin/delegation/result-formatter.js +0 -485
- package/bin/delegation/session-manager.js +0 -157
- package/bin/delegation/settings-parser.js +0 -109
- package/bin/glmt/delta-accumulator.js +0 -276
- package/bin/glmt/glmt-proxy.js +0 -495
- package/bin/glmt/glmt-transformer.js +0 -999
- package/bin/glmt/locale-enforcer.js +0 -72
- package/bin/glmt/reasoning-enforcer.js +0 -173
- package/bin/glmt/sse-parser.js +0 -96
- package/bin/management/doctor.js +0 -721
- package/bin/management/instance-manager.js +0 -202
- package/bin/management/recovery-manager.js +0 -135
- package/bin/management/shared-manager.js +0 -402
- package/bin/utils/claude-detector.js +0 -73
- package/bin/utils/claude-dir-installer.js +0 -283
- package/bin/utils/claude-symlink-manager.js +0 -289
- package/bin/utils/config-manager.js +0 -103
- package/bin/utils/delegation-validator.js +0 -154
- package/bin/utils/error-codes.js +0 -59
- package/bin/utils/error-manager.js +0 -165
- package/bin/utils/helpers.js +0 -136
- package/bin/utils/progress-indicator.js +0 -111
- package/bin/utils/prompt.js +0 -134
- package/bin/utils/shell-completion.js +0 -256
- package/bin/utils/update-checker.js +0 -243
package/lib/ccs.ps1
CHANGED
|
@@ -1,1813 +1,39 @@
|
|
|
1
|
-
# CCS - Claude Code Switch (
|
|
2
|
-
#
|
|
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
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
$
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
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
|
-
#
|
|
1683
|
-
|
|
1684
|
-
|
|
1685
|
-
|
|
1686
|
-
|
|
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
|
-
#
|
|
1740
|
-
$
|
|
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
|