@darkrei08/setup-ai 3.0.4 → 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 -121
- package/setup-ai.sh +508 -205
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,90 +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 (in-process extension is the primary path; MCP adapter optional).
|
|
766
|
-
if command -v pi >/dev/null 2>&1; then
|
|
767
|
-
run_optional "engram" pi install npm:gentle-engram
|
|
768
|
-
run_optional "engram" pi install npm:pi-mcp-adapter
|
|
769
|
-
run_optional "engram" npm exec --yes --package gentle-engram@latest -- pi-engram init
|
|
770
|
-
log_event "INFO" "engram" "enabled" \
|
|
771
|
-
"Engram enabled — RESTART pi, then verify with mem_current_project / mem_doctor / 'engram tui'" 0
|
|
772
|
-
else
|
|
773
|
-
log_event "WARN" "engram" "pi_missing" "pi not found; Engram pi integration skipped"
|
|
774
|
-
fi
|
|
775
|
-
}
|
|
776
|
-
|
|
777
977
|
# --- codex ------------------------------------------------------------------
|
|
778
978
|
mod_codex() {
|
|
779
979
|
section "Codex CLI"
|
|
@@ -782,13 +982,18 @@ mod_codex() {
|
|
|
782
982
|
return
|
|
783
983
|
fi
|
|
784
984
|
if [[ "${OS_FAMILY}" == "macos" ]] && command -v brew >/dev/null 2>&1; then
|
|
785
|
-
|
|
985
|
+
run_cmd "codex" brew install --cask codex
|
|
786
986
|
fi
|
|
787
987
|
if ! command -v codex >/dev/null 2>&1; then
|
|
788
988
|
local installer="${TMP_DIR}/install-codex.sh"
|
|
789
|
-
|
|
790
|
-
[[ -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}"
|
|
791
995
|
fi
|
|
996
|
+
require_command codex
|
|
792
997
|
}
|
|
793
998
|
|
|
794
999
|
# --- antigravity ------------------------------------------------------------
|
|
@@ -799,8 +1004,13 @@ mod_antigravity() {
|
|
|
799
1004
|
return
|
|
800
1005
|
fi
|
|
801
1006
|
local installer="${TMP_DIR}/install-antigravity.sh"
|
|
802
|
-
|
|
803
|
-
[[ -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
|
|
804
1014
|
}
|
|
805
1015
|
|
|
806
1016
|
# --- opencode ---------------------------------------------------------------
|
|
@@ -811,12 +1021,17 @@ mod_opencode() {
|
|
|
811
1021
|
return
|
|
812
1022
|
fi
|
|
813
1023
|
if [[ "${OS_FAMILY}" == "macos" ]]; then
|
|
814
|
-
|
|
1024
|
+
run_cmd "opencode" brew install anomalyco/tap/opencode
|
|
815
1025
|
else
|
|
816
1026
|
local installer="${TMP_DIR}/install-opencode.sh"
|
|
817
|
-
|
|
818
|
-
[[ -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}"
|
|
819
1033
|
fi
|
|
1034
|
+
require_command opencode
|
|
820
1035
|
|
|
821
1036
|
log_event "INFO" "opencode" "zen_hint" "OpenCode Go / Zen provider hint" 0
|
|
822
1037
|
cat <<'HINT' | tee -a "${HUMAN_LOG}"
|
|
@@ -862,8 +1077,11 @@ mod_cockpit() {
|
|
|
862
1077
|
esac
|
|
863
1078
|
|
|
864
1079
|
local url
|
|
865
|
-
url="$(grep -oE '"browser_download_url":[[:space:]]*"[^"]+"' "${release_json}" \
|
|
866
|
-
| 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
|
|
867
1085
|
|
|
868
1086
|
if [[ -z "${url}" ]]; then
|
|
869
1087
|
log_event "WARN" "cockpit" "no_matching_asset" \
|
|
@@ -873,7 +1091,10 @@ mod_cockpit() {
|
|
|
873
1091
|
|
|
874
1092
|
local installer="${TMP_DIR}/cockpit-asset"
|
|
875
1093
|
run_optional "cockpit" curl -fsSL "${url}" -o "${installer}"
|
|
876
|
-
[[ -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
|
|
877
1098
|
|
|
878
1099
|
case "${PM}" in
|
|
879
1100
|
apt-get) run_optional "cockpit" sudo apt-get install -y "${installer}" ;;
|
|
@@ -928,17 +1149,75 @@ quality_gates() {
|
|
|
928
1149
|
run_cmd "quality" bash -n "${BASH_SOURCE[0]}"
|
|
929
1150
|
|
|
930
1151
|
is_selected node && { run_cmd "quality" node --version; run_cmd "quality" npm --version; }
|
|
931
|
-
is_selected bun &&
|
|
932
|
-
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
|
|
933
1161
|
|
|
934
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
|
|
935
1170
|
run_cmd "quality" node -e \
|
|
936
|
-
'const
|
|
937
|
-
"${
|
|
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}"
|
|
938
1173
|
fi
|
|
939
1174
|
|
|
940
|
-
|
|
941
|
-
|
|
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
|
|
942
1221
|
fi
|
|
943
1222
|
|
|
944
1223
|
log_event "INFO" "quality" "gates_done" "Quality gates completed for selected modules"
|
|
@@ -963,8 +1242,8 @@ print_list() {
|
|
|
963
1242
|
printf 'AI Dev Suite %s — modules (core = installed by default):\n\n' "${SCRIPT_VERSION}"
|
|
964
1243
|
local m tag
|
|
965
1244
|
for m in "${MODULE_ORDER[@]}"; do
|
|
966
|
-
if
|
|
967
|
-
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}")"
|
|
968
1247
|
done
|
|
969
1248
|
printf '\nUse: --only <csv> | --all | (default = core)\n'
|
|
970
1249
|
}
|
|
@@ -976,11 +1255,14 @@ print_help() {
|
|
|
976
1255
|
}
|
|
977
1256
|
|
|
978
1257
|
parse_args() {
|
|
979
|
-
local mode="default" only_csv=""
|
|
1258
|
+
local mode="default" only_csv="" all_requested=0
|
|
980
1259
|
while [[ $# -gt 0 ]]; do
|
|
981
1260
|
case "$1" in
|
|
982
|
-
--all) mode="all"; shift ;;
|
|
983
|
-
--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
|
+
;;
|
|
984
1266
|
--only=*) mode="only"; only_csv="${1#*=}"; shift ;;
|
|
985
1267
|
--yes|-y) shift ;; # accepted for launcher parity
|
|
986
1268
|
--list) print_list; exit 0 ;;
|
|
@@ -989,26 +1271,37 @@ parse_args() {
|
|
|
989
1271
|
esac
|
|
990
1272
|
done
|
|
991
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
|
+
|
|
992
1278
|
local requested=()
|
|
993
1279
|
case "${mode}" in
|
|
994
1280
|
all) requested=("${MODULE_ORDER[@]}") ;;
|
|
995
1281
|
only)
|
|
996
|
-
|
|
997
|
-
|
|
998
|
-
|
|
999
|
-
IFS="${IFS_SAVE}"
|
|
1282
|
+
# Split on commas without word-splitting or glob expansion.
|
|
1283
|
+
local raw=()
|
|
1284
|
+
IFS=',' read -r -a raw <<< "${only_csv}"
|
|
1000
1285
|
local r
|
|
1001
1286
|
for r in "${raw[@]}"; do
|
|
1002
|
-
|
|
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:]]}"}"
|
|
1003
1292
|
[[ -z "${r}" ]] && continue
|
|
1004
|
-
|
|
1293
|
+
module_desc "${r}" >/dev/null || { printf 'Unknown module: %s\n' "${r}" >&2; exit 2; }
|
|
1005
1294
|
requested+=("${r}")
|
|
1006
1295
|
done
|
|
1296
|
+
(( ${#requested[@]} > 0 )) || {
|
|
1297
|
+
printf '%s\n' '--only requires a non-empty comma-separated module list.' >&2
|
|
1298
|
+
exit 2
|
|
1299
|
+
}
|
|
1007
1300
|
;;
|
|
1008
1301
|
default)
|
|
1009
1302
|
local m
|
|
1010
1303
|
for m in "${MODULE_ORDER[@]}"; do
|
|
1011
|
-
|
|
1304
|
+
module_is_optional "${m}" && continue
|
|
1012
1305
|
requested+=("${m}")
|
|
1013
1306
|
done
|
|
1014
1307
|
;;
|
|
@@ -1051,10 +1344,15 @@ parse_args "$@"
|
|
|
1051
1344
|
|
|
1052
1345
|
section "Preflight"
|
|
1053
1346
|
require_command bash
|
|
1054
|
-
require_command curl
|
|
1055
|
-
require_command git
|
|
1056
1347
|
detect_os
|
|
1057
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
|
+
|
|
1058
1356
|
if [[ "${OS_FAMILY}" == "linux" ]]; then
|
|
1059
1357
|
require_command sudo
|
|
1060
1358
|
fi
|
|
@@ -1071,6 +1369,11 @@ quality_gates
|
|
|
1071
1369
|
|
|
1072
1370
|
write_report "SUCCESS" 0 "n/a" "n/a" "n/a" "n/a"
|
|
1073
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
|
+
|
|
1074
1377
|
cat >> "${REPORT_FILE}" <<EOF
|
|
1075
1378
|
|
|
1076
1379
|
## Installed (selected modules)
|
|
@@ -1079,11 +1382,11 @@ ${SELECTED_DISPLAY}
|
|
|
1079
1382
|
|
|
1080
1383
|
## Versions
|
|
1081
1384
|
|
|
1082
|
-
- Node.js: \`$
|
|
1083
|
-
- npm: \`$
|
|
1084
|
-
- Bun: \`$
|
|
1385
|
+
- Node.js: \`${REPORT_NODE_VERSION}\`
|
|
1386
|
+
- npm: \`${REPORT_NPM_VERSION}\`
|
|
1387
|
+
- Bun: \`${REPORT_BUN_VERSION}\`
|
|
1085
1388
|
- Pi: \`${PI_VERSION:-n/a}\`
|
|
1086
|
-
- Go: \`$
|
|
1389
|
+
- Go: \`${REPORT_GO_VERSION}\`
|
|
1087
1390
|
EOF
|
|
1088
1391
|
|
|
1089
1392
|
log_event "INFO" "bootstrap" "completed" "AI Dev Suite setup completed successfully" 0 \
|