MacBook dla vibe codera — co zadziałało, co nie i 5 lekcji których nie znajdziesz w tutorialach

MacBook dla vibe codera — co zadziałało, co nie i 5 lekcji których nie znajdziesz w tutorialach
Dziesięć godzin sesji konfiguracji nowego MacBooka M4 jako mobilnej stacji vibe codingu. Stack: Cursor, Claude Code, Ollama, lokalne modele. Spisałem to nie jako tutorial — bo tutoriali jest pełno — tylko jako uczciwy raport z miejsc, w których utknąłem, i z tego, jak się z nich wybierałem.
Co znajdziesz w tym artykule
- Genealogia tematu — skąd ten artykuł
- Czym właściwie jest „vibe coding” i dlaczego MacBook do tego pasuje
- Strategia: Mac Studio jako primary, MacBook jako mobile fallback
- Etap 1: fundament systemowy (Apple ID, Homebrew, core packages, Cursor)
- Etap 2: kod projektów — lazy + curated TOP-list zamiast „wszystko po kolei”
- Etap 3: synchronizacja z Mac Studio — co przez git, co przez rsync, co odpuścić
- Etap 4: pamięć Claude Code dla każdego projektu — najtrudniejsza część
- Etap 5: Ollama + lokalne modele i 11 podpodejść do jednego problemu
- Etap 6: jeden skrypt, który robi mobile sync
- 5 lekcji, których nie znajdziesz w tutorialach
- Właściwa ścieżka — checklist bez pułapek (kolejność, komendy, czasy)
- Co dalej i gotowe skrypty
1. Skąd się wziął ten artykuł
Mam na biurku Mac Studio M4 Max 64 GB. To moja główna stacja pracy, na której od kilku miesięcy buduję ekosystem narzędzi SaaS i agentów AI dla klientów. Wszystko działa, jest porządnie skonfigurowane, kontekst dziesiątek projektów rośnie sobie w pamięci Claude Code, w handoffach, w skillach.
I wtedy dotarł MacBook M4 32 GB. Nowy laptop, czysta instalacja, świeży zegar.
Pomysł był prosty: chcę go skonfigurować tak, żeby był drugą maszyną do tej samej pracy, na której pracuję na Mac Studio. Nie kopia, nie klon — raczej mobilna kapsuła, do której mogę przejść w pociągu, na klienta, na kanapę i kontynuować ten sam wątek bez utraty kontekstu. Vibe coding ze świadomością, że narzędzie nie ma wpłynąć na moje myślenie — ma je obsłużyć.
Zarezerwowałem na to popołudnie. Skończyło się dziesięcioma godzinami efektywnej pracy, jednym problemem launchd, do którego musiałem podejść jedenaście razy, oraz pięcioma lekcjami, których w żadnym poradniku setup nie widziałem. Z tych lekcji powstał ten tekst — bo szkoda, żeby po jednej takiej sesji wiedza wsiąkła tylko w jeden CLAUDE.md.
2. Czym właściwie jest „vibe coding” i dlaczego MacBook do tego pasuje
Termin „vibe coding” spopularyzował Andrej Karpathy w 2025 roku. Sens: piszesz oprogramowanie razem z modelem AI, który jest twoim parafrastą programowym. Nie autocompletą — partnerem. Mówisz mu co chcesz osiągnąć, on proponuje rozwiązanie, ty oceniasz, on poprawia. Nie kontrolujesz każdej linii — kontrolujesz kierunek.
To zmienia stack. Zamiast tradycyjnego edytora z linterem i debuggerem masz trzy kategorie narzędzi:
- IDE z wbudowanym agentem — Cursor, VS Code z wtyczką Claude Code, Zed. Edytor staje się rozmową z modelem, a nie tylko miejscem na pisanie kodu.
- Terminalowe CLI agenta — Claude Code w shellu, Codex CLI, Aider. Operują na repozytorium, modyfikują pliki, uruchamiają testy. Działają poza IDE.
- Lokalne modele językowe — Ollama, LM Studio, llama.cpp. Do zadań, których nie chcesz wysyłać do chmury (poufność klienta), albo gdy chcesz drugą opinię od innego modelu bez kosztu API.
MacBook M4 32 GB obsługuje to wszystko z zapasem. Dwa lokalne modele klasy 30B+ jednocześnie w pamięci, równolegle uruchomiony Cursor z agentem, do tego Vercel CLI, Supabase CLI, Ollama jako serwis w tle. Nawet bez podłączenia do prądu komfortowo wytrzymuje cztery, pięć godzin pracy.
Pytanie nie brzmi „czy to udźwignie”, tylko „jak to skonfigurować, żeby drugi komputer rozumiał kontekst pracy z pierwszego”. I właśnie tutaj zaczyna się temat tego artykułu.
3. Strategia: Mac Studio jako primary, MacBook jako mobile fallback
Zanim zacząłem cokolwiek instalować, ustaliłem sobie jedną zasadę: nie kopiuję wszystkiego, co mam na Mac Studio.
Wynika to z prostego rachunku. Na Mac Studio mam 71 repozytoriów GitHub, do tego 64 katalogi w ~/projekty/ (część to repo, część to research notes, część to klienckie audyty). 29 katalogów aktywnej pamięci Claude Code w ~/.claude/projects/. Setki skilli, kilkanaście kluczy API, połączenia OAuth do siedmiu różnych usług.
Klonowanie tego wszystkiego na MacBook miałoby sens, gdybym faktycznie zamierzał na nim pracować nad każdym z tych projektów. Nie zamierzam. MacBook to mobile fallback — emergency mobile device. Default workflow: praca na Mac Studio. MacBook wchodzi w ruch, gdy wyjeżdżam, gdy klient jest poza biurem albo gdy Mac Studio z jakiegoś powodu nie działa.
Z tego wynikają trzy decyzje, które ułożyły cały setup:
- Klonuję 5–7 projektów Tier A — te, z którymi naprawdę pracuję w danym tygodniu. Reszta on-demand, gdy faktycznie potrzebna.
- Synchronizacja przez git, nie przez Time Machine — git push z Mac Studio, git pull na MacBook. Co nie jest w git, robię ręcznie albo przez rsync nad SSH.
- Lokalne modele dwie sztuki, nie cały zoo — qwen3-coder do kodzenia (mniej overhead, brak thinking mode) i qwen3.6 do analizy strategicznej (z thinking mode, większa głębia rozumowania). Dwa zaspokajają 90% przypadków, reszta ad-hoc.
Brzmi jak truizm, ale prawda jest taka, że gdy zaczynasz konfigurować nową maszynę, naturalnym odruchem jest „kopiuję wszystko, później zobaczę”. Po dziesięciu godzinach widzę: gdybym tak zrobił, byłbym teraz na czterdziestej godzinie, pełen niepotrzebnych .env, kluczy API, których nie używam, i memory katalogów dla projektów, które porzuciłem trzy miesiące temu. Lazy + curated to nie lenistwo — to higiena.
4. Etap 1: fundament systemowy
Kolejność tego etapu nie jest przypadkowa — to wynik kilku iteracji. Najpierw to, co macOS robi natywnie i co musi być zrobione zanim cokolwiek innego ma sens.
Apple ID, iCloud Keychain, Touch ID
Pierwsze logowanie — to samo Apple ID, co na Mac Studio. iCloud Keychain włączony — to oszczędza jedno hasło do Anthropic, GitHub i Vercel, bo zsynchronizują się z Mac Studio. FileVault włączony, bo to jest laptop, który czasem zostaje gdzie indziej.
Touch ID dla sudo — wpisałem standardową konfigurację (auth sufficient pam_tid.so w /etc/pam.d/sudo_local) i… nie zadziałało. Dlaczego — diagnozy nie skończyłem, odłożyłem. To nie jest blocker, sudo działa hasłem. Lekcja: nie zatrzymuj się na nice-to-have. Idź do meritum, do kosmetyki wracaj na końcu.
Xcode CLI Tools i Homebrew
xcode-select --install
/bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)"
Trzy giga Xcode CLI — ściągało się około kwadransa. Homebrew po dorzuceniu do PATH (instrukcje, które sam wyświetla) — kolejny kwadrans. To dwa narzędzia, na których siedzi cała reszta — bez nich nie ma git w sensownej wersji, nie ma node, nie ma niczego.
Core packages — pragmatyczna lista
brew install git gh node pnpm jq ripgrep fd htop wget tree coreutils libpq
Każdy z tych pakietów uzasadnia swoją obecność. gh to GitHub CLI — działa lepiej niż git nad SSH dla prywatnych repo. jq do parsowania JSON-ów (np. odpowiedzi z API). ripgrep i fd to szybsze odpowiedniki grep i find, których agent CLI Claude’a używa pod spodem. libpq daje dostęp do psql, który jest backupem, gdy MCP Supabase nie ma uprawnień (a to się zdarza dla nowych projektów klienckich).
Cursor + fonty z polskimi znakami
Cursor instaluję ręcznie z cursor.com — w Homebrew czasem wisi przestarzała wersja. Skrót cursor w PATH konfiguruję przez Cmd+Shift+P → „Shell Command: Install 'cursor’ command”.
Tu jeden szczegół, który programiści nieanglojęzyczni przegapiają: fonty. JetBrains Mono i Fira Code są dobre, ale w wariancie latin-ext. Bez tego polskie ą, ę, ł renderują się czasem fallbackiem do innej rodziny — na ekranie wygląda dziwnie. W Cursorze: Settings → Editor → Font Family → wpisuję 'JetBrains Mono', monospace i działa.
Polski layout vs Cursor — kolizja Option+Z
Włączyłem polski layout klawiatury (nie polski programisty — preferuję natywny Polski, gdzie ż jest Option+Z). I Cursor zaczął ignorować ż, bo Option+Z był przypisany do „Toggle Word Wrap”. Fix: Cmd+K Cmd+S → search „toggle word wrap” → Remove Keybinding. Warto sprawdzić też Alt+X (ź), Alt+L (ł), Alt+N (ń) — niektóre wtyczki tam wpadają.
CLI dla Claude Code, GitHub, Vercel, Supabase
brew install claude-code
gh auth login
vercel login
supabase login
Po gh auth login dorzuciłem scope admin:public_key — to potrzebne, żeby z poziomu CLI dorzucić klucz SSH do GitHuba (zamiast iść klikać w UI). Klucz SSH wygenerowany ED25519, fingerprint zweryfikowany, gh ssh-key add i koniec.
Vercel i Supabase logowanie tokenowe. Konto Supabase to konto główne (płatne), a nie któreś z kont klienckich — to ważne, bo MCP Supabase później ma uprawnienia tylko do projektów na tym koncie. Dla projektów klientów, gdzie dostałem credentials, fallback to direct psql przez connection string.
5. Etap 2: kod projektów — lazy + curated TOP-list
Po fundamencie — repozytoria. Tutaj zastosowałem strategię, którą opisałem wyżej: nie wszystko, tylko 5–7 projektów Tier A.
Jak wybrałem Tier A
Włączyłem gh repo list <twoj-user> --limit 50, posortowane po dacie ostatniego push. Top tej listy to projekty, w których aktywnie pracuję w ostatnim tygodniu — i to są kandydaci do mobile-ready set.
| Typ projektu | Powód w Tier A |
|---|---|
| główny hub portfolio | centralny dashboard orkiestracji ekosystemu |
| provider wewnętrzny (np. RAG) | usługa współdzielona przez inne projekty |
| SaaS w aktywnym rozwoju | codzienne commity, integracje w toku |
| narzędzie dla użytkowników końcowych | workflow w trakcie wdrożenia |
| projekt klienta (voicebot/agent A) | aktywny — demo do prezentacji |
| projekt klienta (voicebot/agent B) | aktywny — w fazie wdrożenia |
| projekt klienta (panel/integracja) | krytyczny — pilne zadanie produkcyjne |
| repozytorium danych/konfiguracji | czytane przez główny hub |
Twoja lista będzie inna — to są moje typy projektów, ale wzorzec ten sam. Wybierasz po jednym pytaniu: czy w tym tygodniu się tym dotykam, czy nie. Nie po sentymencie („ten projekt jest ważny”), tylko po faktycznej aktywności.
Skrypt clone-priority-repos.sh
Zamiast klonować ręcznie, napisałem skrypt, który robi trzy rzeczy: klonuje (lub git pull, jeśli już sklonowane), wykrywa managera pakietów (pnpm/npm/yarn) i instaluje zależności. Skrypt jest idempotentny — można go puszczać wielokrotnie bez ryzyka.
#!/usr/bin/env bash
set -euo pipefail
PROJEKTY_DIR="$HOME/projekty"
mkdir -p "$PROJEKTY_DIR"
GH_USER="twoj-github-user"
REPOS=(
"$GH_USER/main-saas-project"
"$GH_USER/secondary-saas-project"
"$GH_USER/client-project-a"
"$GH_USER/client-project-b"
"$GH_USER/client-project-c"
"$GH_USER/data-repo"
)
for REPO in "${REPOS[@]}"; do
NAME=$(basename "$REPO")
TARGET="$PROJEKTY_DIR/$NAME"
if [[ -d "$TARGET/.git" ]]; then
git -C "$TARGET" pull --ff-only
else
gh repo clone "$REPO" "$TARGET" -- --depth 1
fi
done
# Auto-detect package manager + install
for REPO in "${REPOS[@]}"; do
TARGET="$PROJEKTY_DIR/$(basename "$REPO")"
[[ -d "$TARGET" ]] || continue
if [[ -f "$TARGET/pnpm-lock.yaml" ]]; then
(cd "$TARGET" && pnpm install --frozen-lockfile)
elif [[ -f "$TARGET/package-lock.json" ]]; then
(cd "$TARGET" && npm ci)
fi
done
Sześć projektów sklonowane shallow (--depth 1) zajęło około minuty. Instalacja deps — kolejne dziesięć. Razem dwa giga miejsca i mam pełen mobile-ready set.
Co NIE jest w git, czyli .env files
Po sklonowaniu projekty są technicznie obecne, ale bez .env nie zadziałają. To świadomy wybór — sekrety nie idą do gita. Trzy podejścia, w zależności od tego, co projekt używa:
- Vercel —
vercel env pull .env.localw katalogu projektu. To pobiera production env z Vercela do lokalnego pliku. - Supabase — credentials trzymam w
~/.claude/projects/<project>/credentials.envi synchronizuję rsync-em z Mac Studio (osobny krok dalej). - Klucze API ad-hoc — kopiuję z
~/.claude/.credentials-global.env, który też synchronizuję rsync-em.
Konsekwencja: po sklonowaniu projektu na MacBook musisz świadomie pomyśleć „jakich envów ten projekt potrzebuje”. To dłużej niż auto-magic, ale daje kontrolę. Klucze klientów nie krążą bez sensu po dyskach.
6. Etap 3: synchronizacja z Mac Studio
Tu zacząłem porządkować. Mam dwie maszyny, na których chcę pracować nad tym samym ekosystemem. Są trzy kategorie rzeczy, które trzeba zsynchronizować, i trzy różne mechanizmy:
Co synchronizuję przez git
Każde repo ma git pull. To pokrywa:
- Kod źródłowy (oczywiste)
- Pliki
CLAUDE.mdz lessons learned per projekt - Handoffy w
.ai/handoffs/— kluczowe, bo tu trzymam stan pracy między sesjami - BACKLOG global (
.ai/BACKLOG.mdw Project Master) - Plany, raporty, scan results — wszystko, co nie zawiera sekretów
Git załatwia 80% potrzeby synchronizacyjnej. Co commitnąłem na Mac Studio i pushnąłem, dostaję na MacBooku jednym git pull.
Co synchronizuję przez rsync nad SSH
Rzeczy, które żyją w ~/.claude/ i nie powinny być w git (zawierają sekrety albo są specyficzne dla użytkownika):
- Skille globalne (
~/.claude/skills/) — około 230 katalogów, 43 MB. Dynamicznie się rozwijają, więc warto sync. - Hooki SessionStart/Stop (
~/.claude/hooks/) - Scripts (
~/.claude/scripts/) — m.in. bridge-read-instructions, scan-all-handoffs - Credentials globalne i per-project
settings.jsonjako reference (nie kopia bezpośrednia, bo additionalDirectories różnią się między maszynami)
Pierwszy raz sync ręcznie przez scp lub rsync -av, kolejne razy przez skrypt. Tu jeden szczegół — Mac Studio musi mieć włączone Remote Login (System Settings → General → Sharing → Remote Login). Lekcja, której nie zakładałem: na macOS Sequoia toggle nie zawsze startuje sshd automatycznie. Diagnoza: sudo lsof -i :22 — powinno pokazać LISTEN. Jeśli nie, wymuszamy: sudo launchctl load -w /System/Library/LaunchDaemons/ssh.plist.
Czego nie synchronizuję
Trzy rzeczy świadomie zostawiam:
- Pliki
.envper projekt — bo różnią się i bo lepiej je ściągać świadomie z Vercela na żądanie - Lokalne modele Ollama — to gigabajty, łatwiej ściągnąć je natywnie przez
ollama pull - Pamięć Claude Code — to osobny, trudny temat, opisany w następnej sekcji
7. Etap 4: pamięć Claude Code dla każdego projektu
Tu się robi ciekawie. Pamięć w Claude Code nie jest jedną bazą wiedzy — to katalog per projekt, w którym agent zapisuje topic-files: lessons learned, decyzje architektoniczne, preferencje użytkownika, kontakty klientów, historię błędów. Po kilku miesiącach pracy mam tam prawie 800 plików w 29 aktywnych projektach.
Pamięć żyje pod ścieżką:
~/.claude/projects/-Users-<user>-projekty-<Project-Name>/memory/
Nazwa katalogu to encoded path — Claude Code bierze absolutną ścieżkę projektu (/Users/dariuszciesielski/projekty/Project Master), zamienia ukośniki na myślniki i spacje też na myślniki. Wynik: -Users-dariuszciesielski-projekty-Project-Master.
Problem 1: dwie maszyny, te same projekty, różne nazwy folderów
Na Mac Studio mam folder ~/projekty/Project Master/ (z wielką literą i spacją). Na MacBooku po gh repo clone <twoj-user>/project-master mam ~/projekty/project-master/ (lowercase, bez spacji — tak nazywa się repo na GitHubie).
Te dwa foldery generują dwa różne encoded paths:
- Mac Studio:
-Users-dariuszciesielski-projekty-Project-Master - MacBook:
-Users-dariuszciesielski-projekty-project-master
Czyli dwa różne katalogi pamięci. Pierwszy ma 271 plików, drugi jest pusty.
Problem 2: pamięć rośnie historycznie i ma śmieci
Na Mac Studio audyt pokazał 68 katalogów memory. Tylko 29 z nich miało dane. Pozostałe 39 było puste — to historyczne duplikaty. Wzór był taki: kiedyś folder nazywał się „Marketing Hub” (ze spacją), Claude Code utworzył memory pod tą nazwą. Później folder przemianowałem na „Marketing-Hub” (z myślnikiem) — Claude utworzył drugi memory dir. Stary z spacjami pozostał pusty, ale został.
Dodatkowo Claude Code tworzy memory dir per otwarty folder. Jeśli kiedyś otworzyłem subdir typu ~/projekty/Project Master/project-master/src/app/, mam dla niego osobny pusty memory dir o nazwie Project-Master-project-master-src-app. Bezsens, ale tak jest.
Rozwiązanie: cleanup, a potem mass sync z mappingiem
Zanim zacząłem synchronizować, zrobiłem porządki na Mac Studio. Strategia trzykrokowa:
- Audyt — porównanie listy memory dirs z listą realnych folderów w
~/projekty/. Klasyfikacja: aktywne (mają dane + matching folder), orphans (mają dane, brak folderu), puste (zero plików). - Archiwizacja pustych — wszystkie 39 pustych przeniesione do
~/.claude/projects/_archive/2026-05-01-cleanup/. Nie usunąłem, tylko przesunąłem. Po 30 dniach, jeśli nic z tego nie używam, wyrzucam. - Mass sync na MacBooka — wszystkie 29 aktywnych skopiowane do
~/.claude/projects/na MacBooku, zachowując encoded paths z Mac Studio. Plus duplikaty dla projektów już sklonowanych z lowercase nazwami.
Drugi krok ma zaletę praktyczną. Gdy w przyszłości sklonuję na MacBooka projekt, którego dziś nie mam (np. kolejne repo klienta), encoded path nowego folderu będzie się pokrywał z istniejącym memory dir z Mac Studio. Pamięć aktywuje się natychmiast, bez dodatkowej akcji. Memory siedzi sobie magazynowo, czeka na moment, gdy ją wywołam.
Skrypt do mass sync (uproszczony)
Skrypt iteruje listę memory dirs z Mac Studio przez SSH, dla każdego sprawdza, czy ma dane, i robi rsync -a --delete do odpowiadającego katalogu na MacBooku. Plus drugi przebieg dla projektów, które na MacBooku istnieją z innym case (np. specjalista-seo vs Specjalista-SEO) — robi duplikat pod lokalnym encoded path.
Końcowy rezultat: 32 memory dirs na MacBooku (29 z Mac Studio + 3 duplikaty case mapping). Razem 750 plików, około 3 MB. Mobilna stacja vibe codingu zna kontekst całego ekosystemu.
8. Etap 5: Ollama, lokalne modele i 11 podpodejść do jednego problemu
To jest część, z której wziął się ten artykuł. Pozornie banalna konfiguracja: dodać zmienną środowiskową do serwisu Ollama, żeby trzymał model w pamięci dłużej niż domyślne 5 minut. Konkretnie: OLLAMA_KEEP_ALIVE=30m.
Bez tego każde wywołanie modelu po dłuższej przerwie zaczyna się od cold startu — 12 do 18 sekund ładowania 23 GB do RAM. Przy częstym cross-model review (sprawdzam plan przez Codexa i Qwena równolegle) to robi się nieprzyjemne. 30 minut keep-alive eliminuje problem dla typowej sesji.
Pomyślałem: pięć minut roboty. Skończyło się trzema godzinami i jedenastoma podpodejściami. Po drodze odkryłem trzy oddzielne pułapki, których nie ma w man page. Opisuję je tutaj nie po to, żeby się chwalić cierpieniem — tylko dlatego, że każda z nich osobno potrafi zatopić wieczór.
Pierwsza pułapka: plutil cicho nie zapisuje zmian
Na macOS jest narzędzie plutil do edycji plików plist (Property List). W teorii działa tak:
plutil -insert "EnvironmentVariables.OLLAMA_KEEP_ALIVE" \
-string "30m" \
~/Library/LaunchAgents/homebrew.mxcl.ollama.plist
Komenda kończy się 0, brak błędu, brak ostrzeżenia. Sprawdzam plik — zmiana NIE jest zapisana. Plutil cicho ignoruje keypath z kropką, gdy próbuje wstawić klucz do zagnieżdżonego słownika.
Rozwiązanie: PlistBuddy, narzędzie Apple z innym syntaxem (dwukropki zamiast kropek):
/usr/libexec/PlistBuddy \
-c "Add :EnvironmentVariables:OLLAMA_KEEP_ALIVE string 30m" \
~/Library/LaunchAgents/homebrew.mxcl.ollama.plist
To zadziałało. plutil -p potwierdził, że zmiana jest w pliku.
Lekcja 1: do edycji zagnieżdżonych dict używaj PlistBuddy, nie plutil
Plutil -insert jest niezawodny dla top-level kluczy. Dla zagnieżdżonych keypath (EnvironmentVariables.X) bywa cichy fail bez błędu. PlistBuddy Add :Path:Key string value jest niezawodne i istnieje od dekad.
Druga pułapka: brew services restart nadpisuje plist
Plist edytowany, robię brew services restart ollama. Sprawdzam ps eww dla procesu Ollama — nowej zmiennej nie ma. Próbuję znowu, znowu nie ma. Trzy razy.
Diagnostyka pokazała, że plik ~/Library/LaunchAgents/homebrew.mxcl.ollama.plist wraca do stanu pierwotnego po każdym restart. Brew services kopiuje go ze źródła w /opt/homebrew/Cellar/ollama/<wersja>/ przy każdym restart, nadpisując manualne edycje.
Rozwiązanie: edytować plist w Cellar, nie w LaunchAgents. Plus pamiętać, że po brew upgrade ollama katalog Cellar starej wersji znika i edycja przepada. Trzeba ją reaplikować.
Lekcja 2: brew services restart silently nadpisuje plist w LaunchAgents
Edytuj plist w /opt/homebrew/Cellar/<formula>/<wersja>/<plist> (lub przez symlink /opt/homebrew/opt/<formula>/), potem skopiuj do LaunchAgents i przeładuj launchd. Po brew upgrade ponów — katalog Cellar starej wersji jest usuwany.
Trzecia pułapka: launchctl bootout z labelem zachowuje cache
Plist edytowany w Cellar, skopiowany do LaunchAgents. Robię launchctl bootout + bootstrap. Sprawdzam env — nowej zmiennej nadal nie ma. Czwarty raz.
Tu utknąłem. Standardowa procedura wygląda tak:
launchctl bootout gui/$(id -u)/homebrew.mxcl.ollama
launchctl bootstrap gui/$(id -u) ~/Library/LaunchAgents/homebrew.mxcl.ollama.plist
Wszystko bez błędu, proces się restartuje, env nadal stary. Zgodnie z zasadą cross-model consult (po trzech-czterech nieudanych próbach standardowych metod sięgam po drugą opinię), wysłałem pełen kontekst do Gemini 3.1 Pro przez skill gemini-delegation. Koszt — zero (free tier), czas — 25 sekund.
Gemini wskazał na subtelną, nigdzie niedokumentowaną różnicę:
launchctl bootout gui/UID/<label>(z nazwą serwisu) zatrzymuje proces, ale ZACHOWUJE cached definicję plist w pamięci launchd. Bootstrap odtwarza ze starej cached wersji.
launchctl bootout gui/UID <plist-path>(ze ścieżką pliku) zatrzymuje proces I czyści cached definicję. Bootstrap czyta świeży plik z dysku.
Różnica: ostatni argument to label albo path. Niby drobiazg, dramatyczny efekt. Po zmianie składni — env się załapał.
Lekcja 3: launchctl bootout — używaj ŚCIEŻKI pliku, nie label serwisu
Aby wymusić re-czytanie plist po edycji EnvironmentVariables: launchctl bootout gui/UID <plist-path>, NIE bootout gui/UID/label. Subtelność, której nie ma w man page; znana z Apple developer forum.
9. Etap 6: jeden skrypt, który robi mobile sync
Po przejściu przez wszystkie poprzednie etapy doszedłem do wniosku, że potrzebuję jednej komendy, która przed każdą mobilną sesją zsynchronizuje wszystko. Skrypt mobile-sync-pull.sh robi cztery rzeczy w kolejności:
git pull --ff-onlyw każdym repo w~/projekty/(skip, jeśli local changes — nie ryzykuje merge conflict)- Rsync skilli + credentials z Mac Studio (opt-out flagą
--skip-mac-studiow trybie offline) - Rsync memory — w trybie
--sync-memorytylko Project Master, w trybie--sync-memory-allwszystkie 29 aktywnych projektów - Pokazuje najnowszy handoff PM jako start point — od czego zaczynać po otwarciu Cursora
Typowy workflow rozpoczęcia mobilnej sesji:
cd ~/projekty/project-master
bash .planning/macbook-setup/mobile-sync-pull.sh --sync-memory-all
# → Cursor → Open ~/projekty/project-master → Cmd+Esc → "wznów"
Pierwsza komenda zajmuje około 30 sekund (zwykle jest co pull, sync skilli przez delta jest błyskawiczny). Druga komenda — Cursor + wtyczka Claude Code z prefixem „wznów” — czyta najnowszy handoff i wraca do tego, na czym skończyłem na Mac Studio.
10. Dwie pozostałe lekcje (i jedna bonusowa)
Trzy lekcje z plist hell opisałem w sekcji 8. Zostały jeszcze dwie z różnych etapów setup, plus jedna bonusowa, która wynurzyła się dopiero przy pisaniu skryptu mass sync.
Lekcja 4: brew services kill blokowany przez KeepAlive=true; killall jest legalny
Przy próbie restartu Ollama natknąłem się na: Service `ollama` is set to automatically restart and can't be killed. Brew chroni serwisy z KeepAlive: true w plist przed niezamierzonym kill. Workaround: killall ollama. Launchd auto-respawnie proces z aktualnego plist (KeepAlive=true się o to martwi). Bezpieczne, dopóki masz uprawnienia do procesu. Niżej-leveled launchctl daje pełną kontrolę, którą brew services świadomie ogranicza.
Lekcja 5: po 3–4 nieudanych próbach standardowego fixu — cross-model consult
Czasem siedzisz nad jednym problemem trzecią godzinę i każda kolejna próba jest piątym wariantem tej samej intuicji. To moment, w którym warto poprosić o drugą perspektywę. Wysłałem do Gemini 3.1 Pro pełny kontekst (co próbowałem, co nie zadziałało, jakie hipotezy mam), bez żadnych instrukcji „jak to powinieneś rozwiązać”. Wskazał wszystkie trzy faktyczne pułapki w 25 sekundach. Koszt zerowy (free tier), 30–60 minut zaoszczędzonego trial-and-error. To nie jest lenistwo — to higiena pracy. Zwykle zatrzymuję się po trzecim, czwartym nieudanym podejściu i robię cross-model review przed kolejną próbą.
Bonus: bash gotcha — ssh i rsync konsumują stdin pętli
Przy pisaniu skryptu mass sync trafiłem na klasyczny bash bug. Pętla iterowała tylko jeden raz, mimo że dane wejściowe miały 29 wierszy:
# Buggy version — ssh wewnątrz konsumuje stdin pętli
while IFS= read -r remote_mem; do
remote_count=$(ssh "$HOST" "find ... | wc -l")
...
done <<< "$REMOTE_MEMS"
Problem: ssh bez flagi -n czyta stdin parent shell’a. W pętli while read wypija pozostałe wiersze, więc pętla kończy po pierwszej iteracji. Tak samo rsync i scp.
Fix: przenieś input pętli na deskryptor 3, bo read czyta z domyślnego stdin (deskryptor 0), a ssh/rsync ten stdin konsumuje:
# Fixed — fd 3 dla pętli, fd 0 dostępne dla ssh/rsync
while IFS= read -ru3 remote_mem; do
remote_count=$(ssh -n "$HOST" "find ... | wc -l")
...
done 3<<< "$REMOTE_MEMS"
Plus ssh -n jako pas i szelki — flag -n mówi explicit „nie czytaj stdin”. Idiom warto zapamiętać: każda pętla while read, która wewnątrz wywołuje ssh/rsync/scp/sftp, musi używać deskryptora 3 lub 4.
11. Właściwa ścieżka — checklist bez pułapek
Skoro znasz pułapki opisane wyżej, jeśli zaczynasz świeżo, oto kolejność, która pozwoli ci to zrobić w 2–3 godziny pracy efektywnej plus około 1,5 godziny czekania na pobrania (Xcode CLI, modele Ollama). Bez plist hell, bez bash gotcha, bez błędów, które ja zaliczyłem.
To nie tutorial szczegółowy — szczegóły masz w sekcjach 4–9 wyżej. Tu jest kondensat: kolejność, czasy, komendy, które wiem, że działają.
-
Apple ID + iCloud Keychain + FileVault (5 min)
System Settings → Apple Account → zaloguj się tym samym ID co główna stacja. iCloud Keychain ON (synchronizuje hasła z drugiej maszyny). FileVault ON w Privacy & Security — to laptop, więc szyfrowanie obowiązkowe.
-
Xcode CLI Tools (~15 min download w tle)
xcode-select --installTrzy giga, idzie sobie w tle. W międzyczasie możesz zacząć pobieranie Cursora z
cursor.com— instalacja po Xcode. -
Homebrew + PATH (~10 min)
/bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)"Po instalacji wykonaj polecenia, które brew wypisze (dodanie do
~/.zprofileliniieval "$(/opt/homebrew/bin/brew shellenv)"). Bez tegobrewnie jest w PATH. -
Core packages (~5 min)
brew install git gh node pnpm jq ripgrep fd htop wget tree coreutils libpq -
Cursor + skrót CLI + fonty latin-ext (~5 min)
Otwórz Cursora, Cmd+Shift+P → „Shell Command: Install 'cursor’ command in PATH”. Pobierz JetBrains Mono w wariancie
latin-ext(z Google Fonts) i ustaw w Settings → Editor. -
Polski layout + fix Option+Z w Cursorze (~3 min)
System Settings → Keyboard → Input Sources → Add „Polski” (NIE QWERTZ). W Cursorze: Cmd+K Cmd+S → search „toggle word wrap” → Remove Keybinding (kolizja z Option+Z = ż).
-
Claude Code CLI + gh + vercel + supabase auth (~10 min)
brew install claude-code gh auth login --scopes admin:public_key vercel login supabase loginScope
admin:public_keyjest potrzebny w punkcie 8 (do dodania klucza SSH przez CLI bez chodzenia do UI). -
SSH key ED25519 + dodanie do GitHuba (~3 min)
ssh-keygen -t ed25519 -C "your_email@example.com" gh ssh-key add ~/.ssh/id_ed25519.pub --title "MacBook M4" -
Klonowanie projektów Tier A — skrypt batch (~15 min)
Lista 5–7 projektów, z którymi naprawdę pracujesz w tym tygodniu. Skrypt z
--depth 1+ auto-detect package managera. Patrz sekcja 5 wyżej, kompletnyclone-priority-repos.shlinkowany na końcu artykułu. -
Ollama + pobranie modeli (~30+ min download)
brew install ollama brew services start ollama ollama pull qwen3-coder:30b ollama pull qwen3.6:35b-a3b-q4_K_M23 GB + 18 GB. To jest faza, w której idziesz na lunch. Cold start pierwszego modelu ~12-15 sek, kolejne wywołania natychmiast.
-
OLLAMA_KEEP_ALIVE bez plist hell (~5 min)
Tu są wszystkie trzy lekcje z sekcji 8 zastosowane od razu. Edytujemy plist w Cellar (nie w LaunchAgents — bo
brew services restartnadpisze), używamy PlistBuddy (nieplutil— bo cicho nie zapisze) i robimy bootout ze ścieżką (nie label — bo cache). Sekwencja:VERSION=$(brew list --versions ollama | awk '{print $2}') PLIST="/opt/homebrew/Cellar/ollama/$VERSION/homebrew.mxcl.ollama.plist" # Dodaj zmienną przez PlistBuddy /usr/libexec/PlistBuddy -c \ "Add :EnvironmentVariables:OLLAMA_KEEP_ALIVE string 30m" "$PLIST" # Synchronizuj do LaunchAgents cp "$PLIST" ~/Library/LaunchAgents/homebrew.mxcl.ollama.plist # Reload launchd ze ścieżką pliku (NIE label!) launchctl bootout gui/$(id -u) ~/Library/LaunchAgents/homebrew.mxcl.ollama.plist killall ollama 2>/dev/null launchctl bootstrap gui/$(id -u) ~/Library/LaunchAgents/homebrew.mxcl.ollama.plist # Verify ps eww $(pgrep -f "ollama serve") | tr ' ' '\n' | grep OLLAMA_KEEP_ALIVEPowinno wypisać
OLLAMA_KEEP_ALIVE=30m. Jeśli wypisze — gotowe. Jeśli nie — jesteś tam, gdzie ja byłem; przeczytaj sekcję 8 jeszcze raz.Po każdym
brew upgrade ollamaponów ten krokStary katalog Cellar znika, edycja przepada. Można sobie zapisać powyższy blok jako
restore-ollama-keepalive.sh(gotowy skrypt linkowany na końcu). -
Mass sync skilli, credentials i pamięci z głównej stacji (~2 min)
Z drugiej maszyny (Mac Studio / Mac mini) musisz mieć włączone Remote Login. Potem na MacBooku jeden skrypt:
bash ~/projekty/project-master/.planning/macbook-setup/mobile-sync-pull.sh --sync-memory-allSkrypt: pull wszystkich repo, rsync skilli (~3500 plików, delta szybka), credentials, hooki, scripts, pamięć Claude Code dla wszystkich aktywnych projektów (29 katalogów). Plus pokaże najnowszy handoff jako start point dla nowej sesji.
-
Otwarcie Cursora i pierwsza sesja (~3 min)
Cursor → Cmd+Shift+P → „Cursor: Sign In” (Apple ID albo Google). Extensions → wpisz „Claude Code” → Install (publisher: Anthropic). Cmd+Shift+P → „Claude Code: Sign In” → autoryzacja przez OAuth, wykorzystuje twoją subskrypcję. File → Open Folder → twój główny projekt. Cmd+Esc otwiera panel agenta. Wpisz „wznów” — wtyczka odczyta najnowszy handoff i wraca do tego, co robiła główna stacja.
To jest 13 kroków, które razem dają w pełni działającą mobilną stację vibe codingu. Realnie: rozpisz na pół dnia, przy czym połowa to czekanie na pobrania, podczas których możesz robić co innego. Gdyby ktoś mi podał taką listę osiem godzin temu — miałbym wieczór wolny.
12. Co dalej i gotowe skrypty
Po dziesięciu godzinach mam działającą mobilną stację vibe codingu. Stack: Cursor + wtyczka Claude Code + Ollama z dwoma modelami lokalnymi + osiem priority repo + 32 katalogi pamięci dla całego ekosystemu. Każdy element zsynchronizowany z Mac Studio, każdy świadomie wybrany.
Trzy obserwacje na koniec:
Co działa
- Workflow „1 komenda, mobile gotowy” —
mobile-sync-pull.sh --sync-memory-allaktualizuje wszystko w 30 sekund - Strategia lazy + curated — sześć projektów Tier A pokrywa 80% mobilnych potrzeb, reszta on-demand
- Pamięć działa cross-machine — gdy klonuję na MacBooku nowy projekt, encoded path matchuje wcześniej zsync’owaną memory dir, kontekst aktywuje się natychmiast
Co świadomie odłożone
- Touch ID dla sudo — nie zadziałał standard fix, diagnoza odłożona. Sudo działa hasłem, niegroźne.
- Master → main migracja dla legacy repo — kosmetyka, bez wpływu na pracę
- Test wszystkich MCP serwerów w wtyczce Claude Code na MacBooku — sprawdzenie odłożone na pierwszą sesję mobilną
Gotowe skrypty (do skopiowania, do dostosowania, do użycia)
Trzy skrypty, które są efektem tej sesji, są wersjonowane w moim repo Project Master. Każdy z nich jest idempotentny (można puścić wielokrotnie bez ryzyka), samo-dokumentujący (komentarz top-of-file z pełnym kontekstem) i odporny na opisane wyżej pułapki.
| Skrypt | Co robi |
|---|---|
clone-priority-repos.sh |
Klonuje 6 projektów Tier A z auto-detekcją package managera i instalacją deps. Idempotent. |
mobile-sync-pull.sh |
Pre-work sync: git pull we wszystkich repo, rsync skilli z Mac Studio, opcjonalnie mass sync memory. Pokazuje najnowszy handoff PM jako start point. |
restore-ollama-keepalive.sh |
Reaplikuje OLLAMA_KEEP_ALIVE=30m do plist w Cellar po brew upgrade ollama. Stosuje wszystkie trzy lekcje z plist hell. |
Jeśli chcesz mieć identyczny mobile setup, masz dwie ścieżki. Pierwsza: skopiuj te skrypty do swojego repo, dostosuj listę projektów Tier A i ścieżki, puść. Druga, krótsza: napisz mi, podyskutujmy o twoim setupie, dostosujemy razem. Vibe coding to też vibe collaboration — narzędzia są darmowe, doświadczenie z nich się buduje przez rozmowy z osobami, które zaliczyły już te trzygodzinne walki z plist.
Wnioski po sesji
Trzy rzeczy, które wezmę ze sobą do następnej konfiguracji nowej maszyny:
- Genealogia narzędzi to istota stack-u. Nie tylko „co zainstalować”, ale „w jakiej kolejności i dlaczego”. Cursor po Homebrew, klucz SSH po
gh auth login, mass sync memory po cleanup pustych — kolejność określa, czy w połowie setupu nie utkniesz. - Synchronizacja przez git pokrywa 80% potrzeby; rsync nad SSH dokrywa 15%; ostatnie 5% to pamięć Claude Code z mappingiem case. Każda warstwa ma inny mechanizm. Nie próbuj wszystkiego jednym narzędziem.
- Lessons learned to inwestycja, nie zbędny dokument. Pięć lekcji z tej sesji już mi oszczędza czas — następnym razem, gdy będę edytował plist Brew services, zacznę od
PlistBuddyw Cellar i bootout ze ścieżką. Trzy godziny zaoszczędzone vs jednorazowe spisanie. Ratio jakiego nie spotykam w żadnej innej części pracy.
Setup MacBooka to nie cel, tylko fundament. Cel to praca, którą na nim zrobię — z klientem na kanapie, z modelem lokalnym w pociągu, z handoffem otwartym o 23:00, gdy nagle wpadł pomysł. Ten artykuł istnieje, bo dziesięć godzin dziwnych komend w terminalu byłoby stratą, gdyby zostały tylko w mojej głowie. Twój setup będzie wyglądał inaczej — twoje stack-u się różni, twoje preferencje są twoje. Ale lessons learned o launchd, brew services, ssh stdin consumption — to są fakty z dokumentacji, której nie ma w dokumentacji. Mam nadzieję, że oszczędzą ci trzy popołudnia.
O autorze: Dariusz Ciesielski — buduję ekosystemy SaaS i agentów AI dla małych firm. Pracuję z klientami na polskim rynku w branżach hotelarskiej, edukacyjnej, prawniczej i e-commerce. Stack: Next.js, Supabase, Vercel, Claude Code jako parafrasta. Kontakt przez stronę aiwbiznesie.online.
Wersja artykułu: 1.0 (1 maja 2026). Materiał oparty na realnej sesji konfiguracyjnej; wszystkie błędy, komendy i czas wykonania to fakty z dziennika sesji.
Do tego artykułu jest też Checklista — prowadząca po konfiguracji nowego MacBooka, krok po kroku, od włączenia do działającej stacji.