Skip to content
Development
Hook

Hooks

What repobrain runs automatically, and when. A hook is a command Claude Code fires at a fixed moment, without you asking for it.

From plugin
repobrain
1.3k5 skills4 commands1 hook
Install
> /plugin marketplace add study8677/repobrain
> /plugin install repobrain@repobrain

Ships with repobrain. Installing the plugin gets these hooks.

What fires, and when

SessionStart

Fires once when a session begins, and again after a context compaction. It is where a plugin sets up its environment, or restores state the compaction dropped.

  • python3 "${CLAUDE_PLUGIN_ROOT}/hooks/install_engine.py" || python "${CLAUDE_PLUGIN_ROOT}/hooks/install_engine.py"
Read hooks/hooks.json

Where it lives

  • hooks/install_engine.pyRunsGitHub
    Read the script
    #!/usr/bin/env python3
    """SessionStart hook: ensure the RepoBrain CLI and engine are on PATH.
    
    Single cross-platform installer for Claude Code's plugin SessionStart hook.
    Idempotent and silent on the happy path. All output goes to stderr (stdout
    on a hook is interpreted as injected context).
    
    Strategy:
      1. If all RepoBrain commands are on PATH at the bundled versions, exit 0.
      2. Ensure pipx exists:
           - macOS with Homebrew → `brew install pipx`
           - Otherwise → `python -m pip install --user pipx`
         Both paths require no sudo.
      3. `pipx ensurepath` and prepend the pipx bin dir to PATH for the current
         process so the next `pipx install` finds the freshly placed shim.
      4. `pipx install --force <plugin_root>/engine`, then inject the bundled CLI
         into the same environment with `--include-apps`.
      5. If pipx remains unavailable, fall back to `pip install --user --upgrade`.
      6. On total failure, print a clear manual-install message and exit 1.
    """
    from __future__ import annotations
    
    import os
    import re
    import shutil
    import subprocess
    import sys
    from pathlib import Path
    
    
    ENGINE_PACKAGE = "repobrain-engine"
    REQUIRED_COMMANDS = ("rb", "rb-ask", "rb-refresh", "rb-mcp")
    
    
    def log(msg: str) -> None:
        print(msg, file=sys.stderr, flush=True)
    
    
    def has(cmd: str) -> bool:
        return shutil.which(cmd) is not None
    
    
    def user_scripts_bin() -> Path | None:
        """Resolve site.USER_BASE bin/Scripts dir from the current Python."""
        try:
            import site
            base = Path(site.USER_BASE)
        except Exception:
            return None
        candidate = base / ("Scripts" if os.name == "nt" else "bin")
        return candidate if candidate.is_dir() else None
    
    
    def prepend_path(p: Path) -> None:
        if not p.is_dir():
            return
        cur = os.environ.get("PATH", "")
        parts = cur.split(os.pathsep)
        if str(p) in parts:
            return
        os.environ["PATH"] = str(p) + os.pathsep + cur
    
    
    def run(cmd: list[str]) -> int:
        try:
            return subprocess.run(cmd, stderr=sys.stderr, stdout=sys.stderr).returncode
        except FileNotFoundError:
            return 127
    
    
    def ensure_pipx() -> bool:
        """Install pipx if missing. Returns True if pipx is available afterwards."""
        if has("pipx"):
            return True
    
        # macOS with Homebrew: cleanest path, no sudo.
        if sys.platform == "darwin" and has("brew"):
            log("[repobrain] Installing pipx via Homebrew...")
            run(["brew", "install", "pipx"])
    
        # Cross-platform fallback: pip install --user pipx.
        if not has("pipx"):
            py = sys.executable or ("python" if has("python") else "python3")
            log(f"[repobrain] Installing pipx via pip --user ({py})...")
            run([py, "-m", "pip", "install", "--user", "--quiet", "pipx"])
            # User-base scripts/bin needs to be on PATH for `pipx` to be findable.
            ub = user_scripts_bin()
            if ub:
                prepend_path(ub)
    
        return has("pipx") or _has_pipx_module()
    
    
    def _has_pipx_module() -> bool:
        """Some installs put pipx on PATH only after `ensurepath`. Check for the
        module so we can invoke it via `python -m pipx` as a fallback."""
        try:
            subprocess.run(
                [sys.executable or "python", "-m", "pipx", "--version"],
                check=True, stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL,
            )
            return True
        except Exception:
            return False
    
    
    def pipx(args: list[str]) -> int:
        if has("pipx"):
            return run(["pipx", *args])
        return run([sys.executable or "python", "-m", "pipx", *args])
    
    
    def read_project_version(package_dir: Path) -> str | None:
        """Read a bundled package version from its ``pyproject.toml``."""
        try:
            text = (package_dir / "pyproject.toml").read_text(encoding="utf-8")
        except OSError:
            return None
        match = re.search(r'^version\s*=\s*"([^"]+)"', text, flags=re.MULTILINE)
        return match.group(1) if match else None
    
    
    def get_installed_engine_version() -> str | None:
        """Return the installed rb-mcp engine version, if it can be queried."""
        rb_mcp = shutil.which("rb-mcp")
        if rb_mcp is None:
            return None
        try:
            completed = subprocess.run(
                [rb_mcp, "--version"],
                check=False,
                capture_output=True,
                text=True,
                timeout=15,
            )
        except Exception:
            return None
        if completed.returncode != 0:
            return None
        output = (completed.stdout or completed.stderr).strip()
        match = re.search(r"(\d+\.\d+\.\d+(?:[-+][A-Za-z0-9.]+)?)", output)
        return match.group(1) if match else None
    
    
    def get_installed_cli_version() -> str | None:
        """Return the installed ``rb`` CLI version, if it can be queried."""
        rb = shutil.which("rb")
        if rb is None:
            return None
        try:
            completed = subprocess.run(
                [rb, "version"],
                check=False,
                capture_output=True,
                text=True,
                timeout=15,
            )
        except Exception:
            return None
        if completed.returncode != 0:
            return None
        output = (completed.stdout or completed.stderr).strip()
        match = re.search(r"(\d+\.\d+\.\d+(?:[-+][A-Za-z0-9.]+)?)", output)
        return match.group(1) if match else None
    
    
    def required_commands_available() -> bool:
        """Return whether every command promised by the plugin is on PATH."""
        return all(has(command) for command in REQUIRED_COMMANDS)
    
    
    def installation_is_current(
        installed_engine: str | None,
        expected_engine: str | None,
        installed_cli: str | None,
        expected_cli: str | None,
    ) -> bool:
        """Return whether versions and all public commands match the bundle."""
        return (
            installed_engine == expected_engine
            and installed_cli == expected_cli
            and None not in (installed_engine, expected_engine, installed_cli, expected_cli)
            and required_commands_available()
        )
    
    
    def installed_bundle_is_current(
        expected_engine: str | None,
        expected_cli: str | None
  • hooks/install_engine.shGitHub
    Read the script
    #!/usr/bin/env bash
    # Thin wrapper for manual invocation. The real install logic lives in
    # `install_engine.py` so it can be reused by the Windows .bat wrapper and
    # the cross-platform hook command in `hooks.json`.
    set -u
    DIR="$(cd "$(dirname "$0")" && pwd)"
    exec python3 "${DIR}/install_engine.py" 2>/dev/null \
      || exec python "${DIR}/install_engine.py"
    

Read the script before you install anything that runs on your machine. This is the one part of a plugin that acts without being asked.

Ships withrepobrain

🧠 RepoBrain (formerly Antigravity) — Give your repo a brain. ChatGPT for your codebase: works in Claude Code, Cursor, Codex, Windsurf & more.

Get the whole plugin
Stats
1,300
Stars
262
Forks
Active
Maintenance
Python
Language
MIT
License
9d ago
Last commit
8mo ago
Created

Repo: study8677/repobrain