#!/usr/bin/env bash # Shared helpers for the pi-agent-config scripts. # shellcheck shell=bash set -uo pipefail REPO_ROOT="$(git rev-parse --show-toplevel 2>/dev/null)" || { echo "error: not inside the pi-agent-config git work tree" >&2 exit 1 } export REPO_ROOT if [ -t 1 ]; then C_RED=$'\033[31m'; C_GREEN=$'\033[32m'; C_YELLOW=$'\033[33m' C_BOLD=$'\033[1m'; C_DIM=$'\033[2m'; C_OFF=$'\033[0m' else C_RED=''; C_GREEN=''; C_YELLOW=''; C_BOLD=''; C_DIM=''; C_OFF='' fi info() { printf '%s\n' "$*"; } ok() { printf '%s %s%s\n' "${C_GREEN}OK${C_OFF}" "$*" ''; } warn() { printf '%s %s\n' "${C_YELLOW}WARN${C_OFF}" "$*" >&2; } die() { printf '%s %s\n' "${C_RED}ERROR${C_OFF}" "$*" >&2; exit 1; } head1() { printf '\n%s%s%s\n' "$C_BOLD" "$*" "$C_OFF"; } # List the scenarios that exist, excluding the template. list_scenarios() { find "$REPO_ROOT/scenarios" -mindepth 1 -maxdepth 1 -type d \ -not -name '_*' -printf '%f\n' | sort } require_scenario() { local name="${1:-}" [ -n "$name" ] || die "a scenario name is required. Available: $(list_scenarios | tr '\n' ' ')" [ -d "$REPO_ROOT/scenarios/$name" ] \ || die "unknown scenario '$name'. Available: $(list_scenarios | tr '\n' ' ')" printf '%s' "$REPO_ROOT/scenarios/$name" } # Read a flat "key = value" entry from a TOML section. # Intentionally minimal: these profiles are hand-written and flat. Anything more # structured should be read by the Python consumer, not by shell. toml_get() { local file="$1" section="$2" key="$3" awk -v want_section="$section" -v want_key="$key" ' /^[[:space:]]*\[/ { s = $0; sub(/^[[:space:]]*\[/, "", s); sub(/\][[:space:]]*$/, "", s) current = s; next } { line = $0 sub(/[[:space:]]*#.*$/, "", line) if (current != want_section) next if (line !~ /=/) next k = line; sub(/=.*$/, "", k); gsub(/[[:space:]]/, "", k) if (k != want_key) next v = line; sub(/^[^=]*=[[:space:]]*/, "", v) gsub(/^"|"$/, "", v) gsub(/[[:space:]]+$/, "", v) print v; exit } ' "$file" } # Print a unified diff between a tracked file and its live counterpart. # Returns 0 when identical, 1 when different or missing. # Read a TOML array of strings, one element per line. # # Uses Python's tomllib rather than more awk: an array can span lines, hold # comments, or be empty, and a hand-rolled parser that mostly works is worse # than none because it fails on the one profile that formats it differently. toml_list() { local file="$1" section="$2" key="$3" python3 - "$file" "$section" "$key" <<'PYEOF' import sys, tomllib, pathlib data = tomllib.loads(pathlib.Path(sys.argv[1]).read_text()) value = data.get(sys.argv[2], {}).get(sys.argv[3]) or [] if isinstance(value, str): value = [value] for item in value: print(item) PYEOF } diff_file() { local tracked="$1" live="$2" label="$3" if [ ! -f "$live" ]; then printf '%s %s %s(live file absent)%s\n' "${C_YELLOW}DIFF${C_OFF}" "$label" "$C_DIM" "$C_OFF" return 1 fi if cmp -s "$tracked" "$live"; then printf '%s %s\n' "${C_GREEN}SAME${C_OFF}" "$label" return 0 fi printf '%s %s\n' "${C_YELLOW}DIFF${C_OFF}" "$label" diff -u --label "repo/$label" "$tracked" --label "live/$label" "$live" \ | sed 's/^/ /' || true return 1 }