mirror of
https://github.com/samvallad33/vestige.git
synced 2026-04-28 10:26:22 +02:00
feat: Xcode 26.3 integration — install script, updated docs, blog post
Discovered that Xcode's claudeai-mcp feature gate blocks custom MCP servers from the global .claude config. Project-level .mcp.json files bypass this gate entirely — this is now the documented method. - Add scripts/xcode-setup.sh: auto-installs vestige-mcp and configures .mcp.json for any Xcode project with checksum verification - Rewrite docs/integrations/xcode.md with .mcp.json method, gate bypass docs, and new troubleshooting for "Agent has been closed" error - Add docs/blog/xcode-memory.md: launch blog post for Vestige on Xcode
This commit is contained in:
parent
1bc4a762dc
commit
33d8b6b405
3 changed files with 527 additions and 110 deletions
265
scripts/xcode-setup.sh
Executable file
265
scripts/xcode-setup.sh
Executable file
|
|
@ -0,0 +1,265 @@
|
|||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
|
||||
# Vestige Xcode Setup
|
||||
# Gives Xcode's AI agent persistent memory in 30 seconds.
|
||||
# https://github.com/samvallad33/vestige
|
||||
|
||||
VESTIGE_VERSION="latest"
|
||||
BINARY_NAME="vestige-mcp"
|
||||
MCP_CONFIG='.mcp.json'
|
||||
|
||||
# Colors
|
||||
RED='\033[0;31m'
|
||||
GREEN='\033[0;32m'
|
||||
YELLOW='\033[1;33m'
|
||||
BLUE='\033[0;34m'
|
||||
BOLD='\033[1m'
|
||||
NC='\033[0m'
|
||||
|
||||
print_step() { echo -e "\n${BLUE}▸${NC} ${BOLD}$1${NC}"; }
|
||||
print_ok() { echo -e " ${GREEN}✓${NC} $1"; }
|
||||
print_warn() { echo -e " ${YELLOW}!${NC} $1"; }
|
||||
print_err() { echo -e " ${RED}✗${NC} $1"; }
|
||||
|
||||
echo -e "${BOLD}"
|
||||
echo " ╦ ╦┌─┐┌─┐┌┬┐┬┌─┐┌─┐"
|
||||
echo " ╚╗╔╝├┤ └─┐ │ ││ ┬├┤ "
|
||||
echo " ╚╝ └─┘└─┘ ┴ ┴└─┘└─┘"
|
||||
echo -e "${NC}"
|
||||
echo " Memory for Xcode's AI Agent"
|
||||
echo ""
|
||||
|
||||
# --- Step 1: Detect or install vestige-mcp ---
|
||||
print_step "Checking for vestige-mcp..."
|
||||
|
||||
VESTIGE_PATH=""
|
||||
for p in /usr/local/bin/vestige-mcp "$HOME/.local/bin/vestige-mcp" "$HOME/.cargo/bin/vestige-mcp"; do
|
||||
if [ -x "$p" ]; then
|
||||
VESTIGE_PATH="$p"
|
||||
break
|
||||
fi
|
||||
done
|
||||
|
||||
if [ -n "$VESTIGE_PATH" ]; then
|
||||
VERSION=$("$VESTIGE_PATH" --version 2>/dev/null || echo "unknown")
|
||||
print_ok "Found: $VESTIGE_PATH ($VERSION)"
|
||||
else
|
||||
print_warn "vestige-mcp not found. Installing..."
|
||||
|
||||
ARCH=$(uname -m)
|
||||
OS=$(uname -s)
|
||||
|
||||
if [ "$OS" = "Darwin" ] && [ "$ARCH" = "arm64" ]; then
|
||||
TARBALL="vestige-mcp-aarch64-apple-darwin.tar.gz"
|
||||
elif [ "$OS" = "Darwin" ] && [ "$ARCH" = "x86_64" ]; then
|
||||
TARBALL="vestige-mcp-x86_64-apple-darwin.tar.gz"
|
||||
elif [ "$OS" = "Linux" ] && [ "$ARCH" = "x86_64" ]; then
|
||||
TARBALL="vestige-mcp-x86_64-unknown-linux-gnu.tar.gz"
|
||||
else
|
||||
print_err "Unsupported platform: $OS/$ARCH"
|
||||
echo " Install manually: https://github.com/samvallad33/vestige#install"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
URL="https://github.com/samvallad33/vestige/releases/latest/download/$TARBALL"
|
||||
CHECKSUM_URL="${URL}.sha256"
|
||||
VESTIGE_TMPDIR=$(mktemp -d)
|
||||
trap 'rm -rf "$VESTIGE_TMPDIR"' EXIT
|
||||
|
||||
echo " Downloading $TARBALL..."
|
||||
curl -fsSL "$URL" -o "$VESTIGE_TMPDIR/$TARBALL"
|
||||
|
||||
# Verify checksum if available
|
||||
if curl -fsSL "$CHECKSUM_URL" -o "$VESTIGE_TMPDIR/$TARBALL.sha256" 2>/dev/null; then
|
||||
echo " Verifying checksum..."
|
||||
(cd "$VESTIGE_TMPDIR" && shasum -a 256 -c "$TARBALL.sha256")
|
||||
print_ok "Checksum verified"
|
||||
else
|
||||
print_warn "No checksum file found — skipping verification"
|
||||
fi
|
||||
|
||||
# Extract and verify binary
|
||||
tar -xz -f "$VESTIGE_TMPDIR/$TARBALL" -C "$VESTIGE_TMPDIR"
|
||||
|
||||
if [ ! -f "$VESTIGE_TMPDIR/vestige-mcp" ] || [ ! -s "$VESTIGE_TMPDIR/vestige-mcp" ]; then
|
||||
print_err "Download appears corrupt — vestige-mcp not found in tarball"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if ! file "$VESTIGE_TMPDIR/vestige-mcp" | grep -q "Mach-O\|ELF"; then
|
||||
print_err "Downloaded file is not a valid binary"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
chmod +x "$VESTIGE_TMPDIR/vestige-mcp"
|
||||
|
||||
# Prefer user-local install, fall back to /usr/local/bin with sudo
|
||||
INSTALL_DIR="$HOME/.local/bin"
|
||||
if [ -w "/usr/local/bin" ]; then
|
||||
INSTALL_DIR="/usr/local/bin"
|
||||
elif [ ! -d "$INSTALL_DIR" ]; then
|
||||
mkdir -p "$INSTALL_DIR"
|
||||
fi
|
||||
|
||||
if [ "$INSTALL_DIR" = "/usr/local/bin" ] && [ ! -w "$INSTALL_DIR" ]; then
|
||||
echo ""
|
||||
echo " Install location: $INSTALL_DIR (requires sudo)"
|
||||
read -rp " Continue? (y/N): " confirm
|
||||
if [ "$confirm" != "y" ] && [ "$confirm" != "Y" ]; then
|
||||
INSTALL_DIR="$HOME/.local/bin"
|
||||
mkdir -p "$INSTALL_DIR"
|
||||
print_warn "Installing to $INSTALL_DIR instead"
|
||||
else
|
||||
sudo mv "$VESTIGE_TMPDIR/vestige-mcp" "$INSTALL_DIR/"
|
||||
[ -f "$VESTIGE_TMPDIR/vestige" ] && sudo mv "$VESTIGE_TMPDIR/vestige" "$INSTALL_DIR/"
|
||||
[ -f "$VESTIGE_TMPDIR/vestige-restore" ] && sudo mv "$VESTIGE_TMPDIR/vestige-restore" "$INSTALL_DIR/"
|
||||
fi
|
||||
fi
|
||||
|
||||
if [ "$INSTALL_DIR" != "/usr/local/bin" ] || [ -w "$INSTALL_DIR" ]; then
|
||||
mv "$VESTIGE_TMPDIR/vestige-mcp" "$INSTALL_DIR/" 2>/dev/null || true
|
||||
[ -f "$VESTIGE_TMPDIR/vestige" ] && mv "$VESTIGE_TMPDIR/vestige" "$INSTALL_DIR/" 2>/dev/null || true
|
||||
[ -f "$VESTIGE_TMPDIR/vestige-restore" ] && mv "$VESTIGE_TMPDIR/vestige-restore" "$INSTALL_DIR/" 2>/dev/null || true
|
||||
fi
|
||||
|
||||
VESTIGE_PATH="$INSTALL_DIR/vestige-mcp"
|
||||
VERSION=$("$VESTIGE_PATH" --version 2>/dev/null || echo "unknown")
|
||||
print_ok "Installed: $VESTIGE_PATH ($VERSION)"
|
||||
fi
|
||||
|
||||
# --- Helper: generate .mcp.json content ---
|
||||
generate_mcp_json() {
|
||||
local cmd_path="$1"
|
||||
# Escape backslashes and double quotes for valid JSON
|
||||
local escaped_path
|
||||
escaped_path=$(printf '%s' "$cmd_path" | sed 's/\\/\\\\/g; s/"/\\"/g')
|
||||
cat << MCPEOF
|
||||
{
|
||||
"mcpServers": {
|
||||
"vestige": {
|
||||
"type": "stdio",
|
||||
"command": "$escaped_path",
|
||||
"args": [],
|
||||
"env": {
|
||||
"PATH": "/usr/local/bin:/usr/bin:/bin"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
MCPEOF
|
||||
}
|
||||
|
||||
# --- Step 2: Find or select project ---
|
||||
print_step "Finding Xcode projects..."
|
||||
|
||||
PROJECT_DIR=""
|
||||
|
||||
if [ -n "${1:-}" ]; then
|
||||
# User passed a project path
|
||||
PROJECT_DIR="$(cd "$1" && pwd)"
|
||||
print_ok "Using: $PROJECT_DIR"
|
||||
elif ls "$(pwd)/"*.xcodeproj >/dev/null 2>&1 || [ -f "$(pwd)/Package.swift" ]; then
|
||||
PROJECT_DIR="$(pwd)"
|
||||
print_ok "Current directory: $PROJECT_DIR"
|
||||
else
|
||||
# Search for projects
|
||||
PROJECTS=$(find "$HOME/Developer" -maxdepth 4 \( -name "*.xcodeproj" -o -name "Package.swift" \) 2>/dev/null | head -20)
|
||||
|
||||
if [ -z "$PROJECTS" ]; then
|
||||
print_warn "No Xcode projects found in ~/Developer"
|
||||
echo ""
|
||||
echo " Usage: $0 /path/to/your/xcode/project"
|
||||
echo ""
|
||||
echo " Or run from inside your project directory:"
|
||||
echo " cd /path/to/project && $0"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo " Found projects:"
|
||||
i=1
|
||||
declare -a PROJECT_LIST=()
|
||||
while IFS= read -r proj; do
|
||||
dir=$(dirname "$proj")
|
||||
if [ -d "$dir" ]; then
|
||||
PROJECT_LIST+=("$dir")
|
||||
echo " $i) $dir"
|
||||
((i++))
|
||||
fi
|
||||
done <<< "$PROJECTS"
|
||||
|
||||
max_choice=$((i - 1))
|
||||
echo ""
|
||||
read -rp " Select project (1-$max_choice), or 'a' for all: " choice
|
||||
|
||||
if [ "$choice" = "a" ]; then
|
||||
for dir in "${PROJECT_LIST[@]}"; do
|
||||
if [ ! -f "$dir/$MCP_CONFIG" ]; then
|
||||
generate_mcp_json "$VESTIGE_PATH" > "$dir/$MCP_CONFIG"
|
||||
print_ok "$dir/$MCP_CONFIG"
|
||||
else
|
||||
print_warn "$dir/$MCP_CONFIG (already exists, skipped)"
|
||||
fi
|
||||
done
|
||||
|
||||
echo ""
|
||||
echo -e "${GREEN}${BOLD}Done!${NC} Vestige added to ${#PROJECT_LIST[@]} projects."
|
||||
echo ""
|
||||
echo " Restart Xcode (Cmd+Q) and type /context to verify."
|
||||
exit 0
|
||||
elif [[ "$choice" =~ ^[0-9]+$ ]] && [ "$choice" -ge 1 ] && [ "$choice" -le "$max_choice" ]; then
|
||||
idx=$((choice - 1))
|
||||
PROJECT_DIR="${PROJECT_LIST[$idx]}"
|
||||
print_ok "Selected: $PROJECT_DIR"
|
||||
else
|
||||
print_err "Invalid selection: $choice"
|
||||
exit 1
|
||||
fi
|
||||
fi
|
||||
|
||||
# --- Step 3: Create .mcp.json ---
|
||||
print_step "Creating $MCP_CONFIG..."
|
||||
|
||||
CONFIG_PATH="$PROJECT_DIR/$MCP_CONFIG"
|
||||
|
||||
if [ -f "$CONFIG_PATH" ]; then
|
||||
print_warn "$CONFIG_PATH already exists"
|
||||
read -rp " Overwrite? (y/N): " overwrite
|
||||
if [ "$overwrite" != "y" ] && [ "$overwrite" != "Y" ]; then
|
||||
echo " Skipped."
|
||||
exit 0
|
||||
fi
|
||||
cp "$CONFIG_PATH" "$CONFIG_PATH.bak"
|
||||
print_ok "Backed up existing config to $CONFIG_PATH.bak"
|
||||
fi
|
||||
|
||||
generate_mcp_json "$VESTIGE_PATH" > "$CONFIG_PATH"
|
||||
print_ok "Created $CONFIG_PATH"
|
||||
|
||||
# --- Step 4: Verify ---
|
||||
print_step "Verifying vestige-mcp starts..."
|
||||
|
||||
if "$VESTIGE_PATH" --version >/dev/null 2>&1; then
|
||||
print_ok "vestige-mcp binary OK"
|
||||
else
|
||||
print_err "vestige-mcp failed to start — check the binary"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# --- Done ---
|
||||
echo ""
|
||||
echo -e "${GREEN}${BOLD}Vestige is ready for Xcode.${NC}"
|
||||
echo ""
|
||||
echo " Next steps:"
|
||||
echo " 1. Restart Xcode (Cmd+Q, then reopen)"
|
||||
echo " 2. Open your project"
|
||||
echo " 3. Type /context in the Agent panel"
|
||||
echo " 4. You should see vestige listed with 23 tools"
|
||||
echo ""
|
||||
echo " Try it:"
|
||||
echo " \"Remember that this project uses SwiftUI with MVVM architecture\""
|
||||
echo " (new session) → \"What architecture does this project use?\""
|
||||
echo " It remembers."
|
||||
echo ""
|
||||
echo -e " ${BLUE}https://github.com/samvallad33/vestige${NC}"
|
||||
echo ""
|
||||
Loading…
Add table
Add a link
Reference in a new issue