---
name: sync-agenta
description: Synchronizacja konfiguracji Claude Code (CLAUDE.md, skille, komendy, reguły, hooki, agenci, pamięć projektów, pluginy) między komputerem lokalnym (Mac lub Windows) a serwerem VPS z Linuksem, przez prywatne repozytorium GitHub; silnikiem jest narzędzie claude-nomad. Use when użytkownik mówi "sync agenta", "sync-agenta", "synchronizuj konfigurację", "zainstaluj synchronizację", "przenieś konfigurację na VPS", "ta sama konfiguracja na obu maszynach", "przegląd synchronizacji", "napraw synchronizację", "odinstaluj synchronizację".
---

# sync-agenta — synchronizacja konfiguracji Claude Code między maszynami

**Wersja skilla: 2.1.4** (historia zmian: [CHANGELOG.md](CHANGELOG.md)). Numeracja skilla jest niezależna od wersji silnika.

Silnik: **claude-nomad** (zalecana wersja przypięta: `npm i -g claude-nomad@0.64.1`).
Skill trzyma się WYŁĄCZNIE publicznych komend nomada (`nomad init|pull|push|sync|doctor|diff|eject|capture-settings|drop-session`) i jego kodów wyjścia (0 = OK, 2 = zła komenda, 4 = zacięte repozytorium wymagające ręcznej naprawy, 5 = wykryty sekret, 130 = przerwane przez użytkownika). Nigdy nie zaglądaj do kodu źródłowego nomada ani nie zakładaj jego wewnętrznej struktury.

## Zasady nadrzędne (obowiązują w każdym kroku)

1. **Mów prostym językiem.** Użytkownik nie jest programistą. Każdy termin techniczny wyjaśnij przy pierwszym użyciu (symlink = plik-skrót wskazujący na inny plik; SSH = szyfrowane zdalne połączenie; cron = harmonogram zadań w Linuksie).
2. **Wyjaśnij PRZED wykonaniem.** Przed każdą komendą powiedz jednym zdaniem, co zrobi i po co.
3. **Rolę maszyny USTALA UŻYTKOWNIK, nie ty.** System operacyjny wykrywasz sam, ale zawsze pytasz: „komputer lokalny czy VPS?".
4. **Pierwsza wysyłka do GitHuba = bramka P0.** Pokaż pełną listę tego, co pojedzie (`nomad push --dry-run` + zawartość `custom/`), przypomnij, co NIGDY nie jedzie, i czekaj na wyraźne „tak / wysyłaj". Bez zgody nie wysyłasz.
5. **Sprawdź prywatność repozytorium** przed pierwszą wysyłką: `gh repo view <repo> --json visibility` musi zwrócić `PRIVATE`. Jeśli nie — zatrzymaj się i pomóż zmienić widoczność (`gh repo edit <repo> --visibility private --accept-visibility-change-consequences`).
6a. **Nigdy nie zakładaj, że skrypt z Windowsa uruchomi się na Linuksie.** Pliki `.ps1` na serwerze nie zadziałają (nie ma PowerShella), a ścieżki `C:\Users\...` wewnątrz skilli nie są nigdzie przepisywane. Przy pobraniu na VPS wypisz użytkownikowi listę skilli zawierających `.ps1` albo ścieżki windowsowe i powiedz wprost, że na serwerze są martwe.
6. **Błędy tłumacz po ludzku.** Nigdy nie zostawiaj użytkownika z surowym błędem gita/nomada — użyj tabeli w `references/awarie.md`.
7. **Niczego nie kasuj bez wyraźnego polecenia.**
8. **Po KAŻDEJ ręcznej zmianie ustawień → natychmiast `NOMAD_HOST=<etykieta> nomad capture-settings --host --yes`.** Pull odtwarza `settings.json` z repozytorium przy każdym starcie sesji — zmiana zrobiona ręcznie albo przez `/config`, nieutrwalona tą komendą, zniknie bez ostrzeżenia. To reguła stała, nie tylko instalacyjna.
9. **Etykieta maszyny (NOMAD_HOST) zawsze wprost w automatyce.** Pliki profilu powłoki (`.zshrc`/`.bashrc`) NIE docierają ani do crona, ani do Claude Code uruchomionego z aplikacji. Dlatego `hooks/uruchom.mjs` czyta etykietę z `~/.cache/sync-agenta/rola.json`, a `install-cron.sh` wpisuje `NOMAD_HOST=<etykieta>` bezpośrednio w komendę — nigdy nie polegaj na samym profilu powłoki. (Bez etykiety nomad pracuje pod nazwą sieciową maszyny, ignoruje `path-map.json` i pull kasuje wpisy hooków.)
10. **Środowiska binarne mieszkają POZA katalogiem skilli.** Katalogi `venv/`, `.venv/`, `node_modules/` NIGDY nie jadą przez repozytorium (zapora w `shared/.gitignore`) — ale to nie wystarcza: pull nomada PRZYCINA WNĘTRZE każdego skilla, który repozytorium śledzi — kasuje w nim wszystko, czego w repozytorium nie ma — więc środowisko zbudowane wprost w takim skillu zostałoby skasowane przy najbliższym pobraniu. (Skille istniejące WYŁĄCZNIE lokalnie, nieznane repozytorium, są chronione i pull ich nie rusza — sprawdzone w kodzie nomada 0.64.1, `syncSkillsPull` przekazuje `isRootPreserved`. Nie licz na to: skill, który raz pojedzie do repozytorium, przestaje być chroniony.) Dlatego środowiska buduje się w `~/.claude/skill-envs/<skill>--<nazwa>` (katalog, którego nomad nie dotyka), a w skillu zostaje tylko dowiązanie (symlink; na Windows junction). Pull kasuje co najwyżej dowiązanie — wrapper odtwarza je automatycznie po każdym pobraniu, samo środowisko nie ginie.
11. **Hooki i agentów tworzy się na KOMPUTERZE LOKALNYM, nie na VPS.** Jednostronna jest WYŁĄCZNIE warstwa `custom/`: komputer lokalny wysyła, VPS tylko pobiera. **Wspólna konfiguracja (`shared/`: skille, `CLAUDE.md`, reguły, komendy) jedzie w OBIE strony — także z VPS na komputer, automatycznie przy każdym biegu synchronizacji.** Zmiana zrobiona na serwerze w tych plikach trafi do repozytorium bez pytania i nadpisze wersję na komputerze. Powiedz to użytkownikowi wprost, bo samo zdanie „VPS tylko pobiera" jest mylące (wpis roli w `~/.cache/sync-agenta/rola.json` o tym decyduje). Powód: lustrzana synchronizacja w obie strony bez punktu odniesienia kasowałaby na zmianę zmiany obu maszyn. Konsekwencja do zakomunikowania użytkownikowi: hook albo agent dopisany ręcznie na VPS zostanie nadpisany przy najbliższym biegu crona — jeśli coś powstało na VPS i ma zostać, trzeba to przenieść do `~/claude-nomad/custom/` na komputerze lokalnym (albo poprosić Claude na komputerze lokalnym o dodanie).
12. **Ręczne wywołania nomada ZAWSZE z prefiksem etykiety:** `NOMAD_HOST=<etykieta> nomad <komenda>` (Windows: najpierw `$env:NOMAD_HOST='<etykieta>'`). Powłoka, w której pracujesz, mogła nie wczytać profilu — a nomad pod złą nazwą maszyny kasuje hooki i gubi mapowanie ścieżek (zasada 9).
13. **Zagnieżdżone repozytoria `.git` nie mogą trafić do drzewa synchronizacji.** Skill pobrany z GitHuba ma w środku własne repozytorium; skopiowany razem z nim staje się „gitlinkiem" i nomad odmawia wysyłki (`gitlink scan`). **Samego skilla sync-agenta to już nie dotyczy** — od 2.0.0 jedzie jako plugin i mieszka poza drzewem synchronizacji. Reguła obowiązuje dla KAŻDEGO innego skilla użytkownika pobranego z GitHuba: wyprowadź jego `.git` poza katalog skilla (`~/.claude/skill-git/`) albo zainstaluj go jako plugin. Czyszczenie przez wrapper NIE wystarcza: nomad lustrzy żywy katalog i skanuje go w tej samej komendzie, więc wrapper nie zdąży (sprawdzone na Windowsie, błąd B17). Wrapper zostaje wyłącznie jako zabezpieczenie na wypadek `.git` przyniesionego inną drogą. Przy ręcznym kopiowaniu skilli zawsze dodawaj `--exclude '.git'`.
14. **NIGDY nie nadpisuj plików w repozytorium żywym `settings.json`.** Żywy plik bywa pusty (świeża instalacja, reinstalacja po odinstalowaniu), a repozytorium trzyma pełną konfigurację. Scalaj wyłącznie potrzebny klucz (zwykle `hooks`), nigdy cały plik. Przy reinstalacji najpierw `nomad pull`, dopiero potem cokolwiek zapisuj w repozytorium.

