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