Files

17 KiB
Raw Permalink Blame History

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 из коробки).

Проверка тулчейна:

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:

  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 (чтобы не мешать «боевому»):

# терминал 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 — живой терминал

  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)

Полный сброс состояния (демон должен быть остановлен):

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)

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 гасит демон.