* feat(system): fix(windows-ci): combine pip bootstrap fix + venv activation in Verify step Combines both fixes needed to get windows-test.yml actually passing: 1. setup.sh: stop swallowing ensurepip errors (quiet + 2>/dev/null + || true was hiding real failures — pip was silently not installed). Add get-pip.py fallback and hard pip verification. 2. windows-test.yml: add 'source .venv/Scripts/activate' to Verify step. Each CI step gets a fresh shell — setup.sh's venv activation doesn't carry over, so drone wasn't on PATH in the subsequent step. Together, these should take Windows CI from the 'silent failure every run since creation' state to actually green. Supersedes PRs #330 and #331 which had the fixes on separate branches (neither green alone). Co-Authored-By: @devpulse <devpulse@aipass> * feat(system): chore(lint): ruff auto-fix sweep — 303 errors across 178 files (F401 unused imports + F541 f-string placeholders + F811 redefined); restored report_error re-export + added logger call in errors.py Co-Authored-By: @devpulse <devpulse@aipass> --------- Co-authored-by: @devpulse <devpulse@aipass>
606 lines
20 KiB
Bash
Executable File
606 lines
20 KiB
Bash
Executable File
#!/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 ""
|
|
|
|
# --- Strip broken venv from PATH (Windows: stale .venv/Scripts shadows real Python) ---
|
|
if [ "$IS_WINDOWS" -eq 1 ]; then
|
|
export PATH=$(echo "$PATH" | tr ':' '\n' | grep -v '\.venv' | tr '\n' ':' | sed 's/:$//')
|
|
export PYTHONUTF8=1
|
|
fi
|
|
|
|
# --- Find working Python (#292: Windows python3 → MS Store alias) ---
|
|
PYTHON=""
|
|
# Try python3 first — verify it actually runs (not just exists on PATH)
|
|
if command -v python3 &>/dev/null && python3 -c "import sys" &>/dev/null 2>&1; then
|
|
PYTHON="python3"
|
|
# Fall back to python (Windows installs as 'python' not 'python3')
|
|
elif command -v python &>/dev/null && python -c "import sys" &>/dev/null 2>&1; then
|
|
PYTHON="python"
|
|
else
|
|
echo "FAIL: No working Python found. Install Python 3.10+ and try again."
|
|
echo " Windows: install from python.org, NOT the Microsoft Store."
|
|
echo " Then disable the Store alias: Settings > Apps > Advanced app settings > App execution aliases"
|
|
exit 1
|
|
fi
|
|
|
|
PY_VERSION=$($PYTHON -c 'import sys; print(f"{sys.version_info.major}.{sys.version_info.minor}")')
|
|
echo "Found $PYTHON $PY_VERSION"
|
|
|
|
# --- Check minimum version ---
|
|
PY_OK=$($PYTHON -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 [ "$IS_WINDOWS" -eq 1 ] && [ -f ".venv/Scripts/python.exe" ]; then
|
|
# Windows: skip venv recreation if python.exe exists (rm -rf unreliable due to file locking)
|
|
echo "Existing .venv found — reusing (Windows file locking prevents clean removal)"
|
|
elif [ -d ".venv" ]; then
|
|
echo "Existing .venv found — removing it for a clean install."
|
|
rm -rf .venv
|
|
fi
|
|
|
|
if [ ! -d ".venv" ]; then
|
|
echo "Creating virtual environment at .venv ..."
|
|
if [ "$IS_WINDOWS" -eq 1 ]; then
|
|
# Windows: create without pip, bootstrap manually to avoid subprocess path issues
|
|
$PYTHON -m venv --without-pip .venv
|
|
else
|
|
$PYTHON -m venv .venv
|
|
fi
|
|
fi
|
|
|
|
# --- Activate and install ---
|
|
# Determine venv python path for explicit invocation
|
|
if [ "$IS_WINDOWS" -eq 1 ] && [ -f ".venv/Scripts/python.exe" ]; then
|
|
source .venv/Scripts/activate
|
|
VENV_PYTHON=".venv/Scripts/python.exe"
|
|
# Bootstrap pip if missing (--without-pip on Windows)
|
|
if ! "$VENV_PYTHON" -m pip --version &>/dev/null 2>&1; then
|
|
echo "Bootstrapping pip in venv ..."
|
|
"$VENV_PYTHON" -m ensurepip --default-pip || true
|
|
if ! "$VENV_PYTHON" -m pip --version &>/dev/null 2>&1; then
|
|
echo "ensurepip did not install pip — falling back to get-pip.py"
|
|
"$VENV_PYTHON" -c "import urllib.request; urllib.request.urlretrieve('https://bootstrap.pypa.io/get-pip.py', 'get-pip.py')"
|
|
"$VENV_PYTHON" get-pip.py
|
|
rm -f get-pip.py
|
|
fi
|
|
"$VENV_PYTHON" -m pip --version || { echo "ERROR: pip still missing after bootstrap" >&2; exit 1; }
|
|
fi
|
|
else
|
|
source .venv/bin/activate
|
|
VENV_PYTHON="python3"
|
|
fi
|
|
|
|
echo "Upgrading pip ..."
|
|
"$VENV_PYTHON" -m pip install --upgrade pip --quiet
|
|
|
|
echo "Installing aipass in editable mode (with dev extras) ..."
|
|
"$VENV_PYTHON" -m 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,
|
|
})
|
|
|
|
# NOTE: commons and skills were external branches, now removed from public repo.
|
|
# Registry only includes branches discovered under src/aipass/.
|
|
|
|
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 "memory" "$SCRIPT_DIR/src/aipass/memory" "builder" "Vector memory bank"
|
|
|
|
# External branches
|
|
# NOTE: backup, daemon removed S82/S87. commons, skills moved to external repos.
|
|
# Only the 11 core branches above should be bootstrapped.
|
|
|
|
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"
|
|
|
|
# Determine python command for hooks — venv python on Windows, python3 on Unix
|
|
if [ "$IS_WINDOWS" -eq 1 ]; then
|
|
HOOK_PYTHON="$SCRIPT_DIR/.venv/Scripts/python.exe"
|
|
else
|
|
HOOK_PYTHON="python3"
|
|
fi
|
|
|
|
"$PYTHON" - "$SCRIPT_DIR" "$CLAUDE_SETTINGS" "$HOOK_PYTHON" << 'PYEOF'
|
|
import json
|
|
import sys
|
|
from pathlib import Path
|
|
|
|
repo_root = sys.argv[1]
|
|
settings_path = Path(sys.argv[2])
|
|
hook_python = sys.argv[3]
|
|
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"{hook_python} {hooks_dir}/branch_prompt_loader.py"}]},
|
|
{"hooks": [{"type": "command", "command": f"{hook_python} {hooks_dir}/identity_injector.py"}]},
|
|
{"hooks": [{"type": "command", "command": f"{hook_python} {hooks_dir}/email_notification.py"}]},
|
|
],
|
|
"PreToolUse": [
|
|
{"matcher": "Bash|Edit|MultiEdit|Write|Read|Grep|Glob|WebSearch|WebFetch|Task",
|
|
"hooks": [{"type": "command", "command": f"{hook_python} {hooks_dir}/tool_use_sound.py"}]},
|
|
],
|
|
"PostToolUse": [
|
|
{"matcher": "Edit|MultiEdit|Write|NotebookEdit",
|
|
"hooks": [{"type": "command", "command": f"{hook_python} {hooks_dir}/auto_fix_diagnostics.py"}]},
|
|
],
|
|
"Stop": [
|
|
{"hooks": [{"type": "command", "command": f"{hook_python} {hooks_dir}/stop_sound.py"}]},
|
|
],
|
|
"Notification": [
|
|
{"hooks": [{"type": "command", "command": f"{hook_python} {hooks_dir}/notification_sound.py"}]},
|
|
],
|
|
"PreCompact": [
|
|
{"matcher": "manual", "hooks": [{"type": "command", "command": f"{hook_python} {hooks_dir}/pre_compact.py", "timeout": 60}]},
|
|
{"matcher": "auto", "hooks": [{"type": "command", "command": f"{hook_python} {hooks_dir}/pre_compact.py", "timeout": 60}]},
|
|
],
|
|
}
|
|
|
|
# Inject AIPASS_HOME into env block so dispatched agents find AIPass
|
|
import os
|
|
env_block = settings.get("env", {})
|
|
env_block["AIPASS_HOME"] = repo_root
|
|
# Windows: force UTF-8 for Rich output in hook processes
|
|
msys = os.environ.get("MSYSTEM", "") + os.environ.get("OSTYPE", "")
|
|
if "MSYS" in msys or "msys" in msys or "MINGW" in msys:
|
|
env_block["PYTHONUTF8"] = "1"
|
|
settings["env"] = env_block
|
|
|
|
settings_path.write_text(json.dumps(settings, indent=2) + "\n")
|
|
print(f" hooks -> {settings_path}")
|
|
print(f" AIPASS_HOME -> {repo_root} (in settings.json env)")
|
|
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"{hook_python} {hooks_dir}/session_start_identity.py", "timeout": 10}]}
|
|
],
|
|
"BeforeModel": [
|
|
{"hooks": [{"type": "command", "command": f"{hook_python} {hooks_dir}/prompt_inject.py", "timeout": 10}]}
|
|
],
|
|
"BeforeTool": [
|
|
{"matcher": "Edit|Write",
|
|
"hooks": [{"type": "command", "command": f"{hook_python} {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
|
|
|
|
# --- Set AIPASS_HOME + PATH so all services work from any project ---
|
|
echo ""
|
|
echo "Configuring cross-project access ..."
|
|
|
|
if [ "$IS_WINDOWS" -eq 1 ]; then
|
|
# Windows (Git Bash): write to ~/.bash_profile
|
|
PROFILE="$HOME/.bash_profile"
|
|
touch "$PROFILE"
|
|
|
|
# AIPASS_HOME
|
|
if ! grep -q "AIPASS_HOME" "$PROFILE" 2>/dev/null; then
|
|
echo "" >> "$PROFILE"
|
|
echo "# AIPass — cross-project access" >> "$PROFILE"
|
|
echo "export AIPASS_HOME=\"$SCRIPT_DIR\"" >> "$PROFILE"
|
|
echo " AIPASS_HOME added to $PROFILE"
|
|
else
|
|
echo " AIPASS_HOME already in $PROFILE"
|
|
fi
|
|
|
|
# PATH (venv Scripts for Windows)
|
|
VENV_SCRIPTS="$SCRIPT_DIR/.venv/Scripts"
|
|
if ! grep -q ".venv/Scripts" "$PROFILE" 2>/dev/null; then
|
|
echo "export PATH=\"$VENV_SCRIPTS:\$PATH\"" >> "$PROFILE"
|
|
echo " PATH updated in $PROFILE (drone available globally)"
|
|
else
|
|
echo " PATH already includes venv in $PROFILE"
|
|
fi
|
|
|
|
# PYTHONUTF8
|
|
if ! grep -q "PYTHONUTF8" "$PROFILE" 2>/dev/null; then
|
|
echo "export PYTHONUTF8=1" >> "$PROFILE"
|
|
echo " PYTHONUTF8=1 added to $PROFILE"
|
|
fi
|
|
|
|
# Export for current session too
|
|
export AIPASS_HOME="$SCRIPT_DIR"
|
|
export PATH="$VENV_SCRIPTS:$PATH"
|
|
export PYTHONUTF8=1
|
|
else
|
|
# Linux/macOS: write to ~/.bashrc
|
|
PROFILE="$HOME/.bashrc"
|
|
|
|
# AIPASS_HOME
|
|
if ! grep -q "AIPASS_HOME" "$PROFILE" 2>/dev/null; then
|
|
echo "" >> "$PROFILE"
|
|
echo "# AIPass — cross-project access" >> "$PROFILE"
|
|
echo "export AIPASS_HOME=\"$SCRIPT_DIR\"" >> "$PROFILE"
|
|
echo " AIPASS_HOME added to $PROFILE"
|
|
else
|
|
echo " AIPASS_HOME already in $PROFILE"
|
|
fi
|
|
|
|
export AIPASS_HOME="$SCRIPT_DIR"
|
|
fi
|
|
|
|
# --- Create global symlinks for CLI tools (Linux/macOS only) ---
|
|
echo ""
|
|
if [ "$IS_WINDOWS" -eq 1 ]; then
|
|
echo "Windows: drone available via PATH (set above)"
|
|
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
|