Repository navigation
Expand file tree
/
Copy pathinstall.sh
More file actions
executable file
·493 lines (454 loc) · 21.7 KB
/
Copy pathinstall.sh
File metadata and controls
executable file
·493 lines (454 loc) · 21.7 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
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
#!/usr/bin/env bash
#
# A wizard walks a human through a manual procedure, step by step.
# Generated by the /wizard skill.
#
# Everything above the "STAGES" marker is the wizard library: do not hand-edit
# it. Author the per-step stages below the marker.
set -euo pipefail
# ──────────────────────────────────────────────────────────────────────────
# Wizard library: delightful, consistent UX, identical across every wizard.
# ──────────────────────────────────────────────────────────────────────────
if [[ -t 1 ]] && command -v tput >/dev/null 2>&1 && [[ "$(tput colors 2>/dev/null || echo 0)" -ge 8 ]]; then
BOLD=$(tput bold); DIM=$(tput dim); RESET=$(tput sgr0)
BLUE=$(tput setaf 4); GREEN=$(tput setaf 2); YELLOW=$(tput setaf 3)
else
BOLD=""; DIM=""; RESET=""; BLUE=""; GREEN=""; YELLOW=""
fi
# Author sets this at the top of the stages section.
TOTAL_STAGES=0
_STAGE_INDEX=0
ENV_FILE="${ENV_FILE:-.env}"
WRITTEN_ENV=() # KEYs written to ENV_FILE this run
WRITTEN_SECRET=() # secret NAMEs set this run
SKIPPED=() # things we couldn't do (e.g. gh missing)
# _clear wipes the terminal so only the current step is on screen. No-op when
# output isn't a terminal, so piped logs stay readable.
_clear() {
[[ -t 1 ]] || return 0
if command -v tput >/dev/null 2>&1 && tput clear 2>/dev/null; then return 0; fi
printf '\033[2J\033[3J\033[H'
}
# banner "Title" shows the opening frame: what this wizard does.
banner() {
_clear
printf '\n%s%s %s%s\n' "$BOLD" "$BLUE" "$1" "$RESET"
printf '%s %s stages%s\n\n' "$DIM" "$TOTAL_STAGES" "$RESET"
printf '%s You drive the browser; this wizard tells you exactly what to do and\n' "$DIM"
printf ' captures the values you copy back. Stop any time with Ctrl-C and re-run\n'
printf ' later, since it remembers values already saved.%s\n' "$RESET"
pause "Ready to start?"
}
# stage "Name" clears the screen, then announces a stage and shows progress.
# Clearing keeps only the current step on screen.
stage() {
_clear
_STAGE_INDEX=$((_STAGE_INDEX + 1))
printf '\n%s%s▸ Stage %s/%s · %s%s\n' \
"$BOLD" "$BLUE" "$_STAGE_INDEX" "$TOTAL_STAGES" "$1" "$RESET"
}
# say "..." prints a plain instruction line.
say() { printf ' %s\n' "$1"; }
# step "..." is a numbered-feeling action the human takes in the browser.
step() { printf ' %s•%s %s\n' "$BLUE" "$RESET" "$1"; }
note() { printf ' %s%s%s\n' "$DIM" "$1" "$RESET"; }
warn() { printf ' %s⚠ %s%s\n' "$YELLOW" "$1" "$RESET"; }
# open_url URL opens it in the human's browser, cross-platform incl. WSL.
open_url() {
local url="$1"
printf ' %s↗ opening%s %s\n' "$GREEN" "$RESET" "$url"
{ if command -v wslview >/dev/null 2>&1; then wslview "$url"
elif command -v explorer.exe >/dev/null 2>&1; then explorer.exe "$url"
elif command -v xdg-open >/dev/null 2>&1; then xdg-open "$url"
elif command -v open >/dev/null 2>&1; then open "$url"
else false; fi
} >/dev/null 2>&1 || warn "couldn't open a browser, so visit it manually: $url"
}
# pause "msg" waits for the human to confirm they've done the manual part.
pause() {
printf ' %s%s%s ' "$DIM" "${1:-Press Enter to continue}" "$RESET"
read -r _ || true
}
# confirm "question" is a y/N gate; returns success on yes.
confirm() {
local reply=""
printf ' %s? %s [y/N] ' "$YELLOW" "$1"
read -r reply || true
[[ "$reply" =~ ^[Yy] ]]
}
# _existing KEY: current value of KEY in ENV_FILE, if any, with write_env's single
# quotes or a plain dotenv double-quoted value undone.
_existing() {
[[ -f "$ENV_FILE" ]] || return 1
local line value sq="'\\''"; line=$(grep -E "^${1}=" "$ENV_FILE" | tail -n1) || return 1
value="${line#*=}"
if [[ "$value" == \'*\' ]]; then value="${value:1:${#value}-2}"; value="${value//"$sq"/\'}"
elif [[ "$value" == \"*\" ]]; then
value="${value:1:${#value}-2}"
[[ "$value" == *[\\\$\"]* ]] && return 1 # escapes, $ or ": can't decode safely; ask again
fi
printf '%s' "$value"
}
# ask KEY "Prompt" reads a value into $KEY. Offers the existing .env value as
# a default on re-runs (Enter keeps it). Visible input (non-secret), edited
# with Readline. Fails at EOF with no input.
ask() {
local key="$1" prompt="$2" current input s=$'\001' e=$'\002' p rc=0
current=$(_existing "$key" || true)
# Readline needs the colour codes wrapped in \001..\002 to place the cursor.
p=" $s$BOLD$e$prompt$s$RESET$e "
[[ -n "$current" ]] && p+="$s$DIM$e[Enter keeps current]$s$RESET$e "
read -e -r -p "$p" input || rc=1
if (( rc )) && [[ -z "$input" ]]; then return 1; fi
[[ -z "$input" && -n "$current" ]] && input="$current"
printf -v "$key" '%s' "$input"
}
# ask_secret KEY "Prompt" is like ask, but input is hidden.
ask_secret() {
local key="$1" prompt="$2" current input rc=0
current=$(_existing "$key" || true)
if [[ -n "$current" ]]; then
printf ' %s%s%s %s[Enter keeps current]%s ' "$BOLD" "$prompt" "$RESET" "$DIM" "$RESET"
else
printf ' %s%s%s ' "$BOLD" "$prompt" "$RESET"
fi
read -rs input || rc=1
printf '\n'
if (( rc )) && [[ -z "$input" ]]; then return 1; fi
[[ -z "$input" && -n "$current" ]] && input="$current"
printf -v "$key" '%s' "$input"
}
# write_env KEY VALUE upserts KEY='VALUE' into ENV_FILE (replaces any existing
# line) and sets $KEY. Idempotent. Creates ENV_FILE at mode 0600; writes through
# a symlink and keeps an existing file's mode.
write_env() {
local key="$1" value="$2" tmp sq="'\\''"
[[ -e "$ENV_FILE" ]] || (umask 077 && : > "$ENV_FILE")
tmp=$(mktemp)
grep -vE "^${key}=" "$ENV_FILE" > "$tmp" || true
printf "%s='%s'\n" "$key" "${value//\'/$sq}" >> "$tmp"
cat "$tmp" > "$ENV_FILE"
rm -f "$tmp"
printf -v "$key" '%s' "$value"
WRITTEN_ENV+=("$key")
printf ' %s✓ wrote%s %s → %s\n' "$GREEN" "$RESET" "$key" "$ENV_FILE"
}
# set_secret NAME VALUE sets a GitHub Actions repo secret via gh. Falls back
# to a warning (and records it) if gh is unavailable or unauthenticated.
set_secret() {
local name="$1" value="$2"
if command -v gh >/dev/null 2>&1 && gh auth status >/dev/null 2>&1; then
if printf '%s' "$value" | gh secret set "$name" >/dev/null 2>&1; then
WRITTEN_SECRET+=("$name")
printf ' %s✓ set%s GitHub secret %s\n' "$GREEN" "$RESET" "$name"
return
fi
fi
SKIPPED+=("GitHub secret $name (set it manually: gh secret set $name)")
warn "skipped GitHub secret $name: gh not ready; set it later"
}
# set_var NAME VALUE sets a GitHub Actions repo variable (non-secret).
set_var() {
local name="$1" value="$2"
if command -v gh >/dev/null 2>&1 && gh auth status >/dev/null 2>&1; then
if gh variable set "$name" --body "$value" >/dev/null 2>&1; then
printf ' %s✓ set%s GitHub variable %s\n' "$GREEN" "$RESET" "$name"
return
fi
fi
SKIPPED+=("GitHub variable $name")
warn "skipped GitHub variable $name, gh not ready; set it later"
}
# finish clears, then shows a closing summary of everything configured.
finish() {
_clear
printf '\n%s%s ✓ Setup complete%s\n' "$BOLD" "$GREEN" "$RESET"
(( ${#WRITTEN_ENV[@]} )) && note "wrote ${#WRITTEN_ENV[@]} value(s) to $ENV_FILE: ${WRITTEN_ENV[*]}"
(( ${#WRITTEN_SECRET[@]} )) && note "set ${#WRITTEN_SECRET[@]} GitHub secret(s): ${WRITTEN_SECRET[*]}"
if (( ${#SKIPPED[@]} )); then
printf '\n'; warn "still to do by hand:"
for s in "${SKIPPED[@]}"; do note " - $s"; done
fi
printf '\n'
}
# ──────────────────────────────────────────────────────────────────────────
# STAGES: author this section. One stage() per step the human takes.
# Replace the example below. Set TOTAL_STAGES to match the stages you write.
# Keep every stage inside run_wizard and the last line as is: bash then parses
# the whole file before the first prompt, so an edit mid-run can't break it.
# ──────────────────────────────────────────────────────────────────────────
fail() { printf 'error: %s\n' "$1" >&2; exit 1; }
prompt() {
local key="$1" label="$2" default="${3:-}" optional="${4:-false}"
while true; do
ask "$key" "$label${default:+ [$default]}:" || fail "Input ended; nothing further will be changed."
[[ -n "${!key}" ]] || printf -v "$key" '%s' "$default"
if [[ "${!key}" =~ [[:cntrl:]] ]]; then
warn "Control characters are not allowed."
elif [[ -n "${!key}" || "$optional" = true ]]; then
return
else
warn "Enter a value; this setting has no default."
fi
done
}
choice() {
local key="$1" label="$2" default="$3" allowed="$4"
while true; do
prompt "$key" "$label" "$default"
case " $allowed " in *" ${!key} "*) return ;; esac
warn "Choose one of: $allowed."
done
}
absolute_path() {
local key="$1"
while true; do
prompt "$key" "$2" "${3:-}" "${4:-false}"
case "${!key}" in
'~') printf -v "$key" '%s' "$HOME" ;;
'~/'*) printf -v "$key" '%s' "$HOME/${!key:2}" ;;
esac
[[ -z "${!key}" || "${!key}" = /* ]] && return
warn "Use an absolute path, such as $HOME/example."
done
}
valid_api_url() {
local https='^https://([[:alnum:].-]+|\[[[:xdigit:]:]+\])(:[0-9]+)?(/[^[:space:]?#]*)?$'
local loopback='^http://(localhost|127\.0\.0\.1|\[::1\])(:[0-9]+)?(/[^[:space:]?#]*)?$'
[[ "$1" =~ $https || "$1" =~ $loopback ]]
}
default_config_path() {
if [[ -n "${AAR_CONFIG:-}" ]]; then
printf '%s' "$AAR_CONFIG"
elif [[ -n "${XDG_CONFIG_HOME:-}" ]]; then
printf '%s/aar/config.toml' "$XDG_CONFIG_HOME"
elif [[ "$os" = darwin ]]; then
printf '%s/Library/Application Support/aar/config.toml' "$HOME"
else
printf '%s/.config/aar/config.toml' "$HOME"
fi
}
toml_quote() {
local value="$1"
value="${value//\\/\\\\}"
# shortcut: use Unicode quote escapes until AAR's TOML scanner handles escaped quotes.
value="${value//\"/\\u0022}"
value="${value//$'\n'/\\n}"
value="${value//$'\r'/\\r}"
value="${value//$'\t'/\\t}"
printf '"%s"' "$value"
}
write_config() {
local i separator=""
if (( connection_count )); then
printf '[assessor]\ntotalTimeoutMs = %s\nonUnavailable = "%s"\n' "$timeout_ms" "$unavailable"
for ((i=0; i<connection_count; i++)); do
(( i == 0 )) || printf '\n[[assessor.fallbacks]]\n'
printf 'authMode = "%s"\nmodel = ' "${connection_modes[i]}"
toml_quote "${models[i]}"; printf '\n'
if [[ "${connection_modes[i]}" = api-key ]]; then
printf 'baseUrl = '; toml_quote "${urls[i]}"; printf '\n'
printf 'apiKeyEnv = '; toml_quote "${key_envs[i]}"; printf '\n'
fi
done
printf '\n'
fi
printf '[policy]\nprofile = "%s"\nprotectedPaths = [' "$profile"
for ((i=0; i<${#protected_paths[@]}; i++)); do
printf '%s' "$separator"; toml_quote "${protected_paths[i]}"; separator=", "
done
printf ']\n\n[grants]\nsessionTrust = %s\n\n[audit]\nenabled = %s\n' "$session_trust" "$history"
}
hash_file() {
if command -v sha256sum >/dev/null 2>&1; then
sha256sum "$1" | cut -d' ' -f 1
else
shasum -a 256 "$1" | cut -d' ' -f 1
fi
}
download_aar() {
local repo="Glaicer/supercode-command-gate" tag version asset base_url expected
tag=$(curl -fsSL "https://api.github.com/repos/$repo/releases/latest" |
sed -n 's/.*"tag_name": *"\([^"]*\)".*/\1/p' | head -n 1)
case "$tag" in aar-v*) version="${tag#aar-v}" ;; v*) version="${tag#v}" ;; *) version="$tag" ;; esac
[[ "$version" =~ ^[0-9]+\.[0-9]+\.[0-9]+([+-][[:alnum:].-]+)?$ ]] || fail "Invalid or missing release version."
asset="aar-v${version}-${os}-${arch}"
base_url="https://github.com/$repo/releases/download/$tag"
say "Downloading $asset; SHA-256 verification is required before installation."
curl -fsSL -o "$work_dir/aar" "$base_url/$asset"
curl -fsSL -o "$work_dir/SHA256SUMS" "$base_url/SHA256SUMS"
expected=$(awk -v file="$asset" '$2 == file { print $1 }' "$work_dir/SHA256SUMS")
[[ "$expected" =~ ^[[:xdigit:]]{64}$ ]] || fail "Missing, duplicate or invalid checksum for $asset."
[[ "$(hash_file "$work_dir/aar")" = "$expected" ]] || fail "Checksum mismatch; existing files were not changed."
}
install_files() {
local temporary backup
if [[ "$keep_config" = false ]]; then
[[ ! -L "$config_path" ]] || fail "Refusing to replace a config symlink."
if [[ -e "$config_path" ]]; then
[[ -f "$config_path" && "$(hash_file "$config_path")" = "$config_before" ]] || fail "Config changed during setup; run the installer again."
else
[[ -z "$config_before" ]] || fail "Config disappeared during setup; run the installer again."
fi
fi
mkdir -p "$bin_dir"
temporary=$(mktemp "$bin_dir/.aar.XXXXXX")
cp "$work_dir/aar" "$temporary"
chmod 0755 "$temporary"
mv -f "$temporary" "$bin_dir/aar"
if [[ "$keep_config" = false ]]; then
mkdir -p "$(dirname "$config_path")"
if [[ -n "$config_before" ]]; then
backup=$(mktemp "${config_path}.bak.XXXXXX")
cp "$config_path" "$backup"
chmod 0600 "$backup"
say "Previous config backed up to: $backup"
fi
temporary=$(mktemp "${config_path}.tmp.XXXXXX")
cp "$work_dir/config.toml" "$temporary"
chmod 0600 "$temporary"
mv -f "$temporary" "$config_path"
fi
}
run_wizard() {
if [[ ! -t 0 ]]; then
{ exec 0</dev/tty; } 2>/dev/null || fail "A terminal is required. Download install.sh and run it with bash in an interactive terminal."
fi
ENV_FILE=/dev/null
TOTAL_STAGES=6
umask 077
case "$(uname -s)" in Linux) os=linux ;; Darwin) os=darwin ;; *) fail "Use install.ps1 for native Windows; this installer supports Linux/macOS." ;; esac
case "$(uname -m)" in x86_64) arch=amd64 ;; aarch64|arm64) arch=arm64 ;; *) fail "Unsupported architecture." ;; esac
command -v curl >/dev/null 2>&1 || fail "Install curl before running this wizard."
command -v sha256sum >/dev/null 2>&1 || command -v shasum >/dev/null 2>&1 || fail "Install sha256sum or shasum first."
work_dir=$(mktemp -d)
trap 'rm -rf "$work_dir"' EXIT
trap 'exit 130' INT
trap 'exit 143' TERM
connection_count=0; connection_modes=(); models=(); urls=(); key_envs=(); protected_paths=()
keep_config=false; config_before=""; timeout_ms=5000; unavailable=deny
say "AAR setup: install the command reviewer and create its TOML config."
say "This does not install or configure the OpenCode plugin. No API keys will be requested or saved."
stage "Installation paths"
say "The binary directory holds the aar executable; it should be on your PATH. No sudo is needed."
absolute_path bin_dir "Binary directory" "${AAR_BIN_DIR:-$HOME/.local/bin}"
config_path=$(default_config_path)
say "AAR reads AAR_CONFIG first, then XDG_CONFIG_HOME, then the OS default. Project .aar.toml files can override it."
absolute_path config_path "Config file" "$config_path"
[[ "$config_path" != "$bin_dir/aar" ]] || fail "The config file and executable must use different paths."
[[ ! -L "$config_path" && ( ! -e "$config_path" || -f "$config_path" ) ]] || fail "Config must be a regular file, not a directory or symlink."
if [[ -f "$config_path" ]]; then
config_before=$(hash_file "$config_path")
say "A config already exists. Keeping it preserves all custom/advanced settings."
choice existing "1 = keep existing config, 2 = replace with a new config and backup" 1 "1 2"
[[ "$existing" = 2 ]] || keep_config=true
fi
stage "Model connection"
say "The Assessor is an LLM that reviews commands local rules cannot decide. API calls may incur provider charges."
if [[ "$keep_config" = false ]]; then
say "1 = OpenAI-compatible API (your endpoint and key); 2 = ChatGPT subscription via OpenCode; 3 = no LLM."
say "No LLM means unclear commands are denied. OpenCode mode reads an existing ChatGPT login, never an API key."
choice mode "Assessment mode" 1 "1 2 3"
if [[ "$mode" != 3 ]]; then
while true; do
if [[ "$mode" = 2 ]]; then
connection_modes[connection_count]=opencode; urls[connection_count]=""; key_envs[connection_count]=""
say "Sign in separately with: opencode auth login openai --method chatgpt-browser"
say "Use the exact model ID supported by that subscription. This wizard does not start login or change credentials."
else
connection_modes[connection_count]=api-key
say "Find the OpenAI-compatible base URL and exact model ID in your provider's API documentation."
say "The URL normally ends in /v1. HTTPS is required, except HTTP on localhost, 127.0.0.1 or [::1]."
while true; do
prompt endpoint "API base URL"
valid_api_url "$endpoint" && break
warn "Use an HTTPS URL (or local HTTP), without credentials, ?query or #fragment."
done
urls[connection_count]="$endpoint"
say "Enter only an environment-variable NAME, never the key itself. Export the key before starting OpenCode."
while true; do
prompt env_name "API key environment-variable name" ASSESSOR_API_KEY
[[ "$env_name" =~ ^[a-zA-Z_][a-zA-Z0-9_]*$ ]] && break
warn "Use letters, digits and underscores, starting with a letter or underscore."
done
key_envs[connection_count]="$env_name"
fi
prompt model "Model ID"
models[connection_count]="$model"
connection_count=$((connection_count + 1))
say "Fallbacks are tried in order only if the previous connection is unavailable, not to overturn a verdict."
choice another "Add a fallback connection? (y/n)" n "y n"
[[ "$another" = y ]] || break
choice mode "Fallback: 1 = API, 2 = ChatGPT via OpenCode" 1 "1 2"
done
fi
else
say "Keeping the existing model configuration."
fi
stage "Protection policy"
say "data-loss: focus on substantial irreversible loss of valuable data (recommended default)."
say "balanced: also weigh credentials, privilege and external/network effects. strict: weigh every risk category."
say "These are review policies, not a sandbox. Hard-deny rules still apply in every profile."
if [[ "$keep_config" = false ]]; then
choice profile "Policy profile" data-loss "data-loss balanced strict"
say "Protected paths identify valuable data for stricter review. They are not filesystem access controls."
say "Enter one absolute file/directory per question; blank finishes the list."
while true; do
absolute_path protected "Protected path (blank to finish)" "" true
[[ -n "$protected" ]] || break
protected_paths+=("$protected")
done
else
say "Keeping the existing policy and protected paths."
fi
stage "Timeout, failures and history"
if [[ "$keep_config" = false ]]; then
if (( connection_count )); then
say "The timeout is the total LLM budget, including fallbacks, in milliseconds (1000 ms = 1 second)."
say "The plugin timeout must be longer; this installer does not change the plugin's settings."
while true; do
prompt timeout_ms "Total assessment timeout (1-600000 ms)" 5000
[[ "$timeout_ms" =~ ^[1-9][0-9]{0,5}$ ]] && (( timeout_ms <= 600000 )) && break
warn "Enter an integer from 1 to 600000."
done
say "If every connection fails: deny blocks the command; ask requests human review."
say "ask only shows a dialog when the plugin supports interactive approvals; never-ask mode still refuses it."
choice unavailable "When the Assessor is unavailable" deny "deny ask"
fi
say "History stores local review evidence and command text for diagnostics; it may contain sensitive project data."
choice history_answer "Save review history? (y/n)" y "y n"
history=false; [[ "$history_answer" = n ]] || history=true
say "Session trust permits eligible exact commands to reuse a human's 'Trust for this session' approval."
say "It never approves a command on its own; disabling it still allows one-time approvals."
choice trust_answer "Enable session trust? (y/n)" y "y n"
session_trust=false; [[ "$trust_answer" = n ]] || session_trust=true
write_config > "$work_dir/config.toml"
else
say "Keeping the existing behavior settings."
fi
stage "Review before making changes"
say "Binary: $bin_dir/aar (an existing binary will be replaced after checksum verification)"
say "Config: $config_path"
if [[ "$keep_config" = true ]]; then
say "The existing config will not be changed."
else
[[ -z "$config_before" ]] || warn "Replacement creates a .bak.* copy; custom settings are not merged."
cat "$work_dir/config.toml"
fi
confirm "Download the latest AAR release and apply this setup?" || { say "Cancelled; existing files were not changed."; return; }
stage "Install and verify"
download_aar
install_files
finish
say "Binary: $bin_dir/aar"
say "Config: $config_path"
case ":$PATH:" in *":$bin_dir:"*) ;; *) warn "Add $bin_dir to PATH before running aar." ;; esac
say "For a custom config location, export AAR_CONFIG pointing to the file above before starting OpenCode."
say "API mode needs the named key variables in that same environment; no key values were saved by this installer."
say "doctor checks config and credentials, not model access. In OpenCode mode it may start/contact the local service."
if confirm "Run aar doctor now (no LLM/provider request)?"; then
(cd "$work_dir" && AAR_CONFIG="$config_path" AAR_WORKSPACE_ROOT="$work_dir" "$bin_dir/aar" doctor) || warn "Installed, but doctor did not pass. Fix credentials/config and run aar doctor again."
fi
say "The OpenCode plugin is separate and was not installed or changed."
}
run_wizard "$@"; exit