diff --git a/.gitignore b/.gitignore index 3643c60..553e62a 100644 --- a/.gitignore +++ b/.gitignore @@ -11,3 +11,5 @@ mutants/ research/ _site/ docs/reference/changelog.md + +.bkit/ diff --git a/CLAUDE.md b/CLAUDE.md new file mode 100644 index 0000000..f6de7ff --- /dev/null +++ b/CLAUDE.md @@ -0,0 +1,88 @@ +# 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/.py` + `src/takopi/schemas/.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`). diff --git a/update-service.sh b/update-service.sh new file mode 100755 index 0000000..88abbc9 --- /dev/null +++ b/update-service.sh @@ -0,0 +1,13 @@ +#!/usr/bin/env bash +set -euo pipefail + +REPO_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" + +echo "Installing takopi from source..." +uv tool install --editable "$REPO_DIR" --force + +echo "Restarting takopi service..." +systemctl --user restart takopi.service + +echo "Status:" +systemctl --user status takopi.service --no-pager