-
Notifications
You must be signed in to change notification settings - Fork 9
Expand file tree
/
Copy pathconfigure-claude-code.ps1
More file actions
393 lines (358 loc) · 17.8 KB
/
Copy pathconfigure-claude-code.ps1
File metadata and controls
393 lines (358 loc) · 17.8 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
<#
.SYNOPSIS
Configure Claude Code CLI for Microsoft Foundry after `azd up`.
.DESCRIPTION
Designed to be invoked from the `postprovision` hook in `azure.yaml`.
Reads the per-family deployment outputs from `azd env get-values` and
wires up Claude Code so the user can immediately run `claude`:
1. Writes a project-scoped activator at the repo root:
claude-code.env.ps1 (PowerShell)
claude-code.env.sh (Bash / WSL)
containing ANTHROPIC_DEFAULT_<FAMILY>_MODEL for each non-empty
family deployment (haiku / sonnet / opus). Documented at:
https://learn.microsoft.com/azure/foundry/foundry-models/how-to/configure-claude-code
2. Writes (or merges into) `.vscode/settings.json` with
`claudeCode.environmentVariables` and `claudeCode.disableLoginPrompt`.
3. Detects whether the `claude` CLI is on PATH. If not, prints the
platform-appropriate install command. Set CLAUDE_CODE_AUTO_INSTALL=true
to run the official installer automatically.
Works on PowerShell 7+ on Windows, Linux, and macOS. Safe to re-run.
.NOTES
Exit codes:
0 Configuration written.
1 No deployment outputs found (provision didn't deploy any family).
2 azd CLI not on PATH (when running standalone).
#>
[CmdletBinding()]
param(
[string] $RepoRoot,
[switch] $WriteVsCodeSettings,
# Deprecated no-op: skipping is now the default. Kept so existing CI / docs
# that still pass -SkipVsCodeSettings don't break.
[switch] $SkipVsCodeSettings
)
$ErrorActionPreference = 'Stop'
# .vscode/settings.json is opt-in. Most users of this starter call Claude from
# the Anthropic SDK, the Claude Code CLI (uses the activator at the repo root,
# no workspace settings needed), or another OpenAI-compatible client. Only
# users of the Anthropic Claude Code VS Code extension benefit from having
# claudeCode.* keys written into their workspace settings, so make them ask:
# azd env set CLAUDE_WRITE_VSCODE_SETTINGS 1
# (or pass -WriteVsCodeSettings when running the script standalone).
if (-not $WriteVsCodeSettings) {
$writeEnv = $env:CLAUDE_WRITE_VSCODE_SETTINGS
if ($writeEnv -and $writeEnv -match '^(1|true|yes|on)$') {
$WriteVsCodeSettings = $true
}
}
function Fail([int]$code, [string]$message) {
Write-Host ""
Write-Host "ERROR: $message" -ForegroundColor Red
Write-Host ""
exit $code
}
# ---------------------------------------------------------------------------
# Locate the repo root.
# ---------------------------------------------------------------------------
if (-not $RepoRoot) {
$here = Split-Path -Parent $PSCommandPath
$RepoRoot = Resolve-Path (Join-Path $here '..') | Select-Object -ExpandProperty Path
}
Write-Host "Configuring Claude Code: repo root '$RepoRoot'"
# ---------------------------------------------------------------------------
# Resolve azd outputs.
# ---------------------------------------------------------------------------
$accountName = $env:FOUNDRY_ACCOUNT_NAME
$resourceGroup = $env:AZURE_RESOURCE_GROUP
$haikuDeploy = $env:CLAUDE_HAIKU_DEPLOYMENT_NAME
$sonnetDeploy = $env:CLAUDE_SONNET_DEPLOYMENT_NAME
$opusDeploy = $env:CLAUDE_OPUS_DEPLOYMENT_NAME
$legacyDeploy = $env:CLAUDE_DEPLOYMENT_NAME
# When the trio outputs aren't in env (running standalone), parse azd env.
$needsAzd = -not $accountName -or
(-not $haikuDeploy -and -not $sonnetDeploy -and -not $opusDeploy -and -not $legacyDeploy)
if ($needsAzd) {
$azd = Get-Command azd -ErrorAction SilentlyContinue
if (-not $azd) {
Fail 2 "azd CLI not on PATH and required outputs not in env. Install azd or run from an azd-aware shell."
}
Write-Host "Reading outputs from 'azd env get-values'..."
$vals = & azd env get-values 2>$null
foreach ($line in $vals) {
if ($line -match '^(?<k>[A-Z0-9_]+)="?(?<v>.*?)"?$') {
switch ($Matches['k']) {
'FOUNDRY_ACCOUNT_NAME' { if (-not $accountName) { $accountName = $Matches['v'] } }
'AZURE_RESOURCE_GROUP' { if (-not $resourceGroup){ $resourceGroup= $Matches['v'] } }
'CLAUDE_HAIKU_DEPLOYMENT_NAME' { if (-not $haikuDeploy) { $haikuDeploy = $Matches['v'] } }
'CLAUDE_SONNET_DEPLOYMENT_NAME' { if (-not $sonnetDeploy) { $sonnetDeploy = $Matches['v'] } }
'CLAUDE_OPUS_DEPLOYMENT_NAME' { if (-not $opusDeploy) { $opusDeploy = $Matches['v'] } }
'CLAUDE_DEPLOYMENT_NAME' { if (-not $legacyDeploy) { $legacyDeploy = $Matches['v'] } }
}
}
}
}
if (-not $accountName) {
Fail 1 "FOUNDRY_ACCOUNT_NAME not available. Has 'azd provision' completed?"
}
# Build the list of (family, deployment) pairs that were actually deployed.
$deployments = @()
if ($haikuDeploy) { $deployments += [pscustomobject]@{ Family='HAIKU'; Deployment=$haikuDeploy } }
if ($sonnetDeploy) { $deployments += [pscustomobject]@{ Family='SONNET'; Deployment=$sonnetDeploy } }
if ($opusDeploy) { $deployments += [pscustomobject]@{ Family='OPUS'; Deployment=$opusDeploy } }
if ($deployments.Count -eq 0) {
# Legacy single-deployment fallback: infer family from the model name baked
# into the deployment name (e.g. "claude-opus-4-6-abc123" → OPUS).
if (-not $legacyDeploy) {
Fail 1 "No family deployments and no legacy CLAUDE_DEPLOYMENT_NAME found. Has 'azd provision' completed?"
}
$lower = $legacyDeploy.ToLower()
$family =
if ($lower -like '*sonnet*') { 'SONNET' }
elseif ($lower -like '*haiku*') { 'HAIKU' }
elseif ($lower -like '*opus*') { 'OPUS' }
else { Fail 1 "Could not infer Claude family from deployment name '$legacyDeploy'." }
$deployments += [pscustomobject]@{ Family=$family; Deployment=$legacyDeploy }
}
Write-Host " Foundry account : $accountName"
foreach ($d in $deployments) {
Write-Host (" {0,-18} : {1}" -f $d.Family, $d.Deployment)
}
# ---------------------------------------------------------------------------
# 1. Write the PowerShell + Bash activator scripts at the repo root.
# ---------------------------------------------------------------------------
$ps1Path = Join-Path $RepoRoot 'claude-code.env.ps1'
$shPath = Join-Path $RepoRoot 'claude-code.env.sh'
$ps1Lines = @(
"# Auto-generated by scripts/configure-claude-code.ps1 — safe to overwrite.",
"# Source me with: . ./claude-code.env.ps1",
"# Then run: claude",
"",
"# Scope 'az login' (and azd) to this workspace only — never touches ~/.azure",
"# and never leaks tokens into other VS Code windows or shells.",
"`$_claudeRoot = Split-Path -Parent `$MyInvocation.MyCommand.Path",
"`$env:AZURE_CONFIG_DIR = Join-Path `$_claudeRoot '.azure-cli'",
"if (-not (Test-Path `$env:AZURE_CONFIG_DIR)) { New-Item -ItemType Directory -Path `$env:AZURE_CONFIG_DIR -Force | Out-Null }",
"",
"`$env:CLAUDE_CODE_USE_FOUNDRY = '1'",
"`$env:ANTHROPIC_FOUNDRY_RESOURCE = '$accountName'"
)
foreach ($d in $deployments) {
$ps1Lines += "`$env:ANTHROPIC_DEFAULT_$($d.Family)_MODEL = '$($d.Deployment)'"
}
$ps1Lines += ""
$ps1Lines += "Write-Host `"Claude Code configured for Foundry resource '$accountName'.`" -ForegroundColor Green"
$ps1Lines += "Write-Host `"Azure CLI config scoped to: `$env:AZURE_CONFIG_DIR`" -ForegroundColor Green"
$ps1Lines += "Write-Host `"Authentication: Microsoft Entra ID via 'az login' (already done if 'azd up' succeeded).`" -ForegroundColor Green"
$shLines = @(
"# Auto-generated by scripts/configure-claude-code.ps1 — safe to overwrite.",
"# Source me with: source ./claude-code.env.sh (or: . ./claude-code.env.sh)",
"# Then run: claude",
"",
"# Scope 'az login' (and azd) to this workspace only — never touches ~/.azure",
"# and never leaks tokens into other VS Code windows or shells.",
"_claude_root=`"`$(cd `"`$(dirname `"`${BASH_SOURCE[0]:-`$0}`")`" && pwd)`"",
"export AZURE_CONFIG_DIR=`"`$_claude_root/.azure-cli`"",
"mkdir -p `"`$AZURE_CONFIG_DIR`"",
"unset _claude_root",
"",
"export CLAUDE_CODE_USE_FOUNDRY=1",
"export ANTHROPIC_FOUNDRY_RESOURCE='$accountName'"
)
foreach ($d in $deployments) {
$shLines += "export ANTHROPIC_DEFAULT_$($d.Family)_MODEL='$($d.Deployment)'"
}
$shLines += ""
$shLines += "echo `"Claude Code configured for Foundry resource '$accountName'.`""
$shLines += "echo `"Azure CLI config scoped to: `$AZURE_CONFIG_DIR`""
$shLines += "echo `"Authentication: Microsoft Entra ID via 'az login' (already done if 'azd up' succeeded).`""
Set-Content -Path $ps1Path -Value ($ps1Lines -join [Environment]::NewLine) -Encoding utf8
Set-Content -Path $shPath -Value ($shLines -join "`n") -Encoding utf8 -NoNewline:$false
Write-Host "Wrote activator: $ps1Path"
Write-Host "Wrote activator: $shPath"
# ---------------------------------------------------------------------------
# 2. Optionally write / merge `.vscode/settings.json` for the Claude Code
# VS Code extension. Opt-in via CLAUDE_WRITE_VSCODE_SETTINGS=1 (or
# -WriteVsCodeSettings) since most users don't run that extension.
# ---------------------------------------------------------------------------
if (-not $WriteVsCodeSettings) {
Write-Host "Skipping .vscode/settings.json (opt-in). Set 'azd env set CLAUDE_WRITE_VSCODE_SETTINGS 1' before 'azd up' to wire up the Anthropic Claude Code VS Code extension automatically. The activator above already works for sourced shells."
} else {
$vscodeDir = Join-Path $RepoRoot '.vscode'
$settingsPath = Join-Path $vscodeDir 'settings.json'
if (-not (Test-Path $vscodeDir)) {
New-Item -ItemType Directory -Path $vscodeDir -Force | Out-Null
}
$existing = [ordered]@{}
$parseFailed = $false
if (Test-Path $settingsPath) {
try {
$raw = Get-Content -Raw -Path $settingsPath
if ($raw -and $raw.Trim()) {
$obj = $raw | ConvertFrom-Json -ErrorAction Stop
foreach ($p in $obj.PSObject.Properties) {
$existing[$p.Name] = $p.Value
}
}
} catch {
Write-Host "WARNING: Could not parse existing $settingsPath ($($_.Exception.Message)). Leaving it untouched." -ForegroundColor Yellow
$parseFailed = $true
}
}
if (-not $parseFailed) {
# Use [ordered] hashtables per entry so name appears before value in
# the rendered JSON (PSCustomObject hashtable iteration is unordered).
$claudeEnv = @(
[ordered]@{ name = 'CLAUDE_CODE_USE_FOUNDRY'; value = '1' }
[ordered]@{ name = 'ANTHROPIC_FOUNDRY_RESOURCE'; value = $accountName }
)
foreach ($d in $deployments) {
$claudeEnv += [ordered]@{ name = "ANTHROPIC_DEFAULT_$($d.Family)_MODEL"; value = $d.Deployment }
}
$existing['claudeCode.environmentVariables'] = $claudeEnv
$existing['claudeCode.disableLoginPrompt'] = $true
# Scope 'az login' (and azd) to a workspace-local config dir so it
# never touches ~/.azure and never leaks tokens into other VS Code
# windows. Applies to every terminal VS Code spawns in this workspace.
$azCfgWin = [ordered]@{ AZURE_CONFIG_DIR = '${workspaceFolder}\.azure-cli' }
$azCfgPosix = [ordered]@{ AZURE_CONFIG_DIR = '${workspaceFolder}/.azure-cli' }
$existing['terminal.integrated.env.windows'] = $azCfgWin
$existing['terminal.integrated.env.linux'] = $azCfgPosix
$existing['terminal.integrated.env.osx'] = $azCfgPosix
# Strip any stale display-title key from prior versions of this script.
if ($existing.Contains('Claude Code: Environment Variables')) {
$existing.Remove('Claude Code: Environment Variables')
}
($existing | ConvertTo-Json -Depth 8) | Set-Content -Path $settingsPath -Encoding utf8
Write-Host "Wrote VS Code settings: $settingsPath"
}
}
# ---------------------------------------------------------------------------
# 2b. Write / merge `.claude/settings.json` to pin the workspace default model
# to a deployed family. Avoids "model X is not available on your foundry
# deployment" when the user's global ~/.claude/settings.json has `model` set
# to a family this workspace didn't deploy.
# ---------------------------------------------------------------------------
# Family priority: sonnet > opus > haiku (matches outputs.tf legacy
# CLAUDE_DEPLOYMENT_NAME picker and the README's "recommended general default").
$workspaceModelFamily = $null
foreach ($d in $deployments) {
if ($d.Family -eq 'SONNET') { $workspaceModelFamily = 'sonnet'; break }
}
if (-not $workspaceModelFamily) {
foreach ($d in $deployments) {
if ($d.Family -eq 'OPUS') { $workspaceModelFamily = 'opus'; break }
}
}
if (-not $workspaceModelFamily) {
foreach ($d in $deployments) {
if ($d.Family -eq 'HAIKU') { $workspaceModelFamily = 'haiku'; break }
}
}
if ($workspaceModelFamily) {
$claudeDir = Join-Path $RepoRoot '.claude'
$claudeSettingsPath = Join-Path $claudeDir 'settings.json'
if (-not (Test-Path $claudeDir)) {
New-Item -ItemType Directory -Path $claudeDir -Force | Out-Null
}
$claudeExisting = [ordered]@{}
$skipClaudeMerge = $false
if (Test-Path $claudeSettingsPath) {
try {
$raw = Get-Content -Raw -Path $claudeSettingsPath
if ($raw -and $raw.Trim()) {
$obj = $raw | ConvertFrom-Json -ErrorAction Stop
foreach ($p in $obj.PSObject.Properties) {
$claudeExisting[$p.Name] = $p.Value
}
}
} catch {
Write-Host "WARNING: Could not parse existing $claudeSettingsPath ($($_.Exception.Message)). Leaving it untouched." -ForegroundColor Yellow
$skipClaudeMerge = $true
}
}
if (-not $skipClaudeMerge) {
$claudeExisting['model'] = $workspaceModelFamily
($claudeExisting | ConvertTo-Json -Depth 8) | Set-Content -Path $claudeSettingsPath -Encoding utf8
Write-Host "Wrote Claude Code workspace settings: $claudeSettingsPath (model=$workspaceModelFamily)"
}
}
# ---------------------------------------------------------------------------
# 3. Detect / optionally install the Claude Code CLI.
# ---------------------------------------------------------------------------
$claude = Get-Command claude -ErrorAction SilentlyContinue
$autoInstall = $env:CLAUDE_CODE_AUTO_INSTALL -and ($env:CLAUDE_CODE_AUTO_INSTALL -ne 'false' -and $env:CLAUDE_CODE_AUTO_INSTALL -ne '0')
if ($claude) {
Write-Host ""
Write-Host "Claude Code CLI detected: $($claude.Source)" -ForegroundColor Green
} else {
Write-Host ""
Write-Host "Claude Code CLI not found on PATH." -ForegroundColor Yellow
$onWindows = ($PSVersionTable.PSEdition -eq 'Desktop') -or `
($PSVersionTable.Platform -eq 'Win32NT') -or `
($env:OS -eq 'Windows_NT')
if ($autoInstall) {
Write-Host "CLAUDE_CODE_AUTO_INSTALL is set — running the official installer..."
try {
if ($onWindows) {
Invoke-RestMethod -Uri 'https://claude.ai/install.ps1' | Invoke-Expression
} else {
& bash -c "curl -fsSL https://claude.ai/install.sh | bash"
}
$claude = Get-Command claude -ErrorAction SilentlyContinue
if ($claude) {
Write-Host "Claude Code installed: $($claude.Source)" -ForegroundColor Green
} else {
Write-Host "Install ran but 'claude' is still not on PATH. Open a new shell, or add the install dir to PATH." -ForegroundColor Yellow
}
} catch {
Write-Host "WARNING: auto-install failed ($($_.Exception.Message)). Install manually." -ForegroundColor Yellow
}
} else {
Write-Host ""
Write-Host "To install (one-time):" -ForegroundColor Cyan
if ($onWindows) {
Write-Host " irm https://claude.ai/install.ps1 | iex"
Write-Host " or in Git Bash / WSL:"
Write-Host " curl -fsSL https://claude.ai/install.sh | bash"
} else {
Write-Host " curl -fsSL https://claude.ai/install.sh | bash"
}
Write-Host ""
Write-Host "Or set CLAUDE_CODE_AUTO_INSTALL=true and re-run 'azd provision' to install automatically."
}
}
# ---------------------------------------------------------------------------
# 4. Final next-step message.
# ---------------------------------------------------------------------------
Write-Host ""
Write-Host "=============================================================" -ForegroundColor Cyan
Write-Host " Claude Code is configured for Microsoft Foundry." -ForegroundColor Cyan
Write-Host "=============================================================" -ForegroundColor Cyan
Write-Host ""
Write-Host " Foundry resource : $accountName"
foreach ($d in $deployments) {
Write-Host (" {0,-16} : {1}" -f "$($d.Family) deployment", $d.Deployment)
}
if ($resourceGroup) { Write-Host " Resource group : $resourceGroup" }
Write-Host ""
Write-Host "To start Claude Code from your terminal:"
Write-Host ""
Write-Host " PowerShell:" -ForegroundColor Cyan
Write-Host " . $RepoRoot\claude-code.env.ps1"
Write-Host " claude"
Write-Host ""
Write-Host " Bash / WSL:" -ForegroundColor Cyan
Write-Host " source $RepoRoot/claude-code.env.sh"
Write-Host " claude"
Write-Host ""
Write-Host "Or in VS Code: install the 'Claude Code' extension"
Write-Host "(https://marketplace.visualstudio.com/items?itemName=anthropic.claude-code)"
if ($WriteVsCodeSettings) {
Write-Host "— the .vscode/settings.json in this workspace already wires it up."
} else {
Write-Host "— then re-run 'azd env set CLAUDE_WRITE_VSCODE_SETTINGS 1; azd provision'"
Write-Host " (or '. .\scripts\configure-claude-code.ps1 -WriteVsCodeSettings')"
Write-Host " to auto-wire the extension to this Foundry deployment."
}
Write-Host ""
exit 0