17 KiB
spacesh — запуск и ручное тестирование
Практический ран-бук: как собрать, запустить и руками проверить всё, что реализовано (M0+M1, M2, M3, M4).
Архитектура в двух словах: демон
spaceshdвладеет живыми PTY-сессиями и слушает Unix-socket; GUI (Tauri) и CLIspacesh— тонкие клиенты к нему. GUI и CLI поднимают демон лениво, если он не запущен.
1. Предусловия
- macOS (целевая платформа).
- Rust (стабильный) —
cargo --version. - Node ≥ 20 + npm —
node --version,npm --version. - Для GUI: системный WebView (есть в macOS из коробки).
Проверка тулчейна:
cargo --version && node --version && npm --version
2. Сборка
Из корня репозитория:
# Rust-крейты (демон, CLI, proto/core/pty)
cargo build
# Фронт-зависимости (один раз)
cd app && npm install && cd ..
Бинарники после cargo build:
- демон →
target/debug/spaceshd - CLI →
target/debug/spacesh
Полный прогон автотестов (должно быть зелено):
cargo test --workspace
cd app && npm run build && cd .. # tsc + vite, проверяет фронт
3. Запуск GUI (основной путь)
Запускает Tauri-окно; демон поднимется лениво.
cd app && npm run tauri dev
Первый старт дольше (компиляция src-tauri). Откроется окно spacesh.
Базовый цикл в GUI:
+ New workspace(слева) → визард: укажи папку, выбери раскладку-пресет (1 / 2↔ / 2↕ / 2×2 / …), назначь агента на каждую панель (shell / claude / codex / gemini) → Create workspace.- Появится грид панелей с терминалами. Печатай — ввод уходит в PTY, вывод рисуется.
- Слева — список воркспейсов (группы, счётчик панелей, кольцо статуса, точка «не забыть»).
- Справа — Event Center (лента событий статуса).
4. Демон вручную (для CLI-тестов и отладки)
GUI поднимает демон сам, но для CLI-сценариев удобно держать изолированный демон на отдельном сокете через SPACESH_SOCK (чтобы не мешать «боевому»):
# терминал A — демон на временном сокете
SOCK=/tmp/spacesh-dev.sock
SPACESH_SOCK=$SOCK ./target/debug/spaceshd
# в логах: "spaceshd listening on /tmp/spacesh-dev.sock"
# терминал 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 (автоперезапуск, опционально)
./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 — сырой ответ.
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 — живой терминал
- GUI →
+ surface(илиspacesh new-surface). - Печатай
echo hi→ видишь вывод. Байты летают GUI↔демон↔PTY.
M1 — переживаемость / reattach
- В панели запусти долгое:
top(или агента). - Закрой окно GUI (демон остаётся жив): проверь
pgrep spaceshdиls ~/.spacesh/sock. - Снова
npm run tauri dev→ панель на месте, экран восстановлен из снапшота,topпродолжает обновляться.
M2 — раскладки, воркспейсы, персист
- Визард → пресет
2×2, агенты на слоты → грид из 4 панелей. - Тяни сплиттер между панелями мышью — пропорции меняются.
- Тулбар пресетов сверху — переразбивка активного воркспейса.
- Закрой панель → дерево схлопывается.
- Холодный рестарт демона:
spacesh shutdown(илиpkill spaceshd), затем снова открой GUI → воркспейсы и раскладка восстановлены с диска, панели показаныstoppedс кнопкой Restart (autostart по умолчанию выкл). Жми Restart → панель поднимается. - Сайдбар: создай группу, перекрась, перетащи воркспейс (если drag включён), метка unread.
Состояние лежит в
~/.spacesh/state.json(или своём, если заданSPACESH_SOCK-профиль — сокет отдельный, но state.json общий в~/.spacesh).
M3 — статусы
- 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не трогается. - shell-панель (zsh) — OSC 133:
new-surface <w>(zsh по умолчанию). Запусти команду: пока идёт — кольцо work; завершилась с 0 — done;false(exit 1) — error. (Интеграция инжектится черезZDOTDIR; bash/fish — на эвристике fallback.) - Эмуляция без агента:
spacesh notify --surface <s_id> --state error→ кольцо краснеет, вstatusвиденerror. - Нативные уведомления: сверни/расфокусь окно GUI, доведи панель до
done/wait/error→ придёт macOS-уведомление (первый раз спросит разрешение). Клик по записи в Event Center фокусит панель. - Авто-unread: событие статуса в неактивном воркспейсе ставит ему синюю точку «не забыть»; выбор воркспейса снимает.
SP2 — персистентный журнал событий / read-model
- Доведи панель до
done/error(илиspacesh notify --surface <s> --state error). В Event Center появляется запись; бейдж наbell(в топ-баре) растёт. - Перезапусти GUI (демон жив): лента на месте — она берётся из демона, не из памяти GUI.
- Холодный рестарт демона (
spacesh shutdown, затем снова открой GUI): лента всё ещё на месте — восстановлена из~/.spacesh/events.json. - Клик по записи (или фокус её панели) помечает её прочитанной — запись тускнеет, бейдж уменьшается.
Mark all readгасит бейдж полностью. - Табы фильтруют по реальным данным:
Unread— только непрочитанные,Errors— только событияerror. - Явное закрытие панели не логируется (это намеренно — пользователь сам её закрыл); в журнал попадают только сами-завершившиеся/упавшие процессы и переходы статуса
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)
Полный сброс состояния (демон должен быть остановлен):
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 devapp-бинарь лежит в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_terminalgrid) остаётся задачей будущего. - Авторизация / личный кабинет / внешние нотификации (Telegram/MAX) / diff-вьюер / remote — не реализованы (M5/M6/auth, см.
DOCS/MAIN.md).
10. Быстрый smoke одной командой (без GUI)
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 гасит демон.