-
Notifications
You must be signed in to change notification settings - Fork 2
Expand file tree
/
Copy pathsetup.sh
More file actions
executable file
·786 lines (692 loc) · 27.3 KB
/
setup.sh
File metadata and controls
executable file
·786 lines (692 loc) · 27.3 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
#!/bin/bash
set -euo pipefail
# Megavibe v3 — Machine setup
# Usage: bash megavibe/setup.sh
# Idempotent: skips tools/MCP already installed, always updates protocol + statusline.
# Supports: macOS, Linux, Windows (Git Bash / WSL). Requires Node.js (npm/npx).
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
BOLD='\033[1m'
GREEN='\033[0;32m'
YELLOW='\033[0;33m'
RED='\033[0;31m'
RESET='\033[0m'
info() { echo -e "${BOLD}$1${RESET}"; }
ok() { echo -e " ${GREEN}✓${RESET} $1"; }
skip() { echo -e " ${YELLOW}skip${RESET} $1 (already installed)"; }
warn() { echo -e " ${YELLOW}!${RESET} $1"; }
fail() { echo -e " ${RED}✗${RESET} $1"; }
NEEDS_LOGIN=()
# ─── npm global prefix (Linux/WSL: avoid EACCES on /usr/local) ─────
if [[ "$(uname -s)" != "Darwin" ]] && [[ "$(id -u 2>/dev/null || echo 1000)" != "0" ]]; then
NPM_PREFIX="${HOME}/.local"
if [[ "$(npm config get prefix 2>/dev/null)" == /usr* ]]; then
npm config set prefix "$NPM_PREFIX"
mkdir -p "$NPM_PREFIX/bin"
# Ensure ~/.local/bin is on PATH for this session
case ":$PATH:" in
*":$NPM_PREFIX/bin:"*) ;;
*) export PATH="$NPM_PREFIX/bin:$PATH" ;;
esac
fi
fi
# ─── Python detection (Windows Git Bash has 'python' not 'python3') ──
PYTHON=""
# poma-memory requires Python >=3.10. Prefer Homebrew python (3.12+) over Xcode CLT python (often 3.9).
for cmd in /opt/homebrew/bin/python3 /usr/local/bin/python3 python3 python; do
if command -v "$cmd" &>/dev/null && "$cmd" -c "import sys; assert sys.version_info >= (3, 10)" &>/dev/null; then
PYTHON="$cmd"
break
fi
done
# If no 3.10+ found, try to install via brew (macOS) or accept older Python with limited functionality
if [ -z "$PYTHON" ]; then
if command -v brew &>/dev/null; then
echo " Python 3.10+ not found — installing via Homebrew..."
brew install python@3.12
for cmd in /opt/homebrew/bin/python3 /usr/local/bin/python3 python3; do
if command -v "$cmd" &>/dev/null && "$cmd" -c "import sys; assert sys.version_info >= (3, 10)" &>/dev/null; then
PYTHON="$cmd"
break
fi
done
if [ -n "$PYTHON" ]; then
ok "Python $($PYTHON --version 2>&1 | cut -d' ' -f2) (Homebrew)"
fi
fi
fi
if [ -z "$PYTHON" ]; then
# Fall back to any Python 3.8+ (telegram-bot works, but poma-memory won't)
for cmd in python3 python; do
if command -v "$cmd" &>/dev/null && "$cmd" -c "import sys; assert sys.version_info >= (3, 8)" &>/dev/null; then
PYTHON="$cmd"
break
fi
done
if [ -n "$PYTHON" ]; then
warn "Python $($PYTHON --version 2>&1 | cut -d' ' -f2) found but poma-memory requires 3.10+"
warn "Install Python 3.10+ for full functionality (brew install python@3.12)"
else
warn "Python 3.8+ not found — poma-memory and Telegram bot will be unavailable"
fi
fi
# ─── pip detection + bootstrap ───────────────────────────────────────
# $PYTHON -m pip may not work if pip wasn't installed with Python (common on
# Debian/Ubuntu where python3-pip is a separate package, or on minimal installs).
# Detect the best pip invocation and bootstrap via ensurepip if needed.
PIP=""
if [ -n "$PYTHON" ]; then
# 1. Try $PYTHON -m pip (most reliable when it works)
if $PYTHON -m pip --version &>/dev/null; then
PIP="$PYTHON -m pip"
else
# 2. Try standalone pip3/pip commands that match our Python
PY_VERSION=$($PYTHON -c "import sys; print(f'{sys.version_info.major}.{sys.version_info.minor}')")
for pipcmd in pip3 pip "pip${PY_VERSION}"; do
if command -v "$pipcmd" &>/dev/null && "$pipcmd" --version &>/dev/null; then
PIP="$pipcmd"
break
fi
done
fi
# 3. Still no pip — try to bootstrap it
if [ -z "$PIP" ]; then
echo " pip not found — attempting to bootstrap..."
if $PYTHON -m ensurepip --default-pip &>/dev/null 2>&1 \
|| $PYTHON -m ensurepip --user &>/dev/null 2>&1; then
if $PYTHON -m pip --version &>/dev/null; then
PIP="$PYTHON -m pip"
ok "pip bootstrapped via ensurepip"
fi
fi
fi
# 4. Last resort: try the OS package manager
if [ -z "$PIP" ]; then
if command -v apt-get &>/dev/null; then
echo " Installing python3-pip via apt..."
sudo apt-get update -qq && sudo apt-get install -y -qq python3-pip
elif command -v dnf &>/dev/null; then
echo " Installing python3-pip via dnf..."
sudo dnf install -y -q python3-pip
elif command -v pacman &>/dev/null; then
echo " Installing python-pip via pacman..."
sudo pacman -S --noconfirm python-pip
elif command -v brew &>/dev/null; then
# macOS: pip comes with Homebrew python, but just in case
echo " Reinstalling python3 via brew (to get pip)..."
brew reinstall python3
fi
# Check again after OS install
if $PYTHON -m pip --version &>/dev/null; then
PIP="$PYTHON -m pip"
ok "pip installed via package manager"
fi
fi
if [ -z "$PIP" ]; then
warn "pip not available — Python packages (poma-memory, telegram-bot) will be unavailable"
else
# Upgrade pip if older than 22 (Xcode CLT ships 21.x which can't resolve modern packages)
PIP_MAJOR=$($PIP --version 2>/dev/null | sed 's/pip \([0-9]*\).*/\1/')
if [ -n "$PIP_MAJOR" ] && [ "$PIP_MAJOR" -lt 22 ] 2>/dev/null; then
echo " Upgrading pip ($PIP_MAJOR.x → latest)..."
$PIP install --user --upgrade pip 2>/dev/null \
|| $PIP install --upgrade pip 2>/dev/null \
|| warn "Could not upgrade pip — installs may fail"
# Re-detect after upgrade (path may have changed)
if $PYTHON -m pip --version &>/dev/null; then
PIP="$PYTHON -m pip"
fi
fi
fi
fi
# ─── 0. Define installation mode ────────────────────────────────────
NONINTERACTIVE_AUTO=0
if [ "$#" -gt 0 ]; then
case "$1" in
--auto-install)
NONINTERACTIVE_AUTO=1
;;
esac
fi
if [ "$NONINTERACTIVE_AUTO" -eq 0 ]; then
echo " How do you want to install Megavibe?"
echo " 1. Automatic (default) - all supported tools will be installed"
echo " 2. Custom"
read -p " Enter your choice: " NONINTERACTIVE_AUTO
case "${NONINTERACTIVE_AUTO:-1}" in
1) NONINTERACTIVE_AUTO=1 ;;
2) NONINTERACTIVE_AUTO=0 ;;
*) echo " Invalid choice. Please enter 1 or 2." && exit 1 ;;
esac
fi
# ─── 1. Install tools ───────────────────────────────────────────────
info "1) Installing tools"
# Claude Code
claude_install() {
if command -v claude &>/dev/null; then
skip "Claude Code ($(claude --version 2>/dev/null || echo 'installed'))"
else
echo " Installing Claude Code..."
curl -fsSL https://claude.ai/install.sh | bash
ok "Claude Code"
NEEDS_LOGIN+=("claude")
fi
}
# Codex CLI
CODEX_INSTALLED=0
codex_install() {
CODEX_INSTALLED=1
if command -v codex &>/dev/null; then
skip "Codex CLI"
else
echo " Installing Codex CLI..."
npm i -g @openai/codex
ok "Codex CLI"
NEEDS_LOGIN+=("codex")
fi
}
# Gemini CLI
GEMINI_INSTALLED=0
gemini_install() {
GEMINI_INSTALLED=1
if command -v gemini &>/dev/null; then
skip "Gemini CLI"
else
echo " Installing Gemini CLI..."
npm i -g @google/gemini-cli
ok "Gemini CLI"
NEEDS_LOGIN+=("gemini")
fi
}
# jq (required by hooks)
jq_install() {
if command -v jq &>/dev/null; then
skip "jq"
else
echo " Installing jq..."
if command -v brew &>/dev/null; then
brew install jq
elif command -v apt-get &>/dev/null; then
sudo apt-get update -qq && sudo apt-get install -y -qq jq
elif command -v dnf &>/dev/null; then
sudo dnf install -y -q jq
elif command -v pacman &>/dev/null; then
sudo pacman -S --noconfirm jq
elif command -v apk &>/dev/null; then
sudo apk add jq
elif command -v winget &>/dev/null; then
winget install --accept-source-agreements jqlang.jq 2>/dev/null || true
elif command -v choco &>/dev/null; then
choco install -y jq
else
fail "jq not found. Install it manually: https://jqlang.github.io/jq/download/"
exit 1
fi
ok "jq"
fi
}
# Ask before installing CLIs when missing (unless non-interactive --auto-install or no TTY)
should_install_codex() {
if [ "$NONINTERACTIVE_AUTO" -eq 1 ]; then
return 0
fi
if [ ! -t 0 ]; then
return 0
fi
read -r -p " Install Codex CLI? [Y/n] " _codex_ans
case "${_codex_ans:-y}" in
[nN]|[nN][oO]) return 1 ;;
*) return 0 ;;
esac
}
should_install_gemini() {
if [ "$NONINTERACTIVE_AUTO" -eq 1 ]; then
return 0
fi
if [ ! -t 0 ]; then
return 0
fi
read -r -p " Install Gemini CLI? [Y/n] " _gemini_ans
case "${_gemini_ans:-y}" in
[nN]|[nN][oO]) return 1 ;;
*) return 0 ;;
esac
}
claude_install
jq_install
# Install Codex CLI when it is missing (unless non-interactive --auto-install or no TTY)
if command -v codex &>/dev/null || should_install_codex; then
codex_install
else
skip "Codex CLI (user skipped)"
fi
if command -v gemini &>/dev/null || should_install_gemini; then
gemini_install
else
skip "Gemini CLI (user skipped)"
fi
# ─── 2. First-time logins ───────────────────────────────────────────
if [ ${#NEEDS_LOGIN[@]} -gt 0 ]; then
echo ""
info "2) First-time logins"
echo ""
echo " These tools were just installed. To activate them, open a NEW"
echo " terminal window and run each command — it will open your browser"
echo " to sign in:"
echo ""
for tool in "${NEEDS_LOGIN[@]}"; do
case "$tool" in
claude) echo " claude (requires Claude subscription)" ;;
codex) echo " codex (optional — uses your ChatGPT account)" ;;
gemini) echo " gemini (optional — uses your Google account)" ;;
*) echo " $tool" ;;
esac
done
echo ""
echo " You can do this now or later — megavibe works with just Claude."
echo ""
read -p " Press Enter to continue... "
else
echo ""
info "2) Logins — all tools already installed, skipping"
fi
# ─── 3. Deploy megavibe to ~/.megavibe/ + install CLI ─────────────
echo ""
info "3) Deploying megavibe"
MEGAVIBE_HOME="$HOME/.megavibe"
mkdir -p "$MEGAVIBE_HOME"
# Copy core files to ~/.megavibe/ (always overwrite — infrastructure)
cp "$SCRIPT_DIR/setup.sh" "$MEGAVIBE_HOME/setup.sh"
cp "$SCRIPT_DIR/init.sh" "$MEGAVIBE_HOME/init.sh"
# Deploy session-status.sh (used by personal assistant for cross-project visibility)
if [ -f "$SCRIPT_DIR/session-status.sh" ]; then
cp "$SCRIPT_DIR/session-status.sh" "$MEGAVIBE_HOME/session-status.sh"
chmod +x "$MEGAVIBE_HOME/session-status.sh"
fi
# Save detected Python command so hooks and MCP can use it
if [ -n "$PYTHON" ]; then
echo "$PYTHON" > "$MEGAVIBE_HOME/python-cmd"
fi
# Install poma-memory from PyPI (preferred) or fall back to bundled poma_memory.py
if [ -n "$PYTHON" ] && [ -n "$PIP" ]; then
if $PYTHON -c "import poma_memory" &>/dev/null; then
skip "poma-memory (pip, already installed)"
else
echo " Installing poma-memory from PyPI (this may take a minute)..."
$PIP install --user "poma-memory[semantic,mcp]" \
|| $PIP install "poma-memory[semantic,mcp]" \
|| true
if $PYTHON -c "import poma_memory" &>/dev/null; then
ok "poma-memory (pip)"
else
# Fallback: bundled single-file poma_memory.py (works without pip)
if [ -f "$SCRIPT_DIR/poma_memory.py" ]; then
cp "$SCRIPT_DIR/poma_memory.py" "$MEGAVIBE_HOME/poma_memory.py"
# Install minimal deps for bundled version
$PIP install --user numpy model2vec \
|| $PIP install numpy model2vec \
|| warn "Could not install poma-memory deps — search will be unavailable"
ok "poma-memory (bundled fallback)"
else
warn "poma-memory unavailable — search will be disabled"
fi
fi
fi
elif [ -n "$PYTHON" ] && [ -f "$SCRIPT_DIR/poma_memory.py" ]; then
# Python available but no pip — deploy bundled version (deps missing, but file is there)
cp "$SCRIPT_DIR/poma_memory.py" "$MEGAVIBE_HOME/poma_memory.py"
warn "poma-memory deployed without deps (pip unavailable)"
elif [ -f "$SCRIPT_DIR/poma_memory.py" ]; then
cp "$SCRIPT_DIR/poma_memory.py" "$MEGAVIBE_HOME/poma_memory.py"
fi
# Deploy Telegram bot (optional — only used if MEGAVIBE_TELEGRAM_TOKEN is set)
telegram_bot_install() {
if [ -f "$SCRIPT_DIR/telegram-bot.py" ]; then
cp "$SCRIPT_DIR/telegram-bot.py" "$MEGAVIBE_HOME/telegram-bot.py"
ok "telegram-bot.py deployed"
# Install python-telegram-bot if not already present (+ httpx for voice I/O)
if [ -n "$PYTHON" ] && [ -n "$PIP" ]; then
if $PYTHON -c "import telegram" &>/dev/null; then
skip "python-telegram-bot"
else
echo " Installing python-telegram-bot (for Megavibe Remote)..."
$PIP install --user "python-telegram-bot>=21" httpx \
|| $PIP install "python-telegram-bot>=21" httpx \
|| warn "Could not install python-telegram-bot — remote will be unavailable"
if $PYTHON -c "import telegram" &>/dev/null; then
ok "python-telegram-bot + httpx"
fi
fi
fi
fi
}
should_install_telegram() {
if [ "$NONINTERACTIVE_AUTO" -eq 1 ]; then
return 0
fi
if [ ! -t 0 ]; then
return 0
fi
read -r -p " Install Telegram bot? [Y/n] " _telegram_ans
case "${_telegram_ans:-y}" in
[nN]|[nN][oO]) return 1 ;;
*) return 0 ;;
esac
}
TELEGRAM_INSTALLED=0
if should_install_telegram; then
telegram_bot_install
TELEGRAM_INSTALLED=1
else
skip "Telegram bot (user skipped)"
fi
#
rm -rf "$MEGAVIBE_HOME/template"
cp -R "$SCRIPT_DIR/template" "$MEGAVIBE_HOME/template"
ok "~/.megavibe/ synced"
# Initialize personal assistant project (standard megavibe dir)
PERSONAL_DIR="$MEGAVIBE_HOME/personal"
if [ ! -d "$PERSONAL_DIR/.agent" ]; then
mkdir -p "$PERSONAL_DIR/.agent"
cat > "$PERSONAL_DIR/CLAUDE.md" << 'PERSONAL_EOF'
# Personal Assistant
You are the user's personal assistant, responding via Telegram (often from Apple Watch).
## Rules
- Answer the question directly. No preamble, no status reports, no tool availability announcements.
- NEVER say things like "Gemini is available" or report which tools/MCP servers are connected. Just answer.
- Keep responses concise but complete. The user reads on a small screen (Watch/phone).
- Plain text preferred. No code blocks, no markdown tables unless specifically asked.
- Use the user's language (German if they write in German, English if English).
## What you do
- Answer general questions (weather, facts, calculations, advice)
- Remember personal preferences, goals, and context from .agent/ files
- Help with life admin (scheduling, reminders, planning)
- When asked about a specific coding project, mention that the user should ask about it by name (e.g. "megavibe", "officeqa") to route to that project
## Session & project visibility
When asked about running sessions, active projects, or "what's going on":
- Run: `bash ~/.megavibe/session-status.sh` — shows all active/idle megavibe sessions
- The project registry is at `~/.megavibe/projects.json` (name → path mapping)
- Each project's `.agent/TASKS.md` has its task status; `.agent/FULL_CONTEXT.md` has recent activity
## Context files
- .agent/FULL_CONTEXT.md — ongoing personal context log
- .agent/LESSONS.md — personal preferences and patterns
- .agent/DECISIONS.md — life decisions and rationale
PERSONAL_EOF
echo "# Personal Context Log" > "$PERSONAL_DIR/.agent/FULL_CONTEXT.md"
echo "# Personal Decisions" > "$PERSONAL_DIR/.agent/DECISIONS.md"
echo "# Personal Preferences" > "$PERSONAL_DIR/.agent/LESSONS.md"
ok "~/.megavibe/personal/ (personal assistant project)"
else
skip "~/.megavibe/personal/ (already exists)"
fi
# Initialize projects registry
if [ ! -f "$MEGAVIBE_HOME/projects.json" ]; then
echo '{}' > "$MEGAVIBE_HOME/projects.json"
ok "~/.megavibe/projects.json (project registry)"
else
skip "~/.megavibe/projects.json (already exists)"
fi
# Remember source repo so the deployed CLI can sync from it later
echo "$SCRIPT_DIR" > "$MEGAVIBE_HOME/source-repo"
# Install CLI wrapper to ~/.local/bin/ (symlink to ~/.megavibe/ copy)
CLI_DIR="$HOME/.local/bin"
mkdir -p "$CLI_DIR"
# Copy to ~/.megavibe/ first (already done above via setup.sh copy)
cp "$SCRIPT_DIR/megavibe" "$MEGAVIBE_HOME/megavibe"
chmod +x "$MEGAVIBE_HOME/megavibe"
# Symlink from ~/.local/bin/ → ~/.megavibe/ so updates propagate automatically
ln -sf "$MEGAVIBE_HOME/megavibe" "$CLI_DIR/megavibe"
ok "megavibe CLI installed to $CLI_DIR/megavibe → ~/.megavibe/megavibe"
# Warn if ~/.local/bin is not in PATH
if ! echo "$PATH" | tr ':' '\n' | grep -qx "$CLI_DIR"; then
warn "$CLI_DIR is not in your PATH"
SHELL_NAME=$(basename "${SHELL:-/bin/bash}")
case "$SHELL_NAME" in
zsh) PROFILE_FILE="~/.zshrc" ;;
bash) PROFILE_FILE="~/.bashrc" ;;
fish) PROFILE_FILE="~/.config/fish/config.fish" ;;
*) PROFILE_FILE="~/.bashrc or ~/.profile" ;;
esac
echo " Add this to your shell profile ($PROFILE_FILE):"
echo " export PATH=\"\$HOME/.local/bin:\$PATH\""
echo ""
fi
# ─── 4. Install/update Megavibe protocol in user-level CLAUDE.md ────
echo ""
info "4) Installing Megavibe protocol"
CLAUDE_MD="$HOME/.claude/CLAUDE.md"
MARKER="<!-- megavibe-v3 -->"
END_MARKER="<!-- /megavibe-v3 -->"
V2_HEADING="Operating rules (Megavibe v2)"
mkdir -p "$HOME/.claude"
if [ -f "$CLAUDE_MD" ] && grep -q "$MARKER" "$CLAUDE_MD"; then
# ── v3 already installed — surgical replace ──
if grep -q "$END_MARKER" "$CLAUDE_MD"; then
sed '\|'"$MARKER"'|,\|'"$END_MARKER"'|d' "$CLAUDE_MD" > "${CLAUDE_MD}.tmp"
if [ ! -s "${CLAUDE_MD}.tmp" ] || ! grep -q '[^[:space:]]' "${CLAUDE_MD}.tmp"; then
cp "$SCRIPT_DIR/template/CLAUDE.md" "$CLAUDE_MD"
else
echo "" >> "${CLAUDE_MD}.tmp"
cat "$SCRIPT_DIR/template/CLAUDE.md" >> "${CLAUDE_MD}.tmp"
mv "${CLAUDE_MD}.tmp" "$CLAUDE_MD"
fi
ok "~/.claude/CLAUDE.md updated (v3 refreshed)"
else
warn "~/.claude/CLAUDE.md has start marker but no end marker (legacy install)."
echo " Add '<!-- /megavibe-v3 -->' at the end of the megavibe block, then re-run."
fi
elif [ -f "$CLAUDE_MD" ] && grep -q "$V2_HEADING" "$CLAUDE_MD"; then
# ── v2 detected — strip and replace ──
warn "Megavibe v2 protocol detected in ~/.claude/CLAUDE.md"
# Back up before modifying
cp "$CLAUDE_MD" "${CLAUDE_MD}.pre-v3-backup"
ok "backup saved to ${CLAUDE_MD}.pre-v3-backup"
# v2 was always appended at the end — strip from v2 heading to EOF
sed "/$V2_HEADING/,\$d" "$CLAUDE_MD" > "${CLAUDE_MD}.tmp"
# Remove trailing blank lines from remaining content
if [ -s "${CLAUDE_MD}.tmp" ] && grep -q '[^[:space:]]' "${CLAUDE_MD}.tmp"; then
# User had content before v2 block — preserve it, append v3
sed -e :a -e '/^[[:space:]]*$/{$d;N;ba' -e '}' "${CLAUDE_MD}.tmp" > "${CLAUDE_MD}.tmp2"
mv "${CLAUDE_MD}.tmp2" "${CLAUDE_MD}.tmp"
echo "" >> "${CLAUDE_MD}.tmp"
cat "$SCRIPT_DIR/template/CLAUDE.md" >> "${CLAUDE_MD}.tmp"
mv "${CLAUDE_MD}.tmp" "$CLAUDE_MD"
ok "v2 replaced with v3 (user content preserved)"
else
# File was entirely v2 content
cp "$SCRIPT_DIR/template/CLAUDE.md" "$CLAUDE_MD"
ok "v2 replaced with v3"
fi
elif [ -f "$CLAUDE_MD" ]; then
# ── User has a CLAUDE.md but no megavibe — append ──
echo "" >> "$CLAUDE_MD"
cat "$SCRIPT_DIR/template/CLAUDE.md" >> "$CLAUDE_MD"
ok "Megavibe protocol appended to existing ~/.claude/CLAUDE.md"
else
# ── No CLAUDE.md at all — create ──
cp "$SCRIPT_DIR/template/CLAUDE.md" "$CLAUDE_MD"
ok "~/.claude/CLAUDE.md created"
fi
# Warn about v2 in parent-directory CLAUDE.md files (Claude Code walks up)
for check_file in "$HOME/CLAUDE.md" "$HOME/Documents/CLAUDE.md"; do
if [ -f "$check_file" ] && grep -q "$V2_HEADING" "$check_file"; then
warn "Megavibe v2 content found in $check_file"
echo " Claude Code walks up directories for CLAUDE.md files."
echo " v2 rules there may conflict with v3. Consider removing them."
fi
done
# Clean up stale tmp files
rm -f "${CLAUDE_MD}.tmp" "${CLAUDE_MD}.tmp2"
# Record installed version (for future upgrade detection)
# Only write if no version file exists yet (install.sh stores the git SHA;
# we must not clobber it with a bare "3" or update-check breaks for non-git installs)
[ -f "$MEGAVIBE_HOME/version" ] || echo "3" > "$MEGAVIBE_HOME/version"
# ─── 5. Install/update statusline ───────────────────────────────────
echo ""
info "5) Installing statusline"
STATUSLINE_SCRIPT="$HOME/.claude/statusline.sh"
cp "$SCRIPT_DIR/template/statusline.sh" "$STATUSLINE_SCRIPT"
chmod +x "$STATUSLINE_SCRIPT"
ok "~/.claude/statusline.sh"
# Add statusLine + attribution config to user-level settings.json
USER_SETTINGS="$HOME/.claude/settings.json"
MEGAVIBE_SETTINGS='{"statusLine":{"type":"command","command":"~/.claude/statusline.sh","padding":2},"attribution":{"commit":"Co-authored-by: megavibe <megavibe@poma-ai.com>","pr":""}}'
if [ -f "$USER_SETTINGS" ]; then
if command -v jq &>/dev/null; then
# Merge megavibe defaults (statusLine + attribution) into existing settings
# jq * does recursive merge — user overrides are preserved if set after setup
jq --argjson mv "$MEGAVIBE_SETTINGS" '. * $mv' \
"$USER_SETTINGS" > "${USER_SETTINGS}.tmp"
mv "${USER_SETTINGS}.tmp" "$USER_SETTINGS"
ok "settings.json updated (statusLine + attribution)"
else
warn "Could not merge settings (jq not available)"
fi
else
echo "$MEGAVIBE_SETTINGS" | jq . > "$USER_SETTINGS"
ok "~/.claude/settings.json created (statusLine + attribution)"
fi
# ─── 6. Register MCP servers ────────────────────────────────────────
echo ""
info "6) Registering MCP servers"
# Check which MCP servers are already registered
EXISTING_MCP=$(claude mcp list 2>/dev/null || echo "")
# Helper: check MCP server health. Returns:
# 0 = connected and healthy (skip)
# 1 = not registered (needs add)
# 2 = registered but not connected (needs remove + re-add)
mcp_health() {
local name="$1"
if echo "$EXISTING_MCP" | grep -qi "${name}.*Connected"; then
return 0
elif echo "$EXISTING_MCP" | grep -qi "$name"; then
return 2
else
return 1
fi
}
# Helper: ensure MCP server is registered and healthy
ensure_mcp() {
local name="$1"
shift
# remaining args are the claude mcp add command args
local status=0
mcp_health "$name" || status=$?
if [ "$status" -eq 0 ]; then
skip "$name MCP server"
return 0
elif [ "$status" -eq 2 ]; then
warn "$name MCP registered but not connected — re-registering"
claude mcp remove "$name" 2>/dev/null || true
fi
claude mcp add --transport stdio --scope user "$name" -- "$@"
ok "$name MCP server"
}
# Codex MCP
register_codex_mcp() {
ensure_mcp codex codex mcp-server
}
if command -v codex &>/dev/null; then
register_codex_mcp
fi
# Gemini MCP
register_gemini_mcp() {
ensure_mcp gemini-cli npx -y gemini-mcp-tool
# Gemini CLI auth: use API key when GEMINI_API_KEY is set
# (OAuth hits Cloud AI Companion API which requires GCP IAM permissions;
# API key mode hits the public generativelanguage.googleapis.com endpoint)
GEMINI_SETTINGS="$HOME/.gemini/settings.json"
if [ -n "${GEMINI_API_KEY:-}" ]; then
mkdir -p "$HOME/.gemini"
if [ -f "$GEMINI_SETTINGS" ]; then
if command -v jq &>/dev/null && jq -e '.security.auth.selectedType' "$GEMINI_SETTINGS" &>/dev/null; then
CURRENT_AUTH=$(jq -r '.security.auth.selectedType' "$GEMINI_SETTINGS")
if [ "$CURRENT_AUTH" != "gemini-api-key" ]; then
jq '.security.auth.selectedType = "gemini-api-key"' "$GEMINI_SETTINGS" > "${GEMINI_SETTINGS}.tmp"
mv "${GEMINI_SETTINGS}.tmp" "$GEMINI_SETTINGS"
ok "Gemini CLI switched to API key auth (was: $CURRENT_AUTH)"
fi
fi
else
echo '{"security":{"auth":{"selectedType":"gemini-api-key"}}}' | jq . > "$GEMINI_SETTINGS"
ok "Gemini CLI configured for API key auth"
fi
fi
}
if [ "$GEMINI_INSTALLED" -eq 1 ]; then
register_gemini_mcp
fi
# Playwright MCP
register_playwright_mcp() {
# Re-register if args drift (legacy --no-sandbox, or missing --headless).
if command -v claude &>/dev/null; then
local current_args
current_args=$(claude mcp get playwright 2>/dev/null | awk -F': ' '/^ Args:/{sub(/^ Args: /,""); print; exit}')
if [ -n "$current_args" ]; then
if echo "$current_args" | grep -q -- "--no-sandbox" || ! echo "$current_args" | grep -q -- "--headless"; then
warn "Playwright MCP args stale — re-registering (was: $current_args)"
claude mcp remove playwright 2>/dev/null || true
fi
fi
fi
ensure_mcp playwright npx -y @playwright/mcp@latest --headless
# Ensure Playwright browsers are installed (required for @playwright/mcp to work)
if npx -y @playwright/mcp@latest --help &>/dev/null 2>&1; then
if npx playwright install chromium &>/dev/null 2>&1; then
ok "Playwright chromium browser"
else
warn "Could not install Playwright browsers — run: npx playwright install chromium"
fi
else
warn "Playwright MCP not available — browser install skipped"
fi
}
should_install_playwright() {
if [ "$NONINTERACTIVE_AUTO" -eq 1 ]; then
return 0
fi
if [ ! -t 0 ]; then
return 0
fi
read -r -p " Install Playwright MCP server? [Y/n] " _playwright_ans
case "${_playwright_ans:-y}" in
[nN]|[nN][oO]) return 1 ;;
*) return 0 ;;
esac
}
if should_install_playwright; then
register_playwright_mcp
else
skip "Playwright MCP server (user skipped)"
fi
# poma-memory MCP (pip-installed preferred, bundled fallback)
# Can't use ensure_mcp directly — has pip vs bundled fallback logic
POMA_MCP_STATUS=0
mcp_health "poma-memory" || POMA_MCP_STATUS=$?
if [ "$POMA_MCP_STATUS" -eq 0 ]; then
skip "poma-memory MCP server"
else
[ "$POMA_MCP_STATUS" -eq 2 ] && {
warn "poma-memory MCP registered but not connected — re-registering"
claude mcp remove poma-memory 2>/dev/null || true
}
if command -v poma-memory &>/dev/null && poma-memory mcp --help &>/dev/null; then
claude mcp add --transport stdio --scope user poma-memory -- poma-memory mcp
ok "poma-memory MCP server (pip)"
elif [ -f "$MEGAVIBE_HOME/poma_memory.py" ] && [ -n "$PYTHON" ]; then
claude mcp add --transport stdio --scope user poma-memory -- "$PYTHON" "$MEGAVIBE_HOME/poma_memory.py" mcp
ok "poma-memory MCP server (bundled)"
else
skip "poma-memory MCP server (not installed — run: pip install poma-memory[mcp])"
fi
fi
# ─── 7. Verify ──────────────────────────────────────────────────────
echo ""
info "7) Verification"
claude mcp list 2>/dev/null && ok "MCP servers listed" || warn "Could not list MCP servers (claude may need login first)"
# ─── Done ────────────────────────────────────────────────────────────
echo ""
info "Machine setup complete."
echo ""
echo " Next: cd into any project and run:"
echo " megavibe"
echo ""