## Jak zainstalować i aktualizować sam skill

Od wersji 2.0.0 skill jedzie jako **plugin Claude Code** z własnym marketplace'em. Instalacja to dwa polecenia w Claude Code:

```
/plugin marketplace add studiogo/sync-agenta
/plugin install sync-agenta
```

Aktualizacja — jedno polecenie: `/plugin update sync-agenta`.

**Dlaczego plugin, a nie klon do `~/.claude/skills/`.** Katalog skilli jedzie przez synchronizację, więc repozytorium `.git` zostawione w środku skilla trafiało do drzewa jako „gitlink" i wysyłka padała (błąd B17); obejściem było wyprowadzanie `.git` do `~/.claude/skill-git/` i aktualizowanie przez `--git-dir`. Plugin mieszka w katalogu pluginów, poza drzewem synchronizacji — problem znika u źródła, a aktualizacje robi sam Claude Code. Hooki niesie sam plugin (`hooks/hooks.json`), a ścieżkę podstawia do nich `${CLAUDE_PLUGIN_ROOT}` — zmiana numeru wersji przy aktualizacji nic nie psuje.

**Lista pluginów jedzie przez synchronizację** (warstwa `custom/plugins.json`), więc na drugiej maszynie plugin odtworzy się razem z resztą konfiguracji.

⚠ **Zmiany w samym skillu wprowadzaj w `~/claude-nomad/shared/skills/sync-agenta/`, NIE w `~/.claude/skills/`.** Źródłem prawdy dla skilli jest repozytorium — edycja żywego pliku zostanie skasowana przez najbliższe pobranie. Kolejność: popraw w `shared/` → commit → wysyłka → `nomad pull` rozniesie zmianę do żywego katalogu (błąd B19).

## Krok 0 — rozpoznanie: pierwsza instalacja czy przegląd?

Sprawdź, czy istnieje plik roli `~/.cache/sync-agenta/rola.json`. (Celowo POZA `~/.claude/` — drzewo skilli jest synchronizowane i przycinane przez nomada, więc plik roli trzymany w skillu przyjechałby z drugiej maszyny z cudzą rolą i etykietą.)

