aid-installer 0.7.5
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/LICENSE +21 -0
- package/README.md +17 -0
- package/VERSION +1 -0
- package/bin/aid +931 -0
- package/bin/aid.cmd +24 -0
- package/bin/aid.js +70 -0
- package/bin/aid.ps1 +875 -0
- package/lib/AidInstallCore.psm1 +1411 -0
- package/lib/aid-install-core.sh +1646 -0
- package/package.json +36 -0
package/bin/aid.ps1
ADDED
|
@@ -0,0 +1,875 @@
|
|
|
1
|
+
#Requires -Version 5.1
|
|
2
|
+
# aid.ps1 - AID CLI dispatcher (PowerShell side).
|
|
3
|
+
#
|
|
4
|
+
# Purpose:
|
|
5
|
+
# Persistent global command installed at $AID_HOME\bin\aid.ps1. Parses
|
|
6
|
+
# subcommands and dispatches to the shared install-core engine located at
|
|
7
|
+
# $AID_HOME\lib\AidInstallCore.psm1. Operates on the current working
|
|
8
|
+
# directory (-Target / AID_TARGET overrides).
|
|
9
|
+
#
|
|
10
|
+
# Usage:
|
|
11
|
+
# aid Show the dashboard
|
|
12
|
+
# aid -h | --help Show help
|
|
13
|
+
# aid version Print the CLI version
|
|
14
|
+
# aid status Show AID state of the current project
|
|
15
|
+
# aid add <tool>[,...] Add tool(s) to the current project
|
|
16
|
+
# aid update [<tool>... | self] Update to latest; no arg = all tools; 'self' = the aid CLI
|
|
17
|
+
# aid remove [<tool>... | self] Remove; no arg = ALL AID from project; 'self' = the aid CLI
|
|
18
|
+
# aid <command> -h | --help Per-command help
|
|
19
|
+
#
|
|
20
|
+
# Flags (shared across subcommands where applicable):
|
|
21
|
+
# -FromBundle <path> Offline install from a pre-downloaded tarball / dir.
|
|
22
|
+
# -Version <v> Pin to a specific release version (e.g. 0.7.0).
|
|
23
|
+
# -Force Overwrite differing files / skip confirmation prompts.
|
|
24
|
+
# -Target <dir> Project root (default: current directory).
|
|
25
|
+
# -Verbose Print per-file detail (default: concise summary).
|
|
26
|
+
# -NoPath (bootstrap / update self only) Skip PATH wiring.
|
|
27
|
+
|
|
28
|
+
# ---------------------------------------------------------------------------
|
|
29
|
+
# Bootstrap URL - single place to update when the branch merges to master.
|
|
30
|
+
# Override with $env:AID_INSTALL_URL for tests.
|
|
31
|
+
# ---------------------------------------------------------------------------
|
|
32
|
+
$script:_AidInstallUrl = if ($env:AID_INSTALL_URL) { $env:AID_INSTALL_URL } else {
|
|
33
|
+
'https://raw.githubusercontent.com/AndreVianna/aid-methodology/worktree-work-002-auto-installer/install.ps1'
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
# ---------------------------------------------------------------------------
|
|
37
|
+
# Piped-mode / terminal-survival guard.
|
|
38
|
+
# When invoked via scriptblock or iex, calling exit <N> kills the host.
|
|
39
|
+
# Use the same sentinel-throw pattern as install.ps1.
|
|
40
|
+
# ---------------------------------------------------------------------------
|
|
41
|
+
$script:_PipedMode = [string]::IsNullOrEmpty($PSCommandPath)
|
|
42
|
+
$script:_SentinelTag = '__AidDispatcherExit__'
|
|
43
|
+
|
|
44
|
+
function script:Exit-Aid {
|
|
45
|
+
param([int]$Code)
|
|
46
|
+
if ($script:_PipedMode) {
|
|
47
|
+
$global:LASTEXITCODE = $Code
|
|
48
|
+
throw "$($script:_SentinelTag)$Code"
|
|
49
|
+
} else {
|
|
50
|
+
exit $Code
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
# ---------------------------------------------------------------------------
|
|
55
|
+
# Locate $AID_HOME. The installed dispatcher lives at $AID_HOME\bin\aid.ps1.
|
|
56
|
+
# ---------------------------------------------------------------------------
|
|
57
|
+
$script:_AidSelfPath = $MyInvocation.MyCommand.Path
|
|
58
|
+
if (-not [string]::IsNullOrEmpty($script:_AidSelfPath)) {
|
|
59
|
+
$script:_AidHome = $env:AID_HOME
|
|
60
|
+
if (-not $script:_AidHome) {
|
|
61
|
+
# bin/aid.ps1 -> parent of bin/ = AID_HOME
|
|
62
|
+
$script:_AidHome = Split-Path -Parent (Split-Path -Parent $script:_AidSelfPath)
|
|
63
|
+
}
|
|
64
|
+
} else {
|
|
65
|
+
$script:_AidHome = if ($env:AID_HOME) { $env:AID_HOME } else {
|
|
66
|
+
if ($env:LOCALAPPDATA) { Join-Path $env:LOCALAPPDATA 'aid' } else { Join-Path $HOME '.aid' }
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
# ---------------------------------------------------------------------------
|
|
71
|
+
# Import the shared install core from $AID_HOME\lib\.
|
|
72
|
+
# ---------------------------------------------------------------------------
|
|
73
|
+
$script:_CoreModule = Join-Path $script:_AidHome 'lib' | Join-Path -ChildPath 'AidInstallCore.psm1'
|
|
74
|
+
if (-not (Test-Path $script:_CoreModule -PathType Leaf)) {
|
|
75
|
+
[Console]::Error.WriteLine("ERROR: aid: install core not found at $($script:_CoreModule). Re-run the AID bootstrap to repair.")
|
|
76
|
+
script:Exit-Aid 1
|
|
77
|
+
}
|
|
78
|
+
# Load the core lib by dot-sourcing its content (NOT Import-Module - avoids PowerShell's
|
|
79
|
+
# module-analysis cache, which can serve a stale exported-command list across upgrades).
|
|
80
|
+
# Export-ModuleMember is a module-only cmdlet; shadow it with a local no-op so the lib's
|
|
81
|
+
# trailing Export-ModuleMember call is harmless when dot-sourced.
|
|
82
|
+
$_aidLibRaw = $null
|
|
83
|
+
try {
|
|
84
|
+
$_aidLibRaw = Get-Content -LiteralPath $script:_CoreModule -Raw -Encoding utf8 -ErrorAction Stop
|
|
85
|
+
} catch {
|
|
86
|
+
[Console]::Error.WriteLine("ERROR: aid: failed to read the CLI core from $($script:_CoreModule): $_")
|
|
87
|
+
script:Exit-Aid 1
|
|
88
|
+
}
|
|
89
|
+
function Export-ModuleMember { param([Parameter(ValueFromRemainingArguments=$true)]$args) }
|
|
90
|
+
. ([scriptblock]::Create($_aidLibRaw))
|
|
91
|
+
|
|
92
|
+
# Defensive guard: verify the required core function was loaded via dot-source.
|
|
93
|
+
# If Get-AidStatusBody is still absent after dot-sourcing, the lib is genuinely
|
|
94
|
+
# broken or incomplete (not a cache issue - the file itself is the problem).
|
|
95
|
+
if (-not (Get-Command 'Get-AidStatusBody' -ErrorAction SilentlyContinue)) {
|
|
96
|
+
[Console]::Error.WriteLine("ERROR: aid: failed to load the CLI core from $($script:_CoreModule). The file may be incomplete - reinstall with: irm $($script:_AidInstallUrl) | iex")
|
|
97
|
+
script:Exit-Aid 1
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
# ---------------------------------------------------------------------------
|
|
101
|
+
# Usage helper.
|
|
102
|
+
# ---------------------------------------------------------------------------
|
|
103
|
+
function script:Show-AidUsage {
|
|
104
|
+
param([string]$Sub = '')
|
|
105
|
+
switch ($Sub) {
|
|
106
|
+
'status' {
|
|
107
|
+
Write-Host 'aid status [-Verbose] [-Target <dir>]'
|
|
108
|
+
Write-Host ' Show AID state of the current project (default: cwd).'
|
|
109
|
+
Write-Host ' Exit 7 when no AID install is found.'
|
|
110
|
+
}
|
|
111
|
+
'add' {
|
|
112
|
+
Write-Host 'aid add <tool>[,<tool>...] [-Version <v>] [-FromBundle <path>]'
|
|
113
|
+
Write-Host ' [-Force] [-Verbose] [-Target <dir>]'
|
|
114
|
+
Write-Host ' Add tool(s) to the current project.'
|
|
115
|
+
Write-Host ' Tools: claude-code, codex, cursor, copilot-cli, antigravity'
|
|
116
|
+
}
|
|
117
|
+
'remove' {
|
|
118
|
+
Write-Host 'aid remove [<tool>[,<tool>...] | self] [-Force] [-Verbose] [-Target <dir>]'
|
|
119
|
+
Write-Host ' Remove tool(s) from the current project (manifest-driven).'
|
|
120
|
+
Write-Host ' No args: remove ALL AID from the project (asks for confirmation).'
|
|
121
|
+
Write-Host ' self: remove the aid CLI itself (asks for confirmation).'
|
|
122
|
+
}
|
|
123
|
+
'update' {
|
|
124
|
+
Write-Host 'aid update [<tool>... | self] [-Version <v>] [-FromBundle <path>]'
|
|
125
|
+
Write-Host ' [-Force] [-Verbose] [-Target <dir>]'
|
|
126
|
+
Write-Host ' Update to latest. No args: update all installed tools.'
|
|
127
|
+
Write-Host ' self: update the aid CLI itself.'
|
|
128
|
+
}
|
|
129
|
+
'version' {
|
|
130
|
+
Write-Host 'aid version'
|
|
131
|
+
Write-Host ' Print the installed aid CLI version and exit 0.'
|
|
132
|
+
}
|
|
133
|
+
default {
|
|
134
|
+
Write-Host 'aid - AID CLI'
|
|
135
|
+
Write-Host ''
|
|
136
|
+
Write-Host 'Usage:'
|
|
137
|
+
Write-Host ' aid Show the dashboard'
|
|
138
|
+
Write-Host ' aid -h | --help Show this help'
|
|
139
|
+
Write-Host ' aid version Print the CLI version'
|
|
140
|
+
Write-Host ' aid status Show AID state of the current project'
|
|
141
|
+
Write-Host ' aid add <tool>[,...] Add tool(s) to the current project'
|
|
142
|
+
Write-Host ' aid update [<tool>... | self] Update to latest; no arg = all tools'
|
|
143
|
+
Write-Host ' aid remove [<tool>... | self] Remove; no arg = ALL AID from project'
|
|
144
|
+
Write-Host " aid <command> -h | --help Per-command help"
|
|
145
|
+
Write-Host ''
|
|
146
|
+
Write-Host 'Flags: -FromBundle, -Version, -Force, -Target, -Verbose'
|
|
147
|
+
Write-Host "Run 'aid <command> -h' for details."
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
# ---------------------------------------------------------------------------
|
|
153
|
+
# Error helper.
|
|
154
|
+
# ---------------------------------------------------------------------------
|
|
155
|
+
function script:Fail-Aid {
|
|
156
|
+
param([string]$Message, [int]$Code = 1)
|
|
157
|
+
[Console]::Error.WriteLine("ERROR: aid: $Message")
|
|
158
|
+
script:Exit-Aid $Code
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
# ---------------------------------------------------------------------------
|
|
162
|
+
# Update check (throttled, cached, non-blocking, opt-out).
|
|
163
|
+
# ---------------------------------------------------------------------------
|
|
164
|
+
|
|
165
|
+
# Invoke-AidUpdateCheck
|
|
166
|
+
# Compares installed CLI version against latest GitHub release.
|
|
167
|
+
# Prints ONE notice line when newer is available. Fail-silent.
|
|
168
|
+
# Throttle: re-fetches at most once per 24h; cache in $AID_HOME\.update-check.
|
|
169
|
+
# Opt-out: $env:AID_NO_UPDATE_CHECK = '1'
|
|
170
|
+
# Test hook: $env:AID_UPDATE_CHECK_URL overrides the fetch URL (and bypasses throttle).
|
|
171
|
+
function script:Invoke-AidUpdateCheck {
|
|
172
|
+
# Opt-out.
|
|
173
|
+
if ($env:AID_NO_UPDATE_CHECK -eq '1') { return }
|
|
174
|
+
|
|
175
|
+
# Read installed version.
|
|
176
|
+
$verFile = Join-Path $script:_AidHome 'VERSION'
|
|
177
|
+
if (-not (Test-Path $verFile -PathType Leaf)) { return }
|
|
178
|
+
$installedVersion = (Get-Content -LiteralPath $verFile -Raw -ErrorAction SilentlyContinue).Trim()
|
|
179
|
+
if (-not $installedVersion) { return }
|
|
180
|
+
|
|
181
|
+
$cacheFile = Join-Path $script:_AidHome '.update-check'
|
|
182
|
+
$throttleSecs = 86400 # 24 hours
|
|
183
|
+
try { $now = [DateTimeOffset]::UtcNow.ToUnixTimeSeconds() } catch { return }
|
|
184
|
+
|
|
185
|
+
# Determine URL and throttle behaviour.
|
|
186
|
+
$checkUrl = $env:AID_UPDATE_CHECK_URL
|
|
187
|
+
$useThrottle = [string]::IsNullOrEmpty($checkUrl)
|
|
188
|
+
if ($useThrottle) {
|
|
189
|
+
$checkUrl = "https://api.github.com/repos/AndreVianna/aid-methodology/releases/latest"
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
# Try to read cache.
|
|
193
|
+
$cachedTs = 0
|
|
194
|
+
$cachedLatest = ''
|
|
195
|
+
if (Test-Path $cacheFile -PathType Leaf) {
|
|
196
|
+
try {
|
|
197
|
+
$lines = @(Get-Content -LiteralPath $cacheFile -ErrorAction SilentlyContinue)
|
|
198
|
+
if ($lines.Count -ge 1) { $cachedTs = [long]$lines[0] }
|
|
199
|
+
if ($lines.Count -ge 2) { $cachedLatest = $lines[1].Trim() }
|
|
200
|
+
} catch {}
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
# Decide whether to fetch.
|
|
204
|
+
$latestVersion = ''
|
|
205
|
+
$needFetch = $true
|
|
206
|
+
if ($useThrottle -and $cachedLatest) {
|
|
207
|
+
$age = $now - $cachedTs
|
|
208
|
+
if ($age -lt $throttleSecs) {
|
|
209
|
+
$needFetch = $false
|
|
210
|
+
$latestVersion = $cachedLatest
|
|
211
|
+
}
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
if ($needFetch) {
|
|
215
|
+
$body = ''
|
|
216
|
+
try {
|
|
217
|
+
# Support file:// URLs for hermetic tests (PowerShell web cmdlets don't
|
|
218
|
+
# handle file://, so we strip the scheme and read the file directly).
|
|
219
|
+
if ($checkUrl -match '^file:///?(.+)$') {
|
|
220
|
+
$filePath = $matches[1]
|
|
221
|
+
# On Windows file:///C:/path -> C:/path; on Linux file:///tmp/path -> /tmp/path
|
|
222
|
+
if ($filePath -notmatch '^[A-Za-z]:') {
|
|
223
|
+
$filePath = '/' + $filePath.TrimStart('/')
|
|
224
|
+
}
|
|
225
|
+
$body = Get-Content -LiteralPath $filePath -Raw -ErrorAction Stop
|
|
226
|
+
} else {
|
|
227
|
+
$resp = Invoke-WebRequest -Uri $checkUrl -UseBasicParsing -TimeoutSec 2 `
|
|
228
|
+
-ErrorAction Stop
|
|
229
|
+
$body = $resp.Content
|
|
230
|
+
}
|
|
231
|
+
} catch {
|
|
232
|
+
return # fail-silent
|
|
233
|
+
}
|
|
234
|
+
if ($body -match '"tag_name"\s*:\s*"([^"]+)"') {
|
|
235
|
+
$tag = $matches[1] -replace '^v', ''
|
|
236
|
+
$latestVersion = $tag
|
|
237
|
+
# Update cache.
|
|
238
|
+
try {
|
|
239
|
+
[System.IO.File]::WriteAllText($cacheFile, "$now`n$latestVersion`n")
|
|
240
|
+
} catch {}
|
|
241
|
+
}
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
if (-not $latestVersion) { return }
|
|
245
|
+
|
|
246
|
+
# Compare: notice only when latest > installed.
|
|
247
|
+
# Inline semver comparison (mirrors script:Test-SemverLt from AidInstallCore.psm1
|
|
248
|
+
# but kept local here since script:-scoped module functions are not callable across
|
|
249
|
+
# the module boundary from the dispatcher script).
|
|
250
|
+
$partsA = $installedVersion -split '\.'
|
|
251
|
+
$partsB = $latestVersion -split '\.'
|
|
252
|
+
$isLt = $false
|
|
253
|
+
for ($i = 0; $i -lt 3; $i++) {
|
|
254
|
+
$rawA = if ($i -lt $partsA.Count) { $partsA[$i] } else { '0' }
|
|
255
|
+
$rawB = if ($i -lt $partsB.Count) { $partsB[$i] } else { '0' }
|
|
256
|
+
if ($rawA -match '^(\d+)') { $va = [int]$matches[1] } else { $va = 0 }
|
|
257
|
+
if ($rawB -match '^(\d+)') { $vb = [int]$matches[1] } else { $vb = 0 }
|
|
258
|
+
if ($va -lt $vb) { $isLt = $true; break }
|
|
259
|
+
if ($va -gt $vb) { break }
|
|
260
|
+
}
|
|
261
|
+
if ($isLt) {
|
|
262
|
+
$updateCmd = switch ($env:AID_INSTALL_CHANNEL) {
|
|
263
|
+
'npm' { 'npm i -g aid-installer@latest' }
|
|
264
|
+
'pypi' { 'pipx upgrade aid-installer (or: pip install --user -U aid-installer)' }
|
|
265
|
+
default { 'aid update self' }
|
|
266
|
+
}
|
|
267
|
+
Write-Host "A newer aid CLI is available: v$latestVersion (you have v$installedVersion). Run: $updateCmd"
|
|
268
|
+
}
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
# Invoke-AidUpdateSelf
|
|
272
|
+
# Re-runs the bootstrap in place. Relays bootstrap exit code.
|
|
273
|
+
# AID_INSTALL_CHANNEL guard: npm/pypi channels print a package-manager hint
|
|
274
|
+
# and exit 0 instead of re-bootstrapping.
|
|
275
|
+
function script:Invoke-AidUpdateSelf {
|
|
276
|
+
switch ($env:AID_INSTALL_CHANNEL) {
|
|
277
|
+
'npm' {
|
|
278
|
+
Write-Host 'Updating the aid CLI: run npm i -g aid-installer@latest'
|
|
279
|
+
script:Exit-Aid 0
|
|
280
|
+
return
|
|
281
|
+
}
|
|
282
|
+
'pypi' {
|
|
283
|
+
Write-Host 'Updating the aid CLI: run pipx upgrade aid-installer (or: pip install --user -U aid-installer)'
|
|
284
|
+
script:Exit-Aid 0
|
|
285
|
+
return
|
|
286
|
+
}
|
|
287
|
+
}
|
|
288
|
+
Write-Host 'Updating the aid CLI...'
|
|
289
|
+
$url = $script:_AidInstallUrl
|
|
290
|
+
try {
|
|
291
|
+
$scriptContent = (Invoke-RestMethod -Uri $url -ErrorAction Stop)
|
|
292
|
+
& ([scriptblock]::Create($scriptContent))
|
|
293
|
+
script:Exit-Aid $LASTEXITCODE
|
|
294
|
+
} catch {
|
|
295
|
+
[Console]::Error.WriteLine("ERROR: aid: update self failed: $_")
|
|
296
|
+
script:Exit-Aid 3
|
|
297
|
+
}
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
# ---------------------------------------------------------------------------
|
|
301
|
+
# PATH wiring helpers (Windows - User-scope registry).
|
|
302
|
+
# ---------------------------------------------------------------------------
|
|
303
|
+
|
|
304
|
+
# Add-AidToPath <binDir> [-NoPath]
|
|
305
|
+
# Idempotently wire binDir into the User PATH via [Environment]::SetEnvironmentVariable.
|
|
306
|
+
# Deduplicates on ';'-split. Warns if path would exceed safe length.
|
|
307
|
+
# Updates $env:Path in-process so the convenience-chain first action works immediately.
|
|
308
|
+
function script:Add-AidToPath {
|
|
309
|
+
param([string]$BinDir, [bool]$NoPath = $false)
|
|
310
|
+
|
|
311
|
+
if ($NoPath) {
|
|
312
|
+
Write-Host "Add `"$BinDir`" to your PATH manually."
|
|
313
|
+
return
|
|
314
|
+
}
|
|
315
|
+
|
|
316
|
+
$currentPath = [Environment]::GetEnvironmentVariable('Path', 'User')
|
|
317
|
+
if (-not $currentPath) { $currentPath = '' }
|
|
318
|
+
|
|
319
|
+
# Split on ';', filter empty, deduplicate while preserving order.
|
|
320
|
+
$parts = $currentPath -split ';' | Where-Object { $_ -and $_.Trim() }
|
|
321
|
+
if ($parts -contains $BinDir) {
|
|
322
|
+
# Already present - update in-process path and return silently.
|
|
323
|
+
if ($env:Path -notmatch [regex]::Escape($BinDir)) {
|
|
324
|
+
$env:Path = "$BinDir;$($env:Path)"
|
|
325
|
+
}
|
|
326
|
+
return
|
|
327
|
+
}
|
|
328
|
+
|
|
329
|
+
$newParts = @($BinDir) + @($parts)
|
|
330
|
+
$newPath = $newParts -join ';'
|
|
331
|
+
|
|
332
|
+
# Safety guard: warn if exceeding ~2000 chars (Windows limit is 32767 but
|
|
333
|
+
# practical registry/shell limit is much lower for User PATH).
|
|
334
|
+
$safeLimit = 2000
|
|
335
|
+
if ($newPath.Length -gt $safeLimit) {
|
|
336
|
+
Write-Host "WARN: aid: User PATH would exceed $safeLimit chars. Skipping automatic PATH wiring."
|
|
337
|
+
Write-Host "Add `"$BinDir`" to your PATH manually via System Properties > Environment Variables."
|
|
338
|
+
return
|
|
339
|
+
}
|
|
340
|
+
|
|
341
|
+
[Environment]::SetEnvironmentVariable('Path', $newPath, 'User')
|
|
342
|
+
|
|
343
|
+
# Update in-process immediately so the convenience-chain first action works.
|
|
344
|
+
if ($env:Path -notmatch [regex]::Escape($BinDir)) {
|
|
345
|
+
$env:Path = "$BinDir;$($env:Path)"
|
|
346
|
+
}
|
|
347
|
+
|
|
348
|
+
Write-Host "PATH wiring added (User scope): $BinDir"
|
|
349
|
+
Write-Host "Open a new shell, or the PATH is already active in this session."
|
|
350
|
+
}
|
|
351
|
+
|
|
352
|
+
# Remove-AidFromPath <binDir>
|
|
353
|
+
# Remove binDir from User PATH idempotently.
|
|
354
|
+
function script:Remove-AidFromPath {
|
|
355
|
+
param([string]$BinDir)
|
|
356
|
+
|
|
357
|
+
$currentPath = [Environment]::GetEnvironmentVariable('Path', 'User')
|
|
358
|
+
if (-not $currentPath) { return }
|
|
359
|
+
|
|
360
|
+
$parts = $currentPath -split ';' | Where-Object { $_ -and $_.Trim() -ne $BinDir }
|
|
361
|
+
$newPath = $parts -join ';'
|
|
362
|
+
|
|
363
|
+
if ($newPath -ne $currentPath) {
|
|
364
|
+
[Environment]::SetEnvironmentVariable('Path', $newPath, 'User')
|
|
365
|
+
Write-Host "PATH wiring removed (User scope): $BinDir"
|
|
366
|
+
}
|
|
367
|
+
}
|
|
368
|
+
|
|
369
|
+
# ---------------------------------------------------------------------------
|
|
370
|
+
# Wrap everything in try/catch for terminal-survival in piped/iex mode.
|
|
371
|
+
# ---------------------------------------------------------------------------
|
|
372
|
+
try {
|
|
373
|
+
|
|
374
|
+
# ---------------------------------------------------------------------------
|
|
375
|
+
# Parse raw args.
|
|
376
|
+
# aid.ps1 is invoked by aid.cmd which forwards all args as positional strings.
|
|
377
|
+
# We parse manually to support both flag-style and positional style uniformly.
|
|
378
|
+
# ---------------------------------------------------------------------------
|
|
379
|
+
$script:_RawArgs = $args
|
|
380
|
+
|
|
381
|
+
# Resolve verbose from env var first (flag overrides below).
|
|
382
|
+
$script:_AidVerbose = ($env:AID_VERBOSE -eq '1')
|
|
383
|
+
|
|
384
|
+
# ---- Bare aid -> dashboard landing screen ----
|
|
385
|
+
if ($script:_RawArgs.Count -eq 0) {
|
|
386
|
+
# Block 1 + 2: Header + description.
|
|
387
|
+
$cliVersion = 'unknown'
|
|
388
|
+
$verFile = Join-Path $script:_AidHome 'VERSION'
|
|
389
|
+
if (Test-Path $verFile -PathType Leaf) {
|
|
390
|
+
$cliVersion = (Get-Content -LiteralPath $verFile -Raw).Trim()
|
|
391
|
+
}
|
|
392
|
+
Write-Host "AID v$cliVersion - Agentic Iterative Development"
|
|
393
|
+
Write-Host "Install, update, and manage AID across your repositories."
|
|
394
|
+
|
|
395
|
+
# Block 3: Installed tools for cwd.
|
|
396
|
+
Write-Host ""
|
|
397
|
+
$null = Get-AidStatusBody -Target '.'
|
|
398
|
+
|
|
399
|
+
# Block 4: Usage/help.
|
|
400
|
+
Write-Host ""
|
|
401
|
+
script:Show-AidUsage
|
|
402
|
+
|
|
403
|
+
# Block 5: Update check notice (final line, non-blocking).
|
|
404
|
+
script:Invoke-AidUpdateCheck
|
|
405
|
+
script:Exit-Aid 0
|
|
406
|
+
}
|
|
407
|
+
|
|
408
|
+
# ---- Early -h / --help ----
|
|
409
|
+
if ($script:_RawArgs[0] -in @('-h', '--help', '-Help', '/help', '/?')) {
|
|
410
|
+
script:Show-AidUsage
|
|
411
|
+
script:Exit-Aid 0
|
|
412
|
+
}
|
|
413
|
+
|
|
414
|
+
$SUBCMD = $script:_RawArgs[0]
|
|
415
|
+
$script:_RemArgs = @($script:_RawArgs | Select-Object -Skip 1)
|
|
416
|
+
|
|
417
|
+
# ---------------------------------------------------------------------------
|
|
418
|
+
# version
|
|
419
|
+
# ---------------------------------------------------------------------------
|
|
420
|
+
if ($SUBCMD -eq 'version') {
|
|
421
|
+
$versionFile = Join-Path $script:_AidHome 'VERSION'
|
|
422
|
+
if (Test-Path $versionFile -PathType Leaf) {
|
|
423
|
+
Write-Host (Get-Content -LiteralPath $versionFile -Raw).Trim()
|
|
424
|
+
} else {
|
|
425
|
+
Write-Host "unknown (VERSION file not found at $versionFile)"
|
|
426
|
+
}
|
|
427
|
+
script:Exit-Aid 0
|
|
428
|
+
}
|
|
429
|
+
|
|
430
|
+
# ---------------------------------------------------------------------------
|
|
431
|
+
# help (bare 'aid help' still works as general help)
|
|
432
|
+
# ---------------------------------------------------------------------------
|
|
433
|
+
if ($SUBCMD -in @('help', '-h', '--help')) {
|
|
434
|
+
script:Show-AidUsage
|
|
435
|
+
script:Exit-Aid 0
|
|
436
|
+
}
|
|
437
|
+
|
|
438
|
+
# ---------------------------------------------------------------------------
|
|
439
|
+
# status
|
|
440
|
+
# ---------------------------------------------------------------------------
|
|
441
|
+
if ($SUBCMD -eq 'status') {
|
|
442
|
+
$statusTarget = ''
|
|
443
|
+
$remIdx = 0
|
|
444
|
+
while ($remIdx -lt $script:_RemArgs.Count) {
|
|
445
|
+
$a = $script:_RemArgs[$remIdx]
|
|
446
|
+
switch ($a) {
|
|
447
|
+
{ $_ -in @('-Target', '--target') } {
|
|
448
|
+
$remIdx++
|
|
449
|
+
if ($remIdx -ge $script:_RemArgs.Count) { script:Fail-Aid "-Target requires a value" 2 }
|
|
450
|
+
$statusTarget = $script:_RemArgs[$remIdx]
|
|
451
|
+
}
|
|
452
|
+
{ $_ -in @('-Verbose', '--verbose') } { $script:_AidVerbose = $true; $env:AID_VERBOSE = '1' }
|
|
453
|
+
{ $_ -in @('-h', '--help', '-Help') } { script:Show-AidUsage 'status'; script:Exit-Aid 0 }
|
|
454
|
+
default {
|
|
455
|
+
if ($a.StartsWith('-')) {
|
|
456
|
+
script:Fail-Aid "unknown flag for status: $a" 2
|
|
457
|
+
} else {
|
|
458
|
+
script:Fail-Aid "unexpected argument for status: $a" 2
|
|
459
|
+
}
|
|
460
|
+
}
|
|
461
|
+
}
|
|
462
|
+
$remIdx++
|
|
463
|
+
}
|
|
464
|
+
|
|
465
|
+
# Apply env-var fallback.
|
|
466
|
+
if (-not $statusTarget -and $env:AID_TARGET) { $statusTarget = $env:AID_TARGET }
|
|
467
|
+
if (-not $statusTarget) { $statusTarget = '.' }
|
|
468
|
+
if ($script:_AidVerbose) { $env:AID_VERBOSE = '1' }
|
|
469
|
+
|
|
470
|
+
$rc = Get-AidStatus -Target $statusTarget
|
|
471
|
+
# Update check notice appended after status output (non-blocking).
|
|
472
|
+
script:Invoke-AidUpdateCheck
|
|
473
|
+
script:Exit-Aid $rc
|
|
474
|
+
}
|
|
475
|
+
|
|
476
|
+
# ---------------------------------------------------------------------------
|
|
477
|
+
# update (with 'self' subarg -> update self)
|
|
478
|
+
# ---------------------------------------------------------------------------
|
|
479
|
+
if ($SUBCMD -eq 'update') {
|
|
480
|
+
if ($script:_RemArgs.Count -gt 0 -and $script:_RemArgs[0] -eq 'self') {
|
|
481
|
+
# Consume any flags after 'self'.
|
|
482
|
+
$remIdx = 1
|
|
483
|
+
while ($remIdx -lt $script:_RemArgs.Count) {
|
|
484
|
+
$a = $script:_RemArgs[$remIdx]
|
|
485
|
+
switch ($a) {
|
|
486
|
+
{ $_ -in @('-Force', '--force', '-y') } { } # no-op for update self
|
|
487
|
+
{ $_ -in @('-h', '--help', '-Help') } { script:Show-AidUsage 'update'; script:Exit-Aid 0 }
|
|
488
|
+
default { script:Fail-Aid "unknown flag for 'update self': $a" 2 }
|
|
489
|
+
}
|
|
490
|
+
$remIdx++
|
|
491
|
+
}
|
|
492
|
+
script:Invoke-AidUpdateSelf
|
|
493
|
+
# Invoke-AidUpdateSelf always calls Exit-Aid.
|
|
494
|
+
}
|
|
495
|
+
# Fall through to shared add/update handler below.
|
|
496
|
+
}
|
|
497
|
+
|
|
498
|
+
# ---------------------------------------------------------------------------
|
|
499
|
+
# remove (with 'self' subarg -> remove self)
|
|
500
|
+
# ---------------------------------------------------------------------------
|
|
501
|
+
if ($SUBCMD -eq 'remove') {
|
|
502
|
+
if ($script:_RemArgs.Count -gt 0 -and $script:_RemArgs[0] -eq 'self') {
|
|
503
|
+
# Parse flags after 'self'.
|
|
504
|
+
$rsForce = $false
|
|
505
|
+
$rsNoPath = $false
|
|
506
|
+
$remIdx = 1
|
|
507
|
+
while ($remIdx -lt $script:_RemArgs.Count) {
|
|
508
|
+
$a = $script:_RemArgs[$remIdx]
|
|
509
|
+
switch ($a) {
|
|
510
|
+
{ $_ -in @('-Force', '--force', '-y') } { $rsForce = $true }
|
|
511
|
+
{ $_ -in @('-NoPath', '--no-path', '/nopath') } { $rsNoPath = $true }
|
|
512
|
+
{ $_ -in @('-h', '--help', '-Help') } { script:Show-AidUsage 'remove'; script:Exit-Aid 0 }
|
|
513
|
+
default { script:Fail-Aid "unknown flag for 'remove self': $a" 2 }
|
|
514
|
+
}
|
|
515
|
+
$remIdx++
|
|
516
|
+
}
|
|
517
|
+
|
|
518
|
+
# Env-var fallback for force.
|
|
519
|
+
if (-not $rsForce -and ($env:AID_FORCE -eq '1' -or $env:AID_FORCE -eq 'true')) {
|
|
520
|
+
$rsForce = $true
|
|
521
|
+
}
|
|
522
|
+
|
|
523
|
+
$aidHome = $script:_AidHome
|
|
524
|
+
|
|
525
|
+
if (-not $rsForce) {
|
|
526
|
+
# Skip prompt when non-interactive.
|
|
527
|
+
$isInteractive = [Environment]::UserInteractive -and [Console]::In -ne [System.IO.TextReader]::Null
|
|
528
|
+
if (-not $isInteractive) {
|
|
529
|
+
$rsForce = $true
|
|
530
|
+
} else {
|
|
531
|
+
Write-Host -NoNewline "Remove the aid CLI from ${aidHome}? [y/N] "
|
|
532
|
+
$answer = Read-Host
|
|
533
|
+
if ($answer -notin @('y', 'Y', 'yes', 'YES')) {
|
|
534
|
+
Write-Host "Aborted."
|
|
535
|
+
script:Exit-Aid 0
|
|
536
|
+
}
|
|
537
|
+
}
|
|
538
|
+
}
|
|
539
|
+
|
|
540
|
+
$partial = $false
|
|
541
|
+
|
|
542
|
+
# Remove PATH wiring.
|
|
543
|
+
if (-not $rsNoPath) {
|
|
544
|
+
$binDir = Join-Path $aidHome 'bin'
|
|
545
|
+
try { script:Remove-AidFromPath -BinDir $binDir } catch { $partial = $true }
|
|
546
|
+
}
|
|
547
|
+
|
|
548
|
+
# Remove $AID_HOME directory.
|
|
549
|
+
if (Test-Path $aidHome -PathType Container) {
|
|
550
|
+
try {
|
|
551
|
+
Remove-Item -LiteralPath $aidHome -Recurse -Force -ErrorAction Stop
|
|
552
|
+
} catch {
|
|
553
|
+
[Console]::Error.WriteLine("ERROR: aid: failed to remove $aidHome : $_")
|
|
554
|
+
$partial = $true
|
|
555
|
+
}
|
|
556
|
+
}
|
|
557
|
+
|
|
558
|
+
if ($partial) {
|
|
559
|
+
Write-Host "aid CLI partially removed. Check the messages above for what remained."
|
|
560
|
+
script:Exit-Aid 1
|
|
561
|
+
}
|
|
562
|
+
|
|
563
|
+
Write-Host "aid CLI removed. Per-project AID installs are unaffected; run 'aid remove' in a project before removing the CLI if you also want to remove those."
|
|
564
|
+
script:Exit-Aid 0
|
|
565
|
+
}
|
|
566
|
+
# Fall through to shared remove handler below (may be 'remove' with no arg or with tool).
|
|
567
|
+
}
|
|
568
|
+
|
|
569
|
+
# ---------------------------------------------------------------------------
|
|
570
|
+
# add / remove / update - validate subcommand
|
|
571
|
+
# ---------------------------------------------------------------------------
|
|
572
|
+
if ($SUBCMD -notin @('add', 'remove', 'update')) {
|
|
573
|
+
[Console]::Error.WriteLine("ERROR: aid: unknown command: $SUBCMD (see 'aid -h')")
|
|
574
|
+
script:Exit-Aid 2
|
|
575
|
+
}
|
|
576
|
+
|
|
577
|
+
# ---------------------------------------------------------------------------
|
|
578
|
+
# Parse shared flags for add/remove/update.
|
|
579
|
+
# ---------------------------------------------------------------------------
|
|
580
|
+
$_AidToolArg = ''
|
|
581
|
+
$_AidVersionArg = ''
|
|
582
|
+
$_AidFromBundle = ''
|
|
583
|
+
$_AidForce = $false
|
|
584
|
+
$_AidRemoveForce = $false
|
|
585
|
+
$_AidTarget = ''
|
|
586
|
+
$_AidPosTools = [System.Collections.Generic.List[string]]::new()
|
|
587
|
+
|
|
588
|
+
$remIdx = 0
|
|
589
|
+
while ($remIdx -lt $script:_RemArgs.Count) {
|
|
590
|
+
$a = $script:_RemArgs[$remIdx]
|
|
591
|
+
switch -Regex ($a) {
|
|
592
|
+
'^(-FromBundle|--from-bundle)$' {
|
|
593
|
+
$remIdx++
|
|
594
|
+
if ($remIdx -ge $script:_RemArgs.Count) { script:Fail-Aid "-FromBundle requires a value" 2 }
|
|
595
|
+
$_AidFromBundle = $script:_RemArgs[$remIdx]
|
|
596
|
+
break
|
|
597
|
+
}
|
|
598
|
+
'^(-Version|--version)$' {
|
|
599
|
+
$remIdx++
|
|
600
|
+
if ($remIdx -ge $script:_RemArgs.Count) { script:Fail-Aid "-Version requires a value" 2 }
|
|
601
|
+
$_AidVersionArg = $script:_RemArgs[$remIdx]
|
|
602
|
+
break
|
|
603
|
+
}
|
|
604
|
+
'^(-Force|--force|-y)$' { $_AidForce = $true; $_AidRemoveForce = $true; break }
|
|
605
|
+
'^(-Verbose|--verbose)$' { $script:_AidVerbose = $true; $env:AID_VERBOSE = '1'; break }
|
|
606
|
+
'^(-Target|--target)$' {
|
|
607
|
+
$remIdx++
|
|
608
|
+
if ($remIdx -ge $script:_RemArgs.Count) { script:Fail-Aid "-Target requires a value" 2 }
|
|
609
|
+
$_AidTarget = $script:_RemArgs[$remIdx]
|
|
610
|
+
break
|
|
611
|
+
}
|
|
612
|
+
'^(-NoPath|--no-path)$' { break <# bootstrap-only; silently ignore here #> }
|
|
613
|
+
'^(-h|--help|-Help)$' { script:Show-AidUsage $SUBCMD; script:Exit-Aid 0 }
|
|
614
|
+
'^-' {
|
|
615
|
+
script:Fail-Aid "unknown flag: $a" 2
|
|
616
|
+
}
|
|
617
|
+
default {
|
|
618
|
+
# Positional: tool name(s) - comma-separated or space-separated.
|
|
619
|
+
$_AidPosTools.Add($a)
|
|
620
|
+
break
|
|
621
|
+
}
|
|
622
|
+
}
|
|
623
|
+
$remIdx++
|
|
624
|
+
}
|
|
625
|
+
|
|
626
|
+
# Apply env-var fallbacks.
|
|
627
|
+
if (-not $_AidToolArg -and $_AidPosTools.Count -gt 0) { $_AidToolArg = $_AidPosTools -join ',' }
|
|
628
|
+
if (-not $_AidToolArg -and $env:AID_TOOL) { $_AidToolArg = $env:AID_TOOL }
|
|
629
|
+
if (-not $_AidVersionArg -and $env:AID_VERSION) { $_AidVersionArg = $env:AID_VERSION }
|
|
630
|
+
if (-not $_AidTarget -and $env:AID_TARGET) { $_AidTarget = $env:AID_TARGET }
|
|
631
|
+
if (-not $_AidForce -and ($env:AID_FORCE -eq '1' -or $env:AID_FORCE -eq 'true')) {
|
|
632
|
+
$_AidForce = $true
|
|
633
|
+
$_AidRemoveForce = $true
|
|
634
|
+
}
|
|
635
|
+
if ($script:_AidVerbose) { $env:AID_VERBOSE = '1' }
|
|
636
|
+
|
|
637
|
+
if (-not $_AidTarget) { $_AidTarget = '.' }
|
|
638
|
+
|
|
639
|
+
# Validate target directory.
|
|
640
|
+
if (-not (Test-Path $_AidTarget -PathType Container)) {
|
|
641
|
+
script:Fail-Aid "target directory does not exist: $_AidTarget" 2
|
|
642
|
+
}
|
|
643
|
+
$_AidTarget = (Resolve-Path $_AidTarget).Path
|
|
644
|
+
|
|
645
|
+
# Strip leading 'v' from version.
|
|
646
|
+
if ($_AidVersionArg) { $_AidVersionArg = $_AidVersionArg -replace '^v', '' }
|
|
647
|
+
|
|
648
|
+
# --from-bundle and --version are mutually exclusive.
|
|
649
|
+
if ($_AidFromBundle -and $_AidVersionArg) {
|
|
650
|
+
script:Fail-Aid "-FromBundle and -Version are mutually exclusive" 2
|
|
651
|
+
}
|
|
652
|
+
|
|
653
|
+
# ---------------------------------------------------------------------------
|
|
654
|
+
# Manifest path.
|
|
655
|
+
# ---------------------------------------------------------------------------
|
|
656
|
+
$_AidManifest = Join-Path $_AidTarget (Join-Path '.aid' '.aid-manifest.json')
|
|
657
|
+
|
|
658
|
+
# ---------------------------------------------------------------------------
|
|
659
|
+
# For 'remove' with no tool arg: confirm then remove all.
|
|
660
|
+
# ---------------------------------------------------------------------------
|
|
661
|
+
if ($SUBCMD -eq 'remove' -and -not $_AidToolArg) {
|
|
662
|
+
if (-not $_AidRemoveForce) {
|
|
663
|
+
# Skip prompt when non-interactive.
|
|
664
|
+
$isInteractive = [Environment]::UserInteractive -and [Console]::In -ne [System.IO.TextReader]::Null
|
|
665
|
+
if (-not $isInteractive) {
|
|
666
|
+
$_AidRemoveForce = $true
|
|
667
|
+
} else {
|
|
668
|
+
Write-Host -NoNewline "Remove ALL AID from ${_AidTarget}? [y/N] "
|
|
669
|
+
$answer = Read-Host
|
|
670
|
+
if ($answer -notin @('y', 'Y', 'yes', 'YES')) {
|
|
671
|
+
Write-Host "Aborted."
|
|
672
|
+
script:Exit-Aid 0
|
|
673
|
+
}
|
|
674
|
+
}
|
|
675
|
+
}
|
|
676
|
+
# Proceed: fall through to resolve all tools from manifest.
|
|
677
|
+
}
|
|
678
|
+
|
|
679
|
+
# ---------------------------------------------------------------------------
|
|
680
|
+
# Resolve tool list.
|
|
681
|
+
# ---------------------------------------------------------------------------
|
|
682
|
+
function script:Resolve-AidToolList {
|
|
683
|
+
# Returns a [ref] result: sets $ResultRef.Value to $true (success) or $false (error).
|
|
684
|
+
# On success, populates $OutList with tool ids (may be empty for update/remove
|
|
685
|
+
# when no manifest exists).
|
|
686
|
+
# On error (auto-detect failed, normalize failed), sets $ResultRef.Value to $false.
|
|
687
|
+
param([string]$Raw, [string]$Subcmd, [string]$ManifestPath, [string]$TargetDir,
|
|
688
|
+
[ref]$ResultRef, [System.Collections.Generic.List[string]]$OutList)
|
|
689
|
+
|
|
690
|
+
if (-not $Raw) {
|
|
691
|
+
if ($Subcmd -in @('update', 'remove')) {
|
|
692
|
+
# No tool specified -> all tools in manifest.
|
|
693
|
+
if (-not (Test-Path $ManifestPath -PathType Leaf)) {
|
|
694
|
+
$ResultRef.Value = $true # success, empty list = no manifest
|
|
695
|
+
return
|
|
696
|
+
}
|
|
697
|
+
$tools = Get-ManifestToolList -ManifestPath $ManifestPath
|
|
698
|
+
foreach ($t in $tools) { $OutList.Add($t.Id) }
|
|
699
|
+
$ResultRef.Value = $true
|
|
700
|
+
return
|
|
701
|
+
}
|
|
702
|
+
# Auto-detect for 'add'.
|
|
703
|
+
$detected = Detect-Tool -TargetPath $TargetDir
|
|
704
|
+
if ($null -eq $detected) { $ResultRef.Value = $false; return }
|
|
705
|
+
$OutList.Add($detected)
|
|
706
|
+
$ResultRef.Value = $true
|
|
707
|
+
return
|
|
708
|
+
}
|
|
709
|
+
|
|
710
|
+
# Split on comma.
|
|
711
|
+
$rawList = $Raw -split ',' | ForEach-Object { $_.Trim() } | Where-Object { $_ }
|
|
712
|
+
foreach ($t in $rawList) {
|
|
713
|
+
$canonical = Normalize-Tool -Raw $t
|
|
714
|
+
if ($null -eq $canonical) { $ResultRef.Value = $false; return }
|
|
715
|
+
$OutList.Add($canonical)
|
|
716
|
+
}
|
|
717
|
+
$ResultRef.Value = $true
|
|
718
|
+
}
|
|
719
|
+
|
|
720
|
+
$_AidToolsList = [System.Collections.Generic.List[string]]::new()
|
|
721
|
+
$_AidToolsOk = [ref]$false
|
|
722
|
+
script:Resolve-AidToolList -Raw $_AidToolArg -Subcmd $SUBCMD `
|
|
723
|
+
-ManifestPath $_AidManifest -TargetDir $_AidTarget `
|
|
724
|
+
-ResultRef $_AidToolsOk -OutList $_AidToolsList
|
|
725
|
+
|
|
726
|
+
if (-not $_AidToolsOk.Value) {
|
|
727
|
+
script:Exit-Aid 2
|
|
728
|
+
}
|
|
729
|
+
|
|
730
|
+
$_AidTools = $_AidToolsList
|
|
731
|
+
|
|
732
|
+
if ($_AidTools.Count -eq 0) {
|
|
733
|
+
switch ($SUBCMD) {
|
|
734
|
+
'remove' {
|
|
735
|
+
[Console]::Error.WriteLine("ERROR: aid: no manifest at $_AidTarget\.aid\.aid-manifest.json (exit 6)")
|
|
736
|
+
script:Exit-Aid 6
|
|
737
|
+
}
|
|
738
|
+
'update' {
|
|
739
|
+
[Console]::Error.WriteLine("ERROR: aid: no manifest at $_AidTarget\.aid\.aid-manifest.json; nothing to update (exit 6)")
|
|
740
|
+
script:Exit-Aid 6
|
|
741
|
+
}
|
|
742
|
+
'add' {
|
|
743
|
+
[Console]::Error.WriteLine("ERROR: aid: cannot auto-detect host tool; pass tool name as argument (e.g. aid add codex)")
|
|
744
|
+
script:Exit-Aid 2
|
|
745
|
+
}
|
|
746
|
+
}
|
|
747
|
+
}
|
|
748
|
+
|
|
749
|
+
# ---------------------------------------------------------------------------
|
|
750
|
+
# Staging area management.
|
|
751
|
+
# ---------------------------------------------------------------------------
|
|
752
|
+
$_AidStagingBase = Join-Path ([System.IO.Path]::GetTempPath()) ("aid-" + [System.IO.Path]::GetRandomFileName())
|
|
753
|
+
New-Item -ItemType Directory -Path $_AidStagingBase -Force | Out-Null
|
|
754
|
+
|
|
755
|
+
$script:_DispResolvedVersion = ''
|
|
756
|
+
$script:_DispStagingDir = ''
|
|
757
|
+
|
|
758
|
+
function script:Prepare-AidToolStaging {
|
|
759
|
+
param([string]$Tool, [string]$Version, [string]$Bundle)
|
|
760
|
+
|
|
761
|
+
$toolStaging = Join-Path $_AidStagingBase ("staging-$Tool-" + [System.IO.Path]::GetRandomFileName())
|
|
762
|
+
New-Item -ItemType Directory -Path $toolStaging -Force | Out-Null
|
|
763
|
+
|
|
764
|
+
if ($Bundle) {
|
|
765
|
+
$tarball = $Bundle
|
|
766
|
+
if (Test-Path $Bundle -PathType Container) {
|
|
767
|
+
$pattern = Join-Path $Bundle "aid-$Tool-v*.tar.gz"
|
|
768
|
+
$found = Get-ChildItem -Path $pattern -ErrorAction SilentlyContinue | Select-Object -First 1
|
|
769
|
+
if (-not $found) {
|
|
770
|
+
[Console]::Error.WriteLine("ERROR: aid: no tarball found for tool '$Tool' in bundle directory: $Bundle")
|
|
771
|
+
script:Exit-Aid 1
|
|
772
|
+
}
|
|
773
|
+
$tarball = $found.FullName
|
|
774
|
+
}
|
|
775
|
+
if (-not (Test-Path $tarball -PathType Leaf)) {
|
|
776
|
+
[Console]::Error.WriteLine("ERROR: aid: bundle file not found: $tarball")
|
|
777
|
+
script:Exit-Aid 1
|
|
778
|
+
}
|
|
779
|
+
if (-not (Verify-BundleChecksum -Tarball $tarball)) { script:Exit-Aid 4 }
|
|
780
|
+
$tbase = [System.IO.Path]::GetFileName($tarball)
|
|
781
|
+
$script:_DispResolvedVersion = $tbase -replace "^aid-$Tool-v", '' -replace '\.tar\.gz$', ''
|
|
782
|
+
if (-not $script:_DispResolvedVersion) {
|
|
783
|
+
$script:_DispResolvedVersion = if ($Version) { $Version } else { 'unknown' }
|
|
784
|
+
}
|
|
785
|
+
if (-not (Extract-Tarball -Tarball $tarball -DestDir $toolStaging)) { script:Exit-Aid 1 }
|
|
786
|
+
} else {
|
|
787
|
+
if (-not $Version) {
|
|
788
|
+
$script:_DispResolvedVersion = Resolve-AidVersion
|
|
789
|
+
if (-not $script:_DispResolvedVersion) { script:Exit-Aid 3 }
|
|
790
|
+
} else {
|
|
791
|
+
$script:_DispResolvedVersion = $Version
|
|
792
|
+
}
|
|
793
|
+
$dlDir = Join-Path $_AidStagingBase ("download-$Tool-" + [System.IO.Path]::GetRandomFileName())
|
|
794
|
+
New-Item -ItemType Directory -Path $dlDir -Force | Out-Null
|
|
795
|
+
if (-not (Fetch-Tarball -Tool $Tool -Version $script:_DispResolvedVersion -DestDir $dlDir)) {
|
|
796
|
+
script:Exit-Aid 3
|
|
797
|
+
}
|
|
798
|
+
$tarball = Join-Path $dlDir "aid-$Tool-v$($script:_DispResolvedVersion).tar.gz"
|
|
799
|
+
if (-not (Extract-Tarball -Tarball $tarball -DestDir $toolStaging)) { script:Exit-Aid 1 }
|
|
800
|
+
}
|
|
801
|
+
|
|
802
|
+
$script:_DispStagingDir = $toolStaging
|
|
803
|
+
}
|
|
804
|
+
|
|
805
|
+
# ---------------------------------------------------------------------------
|
|
806
|
+
# Dispatch to engine.
|
|
807
|
+
# ---------------------------------------------------------------------------
|
|
808
|
+
try {
|
|
809
|
+
$overallBlocked = $false
|
|
810
|
+
|
|
811
|
+
switch ($SUBCMD) {
|
|
812
|
+
{ $_ -in @('add', 'update') } {
|
|
813
|
+
foreach ($t in $_AidTools) {
|
|
814
|
+
Write-Host ""
|
|
815
|
+
script:Prepare-AidToolStaging -Tool $t -Version $_AidVersionArg -Bundle $_AidFromBundle
|
|
816
|
+
Write-Host "Installing $t v$($script:_DispResolvedVersion) -> $_AidTarget"
|
|
817
|
+
$rc = Install-AidTool -StagingDir $script:_DispStagingDir -Tool $t -Target $_AidTarget `
|
|
818
|
+
-Version $script:_DispResolvedVersion -Force ([bool]$_AidForce) `
|
|
819
|
+
-AidVerbose $script:_AidVerbose
|
|
820
|
+
if ($rc -eq 5) {
|
|
821
|
+
$overallBlocked = $true
|
|
822
|
+
} elseif ($rc -ne 0) {
|
|
823
|
+
script:Exit-Aid $rc
|
|
824
|
+
}
|
|
825
|
+
}
|
|
826
|
+
|
|
827
|
+
Write-Host ""
|
|
828
|
+
if ($overallBlocked) {
|
|
829
|
+
Write-Host "Install complete with warnings: one or more root agent files were not overwritten."
|
|
830
|
+
Write-Host "Review the *.aid-new file(s) and merge, or re-run with -Force to overwrite."
|
|
831
|
+
script:Exit-Aid 5
|
|
832
|
+
}
|
|
833
|
+
Write-Host "Done. AID $($script:_DispResolvedVersion) installed into: $_AidTarget"
|
|
834
|
+
script:Exit-Aid 0
|
|
835
|
+
}
|
|
836
|
+
|
|
837
|
+
'remove' {
|
|
838
|
+
if (-not (Test-ManifestExists -ManifestPath $_AidManifest)) {
|
|
839
|
+
[Console]::Error.WriteLine("ERROR: aid: no manifest at $_AidTarget\.aid\.aid-manifest.json; nothing to uninstall")
|
|
840
|
+
script:Exit-Aid 6
|
|
841
|
+
}
|
|
842
|
+
|
|
843
|
+
foreach ($t in $_AidTools) {
|
|
844
|
+
Write-Host ""
|
|
845
|
+
Write-Host "Uninstalling $t from $_AidTarget"
|
|
846
|
+
$rc = Uninstall-AidTool -ManifestPath $_AidManifest -Tool $t -Target $_AidTarget `
|
|
847
|
+
-AidVerbose $script:_AidVerbose
|
|
848
|
+
if ($rc -eq 6) { script:Exit-Aid 6 }
|
|
849
|
+
if ($rc -ne 0) { script:Exit-Aid $rc }
|
|
850
|
+
}
|
|
851
|
+
|
|
852
|
+
Write-Host ""
|
|
853
|
+
Write-Host "Uninstall complete."
|
|
854
|
+
script:Exit-Aid 0
|
|
855
|
+
}
|
|
856
|
+
}
|
|
857
|
+
} finally {
|
|
858
|
+
if (Test-Path $_AidStagingBase -PathType Container) {
|
|
859
|
+
Remove-Item -LiteralPath $_AidStagingBase -Recurse -Force -ErrorAction SilentlyContinue
|
|
860
|
+
}
|
|
861
|
+
}
|
|
862
|
+
|
|
863
|
+
} catch {
|
|
864
|
+
$msg = "$_"
|
|
865
|
+
if ($msg.StartsWith($script:_SentinelTag)) {
|
|
866
|
+
# Clean unwind in piped mode - $global:LASTEXITCODE already set.
|
|
867
|
+
return
|
|
868
|
+
}
|
|
869
|
+
if ($script:_PipedMode) {
|
|
870
|
+
$global:LASTEXITCODE = 1
|
|
871
|
+
[Console]::Error.WriteLine("ERROR: aid: unhandled exception: $_")
|
|
872
|
+
return
|
|
873
|
+
}
|
|
874
|
+
throw
|
|
875
|
+
}
|