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
ADDED
|
@@ -0,0 +1,931 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# aid - AID CLI dispatcher (Bash side).
|
|
3
|
+
#
|
|
4
|
+
# Purpose:
|
|
5
|
+
# Persistent global command installed at $AID_HOME/bin/aid. Parses
|
|
6
|
+
# subcommands and dispatches to the shared install-core engine located at
|
|
7
|
+
# $AID_HOME/lib/aid-install-core.sh. 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
|
+
# --from-bundle <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
|
+
# --no-path (bootstrap / update self only) Skip PATH wiring.
|
|
27
|
+
|
|
28
|
+
set -uo pipefail
|
|
29
|
+
|
|
30
|
+
# ---------------------------------------------------------------------------
|
|
31
|
+
# Bootstrap URL - single place to update when the branch merges to master.
|
|
32
|
+
# Override with AID_INSTALL_URL env var for tests.
|
|
33
|
+
# ---------------------------------------------------------------------------
|
|
34
|
+
AID_INSTALL_URL="${AID_INSTALL_URL:-https://raw.githubusercontent.com/AndreVianna/aid-methodology/worktree-work-002-auto-installer/install.sh}"
|
|
35
|
+
|
|
36
|
+
# ---------------------------------------------------------------------------
|
|
37
|
+
# Locate $AID_HOME. The installed dispatcher lives at $AID_HOME/bin/aid.
|
|
38
|
+
# When invoked directly (not via PATH), BASH_SOURCE[0] is the absolute path.
|
|
39
|
+
# ---------------------------------------------------------------------------
|
|
40
|
+
_AID_SELF="${BASH_SOURCE[0]:-}"
|
|
41
|
+
if [[ -n "$_AID_SELF" && -f "$_AID_SELF" ]]; then
|
|
42
|
+
# Resolve symlinks so we get the real directory.
|
|
43
|
+
_AID_SELF_REAL="$(cd "$(dirname "$_AID_SELF")" && pwd -P)/$(basename "$_AID_SELF")"
|
|
44
|
+
AID_HOME="${AID_HOME:-$(dirname "$(dirname "$_AID_SELF_REAL")")}"
|
|
45
|
+
else
|
|
46
|
+
AID_HOME="${AID_HOME:-${HOME}/.aid}"
|
|
47
|
+
fi
|
|
48
|
+
|
|
49
|
+
# ---------------------------------------------------------------------------
|
|
50
|
+
# Source the shared install core from $AID_HOME/lib/.
|
|
51
|
+
# ---------------------------------------------------------------------------
|
|
52
|
+
_AID_CORE="${AID_HOME}/lib/aid-install-core.sh"
|
|
53
|
+
if [[ ! -f "$_AID_CORE" ]]; then
|
|
54
|
+
echo "ERROR: aid: install core not found at ${_AID_CORE}. Re-run the AID bootstrap to repair." >&2
|
|
55
|
+
exit 1
|
|
56
|
+
fi
|
|
57
|
+
# shellcheck source=../lib/aid-install-core.sh
|
|
58
|
+
source "$_AID_CORE"
|
|
59
|
+
|
|
60
|
+
# Defensive guard: verify the required core function was defined by the sourced lib.
|
|
61
|
+
# This catches an upgrade that left a stale aid-install-core.sh (missing new functions).
|
|
62
|
+
if ! declare -F aid_status_body >/dev/null 2>&1; then
|
|
63
|
+
echo "ERROR: aid: CLI core is stale or incomplete at ${_AID_CORE}. Re-run the installer (or 'aid update self')." >&2
|
|
64
|
+
exit 1
|
|
65
|
+
fi
|
|
66
|
+
|
|
67
|
+
# ---------------------------------------------------------------------------
|
|
68
|
+
# Usage helper.
|
|
69
|
+
# ---------------------------------------------------------------------------
|
|
70
|
+
_aid_usage() {
|
|
71
|
+
local sub="${1:-}"
|
|
72
|
+
case "$sub" in
|
|
73
|
+
status)
|
|
74
|
+
printf 'aid status [--verbose] [--target <dir>]\n'
|
|
75
|
+
printf ' Show AID state of the current project (default: cwd).\n'
|
|
76
|
+
printf ' Exit 7 when no AID install is found.\n'
|
|
77
|
+
;;
|
|
78
|
+
add)
|
|
79
|
+
printf 'aid add <tool>[,<tool>...] [--version <v>] [--from-bundle <path>]\n'
|
|
80
|
+
printf ' [--force] [--verbose] [--target <dir>]\n'
|
|
81
|
+
printf ' Add tool(s) to the current project.\n'
|
|
82
|
+
printf ' Tools: claude-code, codex, cursor, copilot-cli, antigravity\n'
|
|
83
|
+
;;
|
|
84
|
+
remove)
|
|
85
|
+
printf 'aid remove [<tool>[,<tool>...] | self] [--force] [--verbose] [--target <dir>]\n'
|
|
86
|
+
printf ' Remove tool(s) from the current project (manifest-driven).\n'
|
|
87
|
+
printf ' No args: remove ALL AID from the project (asks for confirmation).\n'
|
|
88
|
+
printf ' self: remove the aid CLI itself (asks for confirmation).\n'
|
|
89
|
+
;;
|
|
90
|
+
update)
|
|
91
|
+
printf 'aid update [<tool>... | self] [--version <v>] [--from-bundle <path>]\n'
|
|
92
|
+
printf ' [--force] [--verbose] [--target <dir>]\n'
|
|
93
|
+
printf ' Update to latest. No args: update all installed tools.\n'
|
|
94
|
+
printf ' self: update the aid CLI itself.\n'
|
|
95
|
+
;;
|
|
96
|
+
version)
|
|
97
|
+
printf 'aid version\n'
|
|
98
|
+
printf ' Print the installed aid CLI version and exit 0.\n'
|
|
99
|
+
;;
|
|
100
|
+
*)
|
|
101
|
+
printf 'aid - AID CLI\n'
|
|
102
|
+
printf '\n'
|
|
103
|
+
printf 'Usage:\n'
|
|
104
|
+
printf ' aid Show the dashboard\n'
|
|
105
|
+
printf ' aid -h | --help Show this help\n'
|
|
106
|
+
printf ' aid version Print the CLI version\n'
|
|
107
|
+
printf ' aid status Show AID state of the current project\n'
|
|
108
|
+
printf ' aid add <tool>[,...] Add tool(s) to the current project\n'
|
|
109
|
+
printf ' aid update [<tool>... | self] Update to latest; no arg = all tools\n'
|
|
110
|
+
printf ' aid remove [<tool>... | self] Remove; no arg = ALL AID from project\n'
|
|
111
|
+
printf ' aid <command> -h | --help Per-command help\n'
|
|
112
|
+
printf '\n'
|
|
113
|
+
printf 'Flags: --from-bundle, --version, --force, --target, --verbose\n'
|
|
114
|
+
printf "Run 'aid <command> -h' for details.\n"
|
|
115
|
+
;;
|
|
116
|
+
esac
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
# ---------------------------------------------------------------------------
|
|
120
|
+
# Error helper.
|
|
121
|
+
# ---------------------------------------------------------------------------
|
|
122
|
+
_aid_die() {
|
|
123
|
+
echo "ERROR: aid: $1" >&2
|
|
124
|
+
exit "${2:-1}"
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
# ---------------------------------------------------------------------------
|
|
128
|
+
# Locate the bootstrap install.sh to delegate add/remove/update.
|
|
129
|
+
# Prefers the sibling ../install.sh (if aid is run from the release tree),
|
|
130
|
+
# then a resolved bootstrap relative to AID_HOME.
|
|
131
|
+
# ---------------------------------------------------------------------------
|
|
132
|
+
_find_install_sh() {
|
|
133
|
+
# Sibling of the bin/ dir: AID_HOME/../install.sh would be the release root.
|
|
134
|
+
# But installed layout is: AID_HOME/bin/aid + AID_HOME/lib/aid-install-core.sh
|
|
135
|
+
# The install.sh is NOT shipped inside AID_HOME - we use the core functions directly.
|
|
136
|
+
# Return empty string - callers will use the engine functions directly.
|
|
137
|
+
echo ""
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
# ---------------------------------------------------------------------------
|
|
141
|
+
# Update check (throttled, cached, non-blocking, opt-out).
|
|
142
|
+
# ---------------------------------------------------------------------------
|
|
143
|
+
|
|
144
|
+
# _aid_check_update
|
|
145
|
+
# Compares the installed CLI version ($AID_HOME/VERSION) against the latest
|
|
146
|
+
# GitHub release. Prints ONE notice line when a newer version is available.
|
|
147
|
+
# Fail-silent: any error (no curl, network down, bad JSON) is suppressed.
|
|
148
|
+
# Throttle: re-fetches at most once per 24h; caches result in $AID_HOME/.update-check.
|
|
149
|
+
# Opt-out: AID_NO_UPDATE_CHECK=1 -> skip entirely.
|
|
150
|
+
# Test hook: AID_UPDATE_CHECK_URL overrides the fetch URL (and bypasses throttle).
|
|
151
|
+
_aid_check_update() {
|
|
152
|
+
# Opt-out.
|
|
153
|
+
[[ "${AID_NO_UPDATE_CHECK:-0}" == "1" ]] && return 0
|
|
154
|
+
|
|
155
|
+
# Read installed version.
|
|
156
|
+
local installed_version=""
|
|
157
|
+
local ver_file="${AID_HOME}/VERSION"
|
|
158
|
+
if [[ -f "$ver_file" ]]; then
|
|
159
|
+
installed_version="$(tr -d '[:space:]' < "$ver_file")"
|
|
160
|
+
fi
|
|
161
|
+
[[ -z "$installed_version" ]] && return 0
|
|
162
|
+
|
|
163
|
+
local cache_file="${AID_HOME}/.update-check"
|
|
164
|
+
local now
|
|
165
|
+
now="$(date +%s 2>/dev/null)" || return 0
|
|
166
|
+
local throttle_secs=86400 # 24 hours
|
|
167
|
+
|
|
168
|
+
# Determine the fetch URL (test override or real GitHub API).
|
|
169
|
+
local check_url="${AID_UPDATE_CHECK_URL:-}"
|
|
170
|
+
local use_throttle=1
|
|
171
|
+
if [[ -n "$check_url" ]]; then
|
|
172
|
+
# Test override: bypass throttle so tests run on first invocation.
|
|
173
|
+
use_throttle=0
|
|
174
|
+
else
|
|
175
|
+
check_url="${AID_API_BASE}/releases/latest"
|
|
176
|
+
fi
|
|
177
|
+
|
|
178
|
+
# Try to read cache.
|
|
179
|
+
local cached_ts=0
|
|
180
|
+
local cached_latest=""
|
|
181
|
+
if [[ -f "$cache_file" ]]; then
|
|
182
|
+
cached_ts="$(awk 'NR==1{print $1}' "$cache_file" 2>/dev/null)" || cached_ts=0
|
|
183
|
+
cached_latest="$(awk 'NR==2{print $1}' "$cache_file" 2>/dev/null)" || cached_latest=""
|
|
184
|
+
fi
|
|
185
|
+
|
|
186
|
+
# Decide whether to fetch.
|
|
187
|
+
local latest_version=""
|
|
188
|
+
local need_fetch=1
|
|
189
|
+
if [[ "$use_throttle" -eq 1 && -n "$cached_latest" ]]; then
|
|
190
|
+
local age=$(( now - ${cached_ts:-0} ))
|
|
191
|
+
if [[ "$age" -lt "$throttle_secs" ]]; then
|
|
192
|
+
need_fetch=0
|
|
193
|
+
latest_version="$cached_latest"
|
|
194
|
+
fi
|
|
195
|
+
fi
|
|
196
|
+
|
|
197
|
+
if [[ "$need_fetch" -eq 1 ]]; then
|
|
198
|
+
# Fetch latest release tag - hard 2s timeout, fail-silent.
|
|
199
|
+
local response=""
|
|
200
|
+
if command -v curl >/dev/null 2>&1; then
|
|
201
|
+
response="$(curl --max-time 2 -fsS "$check_url" 2>/dev/null)" || return 0
|
|
202
|
+
else
|
|
203
|
+
return 0
|
|
204
|
+
fi
|
|
205
|
+
|
|
206
|
+
# Parse tag_name; strip leading 'v'.
|
|
207
|
+
local tag
|
|
208
|
+
tag="$(printf '%s' "$response" | grep '"tag_name"' | head -1 | \
|
|
209
|
+
sed 's/.*"tag_name"[[:space:]]*:[[:space:]]*"\([^"]*\)".*/\1/')" || return 0
|
|
210
|
+
[[ -z "$tag" ]] && return 0
|
|
211
|
+
latest_version="${tag#v}"
|
|
212
|
+
|
|
213
|
+
# Update cache.
|
|
214
|
+
printf '%s\n%s\n' "$now" "$latest_version" > "$cache_file" 2>/dev/null || true
|
|
215
|
+
fi
|
|
216
|
+
|
|
217
|
+
[[ -z "$latest_version" ]] && return 0
|
|
218
|
+
|
|
219
|
+
# Compare: show notice only when latest > installed.
|
|
220
|
+
if _semver_lt "$installed_version" "$latest_version"; then
|
|
221
|
+
local _update_cmd
|
|
222
|
+
case "${AID_INSTALL_CHANNEL:-}" in
|
|
223
|
+
npm) _update_cmd="npm i -g aid-installer@latest" ;;
|
|
224
|
+
pypi) _update_cmd="pipx upgrade aid-installer (or: pip install --user -U aid-installer)" ;;
|
|
225
|
+
*) _update_cmd="aid update self" ;;
|
|
226
|
+
esac
|
|
227
|
+
printf 'A newer aid CLI is available: v%s (you have v%s). Run: %s\n' \
|
|
228
|
+
"$latest_version" "$installed_version" "$_update_cmd"
|
|
229
|
+
fi
|
|
230
|
+
return 0
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
# ---------------------------------------------------------------------------
|
|
234
|
+
# update self command (formerly self-update).
|
|
235
|
+
# ---------------------------------------------------------------------------
|
|
236
|
+
_cmd_update_self() {
|
|
237
|
+
# AID_INSTALL_CHANNEL guard: npm/pypi channels print a package-manager hint
|
|
238
|
+
# and exit 0 instead of re-bootstrapping (which would overwrite the channel).
|
|
239
|
+
case "${AID_INSTALL_CHANNEL:-}" in
|
|
240
|
+
npm)
|
|
241
|
+
printf 'Updating the aid CLI: run npm i -g aid-installer@latest\n'
|
|
242
|
+
return 0
|
|
243
|
+
;;
|
|
244
|
+
pypi)
|
|
245
|
+
printf 'Updating the aid CLI: run pipx upgrade aid-installer (or: pip install --user -U aid-installer)\n'
|
|
246
|
+
return 0
|
|
247
|
+
;;
|
|
248
|
+
esac
|
|
249
|
+
printf 'Updating the aid CLI...\n'
|
|
250
|
+
if command -v curl >/dev/null 2>&1; then
|
|
251
|
+
curl -fsSL "${AID_INSTALL_URL}" | bash
|
|
252
|
+
return $?
|
|
253
|
+
else
|
|
254
|
+
echo "ERROR: aid: curl not found; cannot update self" >&2
|
|
255
|
+
return 3
|
|
256
|
+
fi
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
# ---------------------------------------------------------------------------
|
|
260
|
+
# Path-wiring helpers (Unix).
|
|
261
|
+
# ---------------------------------------------------------------------------
|
|
262
|
+
|
|
263
|
+
# _wire_one_profile <bin_dir> <profile_file>
|
|
264
|
+
# Idempotently write the fenced PATH block into a single profile file.
|
|
265
|
+
_wire_one_profile() {
|
|
266
|
+
local bin_dir="$1"
|
|
267
|
+
local profile="$2"
|
|
268
|
+
|
|
269
|
+
# Create the profile file if it doesn't exist.
|
|
270
|
+
if [[ ! -f "$profile" ]]; then
|
|
271
|
+
touch "$profile" 2>/dev/null || {
|
|
272
|
+
echo "WARN: aid: could not create ${profile}; PATH not wired." >&2
|
|
273
|
+
printf 'Add "%s" to your PATH manually.\n' "$bin_dir"
|
|
274
|
+
return 0
|
|
275
|
+
}
|
|
276
|
+
fi
|
|
277
|
+
|
|
278
|
+
local fence_start='# >>> aid CLI >>>'
|
|
279
|
+
local fence_end='# <<< aid CLI <<<'
|
|
280
|
+
# Duplicate-guarded export: safe when multiple rc files are sourced.
|
|
281
|
+
local path_line="case \":\$PATH:\" in *\":${bin_dir}:\"*) ;; *) export PATH=\"${bin_dir}:\$PATH\" ;; esac"
|
|
282
|
+
|
|
283
|
+
if grep -qF "$fence_start" "$profile" 2>/dev/null; then
|
|
284
|
+
# Replace the existing block in-place.
|
|
285
|
+
local tmp_profile
|
|
286
|
+
tmp_profile="$(mktemp "${profile}.aid-tmp.XXXXXX")"
|
|
287
|
+
awk -v fs="$fence_start" -v fe="$fence_end" -v pl="$path_line" '
|
|
288
|
+
BEGIN { skip=0 }
|
|
289
|
+
$0 == fs { skip=1; print fs; print pl; print fe; next }
|
|
290
|
+
skip && $0 == fe { skip=0; next }
|
|
291
|
+
skip { next }
|
|
292
|
+
{ print }
|
|
293
|
+
' "$profile" > "$tmp_profile"
|
|
294
|
+
mv "$tmp_profile" "$profile"
|
|
295
|
+
echo "PATH wiring updated in ${profile}."
|
|
296
|
+
else
|
|
297
|
+
# Append the block.
|
|
298
|
+
printf '\n%s\n%s\n%s\n' "$fence_start" "$path_line" "$fence_end" >> "$profile"
|
|
299
|
+
echo "PATH wiring added to ${profile}."
|
|
300
|
+
fi
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
# _wire_path_unix <aid_bin_dir> [--no-path] [--profile-file <file>]
|
|
304
|
+
# Idempotently add $aid_bin_dir to PATH via a fenced block.
|
|
305
|
+
# Without --profile-file, wires ALL standard rc files that exist (rustup/nvm pattern).
|
|
306
|
+
# When --no-path is given, print the manual instruction and return.
|
|
307
|
+
_wire_path_unix() {
|
|
308
|
+
local bin_dir="$1"
|
|
309
|
+
local no_path=0
|
|
310
|
+
local profile_override=""
|
|
311
|
+
|
|
312
|
+
shift
|
|
313
|
+
while [[ $# -gt 0 ]]; do
|
|
314
|
+
case "$1" in
|
|
315
|
+
--no-path) no_path=1; shift ;;
|
|
316
|
+
--profile-file) profile_override="$2"; shift 2 ;;
|
|
317
|
+
*) shift ;;
|
|
318
|
+
esac
|
|
319
|
+
done
|
|
320
|
+
|
|
321
|
+
if [[ "$no_path" -eq 1 ]]; then
|
|
322
|
+
printf 'Add "%s" to your PATH manually.\n' "$bin_dir"
|
|
323
|
+
return 0
|
|
324
|
+
fi
|
|
325
|
+
|
|
326
|
+
if [[ -n "$profile_override" ]]; then
|
|
327
|
+
_wire_one_profile "$bin_dir" "$profile_override"
|
|
328
|
+
echo "Open a new shell, or run: export PATH=\"${bin_dir}:\$PATH\" (or: source ${profile_override})"
|
|
329
|
+
return 0
|
|
330
|
+
fi
|
|
331
|
+
|
|
332
|
+
# Wire every standard rc file that already exists.
|
|
333
|
+
local _wp_candidates=(
|
|
334
|
+
"${ZDOTDIR:-${HOME}}/.zshrc"
|
|
335
|
+
"${HOME}/.bashrc"
|
|
336
|
+
"${HOME}/.bash_profile"
|
|
337
|
+
"${HOME}/.profile"
|
|
338
|
+
)
|
|
339
|
+
local _wp_wired=()
|
|
340
|
+
local _wp_rc
|
|
341
|
+
for _wp_rc in "${_wp_candidates[@]}"; do
|
|
342
|
+
if [[ -f "$_wp_rc" ]]; then
|
|
343
|
+
_wire_one_profile "$bin_dir" "$_wp_rc"
|
|
344
|
+
_wp_wired+=("$_wp_rc")
|
|
345
|
+
fi
|
|
346
|
+
done
|
|
347
|
+
# If none exist, create and wire ~/.profile.
|
|
348
|
+
if [[ "${#_wp_wired[@]}" -eq 0 ]]; then
|
|
349
|
+
_wire_one_profile "$bin_dir" "${HOME}/.profile"
|
|
350
|
+
_wp_wired+=("${HOME}/.profile")
|
|
351
|
+
fi
|
|
352
|
+
# Summarise.
|
|
353
|
+
local _wp_display=""
|
|
354
|
+
local _wp_w
|
|
355
|
+
for _wp_w in "${_wp_wired[@]}"; do
|
|
356
|
+
local _wp_rel="${_wp_w/#${HOME}/~}"
|
|
357
|
+
_wp_display="${_wp_display:+${_wp_display}, }${_wp_rel}"
|
|
358
|
+
done
|
|
359
|
+
echo "PATH wiring added to: ${_wp_display}"
|
|
360
|
+
echo "Open a new shell to pick up the updated PATH."
|
|
361
|
+
}
|
|
362
|
+
|
|
363
|
+
# _unwire_path_unix [--profile-file <file>]
|
|
364
|
+
# Remove the fenced PATH block from all standard rc files (or a single explicit file).
|
|
365
|
+
_unwire_path_unix() {
|
|
366
|
+
local profile_override=""
|
|
367
|
+
while [[ $# -gt 0 ]]; do
|
|
368
|
+
case "$1" in
|
|
369
|
+
--profile-file) profile_override="$2"; shift 2 ;;
|
|
370
|
+
*) shift ;;
|
|
371
|
+
esac
|
|
372
|
+
done
|
|
373
|
+
|
|
374
|
+
local fence_start='# >>> aid CLI >>>'
|
|
375
|
+
|
|
376
|
+
_unwire_one() {
|
|
377
|
+
local _uw_f="$1"
|
|
378
|
+
if [[ ! -f "$_uw_f" ]]; then
|
|
379
|
+
return 0
|
|
380
|
+
fi
|
|
381
|
+
if ! grep -qF "$fence_start" "$_uw_f" 2>/dev/null; then
|
|
382
|
+
return 0
|
|
383
|
+
fi
|
|
384
|
+
local tmp_profile
|
|
385
|
+
tmp_profile="$(mktemp "${_uw_f}.aid-tmp.XXXXXX")"
|
|
386
|
+
awk -v start="$fence_start" -v end='# <<< aid CLI <<<' '
|
|
387
|
+
BEGIN { skip=0 }
|
|
388
|
+
$0 == start { skip=1; next }
|
|
389
|
+
skip && $0 == end { skip=0; next }
|
|
390
|
+
skip { next }
|
|
391
|
+
{ print }
|
|
392
|
+
' "$_uw_f" > "$tmp_profile"
|
|
393
|
+
mv "$tmp_profile" "$_uw_f"
|
|
394
|
+
echo "PATH wiring removed from ${_uw_f}."
|
|
395
|
+
}
|
|
396
|
+
|
|
397
|
+
if [[ -n "$profile_override" ]]; then
|
|
398
|
+
_unwire_one "$profile_override"
|
|
399
|
+
return 0
|
|
400
|
+
fi
|
|
401
|
+
|
|
402
|
+
# Remove from all standard rc files.
|
|
403
|
+
local _uw_rc
|
|
404
|
+
for _uw_rc in \
|
|
405
|
+
"${ZDOTDIR:-${HOME}}/.zshrc" \
|
|
406
|
+
"${HOME}/.bashrc" \
|
|
407
|
+
"${HOME}/.bash_profile" \
|
|
408
|
+
"${HOME}/.profile"
|
|
409
|
+
do
|
|
410
|
+
_unwire_one "$_uw_rc"
|
|
411
|
+
done
|
|
412
|
+
}
|
|
413
|
+
|
|
414
|
+
# ---------------------------------------------------------------------------
|
|
415
|
+
# Global CLI install helpers.
|
|
416
|
+
# ---------------------------------------------------------------------------
|
|
417
|
+
|
|
418
|
+
# _install_global_cli <version> <src_bin_aid> <src_lib_core>
|
|
419
|
+
# Stage then atomic-move into $AID_HOME.
|
|
420
|
+
_install_global_cli() {
|
|
421
|
+
local version="$1"
|
|
422
|
+
local src_bin_aid="$2"
|
|
423
|
+
local src_lib_core="$3"
|
|
424
|
+
|
|
425
|
+
local aid_home="${AID_HOME:-${HOME}/.aid}"
|
|
426
|
+
local bin_dir="${aid_home}/bin"
|
|
427
|
+
local lib_dir="${aid_home}/lib"
|
|
428
|
+
|
|
429
|
+
mkdir -p "$bin_dir" "$lib_dir"
|
|
430
|
+
|
|
431
|
+
# Copy the dispatcher.
|
|
432
|
+
cp "$src_bin_aid" "${bin_dir}/aid"
|
|
433
|
+
chmod +x "${bin_dir}/aid"
|
|
434
|
+
|
|
435
|
+
# Copy the core lib.
|
|
436
|
+
cp "$src_lib_core" "${lib_dir}/aid-install-core.sh"
|
|
437
|
+
|
|
438
|
+
# Write the VERSION file.
|
|
439
|
+
printf '%s\n' "$version" > "${aid_home}/VERSION"
|
|
440
|
+
|
|
441
|
+
echo "aid CLI v${version} installed to ${aid_home}."
|
|
442
|
+
}
|
|
443
|
+
|
|
444
|
+
# ---------------------------------------------------------------------------
|
|
445
|
+
# remove self (formerly self-uninstall).
|
|
446
|
+
# ---------------------------------------------------------------------------
|
|
447
|
+
|
|
448
|
+
_cmd_remove_self() {
|
|
449
|
+
local force=0
|
|
450
|
+
local no_path=0
|
|
451
|
+
local profile_file=""
|
|
452
|
+
|
|
453
|
+
while [[ $# -gt 0 ]]; do
|
|
454
|
+
case "$1" in
|
|
455
|
+
--force|-y) force=1; shift ;;
|
|
456
|
+
--no-path) no_path=1; shift ;;
|
|
457
|
+
--profile-file) profile_file="$2"; shift 2 ;;
|
|
458
|
+
-h|--help) _aid_usage remove; exit 0 ;;
|
|
459
|
+
*) _aid_die "unknown flag for 'remove self': $1" 2 ;;
|
|
460
|
+
esac
|
|
461
|
+
done
|
|
462
|
+
|
|
463
|
+
# Apply AID_FORCE env-var fallback.
|
|
464
|
+
if [[ "$force" -eq 0 && ( "${AID_FORCE:-0}" == "1" || "${AID_FORCE:-0}" == "true" ) ]]; then
|
|
465
|
+
force=1
|
|
466
|
+
fi
|
|
467
|
+
|
|
468
|
+
local aid_home="${AID_HOME:-${HOME}/.aid}"
|
|
469
|
+
|
|
470
|
+
if [[ "$force" -eq 0 ]]; then
|
|
471
|
+
# Skip prompt when non-interactive (piped or no tty).
|
|
472
|
+
if [[ ! -t 0 ]]; then
|
|
473
|
+
force=1
|
|
474
|
+
else
|
|
475
|
+
printf 'Remove the aid CLI from %s? [y/N] ' "$aid_home"
|
|
476
|
+
local answer
|
|
477
|
+
if [[ -e /dev/tty ]]; then
|
|
478
|
+
read -r answer < /dev/tty
|
|
479
|
+
else
|
|
480
|
+
read -r answer
|
|
481
|
+
fi
|
|
482
|
+
if [[ "$answer" != "y" && "$answer" != "Y" && "$answer" != "yes" && "$answer" != "YES" ]]; then
|
|
483
|
+
echo "Aborted."
|
|
484
|
+
exit 0
|
|
485
|
+
fi
|
|
486
|
+
fi
|
|
487
|
+
fi
|
|
488
|
+
|
|
489
|
+
local partial=0
|
|
490
|
+
|
|
491
|
+
# Remove PATH wiring first (while we still have the profile-detect logic).
|
|
492
|
+
if [[ "$no_path" -eq 0 ]]; then
|
|
493
|
+
if [[ -n "$profile_file" ]]; then
|
|
494
|
+
_unwire_path_unix --profile-file "$profile_file" || partial=1
|
|
495
|
+
else
|
|
496
|
+
_unwire_path_unix || partial=1
|
|
497
|
+
fi
|
|
498
|
+
fi
|
|
499
|
+
|
|
500
|
+
# Remove $AID_HOME.
|
|
501
|
+
if [[ -d "$aid_home" ]]; then
|
|
502
|
+
rm -rf "$aid_home" || {
|
|
503
|
+
echo "ERROR: aid: failed to remove ${aid_home}" >&2
|
|
504
|
+
partial=1
|
|
505
|
+
}
|
|
506
|
+
fi
|
|
507
|
+
|
|
508
|
+
if [[ "$partial" -eq 1 ]]; then
|
|
509
|
+
echo "aid CLI partially removed. Check the messages above for what remained."
|
|
510
|
+
exit 1
|
|
511
|
+
fi
|
|
512
|
+
|
|
513
|
+
echo "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."
|
|
514
|
+
exit 0
|
|
515
|
+
}
|
|
516
|
+
|
|
517
|
+
# ---------------------------------------------------------------------------
|
|
518
|
+
# Parse subcommand and dispatch.
|
|
519
|
+
# ---------------------------------------------------------------------------
|
|
520
|
+
|
|
521
|
+
# Shared flag buckets (populated during subcommand-specific arg parsing).
|
|
522
|
+
_AID_TOOL_ARG=""
|
|
523
|
+
_AID_VERSION_ARG=""
|
|
524
|
+
_AID_FROM_BUNDLE=""
|
|
525
|
+
_AID_FORCE=0
|
|
526
|
+
_AID_TARGET=""
|
|
527
|
+
_AID_VERBOSE="${AID_VERBOSE:-0}"
|
|
528
|
+
_AID_NO_PATH=0
|
|
529
|
+
|
|
530
|
+
# ---------------------------------------------------------------------------
|
|
531
|
+
# Dashboard (bare 'aid' - no arguments).
|
|
532
|
+
# ---------------------------------------------------------------------------
|
|
533
|
+
_cmd_dashboard() {
|
|
534
|
+
# Block 1 + 2: Header + description.
|
|
535
|
+
local cli_version="unknown"
|
|
536
|
+
local ver_file="${AID_HOME}/VERSION"
|
|
537
|
+
if [[ -f "$ver_file" ]]; then
|
|
538
|
+
cli_version="$(tr -d '[:space:]' < "$ver_file")"
|
|
539
|
+
fi
|
|
540
|
+
printf 'AID v%s - Agentic Iterative Development\n' "$cli_version"
|
|
541
|
+
printf 'Install, update, and manage AID across your repositories.\n'
|
|
542
|
+
|
|
543
|
+
# Block 3: Installed tools for cwd.
|
|
544
|
+
printf '\n'
|
|
545
|
+
aid_status_body "."
|
|
546
|
+
|
|
547
|
+
# Block 4: Usage/help.
|
|
548
|
+
printf '\n'
|
|
549
|
+
_aid_usage
|
|
550
|
+
|
|
551
|
+
# Block 5: Update check notice (final line, non-blocking).
|
|
552
|
+
_aid_check_update
|
|
553
|
+
}
|
|
554
|
+
|
|
555
|
+
# Early help check.
|
|
556
|
+
if [[ $# -eq 0 ]]; then
|
|
557
|
+
# Bare 'aid' -> dashboard landing screen.
|
|
558
|
+
_cmd_dashboard
|
|
559
|
+
exit 0
|
|
560
|
+
fi
|
|
561
|
+
|
|
562
|
+
case "$1" in
|
|
563
|
+
-h|--help)
|
|
564
|
+
_aid_usage
|
|
565
|
+
exit 0
|
|
566
|
+
;;
|
|
567
|
+
esac
|
|
568
|
+
|
|
569
|
+
SUBCMD="$1"
|
|
570
|
+
shift
|
|
571
|
+
|
|
572
|
+
# ---- version ----------------------------------------------------------------
|
|
573
|
+
if [[ "$SUBCMD" == "version" ]]; then
|
|
574
|
+
local_version_file="${AID_HOME}/VERSION"
|
|
575
|
+
if [[ -f "$local_version_file" ]]; then
|
|
576
|
+
cat "$local_version_file"
|
|
577
|
+
else
|
|
578
|
+
echo "unknown (VERSION file not found at ${local_version_file})"
|
|
579
|
+
fi
|
|
580
|
+
exit 0
|
|
581
|
+
fi
|
|
582
|
+
|
|
583
|
+
# ---- help -------------------------------------------------------------------
|
|
584
|
+
if [[ "$SUBCMD" == "help" || "$SUBCMD" == "-h" || "$SUBCMD" == "--help" ]]; then
|
|
585
|
+
_aid_usage
|
|
586
|
+
exit 0
|
|
587
|
+
fi
|
|
588
|
+
|
|
589
|
+
# ---- status -----------------------------------------------------------------
|
|
590
|
+
if [[ "$SUBCMD" == "status" ]]; then
|
|
591
|
+
# Parse flags for status.
|
|
592
|
+
while [[ $# -gt 0 ]]; do
|
|
593
|
+
case "$1" in
|
|
594
|
+
--target)
|
|
595
|
+
[[ $# -lt 2 ]] && _aid_die "--target requires a value" 2
|
|
596
|
+
_AID_TARGET="$2"; shift 2 ;;
|
|
597
|
+
--verbose) _AID_VERBOSE=1; shift ;;
|
|
598
|
+
-h|--help) _aid_usage status; exit 0 ;;
|
|
599
|
+
-*) _aid_die "unknown flag for status: $1" 2 ;;
|
|
600
|
+
*) _aid_die "unexpected argument for status: $1" 2 ;;
|
|
601
|
+
esac
|
|
602
|
+
done
|
|
603
|
+
# Apply env-var fallbacks.
|
|
604
|
+
[[ -z "$_AID_TARGET" && -n "${AID_TARGET:-}" ]] && _AID_TARGET="$AID_TARGET"
|
|
605
|
+
_AID_TARGET="${_AID_TARGET:-.}"
|
|
606
|
+
export AID_VERBOSE="$_AID_VERBOSE"
|
|
607
|
+
aid_status "$_AID_TARGET"
|
|
608
|
+
_status_rc=$?
|
|
609
|
+
# Update check notice appended after status output (non-blocking).
|
|
610
|
+
_aid_check_update
|
|
611
|
+
exit $_status_rc
|
|
612
|
+
fi
|
|
613
|
+
|
|
614
|
+
# ---- update -----------------------------------------------------------------
|
|
615
|
+
if [[ "$SUBCMD" == "update" ]]; then
|
|
616
|
+
# Check for 'update self' as first positional arg.
|
|
617
|
+
if [[ $# -gt 0 && "$1" == "self" ]]; then
|
|
618
|
+
shift
|
|
619
|
+
# Consume any flags after 'self'.
|
|
620
|
+
while [[ $# -gt 0 ]]; do
|
|
621
|
+
case "$1" in
|
|
622
|
+
--force|-y) shift ;; # no-op for update self
|
|
623
|
+
-h|--help) _aid_usage update; exit 0 ;;
|
|
624
|
+
*) _aid_die "unknown flag for 'update self': $1" 2 ;;
|
|
625
|
+
esac
|
|
626
|
+
done
|
|
627
|
+
_cmd_update_self
|
|
628
|
+
exit $?
|
|
629
|
+
fi
|
|
630
|
+
# Fall through to the shared add/update handler below.
|
|
631
|
+
fi
|
|
632
|
+
|
|
633
|
+
# ---- remove -----------------------------------------------------------------
|
|
634
|
+
if [[ "$SUBCMD" == "remove" ]]; then
|
|
635
|
+
# Check for 'remove self' as first positional arg.
|
|
636
|
+
if [[ $# -gt 0 && "$1" == "self" ]]; then
|
|
637
|
+
shift
|
|
638
|
+
_cmd_remove_self "$@"
|
|
639
|
+
# _cmd_remove_self always exits.
|
|
640
|
+
fi
|
|
641
|
+
|
|
642
|
+
# Check for 'remove' with no tool args (remove ALL from project).
|
|
643
|
+
# We do this check after parsing flags below, so continue to parse first.
|
|
644
|
+
fi
|
|
645
|
+
|
|
646
|
+
# ---- add / remove / update --------------------------------------------------
|
|
647
|
+
# These subcommands all share flag parsing; we then call the engine functions
|
|
648
|
+
# (install_tool / uninstall_tool) directly through a per-tool loop, exactly as
|
|
649
|
+
# install.sh does. We build a temporary staging area for install/update, and
|
|
650
|
+
# reuse the same prepare_tool_staging + install_tool / uninstall_tool pattern.
|
|
651
|
+
|
|
652
|
+
# First, validate the subcommand.
|
|
653
|
+
case "$SUBCMD" in
|
|
654
|
+
add|remove|update) ;;
|
|
655
|
+
*)
|
|
656
|
+
echo "ERROR: aid: unknown command: ${SUBCMD} (see 'aid -h')" >&2
|
|
657
|
+
exit 2
|
|
658
|
+
;;
|
|
659
|
+
esac
|
|
660
|
+
|
|
661
|
+
|
|
662
|
+
# Collect positional tool args (comma-separated or space-separated before flags).
|
|
663
|
+
_AID_POSITIONAL_TOOLS=""
|
|
664
|
+
_AID_REMOVE_FORCE=0
|
|
665
|
+
|
|
666
|
+
while [[ $# -gt 0 ]]; do
|
|
667
|
+
case "$1" in
|
|
668
|
+
--from-bundle)
|
|
669
|
+
[[ $# -lt 2 ]] && _aid_die "--from-bundle requires a value" 2
|
|
670
|
+
_AID_FROM_BUNDLE="$2"; shift 2 ;;
|
|
671
|
+
--version)
|
|
672
|
+
[[ $# -lt 2 ]] && _aid_die "--version requires a value" 2
|
|
673
|
+
_AID_VERSION_ARG="$2"; shift 2 ;;
|
|
674
|
+
--force|-y) _AID_FORCE=1; _AID_REMOVE_FORCE=1; shift ;;
|
|
675
|
+
--verbose) _AID_VERBOSE=1; shift ;;
|
|
676
|
+
--target)
|
|
677
|
+
[[ $# -lt 2 ]] && _aid_die "--target requires a value" 2
|
|
678
|
+
_AID_TARGET="$2"; shift 2 ;;
|
|
679
|
+
--no-path) _AID_NO_PATH=1; shift ;;
|
|
680
|
+
-h|--help) _aid_usage "$SUBCMD"; exit 0 ;;
|
|
681
|
+
-*) _aid_die "unknown flag: $1" 2 ;;
|
|
682
|
+
*)
|
|
683
|
+
# Positional arg: tool name(s).
|
|
684
|
+
if [[ -z "$_AID_POSITIONAL_TOOLS" ]]; then
|
|
685
|
+
_AID_POSITIONAL_TOOLS="$1"
|
|
686
|
+
else
|
|
687
|
+
# Additional space-separated tools: append as comma-list.
|
|
688
|
+
_AID_POSITIONAL_TOOLS="${_AID_POSITIONAL_TOOLS},$1"
|
|
689
|
+
fi
|
|
690
|
+
shift ;;
|
|
691
|
+
esac
|
|
692
|
+
done
|
|
693
|
+
|
|
694
|
+
# Apply env-var fallbacks.
|
|
695
|
+
[[ -z "$_AID_TOOL_ARG" && -n "$_AID_POSITIONAL_TOOLS" ]] && _AID_TOOL_ARG="$_AID_POSITIONAL_TOOLS"
|
|
696
|
+
[[ -z "$_AID_TOOL_ARG" && -n "${AID_TOOL:-}" ]] && _AID_TOOL_ARG="$AID_TOOL"
|
|
697
|
+
[[ -z "$_AID_VERSION_ARG" && -n "${AID_VERSION:-}" ]] && _AID_VERSION_ARG="$AID_VERSION"
|
|
698
|
+
[[ -z "$_AID_TARGET" && -n "${AID_TARGET:-}" ]] && _AID_TARGET="$AID_TARGET"
|
|
699
|
+
if [[ "$_AID_FORCE" -eq 0 && ( "${AID_FORCE:-0}" == "1" || "${AID_FORCE:-0}" == "true" ) ]]; then
|
|
700
|
+
_AID_FORCE=1
|
|
701
|
+
_AID_REMOVE_FORCE=1
|
|
702
|
+
fi
|
|
703
|
+
export AID_VERBOSE="$_AID_VERBOSE"
|
|
704
|
+
|
|
705
|
+
_AID_TARGET="${_AID_TARGET:-.}"
|
|
706
|
+
# Validate target dir.
|
|
707
|
+
if [[ ! -d "$_AID_TARGET" ]]; then
|
|
708
|
+
_aid_die "target directory does not exist: ${_AID_TARGET}" 2
|
|
709
|
+
fi
|
|
710
|
+
_AID_TARGET="$(cd "$_AID_TARGET" && pwd)"
|
|
711
|
+
|
|
712
|
+
# Strip leading 'v' from version.
|
|
713
|
+
_AID_VERSION_ARG="${_AID_VERSION_ARG#v}"
|
|
714
|
+
|
|
715
|
+
# --from-bundle and --version are mutually exclusive.
|
|
716
|
+
if [[ -n "$_AID_FROM_BUNDLE" && -n "$_AID_VERSION_ARG" ]]; then
|
|
717
|
+
_aid_die "--from-bundle and --version are mutually exclusive" 2
|
|
718
|
+
fi
|
|
719
|
+
|
|
720
|
+
# For 'remove' with no tool arg: confirm, then remove all.
|
|
721
|
+
if [[ "$SUBCMD" == "remove" && -z "$_AID_TOOL_ARG" ]]; then
|
|
722
|
+
# Confirmation required (unless --force or non-interactive).
|
|
723
|
+
if [[ "$_AID_REMOVE_FORCE" -eq 0 ]]; then
|
|
724
|
+
if [[ ! -t 0 ]]; then
|
|
725
|
+
# Non-interactive: auto-proceed (don't hang CI).
|
|
726
|
+
_AID_REMOVE_FORCE=1
|
|
727
|
+
else
|
|
728
|
+
printf 'Remove ALL AID from %s? [y/N] ' "$_AID_TARGET"
|
|
729
|
+
_AID_RM_ANSWER=""
|
|
730
|
+
if [[ -e /dev/tty ]]; then
|
|
731
|
+
read -r _AID_RM_ANSWER < /dev/tty
|
|
732
|
+
else
|
|
733
|
+
read -r _AID_RM_ANSWER
|
|
734
|
+
fi
|
|
735
|
+
if [[ "$_AID_RM_ANSWER" != "y" && "$_AID_RM_ANSWER" != "Y" && "$_AID_RM_ANSWER" != "yes" && "$_AID_RM_ANSWER" != "YES" ]]; then
|
|
736
|
+
echo "Aborted."
|
|
737
|
+
exit 0
|
|
738
|
+
fi
|
|
739
|
+
fi
|
|
740
|
+
fi
|
|
741
|
+
# Proceed: fall through to resolve all tools from manifest.
|
|
742
|
+
fi
|
|
743
|
+
|
|
744
|
+
# Validate constraints per subcommand.
|
|
745
|
+
case "$SUBCMD" in
|
|
746
|
+
add)
|
|
747
|
+
# Tool is required (or must be auto-detectable / env-var set).
|
|
748
|
+
: ;; # handled below in _resolve_tools_for_aid
|
|
749
|
+
remove)
|
|
750
|
+
: ;; # tool optional (empty = all installed, confirmed above)
|
|
751
|
+
update)
|
|
752
|
+
: ;; # tool optional (empty = all installed)
|
|
753
|
+
esac
|
|
754
|
+
|
|
755
|
+
# ---------------------------------------------------------------------------
|
|
756
|
+
# Resolve tool list (reuses the same logic as install.sh _resolve_tools).
|
|
757
|
+
# ---------------------------------------------------------------------------
|
|
758
|
+
_AID_MANIFEST="${_AID_TARGET}/.aid/.aid-manifest.json"
|
|
759
|
+
|
|
760
|
+
_resolve_tools_for_aid() {
|
|
761
|
+
local raw="$1" subcmd="$2" outfile="$3"
|
|
762
|
+
|
|
763
|
+
if [[ -z "$raw" ]]; then
|
|
764
|
+
if [[ "$subcmd" == "update" || "$subcmd" == "remove" ]]; then
|
|
765
|
+
# No tool specified -> all tools in manifest.
|
|
766
|
+
if [[ ! -f "$_AID_MANIFEST" ]]; then
|
|
767
|
+
return 0
|
|
768
|
+
fi
|
|
769
|
+
manifest_list_tools "$_AID_MANIFEST" >> "$outfile"
|
|
770
|
+
return 0
|
|
771
|
+
fi
|
|
772
|
+
# auto-detect for 'add'.
|
|
773
|
+
local detected
|
|
774
|
+
detected="$(detect_tool "$_AID_TARGET")"
|
|
775
|
+
local _rc=$?
|
|
776
|
+
if [[ "$_rc" -ne 0 ]]; then
|
|
777
|
+
return "$_rc"
|
|
778
|
+
fi
|
|
779
|
+
echo "$detected" >> "$outfile"
|
|
780
|
+
return 0
|
|
781
|
+
fi
|
|
782
|
+
|
|
783
|
+
# Split on comma.
|
|
784
|
+
local -a raw_tools=()
|
|
785
|
+
IFS=',' read -ra raw_tools <<< "$raw"
|
|
786
|
+
for t in "${raw_tools[@]}"; do
|
|
787
|
+
t="$(echo "$t" | tr -d '[:space:]')"
|
|
788
|
+
local canonical
|
|
789
|
+
canonical="$(normalize_tool "$t")"
|
|
790
|
+
local _rc=$?
|
|
791
|
+
if [[ "$_rc" -ne 0 ]]; then
|
|
792
|
+
return "$_rc"
|
|
793
|
+
fi
|
|
794
|
+
echo "$canonical" >> "$outfile"
|
|
795
|
+
done
|
|
796
|
+
return 0
|
|
797
|
+
}
|
|
798
|
+
|
|
799
|
+
# Set up staging area.
|
|
800
|
+
_AID_STAGING_BASE="$(mktemp -d /tmp/aid-XXXXXX)"
|
|
801
|
+
trap 'rm -rf "$_AID_STAGING_BASE"' EXIT
|
|
802
|
+
|
|
803
|
+
_TOOLS_TMP="$(mktemp "${_AID_STAGING_BASE}/tools.XXXXXX")"
|
|
804
|
+
_resolve_tools_for_aid "$_AID_TOOL_ARG" "$SUBCMD" "$_TOOLS_TMP"
|
|
805
|
+
_RESOLVE_RC=$?
|
|
806
|
+
if [[ "$_RESOLVE_RC" -ne 0 ]]; then
|
|
807
|
+
rm -rf "$_AID_STAGING_BASE"
|
|
808
|
+
exit "$_RESOLVE_RC"
|
|
809
|
+
fi
|
|
810
|
+
|
|
811
|
+
mapfile -t _AID_TOOLS < "$_TOOLS_TMP"
|
|
812
|
+
|
|
813
|
+
if [[ "${#_AID_TOOLS[@]}" -eq 0 ]]; then
|
|
814
|
+
case "$SUBCMD" in
|
|
815
|
+
remove)
|
|
816
|
+
echo "ERROR: aid: no manifest at ${_AID_TARGET}/.aid/.aid-manifest.json (exit 6)" >&2
|
|
817
|
+
rm -rf "$_AID_STAGING_BASE"
|
|
818
|
+
exit 6
|
|
819
|
+
;;
|
|
820
|
+
update)
|
|
821
|
+
echo "ERROR: aid: no manifest at ${_AID_TARGET}/.aid/.aid-manifest.json; nothing to update (exit 6)" >&2
|
|
822
|
+
rm -rf "$_AID_STAGING_BASE"
|
|
823
|
+
exit 6
|
|
824
|
+
;;
|
|
825
|
+
add)
|
|
826
|
+
echo "ERROR: aid: cannot auto-detect host tool; pass tool name as argument (e.g. aid add codex)" >&2
|
|
827
|
+
rm -rf "$_AID_STAGING_BASE"
|
|
828
|
+
exit 2
|
|
829
|
+
;;
|
|
830
|
+
esac
|
|
831
|
+
fi
|
|
832
|
+
|
|
833
|
+
# ---------------------------------------------------------------------------
|
|
834
|
+
# Prepare staging for install/update (mirrors install.sh prepare_tool_staging).
|
|
835
|
+
# ---------------------------------------------------------------------------
|
|
836
|
+
_AID_RESOLVED_VERSION=""
|
|
837
|
+
_AID_STAGING_DIR=""
|
|
838
|
+
|
|
839
|
+
_prepare_tool_staging_aid() {
|
|
840
|
+
local tool="$1" version="$2" from_bundle="$3"
|
|
841
|
+
|
|
842
|
+
local tool_staging
|
|
843
|
+
tool_staging="$(mktemp -d "${_AID_STAGING_BASE}/staging-${tool}-XXXXXX")"
|
|
844
|
+
|
|
845
|
+
if [[ -n "$from_bundle" ]]; then
|
|
846
|
+
local tarball="$from_bundle"
|
|
847
|
+
if [[ -d "$from_bundle" ]]; then
|
|
848
|
+
tarball="$(ls "${from_bundle}"/aid-${tool}-v*.tar.gz 2>/dev/null | head -1)"
|
|
849
|
+
if [[ -z "$tarball" ]]; then
|
|
850
|
+
echo "ERROR: aid: no tarball found for tool '${tool}' in bundle directory: ${from_bundle}" >&2
|
|
851
|
+
exit 1
|
|
852
|
+
fi
|
|
853
|
+
fi
|
|
854
|
+
if [[ ! -f "$tarball" ]]; then
|
|
855
|
+
echo "ERROR: aid: bundle file not found: ${tarball}" >&2
|
|
856
|
+
exit 1
|
|
857
|
+
fi
|
|
858
|
+
verify_bundle_checksum "$tarball" || exit $?
|
|
859
|
+
local tbase
|
|
860
|
+
tbase="$(basename "$tarball")"
|
|
861
|
+
_AID_RESOLVED_VERSION="$(echo "$tbase" | sed "s/aid-${tool}-v//" | sed 's/\.tar\.gz$//')"
|
|
862
|
+
[[ -z "$_AID_RESOLVED_VERSION" ]] && _AID_RESOLVED_VERSION="${version:-unknown}"
|
|
863
|
+
extract_tarball "$tarball" "$tool_staging" || exit $?
|
|
864
|
+
else
|
|
865
|
+
if [[ -z "$version" ]]; then
|
|
866
|
+
_AID_RESOLVED_VERSION="$(resolve_version)" || exit $?
|
|
867
|
+
else
|
|
868
|
+
_AID_RESOLVED_VERSION="$version"
|
|
869
|
+
fi
|
|
870
|
+
local dl_dir
|
|
871
|
+
dl_dir="$(mktemp -d "${_AID_STAGING_BASE}/download-${tool}-XXXXXX")"
|
|
872
|
+
fetch_tarball "$tool" "$_AID_RESOLVED_VERSION" "$dl_dir" || exit $?
|
|
873
|
+
local tarball="${dl_dir}/aid-${tool}-v${_AID_RESOLVED_VERSION}.tar.gz"
|
|
874
|
+
extract_tarball "$tarball" "$tool_staging" || exit $?
|
|
875
|
+
fi
|
|
876
|
+
|
|
877
|
+
_AID_STAGING_DIR="$tool_staging"
|
|
878
|
+
}
|
|
879
|
+
|
|
880
|
+
# ---------------------------------------------------------------------------
|
|
881
|
+
# Dispatch to engine.
|
|
882
|
+
# ---------------------------------------------------------------------------
|
|
883
|
+
_AID_OVERALL_BLOCKED=0
|
|
884
|
+
|
|
885
|
+
case "$SUBCMD" in
|
|
886
|
+
add|update)
|
|
887
|
+
for _tool in "${_AID_TOOLS[@]}"; do
|
|
888
|
+
echo ""
|
|
889
|
+
_prepare_tool_staging_aid "$_tool" "$_AID_VERSION_ARG" "$_AID_FROM_BUNDLE"
|
|
890
|
+
echo "Installing ${_tool} v${_AID_RESOLVED_VERSION} -> ${_AID_TARGET}"
|
|
891
|
+
install_tool "$_AID_STAGING_DIR" "$_tool" "$_AID_TARGET" "$_AID_RESOLVED_VERSION" "$_AID_FORCE" || {
|
|
892
|
+
_RC=$?
|
|
893
|
+
if [[ "$_RC" -eq 5 ]]; then
|
|
894
|
+
_AID_OVERALL_BLOCKED=1
|
|
895
|
+
else
|
|
896
|
+
exit "$_RC"
|
|
897
|
+
fi
|
|
898
|
+
}
|
|
899
|
+
done
|
|
900
|
+
|
|
901
|
+
echo ""
|
|
902
|
+
if [[ "$_AID_OVERALL_BLOCKED" -eq 1 ]]; then
|
|
903
|
+
echo "Install complete with warnings: one or more root agent files were not overwritten."
|
|
904
|
+
echo "Review the *.aid-new file(s) and merge, or re-run with --force to overwrite."
|
|
905
|
+
exit 5
|
|
906
|
+
fi
|
|
907
|
+
echo "Done. AID ${_AID_RESOLVED_VERSION:-} installed into: ${_AID_TARGET}"
|
|
908
|
+
exit 0
|
|
909
|
+
;;
|
|
910
|
+
|
|
911
|
+
remove)
|
|
912
|
+
manifest_exists "$_AID_MANIFEST" || {
|
|
913
|
+
echo "ERROR: aid: no manifest at ${_AID_TARGET}/.aid/.aid-manifest.json; nothing to uninstall" >&2
|
|
914
|
+
exit 6
|
|
915
|
+
}
|
|
916
|
+
|
|
917
|
+
for _tool in "${_AID_TOOLS[@]}"; do
|
|
918
|
+
echo ""
|
|
919
|
+
echo "Uninstalling ${_tool} from ${_AID_TARGET}"
|
|
920
|
+
uninstall_tool "$_AID_MANIFEST" "$_tool" "$_AID_TARGET" || {
|
|
921
|
+
_RC=$?
|
|
922
|
+
[[ "$_RC" -eq 6 ]] && exit 6
|
|
923
|
+
exit "$_RC"
|
|
924
|
+
}
|
|
925
|
+
done
|
|
926
|
+
|
|
927
|
+
echo ""
|
|
928
|
+
echo "Uninstall complete."
|
|
929
|
+
exit 0
|
|
930
|
+
;;
|
|
931
|
+
esac
|