- **Istnieje** → to DRUGIE uruchomienie. NIE zaczynaj instalacji. Przeczytaj rolę z pliku i zaproponuj menu:
  1. **Przegląd stanu** — uruchom `nomad doctor`, streść wynik po polsku (co zielone, co wymaga działania i jaką komendą to naprawić); pokaż wiek ostatniego udanego wpisu w `~/.claude/logs/sync.log` (jeśli starszy niż 24 h — powiedz to wprost i zdiagnozuj); sprawdź rozmiar repozytorium i pliki-olbrzymy: `du -sh ~/claude-nomad` oraz `find ~/claude-nomad -path ~/claude-nomad/.git -prune -o -type f -size +50M -print` (plik zbliżający się do 100 MB = przyszła blokada wysyłek — reaguj zawczasu, patrz `references/awarie.md`; rosnące bez końca transkrypty sesji to główny podejrzany). Sprawdź też kopie zapasowe nomada: `du -sh ~/.cache/claude-nomad/backup` — wrapper sprząta je raz na dobę (`nomad clean --backups --keep 3`), ale przy podejrzanie dużym rozmiarze uruchom sprzątanie ręcznie.
  2. **Synchronizuj teraz** — `bin/sync-wrapper.sh sync` (Windows: `bin/sync-wrapper.ps1 sync`).
  3. **Zmień częstotliwość crona** (tylko VPS) — `bin/install-cron.sh <minuty>`.
  4. **Napraw problem** — patrz sekcja „Awarie" niżej.
  5. **Odinstaluj** — patrz sekcja „Cofnięcie".
- **Nie istnieje** → sprawdź, czy maszyna NAPRAWDĘ jest dziewicza, zanim cokolwiek zainstalujesz:
  ```bash
  ls -d ~/claude-nomad/.git 2>/dev/null && echo "REPOZYTORIUM JUŻ JEST"
  command -v nomad && nomad --version
  ```
  Sprawdź też ślad po poprzedniej instalacji: `cat ~/.cache/sync-agenta/rola.json.odinstalowana 2>/dev/null` — plik zostawia `uninstall`, więc rola i etykieta są w nim gotowe do potwierdzenia z użytkownikiem.
  - **Repozytorium istnieje** (choć brak `rola.json`) → to REINSTALACJA, nie pierwsza instalacja. **NIE uruchamiaj `nomad init`** — na gotowym repozytorium jest w najlepszym razie zbędny, w najgorszym niszczący. Pomiń punkty 3 i 3a kroku 4A. Rolę i etykietę odczytaj z repozytorium: `ls ~/claude-nomad/hosts/` (nazwa pliku = etykieta) oraz `cat ~/claude-nomad/path-map.json` (klucze mapy = etykiety maszyn); potwierdź je z użytkownikiem, nie zgaduj. Dalej: krok 2 (wymogi) → krok 4A punkt 2 (etykieta) → **`nomad pull`** (odtwarza `settings.json` z repozytorium) → punkt 7 (hooki) → punkt 8. Znalezisko z reinstalacji na Windowsie (błąd B14).
  - **Repozytorium NIE istnieje** → pierwsza instalacja. Idź do kroku 1.

## Krok 1 — system i rola

1. Wykryj system: `uname -s` (Darwin = macOS, Linux = Linux); na Windows rozpoznasz po środowisku (PowerShell/cmd, zmienna `%OS%`).
2. Zapytaj: „Czy ta maszyna to Twój **komputer lokalny** (przy nim siedzisz, ma już Twoją konfigurację), czy **VPS** (zdalny serwer, na którym agent pracuje sam)? Komputer lokalny będzie ŹRÓDŁEM konfiguracji, VPS ją tylko pobierze."
3. Zapytaj o krótką etykietę maszyny (propozycje: `mac`, `windows`, `vps`) — to będzie `NOMAD_HOST`.
4. Zapisz odpowiedzi do `~/.cache/sync-agenta/rola.json` (utwórz katalog: `mkdir -p ~/.cache/sync-agenta`):
   ```json
   {"rola": "lokalny", "system": "macos", "host": "mac", "zainstalowano": "RRRR-MM-DD"}
   ```
   (`rola`: `lokalny` | `vps`; `system`: `macos` | `windows` | `linux`). Plik zapisz DOPIERO po ukończeniu instalacji — wcześniej trzymaj dane w rozmowie.

**Ważna kolejność wdrożenia:** najpierw KOMPUTER LOKALNY zasiewa repozytorium, dopiero potem VPS pobiera. Jeśli użytkownik zaczyna od VPS, a repozytorium jeszcze nie istnieje — zatrzymaj się i poproś, żeby najpierw przeszedł instalację na komputerze lokalnym.

## Krok 2 — wymogi wstępne

Uruchom `bin/check-prereqs.sh` (Linux/macOS) — sprawdza `node` (≥ 22), `git`, `gh`, `gitleaks` i dla każdego braku wypisuje gotową komendę instalacji pod dany system. Na Windows sprawdź ręcznie (`node --version`, `git --version`, `gh --version`, `gitleaks version`) i przy brakach zaproponuj:
```powershell
winget install OpenJS.NodeJS.LTS Git.Git GitHub.cli gitleaks.gitleaks
```
Po instalacji na Windows przypomnij o restarcie terminala. Na Windows ustaw też obsługę długich ścieżek — inaczej `nomad doctor` zgłosi to jako usterkę po fakcie:
```powershell
git config --global core.longpaths true
``` Zainstaluj brakujące PO zgodzie użytkownika, potem uruchom sprawdzenie ponownie — dalej idź dopiero przy komplecie.

