_blocks/memory-protocol.md references ~/.claude/memory/MEMORY.md, but the installer previously only scaffolded agents/ hooks/ skills/ — so the first agent that followed the block would fail reading a non-existent index. Now mkdir -p ~/.claude/memory and, only when MEMORY.md is absent, write a minimal placeholder (frontmatter + pointer to the block). User-edited MEMORY.md is never overwritten.
177 lines
6.6 KiB
Bash
Executable file
177 lines
6.6 KiB
Bash
Executable file
#!/usr/bin/env bash
|
|
# KeiSeiKit — Constructor-Pattern Agent Kit installer
|
|
# Idempotent: safe to re-run. Never overwrites settings.json or existing user manifests.
|
|
|
|
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"
|
|
|
|
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 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
|
|
|
|
# --- done -----------------------------------------------------------------
|
|
echo
|
|
say "install complete"
|
|
echo
|
|
cat <<EOF
|
|
==========================================================================
|
|
NEXT STEP: merge settings-snippet.json into ~/.claude/settings.json
|
|
==========================================================================
|
|
|
|
KeiSeiKit ships 3 hooks (assemble-agents, assemble-validate, no-hand-edit).
|
|
To activate them, merge entries from:
|
|
$KIT_DIR/settings-snippet.json
|
|
into your:
|
|
$HOME_DIR/.claude/settings.json
|
|
|
|
If you have no settings.json yet, you can simply copy the snippet:
|
|
cp $KIT_DIR/settings-snippet.json $HOME_DIR/.claude/settings.json
|
|
|
|
Otherwise, open both files and append the PostToolUse / PreToolUse
|
|
entries to the matching arrays in your existing settings.json.
|
|
|
|
To verify install:
|
|
ls $AGENTS_DIR/*.md # should show ~14 generated agents
|
|
$AGENTS_DIR/_assembler/target/release/assemble --validate
|
|
|
|
To create a new project-specialist agent:
|
|
/new-agent
|
|
|
|
==========================================================================
|
|
EOF
|