Files
claude-mode/claude-mode.ps1
T
smoidoandClaude Opus 5 4091746d4e Split the Windows script into modules; drop the Arkylx Index pieces
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>
2026-09-15 02:06:45 +03:00

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
}