229 lines
17 KiB
Markdown
229 lines
17 KiB
Markdown
# spacesh — запуск и ручное тестирование
|
||
|
||
Практический ран-бук: как собрать, запустить и руками проверить всё, что реализовано (M0+M1, M2, M3, M4).
|
||
|
||
> Архитектура в двух словах: **демон `spaceshd`** владеет живыми PTY-сессиями и слушает Unix-socket; **GUI** (Tauri) и **CLI `spacesh`** — тонкие клиенты к нему. GUI и CLI поднимают демон лениво, если он не запущен.
|
||
|
||
---
|
||
|
||
## 1. Предусловия
|
||
|
||
- macOS (целевая платформа).
|
||
- Rust (стабильный) — `cargo --version`.
|
||
- Node ≥ 20 + npm — `node --version`, `npm --version`.
|
||
- Для GUI: системный WebView (есть в macOS из коробки).
|
||
|
||
Проверка тулчейна:
|
||
```bash
|
||
cargo --version && node --version && npm --version
|
||
```
|
||
|
||
---
|
||
|
||
## 2. Сборка
|
||
|
||
Из корня репозитория:
|
||
```bash
|
||
# Rust-крейты (демон, CLI, proto/core/pty)
|
||
cargo build
|
||
|
||
# Фронт-зависимости (один раз)
|
||
cd app && npm install && cd ..
|
||
```
|
||
|
||
Бинарники после `cargo build`:
|
||
- демон → `target/debug/spaceshd`
|
||
- CLI → `target/debug/spacesh`
|
||
|
||
Полный прогон автотестов (должно быть зелено):
|
||
```bash
|
||
cargo test --workspace
|
||
cd app && npm run build && cd .. # tsc + vite, проверяет фронт
|
||
```
|
||
|
||
---
|
||
|
||
## 3. Запуск GUI (основной путь)
|
||
|
||
Запускает Tauri-окно; демон поднимется лениво.
|
||
```bash
|
||
cd app && npm run tauri dev
|
||
```
|
||
Первый старт дольше (компиляция `src-tauri`). Откроется окно spacesh.
|
||
|
||
Базовый цикл в GUI:
|
||
1. **`+ New workspace`** (слева) → визард: укажи папку, выбери раскладку-пресет (1 / 2↔ / 2↕ / 2×2 / …), назначь агента на каждую панель (shell / claude / codex / gemini) → **Create workspace**.
|
||
2. Появится грид панелей с терминалами. Печатай — ввод уходит в PTY, вывод рисуется.
|
||
3. Слева — список воркспейсов (группы, счётчик панелей, кольцо статуса, точка «не забыть»).
|
||
4. Справа — **Event Center** (лента событий статуса).
|
||
|
||
---
|
||
|
||
## 4. Демон вручную (для CLI-тестов и отладки)
|
||
|
||
GUI поднимает демон сам, но для CLI-сценариев удобно держать **изолированный** демон на отдельном сокете через `SPACESH_SOCK` (чтобы не мешать «боевому»):
|
||
|
||
```bash
|
||
# терминал A — демон на временном сокете
|
||
SOCK=/tmp/spacesh-dev.sock
|
||
SPACESH_SOCK=$SOCK ./target/debug/spaceshd
|
||
# в логах: "spaceshd listening on /tmp/spacesh-dev.sock"
|
||
```
|
||
|
||
```bash
|
||
# терминал B — CLI к тому же сокету
|
||
SOCK=/tmp/spacesh-dev.sock
|
||
SPACESH_SOCK=$SOCK ./target/debug/spacesh status
|
||
```
|
||
|
||
Без `SPACESH_SOCK` сокет по умолчанию — `~/.spacesh/sock` (общий с GUI).
|
||
|
||
Остановить демон: `spacesh shutdown` (к нужному сокету) или `Ctrl-C` в терминале A.
|
||
|
||
### launchd (автоперезапуск, опционально)
|
||
```bash
|
||
./target/debug/spaceshd install-agent # ставит ~/Library/LaunchAgents/xyz.spacesh.daemon.plist (KeepAlive)
|
||
launchctl list | grep spacesh # проверить
|
||
# снять:
|
||
launchctl unload ~/Library/LaunchAgents/xyz.spacesh.daemon.plist
|
||
rm ~/Library/LaunchAgents/xyz.spacesh.daemon.plist
|
||
```
|
||
|
||
---
|
||
|
||
## 5. CLI `spacesh`
|
||
|
||
Все команды бьют в демон (лениво стартуют, если не запущен; `notify` — best-effort, без старта). Глобальный флаг `--json` — сырой ответ.
|
||
|
||
```bash
|
||
SOCK=/tmp/spacesh-dev.sock
|
||
S() { SPACESH_SOCK=$SOCK ./target/debug/spacesh "$@"; }
|
||
|
||
S open /tmp/myproject # → workspace_id (w_…)
|
||
S status # таблица: воркспейсы · панели (id, агент, running/stopped, статус)
|
||
S status --json # сырой JSON
|
||
S new-surface <w_id> # поднять панель (дефолт $SHELL) → surface_id (s_…)
|
||
S new-surface <w_id> --cmd claude
|
||
S split <s_id> --dir right # разбить панель соседней
|
||
S apply-preset <w_id> --preset 2x2 --agent claude --agent shell --agent shell --agent shell
|
||
S notify --surface <s_id> --state work # ← так же делают хуки агентов
|
||
S restart <s_id> # перезапустить stopped-панель
|
||
S close <s_id>
|
||
S group create --name production --color '#F4544E'
|
||
S set-meta <w_id> --group <g_id> --unread true
|
||
S close-workspace <w_id>
|
||
S completions zsh # скрипт автодополнения
|
||
S shutdown
|
||
```
|
||
|
||
Пресеты: `1 2lr 2tb 2+1 1+2 3 2x2 4 2x3 2x4`.
|
||
Состояния (`--state`): `work wait done error idle`.
|
||
|
||
---
|
||
|
||
## 6. Ручные сценарии по фичам
|
||
|
||
Запусти демон на `SPACESH_SOCK=/tmp/spacesh-dev.sock` (см. §4), GUI — `npm run tauri dev` (для GUI-проверок).
|
||
|
||
### M0 — живой терминал
|
||
1. GUI → `+ surface` (или `spacesh new-surface`).
|
||
2. Печатай `echo hi` → видишь вывод. Байты летают GUI↔демон↔PTY.
|
||
|
||
### M1 — переживаемость / reattach
|
||
1. В панели запусти долгое: `top` (или агента).
|
||
2. **Закрой окно GUI** (демон остаётся жив): проверь `pgrep spaceshd` и `ls ~/.spacesh/sock`.
|
||
3. Снова `npm run tauri dev` → панель на месте, экран восстановлен из снапшота, `top` продолжает обновляться.
|
||
|
||
### M2 — раскладки, воркспейсы, персист
|
||
1. Визард → пресет `2×2`, агенты на слоты → грид из 4 панелей.
|
||
2. Тяни **сплиттер** между панелями мышью — пропорции меняются.
|
||
3. Тулбар пресетов сверху — переразбивка активного воркспейса.
|
||
4. Закрой панель → дерево схлопывается.
|
||
5. **Холодный рестарт демона:** `spacesh shutdown` (или `pkill spaceshd`), затем снова открой GUI → воркспейсы и раскладка восстановлены с диска, панели показаны **`stopped`** с кнопкой **Restart** (autostart по умолчанию выкл). Жми Restart → панель поднимается.
|
||
6. Сайдбар: создай группу, перекрась, перетащи воркспейс (если drag включён), метка unread.
|
||
Состояние лежит в `~/.spacesh/state.json` (или своём, если задан `SPACESH_SOCK`-профиль — сокет отдельный, но state.json общий в `~/.spacesh`).
|
||
|
||
### M3 — статусы
|
||
1. **Claude-панель:** `new-surface <w> --cmd claude` (нужен установленный `claude` в PATH). Демон кладёт per-surface хуки в `~/.spacesh/hooks/<surface_id>/` и прокидывает `CLAUDE_CONFIG_DIR`. Поработай в агенте: кольцо панели меняется **work → wait → done** (Stop→done, Notification→wait, UserPromptSubmit→work). Глобальный `~/.claude/settings.json` не трогается.
|
||
2. **shell-панель (zsh) — OSC 133:** `new-surface <w>` (zsh по умолчанию). Запусти команду: пока идёт — кольцо **work**; завершилась с 0 — **done**; `false` (exit 1) — **error**. (Интеграция инжектится через `ZDOTDIR`; bash/fish — на эвристике fallback.)
|
||
3. **Эмуляция без агента:** `spacesh notify --surface <s_id> --state error` → кольцо краснеет, в `status` виден `error`.
|
||
4. **Нативные уведомления:** сверни/расфокусь окно GUI, доведи панель до `done/wait/error` → придёт macOS-уведомление (первый раз спросит разрешение). Клик по записи в **Event Center** фокусит панель.
|
||
5. **Авто-unread:** событие статуса в **неактивном** воркспейсе ставит ему синюю точку «не забыть»; выбор воркспейса снимает.
|
||
|
||
### SP2 — персистентный журнал событий / read-model
|
||
1. Доведи панель до `done`/`error` (или `spacesh notify --surface <s> --state error`). В Event Center появляется запись; бейдж на `bell` (в топ-баре) растёт.
|
||
2. **Перезапусти GUI** (демон жив): лента на месте — она берётся из демона, не из памяти GUI.
|
||
3. **Холодный рестарт демона** (`spacesh shutdown`, затем снова открой GUI): лента всё ещё на месте — восстановлена из `~/.spacesh/events.json`.
|
||
4. Клик по записи (или фокус её панели) помечает её прочитанной — запись тускнеет, бейдж уменьшается. `Mark all read` гасит бейдж полностью.
|
||
5. Табы фильтруют по реальным данным: `Unread` — только непрочитанные, `Errors` — только события `error`.
|
||
6. **Явное закрытие панели не логируется** (это намеренно — пользователь сам её закрыл); в журнал попадают только сами-завершившиеся/упавшие процессы и переходы статуса `done/wait/error`.
|
||
|
||
Файл журнала: `~/.spacesh/events.json` (кольцо на 1000 записей, атомарная запись + corrupt-backup, как у `state.json`).
|
||
|
||
### M4 — CLI
|
||
- `spacesh status --json` против живого демона; `spacesh notify` без демона → молча `exit 0`; `spacesh completions zsh` печатает скрипт.
|
||
|
||
### SP1/SP3/SP4 — health, поиск, zoom
|
||
- **Health (SP1):** футер сайдбара показывает `spaceshd · live` с зелёной точкой и аптайм (`3d 4h`); версия демона — в tooltip. При падении демона точка сереет, текст `offline`, аптайм пропадает.
|
||
- **Поиск (SP3):** `⌘F` (или клик по пилюле «Search scrollback» над гридом) открывает строку поиска для активной панели. Печатай запрос и жми `Enter` → совпадения подсвечиваются, `Enter`/`Shift+Enter` — next/prev, счётчик `i/N`, `Esc` или `✕` — закрыть. Повторный `⌘F` при открытой строке — фокус+выделение поля. Поиск идёт по буферу xterm активной панели (scrollback до 10000 строк).
|
||
- **Zoom (SP4):** иконка `⤢` в шапке панели разворачивает её на весь грид (панель становится активной); `⤡` возвращает. Состояние персистится в `~/.spacesh/state.json` — переживает рестарт демона. При закрытии развёрнутой панели zoom сбрасывается; если процесс в развёрнутой панели завершился — в карточке «Process exited» есть кнопка «Exit zoom».
|
||
|
||
---
|
||
|
||
## 7. Где что лежит / сброс
|
||
|
||
```
|
||
~/.spacesh/
|
||
sock # Unix-socket демона (или путь из $SPACESH_SOCK)
|
||
daemon.lock # single-instance лок
|
||
state.json # персист раскладок/воркспейсов/групп (M2)
|
||
hooks/<surface_id>/ # per-surface CLAUDE_CONFIG_DIR с settings.json (M3, чистится при close)
|
||
shellint/<surface_id>/ # per-surface ZDOTDIR с zsh OSC 133 (M3)
|
||
```
|
||
|
||
Полный сброс состояния (демон должен быть остановлен):
|
||
```bash
|
||
spacesh shutdown 2>/dev/null; pkill spaceshd 2>/dev/null
|
||
rm -rf ~/.spacesh # сбрасывает сокет, лок, state.json, hooks, shellint
|
||
```
|
||
|
||
---
|
||
|
||
## 8. Траблшутинг
|
||
|
||
- **`daemon unavailable` / CLI висит:** сокет битый. `pkill spaceshd; rm -f ~/.spacesh/sock` (или свой `$SPACESH_SOCK`), повтори.
|
||
- **«another spaceshd is already running»:** уже есть живой демон на этом сокете — это норма; используй его или `spacesh shutdown`.
|
||
- **GUI не видит демон / пусто:** проверь, что GUI и демон на одном сокете (если задавал `SPACESH_SOCK` для демона — задай тот же для GUI: `SPACESH_SOCK=… npm run tauri dev`).
|
||
- **GUI пишет «could not connect to spaceshd» (lazy-start не нашёл бинарь):** в `tauri dev` app-бинарь лежит в `app/src-tauri/target/` и демон ему не «сосед» — GUI ищет его по dev-пути в корневом `target/debug/spaceshd` (убедись, что сделан `cargo build -p spaceshd`). Можно явно указать: `SPACESHD_BIN=$PWD/target/debug/spaceshd npm run tauri dev`, либо просто подними демон сам перед GUI: `./target/debug/spaceshd &`. Окно теперь открывается даже без демона (не падает) — команды заработают, как только демон поднимется (перезапусти GUI).
|
||
- **Статусы у claude не меняются:** проверь, что `claude` в PATH, и что в `~/.spacesh/hooks/<sid>/settings.json` абсолютный путь к `spacesh` верный. Имена/формат хуков Claude Code дрейфуют по версиям — при несовпадении правится только `crates/spaceshd/src/hooks.rs`.
|
||
- **Нет уведомлений:** проверь разрешение macOS (Системные настройки → Уведомления → spacesh) и что окно действительно не в фокусе.
|
||
- **`npm run tauri dev` падает на компиляции:** прогони `cargo build -p spaceshd` отдельно, посмотри ошибку; затем `cd app && npm install`.
|
||
|
||
---
|
||
|
||
## 9. Известные ограничения (на сейчас)
|
||
|
||
- **Playwright/headless-браузер** видит только Vite-фронт (`npm run dev`, :1420) — Tauri-IPC там недоступен, живой daemon-флоу не тестируется. Полный e2e — только `npm run tauri dev` на дисплее.
|
||
- **OSC 133 — только zsh** (через `ZDOTDIR`); bash/fish работают на fallback-эвристике.
|
||
- **Клик по нативному уведомлению** не фокусит конкретную панель (клик по записи в Event Center — фокусит).
|
||
- **Event Center** — лента хранится в демоне и персистируется в `~/.spacesh/events.json` (переживает перезапуск GUI и холодный рестарт демона). Вкладки `Unread`/`Errors` и бейдж `bell` работают по реальным данным (флаги прочтения на уровне события). По-прежнему не реализованы: каналы Telegram/MAX в футере Event Center (SP5), а также `search`/`settings` и меню аккаунта в топ-баре.
|
||
- **Статус эфемерен** (work/wait/done/error/idle) — не персистится; после холодного рестарта демона панель `stopped`, статус `idle`.
|
||
- **Поиск по скроллбэку (SP3)** работает в пределах xterm-буфера активной панели (до 10000 строк); поиск по демон-сайд / CLI-сетке (`alacritty_terminal` grid) остаётся задачей будущего.
|
||
- Авторизация / личный кабинет / внешние нотификации (Telegram/MAX) / diff-вьюер / remote — **не реализованы** (M5/M6/auth, см. `DOCS/MAIN.md`).
|
||
|
||
---
|
||
|
||
## 10. Быстрый smoke одной командой (без GUI)
|
||
|
||
```bash
|
||
cargo build
|
||
SOCK=/tmp/spacesh-smoke.sock
|
||
SPACESH_SOCK=$SOCK ./target/debug/spaceshd & DPID=$!
|
||
sleep 1
|
||
SPACESH_SOCK=$SOCK ./target/debug/spacesh open /tmp
|
||
SPACESH_SOCK=$SOCK ./target/debug/spacesh status
|
||
SPACESH_SOCK=$SOCK ./target/debug/spacesh shutdown
|
||
kill $DPID 2>/dev/null; rm -f $SOCK
|
||
```
|
||
Ожидаемо: `open` печатает `w_…`, `status` показывает воркспейс, `shutdown` гасит демон.
|