@darkrei08/setup-ai 3.0.5 → 3.3.0
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/README.md +35 -28
- package/bin/setup-ai.mjs +48 -23
- package/package.json +4 -3
- package/setup-ai.ps1 +618 -127
- package/setup-ai.sh +592 -207
package/setup-ai.sh
CHANGED
|
@@ -2,12 +2,12 @@
|
|
|
2
2
|
|
|
3
3
|
# ==============================================================================
|
|
4
4
|
# AI Dev Suite — Engineering Excellence Edition
|
|
5
|
-
# Version: 3.0
|
|
5
|
+
# Version: 3.3.0
|
|
6
6
|
#
|
|
7
7
|
# Cross-platform (macOS + all major Linux distros) installer for an AI coding
|
|
8
8
|
# toolchain. Windows is handled by the sibling setup-ai.ps1; the Node launcher
|
|
9
9
|
# bin/setup-ai.mjs dispatches to the right script per OS and offers an
|
|
10
|
-
# interactive
|
|
10
|
+
# interactive arrow-key module menu.
|
|
11
11
|
#
|
|
12
12
|
# Design:
|
|
13
13
|
# - modular: every tool is its own mod_* function, driven by an ordered
|
|
@@ -26,7 +26,7 @@
|
|
|
26
26
|
set -Eeuo pipefail
|
|
27
27
|
IFS=$'\n\t'
|
|
28
28
|
|
|
29
|
-
SCRIPT_VERSION="3.0
|
|
29
|
+
SCRIPT_VERSION="3.3.0"
|
|
30
30
|
|
|
31
31
|
SCRIPT_DIR="$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")" && pwd)"
|
|
32
32
|
LOG_DIR="${SCRIPT_DIR}/logs"
|
|
@@ -47,19 +47,33 @@ PI_WORKFLOW_VERSION="${PI_WORKFLOW_VERSION:-}"
|
|
|
47
47
|
|
|
48
48
|
DOTENV_REPO="${DOTENV_REPO:-https://github.com/vekexasia/dotenv.git}"
|
|
49
49
|
|
|
50
|
-
|
|
50
|
+
# Gentle AI ecosystem configurator installer (macOS/Linux). `gentle-ai install`
|
|
51
|
+
# is the interactive per-agent/per-IDE selector that also wires each agent's MCP
|
|
52
|
+
# servers, so the tools show up under /mcp.
|
|
53
|
+
GENTLE_AI_INSTALL="${GENTLE_AI_INSTALL:-https://raw.githubusercontent.com/Gentleman-Programming/gentle-ai/main/scripts/install.sh}"
|
|
54
|
+
|
|
55
|
+
ENGINEERING_EXCELLENCE_SLUG="${ENGINEERING_EXCELLENCE_SLUG:-darkrei08/Engineering-Excellence}"
|
|
51
56
|
ENGINEERING_EXCELLENCE_SKILL="engineering-excellence"
|
|
52
57
|
|
|
58
|
+
# Upstream agent-skill stack mirrored from vekexasia/dotenv setup_env.sh so the
|
|
59
|
+
# same skills land on every OS (dotenv itself is Linux-only). Each entry is
|
|
60
|
+
# "<source> <skill> [<skill>...]" installed via `npx skills add`.
|
|
61
|
+
UPSTREAM_SKILL_SOURCES=(
|
|
62
|
+
"herdrdev/herdr herdr"
|
|
63
|
+
"mattpocock/skills triage grill-me grilling wayfinder domain-modeling prototype research"
|
|
64
|
+
"https://github.com/pedronauck/skills typescript-advanced"
|
|
65
|
+
"humanlayer/skills show-me"
|
|
66
|
+
)
|
|
67
|
+
UPSTREAM_SKILL_NAMES=(herdr triage grill-me grilling wayfinder domain-modeling prototype research typescript-advanced show-me)
|
|
68
|
+
|
|
53
69
|
PI_AGENT_DIR="${HOME}/.pi/agent"
|
|
54
70
|
PI_EXTENSIONS_DIR="${PI_AGENT_DIR}/extensions"
|
|
55
71
|
PI_NPM_DIR="${PI_AGENT_DIR}/npm"
|
|
56
|
-
ENGINEERING_EXCELLENCE_DIR="${PI_AGENT_DIR}/skills/${ENGINEERING_EXCELLENCE_SKILL}"
|
|
57
72
|
|
|
58
73
|
DOTENV_DIR="${HOME}/git/personale/dotenv"
|
|
59
74
|
DOTENV_EXT_DIR="${DOTENV_DIR}/pi/agent/extensions/pi-ext-workflows"
|
|
60
75
|
|
|
61
76
|
COCKPIT_REPO="jlcodes99/cockpit-tools"
|
|
62
|
-
GENTLE_AI_INSTALL="https://raw.githubusercontent.com/Gentleman-Programming/gentle-ai/main/scripts/install.sh"
|
|
63
77
|
|
|
64
78
|
# ------------------------------------------------------------------------------
|
|
65
79
|
# Cleanup
|
|
@@ -221,6 +235,38 @@ capture_cmd() {
|
|
|
221
235
|
log_event "INFO" "${phase}" "capture_success" "Output captured" 0 "${display}"
|
|
222
236
|
}
|
|
223
237
|
|
|
238
|
+
# Run a `grep` probe that distinguishes "no match" from a real failure.
|
|
239
|
+
# Writes matching lines to ${out_file}. Returns 0 when matches were found,
|
|
240
|
+
# 1 when there were none (grep exit 1), and hard-fails on any operational
|
|
241
|
+
# error (grep exit >1, e.g. an unreadable file) instead of hiding it.
|
|
242
|
+
grep_probe() {
|
|
243
|
+
local phase="$1" out_file="$2"; shift 2
|
|
244
|
+
local rc=0
|
|
245
|
+
grep "$@" >"${out_file}" 2>>"${HUMAN_LOG}" || rc=$?
|
|
246
|
+
case "${rc}" in
|
|
247
|
+
0) return 0 ;;
|
|
248
|
+
1) return 1 ;;
|
|
249
|
+
*)
|
|
250
|
+
log_event "ERROR" "${phase}" "grep_probe_failed" \
|
|
251
|
+
"grep probe failed with an operational error" "${rc}" "args=$*"
|
|
252
|
+
exit "${rc}"
|
|
253
|
+
;;
|
|
254
|
+
esac
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
report_version() {
|
|
258
|
+
local output_var="$1" command_name="$2"; shift 2
|
|
259
|
+
if ! command -v "${command_name}" >/dev/null 2>&1; then
|
|
260
|
+
printf -v "${output_var}" '%s' "n/a"
|
|
261
|
+
return 0
|
|
262
|
+
fi
|
|
263
|
+
if ! capture_cmd "${output_var}" "report" "${command_name}" "$@"; then
|
|
264
|
+
printf -v "${output_var}" '%s' "n/a"
|
|
265
|
+
log_event "WARN" "report" "version_unavailable" \
|
|
266
|
+
"Could not read command version" 0 "command=${command_name}"
|
|
267
|
+
fi
|
|
268
|
+
}
|
|
269
|
+
|
|
224
270
|
write_report() {
|
|
225
271
|
local status="$1" rc="$2" line="${3:-n/a}" file="${4:-n/a}"
|
|
226
272
|
local function_name="${5:-n/a}" command="${6:-n/a}"
|
|
@@ -250,44 +296,6 @@ write_report() {
|
|
|
250
296
|
EOF
|
|
251
297
|
}
|
|
252
298
|
|
|
253
|
-
# ------------------------------------------------------------------------------
|
|
254
|
-
# resolve_workflow_version — find the package.json that really owns
|
|
255
|
-
# pi-extensible-workflows by walking up from where Node resolved its entry.
|
|
256
|
-
# ------------------------------------------------------------------------------
|
|
257
|
-
|
|
258
|
-
resolve_workflow_version() {
|
|
259
|
-
local search_root="$1"
|
|
260
|
-
node -e '
|
|
261
|
-
const path = require("path");
|
|
262
|
-
const fs = require("fs");
|
|
263
|
-
const searchRoot = process.argv[1];
|
|
264
|
-
let entry;
|
|
265
|
-
try {
|
|
266
|
-
entry = require.resolve("pi-extensible-workflows", { paths: [searchRoot] });
|
|
267
|
-
} catch (err) {
|
|
268
|
-
console.error(`pi-extensible-workflows is not resolvable from ${searchRoot}: ${err.message}`);
|
|
269
|
-
process.exit(1);
|
|
270
|
-
}
|
|
271
|
-
let dir = path.dirname(entry);
|
|
272
|
-
for (;;) {
|
|
273
|
-
const candidate = path.join(dir, "package.json");
|
|
274
|
-
if (fs.existsSync(candidate)) {
|
|
275
|
-
const pkg = JSON.parse(fs.readFileSync(candidate, "utf8"));
|
|
276
|
-
if (pkg.name === "pi-extensible-workflows") {
|
|
277
|
-
process.stderr.write(`resolved_package_json=${candidate}\n`);
|
|
278
|
-
console.log(pkg.version);
|
|
279
|
-
process.exit(0);
|
|
280
|
-
}
|
|
281
|
-
}
|
|
282
|
-
const parent = path.dirname(dir);
|
|
283
|
-
if (parent === dir) break;
|
|
284
|
-
dir = parent;
|
|
285
|
-
}
|
|
286
|
-
console.error(`Could not find pi-extensible-workflows package.json by walking up from ${entry}`);
|
|
287
|
-
process.exit(1);
|
|
288
|
-
' "${search_root}"
|
|
289
|
-
}
|
|
290
|
-
|
|
291
299
|
# ==============================================================================
|
|
292
300
|
# Module registry
|
|
293
301
|
#
|
|
@@ -297,30 +305,33 @@ resolve_workflow_version() {
|
|
|
297
305
|
# via --all or an explicit --only.
|
|
298
306
|
# ==============================================================================
|
|
299
307
|
|
|
300
|
-
MODULE_ORDER=(base node bun pi go dotenv ee pi-workflows herdr gentle-ai
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
)
|
|
308
|
+
MODULE_ORDER=(base node bun pi go dotenv ee skills pi-workflows herdr gentle-ai codex antigravity opencode cockpit rotator)
|
|
309
|
+
|
|
310
|
+
module_desc() {
|
|
311
|
+
case "$1" in
|
|
312
|
+
base) printf '%s\n' "System packages (build tools, git, gh, python, neovim, jq, imagemagick, go)" ;;
|
|
313
|
+
node) printf '%s\n' "Node.js v22 + npm@latest (nvm on Unix, winget on Windows)" ;;
|
|
314
|
+
bun) printf '%s\n' "Bun runtime" ;;
|
|
315
|
+
pi) printf '%s\n' "pi.dev coding agent CLI" ;;
|
|
316
|
+
go) printf '%s\n' "Go toolchain" ;;
|
|
317
|
+
dotenv) printf '%s\n' "vekexasia/dotenv dotfiles (Linux only: clones + runs setup_env.sh)" ;;
|
|
318
|
+
ee) printf '%s\n' "Engineering Excellence skill (npx skills add, all detected agents)" ;;
|
|
319
|
+
skills) printf '%s\n' "Upstream agent skills (herdr, grilling, research, typescript-advanced, show-me, ...) via npx skills add" ;;
|
|
320
|
+
pi-workflows) printf '%s\n' "pi-extensible-workflows (fix module resolution for pi extensions)" ;;
|
|
321
|
+
herdr) printf '%s\n' "herdr terminal multiplexer" ;;
|
|
322
|
+
gentle-ai) printf '%s\n' "gentle-ai / gga ecosystem configurator (per-agent select + MCP) + gentle-pi" ;;
|
|
323
|
+
codex) printf '%s\n' "OpenAI Codex CLI" ;;
|
|
324
|
+
antigravity) printf '%s\n' "Google Antigravity CLI (agy)" ;;
|
|
325
|
+
opencode) printf '%s\n' "opencode agent CLI (opencode-ai)" ;;
|
|
326
|
+
cockpit) printf '%s\n' "cockpit-tools desktop GUI app (optional, CC BY-NC-SA)" ;;
|
|
327
|
+
rotator) printf '%s\n' "tuxevil-rotator multi-account Gemini/Antigravity gateway (optional, opt-in)" ;;
|
|
328
|
+
*) return 1 ;;
|
|
329
|
+
esac
|
|
330
|
+
}
|
|
319
331
|
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
)
|
|
332
|
+
module_is_optional() {
|
|
333
|
+
[[ "$1" == "cockpit" || "$1" == "rotator" ]]
|
|
334
|
+
}
|
|
324
335
|
|
|
325
336
|
# ------------------------------------------------------------------------------
|
|
326
337
|
# OS / package-manager detection
|
|
@@ -444,7 +455,7 @@ mod_node() {
|
|
|
444
455
|
# nvm refuses to operate when npm_config_prefix is set (e.g. to /usr/local),
|
|
445
456
|
# aborting with "nvm is not compatible with the npm_config_prefix ...".
|
|
446
457
|
# It is only needed by nvm's own shims, so drop it for this process.
|
|
447
|
-
unset npm_config_prefix NPM_CONFIG_PREFIX 2>/dev/null ||
|
|
458
|
+
unset npm_config_prefix NPM_CONFIG_PREFIX 2>/dev/null || log_event "WARN" "node" "unset_prefix_failed" "Could not unset npm prefix vars" 0
|
|
448
459
|
|
|
449
460
|
# shellcheck disable=SC1090
|
|
450
461
|
source "${NVM_DIR}/nvm.sh"
|
|
@@ -453,13 +464,7 @@ mod_node() {
|
|
|
453
464
|
run_cmd "node" nvm alias default 22
|
|
454
465
|
run_cmd "node" nvm use 22
|
|
455
466
|
|
|
456
|
-
|
|
457
|
-
const [major, minor] = process.versions.node.split('.').map(Number);
|
|
458
|
-
if (major < 22 || (major === 22 && minor < 19)) {
|
|
459
|
-
console.error(`Node.js ${process.versions.node} is too old; pi-extensible-workflows needs >= 22.19.`);
|
|
460
|
-
process.exit(1);
|
|
461
|
-
}
|
|
462
|
-
NODE
|
|
467
|
+
assert_node_minimum
|
|
463
468
|
|
|
464
469
|
# npm@latest (NOT npm@12 — that version does not exist).
|
|
465
470
|
run_cmd "node" npm install -g npm@latest
|
|
@@ -474,6 +479,28 @@ NODE
|
|
|
474
479
|
run_optional "node" sudo ln -sf "${npm_bin}" /usr/local/bin/npm
|
|
475
480
|
}
|
|
476
481
|
|
|
482
|
+
assert_node_minimum() {
|
|
483
|
+
require_command node
|
|
484
|
+
|
|
485
|
+
local node_version major minor
|
|
486
|
+
capture_cmd node_version "node" node --version
|
|
487
|
+
if [[ "${node_version}" =~ ^v([0-9]+)\.([0-9]+) ]]; then
|
|
488
|
+
major="${BASH_REMATCH[1]}"
|
|
489
|
+
minor="${BASH_REMATCH[2]}"
|
|
490
|
+
else
|
|
491
|
+
log_event "ERROR" "node" "version_invalid" "Could not parse Node.js version" 1 "version=${node_version}"
|
|
492
|
+
return 1
|
|
493
|
+
fi
|
|
494
|
+
|
|
495
|
+
if (( major < 22 || (major == 22 && minor < 19) )); then
|
|
496
|
+
log_event "ERROR" "node" "version_unsupported" \
|
|
497
|
+
"Node.js ${node_version} is too old; pi-extensible-workflows needs >= 22.19" 1
|
|
498
|
+
return 1
|
|
499
|
+
fi
|
|
500
|
+
log_event "INFO" "node" "runtime_validated" "Node.js version satisfies workflow requirement" 0 \
|
|
501
|
+
"version=${node_version};minimum=22.19"
|
|
502
|
+
}
|
|
503
|
+
|
|
477
504
|
# --- bun --------------------------------------------------------------------
|
|
478
505
|
mod_bun() {
|
|
479
506
|
section "Bun"
|
|
@@ -485,7 +512,12 @@ mod_bun() {
|
|
|
485
512
|
fi
|
|
486
513
|
export PATH="${BUN_INSTALL}/bin:${PATH}"
|
|
487
514
|
require_command bun
|
|
488
|
-
|
|
515
|
+
local bun_version
|
|
516
|
+
if capture_cmd bun_version "bun" bun --version; then
|
|
517
|
+
log_event "INFO" "bun" "runtime_ready" "Bun runtime validated" 0 "version=${bun_version}"
|
|
518
|
+
else
|
|
519
|
+
return 1
|
|
520
|
+
fi
|
|
489
521
|
}
|
|
490
522
|
|
|
491
523
|
# --- pi ---------------------------------------------------------------------
|
|
@@ -499,7 +531,10 @@ mod_pi() {
|
|
|
499
531
|
fi
|
|
500
532
|
export PATH="${HOME}/.pi/bin:${HOME}/.local/bin:${PATH}"
|
|
501
533
|
require_command pi
|
|
502
|
-
PI_VERSION="$(pi --version 2>/dev/null
|
|
534
|
+
if ! PI_VERSION="$(pi --version 2>/dev/null)"; then
|
|
535
|
+
PI_VERSION="unknown"
|
|
536
|
+
log_event "WARN" "pi" "version_unavailable" "Could not read pi --version" 0
|
|
537
|
+
fi
|
|
503
538
|
log_event "INFO" "pi" "cli_ready" "Pi CLI detected" 0 "version=${PI_VERSION}"
|
|
504
539
|
mkdir -p "${PI_AGENT_DIR}" "${PI_EXTENSIONS_DIR}" "${PI_NPM_DIR}" "${PI_AGENT_DIR}/skills"
|
|
505
540
|
}
|
|
@@ -508,8 +543,12 @@ mod_pi() {
|
|
|
508
543
|
mod_go() {
|
|
509
544
|
section "Go toolchain"
|
|
510
545
|
if command -v go >/dev/null 2>&1; then
|
|
511
|
-
|
|
512
|
-
|
|
546
|
+
local go_version
|
|
547
|
+
if capture_cmd go_version "go" go version; then
|
|
548
|
+
log_event "INFO" "go" "already_present" "Go already installed" 0 "version=${go_version}"
|
|
549
|
+
return
|
|
550
|
+
fi
|
|
551
|
+
return 1
|
|
513
552
|
fi
|
|
514
553
|
if [[ "${OS_FAMILY}" == "macos" ]]; then
|
|
515
554
|
run_cmd "go" brew install go
|
|
@@ -542,27 +581,37 @@ mod_dotenv() {
|
|
|
542
581
|
fi
|
|
543
582
|
|
|
544
583
|
# Idempotent upstream-quirk patches (no-ops once upstream merges the fixes).
|
|
545
|
-
if
|
|
546
|
-
|
|
584
|
+
if grep_probe "dotenv" "${TMP_DIR}/monokai_hits" \
|
|
585
|
+
-RIl --exclude-dir=.git "gthelding/monokai-pro.nvim" "${DOTENV_DIR}"; then
|
|
547
586
|
while IFS= read -r file; do
|
|
548
|
-
sed -i 's|gthelding/monokai-pro.nvim|loctvl842/monokai-pro.nvim|g' "${file}"
|
|
587
|
+
run_cmd "dotenv" sed -i 's|gthelding/monokai-pro.nvim|loctvl842/monokai-pro.nvim|g' "${file}"
|
|
549
588
|
log_event "INFO" "dotenv" "reference_patched" "Updated stale monokai-pro reference" 0 "file=${file}"
|
|
550
589
|
done < "${TMP_DIR}/monokai_hits"
|
|
551
590
|
fi
|
|
552
591
|
|
|
553
|
-
if
|
|
554
|
-
|
|
592
|
+
if grep_probe "dotenv" "${TMP_DIR}/sudo_npm_hits" \
|
|
593
|
+
-RIl --exclude-dir=.git 'sudo npm install -g --prefix /usr/local bun' "${DOTENV_DIR}"; then
|
|
555
594
|
while IFS= read -r file; do
|
|
556
|
-
sed -i 's|sudo npm install -g --prefix /usr/local bun|sudo "$(command -v npm)" install -g --prefix /usr/local bun|g' "${file}"
|
|
595
|
+
run_cmd "dotenv" sed -i 's|sudo npm install -g --prefix /usr/local bun|sudo "$(command -v npm)" install -g --prefix /usr/local bun|g' "${file}"
|
|
557
596
|
log_event "INFO" "dotenv" "reference_patched" "Patched sudo npm call to absolute path" 0 "file=${file}"
|
|
558
597
|
done < "${TMP_DIR}/sudo_npm_hits"
|
|
559
598
|
fi
|
|
560
599
|
|
|
561
|
-
if
|
|
562
|
-
|
|
600
|
+
if grep_probe "dotenv" "${TMP_DIR}/treesitter_hits" \
|
|
601
|
+
-RIl --exclude-dir=.git -e 'npm install -g --prefix "\$HOME/.local" tree-sitter-cli' "${DOTENV_DIR}"; then
|
|
563
602
|
while IFS= read -r file; do
|
|
564
|
-
|
|
565
|
-
|
|
603
|
+
local marker_rc=0
|
|
604
|
+
grep -q 'AI_DEV_TS_CLI_PATCH' "${file}" 2>>"${HUMAN_LOG}" || marker_rc=$?
|
|
605
|
+
case "${marker_rc}" in
|
|
606
|
+
0) continue ;; # already patched
|
|
607
|
+
1) ;; # not patched yet; apply below
|
|
608
|
+
*)
|
|
609
|
+
log_event "ERROR" "dotenv" "marker_probe_failed" \
|
|
610
|
+
"grep marker probe failed with an operational error" "${marker_rc}" "file=${file}"
|
|
611
|
+
exit "${marker_rc}"
|
|
612
|
+
;;
|
|
613
|
+
esac
|
|
614
|
+
run_cmd "dotenv" python3 - "${file}" <<'PYPATCH'
|
|
566
615
|
import sys
|
|
567
616
|
path = sys.argv[1]
|
|
568
617
|
with open(path, "r", newline="") as fh:
|
|
@@ -591,10 +640,74 @@ PYPATCH
|
|
|
591
640
|
if [[ -x "${DOTENV_DIR}/setup_env.sh" ]]; then
|
|
592
641
|
run_cmd "dotenv" bash "${DOTENV_DIR}/setup_env.sh"
|
|
593
642
|
else
|
|
594
|
-
log_event "
|
|
643
|
+
log_event "ERROR" "dotenv" "setup_script_missing" \
|
|
644
|
+
"dotenv/setup_env.sh missing or not executable" 1 \
|
|
645
|
+
"expected=${DOTENV_DIR}/setup_env.sh"
|
|
646
|
+
return 1
|
|
647
|
+
fi
|
|
648
|
+
}
|
|
649
|
+
|
|
650
|
+
# --- skill target helpers ----------------------------------------------------
|
|
651
|
+
agent_config_dir() {
|
|
652
|
+
case "$1" in
|
|
653
|
+
pi) printf '%s\n' "${HOME}/.pi" ;;
|
|
654
|
+
claude-code) printf '%s\n' "${HOME}/.claude" ;;
|
|
655
|
+
gemini-cli) printf '%s\n' "${HOME}/.gemini" ;;
|
|
656
|
+
cursor) printf '%s\n' "${HOME}/.cursor" ;;
|
|
657
|
+
antigravity) printf '%s\n' "${HOME}/.antigravity" ;;
|
|
658
|
+
codex) printf '%s\n' "${HOME}/.codex" ;;
|
|
659
|
+
opencode) printf '%s\n' "${HOME}/.config/opencode" ;;
|
|
660
|
+
*) return 1 ;;
|
|
661
|
+
esac
|
|
662
|
+
}
|
|
663
|
+
|
|
664
|
+
agent_skill_root() {
|
|
665
|
+
case "$1" in
|
|
666
|
+
pi) printf '%s\n' "${PI_AGENT_DIR}/skills" ;;
|
|
667
|
+
claude-code) printf '%s\n' "${HOME}/.claude/skills" ;;
|
|
668
|
+
gemini-cli) printf '%s\n' "${HOME}/.gemini/skills" ;;
|
|
669
|
+
cursor) printf '%s\n' "${HOME}/.cursor/skills" ;;
|
|
670
|
+
antigravity) printf '%s\n' "${HOME}/.antigravity/skills" ;;
|
|
671
|
+
codex) printf '%s\n' "${HOME}/.codex/skills" ;;
|
|
672
|
+
opencode) printf '%s\n' "${HOME}/.config/opencode/skills" ;;
|
|
673
|
+
*) return 1 ;;
|
|
674
|
+
esac
|
|
675
|
+
}
|
|
676
|
+
|
|
677
|
+
# Candidate skill roots per agent (one per line): verification passes if
|
|
678
|
+
# SKILL.md exists under any of them. Every agent keeps its single existing root;
|
|
679
|
+
# Codex also accepts ${HOME}/.agents/skills because upstream `skills add
|
|
680
|
+
# --global` writes Codex skills there instead of ${HOME}/.codex/skills.
|
|
681
|
+
agent_skill_roots() {
|
|
682
|
+
agent_skill_root "$1" || return 1
|
|
683
|
+
if [[ "$1" == codex ]]; then
|
|
684
|
+
printf '%s\n' "${HOME}/.agents/skills"
|
|
595
685
|
fi
|
|
596
686
|
}
|
|
597
687
|
|
|
688
|
+
verify_skill_for_agents() {
|
|
689
|
+
local phase="$1" skill="$2"; shift 2
|
|
690
|
+
local agent root found checked
|
|
691
|
+
for agent in "$@"; do
|
|
692
|
+
found=0
|
|
693
|
+
checked=""
|
|
694
|
+
while IFS= read -r root; do
|
|
695
|
+
checked="${checked:+${checked}, }${root}/${skill}/SKILL.md"
|
|
696
|
+
if [[ -f "${root}/${skill}/SKILL.md" ]]; then
|
|
697
|
+
found=1
|
|
698
|
+
break
|
|
699
|
+
fi
|
|
700
|
+
done < <(agent_skill_roots "${agent}")
|
|
701
|
+
if (( found == 0 )); then
|
|
702
|
+
log_event "ERROR" "${phase}" "skill_missing" \
|
|
703
|
+
"Skill SKILL.md missing for targeted agent" 1 \
|
|
704
|
+
"agent=${agent};skill=${skill};checked=${checked}"
|
|
705
|
+
return 1
|
|
706
|
+
fi
|
|
707
|
+
done
|
|
708
|
+
return 0
|
|
709
|
+
}
|
|
710
|
+
|
|
598
711
|
# --- engineering-excellence -------------------------------------------------
|
|
599
712
|
mod_ee() {
|
|
600
713
|
section "Engineering Excellence"
|
|
@@ -604,41 +717,72 @@ mod_ee() {
|
|
|
604
717
|
# (replaces the old git-clone-and-move). Agents are detected by their config dir.
|
|
605
718
|
# Keys are the `skills` CLI agent names (claude-code, gemini-cli, ...), which
|
|
606
719
|
# differ from the config-dir basename; values are the dir we detect them by.
|
|
607
|
-
local agent
|
|
608
|
-
local -A agent_dir=(
|
|
609
|
-
[pi]="${HOME}/.pi"
|
|
610
|
-
[claude-code]="${HOME}/.claude"
|
|
611
|
-
[gemini-cli]="${HOME}/.gemini"
|
|
612
|
-
[cursor]="${HOME}/.cursor"
|
|
613
|
-
[antigravity]="${HOME}/.antigravity"
|
|
614
|
-
[codex]="${HOME}/.codex"
|
|
615
|
-
[opencode]="${HOME}/.config/opencode"
|
|
616
|
-
)
|
|
720
|
+
local agent
|
|
617
721
|
local installed_any=0
|
|
722
|
+
local -a target_agents=()
|
|
618
723
|
for agent in pi claude-code gemini-cli cursor antigravity codex opencode; do
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
run_optional "engineering-excellence" \
|
|
724
|
+
[[ -d "$(agent_config_dir "${agent}")" ]] || continue
|
|
725
|
+
run_cmd "engineering-excellence" \
|
|
622
726
|
npx --yes skills@latest add "${ENGINEERING_EXCELLENCE_SLUG}" \
|
|
623
727
|
--skill "${ENGINEERING_EXCELLENCE_SKILL}" --global --agent "${agent}" --copy --yes
|
|
728
|
+
target_agents+=("${agent}")
|
|
624
729
|
installed_any=1
|
|
625
730
|
done
|
|
626
731
|
|
|
627
732
|
if (( installed_any == 0 )); then
|
|
628
733
|
# No agent detected yet — install at least for pi (created by mod_pi).
|
|
629
|
-
|
|
734
|
+
run_cmd "engineering-excellence" \
|
|
630
735
|
npx --yes skills@latest add "${ENGINEERING_EXCELLENCE_SLUG}" \
|
|
631
736
|
--skill "${ENGINEERING_EXCELLENCE_SKILL}" --global --agent pi --copy --yes
|
|
737
|
+
target_agents=(pi)
|
|
632
738
|
fi
|
|
633
739
|
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
|
|
641
|
-
|
|
740
|
+
verify_skill_for_agents "engineering-excellence" "${ENGINEERING_EXCELLENCE_SKILL}" "${target_agents[@]}"
|
|
741
|
+
log_event "INFO" "engineering-excellence" "skills_verified" \
|
|
742
|
+
"Engineering Excellence skill verified for every targeted agent" 0 \
|
|
743
|
+
"agents=$(IFS=,; printf '%s' "${target_agents[*]}")"
|
|
744
|
+
}
|
|
745
|
+
|
|
746
|
+
# --- upstream agent skills --------------------------------------------------
|
|
747
|
+
# Installs vekexasia/dotenv's skill stack on every OS via `npx skills add`.
|
|
748
|
+
# On Linux the dotenv module may already install these; skills add --copy is
|
|
749
|
+
# idempotent, so a re-run is safe.
|
|
750
|
+
mod_skills() {
|
|
751
|
+
section "Agent skills (upstream stack)"
|
|
752
|
+
require_command npx
|
|
753
|
+
|
|
754
|
+
# Agent keys are the `skills` CLI names (claude-code, gemini-cli, ...),
|
|
755
|
+
# detected by their config dir — same mapping as mod_ee.
|
|
756
|
+
local agent
|
|
757
|
+
local -a agents=()
|
|
758
|
+
for agent in pi claude-code gemini-cli cursor antigravity codex opencode; do
|
|
759
|
+
[[ -d "$(agent_config_dir "${agent}")" ]] && agents+=("${agent}")
|
|
760
|
+
done
|
|
761
|
+
# pi is created by mod_pi; guarantee at least pi so the stack always lands.
|
|
762
|
+
(( ${#agents[@]} == 0 )) && agents=(pi)
|
|
763
|
+
|
|
764
|
+
local source_spec source skill_csv
|
|
765
|
+
local -a skill_list
|
|
766
|
+
for source_spec in "${UPSTREAM_SKILL_SOURCES[@]}"; do
|
|
767
|
+
source="${source_spec%% *}"
|
|
768
|
+
skill_csv="${source_spec#* }"
|
|
769
|
+
# The script keeps global IFS at newline/tab for safe line reads;
|
|
770
|
+
# explicitly restore spaces for this space-separated skill list.
|
|
771
|
+
IFS=' ' read -r -a skill_list <<< "${skill_csv}"
|
|
772
|
+
for agent in "${agents[@]}"; do
|
|
773
|
+
run_cmd "skills" \
|
|
774
|
+
npx --yes skills@latest add "${source}" \
|
|
775
|
+
--skill "${skill_list[@]}" --global --agent "${agent}" --copy --yes
|
|
776
|
+
done
|
|
777
|
+
done
|
|
778
|
+
|
|
779
|
+
local sk
|
|
780
|
+
for sk in "${UPSTREAM_SKILL_NAMES[@]}"; do
|
|
781
|
+
verify_skill_for_agents "skills" "${sk}" "${agents[@]}"
|
|
782
|
+
done
|
|
783
|
+
log_event "INFO" "skills" "skills_verified" \
|
|
784
|
+
"Upstream skills verified for every targeted agent" 0 \
|
|
785
|
+
"agents=$(IFS=,; printf '%s' "${agents[*]}")"
|
|
642
786
|
}
|
|
643
787
|
|
|
644
788
|
# --- pi-extensible-workflows ------------------------------------------------
|
|
@@ -646,6 +790,7 @@ mod_pi_workflows() {
|
|
|
646
790
|
section "pi-extensible-workflows"
|
|
647
791
|
require_command pi
|
|
648
792
|
require_command node
|
|
793
|
+
assert_node_minimum
|
|
649
794
|
|
|
650
795
|
if [[ -z "${PI_WORKFLOW_VERSION}" ]]; then
|
|
651
796
|
capture_cmd PI_WORKFLOW_VERSION "pi-workflows" npm view pi-extensible-workflows version
|
|
@@ -661,16 +806,33 @@ mod_pi_workflows() {
|
|
|
661
806
|
mkdir -p "${PI_EXTENSIONS_DIR}"
|
|
662
807
|
pushd "${PI_EXTENSIONS_DIR}" >/dev/null
|
|
663
808
|
printf '%s\n' 'ignore-scripts=false' > .npmrc
|
|
809
|
+
# Mark this directory as an npm project root so `npm install` lands HERE and
|
|
810
|
+
# cannot walk up the tree into an ancestor project. This matters when
|
|
811
|
+
# ~/.pi/agent is symlinked into another repo (e.g. the dotenv dotfiles):
|
|
812
|
+
# without a local package.json, npm would install into that repo's
|
|
813
|
+
# node_modules and verification would then pick up a stale, shadowing copy.
|
|
814
|
+
if [[ ! -f package.json ]]; then
|
|
815
|
+
printf '%s\n' '{"name":"pi-extensions","private":true}' > package.json
|
|
816
|
+
fi
|
|
664
817
|
run_cmd "pi-workflows-node" npm install --save-exact --no-audit --no-fund \
|
|
665
818
|
"pi-extensible-workflows@${PI_WORKFLOW_VERSION}"
|
|
666
819
|
popd >/dev/null
|
|
667
820
|
|
|
668
|
-
|
|
669
|
-
|
|
670
|
-
|
|
821
|
+
# Verify the version that actually landed in THIS directory by reading its
|
|
822
|
+
# local package.json directly. Do NOT use require.resolve here: it ascends
|
|
823
|
+
# the directory tree and can resolve a shadowing copy in an ancestor
|
|
824
|
+
# node_modules, producing a false version_mismatch.
|
|
825
|
+
local local_pkg="${PI_EXTENSIONS_DIR}/node_modules/pi-extensible-workflows/package.json"
|
|
826
|
+
if [[ ! -f "${local_pkg}" ]]; then
|
|
827
|
+
log_event "ERROR" "pi-workflows-node" "install_missing" "pi-extensible-workflows not installed in extensions dir" 1 \
|
|
828
|
+
"expected_path=${local_pkg}"
|
|
829
|
+
exit 1
|
|
830
|
+
fi
|
|
831
|
+
log_event "INFO" "pi-workflows-node" "module_resolved" "Installed in pi extensions dir" 0 "resolved=${local_pkg}"
|
|
671
832
|
|
|
672
833
|
local installed
|
|
673
|
-
installed
|
|
834
|
+
capture_cmd installed "pi-workflows-node" node -e \
|
|
835
|
+
'console.log(require(process.argv[1]).version)' "${local_pkg}"
|
|
674
836
|
if [[ "${installed}" != "${PI_WORKFLOW_VERSION}" ]]; then
|
|
675
837
|
log_event "ERROR" "pi-workflows-node" "version_mismatch" "Installed version mismatch" 1 \
|
|
676
838
|
"expected=${PI_WORKFLOW_VERSION};actual=${installed}"
|
|
@@ -680,9 +842,31 @@ mod_pi_workflows() {
|
|
|
680
842
|
if [[ -d "${DOTENV_EXT_DIR}" ]]; then
|
|
681
843
|
pushd "${DOTENV_EXT_DIR}" >/dev/null
|
|
682
844
|
printf '%s\n' 'ignore-scripts=false' > .npmrc
|
|
845
|
+
if [[ ! -f package.json ]]; then
|
|
846
|
+
printf '%s\n' '{"name":"pi-ext-workflows","private":true}' > package.json
|
|
847
|
+
fi
|
|
683
848
|
run_cmd "dotenv-workflows" npm install --save-exact --no-audit --no-fund \
|
|
684
849
|
"pi-extensible-workflows@${PI_WORKFLOW_VERSION}"
|
|
685
850
|
popd >/dev/null
|
|
851
|
+
|
|
852
|
+
local dotenv_workflow_pkg="${DOTENV_EXT_DIR}/node_modules/pi-extensible-workflows/package.json"
|
|
853
|
+
if [[ ! -f "${dotenv_workflow_pkg}" ]]; then
|
|
854
|
+
log_event "ERROR" "dotenv-workflows" "install_missing" \
|
|
855
|
+
"pi-extensible-workflows not installed in dotenv extensions dir" 1 \
|
|
856
|
+
"expected_path=${dotenv_workflow_pkg}"
|
|
857
|
+
exit 1
|
|
858
|
+
fi
|
|
859
|
+
local dotenv_installed
|
|
860
|
+
capture_cmd dotenv_installed "dotenv-workflows" node -e \
|
|
861
|
+
'console.log(require(process.argv[1]).version)' "${dotenv_workflow_pkg}"
|
|
862
|
+
if [[ "${dotenv_installed}" != "${PI_WORKFLOW_VERSION}" ]]; then
|
|
863
|
+
log_event "ERROR" "dotenv-workflows" "version_mismatch" \
|
|
864
|
+
"Installed dotenv workflow version mismatch" 1 \
|
|
865
|
+
"expected=${PI_WORKFLOW_VERSION};actual=${dotenv_installed}"
|
|
866
|
+
exit 1
|
|
867
|
+
fi
|
|
868
|
+
log_event "INFO" "dotenv-workflows" "module_verified" \
|
|
869
|
+
"Verified installed dotenv workflow package" 0 "path=${dotenv_workflow_pkg}"
|
|
686
870
|
fi
|
|
687
871
|
}
|
|
688
872
|
|
|
@@ -690,92 +874,126 @@ mod_pi_workflows() {
|
|
|
690
874
|
mod_herdr() {
|
|
691
875
|
section "herdr"
|
|
692
876
|
if command -v herdr >/dev/null 2>&1; then
|
|
693
|
-
|
|
694
|
-
|
|
877
|
+
local herdr_version
|
|
878
|
+
if capture_cmd herdr_version "herdr" herdr --version; then
|
|
879
|
+
log_event "INFO" "herdr" "already_present" "herdr already installed" 0 "version=${herdr_version}"
|
|
880
|
+
return
|
|
881
|
+
fi
|
|
882
|
+
return 1
|
|
695
883
|
fi
|
|
696
884
|
if [[ "${OS_FAMILY}" == "macos" ]]; then
|
|
697
|
-
|
|
885
|
+
run_cmd "herdr" brew install herdr
|
|
698
886
|
else
|
|
699
887
|
local installer="${TMP_DIR}/install-herdr.sh"
|
|
700
|
-
|
|
701
|
-
[[ -s "${installer}" ]]
|
|
888
|
+
run_cmd "herdr" curl -fsSL https://herdr.dev/install.sh -o "${installer}"
|
|
889
|
+
[[ -s "${installer}" ]] || {
|
|
890
|
+
log_event "ERROR" "herdr" "installer_missing" "herdr installer is empty" 1 "path=${installer}"
|
|
891
|
+
return 1
|
|
892
|
+
}
|
|
893
|
+
run_cmd "herdr" sh "${installer}"
|
|
702
894
|
fi
|
|
895
|
+
require_command herdr
|
|
703
896
|
}
|
|
704
897
|
|
|
705
898
|
# --- gentle-ai --------------------------------------------------------------
|
|
899
|
+
# Reinstates the Gentle AI ecosystem configurator. `gentle-ai install` is the
|
|
900
|
+
# per-agent/per-IDE selector (Pi, Claude Code, Cursor, Codex, ...) that also
|
|
901
|
+
# wires each selected agent's MCP servers, so tools appear under /mcp. The
|
|
902
|
+
# configurator is a CORE step: it always runs (interactively with a TTY, or
|
|
903
|
+
# non-interactively over the detected agents in CI/pipes) and a failure fails
|
|
904
|
+
# the module. For pi we additionally guarantee + verify the first-class
|
|
905
|
+
# gentle-pi harness and pi-mcp-adapter. Idempotent: safe to re-run.
|
|
706
906
|
mod_gentle_ai() {
|
|
707
907
|
section "gentle-ai"
|
|
708
|
-
|
|
908
|
+
|
|
909
|
+
# Installers drop the CLI into a PATH dir; make sure the usual ones resolve
|
|
910
|
+
# in this live process so the post-install verification can find it.
|
|
911
|
+
export PATH="${HOME}/.local/bin:${HOME}/go/bin:${PATH}"
|
|
912
|
+
|
|
913
|
+
# The per-agent selector and the pi harness both require the gentle-ai CLI
|
|
914
|
+
# itself; a legacy standalone `gga` is NOT enough, so install whenever the
|
|
915
|
+
# gentle-ai CLI is missing even if an old gga is on PATH.
|
|
916
|
+
if ! command -v gentle-ai >/dev/null 2>&1; then
|
|
709
917
|
if [[ "${OS_FAMILY}" == "macos" ]]; then
|
|
710
|
-
|
|
711
|
-
|
|
918
|
+
run_cmd "gentle-ai" brew tap gentleman-programming/tap
|
|
919
|
+
run_cmd "gentle-ai" brew install gentle-ai
|
|
712
920
|
else
|
|
713
921
|
local installer="${TMP_DIR}/install-gentle-ai.sh"
|
|
714
|
-
|
|
715
|
-
[[ -s "${installer}" ]]
|
|
922
|
+
run_cmd "gentle-ai" curl -fsSL "${GENTLE_AI_INSTALL}" -o "${installer}"
|
|
923
|
+
[[ -s "${installer}" ]] || {
|
|
924
|
+
log_event "ERROR" "gentle-ai" "installer_missing" "gentle-ai installer is empty" 1 "path=${installer}"
|
|
925
|
+
return 1
|
|
926
|
+
}
|
|
927
|
+
run_cmd "gentle-ai" bash "${installer}"
|
|
928
|
+
fi
|
|
929
|
+
fi
|
|
930
|
+
|
|
931
|
+
# Verify the CLI is present (a binary, not an npm tree — command -v + --version
|
|
932
|
+
# is the correct check). Required for both the selector and the pi harness.
|
|
933
|
+
command -v gentle-ai >/dev/null 2>&1 || {
|
|
934
|
+
log_event "ERROR" "gentle-ai" "binary_missing" "gentle-ai CLI not found on PATH after install" 1
|
|
935
|
+
return 1
|
|
936
|
+
}
|
|
937
|
+
run_cmd "gentle-ai" gentle-ai --version
|
|
938
|
+
|
|
939
|
+
# Detect the agents/IDEs present on this machine (same mapping as mod_ee).
|
|
940
|
+
local agent
|
|
941
|
+
local -a detected_agents=()
|
|
942
|
+
for agent in pi claude-code gemini-cli cursor antigravity codex opencode; do
|
|
943
|
+
[[ -d "$(agent_config_dir "${agent}")" ]] && detected_agents+=("${agent}")
|
|
944
|
+
done
|
|
945
|
+
(( ${#detected_agents[@]} == 0 )) && detected_agents=(pi)
|
|
946
|
+
|
|
947
|
+
# Per-agent / per-IDE selection + MCP wiring, owned by gentle-ai. This is a
|
|
948
|
+
# core step and must actually run: with a real TTY we launch the interactive
|
|
949
|
+
# selector (run directly — run_cmd would redirect stdout and hide prompts);
|
|
950
|
+
# otherwise we run it non-interactively over the detected agents so CI/pipes
|
|
951
|
+
# never hang. A failure fails the module (no silent downgrade).
|
|
952
|
+
if [[ -t 0 && -t 1 ]]; then
|
|
953
|
+
log_event "INFO" "gentle-ai" "configurator_start" "Launching gentle-ai install (choose agents/IDEs + MCP)" 0
|
|
954
|
+
if ! gentle-ai install --scope global; then
|
|
955
|
+
log_event "ERROR" "gentle-ai" "configurator_failed" "gentle-ai install failed" 1
|
|
956
|
+
return 1
|
|
716
957
|
fi
|
|
958
|
+
else
|
|
959
|
+
local agents_csv; agents_csv="$(IFS=,; printf '%s' "${detected_agents[*]}")"
|
|
960
|
+
log_event "INFO" "gentle-ai" "configurator_noninteractive" \
|
|
961
|
+
"No TTY; installing gentle-ai for detected agents" 0 "agents=${agents_csv}"
|
|
962
|
+
run_cmd "gentle-ai" gentle-ai install --scope global --agents "${agents_csv}"
|
|
717
963
|
fi
|
|
964
|
+
log_event "INFO" "gentle-ai" "configurator_done" "gentle-ai install completed" 0
|
|
718
965
|
|
|
719
|
-
#
|
|
720
|
-
#
|
|
966
|
+
# Guarantee pi reads gentle-ai in its MCP list (/mcp): install the first-class
|
|
967
|
+
# gentle-pi harness and the pi-mcp-adapter bridge, then verify the exact
|
|
968
|
+
# target (pi's own settings file), not a walked resolution.
|
|
721
969
|
if command -v pi >/dev/null 2>&1; then
|
|
722
|
-
|
|
723
|
-
|
|
724
|
-
|
|
970
|
+
run_cmd "gentle-ai" pi install npm:gentle-pi
|
|
971
|
+
run_cmd "gentle-ai" pi install npm:pi-mcp-adapter
|
|
972
|
+
local pi_settings="${PI_AGENT_DIR}/settings.json"
|
|
973
|
+
if [[ -f "${pi_settings}" ]] \
|
|
974
|
+
&& grep -q '"npm:gentle-pi"' "${pi_settings}" \
|
|
975
|
+
&& grep -q '"npm:pi-mcp-adapter"' "${pi_settings}"; then
|
|
976
|
+
log_event "INFO" "gentle-ai" "pi_enabled" \
|
|
977
|
+
"gentle-pi + pi-mcp-adapter registered in pi (verify: /mcp, /gentle-ai:status)" 0
|
|
978
|
+
else
|
|
979
|
+
log_event "ERROR" "gentle-ai" "pi_enable_failed" \
|
|
980
|
+
"gentle-pi and/or pi-mcp-adapter not present in pi settings after install" 1 "expected=${pi_settings}"
|
|
981
|
+
return 1
|
|
982
|
+
fi
|
|
725
983
|
fi
|
|
726
984
|
|
|
727
|
-
# Surface gentle-ai's own next-steps (do NOT run — they are per-repo).
|
|
728
985
|
log_event "INFO" "gentle-ai" "next_steps" "gentle-ai post-install hints" 0
|
|
729
986
|
cat <<'HINT' | tee -a "${HUMAN_LOG}"
|
|
730
987
|
gentle-ai next steps (run yourself, per project):
|
|
731
988
|
1) Set your API keys
|
|
732
989
|
2) Run your selected agent
|
|
733
|
-
3) Try: /sdd-new my-feature (in pi: /gentle-ai:status, /gentleman:models)
|
|
990
|
+
3) Try: /sdd-new my-feature (in pi: /gentle-ai:status, /gentleman:models, /mcp)
|
|
734
991
|
GGA (per project):
|
|
735
992
|
gga init # inside each repo
|
|
736
993
|
gga install
|
|
737
994
|
HINT
|
|
738
995
|
}
|
|
739
996
|
|
|
740
|
-
# --- engram (pi persistent memory) ------------------------------------------
|
|
741
|
-
# The gentle-engram pi extension auto-starts `engram serve`, so the Engram Go
|
|
742
|
-
# binary MUST be on PATH first — otherwise the extension loads but silently
|
|
743
|
-
# fails (the "engram doesn't work in pi" symptom). Install binary, then the pi
|
|
744
|
-
# packages, then init, then the user restarts pi.
|
|
745
|
-
mod_engram() {
|
|
746
|
-
section "Engram memory (pi)"
|
|
747
|
-
|
|
748
|
-
# 1. Engram binary (Go).
|
|
749
|
-
if ! command -v engram >/dev/null 2>&1; then
|
|
750
|
-
if [[ "${OS_FAMILY}" == "macos" ]]; then
|
|
751
|
-
run_optional "engram" brew install gentleman-programming/tap/engram
|
|
752
|
-
fi
|
|
753
|
-
if ! command -v engram >/dev/null 2>&1 && command -v go >/dev/null 2>&1; then
|
|
754
|
-
run_optional "engram" go install github.com/Gentleman-Programming/engram/cmd/engram@latest
|
|
755
|
-
fi
|
|
756
|
-
fi
|
|
757
|
-
# go installs into $GOPATH/bin (default ~/go/bin), which may not be on PATH yet.
|
|
758
|
-
[[ -x "${HOME}/go/bin/engram" ]] && export PATH="${HOME}/go/bin:${PATH}"
|
|
759
|
-
|
|
760
|
-
if ! command -v engram >/dev/null 2>&1; then
|
|
761
|
-
log_event "WARN" "engram" "binary_missing" \
|
|
762
|
-
"engram binary not found after install; the pi extension needs it on PATH" 1
|
|
763
|
-
fi
|
|
764
|
-
|
|
765
|
-
# 2. pi integration. `pi-engram init` is the single source of truth: it adds
|
|
766
|
-
# the pinned gentle-engram to settings.json and wires the Engram MCP server
|
|
767
|
-
# in mcp.json. Do NOT also `pi install npm:gentle-engram` — that adds a
|
|
768
|
-
# second, unversioned entry and creates a duplicate on every run.
|
|
769
|
-
if command -v pi >/dev/null 2>&1; then
|
|
770
|
-
run_optional "engram" pi install npm:pi-mcp-adapter
|
|
771
|
-
run_optional "engram" npm exec --yes --package gentle-engram@latest -- pi-engram init
|
|
772
|
-
log_event "INFO" "engram" "enabled" \
|
|
773
|
-
"Engram enabled — RESTART pi, then verify with mem_current_project / mem_doctor / 'engram tui'" 0
|
|
774
|
-
else
|
|
775
|
-
log_event "WARN" "engram" "pi_missing" "pi not found; Engram pi integration skipped"
|
|
776
|
-
fi
|
|
777
|
-
}
|
|
778
|
-
|
|
779
997
|
# --- codex ------------------------------------------------------------------
|
|
780
998
|
mod_codex() {
|
|
781
999
|
section "Codex CLI"
|
|
@@ -784,13 +1002,18 @@ mod_codex() {
|
|
|
784
1002
|
return
|
|
785
1003
|
fi
|
|
786
1004
|
if [[ "${OS_FAMILY}" == "macos" ]] && command -v brew >/dev/null 2>&1; then
|
|
787
|
-
|
|
1005
|
+
run_cmd "codex" brew install --cask codex
|
|
788
1006
|
fi
|
|
789
1007
|
if ! command -v codex >/dev/null 2>&1; then
|
|
790
1008
|
local installer="${TMP_DIR}/install-codex.sh"
|
|
791
|
-
|
|
792
|
-
[[ -s "${installer}" ]]
|
|
1009
|
+
run_cmd "codex" curl -fsSL https://chatgpt.com/codex/install.sh -o "${installer}"
|
|
1010
|
+
[[ -s "${installer}" ]] || {
|
|
1011
|
+
log_event "ERROR" "codex" "installer_missing" "codex installer is empty" 1 "path=${installer}"
|
|
1012
|
+
return 1
|
|
1013
|
+
}
|
|
1014
|
+
run_cmd "codex" sh "${installer}"
|
|
793
1015
|
fi
|
|
1016
|
+
require_command codex
|
|
794
1017
|
}
|
|
795
1018
|
|
|
796
1019
|
# --- antigravity ------------------------------------------------------------
|
|
@@ -801,8 +1024,13 @@ mod_antigravity() {
|
|
|
801
1024
|
return
|
|
802
1025
|
fi
|
|
803
1026
|
local installer="${TMP_DIR}/install-antigravity.sh"
|
|
804
|
-
|
|
805
|
-
[[ -s "${installer}" ]]
|
|
1027
|
+
run_cmd "antigravity" curl -fsSL https://antigravity.google/cli/install.sh -o "${installer}"
|
|
1028
|
+
[[ -s "${installer}" ]] || {
|
|
1029
|
+
log_event "ERROR" "antigravity" "installer_missing" "Antigravity installer is empty" 1 "path=${installer}"
|
|
1030
|
+
return 1
|
|
1031
|
+
}
|
|
1032
|
+
run_cmd "antigravity" bash "${installer}"
|
|
1033
|
+
require_command agy
|
|
806
1034
|
}
|
|
807
1035
|
|
|
808
1036
|
# --- opencode ---------------------------------------------------------------
|
|
@@ -813,12 +1041,17 @@ mod_opencode() {
|
|
|
813
1041
|
return
|
|
814
1042
|
fi
|
|
815
1043
|
if [[ "${OS_FAMILY}" == "macos" ]]; then
|
|
816
|
-
|
|
1044
|
+
run_cmd "opencode" brew install anomalyco/tap/opencode
|
|
817
1045
|
else
|
|
818
1046
|
local installer="${TMP_DIR}/install-opencode.sh"
|
|
819
|
-
|
|
820
|
-
[[ -s "${installer}" ]]
|
|
1047
|
+
run_cmd "opencode" curl -fsSL https://opencode.ai/install -o "${installer}"
|
|
1048
|
+
[[ -s "${installer}" ]] || {
|
|
1049
|
+
log_event "ERROR" "opencode" "installer_missing" "opencode installer is empty" 1 "path=${installer}"
|
|
1050
|
+
return 1
|
|
1051
|
+
}
|
|
1052
|
+
run_cmd "opencode" bash "${installer}"
|
|
821
1053
|
fi
|
|
1054
|
+
require_command opencode
|
|
822
1055
|
|
|
823
1056
|
log_event "INFO" "opencode" "zen_hint" "OpenCode Go / Zen provider hint" 0
|
|
824
1057
|
cat <<'HINT' | tee -a "${HUMAN_LOG}"
|
|
@@ -864,8 +1097,11 @@ mod_cockpit() {
|
|
|
864
1097
|
esac
|
|
865
1098
|
|
|
866
1099
|
local url
|
|
867
|
-
url="$(grep -oE '"browser_download_url":[[:space:]]*"[^"]+"' "${release_json}" \
|
|
868
|
-
| cut -d '"' -f4 | grep -iE "${asset_pat}" |
|
|
1100
|
+
if ! url="$(grep -oE '"browser_download_url":[[:space:]]*"[^"]+"' "${release_json}" \
|
|
1101
|
+
| cut -d '"' -f4 | grep -iE "${asset_pat}" | sed -n '1p')"; then
|
|
1102
|
+
log_event "WARN" "cockpit" "asset_parse_failed" "Could not parse cockpit-tools release assets"
|
|
1103
|
+
return 0
|
|
1104
|
+
fi
|
|
869
1105
|
|
|
870
1106
|
if [[ -z "${url}" ]]; then
|
|
871
1107
|
log_event "WARN" "cockpit" "no_matching_asset" \
|
|
@@ -875,7 +1111,10 @@ mod_cockpit() {
|
|
|
875
1111
|
|
|
876
1112
|
local installer="${TMP_DIR}/cockpit-asset"
|
|
877
1113
|
run_optional "cockpit" curl -fsSL "${url}" -o "${installer}"
|
|
878
|
-
[[ -s "${installer}" ]]
|
|
1114
|
+
if [[ ! -s "${installer}" ]]; then
|
|
1115
|
+
log_event "WARN" "cockpit" "download_unavailable" "Could not download the selected cockpit-tools asset"
|
|
1116
|
+
return 0
|
|
1117
|
+
fi
|
|
879
1118
|
|
|
880
1119
|
case "${PM}" in
|
|
881
1120
|
apt-get) run_optional "cockpit" sudo apt-get install -y "${installer}" ;;
|
|
@@ -890,6 +1129,70 @@ mod_cockpit() {
|
|
|
890
1129
|
esac
|
|
891
1130
|
}
|
|
892
1131
|
|
|
1132
|
+
# --- rotator (opt-in: tuxevil-rotator multi-account gateway) -----------------
|
|
1133
|
+
mod_rotator() {
|
|
1134
|
+
section "tuxevil-rotator gateway"
|
|
1135
|
+
# Detect a desktop cockpit-tools data dir by marker file only; never read tokens.
|
|
1136
|
+
local -a candidates=(
|
|
1137
|
+
"${HOME}/.antigravity_cockpit"
|
|
1138
|
+
"${HOME}/.local/share/cockpit-tools"
|
|
1139
|
+
"${HOME}/.config/cockpit-tools"
|
|
1140
|
+
"${HOME}/.wizard-ai/cockpit-tools"
|
|
1141
|
+
"${HOME}/Library/Application Support/cockpit-tools"
|
|
1142
|
+
)
|
|
1143
|
+
# Windows-only locations, appended only when set (mirrors the ps1 behavior).
|
|
1144
|
+
[[ -n "${APPDATA:-}" ]] && candidates+=("${APPDATA}/cockpit-tools")
|
|
1145
|
+
[[ -n "${LOCALAPPDATA:-}" ]] && candidates+=("${LOCALAPPDATA}/cockpit-tools")
|
|
1146
|
+
local dir cockpit_dir=""
|
|
1147
|
+
for dir in "${candidates[@]}"; do
|
|
1148
|
+
if [[ -f "${dir}/accounts.json" || -f "${dir}/account-token.key" ]]; then
|
|
1149
|
+
cockpit_dir="${dir}"; break
|
|
1150
|
+
fi
|
|
1151
|
+
done
|
|
1152
|
+
if [[ -n "${cockpit_dir}" ]]; then
|
|
1153
|
+
log_event "INFO" "rotator" "cockpit_detected" "cockpit-tools data directory detected" 0 "dir=${cockpit_dir}"
|
|
1154
|
+
else
|
|
1155
|
+
log_event "INFO" "rotator" "cockpit_absent" "No cockpit-tools data directory detected; the rotator can still use its own accounts" 0
|
|
1156
|
+
fi
|
|
1157
|
+
# Non-fatal health probe.
|
|
1158
|
+
local gw="http://localhost:51200/v1/models" body count
|
|
1159
|
+
if body="$(curl -fsS -m 5 -H 'Authorization: Bearer tuxevil' "${gw}" 2>/dev/null)"; then
|
|
1160
|
+
# curl -fsS already proved reachability; the count is informational only
|
|
1161
|
+
# (awk always exits 0, so no operational failure is masked here).
|
|
1162
|
+
count="$(printf '%s' "${body}" | awk '{c+=gsub(/"id"/,"&")} END{print c+0}')"
|
|
1163
|
+
log_event "INFO" "rotator" "gateway_up" "tuxevil-rotator gateway is reachable" 0 "url=${gw};models=${count}"
|
|
1164
|
+
else
|
|
1165
|
+
log_event "INFO" "rotator" "gateway_down" "tuxevil-rotator gateway not reachable; start it with 'tuxevil-rotator start'" 0 "url=${gw}"
|
|
1166
|
+
fi
|
|
1167
|
+
# Install the CLI idempotently. Never runs login/start or writes secrets.
|
|
1168
|
+
if command -v tuxevil-rotator >/dev/null 2>&1; then
|
|
1169
|
+
log_event "INFO" "rotator" "already_present" "tuxevil-rotator already installed" 0
|
|
1170
|
+
else
|
|
1171
|
+
run_cmd "rotator" npm install --global tuxevil-rotator
|
|
1172
|
+
require_command tuxevil-rotator
|
|
1173
|
+
fi
|
|
1174
|
+
if command -v pi >/dev/null 2>&1; then
|
|
1175
|
+
run_cmd "rotator" pi install "github:darkrei08/pi-cockpit-tools-sync"
|
|
1176
|
+
local pi_settings="${PI_AGENT_DIR}/settings.json"
|
|
1177
|
+
if [[ ! -f "${pi_settings}" ]] || ! grep -Fq 'github:darkrei08/pi-cockpit-tools-sync' "${pi_settings}"; then
|
|
1178
|
+
log_event "ERROR" "rotator" "pi_extension_missing" "Pi did not register cockpit sync extension" 1 "expected=${pi_settings}"
|
|
1179
|
+
return 1
|
|
1180
|
+
fi
|
|
1181
|
+
log_event "INFO" "rotator" "pi_extension_verified" "Cockpit sync extension registered in Pi" 0 "path=${pi_settings}"
|
|
1182
|
+
else
|
|
1183
|
+
log_event "INFO" "rotator" "pi_extension_skipped" "pi not found; cockpit sync extension was not installed" 0
|
|
1184
|
+
fi
|
|
1185
|
+
cat <<'HINT' | tee -a "${HUMAN_LOG}"
|
|
1186
|
+
tuxevil-rotator installed. To use the multi-account Gemini/Antigravity gateway:
|
|
1187
|
+
tuxevil-rotator login # add a Google Antigravity account (repeat to add more)
|
|
1188
|
+
tuxevil-rotator import # or bulk-import accounts from a cockpit-tools JSON
|
|
1189
|
+
tuxevil-rotator start # start the rotating proxy on http://localhost:51200
|
|
1190
|
+
Pi reaches it through the 'tuxevil-rotator' provider configured in your dotenv.
|
|
1191
|
+
The cockpit sync extension provides /cockpit-sync, /cockpit-provision, and /cockpit-proxy.
|
|
1192
|
+
Login/start are never run by setup-ai and no tokens are read or stored.
|
|
1193
|
+
HINT
|
|
1194
|
+
}
|
|
1195
|
+
|
|
893
1196
|
# ==============================================================================
|
|
894
1197
|
# Shell environment
|
|
895
1198
|
# ==============================================================================
|
|
@@ -930,17 +1233,75 @@ quality_gates() {
|
|
|
930
1233
|
run_cmd "quality" bash -n "${BASH_SOURCE[0]}"
|
|
931
1234
|
|
|
932
1235
|
is_selected node && { run_cmd "quality" node --version; run_cmd "quality" npm --version; }
|
|
933
|
-
is_selected bun &&
|
|
934
|
-
is_selected pi
|
|
1236
|
+
is_selected bun && run_cmd "quality" bun --version
|
|
1237
|
+
if is_selected pi; then
|
|
1238
|
+
require_command pi
|
|
1239
|
+
run_cmd "quality" pi --no-extensions --version
|
|
1240
|
+
fi
|
|
1241
|
+
|
|
1242
|
+
if is_selected node || is_selected pi-workflows; then
|
|
1243
|
+
assert_node_minimum
|
|
1244
|
+
fi
|
|
935
1245
|
|
|
936
1246
|
if is_selected pi-workflows; then
|
|
1247
|
+
local workflow_pkg="${PI_EXTENSIONS_DIR}/node_modules/pi-extensible-workflows/package.json"
|
|
1248
|
+
if [[ ! -f "${workflow_pkg}" ]]; then
|
|
1249
|
+
log_event "ERROR" "quality" "workflow_package_missing" \
|
|
1250
|
+
"pi-extensible-workflows package.json missing from extensions dir" 1 \
|
|
1251
|
+
"expected_path=${workflow_pkg}"
|
|
1252
|
+
exit 1
|
|
1253
|
+
fi
|
|
937
1254
|
run_cmd "quality" node -e \
|
|
938
|
-
'const
|
|
939
|
-
"${
|
|
1255
|
+
'const fs=require("fs"); const path=process.argv[1]; const pkg=JSON.parse(fs.readFileSync(path,"utf8")); console.log("VERSION="+pkg.version)' \
|
|
1256
|
+
"${workflow_pkg}"
|
|
940
1257
|
fi
|
|
941
1258
|
|
|
942
|
-
|
|
943
|
-
|
|
1259
|
+
local -a target_agents=()
|
|
1260
|
+
local agent sk
|
|
1261
|
+
for agent in pi claude-code gemini-cli cursor antigravity codex opencode; do
|
|
1262
|
+
[[ -d "$(agent_config_dir "${agent}")" ]] && target_agents+=("${agent}")
|
|
1263
|
+
done
|
|
1264
|
+
(( ${#target_agents[@]} == 0 )) && target_agents=(pi)
|
|
1265
|
+
|
|
1266
|
+
if is_selected ee; then
|
|
1267
|
+
verify_skill_for_agents "quality" "${ENGINEERING_EXCELLENCE_SKILL}" "${target_agents[@]}"
|
|
1268
|
+
log_event "INFO" "quality" "ee_gate_passed" \
|
|
1269
|
+
"Engineering Excellence skill verified for every targeted agent" 0
|
|
1270
|
+
fi
|
|
1271
|
+
|
|
1272
|
+
if is_selected skills; then
|
|
1273
|
+
for sk in "${UPSTREAM_SKILL_NAMES[@]}"; do
|
|
1274
|
+
verify_skill_for_agents "quality" "${sk}" "${target_agents[@]}"
|
|
1275
|
+
done
|
|
1276
|
+
log_event "INFO" "quality" "skills_gate_passed" \
|
|
1277
|
+
"All upstream skills verified for every targeted agent" 0
|
|
1278
|
+
fi
|
|
1279
|
+
|
|
1280
|
+
if is_selected gentle-ai; then
|
|
1281
|
+
# The module requires the gentle-ai CLI specifically; a legacy standalone
|
|
1282
|
+
# gga cannot prove the per-agent/MCP configuration ran.
|
|
1283
|
+
if ! command -v gentle-ai >/dev/null 2>&1; then
|
|
1284
|
+
log_event "ERROR" "quality" "gentle_ai_missing" \
|
|
1285
|
+
"gentle-ai CLI not found on PATH after install" 1
|
|
1286
|
+
exit 1
|
|
1287
|
+
fi
|
|
1288
|
+
if command -v pi >/dev/null 2>&1; then
|
|
1289
|
+
local gentle_settings="${PI_AGENT_DIR}/settings.json"
|
|
1290
|
+
if [[ ! -f "${gentle_settings}" ]] \
|
|
1291
|
+
|| ! grep -q '"npm:gentle-pi"' "${gentle_settings}" \
|
|
1292
|
+
|| ! grep -q '"npm:pi-mcp-adapter"' "${gentle_settings}"; then
|
|
1293
|
+
log_event "ERROR" "quality" "gentle_pi_missing" \
|
|
1294
|
+
"gentle-pi and/or pi-mcp-adapter not registered in pi settings" 1 "expected=${gentle_settings}"
|
|
1295
|
+
exit 1
|
|
1296
|
+
fi
|
|
1297
|
+
log_event "INFO" "quality" "gentle_ai_gate_passed" \
|
|
1298
|
+
"gentle-ai verified (CLI present; gentle-pi + pi-mcp-adapter registered in pi)" 0
|
|
1299
|
+
else
|
|
1300
|
+
# No pi on PATH: pi MCP wiring is genuinely not applicable, so do not
|
|
1301
|
+
# claim it was registered.
|
|
1302
|
+
log_event "INFO" "quality" "gentle_ai_gate_passed" \
|
|
1303
|
+
"gentle-ai CLI verified; pi not present, so pi MCP wiring was not required" 0
|
|
1304
|
+
fi
|
|
944
1305
|
fi
|
|
945
1306
|
|
|
946
1307
|
log_event "INFO" "quality" "gates_done" "Quality gates completed for selected modules"
|
|
@@ -965,8 +1326,8 @@ print_list() {
|
|
|
965
1326
|
printf 'AI Dev Suite %s — modules (core = installed by default):\n\n' "${SCRIPT_VERSION}"
|
|
966
1327
|
local m tag
|
|
967
1328
|
for m in "${MODULE_ORDER[@]}"; do
|
|
968
|
-
if
|
|
969
|
-
printf ' [%s] %-14s %s\n' "${tag}" "${m}" "${
|
|
1329
|
+
if module_is_optional "${m}"; then tag="optional"; else tag="core "; fi
|
|
1330
|
+
printf ' [%s] %-14s %s\n' "${tag}" "${m}" "$(module_desc "${m}")"
|
|
970
1331
|
done
|
|
971
1332
|
printf '\nUse: --only <csv> | --all | (default = core)\n'
|
|
972
1333
|
}
|
|
@@ -978,11 +1339,14 @@ print_help() {
|
|
|
978
1339
|
}
|
|
979
1340
|
|
|
980
1341
|
parse_args() {
|
|
981
|
-
local mode="default" only_csv=""
|
|
1342
|
+
local mode="default" only_csv="" all_requested=0
|
|
982
1343
|
while [[ $# -gt 0 ]]; do
|
|
983
1344
|
case "$1" in
|
|
984
|
-
--all) mode="all"; shift ;;
|
|
985
|
-
--only)
|
|
1345
|
+
--all) mode="all"; all_requested=1; shift ;;
|
|
1346
|
+
--only)
|
|
1347
|
+
[[ $# -ge 2 ]] || { printf '%s\n' '--only requires a non-empty comma-separated module list.' >&2; exit 2; }
|
|
1348
|
+
mode="only"; only_csv="$2"; shift 2
|
|
1349
|
+
;;
|
|
986
1350
|
--only=*) mode="only"; only_csv="${1#*=}"; shift ;;
|
|
987
1351
|
--yes|-y) shift ;; # accepted for launcher parity
|
|
988
1352
|
--list) print_list; exit 0 ;;
|
|
@@ -991,26 +1355,37 @@ parse_args() {
|
|
|
991
1355
|
esac
|
|
992
1356
|
done
|
|
993
1357
|
|
|
1358
|
+
# Match PowerShell: -All wins whenever it is present, regardless of
|
|
1359
|
+
# where it appears relative to --only.
|
|
1360
|
+
(( all_requested == 1 )) && mode="all"
|
|
1361
|
+
|
|
994
1362
|
local requested=()
|
|
995
1363
|
case "${mode}" in
|
|
996
1364
|
all) requested=("${MODULE_ORDER[@]}") ;;
|
|
997
1365
|
only)
|
|
998
|
-
|
|
999
|
-
|
|
1000
|
-
|
|
1001
|
-
IFS="${IFS_SAVE}"
|
|
1366
|
+
# Split on commas without word-splitting or glob expansion.
|
|
1367
|
+
local raw=()
|
|
1368
|
+
IFS=',' read -r -a raw <<< "${only_csv}"
|
|
1002
1369
|
local r
|
|
1003
1370
|
for r in "${raw[@]}"; do
|
|
1004
|
-
|
|
1371
|
+
# Trim only surrounding whitespace (match PowerShell .Trim());
|
|
1372
|
+
# internal whitespace makes the token invalid instead of being
|
|
1373
|
+
# silently collapsed (e.g. "co dex" must not become "codex").
|
|
1374
|
+
r="${r#"${r%%[![:space:]]*}"}"
|
|
1375
|
+
r="${r%"${r##*[![:space:]]}"}"
|
|
1005
1376
|
[[ -z "${r}" ]] && continue
|
|
1006
|
-
|
|
1377
|
+
module_desc "${r}" >/dev/null || { printf 'Unknown module: %s\n' "${r}" >&2; exit 2; }
|
|
1007
1378
|
requested+=("${r}")
|
|
1008
1379
|
done
|
|
1380
|
+
(( ${#requested[@]} > 0 )) || {
|
|
1381
|
+
printf '%s\n' '--only requires a non-empty comma-separated module list.' >&2
|
|
1382
|
+
exit 2
|
|
1383
|
+
}
|
|
1009
1384
|
;;
|
|
1010
1385
|
default)
|
|
1011
1386
|
local m
|
|
1012
1387
|
for m in "${MODULE_ORDER[@]}"; do
|
|
1013
|
-
|
|
1388
|
+
module_is_optional "${m}" && continue
|
|
1014
1389
|
requested+=("${m}")
|
|
1015
1390
|
done
|
|
1016
1391
|
;;
|
|
@@ -1053,10 +1428,15 @@ parse_args "$@"
|
|
|
1053
1428
|
|
|
1054
1429
|
section "Preflight"
|
|
1055
1430
|
require_command bash
|
|
1056
|
-
require_command curl
|
|
1057
|
-
require_command git
|
|
1058
1431
|
detect_os
|
|
1059
1432
|
|
|
1433
|
+
# The base module owns bootstrap tools on a clean machine. For explicit
|
|
1434
|
+
# selections that skip base, fail early because later installers need them.
|
|
1435
|
+
if ! is_selected base; then
|
|
1436
|
+
require_command curl
|
|
1437
|
+
require_command git
|
|
1438
|
+
fi
|
|
1439
|
+
|
|
1060
1440
|
if [[ "${OS_FAMILY}" == "linux" ]]; then
|
|
1061
1441
|
require_command sudo
|
|
1062
1442
|
fi
|
|
@@ -1073,6 +1453,11 @@ quality_gates
|
|
|
1073
1453
|
|
|
1074
1454
|
write_report "SUCCESS" 0 "n/a" "n/a" "n/a" "n/a"
|
|
1075
1455
|
|
|
1456
|
+
report_version REPORT_NODE_VERSION node --version
|
|
1457
|
+
report_version REPORT_NPM_VERSION npm --version
|
|
1458
|
+
report_version REPORT_BUN_VERSION bun --version
|
|
1459
|
+
report_version REPORT_GO_VERSION go version
|
|
1460
|
+
|
|
1076
1461
|
cat >> "${REPORT_FILE}" <<EOF
|
|
1077
1462
|
|
|
1078
1463
|
## Installed (selected modules)
|
|
@@ -1081,11 +1466,11 @@ ${SELECTED_DISPLAY}
|
|
|
1081
1466
|
|
|
1082
1467
|
## Versions
|
|
1083
1468
|
|
|
1084
|
-
- Node.js: \`$
|
|
1085
|
-
- npm: \`$
|
|
1086
|
-
- Bun: \`$
|
|
1469
|
+
- Node.js: \`${REPORT_NODE_VERSION}\`
|
|
1470
|
+
- npm: \`${REPORT_NPM_VERSION}\`
|
|
1471
|
+
- Bun: \`${REPORT_BUN_VERSION}\`
|
|
1087
1472
|
- Pi: \`${PI_VERSION:-n/a}\`
|
|
1088
|
-
- Go: \`$
|
|
1473
|
+
- Go: \`${REPORT_GO_VERSION}\`
|
|
1089
1474
|
EOF
|
|
1090
1475
|
|
|
1091
1476
|
log_event "INFO" "bootstrap" "completed" "AI Dev Suite setup completed successfully" 0 \
|