claude-glm 1.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/install.ps1 ADDED
@@ -0,0 +1,963 @@
1
+ # Claude-GLM PowerShell Installer for Windows
2
+ # Works without admin rights, installs to user's profile directory
3
+ #
4
+ # Usage with parameters when downloading:
5
+ # Test error reporting:
6
+ # $env:CLAUDE_GLM_TEST_ERROR=1; iwr -useb https://raw.githubusercontent.com/JoeInnsp23/claude-glm-wrapper/main/install.ps1 | iex; $env:CLAUDE_GLM_TEST_ERROR=$null
7
+ #
8
+ # Enable debug mode:
9
+ # $env:CLAUDE_GLM_DEBUG=1; iwr -useb https://raw.githubusercontent.com/JoeInnsp23/claude-glm-wrapper/main/install.ps1 | iex; $env:CLAUDE_GLM_DEBUG=$null
10
+ #
11
+ # Usage when running locally:
12
+ # .\install.ps1 -TestError
13
+ # .\install.ps1 -Debug
14
+
15
+ param(
16
+ [switch]$TestError,
17
+ [switch]$Debug
18
+ )
19
+
20
+ # Support environment variables for parameters when using iwr | iex
21
+ if ($env:CLAUDE_GLM_TEST_ERROR -eq "1" -or $env:CLAUDE_GLM_TEST_ERROR -eq "true") {
22
+ $TestError = $true
23
+ }
24
+ if ($env:CLAUDE_GLM_DEBUG -eq "1" -or $env:CLAUDE_GLM_DEBUG -eq "true") {
25
+ $Debug = $true
26
+ }
27
+
28
+ # Configuration
29
+ $UserBinDir = "$env:USERPROFILE\.local\bin"
30
+ $GlmConfigDir = "$env:USERPROFILE\.claude-glm"
31
+ $Glm45ConfigDir = "$env:USERPROFILE\.claude-glm-45"
32
+ $GlmFastConfigDir = "$env:USERPROFILE\.claude-glm-fast"
33
+ $ZaiApiKey = "YOUR_ZAI_API_KEY_HERE"
34
+
35
+ # Debug logging
36
+ function Write-DebugLog {
37
+ param([string]$Message)
38
+ if ($Debug) {
39
+ Write-Host "DEBUG: $Message" -ForegroundColor Gray
40
+ }
41
+ }
42
+
43
+ # Find all existing wrapper installations
44
+ function Find-AllInstallations {
45
+ Write-DebugLog "Searching for existing installations..."
46
+ $locations = @(
47
+ "$env:USERPROFILE\.local\bin",
48
+ "$env:ProgramFiles\Claude-GLM",
49
+ "$env:LOCALAPPDATA\Programs\claude-glm",
50
+ "C:\Program Files\Claude-GLM"
51
+ )
52
+
53
+ $foundFiles = @()
54
+
55
+ foreach ($location in $locations) {
56
+ Write-DebugLog "Checking location: $location"
57
+ if (Test-Path $location) {
58
+ # Find all claude-glm*.ps1 files in this location
59
+ try {
60
+ $files = Get-ChildItem -Path $location -Filter "claude-glm*.ps1" -ErrorAction Stop
61
+ foreach ($file in $files) {
62
+ Write-DebugLog "Found: $($file.FullName)"
63
+ $foundFiles += $file.FullName
64
+ }
65
+ } catch {
66
+ Write-DebugLog "Could not access $location : $_"
67
+ # Continue searching other locations
68
+ }
69
+ }
70
+ }
71
+
72
+ Write-DebugLog "Total installations found: $($foundFiles.Count)"
73
+ return $foundFiles
74
+ }
75
+
76
+ # Clean up old wrapper installations
77
+ function Remove-OldWrappers {
78
+ $currentLocation = $UserBinDir
79
+ $allWrappers = Find-AllInstallations
80
+
81
+ if ($allWrappers.Count -eq 0) {
82
+ return
83
+ }
84
+
85
+ # Separate current location files from old ones
86
+ $oldWrappers = @()
87
+ $currentWrappers = @()
88
+
89
+ foreach ($wrapper in $allWrappers) {
90
+ if ($wrapper -like "$currentLocation*") {
91
+ $currentWrappers += $wrapper
92
+ } else {
93
+ $oldWrappers += $wrapper
94
+ }
95
+ }
96
+
97
+ # If no old wrappers found, nothing to clean
98
+ if ($oldWrappers.Count -eq 0) {
99
+ return
100
+ }
101
+
102
+ Write-Host ""
103
+ Write-Host "SEARCH: Found existing wrappers in multiple locations:"
104
+ Write-Host ""
105
+
106
+ foreach ($wrapper in $oldWrappers) {
107
+ Write-Host " REMOVED: $wrapper (old location)"
108
+ }
109
+
110
+ if ($currentWrappers.Count -gt 0) {
111
+ foreach ($wrapper in $currentWrappers) {
112
+ Write-Host " OK: $wrapper (current location)"
113
+ }
114
+ }
115
+
116
+ Write-Host ""
117
+ $cleanupChoice = Read-Host "Would you like to clean up old installations? (y/n)"
118
+
119
+ if ($cleanupChoice -eq "y" -or $cleanupChoice -eq "Y") {
120
+ Write-Host ""
121
+ Write-Host "Removing old wrappers..."
122
+ foreach ($wrapper in $oldWrappers) {
123
+ try {
124
+ Remove-Item -Path $wrapper -Force -ErrorAction Stop
125
+ Write-Host " OK: Removed: $wrapper"
126
+ } catch {
127
+ Write-Host " WARNING: Could not remove: $wrapper (permission denied)"
128
+ }
129
+ }
130
+ Write-Host ""
131
+ Write-Host "OK: Cleanup complete!"
132
+ } else {
133
+ Write-Host ""
134
+ Write-Host "WARNING: Skipping cleanup. Old wrappers may interfere with the new installation."
135
+ Write-Host " You may want to manually remove them later."
136
+ }
137
+
138
+ Write-Host ""
139
+ }
140
+
141
+ # Setup user bin directory and add to PATH
142
+ function Setup-UserBin {
143
+ # Create user bin directory
144
+ if (-not (Test-Path $UserBinDir)) {
145
+ New-Item -ItemType Directory -Path $UserBinDir -Force | Out-Null
146
+ }
147
+
148
+ # Check if PATH includes user bin
149
+ $currentPath = [Environment]::GetEnvironmentVariable("PATH", "User")
150
+ if ($currentPath -notlike "*$UserBinDir*") {
151
+ Write-Host "INFO: Adding $UserBinDir to PATH..."
152
+
153
+ # Add to user PATH
154
+ $newPath = if ($currentPath) { "$currentPath;$UserBinDir" } else { $UserBinDir }
155
+ [Environment]::SetEnvironmentVariable("PATH", $newPath, "User")
156
+
157
+ # Update current session PATH
158
+ $env:PATH = "$env:PATH;$UserBinDir"
159
+
160
+ Write-Host ""
161
+ Write-Host "WARNING: IMPORTANT: PATH has been updated for future sessions."
162
+ Write-Host " For this session, restart PowerShell or run: `$env:PATH += ';$UserBinDir'"
163
+ Write-Host ""
164
+ }
165
+ }
166
+
167
+ # Add aliases to PowerShell profile
168
+ function Add-PowerShellAliases {
169
+ # Ensure profile exists
170
+ if (-not (Test-Path $PROFILE)) {
171
+ $profileDir = Split-Path $PROFILE
172
+ if (-not (Test-Path $profileDir)) {
173
+ New-Item -ItemType Directory -Path $profileDir -Force | Out-Null
174
+ }
175
+ New-Item -ItemType File -Path $PROFILE -Force | Out-Null
176
+ }
177
+
178
+ # Read current profile
179
+ $profileContent = @()
180
+ if (Test-Path $PROFILE) {
181
+ try {
182
+ $profileContent = Get-Content $PROFILE -ErrorAction Stop
183
+ $lineCount = $profileContent.Count
184
+ Write-DebugLog "Read existing profile with $lineCount lines"
185
+ } catch {
186
+ Write-DebugLog "Could not read profile: $_"
187
+ $profileContent = @()
188
+ }
189
+ }
190
+
191
+ # Remove old aliases if they exist
192
+ $filteredContent = $profileContent | Where-Object {
193
+ $_ -notmatch "# Claude Code Model Switcher Aliases" -and
194
+ $_ -notmatch "Set-Alias cc " -and
195
+ $_ -notmatch "Set-Alias ccg " -and
196
+ $_ -notmatch "Set-Alias ccg45 " -and
197
+ $_ -notmatch "Set-Alias ccf "
198
+ }
199
+
200
+ # Add new aliases
201
+ $aliases = @"
202
+
203
+ # Claude Code Model Switcher Aliases
204
+ Set-Alias cc claude
205
+ Set-Alias ccg claude-glm
206
+ Set-Alias ccg45 claude-glm-4.5
207
+ Set-Alias ccf claude-glm-fast
208
+ "@
209
+
210
+ $newContent = $filteredContent + $aliases
211
+ Set-Content -Path $PROFILE -Value $newContent
212
+
213
+ Write-Host "OK: Added aliases to PowerShell profile: $PROFILE"
214
+ }
215
+
216
+ # Create the GLM-4.7 wrapper
217
+ function New-ClaudeGlmWrapper {
218
+ $wrapperPath = Join-Path $UserBinDir "claude-glm.ps1"
219
+
220
+ # Build wrapper content using array and join to avoid nested here-strings
221
+ $wrapperContent = @(
222
+ '# Claude-GLM - Claude Code with Z.AI GLM-4.7 (Standard Model)',
223
+ '',
224
+ '# Set Z.AI environment variables',
225
+ '$env:ANTHROPIC_BASE_URL = "https://api.z.ai/api/anthropic"',
226
+ "`$env:ANTHROPIC_AUTH_TOKEN = `"$ZaiApiKey`"",
227
+ '$env:ANTHROPIC_MODEL = "glm-4.7"',
228
+ '$env:ANTHROPIC_SMALL_FAST_MODEL = "glm-4.5-air"',
229
+ '',
230
+ '# Use custom config directory to avoid conflicts',
231
+ "`$env:CLAUDE_HOME = `"$GlmConfigDir`"",
232
+ '',
233
+ '# Create config directory if it doesn''t exist',
234
+ 'if (-not (Test-Path $env:CLAUDE_HOME)) {',
235
+ ' New-Item -ItemType Directory -Path $env:CLAUDE_HOME -Force | Out-Null',
236
+ '}',
237
+ '',
238
+ '# Create/update settings file with GLM configuration',
239
+ '$settingsJson = "{`"env`":{`"ANTHROPIC_BASE_URL`":`"https://api.z.ai/api/anthropic`",`"ANTHROPIC_AUTH_TOKEN`":`"' + $ZaiApiKey + '`",`"ANTHROPIC_MODEL`":`"glm-4.7`",`"ANTHROPIC_SMALL_FAST_MODEL`":`"glm-4.5-air`"}}"',
240
+ 'Set-Content -Path (Join-Path $env:CLAUDE_HOME "settings.json") -Value $settingsJson',
241
+ '',
242
+ '# Launch Claude Code with custom config',
243
+ 'Write-Host "LAUNCH: Starting Claude Code with GLM-4.7 (Standard Model)..."',
244
+ 'Write-Host "CONFIG: Config directory: $env:CLAUDE_HOME"',
245
+ 'Write-Host ""',
246
+ '',
247
+ '# Check if claude exists',
248
+ 'if (-not (Get-Command claude -ErrorAction SilentlyContinue)) {',
249
+ ' Write-Host "ERROR: ''claude'' command not found!"',
250
+ ' Write-Host "Please ensure Claude Code is installed and in your PATH"',
251
+ ' exit 1',
252
+ '}',
253
+ '',
254
+ '# Run the actual claude command',
255
+ '& claude $args'
256
+ ) -join "`n"
257
+
258
+ Set-Content -Path $wrapperPath -Value $wrapperContent
259
+ Write-Host "OK: Installed claude-glm at $wrapperPath" -ForegroundColor Green
260
+ }
261
+
262
+ # Create the GLM-4.5 wrapper
263
+ function New-ClaudeGlm45Wrapper {
264
+ $wrapperPath = Join-Path $UserBinDir "claude-glm-4.5.ps1"
265
+
266
+ # Build wrapper content using array and join to avoid nested here-strings
267
+ $wrapperContent = @(
268
+ '# Claude-GLM-4.5 - Claude Code with Z.AI GLM-4.5',
269
+ '',
270
+ '# Set Z.AI environment variables',
271
+ '$env:ANTHROPIC_BASE_URL = "https://api.z.ai/api/anthropic"',
272
+ "`$env:ANTHROPIC_AUTH_TOKEN = `"$ZaiApiKey`"",
273
+ '$env:ANTHROPIC_MODEL = "glm-4.5"',
274
+ '$env:ANTHROPIC_SMALL_FAST_MODEL = "glm-4.5-air"',
275
+ '',
276
+ '# Use custom config directory to avoid conflicts',
277
+ "`$env:CLAUDE_HOME = `"$Glm45ConfigDir`"",
278
+ '',
279
+ '# Create config directory if it doesn''t exist',
280
+ 'if (-not (Test-Path $env:CLAUDE_HOME)) {',
281
+ ' New-Item -ItemType Directory -Path $env:CLAUDE_HOME -Force | Out-Null',
282
+ '}',
283
+ '',
284
+ '# Create/update settings file with GLM configuration',
285
+ '$settingsJson = "{`"env`":{`"ANTHROPIC_BASE_URL`":`"https://api.z.ai/api/anthropic`",`"ANTHROPIC_AUTH_TOKEN`":`"' + $ZaiApiKey + '`",`"ANTHROPIC_MODEL`":`"glm-4.5`",`"ANTHROPIC_SMALL_FAST_MODEL`":`"glm-4.5-air`"}}"',
286
+ 'Set-Content -Path (Join-Path $env:CLAUDE_HOME "settings.json") -Value $settingsJson',
287
+ '',
288
+ '# Launch Claude Code with custom config',
289
+ 'Write-Host "LAUNCH: Starting Claude Code with GLM-4.5..."',
290
+ 'Write-Host "CONFIG: Config directory: $env:CLAUDE_HOME"',
291
+ 'Write-Host ""',
292
+ '',
293
+ '# Check if claude exists',
294
+ 'if (-not (Get-Command claude -ErrorAction SilentlyContinue)) {',
295
+ ' Write-Host "ERROR: ''claude'' command not found!"',
296
+ ' Write-Host "Please ensure Claude Code is installed and in your PATH"',
297
+ ' exit 1',
298
+ '}',
299
+ '',
300
+ '# Run the actual claude command',
301
+ '& claude $args'
302
+ ) -join "`n"
303
+
304
+ Set-Content -Path $wrapperPath -Value $wrapperContent
305
+ Write-Host "OK: Installed claude-glm-4.5 at $wrapperPath" -ForegroundColor Green
306
+ }
307
+
308
+ # Create the fast GLM-4.5-Air wrapper
309
+ function New-ClaudeGlmFastWrapper {
310
+ $wrapperPath = Join-Path $UserBinDir "claude-glm-fast.ps1"
311
+
312
+ # Build wrapper content using array and join to avoid nested here-strings
313
+ $wrapperContent = @(
314
+ '# Claude-GLM-Fast - Claude Code with Z.AI GLM-4.5-Air (Fast Model)',
315
+ '',
316
+ '# Set Z.AI environment variables',
317
+ '$env:ANTHROPIC_BASE_URL = "https://api.z.ai/api/anthropic"',
318
+ "`$env:ANTHROPIC_AUTH_TOKEN = `"$ZaiApiKey`"",
319
+ '$env:ANTHROPIC_MODEL = "glm-4.5-air"',
320
+ '$env:ANTHROPIC_SMALL_FAST_MODEL = "glm-4.5-air"',
321
+ '',
322
+ '# Use custom config directory to avoid conflicts',
323
+ "`$env:CLAUDE_HOME = `"$GlmFastConfigDir`"",
324
+ '',
325
+ '# Create config directory if it doesn''t exist',
326
+ 'if (-not (Test-Path $env:CLAUDE_HOME)) {',
327
+ ' New-Item -ItemType Directory -Path $env:CLAUDE_HOME -Force | Out-Null',
328
+ '}',
329
+ '',
330
+ '# Create/update settings file with GLM-Air configuration',
331
+ '$settingsJson = "{`"env`":{`"ANTHROPIC_BASE_URL`":`"https://api.z.ai/api/anthropic`",`"ANTHROPIC_AUTH_TOKEN`":`"' + $ZaiApiKey + '`",`"ANTHROPIC_MODEL`":`"glm-4.5-air`",`"ANTHROPIC_SMALL_FAST_MODEL`":`"glm-4.5-air`"}}"',
332
+ 'Set-Content -Path (Join-Path $env:CLAUDE_HOME "settings.json") -Value $settingsJson',
333
+ '',
334
+ '# Launch Claude Code with custom config',
335
+ 'Write-Host "FAST: Starting Claude Code with GLM-4.5-Air (Fast Model)..."',
336
+ 'Write-Host "CONFIG: Config directory: $env:CLAUDE_HOME"',
337
+ 'Write-Host ""',
338
+ '',
339
+ '# Check if claude exists',
340
+ 'if (-not (Get-Command claude -ErrorAction SilentlyContinue)) {',
341
+ ' Write-Host "ERROR: ''claude'' command not found!"',
342
+ ' Write-Host "Please ensure Claude Code is installed and in your PATH"',
343
+ ' exit 1',
344
+ '}',
345
+ '',
346
+ '# Run the actual claude command',
347
+ '& claude $args'
348
+ ) -join "`n"
349
+
350
+ Set-Content -Path $wrapperPath -Value $wrapperContent
351
+ Write-Host "OK: Installed claude-glm-fast at $wrapperPath" -ForegroundColor Green
352
+ }
353
+
354
+ # Install ccx multi-provider proxy
355
+ function Install-Ccx {
356
+ Write-Host "INSTALL: Installing ccx (multi-provider proxy)..." -ForegroundColor Cyan
357
+
358
+ $ccxHome = Join-Path $env:USERPROFILE ".claude-proxy"
359
+ $wrapperPath = Join-Path $UserBinDir "ccx.ps1"
360
+
361
+ # Create ccx home directory
362
+ if (-not (Test-Path $ccxHome)) {
363
+ New-Item -ItemType Directory -Path $ccxHome -Force | Out-Null
364
+ }
365
+
366
+ # Copy adapters directory from the npm package
367
+ $scriptDir = Split-Path -Parent $PSCommandPath
368
+
369
+ if (Test-Path (Join-Path $scriptDir "adapters")) {
370
+ Write-Host " Copying adapters to $ccxHome\adapters..."
371
+ $adaptersSource = Join-Path $scriptDir "adapters"
372
+ $adaptersTarget = Join-Path $ccxHome "adapters"
373
+ if (Test-Path $adaptersTarget) {
374
+ Remove-Item -Recurse -Force $adaptersTarget
375
+ }
376
+ Copy-Item -Recurse $adaptersSource $adaptersTarget
377
+ } else {
378
+ Write-Host " WARNING: adapters directory not found. Proxy may not work." -ForegroundColor Yellow
379
+ }
380
+
381
+ # Create ccx wrapper script
382
+ $ccxContent = @'
383
+ param([switch]$Setup)
384
+
385
+ $ErrorActionPreference = "Stop"
386
+
387
+ $ROOT_DIR = Join-Path $env:USERPROFILE ".claude-proxy"
388
+ $ENV_FILE = Join-Path $ROOT_DIR ".env"
389
+ $PORT = if ($env:CLAUDE_PROXY_PORT) { $env:CLAUDE_PROXY_PORT } else { 17870 }
390
+
391
+ if ($Setup) {
392
+ Write-Host "Setting up ~/.claude-proxy/.env..."
393
+ if (-not (Test-Path $ROOT_DIR)) {
394
+ New-Item -ItemType Directory -Path $ROOT_DIR | Out-Null
395
+ }
396
+
397
+ if (Test-Path $ENV_FILE) {
398
+ Write-Host "Existing .env found. Edit it manually at: $ENV_FILE"
399
+ exit 0
400
+ }
401
+
402
+ @"
403
+ # Claude Proxy Configuration
404
+ # Edit this file to add your API keys
405
+
406
+ # OpenAI (optional)
407
+ OPENAI_API_KEY=
408
+ OPENAI_BASE_URL=https://api.openai.com/v1
409
+
410
+ # OpenRouter (optional)
411
+ OPENROUTER_API_KEY=
412
+ OPENROUTER_BASE_URL=https://openrouter.ai/api/v1
413
+ OPENROUTER_REFERER=
414
+ OPENROUTER_TITLE=Claude Code via ccx
415
+
416
+ # Gemini (optional)
417
+ GEMINI_API_KEY=
418
+ GEMINI_BASE_URL=https://generativelanguage.googleapis.com/v1beta
419
+
420
+ # Z.AI GLM (optional)
421
+ GLM_UPSTREAM_URL=https://api.z.ai/api/anthropic
422
+ ZAI_API_KEY=
423
+
424
+ # Anthropic (optional)
425
+ ANTHROPIC_UPSTREAM_URL=https://api.anthropic.com
426
+ ANTHROPIC_API_KEY=
427
+ ANTHROPIC_VERSION=2023-06-01
428
+
429
+ # Proxy settings
430
+ CLAUDE_PROXY_PORT=17870
431
+ "@ | Out-File -FilePath $ENV_FILE -Encoding utf8
432
+
433
+ Write-Host "OK: Created $ENV_FILE"
434
+ Write-Host ""
435
+ Write-Host "Edit it to add your API keys, then run: ccx"
436
+ Write-Host ""
437
+ Write-Host "Example:"
438
+ Write-Host " notepad $ENV_FILE"
439
+ exit 0
440
+ }
441
+
442
+ # Load .env file
443
+ if (Test-Path $ENV_FILE) {
444
+ Get-Content $ENV_FILE | ForEach-Object {
445
+ if ($_ -match '^\s*([^#][^=]+)=(.*)$') {
446
+ $name = $matches[1].Trim()
447
+ $value = $matches[2].Trim()
448
+ if ($value) {
449
+ [Environment]::SetEnvironmentVariable($name, $value, "Process")
450
+ }
451
+ }
452
+ }
453
+ }
454
+
455
+ $env:ANTHROPIC_BASE_URL = "http://127.0.0.1:$PORT"
456
+ if (-not $env:ANTHROPIC_AUTH_TOKEN) {
457
+ $env:ANTHROPIC_AUTH_TOKEN = "local-proxy-token"
458
+ }
459
+
460
+ Write-Host "[ccx] Starting Claude Code with multi-provider proxy..."
461
+ Write-Host "[ccx] Proxy will listen on: $($env:ANTHROPIC_BASE_URL)"
462
+
463
+ # Start proxy
464
+ $gatewayPath = Join-Path $ROOT_DIR "adapters\anthropic-gateway.ts"
465
+ $logPath = Join-Path $env:TEMP "claude-proxy.log"
466
+ $errorLogPath = Join-Path $env:TEMP "claude-proxy-error.log"
467
+
468
+ $proc = Start-Process "npx" -ArgumentList "-y","tsx",$gatewayPath -PassThru -WindowStyle Hidden -RedirectStandardOutput $logPath -RedirectStandardError $errorLogPath
469
+
470
+ # Wait for health check
471
+ Write-Host "[ccx] Waiting for proxy to start..."
472
+ $ready = $false
473
+ for ($i = 0; $i -lt 30; $i++) {
474
+ try {
475
+ $response = Invoke-WebRequest -Uri "http://127.0.0.1:$PORT/healthz" -UseBasicParsing -TimeoutSec 1 -ErrorAction Stop
476
+ if ($response.StatusCode -eq 200) {
477
+ Write-Host "[ccx] Proxy ready!"
478
+ $ready = $true
479
+ break
480
+ }
481
+ } catch {
482
+ Start-Sleep -Milliseconds 500
483
+ }
484
+ }
485
+
486
+ if (-not $ready) {
487
+ Write-Host "ERROR: Proxy failed to start. Check logs:" -ForegroundColor Red
488
+ Write-Host " $logPath"
489
+ Write-Host " $errorLogPath"
490
+ if (Test-Path $errorLogPath) {
491
+ Get-Content $errorLogPath
492
+ }
493
+ if ($proc -and -not $proc.HasExited) { $proc.Kill() }
494
+ exit 1
495
+ }
496
+
497
+ Write-Host ""
498
+ Write-Host "MODELS: Available model prefixes:"
499
+ Write-Host " openai:<model> - OpenAI models (gpt-4o, gpt-4o-mini, etc.)"
500
+ Write-Host " openrouter:<model> - OpenRouter models"
501
+ Write-Host " gemini:<model> - Google Gemini models"
502
+ Write-Host " glm:<model> - Z.AI GLM models (glm-4.7, glm-4.5, etc.)"
503
+ Write-Host " anthropic:<model> - Anthropic Claude models"
504
+ Write-Host ""
505
+ Write-Host "TIP: Switch models in-session with: /model <prefix>:<model-name>"
506
+ Write-Host ""
507
+
508
+ try {
509
+ & claude @args
510
+ } finally {
511
+ Write-Host ""
512
+ Write-Host "[ccx] Shutting down proxy..."
513
+ if ($proc -and -not $proc.HasExited) {
514
+ $proc.Kill()
515
+ }
516
+ }
517
+ '@
518
+
519
+ Set-Content -Path $wrapperPath -Value $ccxContent
520
+ Write-Host "OK: Installed ccx at $wrapperPath" -ForegroundColor Green
521
+
522
+ # Add ccx function to PowerShell profile
523
+ Add-CcxFunction
524
+ }
525
+
526
+ # Add ccx function to PowerShell profile
527
+ function Add-CcxFunction {
528
+ if (-not (Test-Path -LiteralPath $PROFILE)) {
529
+ New-Item -ItemType File -Path $PROFILE -Force | Out-Null
530
+ }
531
+
532
+ $content = Get-Content $PROFILE -Raw -ErrorAction SilentlyContinue
533
+
534
+ # Check if function already exists
535
+ if ($content -match "function ccx") {
536
+ return
537
+ }
538
+
539
+ # Add ccx function
540
+ $ccxFunction = @"
541
+
542
+ # ccx multi-provider proxy function
543
+ function ccx { & `"$UserBinDir\ccx.ps1`" @args }
544
+ "@
545
+
546
+ Add-Content $PROFILE $ccxFunction
547
+ }
548
+
549
+ # Check Claude Code availability
550
+ function Test-ClaudeInstallation {
551
+ Write-Host "CHECKING: Claude Code installation..."
552
+
553
+ if (Get-Command claude -ErrorAction SilentlyContinue) {
554
+ $claudePath = (Get-Command claude).Source
555
+ Write-Host "OK: Claude Code found at: $claudePath"
556
+ return $true
557
+ } else {
558
+ Write-Host "WARNING: Claude Code not found in PATH"
559
+ Write-Host ""
560
+ Write-Host "Options:"
561
+ Write-Host "1. If Claude Code is installed elsewhere, add it to PATH first"
562
+ Write-Host "2. Install Claude Code from: https://www.anthropic.com/claude-code"
563
+ Write-Host "3. Continue anyway (wrappers will be created but will not work until claude is available)"
564
+ Write-Host ""
565
+ $continue = Read-Host "Continue with installation? (y/n)"
566
+ if ($continue -ne "y" -and $continue -ne "Y") {
567
+ Write-Host "Installation cancelled."
568
+ exit 1
569
+ }
570
+ return $false
571
+ }
572
+ }
573
+
574
+ # Report installation errors to GitHub
575
+ function Report-Error {
576
+ param(
577
+ [string]$ErrorMessage,
578
+ [string]$ErrorLine = "",
579
+ [object]$ErrorRecord = $null
580
+ )
581
+
582
+ Write-Host ""
583
+ Write-Host "=============================================" -ForegroundColor Red
584
+ Write-Host "ERROR: Installation failed!" -ForegroundColor Red
585
+ Write-Host "=============================================" -ForegroundColor Red
586
+ Write-Host ""
587
+
588
+ # Collect system information
589
+ $osInfo = try {
590
+ $os = Get-CimInstance -ClassName Win32_OperatingSystem -ErrorAction SilentlyContinue
591
+ "Windows $($os.Version) ($($os.Caption))"
592
+ } catch {
593
+ "Windows (version unknown)"
594
+ }
595
+
596
+ $psVersion = $PSVersionTable.PSVersion.ToString()
597
+ $timestamp = Get-Date -Format "yyyy-MM-dd HH:mm:ss UTC"
598
+
599
+ # Sanitize error message (remove API keys)
600
+ $sanitizedError = $ErrorMessage -replace 'ANTHROPIC_AUTH_TOKEN\s*=\s*\S+', 'ANTHROPIC_AUTH_TOKEN="[REDACTED]"'
601
+ $sanitizedError = $sanitizedError -replace 'ZaiApiKey\s*=\s*\S+', 'ZaiApiKey="[REDACTED]"'
602
+ $sanitizedError = $sanitizedError -replace '\$ZaiApiKey\s*=\s*"\S+"', '$ZaiApiKey="[REDACTED]"'
603
+
604
+ # Display error details to user
605
+ Write-Host "Error Details:" -ForegroundColor Yellow
606
+ Write-Host $sanitizedError -ForegroundColor White
607
+ if ($ErrorLine) {
608
+ Write-Host "Location: $ErrorLine" -ForegroundColor Gray
609
+ }
610
+ Write-Host ""
611
+
612
+ # Ask if user wants to report the error
613
+ Write-Host "Would you like to report this error to GitHub?" -ForegroundColor Cyan
614
+ Write-Host "This will open your browser with a pre-filled issue report." -ForegroundColor Gray
615
+ $reportChoice = Read-Host "Report error? (y/n)"
616
+ Write-Host ""
617
+
618
+ if ($reportChoice -ne "y" -and $reportChoice -ne "Y") {
619
+ Write-Host "Error not reported. You can get help at:" -ForegroundColor Yellow
620
+ Write-Host " https://github.com/JoeInnsp23/claude-glm-wrapper/issues" -ForegroundColor Cyan
621
+ Write-Host ""
622
+ Write-Host "Press Enter to close..." -ForegroundColor Gray
623
+ $null = Read-Host
624
+ return
625
+ }
626
+
627
+ # Get additional context
628
+ $claudeFound = if (Get-Command claude -ErrorAction SilentlyContinue) { "Yes" } else { "No" }
629
+
630
+ # Build error report (using string concatenation to avoid here-string parsing issues)
631
+ $issueBody = "## Installation Error (Windows PowerShell)`n`n"
632
+ $issueBody += "**OS:** $osInfo`n"
633
+ $issueBody += "**PowerShell:** $psVersion`n"
634
+ $issueBody += "**Timestamp:** $timestamp`n`n"
635
+ $issueBody += "### Error Details:`n"
636
+ $issueBody += "``````n"
637
+ $issueBody += "$sanitizedError`n"
638
+ $issueBody += "``````n`n"
639
+
640
+ if ($ErrorLine) {
641
+ $issueBody += "**Error Location:** $ErrorLine`n`n"
642
+ }
643
+
644
+ $issueBody += "### System Information:`n"
645
+ $issueBody += "- Installation Location: $UserBinDir`n"
646
+ $issueBody += "- Claude Code Found: $claudeFound`n"
647
+
648
+ try {
649
+ $execPolicy = Get-ExecutionPolicy -Scope CurrentUser -ErrorAction SilentlyContinue
650
+ $issueBody += "- PowerShell Execution Policy: $execPolicy`n"
651
+ } catch {
652
+ $issueBody += "- PowerShell Execution Policy: Unknown`n"
653
+ }
654
+
655
+ $issueBody += "`n### Additional Context:`n"
656
+
657
+ if ($ErrorRecord) {
658
+ try {
659
+ $exceptionType = $ErrorRecord.Exception.GetType().FullName
660
+ $category = $ErrorRecord.CategoryInfo.Category
661
+ $issueBody += "- Exception Type: $exceptionType`n"
662
+ $issueBody += "- Category: $category`n"
663
+ } catch {
664
+ $issueBody += "- Additional error details unavailable`n"
665
+ }
666
+ }
667
+
668
+ $issueBody += "`n---`n"
669
+ $issueBody += "*This error was automatically reported by the installer. Please add any additional context below.*"
670
+
671
+ # URL encode the body (native PowerShell method, no dependencies)
672
+ Write-DebugLog "Encoding error report for URL..."
673
+
674
+ # Truncate body if too long (GitHub has URL limits)
675
+ if ($issueBody.Length -gt 5000) {
676
+ $issueBody = $issueBody.Substring(0, 5000) + "`n`n[Report truncated due to length]"
677
+ Write-DebugLog "Truncated error report to 5000 characters"
678
+ }
679
+
680
+ # Use native PowerShell URL encoding
681
+ $encodedBody = [uri]::EscapeDataString($issueBody)
682
+ $encodedTitle = [uri]::EscapeDataString("Installation Error: Windows PowerShell")
683
+
684
+ $issueUrl = "https://github.com/JoeInnsp23/claude-glm-wrapper/issues/new?title=$encodedTitle`&body=$encodedBody`&labels=bug,windows,installation"
685
+
686
+ Write-Host "INFO: Error details have been prepared for reporting."
687
+ Write-Host ""
688
+
689
+ # Try multiple methods to open the browser
690
+ $browserOpened = $false
691
+
692
+ Write-DebugLog "Attempting to open browser with Start-Process..."
693
+ try {
694
+ Start-Process $issueUrl -ErrorAction Stop
695
+ $browserOpened = $true
696
+ Write-Host "OK: Browser opened with pre-filled error report." -ForegroundColor Green
697
+ } catch {
698
+ Write-DebugLog "Start-Process failed: $_"
699
+ }
700
+
701
+ if (-not $browserOpened) {
702
+ Write-DebugLog "Attempting to open browser with cmd /c start..."
703
+ try {
704
+ & cmd /c start $issueUrl 2>$null
705
+ if ($LASTEXITCODE -eq 0) {
706
+ $browserOpened = $true
707
+ Write-Host "OK: Browser opened with pre-filled error report." -ForegroundColor Green
708
+ }
709
+ } catch {
710
+ Write-DebugLog "cmd /c start failed: $_"
711
+ }
712
+ }
713
+
714
+ if (-not $browserOpened) {
715
+ Write-DebugLog "Attempting to open browser with explorer.exe..."
716
+ try {
717
+ & explorer.exe $issueUrl
718
+ $browserOpened = $true
719
+ Write-Host "OK: Browser opened with pre-filled error report." -ForegroundColor Green
720
+ } catch {
721
+ Write-DebugLog "explorer.exe failed: $_"
722
+ }
723
+ }
724
+
725
+ if (-not $browserOpened) {
726
+ Write-Host "WARNING: Could not open browser automatically." -ForegroundColor Yellow
727
+ Write-Host ""
728
+ Write-Host "Please copy and open this URL manually:" -ForegroundColor Yellow
729
+ Write-Host $issueUrl -ForegroundColor Cyan
730
+ Write-Host ""
731
+ Write-Host "Or press Enter to see a shortened URL..." -ForegroundColor Gray
732
+ $null = Read-Host
733
+
734
+ # Create a shorter URL with just the title
735
+ $shortUrl = "https://github.com/JoeInnsp23/claude-glm-wrapper/issues/new?title=$encodedTitle`&labels=bug,windows,installation"
736
+ Write-Host "Shortened URL (add error details manually):" -ForegroundColor Yellow
737
+ Write-Host $shortUrl -ForegroundColor Cyan
738
+ }
739
+
740
+ Write-Host ""
741
+
742
+ # Add instructions and wait for user
743
+ if ($browserOpened) {
744
+ Write-Host "Please review the error report in your browser and submit the issue." -ForegroundColor Cyan
745
+ Write-Host "After submitting (or if you choose not to), return here." -ForegroundColor Gray
746
+ }
747
+
748
+ Write-Host ""
749
+ Write-Host "Press Enter to continue..." -ForegroundColor Gray
750
+ $null = Read-Host
751
+ }
752
+
753
+ # Main installation
754
+ function Install-ClaudeGlm {
755
+ Write-Host "INSTALLER: Claude-GLM PowerShell Installer for Windows"
756
+ Write-Host "==============================================="
757
+ Write-Host ""
758
+ Write-Host "This installer:"
759
+ Write-Host " • Does NOT require administrator rights"
760
+ Write-Host " • Installs to: $UserBinDir"
761
+ Write-Host " • Works on Windows systems"
762
+ Write-Host ""
763
+
764
+ if ($Debug) {
765
+ Write-Host "DEBUG: Debug mode enabled" -ForegroundColor Gray
766
+ Write-Host ""
767
+ }
768
+
769
+ Write-DebugLog "Starting installation process..."
770
+
771
+ # Check Claude Code
772
+ Write-DebugLog "Checking Claude Code installation..."
773
+ Test-ClaudeInstallation
774
+
775
+ # Setup user bin directory
776
+ Write-DebugLog "Setting up user bin directory..."
777
+ Setup-UserBin
778
+
779
+ # Clean up old installations from different locations
780
+ Write-DebugLog "Checking for old installations..."
781
+ Remove-OldWrappers
782
+
783
+ # Check if already installed
784
+ $glmWrapper = Join-Path $UserBinDir "claude-glm.ps1"
785
+ $glmFastWrapper = Join-Path $UserBinDir "claude-glm-fast.ps1"
786
+
787
+ if ((Test-Path $glmWrapper) -or (Test-Path $glmFastWrapper)) {
788
+ Write-Host ""
789
+ Write-Host "OK: Existing installation detected!"
790
+ Write-Host "1. Update API key only"
791
+ Write-Host "2. Reinstall everything"
792
+ Write-Host "3. Cancel"
793
+ $choice = Read-Host "Choice (1-3)"
794
+
795
+ switch ($choice) {
796
+ "1" {
797
+ $inputKey = Read-Host "Enter your Z.AI API key"
798
+ if ($inputKey) {
799
+ $script:ZaiApiKey = $inputKey
800
+ New-ClaudeGlmWrapper
801
+ New-ClaudeGlm45Wrapper
802
+ New-ClaudeGlmFastWrapper
803
+ Write-Host "OK: API key updated!"
804
+ exit 0
805
+ }
806
+ }
807
+ "2" {
808
+ Write-Host "Reinstalling..."
809
+ }
810
+ default {
811
+ exit 0
812
+ }
813
+ }
814
+ }
815
+
816
+ # Get API key
817
+ Write-Host ""
818
+ Write-Host "Enter your Z.AI API key (from https://z.ai/manage-apikey/apikey-list)"
819
+ $inputKey = Read-Host "API Key"
820
+
821
+ if ($inputKey) {
822
+ $script:ZaiApiKey = $inputKey
823
+ $keyLength = $inputKey.Length
824
+ Write-Host "OK: API key received ($keyLength characters)"
825
+ } else {
826
+ Write-Host "WARNING: No API key provided. Add it manually later to:"
827
+ Write-Host " $UserBinDir\claude-glm.ps1"
828
+ Write-Host " $UserBinDir\claude-glm-4.5.ps1"
829
+ Write-Host " $UserBinDir\claude-glm-fast.ps1"
830
+ }
831
+
832
+ # Create wrappers
833
+ New-ClaudeGlmWrapper
834
+ New-ClaudeGlm45Wrapper
835
+ New-ClaudeGlmFastWrapper
836
+ Add-PowerShellAliases
837
+
838
+ # Ask about ccx installation
839
+ Write-Host ""
840
+ Write-Host "MULTI-PROVIDER: Multi-Provider Proxy (ccx)"
841
+ Write-Host "================================"
842
+ Write-Host "ccx allows you to switch between multiple AI providers in a single session:"
843
+ Write-Host " • OpenAI (GPT-4, GPT-4o, etc.)"
844
+ Write-Host " • OpenRouter (access to many models)"
845
+ Write-Host " • Google Gemini"
846
+ Write-Host " • Z.AI GLM models"
847
+ Write-Host " • Anthropic Claude"
848
+ Write-Host ""
849
+ $installCcxChoice = Read-Host "Install ccx? (Y/n)"
850
+
851
+ $ccxInstalled = $false
852
+ if ($installCcxChoice -ne "n" -and $installCcxChoice -ne "N") {
853
+ Install-Ccx
854
+ Write-Host ""
855
+ Write-Host "OK: ccx installed! Run 'ccx --setup' to configure API keys." -ForegroundColor Green
856
+ $ccxInstalled = $true
857
+ }
858
+
859
+ # Final instructions
860
+ Write-Host ""
861
+ Write-Host "OK: Installation complete!"
862
+ Write-Host ""
863
+ Write-Host "=========================================="
864
+ Write-Host "IMPORTANT: Restart PowerShell or reload profile:"
865
+ Write-Host "=========================================="
866
+ Write-Host ""
867
+ Write-Host " . `$PROFILE"
868
+ Write-Host ""
869
+ Write-Host "=========================================="
870
+ Write-Host ""
871
+ Write-Host "INFO: After reloading, you can use:"
872
+ Write-Host ""
873
+ Write-Host "Commands:"
874
+ Write-Host " claude-glm - GLM-4.7 (latest)"
875
+ Write-Host " claude-glm-4.5 - GLM-4.5"
876
+ Write-Host " claude-glm-fast - GLM-4.5-Air (fast)"
877
+ if ($ccxInstalled) {
878
+ Write-Host " ccx - Multi-provider proxy (switch models in-session)"
879
+ }
880
+ Write-Host ""
881
+ Write-Host "Aliases:"
882
+ Write-Host " cc - claude (regular Claude)"
883
+ Write-Host " ccg - claude-glm (GLM-4.7)"
884
+ Write-Host " ccg45 - claude-glm-4.5 (GLM-4.5)"
885
+ Write-Host " ccf - claude-glm-fast"
886
+ if ($ccxInstalled) {
887
+ Write-Host " ccx - Multi-provider proxy"
888
+ }
889
+ Write-Host ""
890
+
891
+ if ($ZaiApiKey -eq "YOUR_ZAI_API_KEY_HERE") {
892
+ Write-Host "WARNING: Do not forget to add your API key to:"
893
+ Write-Host " $UserBinDir\claude-glm.ps1"
894
+ Write-Host " $UserBinDir\claude-glm-4.5.ps1"
895
+ Write-Host " $UserBinDir\claude-glm-fast.ps1"
896
+ }
897
+
898
+ Write-Host ""
899
+ Write-Host "LOCATION: Installation location: $UserBinDir"
900
+ Write-Host "LOCATION: Config directories: $GlmConfigDir, $Glm45ConfigDir, $GlmFastConfigDir"
901
+ }
902
+
903
+ # Test error functionality if requested
904
+ if ($TestError) {
905
+ Write-Host "TEST: Testing error reporting functionality..." -ForegroundColor Magenta
906
+ Write-Host ""
907
+
908
+ # Show how script was invoked
909
+ if ($env:CLAUDE_GLM_TEST_ERROR) {
910
+ Write-Host " (Invoked via environment variable)" -ForegroundColor Gray
911
+ }
912
+ Write-Host ""
913
+
914
+ # Create a test error
915
+ $testErrorMessage = "This is a test error to verify error reporting works correctly"
916
+ $testErrorLine = "Test mode - no actual error"
917
+
918
+ # Create a mock error record
919
+ try {
920
+ throw $testErrorMessage
921
+ } catch {
922
+ Report-Error -ErrorMessage $testErrorMessage -ErrorLine $testErrorLine -ErrorRecord $_
923
+ }
924
+
925
+ Write-Host "OK: Test complete. If a browser window opened, error reporting is working!" -ForegroundColor Green
926
+ Write-Host ""
927
+ Write-Host "To run normal installation, use:" -ForegroundColor Gray
928
+ Write-Host " iwr -useb https://raw.githubusercontent.com/JoeInnsp23/claude-glm-wrapper/main/install.ps1 | iex" -ForegroundColor Cyan
929
+ Write-Host ""
930
+ Write-Host "Press Enter to finish (window will remain open)..." -ForegroundColor Gray
931
+ $null = Read-Host
932
+ # Script will not continue to installation - test mode ends here
933
+ }
934
+
935
+ # Only run installation if not in test mode
936
+ if (-not $TestError) {
937
+ # Run installation with error handling
938
+ try {
939
+ $ErrorActionPreference = "Stop"
940
+ Write-DebugLog "Starting installation with ErrorActionPreference = Stop"
941
+ Install-ClaudeGlm
942
+ } catch {
943
+ $errorMessage = $_.Exception.Message
944
+ $errorLine = if ($_.InvocationInfo.ScriptLineNumber) {
945
+ $lineNum = $_.InvocationInfo.ScriptLineNumber
946
+ $scriptName = $_.InvocationInfo.ScriptName
947
+ "Line $lineNum in $scriptName"
948
+ } else {
949
+ "Unknown location"
950
+ }
951
+
952
+ Write-DebugLog "Caught error: $errorMessage at $errorLine"
953
+ Report-Error -ErrorMessage $errorMessage -ErrorLine $errorLine -ErrorRecord $_
954
+
955
+ # Give user time to read any final messages before stopping
956
+ Write-Host ""
957
+ Write-Host "Installation terminated due to error." -ForegroundColor Red
958
+ Write-Host "Press Enter to finish (window will remain open)..." -ForegroundColor Gray
959
+ $null = Read-Host
960
+ # Return to stop script execution without closing window
961
+ return
962
+ }
963
+ }