#!/usr/bin/env bash # # AIPass setup script # Creates a venv, installs the package in editable mode, and verifies CLI entry points. # set -euo pipefail # cd to repo root (where this script lives) so it works from anywhere SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" cd "$SCRIPT_DIR" # --- OS detection --- # Detect Windows (Git Bash / MSYS2 / Cygwin) — used throughout the script IS_WINDOWS=0 case "${OSTYPE:-}" in msys*|cygwin*|mingw*) IS_WINDOWS=1 ;; *) # Fallback: check uname if OSTYPE is unset if uname -s 2>/dev/null | grep -qi "mingw\|msys\|cygwin"; then IS_WINDOWS=1 fi ;; esac echo "=== AIPass Setup ===" echo "Repo root: $SCRIPT_DIR" echo "" # --- Check python3 exists --- if ! command -v python3 &>/dev/null; then echo "FAIL: python3 not found. Install Python 3.10+ and try again." exit 1 fi PY_VERSION=$(python3 -c 'import sys; print(f"{sys.version_info.major}.{sys.version_info.minor}")') echo "Found python3 $PY_VERSION" # --- Check minimum version --- PY_OK=$(python3 -c 'import sys; print(int(sys.version_info >= (3, 10)))') if [ "$PY_OK" != "1" ]; then echo "FAIL: Python 3.10+ required, found $PY_VERSION" exit 1 fi # --- Create venv --- if [ -d ".venv" ]; then echo "Existing .venv found — removing it for a clean install." rm -rf .venv fi echo "Creating virtual environment at .venv ..." python3 -m venv .venv # --- Activate and install --- # Activate venv (Scripts/ on Windows, bin/ on Linux/macOS) if [ "$IS_WINDOWS" -eq 1 ] && [ -f ".venv/Scripts/activate" ]; then source .venv/Scripts/activate else source .venv/bin/activate fi echo "Upgrading pip ..." pip install --upgrade pip --quiet echo "Installing aipass in editable mode (with dev extras) ..." pip install -e ".[dev]" --quiet # --- Verify CLI entry points --- FAIL=0 echo "" echo "Verifying entry points ..." if drone --help &>/dev/null; then echo " drone ... ok" else echo " drone ... FAILED" FAIL=1 fi # seedgo is accessed via drone @seedgo, not as a standalone CLI if drone @seedgo --help &>/dev/null; then echo " seedgo ... ok (via drone @seedgo)" else echo " seedgo ... FAILED" FAIL=1 fi # --- Create secrets directory --- SECRETS_DIR="$HOME/.secrets/aipass" if [ ! -d "$SECRETS_DIR" ]; then echo "Creating secrets directory at $SECRETS_DIR ..." mkdir -p "$SECRETS_DIR" chmod 700 "$HOME/.secrets" echo " ~/.secrets/aipass/ ... created" else echo "Secrets directory already exists — skipping" fi # --- Seed .env template into secrets --- if [ ! -f "$SECRETS_DIR/.env" ] && [ -f ".env.example" ]; then cp .env.example "$SECRETS_DIR/.env" echo " Copied .env.example → ~/.secrets/aipass/.env (add your API keys there)" fi # --- Generate branch registry --- if [ ! -f "AIPASS_REGISTRY.json" ]; then echo "Generating AIPASS_REGISTRY.json ..." python3 - "$SCRIPT_DIR" << 'PYEOF' import json, sys, os from pathlib import Path from datetime import date repo_root = sys.argv[1] src_dir = Path(repo_root) / "src" / "aipass" today = date.today().isoformat() branches = [] # Discover modules under src/aipass/ for d in sorted(src_dir.iterdir()): if d.is_dir() and not d.name.startswith(("_", ".")): branches.append({ "name": d.name, "path": str(d), "profile": "library", "description": "", "email": f"@{d.name}", "status": "active", "created": today, "last_active": today, }) # Add external branches: commons and skills for ext_name in ["commons", "skills"]: ext_path = Path(repo_root) / "src" / ext_name if ext_path.is_dir(): branches.append({ "name": ext_name, "path": str(ext_path), "profile": "library", "description": "", "email": f"@{ext_name}", "status": "active", "created": today, "last_active": today, }) registry = { "metadata": { "version": "1.0.0", "last_updated": today, "total_branches": len(branches), }, "branches": branches, } out = Path(repo_root) / "AIPASS_REGISTRY.json" out.write_text(json.dumps(registry, indent=2) + "\n") print(f" {len(branches)} branches registered") PYEOF else echo "AIPASS_REGISTRY.json already exists — skipping" fi # --- Bootstrap branch identity and memory files --- echo "" echo "Bootstrapping branch identity files ..." DATE_TODAY=$(date +%Y-%m-%d) bootstrap_branch() { local name="$1" local path="$2" local citizen_class="$3" local role="$4" local created=0 # .trinity/passport.json mkdir -p "$path/.trinity" if [ ! -f "$path/.trinity/passport.json" ]; then cat > "$path/.trinity/passport.json" << JSONEOF { "document_metadata": { "document_type": "identity", "document_name": "${name}.PASSPORT", "version": "1.0.0", "schema_version": "1.0.0", "created": "${DATE_TODAY}", "last_updated": "${DATE_TODAY}", "managed_by": "${name}" }, "identity": { "name": "${name}", "citizen_class": "${citizen_class}", "role": "${role}", "status": "active" } } JSONEOF created=1 fi # .trinity/local.json if [ ! -f "$path/.trinity/local.json" ]; then cat > "$path/.trinity/local.json" << JSONEOF { "document_metadata": { "document_type": "session_history", "document_name": "${name}.LOCAL", "version": "1.0.0", "schema_version": "1.0.0", "created": "${DATE_TODAY}", "last_updated": "${DATE_TODAY}", "managed_by": "${name}", "tags": ["session_tracking", "work_log", "${name}"], "limits": {"max_lines": 600, "note": "Auto-rollover when max_lines exceeded"}, "status": {"health": "healthy", "current_lines": 0, "last_health_check": "${DATE_TODAY}"} }, "active_tasks": { "today_focus": "First session — explore codebase and capabilities", "recently_completed": [] }, "key_learnings": {}, "sessions": [] } JSONEOF created=1 fi # .trinity/observations.json if [ ! -f "$path/.trinity/observations.json" ]; then cat > "$path/.trinity/observations.json" << JSONEOF { "document_metadata": { "document_type": "collaboration_patterns", "document_name": "${name}.OBSERVATIONS", "version": "1.0.0", "schema_version": "1.0.0", "created": "${DATE_TODAY}", "last_updated": "${DATE_TODAY}", "managed_by": "${name}", "tags": ["collaboration", "patterns", "${name}"], "limits": {"max_lines": 600, "note": "Auto-rollover when max_lines exceeded"}, "status": {"health": "healthy", "current_lines": 0, "last_health_check": "${DATE_TODAY}"} }, "guidelines": { "purpose": "Capture collaboration patterns and experiential insights over time", "chronological_order": "Newest entries at TOP, oldest at BOTTOM - NEVER reorder" }, "observations": [ { "date": "${DATE_TODAY}", "session": 1, "entries": [ {"title": "First Contact", "detail": "Branch initialized. Ready to begin capturing collaboration patterns."} ] } ] } JSONEOF created=1 fi # .seedgo/bypass.json mkdir -p "$path/.seedgo" if [ ! -f "$path/.seedgo/bypass.json" ]; then echo '{}' > "$path/.seedgo/bypass.json" created=1 fi # .ai_mail.local/inbox.json mkdir -p "$path/.ai_mail.local" if [ ! -f "$path/.ai_mail.local/inbox.json" ]; then echo '{"inbox": []}' > "$path/.ai_mail.local/inbox.json" created=1 fi if [ "$created" -eq 1 ]; then echo " @${name} ... bootstrapped" else echo " @${name} ... exists (skipped)" fi } # Branches inside src/aipass/ bootstrap_branch "drone" "$SCRIPT_DIR/src/aipass/drone" "builder" "Command routing and module discovery" bootstrap_branch "seedgo" "$SCRIPT_DIR/src/aipass/seedgo" "builder" "Standards enforcement and code auditing" bootstrap_branch "prax" "$SCRIPT_DIR/src/aipass/prax" "builder" "Logging and monitoring system" bootstrap_branch "cli" "$SCRIPT_DIR/src/aipass/cli" "builder" "Display formatting service" bootstrap_branch "flow" "$SCRIPT_DIR/src/aipass/flow" "builder" "Workflow and plan management" bootstrap_branch "ai_mail" "$SCRIPT_DIR/src/aipass/ai_mail" "builder" "Inter-agent messaging and dispatch" bootstrap_branch "api" "$SCRIPT_DIR/src/aipass/api" "builder" "LLM access and model routing" bootstrap_branch "trigger" "$SCRIPT_DIR/src/aipass/trigger" "builder" "Event-driven automation" bootstrap_branch "spawn" "$SCRIPT_DIR/src/aipass/spawn" "builder" "Branch lifecycle management" bootstrap_branch "devpulse" "$SCRIPT_DIR/src/aipass/devpulse" "manager" "Orchestration hub and coordination" bootstrap_branch "backup" "$SCRIPT_DIR/src/aipass/backup" "builder" "Multi-mode backup system" bootstrap_branch "daemon" "$SCRIPT_DIR/src/aipass/daemon" "builder" "Background scheduler" bootstrap_branch "memory" "$SCRIPT_DIR/src/aipass/memory" "builder" "Vector memory bank" # External branches bootstrap_branch "commons" "$SCRIPT_DIR/src/commons" "builder" "Social network for branches" bootstrap_branch "skills" "$SCRIPT_DIR/src/skills" "builder" "Capability framework" echo " 15 branches bootstrapped" # --- Install Claude Code hooks --- CLAUDE_SETTINGS="$HOME/.claude/settings.json" if [ -d "$SCRIPT_DIR/.claude/hooks" ]; then echo "Installing Claude Code hooks ..." mkdir -p "$HOME/.claude" python3 - "$SCRIPT_DIR" "$CLAUDE_SETTINGS" << 'PYEOF' import json import sys from pathlib import Path repo_root = sys.argv[1] settings_path = Path(sys.argv[2]) hooks_dir = f"{repo_root}/.claude/hooks" # Load existing settings or start fresh if settings_path.exists(): settings = json.loads(settings_path.read_text()) else: settings = {} # Build hooks config with absolute paths settings["hooks"] = { "UserPromptSubmit": [ {"hooks": [{"type": "command", "command": f"cat {repo_root}/.aipass/aipass_global_prompt.md 2>/dev/null || true"}]}, {"hooks": [{"type": "command", "command": f"python3 {hooks_dir}/branch_prompt_loader.py"}]}, {"hooks": [{"type": "command", "command": f"python3 {hooks_dir}/identity_injector.py"}]}, {"hooks": [{"type": "command", "command": f"python3 {hooks_dir}/email_notification.py"}]}, ], "PreToolUse": [ {"matcher": "Bash|Edit|MultiEdit|Write|Read|Grep|Glob|WebSearch|WebFetch|Task", "hooks": [{"type": "command", "command": f"python3 {hooks_dir}/tool_use_sound.py"}]}, ], "PostToolUse": [ {"matcher": "Edit|MultiEdit|Write|NotebookEdit", "hooks": [{"type": "command", "command": f"python3 {hooks_dir}/auto_fix_diagnostics.py"}]}, ], "Stop": [ {"hooks": [{"type": "command", "command": f"python3 {hooks_dir}/stop_sound.py"}]}, ], "Notification": [ {"hooks": [{"type": "command", "command": f"python3 {hooks_dir}/notification_sound.py"}]}, ], "PreCompact": [ {"matcher": "manual", "hooks": [{"type": "command", "command": f"python3 {hooks_dir}/pre_compact.py", "timeout": 60}]}, {"matcher": "auto", "hooks": [{"type": "command", "command": f"python3 {hooks_dir}/pre_compact.py", "timeout": 60}]}, ], } settings_path.write_text(json.dumps(settings, indent=2) + "\n") print(f" hooks -> {settings_path}") PYEOF else echo "Skipping hooks (no .claude/hooks/ directory found)" fi # --- Install Codex CLI hooks --- if command -v codex &>/dev/null; then if [ -f "$SCRIPT_DIR/.codex/hooks.json" ]; then echo "Installing Codex CLI hooks ..." mkdir -p "$HOME/.codex" python3 - "$SCRIPT_DIR" "$HOME/.codex/config.toml" << 'PYEOF' import sys from pathlib import Path repo_root = sys.argv[1] config_path = Path(sys.argv[2]) # Read existing config or start fresh existing = {} if config_path.exists(): for line in config_path.read_text().splitlines(): line = line.strip() if line and not line.startswith("[") and not line.startswith("#") and "=" in line: key, val = line.split("=", 1) existing[key.strip()] = val.strip() # Preserve model if set model = existing.get("model", '"o4-mini"') config = f'''model = {model} check_for_update_on_startup = false [features] codex_hooks = true [experimental_features] multi_agent = true multi_agent_v2 = true [projects."{repo_root}"] trust_level = "trusted" ''' config_path.write_text(config) print(f" config.toml -> {config_path}") print(f" hooks.json -> {repo_root}/.codex/hooks.json (project-level, travels with repo)") PYEOF else echo "Skipping Codex hooks (no .codex/hooks.json found in repo)" fi else echo "Skipping Codex CLI (not installed)" fi # --- Install Gemini CLI hooks --- if command -v gemini &>/dev/null; then if [ -d "$SCRIPT_DIR/.gemini/hooks" ]; then echo "Installing Gemini CLI hooks ..." GEMINI_SETTINGS="$HOME/.gemini/settings.json" mkdir -p "$HOME/.gemini" python3 - "$SCRIPT_DIR" "$GEMINI_SETTINGS" << 'PYEOF' import json import sys from pathlib import Path repo_root = sys.argv[1] settings_path = Path(sys.argv[2]) hooks_dir = f"{repo_root}/.gemini/hooks" # Load existing settings or start fresh if settings_path.exists(): settings = json.loads(settings_path.read_text()) else: settings = {} # Build hooks config with absolute paths (Gemini uses different event names) settings["hooks"] = { "SessionStart": [ {"hooks": [{"type": "command", "command": f"python3 {hooks_dir}/session_start_identity.py", "timeout": 10}]} ], "BeforeModel": [ {"hooks": [{"type": "command", "command": f"python3 {hooks_dir}/prompt_inject.py", "timeout": 10}]} ], "BeforeTool": [ {"matcher": "Edit|Write", "hooks": [{"type": "command", "command": f"python3 {hooks_dir}/pre_edit_gate.py", "timeout": 5}]} ], } settings_path.write_text(json.dumps(settings, indent=2) + "\n") print(f" hooks -> {settings_path}") PYEOF else echo "Skipping Gemini hooks (no .gemini/hooks/ directory found in repo)" fi else echo "Skipping Gemini CLI (not installed)" fi # --- Create global symlinks for CLI tools (Linux/macOS only) --- echo "" if [ "$IS_WINDOWS" -eq 1 ]; then echo "Windows detected — skipping symlink (ln/sudo not available on Windows)" echo "" echo "To use drone from any directory, add the venv to your PATH:" echo " PowerShell: \$env:PATH = \"$SCRIPT_DIR\\.venv\\Scripts;\" + \$env:PATH" echo " Git Bash: export PATH=\"$SCRIPT_DIR/.venv/bin:\$PATH\"" echo " (Add to ~/.bash_profile for Git Bash persistence)" else echo "Creating global symlinks ..." VENV_BIN="$SCRIPT_DIR/.venv/bin" LOCAL_BIN="/usr/local/bin" for cmd in drone; do if [ -f "$VENV_BIN/$cmd" ]; then if sudo ln -sf "$VENV_BIN/$cmd" "$LOCAL_BIN/$cmd" 2>/dev/null; then echo " $LOCAL_BIN/$cmd -> $VENV_BIN/$cmd" else echo " WARN: Could not create symlink for $cmd (try running with sudo)" echo " Manual fix: sudo ln -sf $VENV_BIN/$cmd $LOCAL_BIN/$cmd" fi fi done fi # --- Result --- echo "" if [ "$FAIL" -eq 0 ]; then echo "=== Setup complete ===" echo "" if [ "$IS_WINDOWS" -eq 1 ]; then echo "drone is available in .venv/Scripts/ (or .venv/bin/ for Git Bash)." echo "Add the appropriate directory to your PATH (see above)." else echo "drone is available globally via /usr/local/bin symlink." fi echo "seedgo is accessed via: drone @seedgo" echo "No venv activation needed for CLI commands." echo "" echo "CLI integrations:" echo " Claude Code: hooks installed to ~/.claude/settings.json" command -v codex &>/dev/null && echo " Codex CLI: hooks at .codex/hooks.json + config at ~/.codex/config.toml" command -v gemini &>/dev/null && echo " Gemini CLI: hooks installed to ~/.gemini/settings.json" echo "" else echo "=== Setup finished with errors ===" echo "The venv was created and the package was installed, but one or more" echo "CLI entry points failed verification. Check the output above." exit 1 fi