89 lines
3.6 KiB
Markdown
89 lines
3.6 KiB
Markdown
# CLAUDE.md
|
|
|
|
This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
|
|
|
|
## Commands
|
|
|
|
```bash
|
|
# Run all checks (format, lint, type, tests)
|
|
just check
|
|
|
|
# Individual checks
|
|
uv run ruff format --check src tests
|
|
uv run ruff check src tests
|
|
uv run ty check src tests
|
|
uv run pytest
|
|
|
|
# Single test
|
|
uv run pytest tests/test_runner_contract.py
|
|
|
|
# Run directly without install
|
|
uv run takopi --help
|
|
|
|
# Mutation testing
|
|
uv run mutmut run
|
|
|
|
# Docs
|
|
just docs-serve
|
|
```
|
|
|
|
## Architecture
|
|
|
|
Takopi is a Telegram bridge for agent CLIs (Claude Code, Codex, OpenCode, Pi). It polls Telegram, routes messages to agent runners, streams progress back, and embeds resume tokens so sessions can continue.
|
|
|
|
### Layer stack (top → bottom)
|
|
|
|
| Layer | Key modules |
|
|
|-------|-------------|
|
|
| **CLI** | `cli.py` — entry point, config, lock |
|
|
| **Plugins** | `plugins.py`, `engines.py`, `transports.py`, `commands.py`, `api.py` |
|
|
| **Orchestration** | `router.py` (auto-route by resume token), `scheduler.py` (per-thread FIFO queue), `transport_runtime.py` |
|
|
| **Bridge** | `telegram/bridge.py` (poll loop, parse directives), `runner_bridge.py` (progress + final render) |
|
|
| **Runner** | `runner.py` (protocol + `JsonlSubprocessRunner`), `runners/*.py`, `schemas/*.py` |
|
|
| **Transport** | `transport.py`, `presenter.py`, `telegram/client.py`, `telegram/render.py` |
|
|
| **Domain** | `model.py`, `events.py`, `progress.py` |
|
|
|
|
### Plugin system
|
|
|
|
Engines, transports, and commands are discovered via Python entrypoints:
|
|
|
|
- `takopi.engine_backends` — runner backends (id must match entrypoint name)
|
|
- `takopi.transport_backends` — transport backends
|
|
- `takopi.command_backends` — in-chat command handlers
|
|
|
|
Public API surface for plugin authors: `takopi.api`. Internal modules should not be imported directly from plugins.
|
|
|
|
### Runner contract
|
|
|
|
Every runner must yield events satisfying (enforced by `tests/test_runner_contract.py`):
|
|
|
|
1. Exactly one `StartedEvent` (first)
|
|
2. Exactly one `CompletedEvent` (last)
|
|
3. `CompletedEvent.resume == StartedEvent.resume`
|
|
|
|
Runners extend `JsonlSubprocessRunner` + `ResumeTokenMixin` and implement:
|
|
- `build_args(...)` / `stdin_payload(...)` — build subprocess command
|
|
- `decode_jsonl(...)` — parse one JSONL line via msgspec
|
|
- `translate(...)` — pure function converting engine events to `TakopiEvent`s
|
|
- `format_resume()` / `extract_resume()` / `is_resume_line()` — resume codec
|
|
|
|
### Resume flow
|
|
|
|
Resume tokens are embedded as inline code in the final Telegram message (e.g., `` `claude --resume abc123` ``). When a user replies to that message, the bridge extracts the token and passes it to the matching runner. Each runner owns its own resume regex and format.
|
|
|
|
### Thread scheduling
|
|
|
|
`ThreadScheduler` maintains per-thread FIFO queues. Same-thread jobs run sequentially; different threads run in parallel. The "thread" key is the Telegram message thread / reply chain.
|
|
|
|
### Adding a runner
|
|
|
|
See `docs/how-to/add-a-runner.md`. Short version: add `src/takopi/runners/<engine>.py` + `src/takopi/schemas/<engine>.py`, expose `BACKEND = EngineBackend(...)`, add entrypoint in `pyproject.toml`. No changes to bridge or CLI needed.
|
|
|
|
## Key invariants
|
|
|
|
- `StartedEvent` must be emitted as soon as the session ID is known (enables early lock acquisition).
|
|
- Runners must not invent new event types — translate everything into `StartedEvent`, `ActionEvent`, `CompletedEvent`.
|
|
- Schema decoding uses **msgspec** (not pydantic); decoders live in `schemas/`.
|
|
- Config lives in `~/.takopi/takopi.toml`; loaded via pydantic-settings.
|
|
- Coverage threshold: 81% (`pyproject.toml` → `--cov-fail-under=81`).
|