Skip to content

Instantly share code, notes, and snippets.

@NoDataFound
Last active April 16, 2026 23:27
Show Gist options
  • Select an option

  • Save NoDataFound/4dfd14bf575c469ee9865848f6ad1d78 to your computer and use it in GitHub Desktop.

Select an option

Save NoDataFound/4dfd14bf575c469ee9865848f6ad1d78 to your computer and use it in GitHub Desktop.
VantaBrain installer — includes hakcerline (Claude Code statusline). Standalone hakcerline install: npx hakcerline install
VantaBrain installer — includes hakcerline (Claude Code statusline). Standalone hakcerline install: npx hakcerline install
#!/bin/bash
# VantaBrain installer
# curl -fsSL https://vb.darkcode.ai | bash
set -u
VB_VERSION="3.2.1"
REPO="vantagrid/vantabrain"
BRAIN_HOME="${BRAIN_HOME:-$HOME/VantaBrain}"
# hakcerline — npm package, no local clone needed
HAKCER_CONFIG_DIR="${XDG_CONFIG_HOME:-$HOME/.config}/hakcerline"
# Lobotomy marker — if this exists, installer refuses to run
LOBOTOMY_MARKER="$HOME/.vantabrain-lobotomy"
# -- styling ---------------------------------------------------------------
CYAN='\033[38;2;0;180;220m'
GREEN='\033[38;2;0;200;100m'
RED='\033[38;2;220;50;30m'
YELLOW='\033[38;2;255;200;0m'
DIM='\033[2m'
BOLD='\033[1m'
WHITE='\033[38;2;255;255;255m'
NC='\033[0m'
INVERT='\033[7m'
# Spinner
SPIN_FRAMES=('·' '' '' '' '' '' '' '' '' '')
SPIN_PID=""
spin_start() {
local msg="$1"
(
local i=0
while true; do
local ch="${SPIN_FRAMES[$((i % ${#SPIN_FRAMES[@]}))]}"
printf "\r\033[2K ${CYAN}%s${NC} %b" "$ch" "$msg" >&2
i=$((i + 1))
sleep 0.12
done
) &
SPIN_PID=$!
}
spin_ok() {
[ -n "$SPIN_PID" ] && kill "$SPIN_PID" 2>/dev/null && wait "$SPIN_PID" 2>/dev/null
SPIN_PID=""
printf "\r\033[2K ${GREEN}${NC} %b\n" "$*" >&2
}
spin_fail() {
[ -n "$SPIN_PID" ] && kill "$SPIN_PID" 2>/dev/null && wait "$SPIN_PID" 2>/dev/null
SPIN_PID=""
printf "\r\033[2K ${RED}${NC} %b\n" "$*" >&2
}
trap '[ -n "$SPIN_PID" ] && kill "$SPIN_PID" 2>/dev/null' EXIT
# -- TTY handling ----------------------------------------------------------
# When piped via curl|bash, stdin is the pipe. All interactive reads use TTY.
if [ -t 0 ]; then
TTY=/dev/stdin
else
TTY=/dev/tty
fi
# Read a single keypress (including arrow key sequences)
read_key() {
local key
IFS= read -rsn1 key <"$TTY"
# If escape, read the rest of the sequence
if [ "$key" = $'\x1b' ]; then
local seq
IFS= read -rsn2 seq <"$TTY"
key="${key}${seq}"
fi
printf '%s' "$key"
}
# -- menu: single select --------------------------------------------------
# Usage: menu_select "prompt" "option1" "option2" ...
# Returns: index (0-based) in $MENU_RESULT
MENU_RESULT=0
menu_select() {
local prompt="$1"
shift
local options=("$@")
local count=${#options[@]}
local cursor=0
# Hide cursor
printf '\033[?25l' >&2
# Print prompt
printf "\n ${BOLD}${WHITE}%s${NC}\n\n" "$prompt" >&2
# Draw initial menu
local i
for ((i = 0; i < count; i++)); do
if [ $i -eq $cursor ]; then
printf " ${CYAN}${NC} ${WHITE}%s${NC}\n" "${options[$i]}" >&2
else
printf " ${DIM}%s${NC}\n" "${options[$i]}" >&2
fi
done
while true; do
local key
key="$(read_key)"
case "$key" in
$'\x1b[A') # Up arrow
[ $cursor -gt 0 ] && cursor=$((cursor - 1))
;;
$'\x1b[B') # Down arrow
[ $cursor -lt $((count - 1)) ] && cursor=$((cursor + 1))
;;
'') # Enter
break
;;
esac
# Redraw: move up $count lines, redraw
printf "\033[%dA" "$count" >&2
for ((i = 0; i < count; i++)); do
printf "\033[2K" >&2
if [ $i -eq $cursor ]; then
printf " ${CYAN}${NC} ${WHITE}%s${NC}\n" "${options[$i]}" >&2
else
printf " ${DIM}%s${NC}\n" "${options[$i]}" >&2
fi
done
done
# Show cursor, print selection
printf '\033[?25h' >&2
printf "\033[%dA" "$count" >&2
for ((i = 0; i < count; i++)); do
printf "\033[2K" >&2
if [ $i -eq $cursor ]; then
printf " ${GREEN}${NC} ${WHITE}%s${NC}\n" "${options[$i]}" >&2
else
printf "\033[1B" >&2
fi
done
# Move to end
printf "\033[%dB" "$((count - cursor - 1))" >&2
# Clear the unselected lines
printf "\033[%dA" "$((count))" >&2
for ((i = 0; i < count; i++)); do
if [ $i -eq $cursor ]; then
printf " ${GREEN}${NC} ${WHITE}%s${NC}\n" "${options[$i]}" >&2
else
printf "\033[2K\n" >&2
fi
done
MENU_RESULT=$cursor
}
# -- menu: multiselect ----------------------------------------------------
# Usage: menu_multiselect "prompt" "opt1" "opt2" ...
# Returns: space-separated indices in $MULTI_RESULT
# First option is always "All" — toggling it toggles everything.
MULTI_RESULT=""
menu_multiselect() {
local prompt="$1"
shift
local options=("$@")
local count=${#options[@]}
local cursor=0
local selected=()
# Initialize all unselected
local i
for ((i = 0; i < count; i++)); do
selected+=("0")
done
printf '\033[?25l' >&2
printf "\n ${BOLD}${WHITE}%s${NC}\n" "$prompt" >&2
printf " ${DIM}↑↓ navigate · space toggle · enter confirm${NC}\n\n" >&2
# Initial draw
for ((i = 0; i < count; i++)); do
printf "\033[2K" >&2
if [ "${selected[$i]}" = "1" ]; then
local _chk="${GREEN}${NC}"
else
local _chk="${DIM}${NC}"
fi
if [ $i -eq $cursor ]; then
printf " ${CYAN}${NC} %b ${WHITE}%s${NC}\n" "$_chk" "${options[$i]}" >&2
else
printf " %b ${DIM}%s${NC}\n" "$_chk" "${options[$i]}" >&2
fi
done
while true; do
local key
key="$(read_key)"
case "$key" in
$'\x1b[A') # Up
[ $cursor -gt 0 ] && cursor=$((cursor - 1))
;;
$'\x1b[B') # Down
[ $cursor -lt $((count - 1)) ] && cursor=$((cursor + 1))
;;
' ') # Space — toggle
if [ $cursor -eq 0 ]; then
# "All" toggle
local new_state
if [ "${selected[0]}" = "1" ]; then new_state="0"; else new_state="1"; fi
for ((i = 0; i < count; i++)); do
selected[$i]="$new_state"
done
else
# Toggle individual
if [ "${selected[$cursor]}" = "1" ]; then
selected[$cursor]="0"
selected[0]="0"
else
selected[$cursor]="1"
local all_on=1
for ((i = 1; i < count; i++)); do
[ "${selected[$i]}" = "0" ] && all_on=0 && break
done
[ $all_on -eq 1 ] && selected[0]="1"
fi
fi
;;
'') # Enter
break
;;
esac
# Redraw
printf "\033[%dA" "$count" >&2
for ((i = 0; i < count; i++)); do
printf "\033[2K" >&2
if [ "${selected[$i]}" = "1" ]; then
_chk="${GREEN}${NC}"
else
_chk="${DIM}${NC}"
fi
if [ $i -eq $cursor ]; then
printf " ${CYAN}${NC} %b ${WHITE}%s${NC}\n" "$_chk" "${options[$i]}" >&2
else
printf " %b ${DIM}%s${NC}\n" "$_chk" "${options[$i]}" >&2
fi
done
done
printf '\033[?25h' >&2
# Collapse to final display
printf "\033[%dA" "$count" >&2
MULTI_RESULT=""
for ((i = 0; i < count; i++)); do
printf "\033[2K" >&2
if [ "${selected[$i]}" = "1" ] && [ $i -gt 0 ]; then
printf " ${GREEN}${NC} ${WHITE}%s${NC}\n" "${options[$i]}" >&2
MULTI_RESULT="${MULTI_RESULT} ${i}"
else
printf "\n" >&2
fi
done
MULTI_RESULT="${MULTI_RESULT# }"
}
# -- confirm prompt --------------------------------------------------------
confirm() {
local prompt="$1"
local default="${2:-n}"
printf "\n ${BOLD}${WHITE}%s${NC} " "$prompt" >&2
if [ "$default" = "y" ]; then
printf "${DIM}[Y/n]${NC} " >&2
else
printf "${DIM}[y/N]${NC} " >&2
fi
local answer
IFS= read -rn1 answer <"$TTY"
printf "\n" >&2
case "$answer" in
[Yy]) return 0 ;;
[Nn]) return 1 ;;
'')
[ "$default" = "y" ] && return 0 || return 1
;;
*) return 1 ;;
esac
}
# -- detection functions ---------------------------------------------------
has_vantabrain() {
[ -d "$BRAIN_HOME/.git" ]
}
has_hakcer() {
# Check for config dir (installed via npx hakcerline install)
[ -f "$HAKCER_CONFIG_DIR/config.json" ] && return 0
# Check for statusLine in Claude settings
if [ -f "$HOME/.claude/settings.json" ]; then
node -e "
const s = JSON.parse(require('fs').readFileSync('$HOME/.claude/settings.json','utf8'));
process.exit(s.statusLine && String(s.statusLine.command || '').includes('hakcerline') ? 0 : 1);
" 2>/dev/null && return 0
fi
return 1
}
has_vscode_ext() {
# Check code-server first, then regular VS Code
if command -v code-server >/dev/null 2>&1; then
code-server --list-extensions 2>/dev/null | grep -qi "vantabrain" && return 0
fi
if command -v code >/dev/null 2>&1; then
code --list-extensions 2>/dev/null | grep -qi "vantabrain" && return 0
fi
# Check known extension directories
[ -d "$HOME/.vscode/extensions/vantagrid.vantabrain-"* ] 2>/dev/null && return 0
[ -d "$HOME/.local/share/code-server/extensions/vantagrid.vantabrain-"* ] 2>/dev/null && return 0
return 1
}
has_anything() {
has_vantabrain || has_hakcer || has_vscode_ext
}
# -- install functions -----------------------------------------------------
install_vantabrain() {
# Clone or update
if [ -d "$BRAIN_HOME/.git" ]; then
spin_start "Updating VantaBrain ${DIM}$BRAIN_HOME${NC}"
git -C "$BRAIN_HOME" pull --rebase --autostash >/dev/null 2>&1 || {
spin_fail "git pull failed — continuing with existing version"
}
spin_ok "Updated ${DIM}$BRAIN_HOME${NC}"
elif [ -d "$BRAIN_HOME" ]; then
spin_start "Backing up and cloning fresh..."
mv "$BRAIN_HOME" "${BRAIN_HOME}.bak.$(date +%s)"
gh repo clone "$REPO" "$BRAIN_HOME" -- --quiet
spin_ok "Cloned ${DIM}(old dir backed up)${NC}"
else
spin_start "Cloning VantaBrain..."
gh repo clone "$REPO" "$BRAIN_HOME" -- --quiet
spin_ok "Cloned to ${DIM}$BRAIN_HOME${NC}"
fi
# CLI deps
spin_start "Installing CLI dependencies..."
(cd "$BRAIN_HOME/cli" && npm install --silent 2>/dev/null)
spin_ok "CLI dependencies installed"
# Web deps
spin_start "Installing web dependencies..."
(cd "$BRAIN_HOME/web" && npm install --silent 2>/dev/null)
spin_ok "Web dependencies installed"
# PATH — persist to shell profile so it survives reboots
export PATH="$BRAIN_HOME/bin:$PATH"
export BRAIN_HOME="$BRAIN_HOME"
local profile
profile="$(detect_profile)"
if [ -n "$profile" ]; then
# Add PATH + BRAIN_HOME if not already present
if ! grep -q 'VantaBrain/bin' "$profile" 2>/dev/null; then
{
echo ""
echo "# VantaBrain"
echo "export BRAIN_HOME=\"$BRAIN_HOME\""
echo "export PATH=\"\$BRAIN_HOME/bin:\$PATH\""
} >> "$profile"
spin_ok "PATH added to ${DIM}$profile${NC}"
else
spin_ok "PATH already in ${DIM}$profile${NC}"
fi
fi
# Make all bin scripts executable
chmod +x "$BRAIN_HOME/bin/"* 2>/dev/null || true
# Database setup
if [ ! -f "$BRAIN_HOME/.env" ]; then
echo "" >&2
echo -e " ${BOLD}${WHITE}Database Setup${NC}" >&2
echo -e " ${DIM}VantaBrain uses Neon Postgres for multi-machine state.${NC}" >&2
echo -e " ${DIM}Get your connection string from: https://console.neon.tech${NC}" >&2
echo "" >&2
printf " DATABASE_URL: " >&2
local db_url
IFS= read -r db_url <"$TTY"
if [ -n "$db_url" ]; then
echo "DATABASE_URL=$db_url" > "$BRAIN_HOME/.env"
spin_ok "Database configured"
else
echo -e " ${DIM}Skipped. Add later: echo 'DATABASE_URL=...' > $BRAIN_HOME/.env${NC}" >&2
fi
fi
# Run Ink TUI onboard
spin_start "Launching setup..."
sleep 0.3
spin_ok "Ready"
echo "" >&2
if [ -t 0 ]; then
(cd "$BRAIN_HOME/cli" && npx tsx src/cli.tsx onboard)
else
(cd "$BRAIN_HOME/cli" && npx tsx src/cli.tsx onboard <"$TTY")
fi
# Source profile
if [ -n "$profile" ]; then
. "$profile" 2>/dev/null || true
fi
}
install_hakcer() {
spin_start "Installing hakcerline via npm"
# Run the built-in interactive installer (TUI with scene picker)
spin_ok "Launching hakcerline installer"
echo "" >&2
if [ -t 0 ]; then
npx -y hakcerline@latest install
else
npx -y hakcerline@latest install <"$TTY"
fi
local exit_code=$?
if [ $exit_code -ne 0 ]; then
spin_fail "hakcerline install failed (exit $exit_code)"
return 1
fi
# Verify it landed
if has_hakcer; then
spin_ok "Claude Code statusLine → ${WHITE}hakcerline${NC}"
else
spin_fail "hakcerline installed but statusLine not detected — run ${WHITE}npx hakcerline install${NC} manually"
return 1
fi
}
install_vscode_ext() {
local vsix="$BRAIN_HOME/vscode/vantabrain-0.2.4.vsix"
if [ ! -f "$vsix" ]; then
# Try to pull latest if brain is installed
if has_vantabrain; then
vsix="$(ls -t "$BRAIN_HOME/vscode/"*.vsix 2>/dev/null | head -1)"
fi
if [ -z "$vsix" ] || [ ! -f "$vsix" ]; then
spin_fail "VantaBrain VSIX not found — install VantaBrain first"
return 1
fi
fi
local installed=0
if command -v code-server >/dev/null 2>&1; then
spin_start "Installing VantaBrain extension into code-server..."
if code-server --install-extension "$vsix" --force >/dev/null 2>&1; then
spin_ok "Extension installed in code-server"
installed=1
else
spin_fail "code-server install failed"
fi
fi
if command -v code >/dev/null 2>&1; then
spin_start "Installing VantaBrain extension into VS Code..."
if code --install-extension "$vsix" --force >/dev/null 2>&1; then
spin_ok "Extension installed in VS Code"
installed=1
else
spin_fail "VS Code install failed"
fi
fi
if [ $installed -eq 0 ]; then
spin_fail "Neither code-server nor VS Code found on PATH"
return 1
fi
}
# -- uninstall functions ---------------------------------------------------
uninstall_vantabrain() {
if ! has_vantabrain; then
spin_ok "VantaBrain not installed — nothing to do"
return 0
fi
spin_start "Removing VantaBrain ${DIM}$BRAIN_HOME${NC}"
# Remove PATH entry from shell profile
local profile
profile="$(detect_profile)"
if [ -n "$profile" ] && [ -f "$profile" ]; then
# Remove lines that add BRAIN_HOME/bin to PATH
sed -i.bak '/VantaBrain\/bin/d' "$profile" 2>/dev/null || \
sed -i '' '/VantaBrain\/bin/d' "$profile" 2>/dev/null || true
fi
rm -rf "$BRAIN_HOME"
spin_ok "VantaBrain removed"
}
uninstall_hakcer() {
if ! has_hakcer; then
spin_ok "hakcerline not installed — nothing to do"
return 0
fi
spin_start "Removing hakcerline"
# Use the built-in uninstaller (removes statusLine from settings + config dir)
npx -y hakcerline@latest uninstall --yes >/dev/null 2>&1 || true
# Belt + suspenders: ensure statusLine is gone from Claude settings
unpatch_claude_statusline
# Remove config dir if the npm uninstaller missed it
[ -d "$HAKCER_CONFIG_DIR" ] && rm -rf "$HAKCER_CONFIG_DIR"
spin_ok "hakcerline removed"
}
uninstall_vscode_ext() {
local removed=0
if command -v code-server >/dev/null 2>&1; then
spin_start "Removing VantaBrain extension from code-server..."
code-server --uninstall-extension vantagrid.vantabrain >/dev/null 2>&1 && removed=1
spin_ok "Extension removed from code-server"
fi
if command -v code >/dev/null 2>&1; then
spin_start "Removing VantaBrain extension from VS Code..."
code --uninstall-extension vantagrid.vantabrain >/dev/null 2>&1 && removed=1
spin_ok "Extension removed from VS Code"
fi
if [ $removed -eq 0 ]; then
spin_ok "VantaBrain extension not found — nothing to do"
fi
}
# -- settings patchers -----------------------------------------------------
unpatch_claude_statusline() {
node - >/dev/null 2>&1 <<'NODE'
const fs = require('fs');
const os = require('os');
const path = require('path');
const p = path.join(os.homedir(), '.claude', 'settings.json');
if (!fs.existsSync(p)) return;
try {
const cfg = JSON.parse(fs.readFileSync(p, 'utf-8'));
if (cfg.statusLine && String(cfg.statusLine.command || '').includes('hakcerline')) {
delete cfg.statusLine;
fs.writeFileSync(p, JSON.stringify(cfg, null, 2) + '\n');
}
} catch (e) {}
NODE
}
# -- lobotomy --------------------------------------------------------------
lobotomy() {
echo "" >&2
echo -e " ${RED}${BOLD}╔══════════════════════════════════════════════╗${NC}" >&2
echo -e " ${RED}${BOLD}║ L O B O T O M Y ║${NC}" >&2
echo -e " ${RED}${BOLD}╠══════════════════════════════════════════════╣${NC}" >&2
echo -e " ${RED}${BOLD}${NC} This will: ${RED}${BOLD}${NC}" >&2
echo -e " ${RED}${BOLD}${NC} ${RED}${BOLD}${NC}" >&2
echo -e " ${RED}${BOLD}${NC} ${WHITE}• Remove VantaBrain repo + all notes${NC} ${RED}${BOLD}${NC}" >&2
echo -e " ${RED}${BOLD}${NC} ${WHITE}• Remove hakcerline config + statusLine${NC} ${RED}${BOLD}${NC}" >&2
echo -e " ${RED}${BOLD}${NC} ${WHITE}• Uninstall VS Code extension${NC} ${RED}${BOLD}${NC}" >&2
echo -e " ${RED}${BOLD}${NC} ${WHITE}• Nuke ~/.claude/ (settings, sessions,${NC} ${RED}${BOLD}${NC}" >&2
echo -e " ${RED}${BOLD}${NC} ${WHITE} memory, project data, CLAUDE.md files)${NC} ${RED}${BOLD}${NC}" >&2
echo -e " ${RED}${BOLD}${NC} ${WHITE}• Remove vantabrain from PATH${NC} ${RED}${BOLD}${NC}" >&2
echo -e " ${RED}${BOLD}${NC} ${WHITE}• Drop a marker to prevent re-enrollment${NC} ${RED}${BOLD}${NC}" >&2
echo -e " ${RED}${BOLD}${NC} ${RED}${BOLD}${NC}" >&2
echo -e " ${RED}${BOLD}${NC} ${YELLOW}This cannot be undone.${NC} ${RED}${BOLD}${NC}" >&2
echo -e " ${RED}${BOLD}╚══════════════════════════════════════════════╝${NC}" >&2
echo "" >&2
if ! confirm "Type 'y' to proceed with lobotomy" "n"; then
echo -e " ${DIM}Aborted.${NC}" >&2
return 1
fi
echo "" >&2
# 1. Uninstall VS Code extension
uninstall_vscode_ext
# 2. Uninstall hakcerline
uninstall_hakcer
# 3. Remove VantaBrain repo
if [ -d "$BRAIN_HOME" ]; then
spin_start "Removing VantaBrain ${DIM}$BRAIN_HOME${NC}"
rm -rf "$BRAIN_HOME"
spin_ok "VantaBrain repo removed"
fi
# 4. Remove PATH entries from shell profile
local profile
profile="$(detect_profile)"
if [ -n "$profile" ] && [ -f "$profile" ]; then
spin_start "Cleaning shell profile ${DIM}$profile${NC}"
sed -i.bak '/VantaBrain/d; /vantabrain/d; /hakcerline/d' "$profile" 2>/dev/null || \
sed -i '' '/VantaBrain/d; /vantabrain/d; /hakcerline/d' "$profile" 2>/dev/null || true
spin_ok "Shell profile cleaned"
fi
# 5. Nuke ~/.claude/
if [ -d "$HOME/.claude" ]; then
spin_start "Removing ~/.claude/ ${DIM}(settings, sessions, memory, CLAUDE.md files)${NC}"
rm -rf "$HOME/.claude"
spin_ok "~/.claude/ removed"
fi
# 6. Remove any CLAUDE.md files in home or common project dirs
spin_start "Scanning for stray CLAUDE.md files..."
local found_md=0
while IFS= read -r -d '' f; do
rm -f "$f"
found_md=$((found_md + 1))
done < <(find "$HOME" -maxdepth 3 -name "CLAUDE.md" -print0 2>/dev/null)
if [ $found_md -gt 0 ]; then
spin_ok "Removed $found_md CLAUDE.md file(s)"
else
spin_ok "No stray CLAUDE.md files found"
fi
# 7. Drop lobotomy marker
echo "lobotomized=$(date -u +%Y-%m-%dT%H:%M:%SZ)" > "$LOBOTOMY_MARKER"
spin_ok "Lobotomy marker set ${DIM}($LOBOTOMY_MARKER)${NC}"
echo "" >&2
echo -e " ${RED}${NC} ${BOLD}${WHITE}Lobotomy complete${NC}" >&2
echo -e " ${DIM}VantaBrain has been fully removed and will not auto-enroll.${NC}" >&2
echo -e " ${DIM}To reverse: rm $LOBOTOMY_MARKER && re-run this installer.${NC}" >&2
echo "" >&2
}
# -- helpers ---------------------------------------------------------------
detect_profile() {
local shell_name
shell_name="$(basename "${SHELL:-/bin/sh}")"
case "$shell_name" in
zsh) [ -f "$HOME/.zshrc" ] && echo "$HOME/.zshrc" ;;
bash)
if [ -f "$HOME/.bashrc" ]; then echo "$HOME/.bashrc"
elif [ -f "$HOME/.bash_profile" ]; then echo "$HOME/.bash_profile"
fi ;;
fish) [ -f "$HOME/.config/fish/config.fish" ] && echo "$HOME/.config/fish/config.fish" ;;
*) [ -f "$HOME/.profile" ] && echo "$HOME/.profile" ;;
esac
}
# -- status display --------------------------------------------------------
show_status() {
echo "" >&2
echo -e " ${BOLD}${WHITE}Installed Components${NC}" >&2
echo "" >&2
if has_vantabrain; then
local vb_ver
vb_ver="$(git -C "$BRAIN_HOME" describe --tags 2>/dev/null || git -C "$BRAIN_HOME" log -1 --format='%h' 2>/dev/null || echo 'unknown')"
echo -e " ${GREEN}${NC} VantaBrain ${DIM}$BRAIN_HOME ($vb_ver)${NC}" >&2
else
echo -e " ${DIM}□ VantaBrain not installed${NC}" >&2
fi
if has_hakcer; then
echo -e " ${GREEN}${NC} hakcerline ${DIM}npm · config: $HAKCER_CONFIG_DIR${NC}" >&2
else
echo -e " ${DIM}□ hakcerline not installed${NC}" >&2
fi
if has_vscode_ext; then
echo -e " ${GREEN}${NC} VS Code extension ${DIM}vantagrid.vantabrain${NC}" >&2
else
echo -e " ${DIM}□ VS Code extension not installed${NC}" >&2
fi
echo "" >&2
}
# =========================================================================
# MAIN
# =========================================================================
# -- banner ----------------------------------------------------------------
echo "" >&2
echo -e " ${BOLD}${WHITE}VantaBrain${NC} ${DIM}v${VB_VERSION}${NC}" >&2
echo "" >&2
# -- lobotomy check --------------------------------------------------------
if [ -f "$LOBOTOMY_MARKER" ]; then
echo -e " ${RED}${NC} This machine has been lobotomized." >&2
echo -e " ${DIM}VantaBrain will not install or auto-enroll.${NC}" >&2
echo -e " ${DIM}To reverse: rm $LOBOTOMY_MARKER${NC}" >&2
echo "" >&2
exit 0
fi
# -- preflight -------------------------------------------------------------
spin_start "Checking dependencies..."
missing=0
for dep in gh git node npm; do
if ! command -v "$dep" >/dev/null 2>&1; then
spin_fail "$dep is required but not installed."
missing=1
fi
done
if [ "$missing" -eq 1 ]; then
echo "" >&2
echo -e " ${DIM}macOS: brew install gh git node${NC}" >&2
echo -e " ${DIM}Linux: apt install gh git nodejs npm${NC}" >&2
exit 1
fi
if ! gh auth status >/dev/null 2>&1; then
spin_fail "gh is not authenticated. Run: ${WHITE}gh auth login${NC}"
exit 1
fi
NODE_MAJOR=$(node -e "console.log(process.versions.node.split('.')[0])")
if [ "$NODE_MAJOR" -lt 18 ]; then
spin_fail "Node.js >= 18 required (found $(node -v))"
echo -e " ${DIM}brew install node or nvm install 20${NC}" >&2
exit 1
fi
spin_ok "Dependencies ${DIM}gh $(gh --version | head -1 | awk '{print $3}'), git $(git --version | awk '{print $3}'), node $(node -v | tr -d v)${NC}"
# -- mode selection --------------------------------------------------------
if has_anything; then
show_status
menu_select "What do you want to do?" \
"Install / update components" \
"Uninstall components" \
"Lobotomy (remove everything, prevent re-enrollment)"
MODE=$MENU_RESULT
else
# Nothing installed — go straight to install
MODE=0
fi
# -- INSTALL ---------------------------------------------------------------
if [ $MODE -eq 0 ]; then
# Build option list based on what needs install vs update
local_opts=("All")
if has_vantabrain; then
local_opts+=("VantaBrain (update)")
else
local_opts+=("VantaBrain")
fi
if has_hakcer; then
local_opts+=("hakcerline (update)")
else
local_opts+=("hakcerline")
fi
if has_vscode_ext; then
local_opts+=("VS Code extension (update)")
else
local_opts+=("VS Code extension")
fi
menu_multiselect "Select components to install:" "${local_opts[@]}"
if [ -z "$MULTI_RESULT" ]; then
echo -e "\n ${DIM}Nothing selected.${NC}" >&2
exit 0
fi
echo "" >&2
for idx in $MULTI_RESULT; do
case $idx in
1) install_vantabrain ;;
2) install_hakcer ;;
3) install_vscode_ext ;;
esac
done
# Final message
echo "" >&2
echo -e " ${GREEN}${NC} ${BOLD}${WHITE}VantaBrain v${VB_VERSION}${NC} — install complete" >&2
if has_hakcer; then
echo -e " ${DIM}Preview: ${WHITE}npx hakcerline preview${NC}" >&2
fi
echo "" >&2
fi
# -- UNINSTALL -------------------------------------------------------------
if [ $MODE -eq 1 ]; then
# Build list of installed components
uninst_opts=("All installed")
uninst_keys=()
key_idx=0
if has_vantabrain; then
uninst_opts+=("VantaBrain")
uninst_keys+=("vantabrain")
fi
if has_hakcer; then
uninst_opts+=("hakcerline")
uninst_keys+=("hakcer")
fi
if has_vscode_ext; then
uninst_opts+=("VS Code extension")
uninst_keys+=("vscode")
fi
if [ ${#uninst_keys[@]} -eq 0 ]; then
echo -e "\n ${DIM}Nothing installed to remove.${NC}" >&2
exit 0
fi
menu_multiselect "Select components to uninstall:" "${uninst_opts[@]}"
if [ -z "$MULTI_RESULT" ]; then
echo -e "\n ${DIM}Nothing selected.${NC}" >&2
exit 0
fi
if ! confirm "Uninstall selected components?" "n"; then
echo -e " ${DIM}Aborted.${NC}" >&2
exit 0
fi
echo "" >&2
for idx in $MULTI_RESULT; do
key_idx=$((idx - 1))
case "${uninst_keys[$key_idx]}" in
vantabrain) uninstall_vantabrain ;;
hakcer) uninstall_hakcer ;;
vscode) uninstall_vscode_ext ;;
esac
done
echo "" >&2
echo -e " ${GREEN}${NC} ${BOLD}${WHITE}Uninstall complete${NC}" >&2
echo "" >&2
fi
# -- LOBOTOMY --------------------------------------------------------------
if [ $MODE -eq 2 ]; then
lobotomy
fi
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment