@kaitranntt/ccs 3.0.0 → 3.0.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.ja.md +325 -0
- package/README.md +133 -122
- package/README.vi.md +147 -94
- package/VERSION +1 -1
- package/bin/auth-commands.js +10 -1
- package/bin/ccs.js +44 -34
- package/bin/config-manager.js +36 -7
- package/bin/doctor.js +365 -0
- package/bin/error-manager.js +159 -0
- package/bin/profile-registry.js +3 -4
- package/bin/recovery-manager.js +135 -0
- package/lib/ccs +853 -306
- package/lib/ccs.ps1 +794 -127
- package/package.json +1 -1
- package/scripts/postinstall.js +111 -12
package/lib/ccs.ps1
CHANGED
|
@@ -11,6 +11,13 @@ param(
|
|
|
11
11
|
|
|
12
12
|
$ErrorActionPreference = "Stop"
|
|
13
13
|
|
|
14
|
+
# Version (updated by scripts/bump-version.sh)
|
|
15
|
+
$CcsVersion = "3.0.2"
|
|
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
|
+
|
|
14
21
|
# --- Color/Format Functions ---
|
|
15
22
|
function Write-ErrorMsg {
|
|
16
23
|
param([string]$Message)
|
|
@@ -89,62 +96,44 @@ function Show-Help {
|
|
|
89
96
|
Write-Host ""
|
|
90
97
|
Write-ColorLine "Usage:" "Cyan"
|
|
91
98
|
Write-ColorLine " ccs [profile] [claude-args...]" "Yellow"
|
|
99
|
+
Write-ColorLine " ccs auth <command> [options]" "Yellow"
|
|
92
100
|
Write-ColorLine " ccs [flags]" "Yellow"
|
|
93
101
|
Write-Host ""
|
|
94
102
|
Write-ColorLine "Description:" "Cyan"
|
|
95
|
-
Write-Host " Switch between Claude
|
|
96
|
-
Write-Host "
|
|
97
|
-
Write-Host ""
|
|
98
|
-
Write-ColorLine "Profile Switching:" "Cyan"
|
|
99
|
-
Write-ColorLine " ccs Use default profile" "Yellow"
|
|
100
|
-
Write-ColorLine " ccs glm Switch to GLM profile" "Yellow"
|
|
101
|
-
Write-ColorLine " ccs kimi Switch to Kimi profile" "Yellow"
|
|
102
|
-
Write-ColorLine " ccs glm 'debug this code' Switch to GLM and run command" "Yellow"
|
|
103
|
-
Write-ColorLine " ccs kimi 'write tests' Switch to Kimi and run command" "Yellow"
|
|
104
|
-
Write-ColorLine " ccs glm --verbose Switch to GLM with Claude flags" "Yellow"
|
|
105
|
-
Write-ColorLine " ccs kimi --verbose Switch to Kimi with Claude flags" "Yellow"
|
|
106
|
-
Write-Host ""
|
|
107
|
-
Write-ColorLine "Flags:" "Cyan"
|
|
108
|
-
Write-ColorLine " -h, --help Show this help message" "Yellow"
|
|
109
|
-
Write-ColorLine " -v, --version Show version and installation info" "Yellow"
|
|
110
|
-
Write-Host ""
|
|
111
|
-
Write-ColorLine "Configuration:" "Cyan"
|
|
112
|
-
Write-Host " Config File: ~/.ccs/config.json"
|
|
113
|
-
Write-Host " Settings: ~/.ccs/*.settings.json"
|
|
114
|
-
Write-Host " Environment: CCS_CONFIG (override config path)"
|
|
103
|
+
Write-Host " Switch between multiple Claude accounts (work, personal, team) and"
|
|
104
|
+
Write-Host " alternative models (GLM, Kimi) instantly. Concurrent sessions with"
|
|
105
|
+
Write-Host " auto-recovery. Zero downtime."
|
|
115
106
|
Write-Host ""
|
|
116
|
-
Write-ColorLine "
|
|
117
|
-
Write-
|
|
118
|
-
Write-ColorLine " ccs
|
|
107
|
+
Write-ColorLine "Model Switching:" "Cyan"
|
|
108
|
+
Write-ColorLine " ccs Use default Claude account" "Yellow"
|
|
109
|
+
Write-ColorLine " ccs glm Switch to GLM 4.6 model" "Yellow"
|
|
110
|
+
Write-ColorLine " ccs kimi Switch to Kimi for Coding" "Yellow"
|
|
111
|
+
Write-ColorLine " ccs glm 'debug this code' Use GLM and run command" "Yellow"
|
|
119
112
|
Write-Host ""
|
|
120
|
-
Write-
|
|
121
|
-
Write-ColorLine " ccs
|
|
113
|
+
Write-ColorLine "Account Management:" "Cyan"
|
|
114
|
+
Write-ColorLine " ccs auth --help Manage multiple Claude accounts" "Yellow"
|
|
115
|
+
Write-ColorLine " ccs work Switch to work account" "Yellow"
|
|
116
|
+
Write-ColorLine " ccs personal Switch to personal account" "Yellow"
|
|
122
117
|
Write-Host ""
|
|
123
|
-
Write-
|
|
124
|
-
Write-ColorLine " ccs
|
|
118
|
+
Write-ColorLine "Diagnostics:" "Cyan"
|
|
119
|
+
Write-ColorLine " ccs doctor Run health check and diagnostics" "Yellow"
|
|
125
120
|
Write-Host ""
|
|
126
|
-
Write-
|
|
127
|
-
Write-ColorLine "
|
|
128
|
-
Write-ColorLine "
|
|
121
|
+
Write-ColorLine "Flags:" "Cyan"
|
|
122
|
+
Write-ColorLine " -h, --help Show this help message" "Yellow"
|
|
123
|
+
Write-ColorLine " -v, --version Show version and installation info" "Yellow"
|
|
129
124
|
Write-Host ""
|
|
130
|
-
|
|
131
|
-
Write-Host "
|
|
132
|
-
Write-Host "
|
|
133
|
-
Write-Host "
|
|
125
|
+
Write-ColorLine "Configuration:" "Cyan"
|
|
126
|
+
Write-Host " Config: ~/.ccs/config.json"
|
|
127
|
+
Write-Host " Profiles: ~/.ccs/profiles.json"
|
|
128
|
+
Write-Host " Settings: ~/.ccs/*.settings.json"
|
|
134
129
|
Write-Host ""
|
|
135
130
|
Write-ColorLine "Documentation:" "Cyan"
|
|
136
131
|
Write-Host " GitHub: https://github.com/kaitranntt/ccs"
|
|
137
132
|
Write-Host " Docs: https://github.com/kaitranntt/ccs/blob/main/README.md"
|
|
138
|
-
Write-Host " Issues: https://github.com/kaitranntt/ccs/issues"
|
|
139
133
|
Write-Host ""
|
|
140
134
|
Write-ColorLine "License: MIT" "Cyan"
|
|
141
135
|
}
|
|
142
136
|
|
|
143
|
-
# Version (updated by scripts/bump-version.sh)
|
|
144
|
-
$CcsVersion = "3.0.0"
|
|
145
|
-
$ScriptDir = Split-Path -Parent $MyInvocation.MyCommand.Path
|
|
146
|
-
$ConfigFile = if ($env:CCS_CONFIG) { $env:CCS_CONFIG } else { "$env:USERPROFILE\.ccs\config.json" }
|
|
147
|
-
|
|
148
137
|
function Show-Version {
|
|
149
138
|
$UseColors = $env:FORCE_COLOR -or ([Console]::IsOutputRedirected -eq $false -and -not $env:NO_COLOR)
|
|
150
139
|
|
|
@@ -205,8 +194,689 @@ function Show-Version {
|
|
|
205
194
|
}
|
|
206
195
|
}
|
|
207
196
|
|
|
197
|
+
# --- Auto-Recovery Functions ---
|
|
198
|
+
|
|
199
|
+
function Ensure-CcsDirectory {
|
|
200
|
+
if (Test-Path "$env:USERPROFILE\.ccs") { return $true }
|
|
201
|
+
|
|
202
|
+
try {
|
|
203
|
+
New-Item -ItemType Directory -Path "$env:USERPROFILE\.ccs" -Force | Out-Null
|
|
204
|
+
Write-Host "[i] Auto-recovery: Created ~/.ccs/ directory"
|
|
205
|
+
return $true
|
|
206
|
+
} catch {
|
|
207
|
+
Write-ErrorMsg "Cannot create ~/.ccs/ directory. Check permissions."
|
|
208
|
+
return $false
|
|
209
|
+
}
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
function Ensure-ConfigJson {
|
|
213
|
+
$ConfigFile = "$env:USERPROFILE\.ccs\config.json"
|
|
214
|
+
|
|
215
|
+
# Check if exists and valid
|
|
216
|
+
if (Test-Path $ConfigFile) {
|
|
217
|
+
try {
|
|
218
|
+
Get-Content $ConfigFile -Raw | ConvertFrom-Json | Out-Null
|
|
219
|
+
return $true
|
|
220
|
+
} catch {
|
|
221
|
+
# Corrupted - backup and recreate
|
|
222
|
+
$BackupFile = "$ConfigFile.backup.$(Get-Date -Format 'yyyyMMddHHmmss')"
|
|
223
|
+
Move-Item $ConfigFile $BackupFile -Force
|
|
224
|
+
Write-Host "[i] Auto-recovery: Backed up corrupted config.json"
|
|
225
|
+
}
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
# Create default config
|
|
229
|
+
$DefaultConfig = @{
|
|
230
|
+
profiles = @{
|
|
231
|
+
glm = "~/.ccs/glm.settings.json"
|
|
232
|
+
kimi = "~/.ccs/kimi.settings.json"
|
|
233
|
+
default = "~/.claude/settings.json"
|
|
234
|
+
}
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
$DefaultConfig | ConvertTo-Json -Depth 10 | Set-Content $ConfigFile
|
|
238
|
+
Write-Host "[i] Auto-recovery: Created ~/.ccs/config.json"
|
|
239
|
+
return $true
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
function Ensure-ClaudeSettings {
|
|
243
|
+
$ClaudeDir = "$env:USERPROFILE\.claude"
|
|
244
|
+
$SettingsFile = "$ClaudeDir\settings.json"
|
|
245
|
+
|
|
246
|
+
# Create ~/.claude/ if missing
|
|
247
|
+
if (-not (Test-Path $ClaudeDir)) {
|
|
248
|
+
New-Item -ItemType Directory -Path $ClaudeDir -Force | Out-Null
|
|
249
|
+
Write-Host "[i] Auto-recovery: Created ~/.claude/ directory"
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
# Create settings.json if missing
|
|
253
|
+
if (-not (Test-Path $SettingsFile)) {
|
|
254
|
+
'{}' | Set-Content $SettingsFile
|
|
255
|
+
Write-Host "[i] Auto-recovery: Created ~/.claude/settings.json"
|
|
256
|
+
Write-Host "[i] Next step: Run 'claude /login' to authenticate"
|
|
257
|
+
return $true
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
return $false
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
function Invoke-AutoRecovery {
|
|
264
|
+
if (-not (Ensure-CcsDirectory)) { return $false }
|
|
265
|
+
if (-not (Ensure-ConfigJson)) { return $false }
|
|
266
|
+
Ensure-ClaudeSettings | Out-Null
|
|
267
|
+
return $true
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
# --- Profile Registry Functions (Phase 4) ---
|
|
271
|
+
|
|
272
|
+
function Initialize-ProfilesJson {
|
|
273
|
+
if (Test-Path $ProfilesJson) { return }
|
|
274
|
+
|
|
275
|
+
$InitData = @{
|
|
276
|
+
version = "2.0.0"
|
|
277
|
+
profiles = @{}
|
|
278
|
+
default = $null
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
$InitData | ConvertTo-Json -Depth 10 | Set-Content $ProfilesJson
|
|
282
|
+
|
|
283
|
+
# Set file permissions (user-only)
|
|
284
|
+
$Acl = Get-Acl $ProfilesJson
|
|
285
|
+
$Acl.SetAccessRuleProtection($true, $false)
|
|
286
|
+
$Acl.Access | ForEach-Object { $Acl.RemoveAccessRule($_) | Out-Null }
|
|
287
|
+
$CurrentUser = [System.Security.Principal.WindowsIdentity]::GetCurrent().Name
|
|
288
|
+
$Rule = New-Object System.Security.AccessControl.FileSystemAccessRule(
|
|
289
|
+
$CurrentUser, "FullControl", "Allow"
|
|
290
|
+
)
|
|
291
|
+
$Acl.AddAccessRule($Rule)
|
|
292
|
+
Set-Acl -Path $ProfilesJson -AclObject $Acl
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
function Read-ProfilesJson {
|
|
296
|
+
Initialize-ProfilesJson
|
|
297
|
+
Get-Content $ProfilesJson -Raw | ConvertFrom-Json
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
function Write-ProfilesJson {
|
|
301
|
+
param([PSCustomObject]$Data)
|
|
302
|
+
|
|
303
|
+
$TempFile = "$ProfilesJson.tmp"
|
|
304
|
+
|
|
305
|
+
try {
|
|
306
|
+
$Data | ConvertTo-Json -Depth 10 | Set-Content $TempFile
|
|
307
|
+
Move-Item $TempFile $ProfilesJson -Force
|
|
308
|
+
} catch {
|
|
309
|
+
if (Test-Path $TempFile) { Remove-Item $TempFile -Force }
|
|
310
|
+
throw "Failed to write profiles registry: $_"
|
|
311
|
+
}
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
function Test-ProfileExists {
|
|
315
|
+
param([string]$ProfileName)
|
|
316
|
+
|
|
317
|
+
Initialize-ProfilesJson
|
|
318
|
+
$Data = Read-ProfilesJson
|
|
319
|
+
return $Data.profiles.PSObject.Properties.Name -contains $ProfileName
|
|
320
|
+
}
|
|
321
|
+
|
|
322
|
+
function Register-Profile {
|
|
323
|
+
param([string]$ProfileName)
|
|
324
|
+
|
|
325
|
+
if (Test-ProfileExists $ProfileName) {
|
|
326
|
+
throw "Profile already exists: $ProfileName"
|
|
327
|
+
}
|
|
328
|
+
|
|
329
|
+
$Data = Read-ProfilesJson
|
|
330
|
+
$Timestamp = (Get-Date).ToUniversalTime().ToString("o")
|
|
331
|
+
|
|
332
|
+
$Data.profiles | Add-Member -NotePropertyName $ProfileName -NotePropertyValue ([PSCustomObject]@{
|
|
333
|
+
type = "account"
|
|
334
|
+
created = $Timestamp
|
|
335
|
+
last_used = $null
|
|
336
|
+
})
|
|
337
|
+
|
|
338
|
+
# Note: No longer auto-set as default
|
|
339
|
+
# Users must explicitly run: ccs auth default <profile>
|
|
340
|
+
# Default always stays on implicit 'default' profile (uses ~/.claude/)
|
|
341
|
+
|
|
342
|
+
Write-ProfilesJson $Data
|
|
343
|
+
}
|
|
344
|
+
|
|
345
|
+
function Unregister-Profile {
|
|
346
|
+
param([string]$ProfileName)
|
|
347
|
+
|
|
348
|
+
if (-not (Test-ProfileExists $ProfileName)) { return } # Idempotent
|
|
349
|
+
|
|
350
|
+
$Data = Read-ProfilesJson
|
|
351
|
+
|
|
352
|
+
# Remove profile
|
|
353
|
+
$Data.profiles.PSObject.Properties.Remove($ProfileName)
|
|
354
|
+
|
|
355
|
+
# Update default if it was the deleted profile
|
|
356
|
+
if ($Data.default -eq $ProfileName) {
|
|
357
|
+
$Remaining = $Data.profiles.PSObject.Properties.Name
|
|
358
|
+
$Data.default = if ($Remaining.Count -gt 0) { $Remaining[0] } else { $null }
|
|
359
|
+
}
|
|
360
|
+
|
|
361
|
+
Write-ProfilesJson $Data
|
|
362
|
+
}
|
|
363
|
+
|
|
364
|
+
function Update-ProfileTimestamp {
|
|
365
|
+
param([string]$ProfileName)
|
|
366
|
+
|
|
367
|
+
if (-not (Test-ProfileExists $ProfileName)) { return } # Silent fail
|
|
368
|
+
|
|
369
|
+
$Data = Read-ProfilesJson
|
|
370
|
+
$Timestamp = (Get-Date).ToUniversalTime().ToString("o")
|
|
371
|
+
|
|
372
|
+
$Data.profiles.$ProfileName.last_used = $Timestamp
|
|
373
|
+
|
|
374
|
+
Write-ProfilesJson $Data
|
|
375
|
+
}
|
|
376
|
+
|
|
377
|
+
function Get-DefaultProfile {
|
|
378
|
+
Initialize-ProfilesJson
|
|
379
|
+
$Data = Read-ProfilesJson
|
|
380
|
+
return $Data.default
|
|
381
|
+
}
|
|
382
|
+
|
|
383
|
+
function Set-DefaultProfile {
|
|
384
|
+
param([string]$ProfileName)
|
|
385
|
+
|
|
386
|
+
if (-not (Test-ProfileExists $ProfileName)) {
|
|
387
|
+
throw "Profile not found: $ProfileName"
|
|
388
|
+
}
|
|
389
|
+
|
|
390
|
+
$Data = Read-ProfilesJson
|
|
391
|
+
$Data.default = $ProfileName
|
|
392
|
+
|
|
393
|
+
Write-ProfilesJson $Data
|
|
394
|
+
}
|
|
395
|
+
|
|
396
|
+
# --- Instance Management Functions (Phase 2) ---
|
|
397
|
+
|
|
398
|
+
function Get-SanitizedProfileName {
|
|
399
|
+
param([string]$Name)
|
|
400
|
+
|
|
401
|
+
# Replace unsafe chars, lowercase
|
|
402
|
+
return $Name.ToLower() -replace '[^a-z0-9_-]', '-'
|
|
403
|
+
}
|
|
404
|
+
|
|
405
|
+
function Set-InstancePermissions {
|
|
406
|
+
param([string]$Path)
|
|
407
|
+
|
|
408
|
+
# Get current user
|
|
409
|
+
$CurrentUser = [System.Security.Principal.WindowsIdentity]::GetCurrent().Name
|
|
410
|
+
|
|
411
|
+
# Create new ACL with inheritance disabled
|
|
412
|
+
$Acl = Get-Acl $Path
|
|
413
|
+
$Acl.SetAccessRuleProtection($true, $false) # Disable inheritance
|
|
414
|
+
$Acl.Access | ForEach-Object { $Acl.RemoveAccessRule($_) | Out-Null }
|
|
415
|
+
|
|
416
|
+
# Grant full control to current user only
|
|
417
|
+
$Rule = New-Object System.Security.AccessControl.FileSystemAccessRule(
|
|
418
|
+
$CurrentUser, "FullControl", "ContainerInherit,ObjectInherit", "None", "Allow"
|
|
419
|
+
)
|
|
420
|
+
$Acl.AddAccessRule($Rule)
|
|
421
|
+
|
|
422
|
+
Set-Acl -Path $Path -AclObject $Acl
|
|
423
|
+
}
|
|
424
|
+
|
|
425
|
+
function Copy-GlobalConfigs {
|
|
426
|
+
param([string]$InstancePath)
|
|
427
|
+
|
|
428
|
+
$GlobalClaude = "$env:USERPROFILE\.claude"
|
|
429
|
+
|
|
430
|
+
# Copy settings.json
|
|
431
|
+
$GlobalSettings = Join-Path $GlobalClaude "settings.json"
|
|
432
|
+
if (Test-Path $GlobalSettings) {
|
|
433
|
+
Copy-Item $GlobalSettings -Destination (Join-Path $InstancePath "settings.json") -ErrorAction SilentlyContinue
|
|
434
|
+
}
|
|
435
|
+
|
|
436
|
+
# Copy commands/
|
|
437
|
+
$GlobalCommands = Join-Path $GlobalClaude "commands"
|
|
438
|
+
if (Test-Path $GlobalCommands) {
|
|
439
|
+
Copy-Item $GlobalCommands -Destination $InstancePath -Recurse -ErrorAction SilentlyContinue
|
|
440
|
+
}
|
|
441
|
+
|
|
442
|
+
# Copy skills/
|
|
443
|
+
$GlobalSkills = Join-Path $GlobalClaude "skills"
|
|
444
|
+
if (Test-Path $GlobalSkills) {
|
|
445
|
+
Copy-Item $GlobalSkills -Destination $InstancePath -Recurse -ErrorAction SilentlyContinue
|
|
446
|
+
}
|
|
447
|
+
}
|
|
448
|
+
|
|
449
|
+
function Initialize-Instance {
|
|
450
|
+
param([string]$InstancePath)
|
|
451
|
+
|
|
452
|
+
# Create base directory with user-only ACL
|
|
453
|
+
New-Item -ItemType Directory -Path $InstancePath -Force | Out-Null
|
|
454
|
+
Set-InstancePermissions $InstancePath
|
|
455
|
+
|
|
456
|
+
# Create subdirectories
|
|
457
|
+
$Subdirs = @('session-env', 'todos', 'logs', 'file-history',
|
|
458
|
+
'shell-snapshots', 'debug', '.anthropic', 'commands', 'skills')
|
|
459
|
+
|
|
460
|
+
foreach ($Dir in $Subdirs) {
|
|
461
|
+
$DirPath = Join-Path $InstancePath $Dir
|
|
462
|
+
New-Item -ItemType Directory -Path $DirPath -Force | Out-Null
|
|
463
|
+
}
|
|
464
|
+
|
|
465
|
+
# Copy global configs
|
|
466
|
+
Copy-GlobalConfigs $InstancePath
|
|
467
|
+
}
|
|
468
|
+
|
|
469
|
+
function Validate-Instance {
|
|
470
|
+
param([string]$InstancePath)
|
|
471
|
+
|
|
472
|
+
$RequiredDirs = @('session-env', 'todos', 'logs', 'file-history',
|
|
473
|
+
'shell-snapshots', 'debug', '.anthropic')
|
|
474
|
+
|
|
475
|
+
foreach ($Dir in $RequiredDirs) {
|
|
476
|
+
$DirPath = Join-Path $InstancePath $Dir
|
|
477
|
+
if (-not (Test-Path $DirPath)) {
|
|
478
|
+
New-Item -ItemType Directory -Path $DirPath -Force | Out-Null
|
|
479
|
+
}
|
|
480
|
+
}
|
|
481
|
+
}
|
|
482
|
+
|
|
483
|
+
function Ensure-Instance {
|
|
484
|
+
param([string]$ProfileName)
|
|
485
|
+
|
|
486
|
+
$SafeName = Get-SanitizedProfileName $ProfileName
|
|
487
|
+
$InstancePath = "$InstancesDir\$SafeName"
|
|
488
|
+
|
|
489
|
+
if (-not (Test-Path $InstancePath)) {
|
|
490
|
+
Initialize-Instance $InstancePath
|
|
491
|
+
}
|
|
492
|
+
|
|
493
|
+
Validate-Instance $InstancePath
|
|
494
|
+
|
|
495
|
+
return $InstancePath
|
|
496
|
+
}
|
|
497
|
+
|
|
498
|
+
# --- Profile Detection Logic (Phase 1) ---
|
|
499
|
+
|
|
500
|
+
function Get-AvailableProfiles {
|
|
501
|
+
$lines = @()
|
|
502
|
+
|
|
503
|
+
# Settings-based profiles
|
|
504
|
+
if (Test-Path $ConfigFile) {
|
|
505
|
+
try {
|
|
506
|
+
$Config = Get-Content $ConfigFile -Raw | ConvertFrom-Json
|
|
507
|
+
$SettingsProfiles = $Config.profiles.PSObject.Properties.Name
|
|
508
|
+
|
|
509
|
+
if ($SettingsProfiles.Count -gt 0) {
|
|
510
|
+
$lines += "Settings-based profiles (GLM, Kimi, etc.):"
|
|
511
|
+
foreach ($name in $SettingsProfiles) {
|
|
512
|
+
$lines += " - $name"
|
|
513
|
+
}
|
|
514
|
+
}
|
|
515
|
+
} catch {}
|
|
516
|
+
}
|
|
517
|
+
|
|
518
|
+
# Account-based profiles
|
|
519
|
+
if (Test-Path $ProfilesJson) {
|
|
520
|
+
try {
|
|
521
|
+
$Profiles = Read-ProfilesJson
|
|
522
|
+
$AccountProfiles = $Profiles.profiles.PSObject.Properties.Name
|
|
523
|
+
|
|
524
|
+
if ($AccountProfiles.Count -gt 0) {
|
|
525
|
+
$lines += "Account-based profiles:"
|
|
526
|
+
foreach ($name in $AccountProfiles) {
|
|
527
|
+
$IsDefault = if ($name -eq $Profiles.default) { " [DEFAULT]" } else { "" }
|
|
528
|
+
$lines += " - $name$IsDefault"
|
|
529
|
+
}
|
|
530
|
+
}
|
|
531
|
+
} catch {}
|
|
532
|
+
}
|
|
533
|
+
|
|
534
|
+
if ($lines.Count -eq 0) {
|
|
535
|
+
return " (no profiles configured)`n Run `"ccs auth create <profile>`" to create your first account profile."
|
|
536
|
+
} else {
|
|
537
|
+
return $lines -join "`n"
|
|
538
|
+
}
|
|
539
|
+
}
|
|
540
|
+
|
|
541
|
+
function Get-ProfileType {
|
|
542
|
+
param([string]$ProfileName)
|
|
543
|
+
|
|
544
|
+
# Special case: 'default' resolves to default profile
|
|
545
|
+
if ($ProfileName -eq "default") {
|
|
546
|
+
# Check account-based default first
|
|
547
|
+
if (Test-Path $ProfilesJson) {
|
|
548
|
+
$Profiles = Read-ProfilesJson
|
|
549
|
+
$DefaultAccount = $Profiles.default
|
|
550
|
+
|
|
551
|
+
if ($DefaultAccount -and (Test-ProfileExists $DefaultAccount)) {
|
|
552
|
+
return @{
|
|
553
|
+
Type = "account"
|
|
554
|
+
Name = $DefaultAccount
|
|
555
|
+
}
|
|
556
|
+
}
|
|
557
|
+
}
|
|
558
|
+
|
|
559
|
+
# Check settings-based default
|
|
560
|
+
if (Test-Path $ConfigFile) {
|
|
561
|
+
$Config = Get-Content $ConfigFile -Raw | ConvertFrom-Json
|
|
562
|
+
$DefaultSettings = $Config.profiles.default
|
|
563
|
+
|
|
564
|
+
if ($DefaultSettings) {
|
|
565
|
+
return @{
|
|
566
|
+
Type = "settings"
|
|
567
|
+
Path = $DefaultSettings
|
|
568
|
+
Name = "default"
|
|
569
|
+
}
|
|
570
|
+
}
|
|
571
|
+
}
|
|
572
|
+
|
|
573
|
+
# No default configured, use Claude's defaults
|
|
574
|
+
return @{
|
|
575
|
+
Type = "default"
|
|
576
|
+
Name = "default"
|
|
577
|
+
}
|
|
578
|
+
}
|
|
579
|
+
|
|
580
|
+
# Priority 1: Check settings-based profiles (backward compatibility)
|
|
581
|
+
if (Test-Path $ConfigFile) {
|
|
582
|
+
try {
|
|
583
|
+
$Config = Get-Content $ConfigFile -Raw | ConvertFrom-Json
|
|
584
|
+
$SettingsPath = $Config.profiles.$ProfileName
|
|
585
|
+
|
|
586
|
+
if ($SettingsPath) {
|
|
587
|
+
return @{
|
|
588
|
+
Type = "settings"
|
|
589
|
+
Path = $SettingsPath
|
|
590
|
+
Name = $ProfileName
|
|
591
|
+
}
|
|
592
|
+
}
|
|
593
|
+
} catch {}
|
|
594
|
+
}
|
|
595
|
+
|
|
596
|
+
# Priority 2: Check account-based profiles
|
|
597
|
+
if ((Test-Path $ProfilesJson) -and (Test-ProfileExists $ProfileName)) {
|
|
598
|
+
return @{
|
|
599
|
+
Type = "account"
|
|
600
|
+
Name = $ProfileName
|
|
601
|
+
}
|
|
602
|
+
}
|
|
603
|
+
|
|
604
|
+
# Not found
|
|
605
|
+
return @{
|
|
606
|
+
Type = "error"
|
|
607
|
+
}
|
|
608
|
+
}
|
|
609
|
+
|
|
610
|
+
# --- Auth Commands (Phase 3) ---
|
|
611
|
+
|
|
612
|
+
function Show-AuthHelp {
|
|
613
|
+
Write-Host ""
|
|
614
|
+
Write-Host "CCS Account Management" -ForegroundColor White
|
|
615
|
+
Write-Host ""
|
|
616
|
+
Write-Host "Usage:" -ForegroundColor Cyan
|
|
617
|
+
Write-Host " ccs auth <command> [options]" -ForegroundColor Yellow
|
|
618
|
+
Write-Host ""
|
|
619
|
+
Write-Host "Commands:" -ForegroundColor Cyan
|
|
620
|
+
Write-Host " create <profile> Create new profile and login" -ForegroundColor Yellow
|
|
621
|
+
Write-Host " list List all saved profiles" -ForegroundColor Yellow
|
|
622
|
+
Write-Host " show <profile> Show profile details" -ForegroundColor Yellow
|
|
623
|
+
Write-Host " remove <profile> Remove saved profile" -ForegroundColor Yellow
|
|
624
|
+
Write-Host " default <profile> Set default profile" -ForegroundColor Yellow
|
|
625
|
+
Write-Host ""
|
|
626
|
+
Write-Host "Examples:" -ForegroundColor Cyan
|
|
627
|
+
Write-Host " ccs auth create work # Create & login to work profile" -ForegroundColor Yellow
|
|
628
|
+
Write-Host " ccs auth default work # Set work as default" -ForegroundColor Yellow
|
|
629
|
+
Write-Host " ccs auth list # List all profiles" -ForegroundColor Yellow
|
|
630
|
+
Write-Host ' ccs work "review code" # Use work profile' -ForegroundColor Yellow
|
|
631
|
+
Write-Host ' ccs "review code" # Use default profile' -ForegroundColor Yellow
|
|
632
|
+
Write-Host ""
|
|
633
|
+
Write-Host "Note:" -ForegroundColor Cyan
|
|
634
|
+
Write-Host " By default, " -NoNewline
|
|
635
|
+
Write-Host "ccs" -ForegroundColor Yellow -NoNewline
|
|
636
|
+
Write-Host " uses Claude CLI defaults from ~/.claude/"
|
|
637
|
+
Write-Host " Use " -NoNewline
|
|
638
|
+
Write-Host "ccs auth default <profile>" -ForegroundColor Yellow -NoNewline
|
|
639
|
+
Write-Host " to change the default profile."
|
|
640
|
+
Write-Host ""
|
|
641
|
+
}
|
|
642
|
+
|
|
643
|
+
function Invoke-AuthCreate {
|
|
644
|
+
param([string[]]$Args)
|
|
645
|
+
|
|
646
|
+
$ProfileName = ""
|
|
647
|
+
$Force = $false
|
|
648
|
+
|
|
649
|
+
foreach ($arg in $Args) {
|
|
650
|
+
if ($arg -eq "--force") {
|
|
651
|
+
$Force = $true
|
|
652
|
+
} elseif ($arg -match '^-') {
|
|
653
|
+
Write-ErrorMsg "Unknown option: $arg"
|
|
654
|
+
return 1
|
|
655
|
+
} else {
|
|
656
|
+
$ProfileName = $arg
|
|
657
|
+
}
|
|
658
|
+
}
|
|
659
|
+
|
|
660
|
+
if (-not $ProfileName) {
|
|
661
|
+
Write-ErrorMsg "Profile name is required`n`nUsage: ccs auth create <profile> [--force]"
|
|
662
|
+
return 1
|
|
663
|
+
}
|
|
664
|
+
|
|
665
|
+
if (-not $Force -and (Test-ProfileExists $ProfileName)) {
|
|
666
|
+
Write-ErrorMsg "Profile already exists: $ProfileName`nUse --force to overwrite"
|
|
667
|
+
return 1
|
|
668
|
+
}
|
|
669
|
+
|
|
670
|
+
# Create instance
|
|
671
|
+
Write-Host "[i] Creating profile: $ProfileName"
|
|
672
|
+
$InstancePath = Ensure-Instance $ProfileName
|
|
673
|
+
Write-Host "[i] Instance directory: $InstancePath"
|
|
674
|
+
Write-Host ""
|
|
675
|
+
|
|
676
|
+
# Register profile
|
|
677
|
+
Register-Profile $ProfileName
|
|
678
|
+
|
|
679
|
+
# Launch Claude for login
|
|
680
|
+
Write-Host "[i] Starting Claude in isolated instance..." -ForegroundColor Yellow
|
|
681
|
+
Write-Host "[i] You will be prompted to login with your account." -ForegroundColor Yellow
|
|
682
|
+
Write-Host ""
|
|
683
|
+
|
|
684
|
+
$env:CLAUDE_CONFIG_DIR = $InstancePath
|
|
685
|
+
$ClaudeCli = Find-ClaudeCli
|
|
686
|
+
& $ClaudeCli
|
|
687
|
+
|
|
688
|
+
if ($LASTEXITCODE -ne 0) {
|
|
689
|
+
Write-ErrorMsg "Login failed or cancelled`nTo retry: ccs auth create $ProfileName --force"
|
|
690
|
+
return 1
|
|
691
|
+
}
|
|
692
|
+
|
|
693
|
+
Write-Host ""
|
|
694
|
+
Write-Host "[OK] Profile created successfully" -ForegroundColor Green
|
|
695
|
+
Write-Host ""
|
|
696
|
+
Write-Host " Profile: $ProfileName"
|
|
697
|
+
Write-Host " Instance: $InstancePath"
|
|
698
|
+
Write-Host ""
|
|
699
|
+
Write-Host "Usage:"
|
|
700
|
+
Write-Host " ccs $ProfileName `"your prompt here`" # Use this specific profile" -ForegroundColor Yellow
|
|
701
|
+
Write-Host ""
|
|
702
|
+
Write-Host 'To set as default (so you can use just "ccs"):'
|
|
703
|
+
Write-Host " ccs auth default $ProfileName" -ForegroundColor Yellow
|
|
704
|
+
Write-Host ""
|
|
705
|
+
}
|
|
706
|
+
|
|
707
|
+
function Invoke-AuthList {
|
|
708
|
+
param([string[]]$Args)
|
|
709
|
+
|
|
710
|
+
$Verbose = $Args -contains "--verbose"
|
|
711
|
+
|
|
712
|
+
if (-not (Test-Path $ProfilesJson)) {
|
|
713
|
+
Write-Host "No account profiles found" -ForegroundColor Yellow
|
|
714
|
+
Write-Host ""
|
|
715
|
+
Write-Host "To create your first profile:"
|
|
716
|
+
Write-Host " ccs auth create <profile>" -ForegroundColor Yellow
|
|
717
|
+
return
|
|
718
|
+
}
|
|
719
|
+
|
|
720
|
+
$Data = Read-ProfilesJson
|
|
721
|
+
$Profiles = $Data.profiles.PSObject.Properties.Name
|
|
722
|
+
|
|
723
|
+
if ($Profiles.Count -eq 0) {
|
|
724
|
+
Write-Host "No account profiles found" -ForegroundColor Yellow
|
|
725
|
+
return
|
|
726
|
+
}
|
|
727
|
+
|
|
728
|
+
Write-Host "Saved Account Profiles:" -ForegroundColor White
|
|
729
|
+
Write-Host ""
|
|
730
|
+
|
|
731
|
+
foreach ($profile in $Profiles) {
|
|
732
|
+
$IsDefault = $profile -eq $Data.default
|
|
733
|
+
|
|
734
|
+
if ($IsDefault) {
|
|
735
|
+
Write-Host "[*] " -ForegroundColor Green -NoNewline
|
|
736
|
+
Write-Host $profile -ForegroundColor Cyan -NoNewline
|
|
737
|
+
Write-Host " (default)" -ForegroundColor Green
|
|
738
|
+
} else {
|
|
739
|
+
Write-Host "[ ] " -NoNewline
|
|
740
|
+
Write-Host $profile -ForegroundColor Cyan
|
|
741
|
+
}
|
|
742
|
+
|
|
743
|
+
$Type = $Data.profiles.$profile.type
|
|
744
|
+
Write-Host " Type: $Type"
|
|
745
|
+
|
|
746
|
+
if ($Verbose) {
|
|
747
|
+
$Created = $Data.profiles.$profile.created
|
|
748
|
+
$LastUsed = $Data.profiles.$profile.last_used
|
|
749
|
+
if (-not $LastUsed) { $LastUsed = "Never" }
|
|
750
|
+
Write-Host " Created: $Created"
|
|
751
|
+
Write-Host " Last used: $LastUsed"
|
|
752
|
+
}
|
|
753
|
+
|
|
754
|
+
Write-Host ""
|
|
755
|
+
}
|
|
756
|
+
}
|
|
757
|
+
|
|
758
|
+
function Invoke-AuthShow {
|
|
759
|
+
param([string[]]$Args)
|
|
760
|
+
|
|
761
|
+
$ProfileName = $Args[0]
|
|
762
|
+
|
|
763
|
+
if (-not $ProfileName) {
|
|
764
|
+
Write-ErrorMsg "Profile name is required`nUsage: ccs auth show <profile>"
|
|
765
|
+
return 1
|
|
766
|
+
}
|
|
767
|
+
|
|
768
|
+
if (-not (Test-ProfileExists $ProfileName)) {
|
|
769
|
+
Write-ErrorMsg "Profile not found: $ProfileName"
|
|
770
|
+
return 1
|
|
771
|
+
}
|
|
772
|
+
|
|
773
|
+
$Data = Read-ProfilesJson
|
|
774
|
+
$IsDefault = $ProfileName -eq $Data.default
|
|
775
|
+
|
|
776
|
+
Write-Host "Profile: $ProfileName" -ForegroundColor White
|
|
777
|
+
Write-Host ""
|
|
778
|
+
|
|
779
|
+
$Type = $Data.profiles.$ProfileName.type
|
|
780
|
+
$Created = $Data.profiles.$ProfileName.created
|
|
781
|
+
$LastUsed = $Data.profiles.$ProfileName.last_used
|
|
782
|
+
if (-not $LastUsed) { $LastUsed = "Never" }
|
|
783
|
+
$InstancePath = "$InstancesDir\$(Get-SanitizedProfileName $ProfileName)"
|
|
784
|
+
|
|
785
|
+
Write-Host " Type: $Type"
|
|
786
|
+
Write-Host " Default: $(if ($IsDefault) { 'Yes' } else { 'No' })"
|
|
787
|
+
Write-Host " Instance: $InstancePath"
|
|
788
|
+
Write-Host " Created: $Created"
|
|
789
|
+
Write-Host " Last used: $LastUsed"
|
|
790
|
+
Write-Host ""
|
|
791
|
+
}
|
|
792
|
+
|
|
793
|
+
function Invoke-AuthRemove {
|
|
794
|
+
param([string[]]$Args)
|
|
795
|
+
|
|
796
|
+
$ProfileName = ""
|
|
797
|
+
$Force = $false
|
|
798
|
+
|
|
799
|
+
foreach ($arg in $Args) {
|
|
800
|
+
if ($arg -eq "--force") {
|
|
801
|
+
$Force = $true
|
|
802
|
+
} else {
|
|
803
|
+
$ProfileName = $arg
|
|
804
|
+
}
|
|
805
|
+
}
|
|
806
|
+
|
|
807
|
+
if (-not $ProfileName) {
|
|
808
|
+
Write-ErrorMsg "Profile name is required`nUsage: ccs auth remove <profile> --force"
|
|
809
|
+
return 1
|
|
810
|
+
}
|
|
811
|
+
|
|
812
|
+
if (-not (Test-ProfileExists $ProfileName)) {
|
|
813
|
+
Write-ErrorMsg "Profile not found: $ProfileName"
|
|
814
|
+
return 1
|
|
815
|
+
}
|
|
816
|
+
|
|
817
|
+
if (-not $Force) {
|
|
818
|
+
Write-ErrorMsg "Removal requires --force flag for safety`nRun: ccs auth remove $ProfileName --force"
|
|
819
|
+
return 1
|
|
820
|
+
}
|
|
821
|
+
|
|
822
|
+
# Delete instance directory
|
|
823
|
+
$InstancePath = "$InstancesDir\$(Get-SanitizedProfileName $ProfileName)"
|
|
824
|
+
if (Test-Path $InstancePath) {
|
|
825
|
+
Remove-Item $InstancePath -Recurse -Force
|
|
826
|
+
}
|
|
827
|
+
|
|
828
|
+
# Remove from registry
|
|
829
|
+
Unregister-Profile $ProfileName
|
|
830
|
+
|
|
831
|
+
Write-Host "[OK] Profile removed successfully" -ForegroundColor Green
|
|
832
|
+
Write-Host " Profile: $ProfileName"
|
|
833
|
+
Write-Host ""
|
|
834
|
+
}
|
|
835
|
+
|
|
836
|
+
function Invoke-AuthDefault {
|
|
837
|
+
param([string[]]$Args)
|
|
838
|
+
|
|
839
|
+
$ProfileName = $Args[0]
|
|
840
|
+
|
|
841
|
+
if (-not $ProfileName) {
|
|
842
|
+
Write-ErrorMsg "Profile name is required`nUsage: ccs auth default <profile>"
|
|
843
|
+
return 1
|
|
844
|
+
}
|
|
845
|
+
|
|
846
|
+
if (-not (Test-ProfileExists $ProfileName)) {
|
|
847
|
+
Write-ErrorMsg "Profile not found: $ProfileName"
|
|
848
|
+
return 1
|
|
849
|
+
}
|
|
850
|
+
|
|
851
|
+
Set-DefaultProfile $ProfileName
|
|
852
|
+
|
|
853
|
+
Write-Host "[OK] Default profile set" -ForegroundColor Green
|
|
854
|
+
Write-Host " Profile: $ProfileName"
|
|
855
|
+
Write-Host ""
|
|
856
|
+
Write-Host "Now you can use:"
|
|
857
|
+
Write-Host " ccs `"your prompt`" # Uses $ProfileName profile" -ForegroundColor Yellow
|
|
858
|
+
Write-Host ""
|
|
859
|
+
}
|
|
860
|
+
|
|
861
|
+
function Invoke-AuthCommands {
|
|
862
|
+
param([string[]]$Args)
|
|
863
|
+
|
|
864
|
+
$Subcommand = $Args[0]
|
|
865
|
+
$SubArgs = if ($Args.Count -gt 1) { $Args[1..($Args.Count-1)] } else { @() }
|
|
866
|
+
|
|
867
|
+
switch ($Subcommand) {
|
|
868
|
+
"create" { Invoke-AuthCreate $SubArgs }
|
|
869
|
+
"list" { Invoke-AuthList $SubArgs }
|
|
870
|
+
"show" { Invoke-AuthShow $SubArgs }
|
|
871
|
+
"remove" { Invoke-AuthRemove $SubArgs }
|
|
872
|
+
"default" { Invoke-AuthDefault $SubArgs }
|
|
873
|
+
default { Show-AuthHelp }
|
|
874
|
+
}
|
|
875
|
+
}
|
|
876
|
+
|
|
877
|
+
# --- Main Execution Logic ---
|
|
878
|
+
|
|
208
879
|
# Special case: version command (check BEFORE profile detection)
|
|
209
|
-
# Handle switch parameters and remaining arguments
|
|
210
880
|
if ($Version) {
|
|
211
881
|
Show-Version
|
|
212
882
|
exit 0
|
|
@@ -230,30 +900,17 @@ if ($Help) {
|
|
|
230
900
|
}
|
|
231
901
|
}
|
|
232
902
|
|
|
233
|
-
# Special case:
|
|
234
|
-
if ($
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
Write-Host "The --install flag is currently under development."
|
|
239
|
-
Write-Host ".claude/ integration testing is not complete."
|
|
240
|
-
Write-Host ""
|
|
241
|
-
Write-Host "For updates: https://github.com/kaitranntt/ccs/issues"
|
|
242
|
-
Write-Host ""
|
|
243
|
-
exit 0
|
|
903
|
+
# Special case: auth commands
|
|
904
|
+
if ($RemainingArgs.Count -gt 0 -and $RemainingArgs[0] -eq "auth") {
|
|
905
|
+
$AuthArgs = if ($RemainingArgs.Count -gt 1) { $RemainingArgs[1..($RemainingArgs.Count-1)] } else { @() }
|
|
906
|
+
Invoke-AuthCommands $AuthArgs
|
|
907
|
+
exit $LASTEXITCODE
|
|
244
908
|
}
|
|
245
909
|
|
|
246
|
-
#
|
|
247
|
-
if (
|
|
248
|
-
Write-
|
|
249
|
-
|
|
250
|
-
Write-Host ""
|
|
251
|
-
Write-Host "The --uninstall flag is currently under development."
|
|
252
|
-
Write-Host ".claude/ integration testing is not complete."
|
|
253
|
-
Write-Host ""
|
|
254
|
-
Write-Host "For updates: https://github.com/kaitranntt/ccs/issues"
|
|
255
|
-
Write-Host ""
|
|
256
|
-
exit 0
|
|
910
|
+
# Run auto-recovery before main logic
|
|
911
|
+
if (-not (Invoke-AutoRecovery)) {
|
|
912
|
+
Write-ErrorMsg "Auto-recovery failed. Check permissions."
|
|
913
|
+
exit 1
|
|
257
914
|
}
|
|
258
915
|
|
|
259
916
|
# Smart profile detection: if first arg starts with '-', it's a flag not a profile
|
|
@@ -267,20 +924,6 @@ if ($RemainingArgs.Count -eq 0 -or $RemainingArgs[0] -match '^-') {
|
|
|
267
924
|
$RemainingArgs = if ($RemainingArgs.Count -gt 1) { $RemainingArgs | Select-Object -Skip 1 } else { @() }
|
|
268
925
|
}
|
|
269
926
|
|
|
270
|
-
# Check config exists
|
|
271
|
-
if (-not (Test-Path $ConfigFile)) {
|
|
272
|
-
$ErrorMessage = "Config file not found: $ConfigFile" + "`n`n" +
|
|
273
|
-
"Solutions:" + "`n" +
|
|
274
|
-
" 1. Reinstall CCS:" + "`n" +
|
|
275
|
-
" irm ccs.kaitran.ca/install | iex" + "`n`n" +
|
|
276
|
-
" 2. Or create config manually:" + "`n" +
|
|
277
|
-
" New-Item -ItemType Directory -Force -Path '$env:USERPROFILE\.ccs'" + "`n" +
|
|
278
|
-
" Set-Content -Path '$env:USERPROFILE\.ccs\config.json' -Value '{`"profiles`":{`"glm`":`"~/.ccs/glm.settings.json`",`"kimi`":`"~/.ccs/kimi.settings.json`",`"default`":`"~/.claude/settings.json`"}}'"
|
|
279
|
-
|
|
280
|
-
Write-ErrorMsg $ErrorMessage
|
|
281
|
-
exit 1
|
|
282
|
-
}
|
|
283
|
-
|
|
284
927
|
# Validate profile name (alphanumeric, dash, underscore only)
|
|
285
928
|
if ($Profile -notmatch '^[a-zA-Z0-9_-]+$') {
|
|
286
929
|
$ErrorMessage = "Invalid profile name: $Profile" + "`n`n" +
|
|
@@ -290,66 +933,90 @@ if ($Profile -notmatch '^[a-zA-Z0-9_-]+$') {
|
|
|
290
933
|
exit 1
|
|
291
934
|
}
|
|
292
935
|
|
|
293
|
-
#
|
|
294
|
-
|
|
295
|
-
$ConfigContent = Get-Content $ConfigFile -Raw -ErrorAction Stop
|
|
296
|
-
$Config = $ConfigContent | ConvertFrom-Json -ErrorAction Stop
|
|
297
|
-
$SettingsPath = $Config.profiles.$Profile
|
|
936
|
+
# Detect profile type
|
|
937
|
+
$ProfileInfo = Get-ProfileType $Profile
|
|
298
938
|
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
$AvailableProfiles
|
|
304
|
-
|
|
305
|
-
Write-ErrorMsg $ErrorMessage
|
|
306
|
-
exit 1
|
|
307
|
-
}
|
|
308
|
-
} catch {
|
|
309
|
-
$ErrorMessage = "Invalid JSON in $ConfigFile" + "`n`n" +
|
|
310
|
-
"Fix the JSON syntax or reinstall:" + "`n" +
|
|
311
|
-
" irm ccs.kaitran.ca/install | iex"
|
|
939
|
+
if ($ProfileInfo.Type -eq "error") {
|
|
940
|
+
$ErrorMessage = "Profile '$Profile' not found" + "`n`n" +
|
|
941
|
+
"Available profiles:" + "`n" +
|
|
942
|
+
(Get-AvailableProfiles)
|
|
312
943
|
|
|
313
944
|
Write-ErrorMsg $ErrorMessage
|
|
314
945
|
exit 1
|
|
315
946
|
}
|
|
316
947
|
|
|
317
|
-
#
|
|
318
|
-
|
|
319
|
-
if ($SettingsPath -match '^~[/\\]') {
|
|
320
|
-
$SettingsPath = $SettingsPath -replace '^~', $env:USERPROFILE
|
|
321
|
-
}
|
|
948
|
+
# Detect Claude CLI executable
|
|
949
|
+
$ClaudeCli = Find-ClaudeCli
|
|
322
950
|
|
|
323
|
-
#
|
|
324
|
-
|
|
951
|
+
# Execute based on profile type (Phase 5)
|
|
952
|
+
switch ($ProfileInfo.Type) {
|
|
953
|
+
"account" {
|
|
954
|
+
# Account-based profile: use CLAUDE_CONFIG_DIR
|
|
955
|
+
$InstancePath = Ensure-Instance $ProfileInfo.Name
|
|
956
|
+
Update-ProfileTimestamp $ProfileInfo.Name # Update last_used
|
|
957
|
+
|
|
958
|
+
# Execute Claude with isolated config
|
|
959
|
+
$env:CLAUDE_CONFIG_DIR = $InstancePath
|
|
960
|
+
|
|
961
|
+
try {
|
|
962
|
+
if ($RemainingArgs) {
|
|
963
|
+
& $ClaudeCli @RemainingArgs
|
|
964
|
+
} else {
|
|
965
|
+
& $ClaudeCli
|
|
966
|
+
}
|
|
967
|
+
exit $LASTEXITCODE
|
|
968
|
+
} catch {
|
|
969
|
+
Show-ClaudeNotFoundError
|
|
970
|
+
exit 1
|
|
971
|
+
}
|
|
972
|
+
}
|
|
325
973
|
|
|
326
|
-
|
|
327
|
-
|
|
974
|
+
"settings" {
|
|
975
|
+
# Settings-based profile: use --settings flag
|
|
976
|
+
$SettingsPath = $ProfileInfo.Path
|
|
328
977
|
|
|
329
|
-
#
|
|
330
|
-
if (
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
" 3. Or reinstall: irm ccs.kaitran.ca/install | iex"
|
|
978
|
+
# Path expansion and normalization
|
|
979
|
+
if ($SettingsPath -match '^~[/\\]') {
|
|
980
|
+
$SettingsPath = $SettingsPath -replace '^~', $env:USERPROFILE
|
|
981
|
+
}
|
|
982
|
+
$SettingsPath = [System.Environment]::ExpandEnvironmentVariables($SettingsPath)
|
|
983
|
+
$SettingsPath = $SettingsPath -replace '/', '\'
|
|
336
984
|
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
985
|
+
if (-not (Test-Path $SettingsPath)) {
|
|
986
|
+
Write-ErrorMsg "Settings file not found: $SettingsPath"
|
|
987
|
+
exit 1
|
|
988
|
+
}
|
|
340
989
|
|
|
341
|
-
|
|
342
|
-
$
|
|
990
|
+
try {
|
|
991
|
+
if ($RemainingArgs) {
|
|
992
|
+
& $ClaudeCli --settings $SettingsPath @RemainingArgs
|
|
993
|
+
} else {
|
|
994
|
+
& $ClaudeCli --settings $SettingsPath
|
|
995
|
+
}
|
|
996
|
+
exit $LASTEXITCODE
|
|
997
|
+
} catch {
|
|
998
|
+
Show-ClaudeNotFoundError
|
|
999
|
+
exit 1
|
|
1000
|
+
}
|
|
1001
|
+
}
|
|
343
1002
|
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
1003
|
+
"default" {
|
|
1004
|
+
# Default: no special handling
|
|
1005
|
+
try {
|
|
1006
|
+
if ($RemainingArgs) {
|
|
1007
|
+
& $ClaudeCli @RemainingArgs
|
|
1008
|
+
} else {
|
|
1009
|
+
& $ClaudeCli
|
|
1010
|
+
}
|
|
1011
|
+
exit $LASTEXITCODE
|
|
1012
|
+
} catch {
|
|
1013
|
+
Show-ClaudeNotFoundError
|
|
1014
|
+
exit 1
|
|
1015
|
+
}
|
|
350
1016
|
}
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
}
|
|
1017
|
+
|
|
1018
|
+
default {
|
|
1019
|
+
Write-ErrorMsg "Unknown profile type: $($ProfileInfo.Type)"
|
|
1020
|
+
exit 1
|
|
1021
|
+
}
|
|
1022
|
+
}
|