## Krok 3 — GitHub od zera

Użytkownik może NIE mieć konta GitHub. Prowadź wg `references/github-od-zera.md`:
1. Konto: https://github.com/signup (darmowe wystarczy — prywatne repozytoria bez opłat). Poczekaj na potwierdzenie e-maila.
2. Logowanie maszyny: `gh auth login --web --git-protocol https` — wyjaśnij: pokaże ośmioznakowy kod do przepisania w przeglądarce. Na VPS kod przepisuje się w przeglądarce na komputerze lokalnym.
   **Dlaczego HTTPS, nie SSH** (znalezisko testu T1, 09.08.2026): przy `--git-protocol ssh` git łączy się adresem `git@github.com` i wymaga klucza SSH wgranego na konto GitHub. Maszyna zalogowana wcześniej po HTTPS takiego klucza nie ma — pierwsza wysyłka pada na `Permission denied (publickey)`, a komunikat nie mówi, czego brakuje. HTTPS korzysta z tego samego logowania `gh`, które już działa. Dotyczy tak samo macOS, Windowsa i VPS.
3. Weryfikacja: `gh auth status` musi pokazać zalogowanego użytkownika **i zakończyć się kodem 0** (`gh auth status; echo $?`).
   ⚠ Kod 1 przy widocznym „✓ Logged in" oznacza, że w konfiguracji siedzi DRUGIE konto z nieważnym tokenem — `gh` zwraca wtedy błąd mimo działającego konta aktywnego, a nomad odmawia startu komunikatem „gh CLI is not authenticated". Naprawa: `gh auth logout -h github.com -u <nazwa-zepsutego-konta>`.

## Krok 4A — instalacja: KOMPUTER LOKALNY (Mac i Windows)

1. **Silnik:** `npm i -g claude-nomad@0.64.1` (wersja przypięta świadomie; aktualizacja tylko na wyraźne życzenie, po `nomad doctor`).
2. **Etykieta maszyny (trwale):**
   - macOS: dopisz do `~/.zshrc`: `export NOMAD_HOST=<etykieta>` i wykonaj `export NOMAD_HOST=<etykieta>` w bieżącej sesji.
   - Windows: `setx NOMAD_HOST <etykieta>` + ustaw w bieżącej sesji `$env:NOMAD_HOST='<etykieta>'`.
