feat(deploy): install update/remove scripts into .mam_deploy/ and refine markdown staging

This commit is contained in:
2026-08-04 22:17:59 +09:00
parent 2ff8b2c4a9
commit 68eff79810
7 changed files with 1092 additions and 157 deletions
+119 -33
View File
@@ -11,6 +11,7 @@ set -euo pipefail
TARGET_DIR=""
FORCE=0
PURGE_ENV=0
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
# Parse arguments
while [[ $# -gt 0 ]]; do
@@ -31,7 +32,15 @@ while [[ $# -gt 0 ]]; do
done
if [ -z "$TARGET_DIR" ]; then
TARGET_DIR="$(pwd)"
if [ "$(basename "$SCRIPT_DIR")" = ".mam_deploy" ]; then
TARGET_DIR="$(dirname "$SCRIPT_DIR")"
else
TARGET_DIR="$(pwd)"
fi
else
if [ "$(basename "$TARGET_DIR")" = ".mam_deploy" ]; then
TARGET_DIR="$(dirname "$TARGET_DIR")"
fi
fi
echo "===================================================================="
@@ -46,6 +55,11 @@ fi
cd "$TARGET_DIR"
GI_CREATED=0
if [ -f ".mam/install_state" ]; then
GI_CREATED=$(grep '^gitignore_created=' .mam/install_state 2>/dev/null | cut -d= -f2 || echo 0)
fi
# 1. Non-interactive input safety guard (set -e read crash prevention)
if [ ! -t 0 ] && [ $FORCE -eq 0 ]; then
echo "❌ Error: Non-interactive terminal detected. Please run with -y/--yes/--force." >&2
@@ -66,7 +80,6 @@ if [ -f "$MANIFEST_FILE" ]; then
fi
done < "$MANIFEST_FILE"
else
# Fallback to the core MAM directories to check if any exist
fallback_assets=(
".agents/skills/lib.sh"
".agents/skills/multi-agent-mux-create"
@@ -77,6 +90,7 @@ else
".agents/skills/multi-agent-mux-stop"
".venv"
".mam"
".mam_deploy"
)
for asset in "${fallback_assets[@]}"; do
if [ -e "$asset" ] || [ -h "$asset" ]; then
@@ -95,7 +109,6 @@ fi
if [ $FORCE -eq 0 ]; then
echo "⚠️ WARNING: This will permanently remove the MAM orchestration skills, "
echo " virtual environment (.venv), local metadata (.mam), and docs."
echo " (Your own custom files inside .agents/ will NOT be touched)."
if ! read -p "❓ Are you sure you want to proceed? [y/N]: " -r response; then
response="n"
@@ -114,39 +127,110 @@ delete_asset() {
fi
}
# Check for modified skills before deleting
TS=$(date -u +%Y%m%dT%H%M%SZ)
python3 - ".mam/asset_hashes.txt" "$TS" <<'PY' 2>/dev/null || true
import sys, os, hashlib, shutil
hash_db_path = sys.argv[1]
ts = sys.argv[2]
if not os.path.exists(hash_db_path):
sys.exit(0)
modified = []
with open(hash_db_path, "r") as f:
for line in f:
parts = line.strip().split(None, 1)
if len(parts) == 2:
expected_hash, path = parts[0], parts[1]
if os.path.exists(path):
h = hashlib.sha256()
with open(path, "rb") as pf:
while chunk := pf.read(65536):
h.update(chunk)
if h.hexdigest() != expected_hash:
modified.append(path)
if modified:
backup_dir = f".mam-skill-backup.{ts}"
os.makedirs(backup_dir, exist_ok=True)
for p in modified:
dest = os.path.join(backup_dir, p)
os.makedirs(os.path.dirname(dest), exist_ok=True)
shutil.copy2(p, dest)
print(f"💾 Preserved {len(modified)} modified skill file(s) under {backup_dir}")
PY
# 2. Uninstall files using the manifest if present
if [ ${#manifest_files[@]} -gt 0 ]; then
echo "📜 Manifest found. Reversing installer-created files..."
for f in ${manifest_files[@]+"${manifest_files[@]}"}; do
# Skip .env, .mam.env, and remove.sh for now, they are handled separately
if [ "$f" = ".env" ] || [ "$f" = ".mam.env" ] || [ "$f" = "remove.sh" ]; then
if [ "$f" = ".env" ] || [ "$f" = ".mam.env" ] || [ "$f" = "remove.sh" ] || [ "$f" = ".mam_deploy/remove.sh" ]; then
continue
fi
delete_asset "$f"
done
else
# Fallback: Delete MAM skills manually (only if manifest is missing)
echo "⚠️ No manifest found. Deleting standard MAM skills..."
delete_asset ".agents/skills/lib.sh"
delete_asset ".agents/skills/multi-agent-mux-create"
delete_asset ".agents/skills/multi-agent-mux-delegate-job"
delete_asset ".agents/skills/multi-agent-mux-monitor"
delete_asset ".agents/skills/multi-agent-mux-resume"
delete_asset ".agents/skills/multi-agent-mux-status"
delete_asset ".agents/skills/multi-agent-mux-stop"
fi
else
echo "⚠️ No manifest found. Deleting standard MAM skills..."
delete_asset ".agents/skills/lib.sh"
delete_asset ".agents/skills/multi-agent-mux-create"
delete_asset ".agents/skills/multi-agent-mux-delegate-job"
delete_asset ".agents/skills/multi-agent-mux-monitor"
delete_asset ".agents/skills/multi-agent-mux-resume"
delete_asset ".agents/skills/multi-agent-mux-status"
delete_asset ".agents/skills/multi-agent-mux-stop"
fi
# 3. Clean up empty parent directories under .agents recursively to avoid littering
if [ -d ".agents" ]; then
find .agents -depth -type d -exec rmdir {} + 2>/dev/null || true
fi
# 4. Remove virtual environment, monitor cache, and metadata database
# Clean up .gitignore managed block (C8)
MAM_GI_START="# >>> MAM managed block (managed by install.sh — do not edit) >>>"
MAM_GI_END="# <<< MAM managed block <<<"
if [ -f .gitignore ]; then
python3 - .gitignore "$MAM_GI_START" "$MAM_GI_END" "$GI_CREATED" <<'PY'
import sys, os
gi_path, start_marker, end_marker, gi_created = sys.argv[1], sys.argv[2], sys.argv[3], sys.argv[4] == "1"
if not os.path.exists(gi_path):
sys.exit(0)
with open(gi_path, "r") as f:
lines = f.readlines()
new_lines = []
in_block = False
block_found = False
for line in lines:
if line.strip() == start_marker:
in_block = True
block_found = True
continue
if line.strip() == end_marker:
in_block = False
continue
if not in_block:
new_lines.append(line)
if block_found:
content = "".join(new_lines).strip()
if gi_created and not content:
os.remove(gi_path)
else:
with open(gi_path, "w") as f:
f.writelines(new_lines)
PY
fi
delete_asset ".venv"
delete_asset ".cache/multi-agent-mux-monitor"
delete_asset ".mam" # Deletes manifest file too
delete_asset ".mam"
# 5. Clean up .env and .mam.env files (Only if created by installer, or forced with --purge-env)
for env_name in ".mam.env" ".env"; do
[ -f "$env_name" ] || continue
@@ -197,7 +281,6 @@ for env_name in ".mam.env" ".env"; do
fi
mv "$env_name" "$slot"
echo "💾 Backed up $env_name -> $slot"
echo " To remove the configuration entirely, re-run with --purge-env."
fi
else
echo "️ Preserving user-owned $env_name configuration."
@@ -205,21 +288,24 @@ for env_name in ".mam.env" ".env"; do
fi
done
# 6. Remove uninstaller file itself (if we are in the target root)
# Simple check: only delete remove.sh if it is recorded in the manifest
remove_in_manifest=0
for f in ${manifest_files[@]+"${manifest_files[@]}"}; do
if [ "$f" = "remove.sh" ]; then
remove_in_manifest=1
break
# Remove uninstaller file(s)
for self in ".mam_deploy/remove.sh" "remove.sh"; do
[ -f "$self" ] || continue
in_manifest=0
for f in ${manifest_files[@]+"${manifest_files[@]}"}; do
if [ "$f" = "$self" ]; then
in_manifest=1
break
fi
done
if [ $in_manifest -eq 1 ] || [ $FORCE -eq 1 ]; then
echo "🗑️ Removing uninstaller: $self"
rm -f "$self"
fi
done
if [ -f "remove.sh" ] && [ $remove_in_manifest -eq 1 ]; then
echo "🗑️ Removing uninstaller: remove.sh"
# Self-delete is the final action
rm -f "remove.sh"
fi
delete_asset ".mam_deploy/update.sh"
rmdir .mam_deploy 2>/dev/null || true
echo "===================================================================="
echo "🎉 Uninstallation complete!"