#!/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 [args...] # splash → claude args... (forwarded verbatim) # # 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 ...` → the mailbox CLI; everything else falls through to launch. case "${1:-}" in message|msg|m) shift exec "$HOME/.claude/scripts/kei-message.sh" "$@" ;; esac # --- args ---------------------------------------------------------------- SPLASH=1 STATUS_ONLY=0 PASSTHROUGH=() for arg in "$@"; do case "$arg" in --no-splash) SPLASH=0 ;; --status) STATUS_ONLY=1; SPLASH=1 ;; *) PASSTHROUGH+=("$arg") ;; esac done # --- locate claude on PATH ----------------------------------------------- CLAUDE_BIN="$(command -v claude 2>/dev/null || true)" if [ -z "$CLAUDE_BIN" ] && [ "$STATUS_ONLY" = "0" ]; then echo "error: 'claude' not on PATH. Install Claude Code first:" >&2 echo " curl -fsSL https://claude.ai/install.sh | sh" >&2 exit 127 fi # --- 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() { local f="${AGENTS_DIR}/_primitives/.installed" if [ -f "$f" ]; then grep -E "^profile" "$f" 2>/dev/null | head -1 | awk -F= '{gsub(/[ "]/,"",$2); print $2}' || echo "?" 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[@]}"