@darkrei08/setup-ai 3.0.5 → 3.2.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 +47 -23
- package/package.json +4 -3
- package/setup-ai.ps1 +509 -123
- package/setup-ai.sh +508 -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.2.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.2.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,32 @@ 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)
|
|
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
|
+
*) return 1 ;;
|
|
328
|
+
esac
|
|
329
|
+
}
|
|
319
330
|
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
)
|
|
331
|
+
module_is_optional() {
|
|
332
|
+
[[ "$1" == "cockpit" ]]
|
|
333
|
+
}
|
|
324
334
|
|
|
325
335
|
# ------------------------------------------------------------------------------
|
|
326
336
|
# OS / package-manager detection
|
|
@@ -444,7 +454,7 @@ mod_node() {
|
|
|
444
454
|
# nvm refuses to operate when npm_config_prefix is set (e.g. to /usr/local),
|
|
445
455
|
# aborting with "nvm is not compatible with the npm_config_prefix ...".
|
|
446
456
|
# 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 ||
|
|
457
|
+
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
458
|
|
|
449
459
|
# shellcheck disable=SC1090
|
|
450
460
|
source "${NVM_DIR}/nvm.sh"
|
|
@@ -453,13 +463,7 @@ mod_node() {
|
|
|
453
463
|
run_cmd "node" nvm alias default 22
|
|
454
464
|
run_cmd "node" nvm use 22
|
|
455
465
|
|
|
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
|
|
466
|
+
assert_node_minimum
|
|
463
467
|
|
|
464
468
|
# npm@latest (NOT npm@12 — that version does not exist).
|
|
465
469
|
run_cmd "node" npm install -g npm@latest
|
|
@@ -474,6 +478,28 @@ NODE
|
|
|
474
478
|
run_optional "node" sudo ln -sf "${npm_bin}" /usr/local/bin/npm
|
|
475
479
|
}
|
|
476
480
|
|
|
481
|
+
assert_node_minimum() {
|
|
482
|
+
require_command node
|
|
483
|
+
|
|
484
|
+
local node_version major minor
|
|
485
|
+
capture_cmd node_version "node" node --version
|
|
486
|
+
if [[ "${node_version}" =~ ^v([0-9]+)\.([0-9]+) ]]; then
|
|
487
|
+
major="${BASH_REMATCH[1]}"
|
|
488
|
+
minor="${BASH_REMATCH[2]}"
|
|
489
|
+
else
|
|
490
|
+
log_event "ERROR" "node" "version_invalid" "Could not parse Node.js version" 1 "version=${node_version}"
|
|
491
|
+
return 1
|
|
492
|
+
fi
|
|
493
|
+
|
|
494
|
+
if (( major < 22 || (major == 22 && minor < 19) )); then
|
|
495
|
+
log_event "ERROR" "node" "version_unsupported" \
|
|
496
|
+
"Node.js ${node_version} is too old; pi-extensible-workflows needs >= 22.19" 1
|
|
497
|
+
return 1
|
|
498
|
+
fi
|
|
499
|
+
log_event "INFO" "node" "runtime_validated" "Node.js version satisfies workflow requirement" 0 \
|
|
500
|
+
"version=${node_version};minimum=22.19"
|
|
501
|
+
}
|
|
502
|
+
|
|
477
503
|
# --- bun --------------------------------------------------------------------
|
|
478
504
|
mod_bun() {
|
|
479
505
|
section "Bun"
|
|
@@ -485,7 +511,12 @@ mod_bun() {
|
|
|
485
511
|
fi
|
|
486
512
|
export PATH="${BUN_INSTALL}/bin:${PATH}"
|
|
487
513
|
require_command bun
|
|
488
|
-
|
|
514
|
+
local bun_version
|
|
515
|
+
if capture_cmd bun_version "bun" bun --version; then
|
|
516
|
+
log_event "INFO" "bun" "runtime_ready" "Bun runtime validated" 0 "version=${bun_version}"
|
|
517
|
+
else
|
|
518
|
+
return 1
|
|
519
|
+
fi
|
|
489
520
|
}
|
|
490
521
|
|
|
491
522
|
# --- pi ---------------------------------------------------------------------
|
|
@@ -499,7 +530,10 @@ mod_pi() {
|
|
|
499
530
|
fi
|
|
500
531
|
export PATH="${HOME}/.pi/bin:${HOME}/.local/bin:${PATH}"
|
|
501
532
|
require_command pi
|
|
502
|
-
PI_VERSION="$(pi --version 2>/dev/null
|
|
533
|
+
if ! PI_VERSION="$(pi --version 2>/dev/null)"; then
|
|
534
|
+
PI_VERSION="unknown"
|
|
535
|
+
log_event "WARN" "pi" "version_unavailable" "Could not read pi --version" 0
|
|
536
|
+
fi
|
|
503
537
|
log_event "INFO" "pi" "cli_ready" "Pi CLI detected" 0 "version=${PI_VERSION}"
|
|
504
538
|
mkdir -p "${PI_AGENT_DIR}" "${PI_EXTENSIONS_DIR}" "${PI_NPM_DIR}" "${PI_AGENT_DIR}/skills"
|
|
505
539
|
}
|
|
@@ -508,8 +542,12 @@ mod_pi() {
|
|
|
508
542
|
mod_go() {
|
|
509
543
|
section "Go toolchain"
|
|
510
544
|
if command -v go >/dev/null 2>&1; then
|
|
511
|
-
|
|
512
|
-
|
|
545
|
+
local go_version
|
|
546
|
+
if capture_cmd go_version "go" go version; then
|
|
547
|
+
log_event "INFO" "go" "already_present" "Go already installed" 0 "version=${go_version}"
|
|
548
|
+
return
|
|
549
|
+
fi
|
|
550
|
+
return 1
|
|
513
551
|
fi
|
|
514
552
|
if [[ "${OS_FAMILY}" == "macos" ]]; then
|
|
515
553
|
run_cmd "go" brew install go
|
|
@@ -542,27 +580,37 @@ mod_dotenv() {
|
|
|
542
580
|
fi
|
|
543
581
|
|
|
544
582
|
# Idempotent upstream-quirk patches (no-ops once upstream merges the fixes).
|
|
545
|
-
if
|
|
546
|
-
|
|
583
|
+
if grep_probe "dotenv" "${TMP_DIR}/monokai_hits" \
|
|
584
|
+
-RIl --exclude-dir=.git "gthelding/monokai-pro.nvim" "${DOTENV_DIR}"; then
|
|
547
585
|
while IFS= read -r file; do
|
|
548
|
-
sed -i 's|gthelding/monokai-pro.nvim|loctvl842/monokai-pro.nvim|g' "${file}"
|
|
586
|
+
run_cmd "dotenv" sed -i 's|gthelding/monokai-pro.nvim|loctvl842/monokai-pro.nvim|g' "${file}"
|
|
549
587
|
log_event "INFO" "dotenv" "reference_patched" "Updated stale monokai-pro reference" 0 "file=${file}"
|
|
550
588
|
done < "${TMP_DIR}/monokai_hits"
|
|
551
589
|
fi
|
|
552
590
|
|
|
553
|
-
if
|
|
554
|
-
|
|
591
|
+
if grep_probe "dotenv" "${TMP_DIR}/sudo_npm_hits" \
|
|
592
|
+
-RIl --exclude-dir=.git 'sudo npm install -g --prefix /usr/local bun' "${DOTENV_DIR}"; then
|
|
555
593
|
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}"
|
|
594
|
+
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
595
|
log_event "INFO" "dotenv" "reference_patched" "Patched sudo npm call to absolute path" 0 "file=${file}"
|
|
558
596
|
done < "${TMP_DIR}/sudo_npm_hits"
|
|
559
597
|
fi
|
|
560
598
|
|
|
561
|
-
if
|
|
562
|
-
|
|
599
|
+
if grep_probe "dotenv" "${TMP_DIR}/treesitter_hits" \
|
|
600
|
+
-RIl --exclude-dir=.git -e 'npm install -g --prefix "\$HOME/.local" tree-sitter-cli' "${DOTENV_DIR}"; then
|
|
563
601
|
while IFS= read -r file; do
|
|
564
|
-
|
|
565
|
-
|
|
602
|
+
local marker_rc=0
|
|
603
|
+
grep -q 'AI_DEV_TS_CLI_PATCH' "${file}" 2>>"${HUMAN_LOG}" || marker_rc=$?
|
|
604
|
+
case "${marker_rc}" in
|
|
605
|
+
0) continue ;; # already patched
|
|
606
|
+
1) ;; # not patched yet; apply below
|
|
607
|
+
*)
|
|
608
|
+
log_event "ERROR" "dotenv" "marker_probe_failed" \
|
|
609
|
+
"grep marker probe failed with an operational error" "${marker_rc}" "file=${file}"
|
|
610
|
+
exit "${marker_rc}"
|
|
611
|
+
;;
|
|
612
|
+
esac
|
|
613
|
+
run_cmd "dotenv" python3 - "${file}" <<'PYPATCH'
|
|
566
614
|
import sys
|
|
567
615
|
path = sys.argv[1]
|
|
568
616
|
with open(path, "r", newline="") as fh:
|
|
@@ -591,10 +639,55 @@ PYPATCH
|
|
|
591
639
|
if [[ -x "${DOTENV_DIR}/setup_env.sh" ]]; then
|
|
592
640
|
run_cmd "dotenv" bash "${DOTENV_DIR}/setup_env.sh"
|
|
593
641
|
else
|
|
594
|
-
log_event "
|
|
642
|
+
log_event "ERROR" "dotenv" "setup_script_missing" \
|
|
643
|
+
"dotenv/setup_env.sh missing or not executable" 1 \
|
|
644
|
+
"expected=${DOTENV_DIR}/setup_env.sh"
|
|
645
|
+
return 1
|
|
595
646
|
fi
|
|
596
647
|
}
|
|
597
648
|
|
|
649
|
+
# --- skill target helpers ----------------------------------------------------
|
|
650
|
+
agent_config_dir() {
|
|
651
|
+
case "$1" in
|
|
652
|
+
pi) printf '%s\n' "${HOME}/.pi" ;;
|
|
653
|
+
claude-code) printf '%s\n' "${HOME}/.claude" ;;
|
|
654
|
+
gemini-cli) printf '%s\n' "${HOME}/.gemini" ;;
|
|
655
|
+
cursor) printf '%s\n' "${HOME}/.cursor" ;;
|
|
656
|
+
antigravity) printf '%s\n' "${HOME}/.antigravity" ;;
|
|
657
|
+
codex) printf '%s\n' "${HOME}/.codex" ;;
|
|
658
|
+
opencode) printf '%s\n' "${HOME}/.config/opencode" ;;
|
|
659
|
+
*) return 1 ;;
|
|
660
|
+
esac
|
|
661
|
+
}
|
|
662
|
+
|
|
663
|
+
agent_skill_root() {
|
|
664
|
+
case "$1" in
|
|
665
|
+
pi) printf '%s\n' "${PI_AGENT_DIR}/skills" ;;
|
|
666
|
+
claude-code) printf '%s\n' "${HOME}/.claude/skills" ;;
|
|
667
|
+
gemini-cli) printf '%s\n' "${HOME}/.gemini/skills" ;;
|
|
668
|
+
cursor) printf '%s\n' "${HOME}/.cursor/skills" ;;
|
|
669
|
+
antigravity) printf '%s\n' "${HOME}/.antigravity/skills" ;;
|
|
670
|
+
codex) printf '%s\n' "${HOME}/.codex/skills" ;;
|
|
671
|
+
opencode) printf '%s\n' "${HOME}/.config/opencode/skills" ;;
|
|
672
|
+
*) return 1 ;;
|
|
673
|
+
esac
|
|
674
|
+
}
|
|
675
|
+
|
|
676
|
+
verify_skill_for_agents() {
|
|
677
|
+
local phase="$1" skill="$2"; shift 2
|
|
678
|
+
local agent root
|
|
679
|
+
for agent in "$@"; do
|
|
680
|
+
root="$(agent_skill_root "${agent}")"
|
|
681
|
+
if [[ ! -f "${root}/${skill}/SKILL.md" ]]; then
|
|
682
|
+
log_event "ERROR" "${phase}" "skill_missing" \
|
|
683
|
+
"Skill SKILL.md missing for targeted agent" 1 \
|
|
684
|
+
"agent=${agent};skill=${skill};expected=${root}/${skill}/SKILL.md"
|
|
685
|
+
return 1
|
|
686
|
+
fi
|
|
687
|
+
done
|
|
688
|
+
return 0
|
|
689
|
+
}
|
|
690
|
+
|
|
598
691
|
# --- engineering-excellence -------------------------------------------------
|
|
599
692
|
mod_ee() {
|
|
600
693
|
section "Engineering Excellence"
|
|
@@ -604,41 +697,72 @@ mod_ee() {
|
|
|
604
697
|
# (replaces the old git-clone-and-move). Agents are detected by their config dir.
|
|
605
698
|
# Keys are the `skills` CLI agent names (claude-code, gemini-cli, ...), which
|
|
606
699
|
# 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
|
-
)
|
|
700
|
+
local agent
|
|
617
701
|
local installed_any=0
|
|
702
|
+
local -a target_agents=()
|
|
618
703
|
for agent in pi claude-code gemini-cli cursor antigravity codex opencode; do
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
run_optional "engineering-excellence" \
|
|
704
|
+
[[ -d "$(agent_config_dir "${agent}")" ]] || continue
|
|
705
|
+
run_cmd "engineering-excellence" \
|
|
622
706
|
npx --yes skills@latest add "${ENGINEERING_EXCELLENCE_SLUG}" \
|
|
623
707
|
--skill "${ENGINEERING_EXCELLENCE_SKILL}" --global --agent "${agent}" --copy --yes
|
|
708
|
+
target_agents+=("${agent}")
|
|
624
709
|
installed_any=1
|
|
625
710
|
done
|
|
626
711
|
|
|
627
712
|
if (( installed_any == 0 )); then
|
|
628
713
|
# No agent detected yet — install at least for pi (created by mod_pi).
|
|
629
|
-
|
|
714
|
+
run_cmd "engineering-excellence" \
|
|
630
715
|
npx --yes skills@latest add "${ENGINEERING_EXCELLENCE_SLUG}" \
|
|
631
716
|
--skill "${ENGINEERING_EXCELLENCE_SKILL}" --global --agent pi --copy --yes
|
|
717
|
+
target_agents=(pi)
|
|
632
718
|
fi
|
|
633
719
|
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
|
|
641
|
-
|
|
720
|
+
verify_skill_for_agents "engineering-excellence" "${ENGINEERING_EXCELLENCE_SKILL}" "${target_agents[@]}"
|
|
721
|
+
log_event "INFO" "engineering-excellence" "skills_verified" \
|
|
722
|
+
"Engineering Excellence skill verified for every targeted agent" 0 \
|
|
723
|
+
"agents=$(IFS=,; printf '%s' "${target_agents[*]}")"
|
|
724
|
+
}
|
|
725
|
+
|
|
726
|
+
# --- upstream agent skills --------------------------------------------------
|
|
727
|
+
# Installs vekexasia/dotenv's skill stack on every OS via `npx skills add`.
|
|
728
|
+
# On Linux the dotenv module may already install these; skills add --copy is
|
|
729
|
+
# idempotent, so a re-run is safe.
|
|
730
|
+
mod_skills() {
|
|
731
|
+
section "Agent skills (upstream stack)"
|
|
732
|
+
require_command npx
|
|
733
|
+
|
|
734
|
+
# Agent keys are the `skills` CLI names (claude-code, gemini-cli, ...),
|
|
735
|
+
# detected by their config dir — same mapping as mod_ee.
|
|
736
|
+
local agent
|
|
737
|
+
local -a agents=()
|
|
738
|
+
for agent in pi claude-code gemini-cli cursor antigravity codex opencode; do
|
|
739
|
+
[[ -d "$(agent_config_dir "${agent}")" ]] && agents+=("${agent}")
|
|
740
|
+
done
|
|
741
|
+
# pi is created by mod_pi; guarantee at least pi so the stack always lands.
|
|
742
|
+
(( ${#agents[@]} == 0 )) && agents=(pi)
|
|
743
|
+
|
|
744
|
+
local source_spec source skill_csv
|
|
745
|
+
local -a skill_list
|
|
746
|
+
for source_spec in "${UPSTREAM_SKILL_SOURCES[@]}"; do
|
|
747
|
+
source="${source_spec%% *}"
|
|
748
|
+
skill_csv="${source_spec#* }"
|
|
749
|
+
# The script keeps global IFS at newline/tab for safe line reads;
|
|
750
|
+
# explicitly restore spaces for this space-separated skill list.
|
|
751
|
+
IFS=' ' read -r -a skill_list <<< "${skill_csv}"
|
|
752
|
+
for agent in "${agents[@]}"; do
|
|
753
|
+
run_cmd "skills" \
|
|
754
|
+
npx --yes skills@latest add "${source}" \
|
|
755
|
+
--skill "${skill_list[@]}" --global --agent "${agent}" --copy --yes
|
|
756
|
+
done
|
|
757
|
+
done
|
|
758
|
+
|
|
759
|
+
local sk
|
|
760
|
+
for sk in "${UPSTREAM_SKILL_NAMES[@]}"; do
|
|
761
|
+
verify_skill_for_agents "skills" "${sk}" "${agents[@]}"
|
|
762
|
+
done
|
|
763
|
+
log_event "INFO" "skills" "skills_verified" \
|
|
764
|
+
"Upstream skills verified for every targeted agent" 0 \
|
|
765
|
+
"agents=$(IFS=,; printf '%s' "${agents[*]}")"
|
|
642
766
|
}
|
|
643
767
|
|
|
644
768
|
# --- pi-extensible-workflows ------------------------------------------------
|
|
@@ -646,6 +770,7 @@ mod_pi_workflows() {
|
|
|
646
770
|
section "pi-extensible-workflows"
|
|
647
771
|
require_command pi
|
|
648
772
|
require_command node
|
|
773
|
+
assert_node_minimum
|
|
649
774
|
|
|
650
775
|
if [[ -z "${PI_WORKFLOW_VERSION}" ]]; then
|
|
651
776
|
capture_cmd PI_WORKFLOW_VERSION "pi-workflows" npm view pi-extensible-workflows version
|
|
@@ -661,16 +786,33 @@ mod_pi_workflows() {
|
|
|
661
786
|
mkdir -p "${PI_EXTENSIONS_DIR}"
|
|
662
787
|
pushd "${PI_EXTENSIONS_DIR}" >/dev/null
|
|
663
788
|
printf '%s\n' 'ignore-scripts=false' > .npmrc
|
|
789
|
+
# Mark this directory as an npm project root so `npm install` lands HERE and
|
|
790
|
+
# cannot walk up the tree into an ancestor project. This matters when
|
|
791
|
+
# ~/.pi/agent is symlinked into another repo (e.g. the dotenv dotfiles):
|
|
792
|
+
# without a local package.json, npm would install into that repo's
|
|
793
|
+
# node_modules and verification would then pick up a stale, shadowing copy.
|
|
794
|
+
if [[ ! -f package.json ]]; then
|
|
795
|
+
printf '%s\n' '{"name":"pi-extensions","private":true}' > package.json
|
|
796
|
+
fi
|
|
664
797
|
run_cmd "pi-workflows-node" npm install --save-exact --no-audit --no-fund \
|
|
665
798
|
"pi-extensible-workflows@${PI_WORKFLOW_VERSION}"
|
|
666
799
|
popd >/dev/null
|
|
667
800
|
|
|
668
|
-
|
|
669
|
-
|
|
670
|
-
|
|
801
|
+
# Verify the version that actually landed in THIS directory by reading its
|
|
802
|
+
# local package.json directly. Do NOT use require.resolve here: it ascends
|
|
803
|
+
# the directory tree and can resolve a shadowing copy in an ancestor
|
|
804
|
+
# node_modules, producing a false version_mismatch.
|
|
805
|
+
local local_pkg="${PI_EXTENSIONS_DIR}/node_modules/pi-extensible-workflows/package.json"
|
|
806
|
+
if [[ ! -f "${local_pkg}" ]]; then
|
|
807
|
+
log_event "ERROR" "pi-workflows-node" "install_missing" "pi-extensible-workflows not installed in extensions dir" 1 \
|
|
808
|
+
"expected_path=${local_pkg}"
|
|
809
|
+
exit 1
|
|
810
|
+
fi
|
|
811
|
+
log_event "INFO" "pi-workflows-node" "module_resolved" "Installed in pi extensions dir" 0 "resolved=${local_pkg}"
|
|
671
812
|
|
|
672
813
|
local installed
|
|
673
|
-
installed
|
|
814
|
+
capture_cmd installed "pi-workflows-node" node -e \
|
|
815
|
+
'console.log(require(process.argv[1]).version)' "${local_pkg}"
|
|
674
816
|
if [[ "${installed}" != "${PI_WORKFLOW_VERSION}" ]]; then
|
|
675
817
|
log_event "ERROR" "pi-workflows-node" "version_mismatch" "Installed version mismatch" 1 \
|
|
676
818
|
"expected=${PI_WORKFLOW_VERSION};actual=${installed}"
|
|
@@ -680,9 +822,31 @@ mod_pi_workflows() {
|
|
|
680
822
|
if [[ -d "${DOTENV_EXT_DIR}" ]]; then
|
|
681
823
|
pushd "${DOTENV_EXT_DIR}" >/dev/null
|
|
682
824
|
printf '%s\n' 'ignore-scripts=false' > .npmrc
|
|
825
|
+
if [[ ! -f package.json ]]; then
|
|
826
|
+
printf '%s\n' '{"name":"pi-ext-workflows","private":true}' > package.json
|
|
827
|
+
fi
|
|
683
828
|
run_cmd "dotenv-workflows" npm install --save-exact --no-audit --no-fund \
|
|
684
829
|
"pi-extensible-workflows@${PI_WORKFLOW_VERSION}"
|
|
685
830
|
popd >/dev/null
|
|
831
|
+
|
|
832
|
+
local dotenv_workflow_pkg="${DOTENV_EXT_DIR}/node_modules/pi-extensible-workflows/package.json"
|
|
833
|
+
if [[ ! -f "${dotenv_workflow_pkg}" ]]; then
|
|
834
|
+
log_event "ERROR" "dotenv-workflows" "install_missing" \
|
|
835
|
+
"pi-extensible-workflows not installed in dotenv extensions dir" 1 \
|
|
836
|
+
"expected_path=${dotenv_workflow_pkg}"
|
|
837
|
+
exit 1
|
|
838
|
+
fi
|
|
839
|
+
local dotenv_installed
|
|
840
|
+
capture_cmd dotenv_installed "dotenv-workflows" node -e \
|
|
841
|
+
'console.log(require(process.argv[1]).version)' "${dotenv_workflow_pkg}"
|
|
842
|
+
if [[ "${dotenv_installed}" != "${PI_WORKFLOW_VERSION}" ]]; then
|
|
843
|
+
log_event "ERROR" "dotenv-workflows" "version_mismatch" \
|
|
844
|
+
"Installed dotenv workflow version mismatch" 1 \
|
|
845
|
+
"expected=${PI_WORKFLOW_VERSION};actual=${dotenv_installed}"
|
|
846
|
+
exit 1
|
|
847
|
+
fi
|
|
848
|
+
log_event "INFO" "dotenv-workflows" "module_verified" \
|
|
849
|
+
"Verified installed dotenv workflow package" 0 "path=${dotenv_workflow_pkg}"
|
|
686
850
|
fi
|
|
687
851
|
}
|
|
688
852
|
|
|
@@ -690,92 +854,126 @@ mod_pi_workflows() {
|
|
|
690
854
|
mod_herdr() {
|
|
691
855
|
section "herdr"
|
|
692
856
|
if command -v herdr >/dev/null 2>&1; then
|
|
693
|
-
|
|
694
|
-
|
|
857
|
+
local herdr_version
|
|
858
|
+
if capture_cmd herdr_version "herdr" herdr --version; then
|
|
859
|
+
log_event "INFO" "herdr" "already_present" "herdr already installed" 0 "version=${herdr_version}"
|
|
860
|
+
return
|
|
861
|
+
fi
|
|
862
|
+
return 1
|
|
695
863
|
fi
|
|
696
864
|
if [[ "${OS_FAMILY}" == "macos" ]]; then
|
|
697
|
-
|
|
865
|
+
run_cmd "herdr" brew install herdr
|
|
698
866
|
else
|
|
699
867
|
local installer="${TMP_DIR}/install-herdr.sh"
|
|
700
|
-
|
|
701
|
-
[[ -s "${installer}" ]]
|
|
868
|
+
run_cmd "herdr" curl -fsSL https://herdr.dev/install.sh -o "${installer}"
|
|
869
|
+
[[ -s "${installer}" ]] || {
|
|
870
|
+
log_event "ERROR" "herdr" "installer_missing" "herdr installer is empty" 1 "path=${installer}"
|
|
871
|
+
return 1
|
|
872
|
+
}
|
|
873
|
+
run_cmd "herdr" sh "${installer}"
|
|
702
874
|
fi
|
|
875
|
+
require_command herdr
|
|
703
876
|
}
|
|
704
877
|
|
|
705
878
|
# --- gentle-ai --------------------------------------------------------------
|
|
879
|
+
# Reinstates the Gentle AI ecosystem configurator. `gentle-ai install` is the
|
|
880
|
+
# per-agent/per-IDE selector (Pi, Claude Code, Cursor, Codex, ...) that also
|
|
881
|
+
# wires each selected agent's MCP servers, so tools appear under /mcp. The
|
|
882
|
+
# configurator is a CORE step: it always runs (interactively with a TTY, or
|
|
883
|
+
# non-interactively over the detected agents in CI/pipes) and a failure fails
|
|
884
|
+
# the module. For pi we additionally guarantee + verify the first-class
|
|
885
|
+
# gentle-pi harness and pi-mcp-adapter. Idempotent: safe to re-run.
|
|
706
886
|
mod_gentle_ai() {
|
|
707
887
|
section "gentle-ai"
|
|
708
|
-
|
|
888
|
+
|
|
889
|
+
# Installers drop the CLI into a PATH dir; make sure the usual ones resolve
|
|
890
|
+
# in this live process so the post-install verification can find it.
|
|
891
|
+
export PATH="${HOME}/.local/bin:${HOME}/go/bin:${PATH}"
|
|
892
|
+
|
|
893
|
+
# The per-agent selector and the pi harness both require the gentle-ai CLI
|
|
894
|
+
# itself; a legacy standalone `gga` is NOT enough, so install whenever the
|
|
895
|
+
# gentle-ai CLI is missing even if an old gga is on PATH.
|
|
896
|
+
if ! command -v gentle-ai >/dev/null 2>&1; then
|
|
709
897
|
if [[ "${OS_FAMILY}" == "macos" ]]; then
|
|
710
|
-
|
|
711
|
-
|
|
898
|
+
run_cmd "gentle-ai" brew tap gentleman-programming/tap
|
|
899
|
+
run_cmd "gentle-ai" brew install gentle-ai
|
|
712
900
|
else
|
|
713
901
|
local installer="${TMP_DIR}/install-gentle-ai.sh"
|
|
714
|
-
|
|
715
|
-
[[ -s "${installer}" ]]
|
|
902
|
+
run_cmd "gentle-ai" curl -fsSL "${GENTLE_AI_INSTALL}" -o "${installer}"
|
|
903
|
+
[[ -s "${installer}" ]] || {
|
|
904
|
+
log_event "ERROR" "gentle-ai" "installer_missing" "gentle-ai installer is empty" 1 "path=${installer}"
|
|
905
|
+
return 1
|
|
906
|
+
}
|
|
907
|
+
run_cmd "gentle-ai" bash "${installer}"
|
|
908
|
+
fi
|
|
909
|
+
fi
|
|
910
|
+
|
|
911
|
+
# Verify the CLI is present (a binary, not an npm tree — command -v + --version
|
|
912
|
+
# is the correct check). Required for both the selector and the pi harness.
|
|
913
|
+
command -v gentle-ai >/dev/null 2>&1 || {
|
|
914
|
+
log_event "ERROR" "gentle-ai" "binary_missing" "gentle-ai CLI not found on PATH after install" 1
|
|
915
|
+
return 1
|
|
916
|
+
}
|
|
917
|
+
run_cmd "gentle-ai" gentle-ai --version
|
|
918
|
+
|
|
919
|
+
# Detect the agents/IDEs present on this machine (same mapping as mod_ee).
|
|
920
|
+
local agent
|
|
921
|
+
local -a detected_agents=()
|
|
922
|
+
for agent in pi claude-code gemini-cli cursor antigravity codex opencode; do
|
|
923
|
+
[[ -d "$(agent_config_dir "${agent}")" ]] && detected_agents+=("${agent}")
|
|
924
|
+
done
|
|
925
|
+
(( ${#detected_agents[@]} == 0 )) && detected_agents=(pi)
|
|
926
|
+
|
|
927
|
+
# Per-agent / per-IDE selection + MCP wiring, owned by gentle-ai. This is a
|
|
928
|
+
# core step and must actually run: with a real TTY we launch the interactive
|
|
929
|
+
# selector (run directly — run_cmd would redirect stdout and hide prompts);
|
|
930
|
+
# otherwise we run it non-interactively over the detected agents so CI/pipes
|
|
931
|
+
# never hang. A failure fails the module (no silent downgrade).
|
|
932
|
+
if [[ -t 0 && -t 1 ]]; then
|
|
933
|
+
log_event "INFO" "gentle-ai" "configurator_start" "Launching gentle-ai install (choose agents/IDEs + MCP)" 0
|
|
934
|
+
if ! gentle-ai install --scope global; then
|
|
935
|
+
log_event "ERROR" "gentle-ai" "configurator_failed" "gentle-ai install failed" 1
|
|
936
|
+
return 1
|
|
716
937
|
fi
|
|
938
|
+
else
|
|
939
|
+
local agents_csv; agents_csv="$(IFS=,; printf '%s' "${detected_agents[*]}")"
|
|
940
|
+
log_event "INFO" "gentle-ai" "configurator_noninteractive" \
|
|
941
|
+
"No TTY; installing gentle-ai for detected agents" 0 "agents=${agents_csv}"
|
|
942
|
+
run_cmd "gentle-ai" gentle-ai install --scope global --agents "${agents_csv}"
|
|
717
943
|
fi
|
|
944
|
+
log_event "INFO" "gentle-ai" "configurator_done" "gentle-ai install completed" 0
|
|
718
945
|
|
|
719
|
-
#
|
|
720
|
-
#
|
|
946
|
+
# Guarantee pi reads gentle-ai in its MCP list (/mcp): install the first-class
|
|
947
|
+
# gentle-pi harness and the pi-mcp-adapter bridge, then verify the exact
|
|
948
|
+
# target (pi's own settings file), not a walked resolution.
|
|
721
949
|
if command -v pi >/dev/null 2>&1; then
|
|
722
|
-
|
|
723
|
-
|
|
724
|
-
|
|
950
|
+
run_cmd "gentle-ai" pi install npm:gentle-pi
|
|
951
|
+
run_cmd "gentle-ai" pi install npm:pi-mcp-adapter
|
|
952
|
+
local pi_settings="${PI_AGENT_DIR}/settings.json"
|
|
953
|
+
if [[ -f "${pi_settings}" ]] \
|
|
954
|
+
&& grep -q '"npm:gentle-pi"' "${pi_settings}" \
|
|
955
|
+
&& grep -q '"npm:pi-mcp-adapter"' "${pi_settings}"; then
|
|
956
|
+
log_event "INFO" "gentle-ai" "pi_enabled" \
|
|
957
|
+
"gentle-pi + pi-mcp-adapter registered in pi (verify: /mcp, /gentle-ai:status)" 0
|
|
958
|
+
else
|
|
959
|
+
log_event "ERROR" "gentle-ai" "pi_enable_failed" \
|
|
960
|
+
"gentle-pi and/or pi-mcp-adapter not present in pi settings after install" 1 "expected=${pi_settings}"
|
|
961
|
+
return 1
|
|
962
|
+
fi
|
|
725
963
|
fi
|
|
726
964
|
|
|
727
|
-
# Surface gentle-ai's own next-steps (do NOT run — they are per-repo).
|
|
728
965
|
log_event "INFO" "gentle-ai" "next_steps" "gentle-ai post-install hints" 0
|
|
729
966
|
cat <<'HINT' | tee -a "${HUMAN_LOG}"
|
|
730
967
|
gentle-ai next steps (run yourself, per project):
|
|
731
968
|
1) Set your API keys
|
|
732
969
|
2) Run your selected agent
|
|
733
|
-
3) Try: /sdd-new my-feature (in pi: /gentle-ai:status, /gentleman:models)
|
|
970
|
+
3) Try: /sdd-new my-feature (in pi: /gentle-ai:status, /gentleman:models, /mcp)
|
|
734
971
|
GGA (per project):
|
|
735
972
|
gga init # inside each repo
|
|
736
973
|
gga install
|
|
737
974
|
HINT
|
|
738
975
|
}
|
|
739
976
|
|
|
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
977
|
# --- codex ------------------------------------------------------------------
|
|
780
978
|
mod_codex() {
|
|
781
979
|
section "Codex CLI"
|
|
@@ -784,13 +982,18 @@ mod_codex() {
|
|
|
784
982
|
return
|
|
785
983
|
fi
|
|
786
984
|
if [[ "${OS_FAMILY}" == "macos" ]] && command -v brew >/dev/null 2>&1; then
|
|
787
|
-
|
|
985
|
+
run_cmd "codex" brew install --cask codex
|
|
788
986
|
fi
|
|
789
987
|
if ! command -v codex >/dev/null 2>&1; then
|
|
790
988
|
local installer="${TMP_DIR}/install-codex.sh"
|
|
791
|
-
|
|
792
|
-
[[ -s "${installer}" ]]
|
|
989
|
+
run_cmd "codex" curl -fsSL https://chatgpt.com/codex/install.sh -o "${installer}"
|
|
990
|
+
[[ -s "${installer}" ]] || {
|
|
991
|
+
log_event "ERROR" "codex" "installer_missing" "codex installer is empty" 1 "path=${installer}"
|
|
992
|
+
return 1
|
|
993
|
+
}
|
|
994
|
+
run_cmd "codex" sh "${installer}"
|
|
793
995
|
fi
|
|
996
|
+
require_command codex
|
|
794
997
|
}
|
|
795
998
|
|
|
796
999
|
# --- antigravity ------------------------------------------------------------
|
|
@@ -801,8 +1004,13 @@ mod_antigravity() {
|
|
|
801
1004
|
return
|
|
802
1005
|
fi
|
|
803
1006
|
local installer="${TMP_DIR}/install-antigravity.sh"
|
|
804
|
-
|
|
805
|
-
[[ -s "${installer}" ]]
|
|
1007
|
+
run_cmd "antigravity" curl -fsSL https://antigravity.google/cli/install.sh -o "${installer}"
|
|
1008
|
+
[[ -s "${installer}" ]] || {
|
|
1009
|
+
log_event "ERROR" "antigravity" "installer_missing" "Antigravity installer is empty" 1 "path=${installer}"
|
|
1010
|
+
return 1
|
|
1011
|
+
}
|
|
1012
|
+
run_cmd "antigravity" bash "${installer}"
|
|
1013
|
+
require_command agy
|
|
806
1014
|
}
|
|
807
1015
|
|
|
808
1016
|
# --- opencode ---------------------------------------------------------------
|
|
@@ -813,12 +1021,17 @@ mod_opencode() {
|
|
|
813
1021
|
return
|
|
814
1022
|
fi
|
|
815
1023
|
if [[ "${OS_FAMILY}" == "macos" ]]; then
|
|
816
|
-
|
|
1024
|
+
run_cmd "opencode" brew install anomalyco/tap/opencode
|
|
817
1025
|
else
|
|
818
1026
|
local installer="${TMP_DIR}/install-opencode.sh"
|
|
819
|
-
|
|
820
|
-
[[ -s "${installer}" ]]
|
|
1027
|
+
run_cmd "opencode" curl -fsSL https://opencode.ai/install -o "${installer}"
|
|
1028
|
+
[[ -s "${installer}" ]] || {
|
|
1029
|
+
log_event "ERROR" "opencode" "installer_missing" "opencode installer is empty" 1 "path=${installer}"
|
|
1030
|
+
return 1
|
|
1031
|
+
}
|
|
1032
|
+
run_cmd "opencode" bash "${installer}"
|
|
821
1033
|
fi
|
|
1034
|
+
require_command opencode
|
|
822
1035
|
|
|
823
1036
|
log_event "INFO" "opencode" "zen_hint" "OpenCode Go / Zen provider hint" 0
|
|
824
1037
|
cat <<'HINT' | tee -a "${HUMAN_LOG}"
|
|
@@ -864,8 +1077,11 @@ mod_cockpit() {
|
|
|
864
1077
|
esac
|
|
865
1078
|
|
|
866
1079
|
local url
|
|
867
|
-
url="$(grep -oE '"browser_download_url":[[:space:]]*"[^"]+"' "${release_json}" \
|
|
868
|
-
| cut -d '"' -f4 | grep -iE "${asset_pat}" |
|
|
1080
|
+
if ! url="$(grep -oE '"browser_download_url":[[:space:]]*"[^"]+"' "${release_json}" \
|
|
1081
|
+
| cut -d '"' -f4 | grep -iE "${asset_pat}" | sed -n '1p')"; then
|
|
1082
|
+
log_event "WARN" "cockpit" "asset_parse_failed" "Could not parse cockpit-tools release assets"
|
|
1083
|
+
return 0
|
|
1084
|
+
fi
|
|
869
1085
|
|
|
870
1086
|
if [[ -z "${url}" ]]; then
|
|
871
1087
|
log_event "WARN" "cockpit" "no_matching_asset" \
|
|
@@ -875,7 +1091,10 @@ mod_cockpit() {
|
|
|
875
1091
|
|
|
876
1092
|
local installer="${TMP_DIR}/cockpit-asset"
|
|
877
1093
|
run_optional "cockpit" curl -fsSL "${url}" -o "${installer}"
|
|
878
|
-
[[ -s "${installer}" ]]
|
|
1094
|
+
if [[ ! -s "${installer}" ]]; then
|
|
1095
|
+
log_event "WARN" "cockpit" "download_unavailable" "Could not download the selected cockpit-tools asset"
|
|
1096
|
+
return 0
|
|
1097
|
+
fi
|
|
879
1098
|
|
|
880
1099
|
case "${PM}" in
|
|
881
1100
|
apt-get) run_optional "cockpit" sudo apt-get install -y "${installer}" ;;
|
|
@@ -930,17 +1149,75 @@ quality_gates() {
|
|
|
930
1149
|
run_cmd "quality" bash -n "${BASH_SOURCE[0]}"
|
|
931
1150
|
|
|
932
1151
|
is_selected node && { run_cmd "quality" node --version; run_cmd "quality" npm --version; }
|
|
933
|
-
is_selected bun &&
|
|
934
|
-
is_selected pi
|
|
1152
|
+
is_selected bun && run_cmd "quality" bun --version
|
|
1153
|
+
if is_selected pi; then
|
|
1154
|
+
require_command pi
|
|
1155
|
+
run_cmd "quality" pi --no-extensions --version
|
|
1156
|
+
fi
|
|
1157
|
+
|
|
1158
|
+
if is_selected node || is_selected pi-workflows; then
|
|
1159
|
+
assert_node_minimum
|
|
1160
|
+
fi
|
|
935
1161
|
|
|
936
1162
|
if is_selected pi-workflows; then
|
|
1163
|
+
local workflow_pkg="${PI_EXTENSIONS_DIR}/node_modules/pi-extensible-workflows/package.json"
|
|
1164
|
+
if [[ ! -f "${workflow_pkg}" ]]; then
|
|
1165
|
+
log_event "ERROR" "quality" "workflow_package_missing" \
|
|
1166
|
+
"pi-extensible-workflows package.json missing from extensions dir" 1 \
|
|
1167
|
+
"expected_path=${workflow_pkg}"
|
|
1168
|
+
exit 1
|
|
1169
|
+
fi
|
|
937
1170
|
run_cmd "quality" node -e \
|
|
938
|
-
'const
|
|
939
|
-
"${
|
|
1171
|
+
'const fs=require("fs"); const path=process.argv[1]; const pkg=JSON.parse(fs.readFileSync(path,"utf8")); console.log("VERSION="+pkg.version)' \
|
|
1172
|
+
"${workflow_pkg}"
|
|
940
1173
|
fi
|
|
941
1174
|
|
|
942
|
-
|
|
943
|
-
|
|
1175
|
+
local -a target_agents=()
|
|
1176
|
+
local agent sk
|
|
1177
|
+
for agent in pi claude-code gemini-cli cursor antigravity codex opencode; do
|
|
1178
|
+
[[ -d "$(agent_config_dir "${agent}")" ]] && target_agents+=("${agent}")
|
|
1179
|
+
done
|
|
1180
|
+
(( ${#target_agents[@]} == 0 )) && target_agents=(pi)
|
|
1181
|
+
|
|
1182
|
+
if is_selected ee; then
|
|
1183
|
+
verify_skill_for_agents "quality" "${ENGINEERING_EXCELLENCE_SKILL}" "${target_agents[@]}"
|
|
1184
|
+
log_event "INFO" "quality" "ee_gate_passed" \
|
|
1185
|
+
"Engineering Excellence skill verified for every targeted agent" 0
|
|
1186
|
+
fi
|
|
1187
|
+
|
|
1188
|
+
if is_selected skills; then
|
|
1189
|
+
for sk in "${UPSTREAM_SKILL_NAMES[@]}"; do
|
|
1190
|
+
verify_skill_for_agents "quality" "${sk}" "${target_agents[@]}"
|
|
1191
|
+
done
|
|
1192
|
+
log_event "INFO" "quality" "skills_gate_passed" \
|
|
1193
|
+
"All upstream skills verified for every targeted agent" 0
|
|
1194
|
+
fi
|
|
1195
|
+
|
|
1196
|
+
if is_selected gentle-ai; then
|
|
1197
|
+
# The module requires the gentle-ai CLI specifically; a legacy standalone
|
|
1198
|
+
# gga cannot prove the per-agent/MCP configuration ran.
|
|
1199
|
+
if ! command -v gentle-ai >/dev/null 2>&1; then
|
|
1200
|
+
log_event "ERROR" "quality" "gentle_ai_missing" \
|
|
1201
|
+
"gentle-ai CLI not found on PATH after install" 1
|
|
1202
|
+
exit 1
|
|
1203
|
+
fi
|
|
1204
|
+
if command -v pi >/dev/null 2>&1; then
|
|
1205
|
+
local gentle_settings="${PI_AGENT_DIR}/settings.json"
|
|
1206
|
+
if [[ ! -f "${gentle_settings}" ]] \
|
|
1207
|
+
|| ! grep -q '"npm:gentle-pi"' "${gentle_settings}" \
|
|
1208
|
+
|| ! grep -q '"npm:pi-mcp-adapter"' "${gentle_settings}"; then
|
|
1209
|
+
log_event "ERROR" "quality" "gentle_pi_missing" \
|
|
1210
|
+
"gentle-pi and/or pi-mcp-adapter not registered in pi settings" 1 "expected=${gentle_settings}"
|
|
1211
|
+
exit 1
|
|
1212
|
+
fi
|
|
1213
|
+
log_event "INFO" "quality" "gentle_ai_gate_passed" \
|
|
1214
|
+
"gentle-ai verified (CLI present; gentle-pi + pi-mcp-adapter registered in pi)" 0
|
|
1215
|
+
else
|
|
1216
|
+
# No pi on PATH: pi MCP wiring is genuinely not applicable, so do not
|
|
1217
|
+
# claim it was registered.
|
|
1218
|
+
log_event "INFO" "quality" "gentle_ai_gate_passed" \
|
|
1219
|
+
"gentle-ai CLI verified; pi not present, so pi MCP wiring was not required" 0
|
|
1220
|
+
fi
|
|
944
1221
|
fi
|
|
945
1222
|
|
|
946
1223
|
log_event "INFO" "quality" "gates_done" "Quality gates completed for selected modules"
|
|
@@ -965,8 +1242,8 @@ print_list() {
|
|
|
965
1242
|
printf 'AI Dev Suite %s — modules (core = installed by default):\n\n' "${SCRIPT_VERSION}"
|
|
966
1243
|
local m tag
|
|
967
1244
|
for m in "${MODULE_ORDER[@]}"; do
|
|
968
|
-
if
|
|
969
|
-
printf ' [%s] %-14s %s\n' "${tag}" "${m}" "${
|
|
1245
|
+
if module_is_optional "${m}"; then tag="optional"; else tag="core "; fi
|
|
1246
|
+
printf ' [%s] %-14s %s\n' "${tag}" "${m}" "$(module_desc "${m}")"
|
|
970
1247
|
done
|
|
971
1248
|
printf '\nUse: --only <csv> | --all | (default = core)\n'
|
|
972
1249
|
}
|
|
@@ -978,11 +1255,14 @@ print_help() {
|
|
|
978
1255
|
}
|
|
979
1256
|
|
|
980
1257
|
parse_args() {
|
|
981
|
-
local mode="default" only_csv=""
|
|
1258
|
+
local mode="default" only_csv="" all_requested=0
|
|
982
1259
|
while [[ $# -gt 0 ]]; do
|
|
983
1260
|
case "$1" in
|
|
984
|
-
--all) mode="all"; shift ;;
|
|
985
|
-
--only)
|
|
1261
|
+
--all) mode="all"; all_requested=1; shift ;;
|
|
1262
|
+
--only)
|
|
1263
|
+
[[ $# -ge 2 ]] || { printf '%s\n' '--only requires a non-empty comma-separated module list.' >&2; exit 2; }
|
|
1264
|
+
mode="only"; only_csv="$2"; shift 2
|
|
1265
|
+
;;
|
|
986
1266
|
--only=*) mode="only"; only_csv="${1#*=}"; shift ;;
|
|
987
1267
|
--yes|-y) shift ;; # accepted for launcher parity
|
|
988
1268
|
--list) print_list; exit 0 ;;
|
|
@@ -991,26 +1271,37 @@ parse_args() {
|
|
|
991
1271
|
esac
|
|
992
1272
|
done
|
|
993
1273
|
|
|
1274
|
+
# Match PowerShell: -All wins whenever it is present, regardless of
|
|
1275
|
+
# where it appears relative to --only.
|
|
1276
|
+
(( all_requested == 1 )) && mode="all"
|
|
1277
|
+
|
|
994
1278
|
local requested=()
|
|
995
1279
|
case "${mode}" in
|
|
996
1280
|
all) requested=("${MODULE_ORDER[@]}") ;;
|
|
997
1281
|
only)
|
|
998
|
-
|
|
999
|
-
|
|
1000
|
-
|
|
1001
|
-
IFS="${IFS_SAVE}"
|
|
1282
|
+
# Split on commas without word-splitting or glob expansion.
|
|
1283
|
+
local raw=()
|
|
1284
|
+
IFS=',' read -r -a raw <<< "${only_csv}"
|
|
1002
1285
|
local r
|
|
1003
1286
|
for r in "${raw[@]}"; do
|
|
1004
|
-
|
|
1287
|
+
# Trim only surrounding whitespace (match PowerShell .Trim());
|
|
1288
|
+
# internal whitespace makes the token invalid instead of being
|
|
1289
|
+
# silently collapsed (e.g. "co dex" must not become "codex").
|
|
1290
|
+
r="${r#"${r%%[![:space:]]*}"}"
|
|
1291
|
+
r="${r%"${r##*[![:space:]]}"}"
|
|
1005
1292
|
[[ -z "${r}" ]] && continue
|
|
1006
|
-
|
|
1293
|
+
module_desc "${r}" >/dev/null || { printf 'Unknown module: %s\n' "${r}" >&2; exit 2; }
|
|
1007
1294
|
requested+=("${r}")
|
|
1008
1295
|
done
|
|
1296
|
+
(( ${#requested[@]} > 0 )) || {
|
|
1297
|
+
printf '%s\n' '--only requires a non-empty comma-separated module list.' >&2
|
|
1298
|
+
exit 2
|
|
1299
|
+
}
|
|
1009
1300
|
;;
|
|
1010
1301
|
default)
|
|
1011
1302
|
local m
|
|
1012
1303
|
for m in "${MODULE_ORDER[@]}"; do
|
|
1013
|
-
|
|
1304
|
+
module_is_optional "${m}" && continue
|
|
1014
1305
|
requested+=("${m}")
|
|
1015
1306
|
done
|
|
1016
1307
|
;;
|
|
@@ -1053,10 +1344,15 @@ parse_args "$@"
|
|
|
1053
1344
|
|
|
1054
1345
|
section "Preflight"
|
|
1055
1346
|
require_command bash
|
|
1056
|
-
require_command curl
|
|
1057
|
-
require_command git
|
|
1058
1347
|
detect_os
|
|
1059
1348
|
|
|
1349
|
+
# The base module owns bootstrap tools on a clean machine. For explicit
|
|
1350
|
+
# selections that skip base, fail early because later installers need them.
|
|
1351
|
+
if ! is_selected base; then
|
|
1352
|
+
require_command curl
|
|
1353
|
+
require_command git
|
|
1354
|
+
fi
|
|
1355
|
+
|
|
1060
1356
|
if [[ "${OS_FAMILY}" == "linux" ]]; then
|
|
1061
1357
|
require_command sudo
|
|
1062
1358
|
fi
|
|
@@ -1073,6 +1369,11 @@ quality_gates
|
|
|
1073
1369
|
|
|
1074
1370
|
write_report "SUCCESS" 0 "n/a" "n/a" "n/a" "n/a"
|
|
1075
1371
|
|
|
1372
|
+
report_version REPORT_NODE_VERSION node --version
|
|
1373
|
+
report_version REPORT_NPM_VERSION npm --version
|
|
1374
|
+
report_version REPORT_BUN_VERSION bun --version
|
|
1375
|
+
report_version REPORT_GO_VERSION go version
|
|
1376
|
+
|
|
1076
1377
|
cat >> "${REPORT_FILE}" <<EOF
|
|
1077
1378
|
|
|
1078
1379
|
## Installed (selected modules)
|
|
@@ -1081,11 +1382,11 @@ ${SELECTED_DISPLAY}
|
|
|
1081
1382
|
|
|
1082
1383
|
## Versions
|
|
1083
1384
|
|
|
1084
|
-
- Node.js: \`$
|
|
1085
|
-
- npm: \`$
|
|
1086
|
-
- Bun: \`$
|
|
1385
|
+
- Node.js: \`${REPORT_NODE_VERSION}\`
|
|
1386
|
+
- npm: \`${REPORT_NPM_VERSION}\`
|
|
1387
|
+
- Bun: \`${REPORT_BUN_VERSION}\`
|
|
1087
1388
|
- Pi: \`${PI_VERSION:-n/a}\`
|
|
1088
|
-
- Go: \`$
|
|
1389
|
+
- Go: \`${REPORT_GO_VERSION}\`
|
|
1089
1390
|
EOF
|
|
1090
1391
|
|
|
1091
1392
|
log_event "INFO" "bootstrap" "completed" "AI Dev Suite setup completed successfully" 0 \
|