337 lines
11 KiB
Bash
Executable File
337 lines
11 KiB
Bash
Executable File
#!/usr/bin/env bash
|
|
# ==============================================================================
|
|
# Multi-Agent Mux (MAM) Skill Installer
|
|
# Efficiently installs MAM orchestration rules & skills to another project.
|
|
# ==============================================================================
|
|
|
|
set -euo pipefail
|
|
|
|
# ANSI color codes
|
|
RED='\033[0;31m'
|
|
GREEN='\033[0;32m'
|
|
YELLOW='\033[0;33m'
|
|
BLUE='\033[0;34m'
|
|
NC='\033[0m' # No Color
|
|
|
|
# Helper functions for logs
|
|
log_info() { echo -e "${BLUE}[INFO]${NC} $*"; }
|
|
log_ok() { echo -e "${GREEN}[OK]${NC} $*"; }
|
|
log_warn() { echo -e "${YELLOW}[WARN]${NC} $*"; }
|
|
log_error() { echo -e "${RED}[ERROR]${NC} $*" >&2; }
|
|
|
|
show_help() {
|
|
cat <<EOF
|
|
Usage: $0 [options]
|
|
|
|
Options:
|
|
-t, --target <path> Target directory/project where MAM should be installed (defaults to current directory)
|
|
-f, --force Force copy AGENTS.md even if it already exists (backups are still made)
|
|
-h, --help Show this help message
|
|
EOF
|
|
}
|
|
|
|
TARGET_DIR="."
|
|
FORCE=0
|
|
|
|
# Parse CLI arguments
|
|
while [[ $# -gt 0 ]]; do
|
|
case "$1" in
|
|
-t|--target)
|
|
TARGET_DIR="$2"
|
|
shift 2
|
|
;;
|
|
-f|--force)
|
|
FORCE=1
|
|
shift
|
|
;;
|
|
-h|--help)
|
|
show_help
|
|
exit 0
|
|
;;
|
|
*)
|
|
log_error "Unknown option: $1"
|
|
show_help
|
|
exit 1
|
|
;;
|
|
esac
|
|
done
|
|
|
|
# Resolve absolute path for source and target, tracking symlinks gracefully
|
|
SOURCE="${BASH_SOURCE[0]}"
|
|
while [ -h "$SOURCE" ]; do
|
|
DIR="$( cd -P "$( dirname "$SOURCE" )" >/dev/null 2>&1 && pwd )"
|
|
SOURCE="$(readlink "$SOURCE")"
|
|
[[ $SOURCE != /* ]] && SOURCE="$DIR/$SOURCE"
|
|
done
|
|
DIR="$( cd -P "$( dirname "$SOURCE" )" >/dev/null 2>&1 && pwd )"
|
|
SRC_DIR="$(cd "$DIR/.." && pwd)"
|
|
|
|
# Ensure target directory exists
|
|
if [ ! -d "$TARGET_DIR" ]; then
|
|
log_info "Creating target directory: $TARGET_DIR"
|
|
mkdir -p "$TARGET_DIR"
|
|
fi
|
|
TARGET_DIR="$(cd "$TARGET_DIR" && pwd)"
|
|
|
|
log_info "Installing MAM skills to target project: $TARGET_DIR"
|
|
log_info "Source directory resolved: $SRC_DIR"
|
|
|
|
if [ "$SRC_DIR" = "$TARGET_DIR" ]; then
|
|
log_error "Source and Target directories are the same! Cannot install to oneself."
|
|
exit 1
|
|
fi
|
|
|
|
OWNERSHIP_LIB="$SRC_DIR/deploy/lib_ownership.sh"
|
|
if [ ! -f "$OWNERSHIP_LIB" ]; then
|
|
log_error "Missing $OWNERSHIP_LIB; refusing to install with unknown ownership rules."
|
|
exit 1
|
|
fi
|
|
# shellcheck source=deploy/lib_ownership.sh
|
|
. "$OWNERSHIP_LIB"
|
|
|
|
# 1. Dependency Checks
|
|
log_info "Verifying host dependencies..."
|
|
DEPS=(herdr python3 rsync uuidgen)
|
|
MISSING_DEPS=()
|
|
for dep in "${DEPS[@]}"; do
|
|
if ! command -v "$dep" &>/dev/null; then
|
|
MISSING_DEPS+=("$dep")
|
|
fi
|
|
done
|
|
|
|
if [ ${#MISSING_DEPS[@]} -ne 0 ]; then
|
|
log_error "Missing required dependencies: ${MISSING_DEPS[*]}"
|
|
if [[ " ${MISSING_DEPS[*]} " == *" herdr "* ]]; then
|
|
log_error "To install herdr, please refer to the official installation guide:"
|
|
log_error " curl -fsSL https://herdr.dev/install.sh | sh"
|
|
fi
|
|
log_error "Please install them before using MAM."
|
|
exit 1
|
|
fi
|
|
|
|
# Check Python PyYAML and sqlite3 library (hard dependencies for registry parsing)
|
|
if ! python3 -c "import yaml, sqlite3" &>/dev/null; then
|
|
log_error "Python 'pyyaml' or built-in 'sqlite3' modules are missing (hard dependencies for session registry)."
|
|
log_error "Please run: pip install pyyaml"
|
|
exit 1
|
|
fi
|
|
log_ok "Dependency checks completed."
|
|
|
|
# 2. Copy .agents/ folder and config tools
|
|
log_info "Deploying orchestration rules & skills (.agents/)..."
|
|
mkdir -p "$TARGET_DIR/.agents"
|
|
|
|
# Sync rules and skills, avoiding copying temporary or system files
|
|
# Exclude git histories, reports, references, logs or internal runtime cache
|
|
rsync -a --exclude='.git/' --exclude='/reports/' --exclude='/references/' --exclude='*.log' --exclude='*.tmp' --exclude='__pycache__/' --exclude='*.pyc' "$SRC_DIR/.agents/" "$TARGET_DIR/.agents/"
|
|
log_ok "Deployed Rules and Skills under target's .agents/"
|
|
|
|
# Copy config templates and generate scripts (M-2)
|
|
if [ -f "$SRC_DIR/.mam.env.example" ]; then
|
|
cp "$SRC_DIR/.mam.env.example" "$TARGET_DIR/.mam.env.example"
|
|
log_ok "Copied .mam.env.example configuration template"
|
|
fi
|
|
|
|
# Deploy remove.sh and update.sh into .mam_deploy/ (R-3)
|
|
mkdir -p "$TARGET_DIR/.mam_deploy"
|
|
if [ -f "$SRC_DIR/deploy/remove.sh" ]; then
|
|
cp "$SRC_DIR/deploy/remove.sh" "$TARGET_DIR/.mam_deploy/remove.sh"
|
|
chmod 0755 "$TARGET_DIR/.mam_deploy/remove.sh"
|
|
fi
|
|
if [ -f "$SRC_DIR/deploy/update.sh" ]; then
|
|
cp "$SRC_DIR/deploy/update.sh" "$TARGET_DIR/.mam_deploy/update.sh"
|
|
chmod 0755 "$TARGET_DIR/.mam_deploy/update.sh"
|
|
fi
|
|
|
|
if [ -f "$SRC_DIR/deploy/generate-env.sh" ]; then
|
|
mkdir -p "$TARGET_DIR/scripts"
|
|
cp "$SRC_DIR/deploy/generate-env.sh" "$TARGET_DIR/scripts/generate-env.sh"
|
|
chmod +x "$TARGET_DIR/scripts/generate-env.sh"
|
|
log_ok "Copied deploy/generate-env.sh helper tool"
|
|
fi
|
|
# Copy the user manual into the target's .agents/ (previously came via rsync of .agents/)
|
|
if [ -f "$SRC_DIR/deploy/INSTALL.md" ]; then
|
|
cp "$SRC_DIR/deploy/INSTALL.md" "$TARGET_DIR/.agents/INSTALL.md"
|
|
log_ok "Copied INSTALL.md user manual into target .agents/"
|
|
fi
|
|
|
|
# Record an install manifest (.mam/install_manifest.txt) and state hashes (R-5, F14)
|
|
log_info "Recording install manifest (.mam/install_manifest.txt)..."
|
|
mkdir -p "$TARGET_DIR/.mam"
|
|
MANIFEST_FILE="$TARGET_DIR/.mam/install_manifest.txt"
|
|
: > "$MANIFEST_FILE"
|
|
( cd "$TARGET_DIR" && find .agents -type f -print ) >> "$MANIFEST_FILE"
|
|
for extra in ".mam.env.example" ".mam_deploy/remove.sh" ".mam_deploy/update.sh" \
|
|
"scripts/generate-env.sh"; do
|
|
if [ -e "$TARGET_DIR/$extra" ]; then
|
|
echo "$extra" >> "$MANIFEST_FILE"
|
|
fi
|
|
done
|
|
log_ok "Recorded $(wc -l < "$MANIFEST_FILE" | tr -d ' ') manifest entries."
|
|
|
|
OWNED_LIST="$TARGET_DIR/.mam/.owned_paths.tmp"
|
|
: > "$OWNED_LIST"
|
|
while IFS= read -r rel; do
|
|
rel="${rel#./}"
|
|
if is_framework_owned "$rel"; then
|
|
echo "$rel" >> "$OWNED_LIST"
|
|
if is_registry_file "$rel"; then
|
|
mkdir -p "$TARGET_DIR/.mam/base/$(dirname "$rel")"
|
|
cp -f "$TARGET_DIR/$rel" "$TARGET_DIR/.mam/base/$rel"
|
|
fi
|
|
fi
|
|
done < <( cd "$TARGET_DIR" && find .agents -type f -print )
|
|
|
|
python3 - "$TARGET_DIR" "$OWNED_LIST" <<'MAMSTATE'
|
|
import hashlib, os, sys
|
|
|
|
target, listing = sys.argv[1], sys.argv[2]
|
|
|
|
def sha(path):
|
|
h = hashlib.sha256()
|
|
with open(path, "rb") as f:
|
|
while chunk := f.read(65536):
|
|
h.update(chunk)
|
|
return h.hexdigest()
|
|
|
|
rows = []
|
|
with open(listing) as f:
|
|
for rel in f:
|
|
rel = rel.strip()
|
|
full = os.path.join(target, rel)
|
|
if rel and os.path.isfile(full):
|
|
rows.append("%s %s\n" % (sha(full), rel))
|
|
|
|
with open(os.path.join(target, ".mam", "asset_hashes.txt"), "w") as f:
|
|
f.writelines(sorted(rows))
|
|
MAMSTATE
|
|
rm -f "$OWNED_LIST"
|
|
log_ok "Recorded asset hashes and registry base under .mam/."
|
|
|
|
# 3. Copy AGENTS.md to root or inject guidelines pointer
|
|
log_info "Configuring developer guidelines (AGENTS.md)..."
|
|
AGENTS_FILE="$TARGET_DIR/AGENTS.md"
|
|
MARKER_START="<!-- BEGIN MAM ORCHESTRATION -->"
|
|
MARKER_END="<!-- END MAM ORCHESTRATION -->"
|
|
|
|
if [ -f "$AGENTS_FILE" ]; then
|
|
if [ "$FORCE" -eq 1 ]; then
|
|
log_warn "AGENTS.md already exists in target project. Backing up and overwriting (--force)..."
|
|
cp "$AGENTS_FILE" "$AGENTS_FILE.bak.$(date +%s)"
|
|
cp "$SRC_DIR/AGENTS.md" "$AGENTS_FILE"
|
|
log_ok "Guidelines overwritten successfully."
|
|
else
|
|
if grep -Fq "$MARKER_START" "$AGENTS_FILE"; then
|
|
log_ok "MAM orchestration guidelines pointer already exists in AGENTS.md."
|
|
else
|
|
echo -e "\n$MARKER_START\n# 🤖 Multi-Agent Orchestration Guidelines\nPlease refer to [.agents/MULTI_AGENT_RULES.md](file://./.agents/MULTI_AGENT_RULES.md) for detailed collaborative rules and state flow constraints.\n$MARKER_END" >> "$AGENTS_FILE"
|
|
log_ok "Injected MAM guidelines pointer to existing AGENTS.md."
|
|
fi
|
|
fi
|
|
else
|
|
cp "$SRC_DIR/AGENTS.md" "$AGENTS_FILE"
|
|
log_ok "Guidelines AGENTS.md copied to project root."
|
|
fi
|
|
|
|
# 4. Gitignore adjustments (R-4: managed block)
|
|
log_info "Registering runtime isolation blocks in .gitignore..."
|
|
GITIGNORE="$TARGET_DIR/.gitignore"
|
|
MAM_GI_START="# >>> MAM managed block (managed by install.sh — do not edit) >>>"
|
|
MAM_GI_END="# <<< MAM managed block <<<"
|
|
|
|
python3 - "$GITIGNORE" "$MAM_GI_START" "$MAM_GI_END" <<'PY'
|
|
import sys, os
|
|
|
|
gi_path, start_marker, end_marker = sys.argv[1], sys.argv[2], sys.argv[3]
|
|
|
|
block_lines = [
|
|
start_marker + "\n",
|
|
"/.venv/\n",
|
|
"/.mam/\n",
|
|
"/.mam_deploy/\n",
|
|
"/.mam.env\n",
|
|
"/.mam.env.*\n",
|
|
"!/.mam.env.example\n",
|
|
"/.cache/multi-agent-mux-monitor/\n",
|
|
"/.mam-skill-backup.*/\n",
|
|
"CURRENT_JOB.md\n",
|
|
end_marker + "\n"
|
|
]
|
|
|
|
lines = []
|
|
if os.path.exists(gi_path):
|
|
with open(gi_path, "r") as f:
|
|
lines = f.readlines()
|
|
|
|
new_lines = []
|
|
in_block = False
|
|
block_inserted = False
|
|
|
|
for line in lines:
|
|
if line.strip() == start_marker:
|
|
in_block = True
|
|
if not block_inserted:
|
|
new_lines.extend(block_lines)
|
|
block_inserted = True
|
|
continue
|
|
if line.strip() == end_marker:
|
|
in_block = False
|
|
continue
|
|
if not in_block:
|
|
new_lines.append(line)
|
|
|
|
if not block_inserted:
|
|
if new_lines and not new_lines[-1].endswith("\n"):
|
|
new_lines[-1] += "\n"
|
|
new_lines.extend(block_lines)
|
|
|
|
with open(gi_path, "w") as f:
|
|
f.writelines(new_lines)
|
|
PY
|
|
log_ok "Registered MAM managed block in .gitignore."
|
|
|
|
# 5. Python Virtual Environment Setup (F-1)
|
|
log_info "Bootstrapping Python virtual environment (.venv) in target..."
|
|
VENV_NAME="$TARGET_DIR/.venv"
|
|
if [ ! -d "$VENV_NAME" ]; then
|
|
python3 -m venv "$VENV_NAME"
|
|
log_ok "Virtual environment created."
|
|
else
|
|
log_info "Virtual environment (.venv) already exists. Skipping creation."
|
|
fi
|
|
|
|
# Upgrade pip and install dependencies inside target venv
|
|
# shellcheck disable=SC1091
|
|
source "$VENV_NAME"/bin/activate
|
|
pip install --upgrade pip
|
|
REQ_FILE="$TARGET_DIR/.agents/skills/multi-agent-mux-delegate-job/requirements.txt"
|
|
if [ -f "$REQ_FILE" ]; then
|
|
log_info "Installing dependencies from $REQ_FILE..."
|
|
pip install -r "$REQ_FILE"
|
|
log_ok "Dependencies installed successfully."
|
|
else
|
|
log_warn "Could not find requirements file: $REQ_FILE. Installing defaults."
|
|
pip install "paho-mqtt>=2.0.0" pyyaml
|
|
fi
|
|
deactivate
|
|
|
|
# Done
|
|
log_ok "MAM Installation completed successfully!"
|
|
cat <<EOF
|
|
|
|
--------------------------------------------------------------------------------
|
|
💡 Quick Start Guide:
|
|
1. Initialize a new isolated session:
|
|
$ bash .agents/skills/multi-agent-mux-create/scripts/create_session.sh \\
|
|
--workspace "$TARGET_DIR" --agent claude --role developer --isolate \\
|
|
--herdr-server multi-agent-mux
|
|
|
|
2. Attach to the running session:
|
|
$ HERDR_SERVER_NAME=multi-agent-mux herdr agent attach <session_name>
|
|
|
|
3. Gracefully stop the session:
|
|
$ bash .agents/skills/multi-agent-mux-stop/scripts/stop_session.sh \\
|
|
--session <session_name> --agent claude
|
|
--------------------------------------------------------------------------------
|
|
EOF
|