#!/usr/bin/env bash # KeiSeiKit — Constructor-Pattern Agent Kit installer # Idempotent: safe to re-run. Never overwrites settings.json or existing user manifests. # # Usage: # ./install.sh # install agents + hooks + skills + bridges/ # ./install.sh --with-bridges # also render cross-tool bridges into $PWD # (AGENTS.md, .cursorrules, .cursor/rules/main.mdc, # .github/copilot-instructions.md, Windsurf, Junie, # Continue, Gemini, Aider, Replit — 11 files total) # Skipped if $PWD is the KeiSeiKit repo itself. set -euo pipefail KIT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" HOME_DIR="${HOME:?HOME not set}" AGENTS_DIR="$HOME_DIR/.claude/agents" HOOKS_DIR="$HOME_DIR/.claude/hooks" SKILLS_DIR="$HOME_DIR/.claude/skills" WITH_BRIDGES=0 for arg in "$@"; do case "$arg" in --with-bridges) WITH_BRIDGES=1 ;; --help|-h) sed -n '3,11p' "${BASH_SOURCE[0]}" | sed 's/^# \{0,1\}//' exit 0 ;; esac done say() { printf '\033[1;36m[install]\033[0m %s\n' "$*"; } warn() { printf '\033[1;33m[warn]\033[0m %s\n' "$*"; } err() { printf '\033[1;31m[error]\033[0m %s\n' "$*" >&2; } # Backup a populated target directory to a timestamped sibling before clobber. # No-op if target is absent or contains no regular files (recursively). This # means freshly-mkdir'd scaffolds are NOT backed up — only real user content. # Only called on $AGENTS_DIR/_blocks, _templates, _assembler, $HOOKS_DIR, # $SKILLS_DIR — never on $KIT_DIR source. backup_dir() { local target="$1" [ -d "$target" ] || return 0 # No regular files anywhere under target → nothing worth preserving if [ -z "$(find "$target" -type f -print -quit 2>/dev/null)" ]; then return 0 fi local backup="${target}.bak-$(date +%s)" cp -a "$target" "$backup" say "backed up existing $target to $backup" } # --- prerequisites ---------------------------------------------------------- say "checking prerequisites" if ! command -v cargo >/dev/null 2>&1; then err "cargo not found. Install Rust: https://rustup.rs/" exit 1 fi # Verify cargo actually runs (catches "rustup has no default toolchain") if ! cargo --version >/dev/null 2>&1; then err "cargo is installed but not functional. Run: rustup default stable" exit 1 fi if ! command -v jq >/dev/null 2>&1; then err "jq not found. jq is REQUIRED on any machine that will activate the" err "KeiSeiKit hooks — without it the hooks become dead weight and would" err "otherwise abort Claude Code's Edit/Write/Bash tool calls. Install it:" err " brew install jq (macOS)" err " apt install jq (Debian/Ubuntu)" exit 1 fi # --- create target dirs ----------------------------------------------------- say "creating directories" mkdir -p \ "$AGENTS_DIR/_blocks" \ "$AGENTS_DIR/_manifests" \ "$AGENTS_DIR/_templates" \ "$AGENTS_DIR/_assembler/src" \ "$AGENTS_DIR/_generated" \ "$HOOKS_DIR" \ "$SKILLS_DIR/new-agent" \ "$HOME_DIR/.claude/memory" # --- scaffold MEMORY.md placeholder (user-respecting) ---------------------- # _blocks/memory-protocol.md references ~/.claude/memory/MEMORY.md; without # this file the first agent following the protocol fails on read. MEMORY_INDEX="$HOME_DIR/.claude/memory/MEMORY.md" if [[ ! -f "$MEMORY_INDEX" ]]; then cat > "$MEMORY_INDEX" <<'EOF' # Auto Memory — Index > File-based memory index. Add entries as you save memory files under this directory. > See `_blocks/memory-protocol.md` for format. EOF say "scaffolded $MEMORY_INDEX" fi # --- copy blocks (overwrite ours; blocks are SSoT from kit) ---------------- say "copying shared blocks -> $AGENTS_DIR/_blocks/" backup_dir "$AGENTS_DIR/_blocks" cp -f "$KIT_DIR/_blocks/"*.md "$AGENTS_DIR/_blocks/" # --- copy bridges (overwrite; templates are SSoT from kit) ----------------- if [[ -d "$KIT_DIR/_bridges" ]]; then say "copying bridge templates -> $AGENTS_DIR/_bridges/" mkdir -p "$AGENTS_DIR/_bridges" backup_dir "$AGENTS_DIR/_bridges" cp -f "$KIT_DIR/_bridges/"*.tmpl "$AGENTS_DIR/_bridges/" cp -f "$KIT_DIR/_bridges/README.md" "$AGENTS_DIR/_bridges/" cp -f "$KIT_DIR/_bridges/emit.sh" "$AGENTS_DIR/_bridges/emit.sh" chmod +x "$AGENTS_DIR/_bridges/emit.sh" fi # --- copy generic manifests, DO NOT overwrite user's existing manifests ----- say "copying generic manifests -> $AGENTS_DIR/_manifests/ (skip if exists)" copied=0; skipped=0 for f in "$KIT_DIR/_manifests/"*.toml; do name="$(basename "$f")" if [[ -f "$AGENTS_DIR/_manifests/$name" ]]; then skipped=$((skipped+1)) else cp "$f" "$AGENTS_DIR/_manifests/$name" copied=$((copied+1)) fi done say " copied $copied, skipped $skipped (already present)" # --- copy template --------------------------------------------------------- if compgen -G "$KIT_DIR/_templates/*.template" >/dev/null; then say "copying specialist template" backup_dir "$AGENTS_DIR/_templates" cp -f "$KIT_DIR/_templates/"*.template "$AGENTS_DIR/_templates/" fi # --- copy assembler source (always refresh) -------------------------------- say "copying assembler source" backup_dir "$AGENTS_DIR/_assembler" cp -f "$KIT_DIR/_assembler/Cargo.toml" "$AGENTS_DIR/_assembler/" cp -f "$KIT_DIR/_assembler/src/"*.rs "$AGENTS_DIR/_assembler/src/" if [[ -f "$KIT_DIR/_assembler/.gitignore" ]]; then cp -f "$KIT_DIR/_assembler/.gitignore" "$AGENTS_DIR/_assembler/" fi # --- copy hooks (refresh; hooks are logic, not config) --------------------- say "copying hooks -> $HOOKS_DIR/" backup_dir "$HOOKS_DIR" for h in assemble-agents.sh assemble-validate.sh no-hand-edit-agents.sh; do cp -f "$KIT_DIR/hooks/$h" "$HOOKS_DIR/$h" chmod +x "$HOOKS_DIR/$h" done # --- copy skills ----------------------------------------------------------- if [[ -d "$KIT_DIR/skills" ]]; then say "copying skills" backup_dir "$SKILLS_DIR" for skill_dir in "$KIT_DIR/skills/"*/; do [ -d "$skill_dir" ] || continue skill_name="$(basename "$skill_dir")" mkdir -p "$SKILLS_DIR/$skill_name" cp -rf "$skill_dir"* "$SKILLS_DIR/$skill_name/" 2>/dev/null || true say " -> $skill_name" done fi # --- build assembler ------------------------------------------------------- say "building Rust assembler (cargo build --release)" ( cd "$AGENTS_DIR/_assembler" && cargo build --release ) if [[ ! -x "$AGENTS_DIR/_assembler/target/release/assemble" ]]; then err "build succeeded but binary not found at $AGENTS_DIR/_assembler/target/release/assemble" exit 2 fi # --- generate .md agents in-place ------------------------------------------ say "generating agent .md files (--in-place)" AGENT_ROOT="$AGENTS_DIR" "$AGENTS_DIR/_assembler/target/release/assemble" --in-place # --- optional: render cross-tool bridges into $PWD ------------------------- if [[ "$WITH_BRIDGES" == "1" ]]; then if [[ -f "./install.sh" && -d "./_bridges" ]]; then warn "not generating bridges — you are in the KeiSeiKit repo, not a project directory" else say "rendering cross-tool bridges into $PWD" "$KIT_DIR/_bridges/emit.sh" "$PWD" fi fi # --- done ----------------------------------------------------------------- echo say "install complete" echo cat <