Add neuron-dev-setup: one-command CORE dev stack onboarding installer
Scaffolds a reproducible, idempotent installer that stands up the four native launchd core services (soul :7770, engram :8742, mcp-wrapper :17779, mcp-proxy :7779), seeds a fresh engram with the genesis identity via forge, and installs the Claude Code config (neuron agent + core hooks + local MCP). Fully templated to the invoking user's $HOME; Anthropic key prompted and stored in Keychain; no secrets committed. Personal automations and synapse-dependent hooks excluded from core.
This commit is contained in:
Executable
+395
@@ -0,0 +1,395 @@
|
||||
#!/usr/bin/env bash
|
||||
#
|
||||
# neuron-dev-setup / install.sh
|
||||
# ─────────────────────────────────────────────────────────────────────────────
|
||||
# One-command onboarding for the Neuron CORE dev stack on a fresh Mac.
|
||||
#
|
||||
# Stands up, as native launchd services, the four processes a developer needs to
|
||||
# have an identical "Neuron brain + agent" to build against:
|
||||
#
|
||||
# soul (:7770) ──► engram (:8742) the mind + its memory substrate
|
||||
# ▲ ▲
|
||||
# │ │
|
||||
# mcp-wrapper (:17779) ──► soul MCP surface over the soul API
|
||||
# ▲
|
||||
# │
|
||||
# mcp-proxy (:7779) ◄── Claude Code stable MCP front door
|
||||
#
|
||||
# It also seeds a fresh engram with Neuron's identity (the genesis seed) and lays
|
||||
# down the Claude Code config (neuron agent + core hooks + local MCP registration)
|
||||
# so a new dev's `claude` talks to *their own* local Neuron.
|
||||
#
|
||||
# DESIGN RULES
|
||||
# * Idempotent: safe to re-run. Existing state is detected and reused.
|
||||
# * Templated: every path/port/user is derived from $HOME and config.env.
|
||||
# Nothing is hardcoded to another developer's machine.
|
||||
# * Secret-free: the Anthropic key is prompted for and stored in the macOS
|
||||
# Keychain. No key is ever written to a plist, this repo, or a logfile.
|
||||
#
|
||||
# USAGE
|
||||
# ./install.sh # full install
|
||||
# ./install.sh --dry-run # print what would happen, touch nothing
|
||||
# ./install.sh --skip-build # assume binaries already built (see --use-local)
|
||||
# ./install.sh --skip-services # lay down files but don't load LaunchAgents
|
||||
# ./install.sh --help
|
||||
# ─────────────────────────────────────────────────────────────────────────────
|
||||
set -euo pipefail
|
||||
|
||||
# ── Locate ourselves ─────────────────────────────────────────────────────────
|
||||
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||
TEMPLATES="${SCRIPT_DIR}/templates"
|
||||
|
||||
# ── Flags ────────────────────────────────────────────────────────────────────
|
||||
DRY_RUN=0; SKIP_BUILD=0; SKIP_SERVICES=0; USE_LOCAL_BINARIES=0
|
||||
for arg in "$@"; do
|
||||
case "$arg" in
|
||||
--dry-run) DRY_RUN=1 ;;
|
||||
--skip-build) SKIP_BUILD=1 ;;
|
||||
--skip-services) SKIP_SERVICES=1 ;;
|
||||
--use-local) USE_LOCAL_BINARIES=1 ;;
|
||||
--help|-h)
|
||||
sed -n '2,40p' "${BASH_SOURCE[0]}" | sed 's/^# \{0,1\}//'
|
||||
exit 0 ;;
|
||||
*) echo "unknown flag: $arg" >&2; exit 2 ;;
|
||||
esac
|
||||
done
|
||||
|
||||
# ── Pretty logging ───────────────────────────────────────────────────────────
|
||||
c_blue=$'\033[1;34m'; c_grn=$'\033[1;32m'; c_yel=$'\033[1;33m'; c_red=$'\033[1;31m'; c_off=$'\033[0m'
|
||||
step() { echo "${c_blue}▶${c_off} $*"; }
|
||||
ok() { echo "${c_grn}✓${c_off} $*"; }
|
||||
warn() { echo "${c_yel}!${c_off} $*"; }
|
||||
die() { echo "${c_red}✗ $*${c_off}" >&2; exit 1; }
|
||||
run() { if [ "$DRY_RUN" = 1 ]; then echo " [dry-run] $*"; else eval "$*"; fi; }
|
||||
|
||||
# ── Load config ──────────────────────────────────────────────────────────────
|
||||
if [ -f "${SCRIPT_DIR}/config.env" ]; then
|
||||
# shellcheck disable=SC1091
|
||||
source "${SCRIPT_DIR}/config.env"
|
||||
else
|
||||
# shellcheck disable=SC1091
|
||||
source "${SCRIPT_DIR}/config.env.example"
|
||||
warn "No config.env found — using defaults from config.env.example."
|
||||
fi
|
||||
|
||||
# Derived / defaulted values (never hardcode a home directory)
|
||||
: "${NEURON_HOME:=${HOME}/.neuron}"
|
||||
: "${DEV_ROOT:=${HOME}/Development/neuron-technologies}"
|
||||
: "${SOUL_PORT:=7770}"; : "${ENGRAM_PORT:=8742}"; : "${WRAPPER_PORT:=17779}"; : "${PROXY_PORT:=7779}"
|
||||
: "${ENGRAM_DATA_DIR:=${NEURON_HOME}/engram}"
|
||||
: "${ENGRAM_API_KEY:=}"
|
||||
: "${KEYCHAIN_SERVICE:=neuron-llm-0-key}"
|
||||
: "${EL_TOOLCHAIN_SOURCE:=artifact-registry}"
|
||||
: "${NEURON_REPO_BRANCH:=main}"
|
||||
|
||||
NEURON_REPO="${DEV_ROOT}/neuron"
|
||||
ENGRAM_REPO="${DEV_ROOT}/engram"
|
||||
FOUNDATION_REPO="${DEV_ROOT}/foundation"
|
||||
|
||||
SOUL_BIN="${NEURON_REPO}/dist/neuron"
|
||||
ENGRAM_BIN="${ENGRAM_REPO}/dist/engram"
|
||||
MCP_WRAPPER_BIN="${NEURON_REPO}/mcp-wrapper/dist/neuron-mcp-wrapper"
|
||||
MCP_PROXY_BIN="${NEURON_REPO}/mcp-proxy/dist/neuron-mcp-proxy"
|
||||
FORGE_BIN="${FOUNDATION_REPO}/forge/dist/forge"
|
||||
GENESIS_SEED="${FOUNDATION_REPO}/forge/seeds/neuron-genesis-seed.json"
|
||||
|
||||
LAUNCHAGENTS="${HOME}/Library/LaunchAgents"
|
||||
CLAUDE_DIR="${HOME}/.claude"
|
||||
|
||||
# Generate a local engram token if none was supplied.
|
||||
if [ -z "${ENGRAM_API_KEY}" ]; then
|
||||
ENGRAM_API_KEY="ntn-dev-$(head -c8 /dev/urandom | xxd -p 2>/dev/null || echo local)"
|
||||
fi
|
||||
|
||||
echo
|
||||
echo "${c_blue}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${c_off}"
|
||||
echo "${c_blue} Neuron CORE dev stack installer${c_off}"
|
||||
echo "${c_blue}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${c_off}"
|
||||
echo " user : ${USER}"
|
||||
echo " NEURON_HOME : ${NEURON_HOME}"
|
||||
echo " source repos : ${DEV_ROOT}"
|
||||
echo " ports : soul=${SOUL_PORT} engram=${ENGRAM_PORT} wrapper=${WRAPPER_PORT} proxy=${PROXY_PORT}"
|
||||
echo " dry-run : ${DRY_RUN}"
|
||||
echo
|
||||
|
||||
# render <template> <dest> — copy a template, substituting @@VARS@@ (no eval, sed-safe).
|
||||
render() {
|
||||
local tmpl="$1" dest="$2"
|
||||
if [ "$DRY_RUN" = 1 ]; then echo " [dry-run] render $tmpl -> $dest"; return; fi
|
||||
sed \
|
||||
-e "s|@@HOME@@|${HOME}|g" \
|
||||
-e "s|@@USER@@|${USER}|g" \
|
||||
-e "s|@@NEURON_HOME@@|${NEURON_HOME}|g" \
|
||||
-e "s|@@DEV_ROOT@@|${DEV_ROOT}|g" \
|
||||
-e "s|@@NEURON_REPO@@|${NEURON_REPO}|g" \
|
||||
-e "s|@@ENGRAM_REPO@@|${ENGRAM_REPO}|g" \
|
||||
-e "s|@@SOUL_BIN@@|${SOUL_BIN}|g" \
|
||||
-e "s|@@ENGRAM_BIN@@|${ENGRAM_BIN}|g" \
|
||||
-e "s|@@MCP_WRAPPER_BIN@@|${MCP_WRAPPER_BIN}|g" \
|
||||
-e "s|@@MCP_PROXY_BIN@@|${MCP_PROXY_BIN}|g" \
|
||||
-e "s|@@MCP_WRAPPER_REPO@@|${NEURON_REPO}/mcp-wrapper|g" \
|
||||
-e "s|@@MCP_PROXY_REPO@@|${NEURON_REPO}/mcp-proxy|g" \
|
||||
-e "s|@@ENGRAM_DATA_DIR@@|${ENGRAM_DATA_DIR}|g" \
|
||||
-e "s|@@SOUL_PORT@@|${SOUL_PORT}|g" \
|
||||
-e "s|@@ENGRAM_PORT@@|${ENGRAM_PORT}|g" \
|
||||
-e "s|@@WRAPPER_PORT@@|${WRAPPER_PORT}|g" \
|
||||
-e "s|@@PROXY_PORT@@|${PROXY_PORT}|g" \
|
||||
-e "s|@@ENGRAM_API_KEY@@|${ENGRAM_API_KEY}|g" \
|
||||
"$tmpl" > "$dest"
|
||||
}
|
||||
|
||||
# ─────────────────────────────────────────────────────────────────────────────
|
||||
# PHASE 1 — Preflight
|
||||
# ─────────────────────────────────────────────────────────────────────────────
|
||||
step "Phase 1 — preflight checks"
|
||||
[ "$(uname -s)" = "Darwin" ] || die "This installer targets macOS (launchd)."
|
||||
[ "$(uname -m)" = "arm64" ] || warn "Non-arm64 Mac: soul.c build flags assume Apple Silicon; review PHASE 3."
|
||||
|
||||
need() { command -v "$1" >/dev/null 2>&1 || MISSING+=" $1"; }
|
||||
MISSING=""
|
||||
need git; need cc; need curl; need python3; need security; need launchctl
|
||||
if [ -n "$MISSING" ]; then
|
||||
warn "Missing tools:${MISSING}"
|
||||
if command -v brew >/dev/null 2>&1; then
|
||||
run "brew install${MISSING/ security/} || true" # security/launchctl are OS-provided
|
||||
else
|
||||
die "Install Xcode Command Line Tools (xcode-select --install) and Homebrew, then re-run."
|
||||
fi
|
||||
fi
|
||||
# Runtime build deps used by the soul cc line (-lssl -lcrypto -lcurl).
|
||||
if command -v brew >/dev/null 2>&1; then
|
||||
brew list openssl@3 >/dev/null 2>&1 || run "brew install openssl@3"
|
||||
brew list curl >/dev/null 2>&1 || run "brew install curl"
|
||||
fi
|
||||
ok "preflight complete"
|
||||
|
||||
# ─────────────────────────────────────────────────────────────────────────────
|
||||
# PHASE 2 — Anthropic API key -> Keychain (prompt; never store in files)
|
||||
# ─────────────────────────────────────────────────────────────────────────────
|
||||
step "Phase 2 — Anthropic API key (Keychain)"
|
||||
if security find-generic-password -a "$USER" -s "$KEYCHAIN_SERVICE" -w >/dev/null 2>&1; then
|
||||
ok "key already present in Keychain (service '${KEYCHAIN_SERVICE}') — leaving it"
|
||||
elif [ -n "${ANTHROPIC_API_KEY:-}" ]; then
|
||||
run "security add-generic-password -a \"$USER\" -s \"$KEYCHAIN_SERVICE\" -w \"\$ANTHROPIC_API_KEY\" -U"
|
||||
ok "stored ANTHROPIC_API_KEY from environment into Keychain"
|
||||
else
|
||||
if [ "$DRY_RUN" = 1 ]; then
|
||||
echo " [dry-run] would prompt for Anthropic API key and store in Keychain"
|
||||
else
|
||||
echo " Enter your Anthropic API key (input hidden). Get one at https://console.anthropic.com/"
|
||||
read -r -s -p " ANTHROPIC_API_KEY: " _key; echo
|
||||
[ -n "$_key" ] || die "No key entered. Re-run when you have one."
|
||||
security add-generic-password -a "$USER" -s "$KEYCHAIN_SERVICE" -w "$_key" -U
|
||||
unset _key
|
||||
ok "stored key in Keychain (service '${KEYCHAIN_SERVICE}')"
|
||||
fi
|
||||
fi
|
||||
|
||||
# ─────────────────────────────────────────────────────────────────────────────
|
||||
# PHASE 3 — Fetch sources + build the four core binaries
|
||||
# ─────────────────────────────────────────────────────────────────────────────
|
||||
step "Phase 3 — source + build"
|
||||
run "mkdir -p \"$DEV_ROOT\""
|
||||
|
||||
clone_or_pull() {
|
||||
local url="$1" dir="$2" branch="${3:-main}"
|
||||
if [ -d "$dir/.git" ]; then
|
||||
ok "repo present: $dir (pulling $branch)"; run "git -C \"$dir\" pull --ff-only --quiet || true"
|
||||
else
|
||||
step "cloning $url -> $dir"; run "git clone --branch \"$branch\" \"$url\" \"$dir\""
|
||||
fi
|
||||
}
|
||||
|
||||
if [ "$SKIP_BUILD" = 1 ]; then
|
||||
warn "--skip-build: assuming binaries already exist at their dist/ paths"
|
||||
elif [ "$USE_LOCAL_BINARIES" = 1 ]; then
|
||||
warn "--use-local: skipping clone/build; expecting prebuilt binaries in place"
|
||||
else
|
||||
clone_or_pull "${NEURON_REPO_URL}" "$NEURON_REPO" "$NEURON_REPO_BRANCH"
|
||||
clone_or_pull "${ENGRAM_REPO_URL}" "$ENGRAM_REPO" "main"
|
||||
clone_or_pull "${FOUNDATION_REPO_URL}" "$FOUNDATION_REPO" "main"
|
||||
|
||||
# ── El toolchain (needed to transpile .el -> .c for engram/wrapper/proxy) ──
|
||||
# soul does NOT need this: dist/soul.c is committed and compiled directly.
|
||||
EL_RUNTIME_DIR="${DEV_ROOT}/.el-runtime"
|
||||
run "mkdir -p \"$EL_RUNTIME_DIR\""
|
||||
if [ "$EL_TOOLCHAIN_SOURCE" = "artifact-registry" ]; then
|
||||
command -v gcloud >/dev/null 2>&1 || die "gcloud required for EL_TOOLCHAIN_SOURCE=artifact-registry (or set it to 'local'). Ask Will for GCP access to project ${GCP_PROJECT}."
|
||||
# Mirrors .gitea/workflows/ci.yaml: pull el-runtime-c, el-runtime-h, el-elc.
|
||||
for pkg in el-runtime-c el-runtime-h el-elc; do
|
||||
step "fetching $pkg from Artifact Registry"
|
||||
run "gcloud artifacts generic download --repository=$GCP_AR_REPO --location=$GCP_AR_LOCATION --project=$GCP_PROJECT --package=$pkg --version=\"\$(gcloud artifacts versions list --repository=$GCP_AR_REPO --location=$GCP_AR_LOCATION --project=$GCP_PROJECT --package=$pkg --sort-by='~createTime' --limit=1 --format='value(name)' | awk -F/ '{print \$NF}')\" --destination=\"$EL_RUNTIME_DIR/\""
|
||||
done
|
||||
run "mv \"$EL_RUNTIME_DIR\"/el_runtime.c* \"$EL_RUNTIME_DIR/el_runtime.c\" 2>/dev/null || true"
|
||||
run "mv \"$EL_RUNTIME_DIR\"/el_runtime.h* \"$EL_RUNTIME_DIR/el_runtime.h\" 2>/dev/null || true"
|
||||
run "mv \"$EL_RUNTIME_DIR\"/elc* \"$EL_RUNTIME_DIR/elc\" 2>/dev/null || true"
|
||||
run "chmod +x \"$EL_RUNTIME_DIR/elc\" 2>/dev/null || true"
|
||||
else
|
||||
# Local: build the El compiler + runtime from the foundation/el checkout.
|
||||
# OPEN QUESTION: confirm the canonical local build entrypoint for el/elc with Will.
|
||||
warn "EL_TOOLCHAIN_SOURCE=local: expecting el_runtime.{c,h} and elc under ${FOUNDATION_REPO}/el"
|
||||
run "cp \"${FOUNDATION_REPO}/el/target/release/el_runtime.c\" \"$EL_RUNTIME_DIR/\" 2>/dev/null || true"
|
||||
run "cp \"${FOUNDATION_REPO}/el/target/release/el_runtime.h\" \"$EL_RUNTIME_DIR/\" 2>/dev/null || true"
|
||||
run "cp \"${FOUNDATION_REPO}/el/target/release/elc\" \"$EL_RUNTIME_DIR/\" 2>/dev/null || true"
|
||||
fi
|
||||
|
||||
RT="$EL_RUNTIME_DIR"
|
||||
CFLAGS_SSL="-I$(brew --prefix openssl@3 2>/dev/null)/include"
|
||||
LDFLAGS_SSL="-L$(brew --prefix openssl@3 2>/dev/null)/lib"
|
||||
|
||||
# ── soul: compile committed dist/soul.c directly (verified CI recipe) ──────
|
||||
step "building soul (dist/soul.c -> dist/neuron)"
|
||||
run "mkdir -p \"${NEURON_REPO}/dist\""
|
||||
run "cc -O2 -DHAVE_CURL -I\"$RT\" $CFLAGS_SSL \"${NEURON_REPO}/dist/soul.c\" \"$RT/el_runtime.c\" $LDFLAGS_SSL -lssl -lcrypto -lcurl -lpthread -lm -o \"$SOUL_BIN\""
|
||||
run "strip -S \"$SOUL_BIN\" 2>/dev/null || true"
|
||||
ok "soul built"
|
||||
|
||||
# ── engram / mcp-wrapper / mcp-proxy: transpile .el -> .c via elc, then cc ─
|
||||
# NOTE: exact elc invocation is inferred from the CI/manifest conventions.
|
||||
# Verify flags with Will if a build fails (see README OPEN QUESTIONS).
|
||||
build_el_unit() { # <src.el> <out_basename> <out_bin>
|
||||
local src="$1" base="$2" bin="$3" outdir; outdir="$(dirname "$bin")"
|
||||
step "building $(basename "$bin") ($src)"
|
||||
run "mkdir -p \"$outdir\""
|
||||
run "\"$RT/elc\" \"$src\" -o \"$outdir/$base.c\""
|
||||
run "cc -O2 -DHAVE_CURL -I\"$RT\" $CFLAGS_SSL \"$outdir/$base.c\" \"$RT/el_runtime.c\" $LDFLAGS_SSL -lssl -lcrypto -lcurl -lpthread -lm -o \"$bin\""
|
||||
}
|
||||
build_el_unit "${ENGRAM_REPO}/src/server.el" "server" "$ENGRAM_BIN"
|
||||
build_el_unit "${NEURON_REPO}/mcp-wrapper/src/main.el" "main" "$MCP_WRAPPER_BIN"
|
||||
build_el_unit "${NEURON_REPO}/mcp-proxy/src/main.el" "main" "$MCP_PROXY_BIN"
|
||||
build_el_unit "${FOUNDATION_REPO}/forge/src/forge.el" "forge" "$FORGE_BIN"
|
||||
ok "engram, mcp-wrapper, mcp-proxy, forge built"
|
||||
fi
|
||||
|
||||
# ─────────────────────────────────────────────────────────────────────────────
|
||||
# PHASE 4 — Lay down ~/.neuron (bin/, logs/, engram data dir)
|
||||
# ─────────────────────────────────────────────────────────────────────────────
|
||||
step "Phase 4 — ~/.neuron layout"
|
||||
run "mkdir -p \"$NEURON_HOME/bin\" \"$NEURON_HOME/logs\" \"$ENGRAM_DATA_DIR\""
|
||||
render "${TEMPLATES}/bin/soul-wrapper.sh.tmpl" "${NEURON_HOME}/bin/soul-wrapper.sh"
|
||||
run "chmod +x \"${NEURON_HOME}/bin/soul-wrapper.sh\""
|
||||
ok "~/.neuron ready (bin/soul-wrapper.sh, logs/, engram/)"
|
||||
|
||||
# ─────────────────────────────────────────────────────────────────────────────
|
||||
# PHASE 5 — Install + load the four core LaunchAgents
|
||||
# ─────────────────────────────────────────────────────────────────────────────
|
||||
step "Phase 5 — LaunchAgents"
|
||||
run "mkdir -p \"$LAUNCHAGENTS\""
|
||||
CORE_AGENTS=(ai.neuron.engram ai.neuron.soul ai.neuron.mcp-wrapper ai.neuron.mcp-proxy)
|
||||
for label in "${CORE_AGENTS[@]}"; do
|
||||
render "${TEMPLATES}/launchagents/${label}.plist.tmpl" "${LAUNCHAGENTS}/${label}.plist"
|
||||
ok "wrote ${label}.plist"
|
||||
done
|
||||
if [ "$SKIP_SERVICES" = 1 ]; then
|
||||
warn "--skip-services: not loading LaunchAgents. Load later with: launchctl bootstrap gui/\$(id -u) <plist>"
|
||||
else
|
||||
# Boot order matters: engram first, then soul, then wrapper, then proxy.
|
||||
for label in "${CORE_AGENTS[@]}"; do
|
||||
plist="${LAUNCHAGENTS}/${label}.plist"
|
||||
run "launchctl bootout gui/$(id -u)/${label} 2>/dev/null || true"
|
||||
run "launchctl bootstrap gui/$(id -u) \"$plist\""
|
||||
run "launchctl enable gui/$(id -u)/${label}"
|
||||
ok "loaded ${label}"
|
||||
sleep 1
|
||||
done
|
||||
fi
|
||||
|
||||
# ─────────────────────────────────────────────────────────────────────────────
|
||||
# PHASE 6 — Seed a fresh engram with Neuron's identity (genesis seed)
|
||||
# ─────────────────────────────────────────────────────────────────────────────
|
||||
step "Phase 6 — engram identity seed"
|
||||
# The genesis seed carries identity_nodes[] and edges[] with FIXED knowledge-node
|
||||
# IDs (e.g. kn-efeb4a5b...). Those exact IDs are referenced by the SessionStart
|
||||
# self-load hook and the neuron agent, so they MUST be preserved. `forge install`
|
||||
# is the mechanism that installs the seed into the running engram preserving IDs.
|
||||
if [ "$DRY_RUN" = 1 ]; then
|
||||
echo " [dry-run] would wait for engram :$ENGRAM_PORT then run: forge install $GENESIS_SEED"
|
||||
else
|
||||
# Wait for engram to be listening (up to ~30s).
|
||||
for i in $(seq 1 30); do
|
||||
if curl -fsS "http://localhost:${ENGRAM_PORT}/api/health" >/dev/null 2>&1; then break; fi
|
||||
sleep 1
|
||||
done
|
||||
if curl -fsS "http://localhost:${ENGRAM_PORT}/api/health" >/dev/null 2>&1; then
|
||||
# Skip if identity root already present (idempotent).
|
||||
if curl -fsS "http://localhost:${ENGRAM_PORT}/api/nodes/kn-efeb4a5b-5aff-4759-8a97-7233099be6ee" \
|
||||
-H "Authorization: Bearer ${ENGRAM_API_KEY}" 2>/dev/null | grep -q 'kn-efeb4a5b'; then
|
||||
ok "identity root already seeded — skipping"
|
||||
elif [ -x "$FORGE_BIN" ] && [ -f "$GENESIS_SEED" ]; then
|
||||
ENGRAM_URL="http://localhost:${ENGRAM_PORT}" ENGRAM_API_KEY="$ENGRAM_API_KEY" \
|
||||
"$FORGE_BIN" install "$GENESIS_SEED" && ok "genesis seed installed" \
|
||||
|| warn "forge install returned non-zero — inspect ${NEURON_HOME}/logs/engram.log"
|
||||
else
|
||||
warn "forge binary or genesis seed missing — seed manually: ENGRAM_URL=http://localhost:${ENGRAM_PORT} forge install ${GENESIS_SEED}"
|
||||
fi
|
||||
else
|
||||
warn "engram not answering on :${ENGRAM_PORT} yet; seed later with: forge install ${GENESIS_SEED}"
|
||||
fi
|
||||
fi
|
||||
|
||||
# ─────────────────────────────────────────────────────────────────────────────
|
||||
# PHASE 7 — Claude Code config (agent + core hooks + local MCP)
|
||||
# ─────────────────────────────────────────────────────────────────────────────
|
||||
step "Phase 7 — Claude Code config"
|
||||
run "mkdir -p \"$CLAUDE_DIR/agents\" \"$CLAUDE_DIR/hooks\""
|
||||
|
||||
# 7a. neuron agent
|
||||
run "cp \"${TEMPLATES}/claude/agents/neuron.md\" \"$CLAUDE_DIR/agents/neuron.md\""
|
||||
ok "installed agent: ~/.claude/agents/neuron.md"
|
||||
|
||||
# 7b. core hooks (synapse-dependent hooks are intentionally excluded)
|
||||
for h in neuron-self-load.sh neuron-agent-preamble.sh pre-compact.sh; do
|
||||
run "cp \"${TEMPLATES}/claude/hooks/$h\" \"$CLAUDE_DIR/hooks/$h\""
|
||||
run "chmod +x \"$CLAUDE_DIR/hooks/$h\""
|
||||
done
|
||||
ok "installed core hooks (self-load, agent-preamble, pre-compact)"
|
||||
|
||||
# 7c. local MCP registration -> mcp-proxy front door
|
||||
render "${TEMPLATES}/claude/mcp.json.tmpl" "${CLAUDE_DIR}/mcp.json.neuron"
|
||||
if [ -f "${CLAUDE_DIR}/mcp.json" ] && grep -q '"neuron"' "${CLAUDE_DIR}/mcp.json" 2>/dev/null; then
|
||||
ok "~/.claude/mcp.json already registers 'neuron' — wrote reference copy mcp.json.neuron"
|
||||
else
|
||||
run "cp \"${CLAUDE_DIR}/mcp.json.neuron\" \"${CLAUDE_DIR}/mcp.json\""
|
||||
ok "wrote ~/.claude/mcp.json (neuron -> http://127.0.0.1:${PROXY_PORT}/)"
|
||||
fi
|
||||
|
||||
# 7d. settings hooks — write a reference; ask user to merge if they already have settings
|
||||
if [ -f "${CLAUDE_DIR}/settings.json" ]; then
|
||||
run "cp \"${TEMPLATES}/claude/settings.core.json\" \"${CLAUDE_DIR}/settings.core.json\""
|
||||
warn "~/.claude/settings.json exists — wrote settings.core.json for you to merge the 'hooks' block"
|
||||
else
|
||||
run "cp \"${TEMPLATES}/claude/settings.core.json\" \"${CLAUDE_DIR}/settings.json\""
|
||||
ok "wrote ~/.claude/settings.json"
|
||||
fi
|
||||
|
||||
# ─────────────────────────────────────────────────────────────────────────────
|
||||
# PHASE 8 — Verify
|
||||
# ─────────────────────────────────────────────────────────────────────────────
|
||||
echo
|
||||
step "Phase 8 — verification"
|
||||
if [ "$DRY_RUN" = 1 ]; then
|
||||
echo " [dry-run] would health-check :$SOUL_PORT :$ENGRAM_PORT :$WRAPPER_PORT :$PROXY_PORT"
|
||||
else
|
||||
check() { # <name> <url>
|
||||
if curl -fsS --max-time 4 "$2" >/dev/null 2>&1; then ok "$1 healthy ($2)"; else warn "$1 NOT responding ($2)"; fi
|
||||
}
|
||||
sleep 3
|
||||
check "engram" "http://localhost:${ENGRAM_PORT}/api/health"
|
||||
check "soul" "http://localhost:${SOUL_PORT}/health"
|
||||
check "mcp-wrapper" "http://localhost:${WRAPPER_PORT}/health"
|
||||
check "mcp-proxy" "http://localhost:${PROXY_PORT}/health"
|
||||
fi
|
||||
|
||||
echo
|
||||
echo "${c_grn}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${c_off}"
|
||||
echo "${c_grn} Neuron core dev stack install complete.${c_off}"
|
||||
echo "${c_grn}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${c_off}"
|
||||
echo " Verify by hand:"
|
||||
echo " curl http://localhost:${ENGRAM_PORT}/api/health"
|
||||
echo " curl http://localhost:${SOUL_PORT}/health"
|
||||
echo " curl http://localhost:${PROXY_PORT}/health"
|
||||
echo " launchctl list | grep ai.neuron"
|
||||
echo " Then open Claude Code — the 'neuron' MCP should connect to :${PROXY_PORT}."
|
||||
echo " Logs: ${NEURON_HOME}/logs/"
|
||||
echo " Uninstall: ./uninstall.sh"
|
||||
echo
|
||||
Reference in New Issue
Block a user