claude-mode.ps1 was 2,407 lines. It is now 153: the help block, parameters, paths, the managed-key list, a loader, and the dispatch. The rest moved, verbatim, into eleven files under lib/ - providers, output, files, core, vault, switch, guards, health, catalogue, commands, menu - dot-sourced into the script's scope in their original order, with the same check as the bash split that every original line landed in exactly one file. Inside a module $PSScriptRoot is lib\, so the one path beside the main script (providers.json) now goes through $script:Here. install.ps1 ships lib\, clearing old modules first. The Windows suite parses every module and install.ps1 (36 checks, all green on Windows PowerShell 5.1); install.ps1 is parsed but never run, since it edits the real profile and User PATH. linux/bootstrap.sh and scripts/build-package.ps1 existed only to build and serve packages for the Arkylx Index. Both installers fetch the repository's own archive, so a push to master is the release; the two scripts, the dist/ ignore and their mentions in the docs are gone. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
154 lines
6.4 KiB
PowerShell
154 lines
6.4 KiB
PowerShell
<#
|
|
.SYNOPSIS
|
|
claude-mode - switch Claude Code system-wide between native Anthropic auth and
|
|
the gateway providers in providers.json (OpenRouter, Z.AI, LM Studio, Ollama,
|
|
custom endpoints), with named model presets.
|
|
|
|
.DESCRIPTION
|
|
State lives in %USERPROFILE%\.claude-mode. Switching rewrites the managed keys
|
|
inside %USERPROFILE%\.claude\settings.json, which Claude Code re-reads at every
|
|
startup - so a switch applies to every new `claude` invocation from any shell,
|
|
the VS Code extension, and the desktop app, with nothing to re-source.
|
|
|
|
Secrets are never written to settings.json. A remote provider's API key is
|
|
stored DPAPI-encrypted (bound to this Windows user + machine) and handed to
|
|
Claude Code at runtime via the `apiKeyHelper` hook. LM Studio's placeholder
|
|
token is not a secret and is written inline.
|
|
|
|
Run with no arguments for an interactive menu.
|
|
|
|
.NOTES
|
|
Windows PowerShell 5.1 compatible. No external dependencies.
|
|
#>
|
|
|
|
[CmdletBinding()]
|
|
param(
|
|
[Parameter(Position = 0)]
|
|
[string] $Command = '',
|
|
|
|
[Parameter(Position = 1, ValueFromRemainingArguments = $true)]
|
|
[string[]] $Rest
|
|
)
|
|
|
|
# v1 only (uninitialised variables). v2 would throw on absent JSON properties,
|
|
# which is normal here - presets and settings.json are both partially shaped.
|
|
Set-StrictMode -Version 1.0
|
|
$ErrorActionPreference = 'Stop'
|
|
try { [Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12 } catch { }
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# Paths
|
|
# ---------------------------------------------------------------------------
|
|
|
|
$script:Root = Join-Path $env:USERPROFILE '.claude-mode'
|
|
$script:PresetDir = Join-Path $script:Root 'presets'
|
|
$script:VaultDir = Join-Path $script:Root 'vault'
|
|
$script:BackupDir = Join-Path $script:Root 'backups'
|
|
$script:BinDir = Join-Path $script:Root 'bin'
|
|
$script:StatePath = Join-Path $script:Root 'state.json'
|
|
$script:HelperCmd = Join-Path $script:BinDir 'claude-key-helper.cmd'
|
|
$script:SettingsDir = Join-Path $env:USERPROFILE '.claude'
|
|
$script:Settings = Join-Path $script:SettingsDir 'settings.json'
|
|
$script:LmStudioDir = Join-Path $env:USERPROFILE '.lmstudio'
|
|
|
|
# Baseline set of settings.json env keys this tool owns. On every switch these
|
|
# are deleted first, together with whatever a previous switch actually wrote
|
|
# (tracked in state.json), so no value can survive a mode change.
|
|
$script:BaseManagedEnvKeys = @(
|
|
'ANTHROPIC_BASE_URL',
|
|
'ANTHROPIC_AUTH_TOKEN',
|
|
'ANTHROPIC_API_KEY',
|
|
'ANTHROPIC_DEFAULT_OPUS_MODEL',
|
|
'ANTHROPIC_DEFAULT_SONNET_MODEL',
|
|
'ANTHROPIC_DEFAULT_HAIKU_MODEL',
|
|
'ANTHROPIC_DEFAULT_FABLE_MODEL',
|
|
# Both are read by the CLI (21 and 37 references in 2.1.221) and both pin a
|
|
# concrete model outside the tier mapping. A stale value in either survives a
|
|
# switch and is a known cause of "works normally, dies on compaction",
|
|
# because background summarisation uses the small/fast slot.
|
|
'ANTHROPIC_MODEL',
|
|
'ANTHROPIC_SMALL_FAST_MODEL',
|
|
'CLAUDE_CODE_SUBAGENT_MODEL',
|
|
'CLAUDE_CODE_DISABLE_1M_CONTEXT',
|
|
'CLAUDE_CODE_ENABLE_GATEWAY_MODEL_DISCOVERY',
|
|
'CLAUDE_CODE_ATTRIBUTION_HEADER',
|
|
'CLAUDE_CODE_AUTO_COMPACT_WINDOW',
|
|
'CLAUDE_CODE_MAX_CONTEXT_TOKENS',
|
|
'CLAUDE_CODE_DISABLE_NONESSENTIAL_TRAFFIC',
|
|
'API_TIMEOUT_MS'
|
|
)
|
|
|
|
$script:Tiers = @('opus', 'sonnet', 'haiku', 'fable')
|
|
$script:Version = '0.0.0'
|
|
try { $__v = Join-Path $PSScriptRoot 'VERSION'; if (Test-Path $__v) { $script:Version = (Get-Content $__v -Raw).Trim() } } catch { }
|
|
$script:NodeExe = $null # resolved lazily by Format-JsonPretty
|
|
|
|
# Where this script lives. The modules below are dot-sourced, and inside them
|
|
# $PSScriptRoot is lib\ - so anything beside this script (providers.json)
|
|
# is reached through $script:Here instead.
|
|
$script:Here = $PSScriptRoot
|
|
|
|
# The rest of the script, one file per concern, loaded in the order it was
|
|
# written: a few modules set script-scope tables as they load (the providers,
|
|
# mode colours, UI state), and every function exists before the dispatch below.
|
|
foreach ($__m in @('providers', 'output', 'files', 'core', 'vault', 'switch', 'guards', 'health', 'catalogue', 'commands', 'menu')) {
|
|
$__f = Join-Path $PSScriptRoot "lib\$__m.ps1"
|
|
if (-not (Test-Path -LiteralPath $__f)) {
|
|
Write-Host " FAIL $__f is missing - re-run install.ps1" -ForegroundColor Red
|
|
exit 1
|
|
}
|
|
. $__f
|
|
}
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# Dispatch
|
|
# ---------------------------------------------------------------------------
|
|
|
|
Initialize-Root
|
|
|
|
try {
|
|
$cmd = $Command.ToLower()
|
|
|
|
switch ($cmd) {
|
|
'' { Invoke-Menu }
|
|
'menu' { Invoke-Menu }
|
|
'status' { Invoke-Status }
|
|
'anthropic' { Set-ClaudeMode -Mode 'anthropic' -PresetName '' }
|
|
'presets' { Invoke-Presets }
|
|
'preset' { Invoke-PresetCmd -Argv $Rest }
|
|
'set-key' {
|
|
# `set-key [ref] [key]`. The key is normally typed at the hidden
|
|
# prompt; anything key-shaped in the ref position is taken as the
|
|
# key for the default ref, which is the easy mistake to make.
|
|
$kRef = ''; $kKey = ''
|
|
foreach ($a in $Rest) {
|
|
if (-not $kRef -and -not ($a -like 'sk-*' -or $a.Length -gt 24)) { $kRef = $a; continue }
|
|
if (-not $kKey) { $kKey = $a; continue }
|
|
throw "unexpected extra argument '$a'"
|
|
}
|
|
if (-not $kRef) { $kRef = 'openrouter' }
|
|
Set-VaultKey -Ref $kRef -Key $kKey
|
|
}
|
|
'models' { Invoke-Models -Filter $(if ($Rest -and $Rest.Count -ge 1) { $Rest[0] } else { $null }) }
|
|
'doctor' { Invoke-Doctor }
|
|
'repair' { [void](Invoke-Repair -All:([bool]($Rest -and ($Rest -contains '--all')))) }
|
|
'help' { Show-Usage }
|
|
'--help' { Show-Usage }
|
|
'-h' { Show-Usage }
|
|
default {
|
|
# Any provider in providers.json, by id or alias, is a mode. Last,
|
|
# so a provider can never shadow a real command.
|
|
$prov = Resolve-ProviderId $cmd
|
|
if ($prov) {
|
|
$req = if ($Rest -and $Rest.Count -ge 1) { $Rest[0] } else { $null }
|
|
Set-ClaudeMode -Mode $prov -PresetName (Resolve-PresetForProvider $prov $req)
|
|
} else {
|
|
Write-Err2 "unknown command '$Command'"; Show-Usage; exit 1
|
|
}
|
|
}
|
|
}
|
|
} catch {
|
|
Write-Err2 $_.Exception.Message
|
|
exit 1
|
|
}
|