3. **Zasiew repozytorium:** `nomad init --snapshot --repo claude-sync` — utworzy prywatne repo `claude-sync` na koncie użytkownika i zrobi migawkę istniejącego `~/.claude/`. Wyjaśnij różnicę dwóch katalogów: `~/claude-nomad/` = kopia robocza repozytorium (tę się edytuje), `~/.claude/` = konfiguracja „na żywo" (nomad ją odtwarza przy każdym pobraniu).
3a. **Pierwszy commit i adres HTTPS — OD RAZU po `init`, zanim cokolwiek innego.** `nomad init` zostawia katalog roboczy BEZ ani jednego commita, zdalne repozytorium bez gałęzi, a adres `origin` ustawiony na SSH. W tym stanie każda kolejna komenda nomada pada („There is no tracking information for the current branch" albo `Permission denied (publickey)`) — znalezisko testu T1, 09.08.2026. Domknij zasiew:
   ```bash
   cd ~/claude-nomad
   git remote set-url origin "https://github.com/$(gh api user --jq .login)/claude-sync.git"
   git config user.name  "$(gh api user --jq .login)"
   git config user.email "$(gh api user --jq '.id|tostring')+$(gh api user --jq .login)@users.noreply.github.com"
   git add -A && git commit -m "Zasiew konfiguracji"
   git push -u origin main
   ```
   Sprawdzenie: `git ls-remote --heads origin main` musi wypisać jedną linię. Pusty wynik = gałąź nie pojechała, nie idź dalej.
   (Ten `git push` wysyła wyłącznie szkielet utworzony przez `init` — właściwa bramka P0 z pełną listą plików jest w punkcie 6 i nadal obowiązuje.)
4. **Kontrola prywatności:** `gh repo view claude-sync --json visibility` → musi być `PRIVATE` (zasada nadrzędna 5).
5. **Mapa katalogów projektów:** zapytaj, gdzie użytkownik trzyma projekty, nad którymi pracuje Claude (np. `~/Projects`). Wypisz podkatalogi, ustal z użytkownikiem, które mają się synchronizować, i zbuduj `~/claude-nomad/path-map.json`:
   ```json
   {
     "projects": {
       "nazwa-projektu": {
         "mac": "/Users/uzytkownik/Projects/nazwa-projektu",
         "vps": "TBD"
       }
     }
   }
   ```
   `TBD` = „jeszcze nieustalone" — nomad takie wpisy pomija; uzupełni je instalacja na VPS.
5a. **Zapora na środowiska binarne — PRZED pierwszą wysyłką.** Migawka z `nomad init --snapshot` mogła objąć katalogi `venv/`, `.venv/`, `node_modules/` w skillach. Dopisz zaporę i wyprowadź je spod kontroli gita (pliki na dysku zostają):
   ```bash
   printf '%s\n' 'skills/**/venv' 'skills/**/.venv' 'skills/**/node_modules' 'skills/**/__pycache__' >> ~/claude-nomad/shared/.gitignore
   git -C ~/claude-nomad rm -r --cached --ignore-unmatch --quiet \
     'shared/skills/*/venv' 'shared/skills/*/.venv' 'shared/skills/*/node_modules' 'shared/skills/*/__pycache__'
   # stare pełne środowiska trzeba też usunąć z DYSKU w drzewie repozytorium —
   # inaczej najbliższe pobranie skopiowałoby je z powrotem na miejsce świeżych dowiązań:
   rm -rf ~/claude-nomad/shared/skills/*/venv ~/claude-nomad/shared/skills/*/.venv \
          ~/claude-nomad/shared/skills/*/node_modules ~/claude-nomad/shared/skills/*/__pycache__
   ```
   (Wpisy gitignore celowo BEZ końcowego ukośnika — z ukośnikiem git ignorowałby tylko katalogi, a dowiązanie-symlink pojechałoby do repozytorium jako martwa ścieżka z tej maszyny.)
   Wyjaśnij: te katalogi to programy skompilowane pod TEN system — na drugiej maszynie nie zadziałają, a synchronizowane psułyby na zmianę obie strony.
   Następnie WYPROWADŹ istniejące środowiska poza katalog skilli (zasada nadrzędna 10 — inaczej pull by je skasował) i zostaw dowiązania:
   ```bash
   mkdir -p ~/.claude/skill-envs
   for d in ~/.claude/skills/*/venv ~/.claude/skills/*/.venv ~/.claude/skills/*/node_modules; do
     [ -d "$d" ] && [ ! -L "$d" ] || continue
     skill="$(basename "$(dirname "$d")")"; env="$(basename "$d")"
     mv "$d" ~/.claude/skill-envs/"$skill--$env"
     ln -sfn ~/.claude/skill-envs/"$skill--$env" "$d"
   done
   ```
   (Windows: zamiast `ln -s` — `New-Item -ItemType Junction`; junction nie wymaga trybu dewelopera.) Wrapper po każdym pobraniu odtwarza brakujące dowiązania, więc od tej chwili środowiska są trwałe.
5b. **Sprawdź, czy skille faktycznie weszły do repozytorium.** `nomad init --snapshot` przenosi do `shared/` tylko `CLAUDE.md` i `settings.json` — katalog skilli zostaje po stronie `~/.claude/` (znalezisko testu T1, 09.08.2026). Sam `nomad adopt skills` NIE zadziała: ta nazwa jest odrzucana (`adopt` przyjmuje m.in. `CLAUDE.md`, `commands`, `rules`). Uruchom `NOMAD_HOST=<etykieta> nomad doctor --all` i przeczytaj sekcję **Skills**:
   - `skills: N file(s) diverge from shared/skills/` z pozycjami „local only" = skille NIE są w repozytorium.
   **Sam `nomad push` ich NIE zabierze** — sprawdzone: przy skillach obecnych wyłącznie w `~/.claude/skills/` wysyłka kończy się podsumowaniem „clean", czyli niczym. Skille wprowadza się do repozytorium ręcznie, raz:
   ```bash
   rsync -a --exclude '.env' --exclude 'venv' --exclude '.venv' \
             --exclude 'node_modules' --exclude '__pycache__' --exclude '.git' \
     ~/.claude/skills/ ~/claude-nomad/shared/skills/
   ```
   **`--exclude '.git'` jest obowiązkowe.** Skill pobrany z GitHuba ma w środku własne repozytorium; skopiowany razem z nim trafia do drzewa jako „gitlink" (odnośnik do cudzego repozytorium) i nomad odmawia wysyłki: „gitlink scan". Objaw rozpoznawczy: pułapka WRACA po każdej naprawie, bo źródło w `~/.claude/skills/` jest nietknięte. Sprawdzenie: `find ~/claude-nomad/shared/skills -name .git` musi być puste (znalezisko z instalacji na Windowsie, błąd B10).
   **NIE commituj tego ręcznie** — patrz ostrzeżenie w punkcie 6.
   (Wykluczenia są obowiązkowe: `.env` to plik z hasłami, reszta to środowiska binarne z zasady nadrzędnej 10.)
   **Na Windows `rsync` NIE ISTNIEJE** (błąd B18) — użyj:
   ```powershell
   robocopy "$env:USERPROFILE\.claude\skills" "$env:USERPROFILE\claude-nomad\shared\skills" /E /XJ `
     /XD .git venv .venv node_modules __pycache__ /XF .env /NFL /NDL /NJH /NJS | Out-Null
   $global:LASTEXITCODE = 0   # kody 0-7 robocopy to sukces
   ```
   Potem `nomad push --dry-run` musi wypisać w sekcji „Global config" linie `add shared/skills/...` — jeśli dalej mówi tylko „clean", skille nie weszły i nie wolno iść dalej.
   - Po wysyłce powtórz `nomad doctor --all`: sekcja Skills nie może już zgłaszać „local only" dla plików, które mają się synchronizować.
   To sprawdzenie jest obowiązkowe — synchronizacja bez skilli jest bezużyteczna, a nic w komunikatach nomada samo o tym nie powie. Jeśli po wysyłce skille dalej są „local only", ZATRZYMAJ SIĘ i zgłoś to użytkownikowi zamiast iść do automatyki.
   Ta sama sekcja `doctor` sygnalizuje też `CLAUDE.md: NOT a symlink (blocks sync)` — wtedy uruchom `nomad adopt CLAUDE.md`.
5c. **NIE rób `git add` ani `git commit` własną ręką.** Nomad zbiera zmiany sam, z drzewa roboczego. Pliki już zacommitowane są dla niego niewidoczne — wysyłka kończy się komunikatem „nothing to commit" i **nic nie jedzie**, mimo że pliki leżą w repozytorium (znalezisko z instalacji na Windowsie, błąd B11). Jeśli commit już powstał, cofnij go zachowując pliki: `git -C ~/claude-nomad reset --soft HEAD~1`.
5d. **Skrypty `.sh` — końce linii LF i prawo uruchamiania (OBOWIĄZKOWE przy wysyłce z Windowsa).** Windows zapisuje pliki z końcami linii CRLF i nie zna uprawnienia „wykonywalny". Skrypt wysłany stamtąd dociera na serwer w dwóch wymiarach zepsuty: bash przerywa na `$'\r': command not found`, a próba uruchomienia daje `Permission denied`. Na VPS oznacza to **martwą synchronizację, która milczy** — `sync-wrapper.sh` chodzi z crona co 30 minut i tylko zapisuje błędy do dziennika. Nie naprawi się samo: `.gitattributes` nomada zawiera `* -text`, czyli git ma konwersję końcówek celowo wyłączoną (zmierzone na VPS, błąd B22).

   Uruchom PO skopiowaniu skilli do repozytorium, a PRZED bramką P0:
   ```bash
   # macOS / Linux
   cd ~/claude-nomad
   find shared custom -name '*.sh' -type f -print0 2>/dev/null | xargs -0 -r perl -pi -e 's/\r$//'
   find shared custom -name '*.sh' -type f -print0 2>/dev/null | xargs -0 -r git update-index --add --chmod=+x
   ```
   ```powershell
   # Windows
   cd "$env:USERPROFILE\claude-nomad"
   Get-ChildItem -Path shared,custom -Filter *.sh -Recurse -File -ErrorAction SilentlyContinue | ForEach-Object {
     $t = [System.IO.File]::ReadAllText($_.FullName) -replace "`r`n", "`n"
     [System.IO.File]::WriteAllText($_.FullName, $t, (New-Object System.Text.UTF8Encoding $false))
     git update-index --add --chmod=+x ($_.FullName.Substring((Get-Location).Path.Length + 1) -replace '\\','/')
   }
   ```
   **Kontrola pozytywna — bez niej nie idź dalej:**
   ```bash
   git -C ~/claude-nomad ls-files -s -- '*.sh' | grep -v '^100755' && echo "STOP: powyzsze pliki nie maja prawa uruchamiania" || echo "OK: wszystkie .sh maja 100755"
   grep -rlU $'\r' ~/claude-nomad/shared ~/claude-nomad/custom --include='*.sh' && echo "STOP: powyzsze pliki maja CRLF" || echo "OK: zero CRLF"
   ```
   Plików `.ps1` NIE ruszaj — mają działać na Windowsie i CRLF im nie przeszkadza.

6. **BRAMKA P0 — pierwsza wysyłka:** pokaż `nomad push --dry-run` i listę plików, które dołoży warstwa `custom/` (hooki, agenci, lista pluginów). Przypomnij listę „nigdy nie jedzie" (sekcja niżej). Czekaj na wyraźne „tak". Po zgodzie: `nomad push`, następnie `bin/sync-wrapper.sh push` (Windows: `bin/sync-wrapper.ps1 push`), które dośle warstwę `custom/` po własnym skanie gitleaks.
6a. **Przy REINSTALACJI: `nomad pull` PRZED instalacją hooków.** Pobranie odtwarza pełny `settings.json` z repozytorium (model, motyw, wtyczki, hooki). Instalowanie hooków do pustego pliku, a potem utrwalanie go w repozytorium, skasowałoby resztę konfiguracji. Kolejność: `nomad pull` → sprawdź `wc -c ~/.claude/settings.json` (kilka bajtów = plik pusty, coś poszło źle) → dopiero punkt 7.
7. **Automatyka — etykieta maszyny.** Hooków NIE wpinasz: niesie je sam plugin (`hooks/hooks.json`), a Claude Code podstawia do nich aktualną ścieżkę przez `${CLAUDE_PLUGIN_ROOT}` — dlatego aktualizacja pluginu nigdy nie zrywa automatyki. Trzy zdarzenia: SessionStart → `pull`, Stop → `push` (wyciszenie 5 minut), SessionEnd → `push-koniec` (bez wyciszenia — drugiej okazji nie będzie, błąd B23).

   **Twoje jedyne zadanie w tym kroku: zapisać etykietę maszyny**, bo `hooks/uruchom.mjs` czyta ją z `~/.cache/sync-agenta/rola.json` (zmienna `NOMAD_HOST` ma pierwszeństwo, gdy ustawiona). Bez tego pliku hook grzecznie się wycofuje i dopisuje powód do logu — nomad pracowałby pod nazwą sieciową maszyny, ignorował `path-map.json` i psuł konfigurację.

   ```bash
   mkdir -p ~/.cache/sync-agenta
   printf '{"rola":"%s","system":"%s","host":"%s","zainstalowano":"%s"}\n' \
     "<lokalny|vps>" "<macos|linux|windows>" "<etykieta>" "$(date +%F)" > ~/.cache/sync-agenta/rola.json
   ```

   ⚠ **Na Windows zapisuj rolę BEZ BOM.** `Out-File -Encoding utf8` w PowerShellu 5.1 dokleja znacznik BOM na początku pliku i odczyt roli się na nim wywraca (błąd B15). Poprawnie:
   ```powershell
   $tresc = '{"rola":"lokalny","system":"windows","host":"<etykieta>","zainstalowano":"RRRR-MM-DD"}'
   [System.IO.File]::WriteAllText("$env:USERPROFILE\.cache\sync-agenta\rola.json", $tresc, (New-Object System.Text.UTF8Encoding $false))
   ```

   **Kontrola obowiązkowa — dwie rzeczy:**
   ```bash
   claude plugin details sync-agenta     # plugin zainstalowany i niesie 3 hooki?
   cat ~/.cache/sync-agenta/rola.json    # etykieta zapisana i czytelna?
   ```
   Cokolwiek z tego zawodzi — nie idź dalej. **Hooki zaczynają działać od następnego uruchomienia Claude Code** — po instalacji powiedz użytkownikowi wprost, żeby zrestartował sesję.

   Dawny `bin/install-hooks.mjs` zostaje wyłącznie do sprzątania po instalacjach sprzed 2.1.0: `node bin/install-hooks.mjs --remove --host <etykieta>` zdejmuje stare wpisy z `settings.json` i z nakładki `hosts/<etykieta>.json`. Do wpinania go NIE używaj.

8. **Test końcowy:** `NOMAD_HOST=<etykieta> nomad doctor` — streść wynik. **Powtórz też obie kontrole z kroku 5d** (tryb `100755`, zero znaków CR) — wysyłka mogła dołożyć nowe pliki `.sh`.
   Kontrola: pierwsze trzy bajty pliku NIE mogą być `EF BB BF`.

## Krok 4B — instalacja: VPS (Linux)

0. **Bariera pierwsza — logowanie na serwer.** Jeśli użytkownik nie wie, jak wejść na VPS, prowadź wg `references/vps-logowanie.md` (adres IP i dane SSH w panelu dostawcy; połączenie: `ssh root@ADRES_IP`). Dopiero po udanym logowaniu kontynuuj — cała instalacja VPS odbywa się w sesji na serwerze.
1. **Wymogi wstępne** jak w kroku 2 (komendy `apt` z `check-prereqs.sh`).
2. **GitHub:** `gh auth login --web --git-protocol https` (kod przepisuje się w przeglądarce na komputerze lokalnym). HTTPS, nie SSH — powód w kroku 3. Sprawdź kodem wyjścia: `gh auth status; echo $?` musi dać 0.
3. **Silnik:** `npm i -g claude-nomad@0.64.1`.
4. **Kontrola miejsca na dysku — PRZED pobraniem.** Tani VPS bywa mały (20–40 GB), a repozytorium z ciężkimi skillami waży dużo (katalog skilli potrafi ważyć ponad 1 GB). Sprawdź obie strony:
   ```bash
   df -h /                                              # wolne miejsce na serwerze
   gh api repos/<uzytkownik>/claude-sync --jq .size     # rozmiar repozytorium w KB (bez historii lokalnej)
   ```
   Reguła: wolne miejsce musi wynosić co najmniej **3× rozmiar repozytorium** (kopia robocza + historia gita + pliki w `~/.claude/` + zapas na pracę systemu). Jeśli nie wynosi — ZATRZYMAJ SIĘ przed `gh repo clone` i powiedz wprost: „Repozytorium waży X, a na serwerze zostało Y wolnego. Pobranie może zapchać dysk do zera i położyć serwer." Nie proponuj wykluczania skilli (decyzja: jedzie całość) — daj wybór: powiększyć dysk VPS albo świadomie kontynuować na granicy. Zapchany dysk = serwer przestaje działać, więc to bramka twarda.
4a. **Poświadczenia i tożsamość gita — INACZEJ PIERWSZY BIEG PADNIE.** Po zalogowaniu `gh` git wciąż nie zna ani hasła, ani autora commitów; objawy: `could not read Username for 'https://github.com'` przy pobraniu oraz `fatal: empty ident name` przy wysyłce (znaleziska T3, błędy B9a i B9b). Wykonaj OD RAZU po sklonowaniu:
   ```bash
   gh auth setup-git
   git -C ~/claude-nomad config user.name  "$(gh api user --jq .login)"
   git -C ~/claude-nomad config user.email "$(gh api user --jq '.id|tostring')+$(gh api user --jq .login)@users.noreply.github.com"
   ```
5. **Pobranie — bez zasiewania!** Ta maszyna niczego nie wysyła jako pierwsza:
   ```bash
   gh repo clone <uzytkownik>/claude-sync ~/claude-nomad
   echo 'export NOMAD_HOST=vps' >> ~/.bashrc && export NOMAD_HOST=vps
   NOMAD_HOST=vps nomad pull
   ```
   Uprzedź: pierwsze pobranie z ciężkimi skillami może trwać kilka–kilkanaście minut.
6. **Warstwa custom:** `bin/sync-wrapper.sh pull` — skopiuje hooki i agentów z `custom/` do `~/.claude/`.
7. **Mapa ścieżek:** uzupełnij wpisy `TBD` w `~/claude-nomad/path-map.json` prawdziwymi ścieżkami na serwerze (proponuj `/root/projects/<nazwa>`; utwórz te katalogi). Potem `NOMAD_HOST=vps nomad sync`.
8. **Pluginy:** `bin/plugins-restore.sh` — odtworzy rynki i instalacje z `custom/plugins.json`. Pluginy z rynku typu „lokalny katalog" nie odtworzą się — wypisz je z nazwy i wyjaśnij dlaczego (tego katalogu nie ma na serwerze).
9. **Środowiska skilli — buduj POZA katalogiem skilli.** Środowiska z Maca/Windows nie przyjechały (zapora gitignore), a zbudowane wprost w `~/.claude/skills/` zostałyby skasowane przez najbliższy pull (zasada nadrzędna 10). Dlatego dla każdego skilla z `requirements.txt` albo `package.json`:
   ```bash
   mkdir -p ~/.claude/skill-envs
   # przykład dla skilla <nazwa> ze środowiskiem Pythona:
   python3 -m venv ~/.claude/skill-envs/<nazwa>--venv
   ~/.claude/skill-envs/<nazwa>--venv/bin/pip install -r ~/.claude/skills/<nazwa>/requirements.txt
   ln -sfn ~/.claude/skill-envs/<nazwa>--venv ~/.claude/skills/<nazwa>/venv   # -sfn: nadpisze też wiszące stare dowiązanie
   # dla środowiska Node: npm ci --prefix po zbudowaniu w skill-envs + analogiczne dowiązanie
   ```
   Wrapper po każdym pobraniu odtwarza brakujące dowiązania — środowiska przeżyją każdy cron.
10. **Zapisz `~/.cache/sync-agenta/rola.json` TERAZ** (rola `vps`, system `linux`, etykieta hosta; `mkdir -p ~/.cache/sync-agenta`) — zanim ruszy automatyka: `install-cron.sh` i extra-sync czytają z niego rolę i etykietę, a plik miałby powstać dopiero na końcu instalacji.
11. **Automatyka — cron zamiast hooków.** Wyjaśnij: na serwerze sesja agenta chodzi ciągle, zdarzenie „koniec sesji" praktycznie nie zachodzi, więc synchronizację uruchamia harmonogram. Zapytaj o częstotliwość i przedstaw kompromis: częściej (np. co 15 min) = świeższa konfiguracja, ale zmiany mogą wjechać agentowi w środku zadania; rzadziej (np. co 2 h) = spokojniej, ale z opóźnieniem. Proponuj 30 minut. Potem: `bin/install-cron.sh 30 --host vps` — skrypt wpisuje w wiersz harmonogramu `NOMAD_HOST=<etykieta>` (z flagi `--host`, a bez niej ze środowiska albo `~/.cache/sync-agenta/rola.json`) oraz pełny `PATH` do nomada, gitleaks i node, bo cron nie czyta `~/.bashrc` i bez tego synchronizacja pracowałaby pod złą nazwą maszyny albo nie widziała programów.
12. **Test:** `NOMAD_HOST=vps bin/sync-wrapper.sh sync`, potem `NOMAD_HOST=vps nomad doctor`.

## Co NIGDY się nie synchronizuje

Nomad blokuje twardo (w obie strony): `.claude.json`, `.credentials.json`, `settings.local.json`, `history.jsonl`, `sessions`, `todos`, `shell-snapshots`, `cache`, `telemetry`, `statsig` i inne stany robocze maszyny; do tego pliki o kształcie sekretu (`.env*`, `*.pem`, `*.key`, `id_rsa`, `.npmrc`, `.netrc` itd.) oraz wszystko, co gitleaks wykryje w treści. Warstwa `custom/` skilla przechodzi własny skan gitleaks przed każdą wysyłką — znalezisko wstrzymuje wysyłkę `custom/`. Konsekwencje praktyczne, o których mów wprost: serwery MCP (konfiguracja w `~/.claude.json`) i logowanie do konta Anthropic trzeba ustawić na każdej maszynie osobno.

## Awarie — jak reagować

Pełna tabela: `references/awarie.md`. Skrót decyzji:
- **kod 4 (zacięte repozytorium):** to NIE jest „wybierz swoją albo cudzą wersję" — mechanizm scalania utknął w pół kroku (np. przerwane pobieranie) i wymaga naprawy. Procedura: `nomad doctor` (nazwie problem i poda komendę), zwykle `nomad pull --force-remote` (auto-naprawa; nadpisywane pliki dostają kopię zapasową).
- **konflikt treści (obie maszyny zmieniły to samo):** objawia się komunikatem nomada przy pobieraniu albo konfliktem gita w `custom/` — wtedy pokaż `nomad diff` / obie wersje pliku i daj wybór: chmura → `nomad pull --force-remote`, lokalna → nadpisz plik w `~/claude-nomad/` i `nomad sync`.
- **kod 5 (sekret):** nic nie wyciekło; pokaż znalezisko i daj wybór: wyczyścić (`nomad push --redact-all` lub edycja pliku), fałszywy alarm (`nomad push --allow <reguła>`), pominąć sesję (`nomad drop-session <id>` — identyfikator sesji odczytasz z komunikatu nomada o znalezisku; bez identyfikatora komenda odmówi działania).
- **kod 2 (zła komenda):** literówka albo zły argument w wywołaniu nomada — to błąd nasz, nie użytkownika; popraw wywołanie, użytkownikowi powiedz tylko „poprawiam komendę".
- **kod 130 (przerwane):** ktoś nacisnął Ctrl+C w trakcie — nic się nie zepsuło, po prostu uruchom ponownie.
- **brak sieci:** wrapper sam pomija bieg i spróbuje następnym razem — uspokój, nic nie zginęło.
- **log martwy > 24 h:** powiedz wprost „synchronizacja nie działa od wczoraj", pokaż koniec `~/.claude/logs/sync.log` i zdiagnozuj.

## Cofnięcie („odinstaluj")

Uruchom `bin/uninstall.sh` (Windows: `bin/uninstall.ps1`). Usuwa TYLKO hooki skilla z `settings.json` i wpis crona. Pliki, repozytorium i `~/.claude/` zostają nietknięte. Na koniec skrypt podpowiada opcjonalne kroki ręczne (`nomad eject`, usunięcie `~/claude-nomad/`, usunięcie repo z GitHuba) — wykonuj je wyłącznie na wyraźne polecenie.
