#!/usr/bin/env bash # kei — KeiSeiKit launcher with TUI splash, then exec claude. # # Distinct from `keisei` (Rust exobrain CLI: `keisei attach/mount/...`). # `kei` is the lightweight splash + launch entry point most users want. # # Usage: # kei # splash → claude (interactive REPL) # kei --no-splash # skip splash → exec claude # kei --status # status only, don't launch claude # kei message ... # inter-session mailbox (send/inbox/list) — see kei-message.sh # kei configure # re-pick stack profile + opt-in hook packs # kei pick # interactive picker → set primary → launch it # kei agent "" # invoke agent, backend from DNA → primary # kei agent --on= "" # override backend # kei run-via "" # invoke agent on explicit backend # # backends: claude grok agy copilot kimi codex # # `kei run-via list` shows install status + agents # kei primary [] # get/set primary LLM provider (DNA fallback) # kei mcp-wire [] # wire kei-mcp into a CLI's MCP config + hook setup # # (Phase C cross-CLI policy enforcement) # kei mcp-wire --list # show enforcement tier per CLI # kei limits # probe each CLI's subscription quota (best-effort) # # (4 of 5 CLIs have no public API — honest report) # kei onboard # post-install wizard (pick primary + mcp-wire + check) # kei --on= # one-shot launch of (does not change primary) # kei [args...] # splash → exec primary CLI (default: claude) # # The splash shows: substrate version, agent count, last sleep run, # active sessions (kei-ping). Press any key to skip the dwell. # # Returns: same exit code as `claude`. set -e # --- subcommand dispatch (before splash) --------------------------------- # `kei message ...` → mailbox CLI # `kei configure` → hook/stack re-picker # `kei agent ...` → DNA-resolved agent (manifest provider → primary → claude) # `kei run-via ...` → explicit-backend agent invocation # `kei primary ...` → get/set primary LLM provider # rest = splash + launch claude (legacy primary). case "${1:-}" in message|msg|m) shift exec "$HOME/.claude/scripts/kei-message.sh" "$@" ;; configure|config|reconfigure) shift exec "$HOME/.claude/scripts/kei-configure.sh" "$@" ;; agent) shift exec "$HOME/.claude/scripts/kei-agent-cli.sh" "$@" ;; run-via|via|agent-via) shift exec "$HOME/.claude/scripts/kei-agent-cli.sh" "$@" ;; primary) shift exec "$HOME/.claude/scripts/kei-agent-cli.sh" primary "$@" ;; pick) shift exec "$HOME/.claude/scripts/kei-pick.sh" "$@" ;; mcp-wire|wire) shift exec "$HOME/.claude/scripts/kei-mcp-wire.sh" "$@" ;; limits|quota|usage) shift exec "$HOME/.claude/scripts/kei-limits.sh" "$@" ;; onboard|setup|wizard) shift exec "$HOME/.claude/scripts/kei-onboard.sh" "$@" ;; esac # --- one-shot --on= override (does not write primary.toml) ------- ONESHOT_BACKEND="" for arg in "$@"; do case "$arg" in --on=*) ONESHOT_BACKEND="${arg#--on=}" ;; esac done # --- args ---------------------------------------------------------------- SPLASH=1 STATUS_ONLY=0 PASSTHROUGH=() for arg in "$@"; do case "$arg" in --on=*) ;; # already captured in ONESHOT_BACKEND; don't forward --no-splash) SPLASH=0 ;; --status) STATUS_ONLY=1; SPLASH=1 ;; *) PASSTHROUGH+=("$arg") ;; esac done # --- resolve primary backend --------------------------------------------- # Order: --on= override → ~/.claude/config/primary.toml → claude. resolve_primary() { if [ -n "$ONESHOT_BACKEND" ]; then printf '%s\n' "$ONESHOT_BACKEND"; return; fi if [ -n "${KEI_PRIMARY:-}" ]; then printf '%s\n' "$KEI_PRIMARY"; return; fi local cfg="$HOME/.claude/config/primary.toml" if [ -f "$cfg" ]; then awk -F'=' '/^provider[[:space:]]*=/ { gsub(/^[[:space:]]+|[[:space:]]+$/, "", $2) gsub(/^"|"$/, "", $2) print $2; exit }' "$cfg" return fi printf 'claude\n' } # Map backend name → executable. Mirrors scripts/kei-agent-cli.sh::backend_bin. backend_bin_for() { case "$1" in claude) echo "claude" ;; grok) echo "grok" ;; agy|antigravity) echo "agy" ;; copilot) echo "copilot" ;; kimi) echo "kimi" ;; codex) echo "codex" ;; *) return 1 ;; esac } PRIMARY="$(resolve_primary)" PRIMARY_CLI="$(backend_bin_for "$PRIMARY")" || { echo "error: unknown primary backend: $PRIMARY" >&2 exit 2 } PRIMARY_BIN="$(command -v "$PRIMARY_CLI" 2>/dev/null || true)" if [ -z "$PRIMARY_BIN" ] && [ "$STATUS_ONLY" = "0" ]; then echo "error: primary backend '$PRIMARY' → '$PRIMARY_CLI' not on PATH." >&2 case "$PRIMARY" in claude) echo " install: curl -fsSL https://claude.ai/install.sh | sh" >&2 ;; grok) echo " install: see https://x.ai/grok" >&2 ;; copilot) echo " install: npm i -g @github/copilot" >&2 ;; kimi) echo " install: uv tool install kimi-cli" >&2 ;; agy) echo " install: see https://antigravity.dev" >&2 ;; codex) echo " install: see https://github.com/openai/codex" >&2 ;; esac echo " or: kei pick (interactive picker to choose + set primary)" >&2 echo " or: kei primary (set a different default)" >&2 exit 127 fi # Legacy var name for splash code below. CLAUDE_BIN="$PRIMARY_BIN" # --- read state ---------------------------------------------------------- AGENTS_DIR="${HOME}/.claude/agents" SYNC_DIR="${HOME}/.claude/memory/sync-repo" # Agent count from generated .md files agent_count() { local n=0 if [ -d "$AGENTS_DIR" ]; then n=$(find "$AGENTS_DIR" -maxdepth 1 -name "*.md" -not -name "_*" 2>/dev/null | wc -l | tr -d ' ') fi printf '%s' "$n" } # Profile from .installed marker file profile_name() { # install.sh stamps the chosen profile here; .installed only holds primitive # names (so the old `grep ^profile .installed` always returned "?"). local f="$HOME/.claude/.kei-profile" if [ -f "$f" ]; then head -1 "$f"; else echo "?"; fi } # Last Phase B sleep timestamp last_sleep_run() { local f="${SYNC_DIR}/reports/last-run.txt" if [ -f "$f" ]; then local ts ts=$(cat "$f" 2>/dev/null | head -1) if [ -n "$ts" ]; then # epoch → human; macOS date(1) -r and Linux date(1) -d differ if date -r "$ts" '+%Y-%m-%d %H:%M' >/dev/null 2>&1; then date -r "$ts" '+%Y-%m-%d %H:%M' elif date -d "@$ts" '+%Y-%m-%d %H:%M' >/dev/null 2>&1; then date -d "@$ts" '+%Y-%m-%d %H:%M' else echo "$ts" fi else echo "never" fi else echo "never" fi } # Active session count via kei-ping (auto-selects redis or sqlite backend) active_sessions() { if command -v kei-ping >/dev/null 2>&1; then kei-ping list 2>/dev/null | grep -cE '^\s*[0-9]+s\s' || echo "0" else echo "n/a" fi } # --- splash -------------------------------------------------------------- splash() { local p ac sl as p="$(profile_name)" ac="$(agent_count)" sl="$(last_sleep_run)" as="$(active_sessions)" # Only color if stdout is a tty. Brand palette: голубой (sky-blue) + жёлтый (gold). local C0= C1= C2= C3= CV= if [ -t 1 ]; then C0=$'\033[0m' C1=$'\033[1;38;5;39m' # голубой (sky-blue) — logo C2=$'\033[1;38;5;220m' # жёлтый (gold) — brand line C3=$'\033[2;38;5;39m' # dim blue — separators CV=$'\033[1;38;5;220m' # жёлтый — field values fi cat </dev/null || true fi exec "$CLAUDE_BIN" "${PASSTHROUGH[@]}"