# CLAUDE.md

This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.

## Project

**alsogc** — a real-time collaboration relay that lets multiple Claude Code instances talk to each other through shared groups. Published as `pip install alsogc`.

## Commands

```bash
uv sync --dev                    # install deps
uv run python -m pytest          # run all tests
uv run python -m pytest tests/test_server  # run one test module
uv run python -m pytest -k test_name       # run a single test
uv run ruff check src tests      # lint
uv run ruff format src tests     # format
uv build                         # build wheel into dist/
```

## Architecture

Three packages under `src/relay/`, each with its own entry point:

### `relay.server` — FastMCP relay server
- **app.py** — builds the ASGI app. Two modes: `create_server()` for local (in-memory state, no auth) and `create_app()` for production (ClickHouse + auth middleware). Entry point: `relay-server`.
- **state.py** — `RelayState` holds all runtime state (groups, users, sessions, SSE queues). Every method has a DB-backed path (`self._db is not None`) and an in-memory fallback path. This dual-path pattern is central to the codebase.
- **db.py** — `RelayDB` wraps `clickhouse-connect` async client. Tables: accounts, users, user_groups, groups, messages, cursors. Uses `ReplacingMergeTree` with soft-delete for accounts/users/user_groups/groups, and `MergeTree` for messages.
- **tools.py** — registers MCP tools (`self_register`, `send_message`, `read_new_messages`, `list_peers`, `get_history`, `leave_group`, `list_my_groups`). All tools except `self_register` require prior registration via `_resolve()`.
- **auth.py** — `ApiKeyAuthMiddleware` validates `X-Api-Key` header against accounts table. `AuthStore` maps MCP session IDs to auth context. REST routes for login (WorkOS verification), user CRUD, and group management.
- **sse.py** — SSE endpoint at `/notifications/{user_id}` for real-time push via `sse-starlette`. Validates user ownership before connecting.
- **api.py** — REST endpoints under `/auth/` for TUI (messages, peers).

### `relay.tui` — Textual TUI sidebar
- **app.py** — `RelayTUI` Textual app. Shows live message feed + members panel. Entry point: `relay-tui`.
- **sse_client.py** — `SSEListener` connects to the SSE endpoint with exponential backoff reconnection.
- **api_client.py** — `ApiClient` wraps httpx for REST calls to get messages/peers/user groups.
- **injector.py** — `TmuxInjector` types `[ALSO NOTF]` notifications into a target tmux pane via `tmux send-keys`. Input is sanitized to prevent injection.

### `relay.connect` — CLI setup wizard
- **cli.py** — interactive CLI (`alsogc` entry point) using InquirerPy. Authenticates via WorkOS device flow, manages users/groups, discovers Claude Code sessions in tmux, creates 1:1 user-session mappings, launches TUI sidebars, and injects self_register messages. API key stored in `~/.claude.json` MCP config.

## Key Patterns

- **Dual-path state**: `RelayState` methods branch on `self._db is not None`. In-memory path uses `Group.messages` list + `Peer.last_read_index`; DB path uses ClickHouse tables + cursor tracking.
- **Auth flow**: Client sends `X-Api-Key` header. Middleware validates against accounts table, stores `AuthInfo` per MCP session. CC instances call `self_register(user_id)` to adopt an identity. Tools call `auth_store.get(session_id)` to identify the caller.
- **SSE notification fan-out**: `RelayState.sse_queues` maps user_id to asyncio Queue. Messages and presence events are pushed to group members only.
- **Identity model**: Accounts (WorkOS-authenticated) → Users (named personas) → Groups. Each CC session self-registers as a user via MCP tool call.

## Deployment

Production runs on Modal (`modal_app.py`) with ClickHouse for persistence. Secrets: `clickhouse-creds` (CLICKHOUSE_HOST, etc.) and `workos-creds` (WORKOS_API_KEY).

## Testing

Tests use pytest-asyncio with `asyncio_mode = "auto"`. The `conftest.py` provides `state` (bare RelayState) and `registered_pair` (two users in group "g1") fixtures. Tests run against in-memory state only (no ClickHouse).

## Style

- Python 3.12+, ruff for linting/formatting, line length 100
- No comments in code
- Pydantic models for data (`models.py`), dataclasses for DB row types and config
