docs: restructure docs into diataxis (#121)
This commit is contained in:
+17
-363
@@ -1,372 +1,26 @@
|
|||||||
# takopi - Developer Guide
|
# Developer guide (moved)
|
||||||
|
|
||||||
This document describes the internal architecture and module responsibilities.
|
This document was split into smaller Diátaxis pages.
|
||||||
See `specification.md` for the authoritative behavior spec.
|
|
||||||
|
|
||||||
## Development Setup
|
## Setup
|
||||||
|
|
||||||
```bash
|
- [Dev setup](how-to/dev-setup.md)
|
||||||
# Clone and enter the directory
|
|
||||||
git clone https://github.com/banteg/takopi
|
|
||||||
cd takopi
|
|
||||||
|
|
||||||
# Run directly with uv (installs deps automatically)
|
## Understanding the codebase
|
||||||
uv run takopi --help
|
|
||||||
|
|
||||||
# Or install locally from the repo to test outside the repo
|
- [Architecture](explanation/architecture.md)
|
||||||
uv tool install .
|
- [Routing & sessions](explanation/routing-and-sessions.md)
|
||||||
takopi --help
|
- [Module map](explanation/module-map.md)
|
||||||
|
|
||||||
# Run tests, linting, type checking
|
## Reference
|
||||||
uv run pytest
|
|
||||||
uv run ruff check src tests
|
|
||||||
uv run ty check .
|
|
||||||
|
|
||||||
# Or all at once
|
- [Specification](reference/specification.md)
|
||||||
just check
|
- [Plugin API](reference/plugin-api.md)
|
||||||
```
|
- [Environment variables](reference/env-vars.md)
|
||||||
|
- [Telegram transport](reference/transports/telegram.md)
|
||||||
|
- [Runners](reference/runners/index.md)
|
||||||
|
|
||||||
Takopi runs in **auto-router** mode by default. `default_engine` in `takopi.toml` selects
|
## Extending Takopi
|
||||||
the engine for new threads; engine subcommands override that default for the process.
|
|
||||||
|
|
||||||
## Module Responsibilities
|
- [Write a plugin](how-to/write-a-plugin.md)
|
||||||
|
- [Add a runner](how-to/add-a-runner.md)
|
||||||
### `runner_bridge.py` - Transport-agnostic orchestration
|
|
||||||
|
|
||||||
The core handler module containing:
|
|
||||||
|
|
||||||
| Component | Purpose |
|
|
||||||
|-----------|---------|
|
|
||||||
| `ExecBridgeConfig` | Frozen dataclass holding transport + presenter config |
|
|
||||||
| `IncomingMessage` | Normalized incoming message shape |
|
|
||||||
| `handle_message()` | Per-message handler with progress updates and final render |
|
|
||||||
| `ProgressEdits` | Throttled progress edit worker |
|
|
||||||
| `RunningTask` | Cancellation + resume coordination for in-flight runs |
|
|
||||||
|
|
||||||
**Key patterns:**
|
|
||||||
- Progress edits are best-effort and only run when new events arrive (Telegram outbox handles rate limiting/coalescing)
|
|
||||||
- Resume tokens are runner-formatted command lines (e.g., `` `codex resume <token>` ``, `` `claude --resume <token>` ``, `` `pi --session <id>` ``)
|
|
||||||
- Resume lines are stripped from the prompt before invoking the runner
|
|
||||||
- Errors/cancellation render final status while preserving resume tokens when known
|
|
||||||
|
|
||||||
### `telegram/bridge.py` - Telegram bridge loop
|
|
||||||
|
|
||||||
The Telegram adapter module containing:
|
|
||||||
|
|
||||||
| Component | Purpose |
|
|
||||||
|-----------|---------|
|
|
||||||
| `TelegramBridgeConfig` | Frozen dataclass holding bot + router + exec config |
|
|
||||||
| `TelegramTransport` | `BotClient` → `Transport` adapter |
|
|
||||||
| `TelegramPresenter` | `ProgressState` → `RenderedMessage` adapter |
|
|
||||||
| `poll_updates()` | Async generator that drains backlog, long-polls updates, filters messages |
|
|
||||||
| `run_main_loop()` | TaskGroup-based main loop that spawns per-message handlers |
|
|
||||||
| `_handle_cancel()` | `/cancel` routing |
|
|
||||||
|
|
||||||
**Key patterns:**
|
|
||||||
- Bridge schedules runs FIFO per thread to avoid concurrent progress messages; runner locks enforce per-thread serialization
|
|
||||||
- `/cancel` routes by reply-to progress message id (accepts extra text)
|
|
||||||
- `/{engine}` on the first line selects the engine for new threads
|
|
||||||
- Resume parsing polls all runners via `AutoRouter.resolve_resume()` and routes to the first match
|
|
||||||
- Bot command menu is synced on startup (`cancel` + engine + project commands, capped at 100)
|
|
||||||
|
|
||||||
### `transport.py` - Transport protocol
|
|
||||||
|
|
||||||
Defines `Transport`, `MessageRef`, `RenderedMessage`, and `SendOptions`.
|
|
||||||
|
|
||||||
### `presenter.py` - Presenter protocol
|
|
||||||
|
|
||||||
Defines a renderer that converts `ProgressState` into `RenderedMessage` outputs.
|
|
||||||
|
|
||||||
### `transport_runtime.py` - Transport runtime facade
|
|
||||||
|
|
||||||
Provides the `TransportRuntime` helper used by transport backends to resolve
|
|
||||||
messages, select runners, and format context without depending on internal types.
|
|
||||||
|
|
||||||
### `transports.py` - Transport backend loading
|
|
||||||
|
|
||||||
Defines the transport backend protocol and entrypoint-backed loading helpers.
|
|
||||||
|
|
||||||
### `config_migrations.py` - Config migrations
|
|
||||||
|
|
||||||
Applies one-time edits to on-disk config (e.g., legacy Telegram key migration) before
|
|
||||||
`TakopiSettings` validation runs.
|
|
||||||
|
|
||||||
### `telegram/backend.py` - Telegram transport backend
|
|
||||||
|
|
||||||
Adapter that validates Telegram config, runs onboarding, and builds/runs the Telegram bridge.
|
|
||||||
|
|
||||||
### `cli.py` - CLI entry point
|
|
||||||
|
|
||||||
| Component | Purpose |
|
|
||||||
|-----------|---------|
|
|
||||||
| `run()` / `main()` | Typer CLI entry points |
|
|
||||||
| `_run_auto_router()` | Loads settings, resolves transport + engine, builds router, delegates to transport backend |
|
|
||||||
|
|
||||||
### `progress.py` - Progress tracking
|
|
||||||
|
|
||||||
| Function/Class | Purpose |
|
|
||||||
|----------------|---------|
|
|
||||||
| `ProgressTracker` | Stateful reducer of takopi events into progress snapshots |
|
|
||||||
| `ProgressState` | Snapshot of actions, resume token, and engine metadata |
|
|
||||||
|
|
||||||
### `markdown.py` - Markdown formatting
|
|
||||||
|
|
||||||
| Function/Class | Purpose |
|
|
||||||
|----------------|---------|
|
|
||||||
| `MarkdownFormatter` | Converts `ProgressState` into MarkdownParts |
|
|
||||||
| `MarkdownPresenter` | `ProgressState` → `RenderedMessage` (markdown text) |
|
|
||||||
| `MarkdownParts` | Header/body/footer building blocks for markdown output |
|
|
||||||
| `assemble_markdown_parts()` | Join MarkdownParts into a single markdown string |
|
|
||||||
| `render_event_cli()` | Format a takopi event for CLI logs |
|
|
||||||
| `format_elapsed()` | Formats seconds as `Xh Ym`, `Xm Ys`, or `Xs` |
|
|
||||||
|
|
||||||
### `telegram/render.py` - Telegram markdown rendering
|
|
||||||
|
|
||||||
| Function/Class | Purpose |
|
|
||||||
|----------------|---------|
|
|
||||||
| `render_markdown()` | Markdown → Telegram text + entities |
|
|
||||||
| `trim_body()` | Trim body to 3500 chars (header/footer preserved) |
|
|
||||||
| `prepare_telegram()` | Trim + render Markdown parts for Telegram |
|
|
||||||
|
|
||||||
### `telegram/client.py` - Telegram API wrapper
|
|
||||||
|
|
||||||
| Component | Purpose |
|
|
||||||
|-----------|---------|
|
|
||||||
| `BotClient` | Protocol defining the bot client interface |
|
|
||||||
| `TelegramClient` | HTTP client for Telegram Bot API (send, edit, delete messages) |
|
|
||||||
|
|
||||||
See `docs/transports/telegram.md` for outbox behavior, rate limiting, and retry rules.
|
|
||||||
|
|
||||||
### `runners/codex.py` - Codex runner
|
|
||||||
|
|
||||||
| Component | Purpose |
|
|
||||||
|-----------|---------|
|
|
||||||
| `CodexRunner` | Spawns `codex exec --json`, streams JSONL, emits takopi events |
|
|
||||||
| `translate_codex_event()` | Normalizes Codex JSONL into the takopi event schema |
|
|
||||||
| `manage_subprocess()` | Starts a new process group and kills it on cancellation (POSIX) |
|
|
||||||
|
|
||||||
**Key patterns:**
|
|
||||||
- Per-resume locks (WeakValueDictionary) prevent concurrent resumes of the same session
|
|
||||||
- Event delivery uses a single internal queue to preserve order without per-event tasks
|
|
||||||
- Stderr is drained into a bounded tail (debug logging only)
|
|
||||||
- Translation errors abort the run; keep event normalization defensive
|
|
||||||
|
|
||||||
### `runners/pi.py` - Pi runner
|
|
||||||
|
|
||||||
| Component | Purpose |
|
|
||||||
|-----------|---------|
|
|
||||||
| `PiRunner` | Spawns `pi --print --mode json`, streams JSONL, emits takopi events |
|
|
||||||
| `translate_pi_event()` | Normalizes Pi JSONL into the takopi event schema |
|
|
||||||
|
|
||||||
### `model.py` / `runner.py` - Core domain types
|
|
||||||
|
|
||||||
| File | Purpose |
|
|
||||||
|------|---------|
|
|
||||||
| `model.py` | Domain types: resume tokens, actions, events, run result |
|
|
||||||
| `runner.py` | Runner protocol + event queue utilities |
|
|
||||||
|
|
||||||
### `backends.py` - Engine backend contracts
|
|
||||||
|
|
||||||
Defines `EngineBackend`, `SetupIssue`, and the `EngineConfig` type used by
|
|
||||||
runner modules.
|
|
||||||
|
|
||||||
### `plugins.py` - Entrypoint discovery
|
|
||||||
|
|
||||||
Centralizes plugin discovery and lazy loading:
|
|
||||||
|
|
||||||
- lists IDs without importing plugin modules
|
|
||||||
- loads a specific entrypoint on demand
|
|
||||||
- captures load errors for diagnostics
|
|
||||||
- filters by enabled list (distribution names)
|
|
||||||
|
|
||||||
### `commands.py` - Command backend loading
|
|
||||||
|
|
||||||
Defines the command backend protocol, command context/executor helpers, and
|
|
||||||
entrypoint-backed loading for slash-command plugins.
|
|
||||||
|
|
||||||
### `ids.py` - Plugin ID validation
|
|
||||||
|
|
||||||
Defines the shared ID regex used for plugin IDs and Telegram command names.
|
|
||||||
|
|
||||||
### `api.py` - Public plugin API
|
|
||||||
|
|
||||||
Re-exports the supported plugin surface from `takopi.api` (stable API boundary).
|
|
||||||
|
|
||||||
### `engines.py` - Engine backend discovery
|
|
||||||
|
|
||||||
Loads engine backends via entrypoints (`takopi.engine_backends`), with lazy loading
|
|
||||||
and enabled list support.
|
|
||||||
|
|
||||||
### `runners/` - Runner implementations
|
|
||||||
|
|
||||||
| File | Purpose |
|
|
||||||
|------|---------|
|
|
||||||
| `codex.py` | Codex runner (JSONL → takopi events) + per-resume locks |
|
|
||||||
| `claude.py` | Claude runner (JSONL → takopi events) + per-resume locks |
|
|
||||||
| `opencode.py` | OpenCode runner (JSONL → takopi events) + per-resume locks |
|
|
||||||
| `pi.py` | Pi runner (JSONL → takopi events) + per-resume locks |
|
|
||||||
| `mock.py` | Mock runner for tests/demos |
|
|
||||||
|
|
||||||
### `schemas/` - JSONL decoding schemas
|
|
||||||
|
|
||||||
Self-documenting msgspec schemas for decoding engine JSONL streams.
|
|
||||||
|
|
||||||
| File | Purpose |
|
|
||||||
|------|---------|
|
|
||||||
| `codex.py` | `codex exec --json` event schemas |
|
|
||||||
| `claude.py` | `claude -p --output-format stream-json --verbose` event schemas |
|
|
||||||
| `opencode.py` | `opencode run --format json` event schemas |
|
|
||||||
| `pi.py` | `pi --print --mode json` event schemas |
|
|
||||||
|
|
||||||
### `utils/` - Utility modules
|
|
||||||
|
|
||||||
| File | Purpose |
|
|
||||||
|------|---------|
|
|
||||||
| `paths.py` | `relativize_path()`, `relativize_command()` helpers |
|
|
||||||
| `streams.py` | `iter_bytes_lines()`, `drain_stderr()` for async stream handling |
|
|
||||||
| `subprocess.py` | `manage_subprocess()`, `terminate_process()`, `kill_process()` |
|
|
||||||
|
|
||||||
### `router.py` - Auto-router
|
|
||||||
|
|
||||||
| Component | Purpose |
|
|
||||||
|-----------|---------|
|
|
||||||
| `AutoRouter` | Resolves resume tokens by polling all runners, routes to matching engine |
|
|
||||||
| `RunnerEntry` | Dataclass holding runner + backend metadata |
|
|
||||||
| `RunnerUnavailableError` | Raised when requested engine is not available |
|
|
||||||
|
|
||||||
### `scheduler.py` - Thread scheduling
|
|
||||||
|
|
||||||
| Component | Purpose |
|
|
||||||
|-----------|---------|
|
|
||||||
| `ThreadScheduler` | Per-thread FIFO job queuing with serialization |
|
|
||||||
| `ThreadJob` | Dataclass representing a queued job |
|
|
||||||
| `note_thread_known()` | Registers a thread as busy when token discovered mid-run |
|
|
||||||
|
|
||||||
### `events.py` - Event factory
|
|
||||||
|
|
||||||
| Component | Purpose |
|
|
||||||
|-----------|---------|
|
|
||||||
| `EventFactory` | Helper class for creating takopi events with consistent engine/resume |
|
|
||||||
| Builder methods | `started()`, `action()`, `action_started()`, `action_updated()`, `action_completed()`, `completed()`, `completed_ok()`, `completed_error()` |
|
|
||||||
|
|
||||||
### `lockfile.py` - Single-instance enforcement
|
|
||||||
|
|
||||||
| Component | Purpose |
|
|
||||||
|-----------|---------|
|
|
||||||
| `acquire_lock()` | Acquire lock for bot token, returns `LockHandle` context manager |
|
|
||||||
| `LockHandle` | Context manager for automatic lock release |
|
|
||||||
| `LockInfo` | Dataclass with `pid` and `token_fingerprint` |
|
|
||||||
| `token_fingerprint()` | SHA256 hash of bot token, truncated to 10 chars |
|
|
||||||
|
|
||||||
### `backends_helpers.py` - Backend utilities
|
|
||||||
|
|
||||||
| Function | Purpose |
|
|
||||||
|----------|---------|
|
|
||||||
| `install_issue()` | Creates `SetupIssue` with install instructions for missing CLI |
|
|
||||||
|
|
||||||
### `config.py` - Shared configuration errors
|
|
||||||
|
|
||||||
```python
|
|
||||||
class ConfigError(RuntimeError): ...
|
|
||||||
```
|
|
||||||
|
|
||||||
### `settings.py` - Settings loading
|
|
||||||
|
|
||||||
```python
|
|
||||||
def load_settings(path: str | Path | None = None) -> tuple[TakopiSettings, Path]:
|
|
||||||
# Loads ~/.takopi/takopi.toml (TOML + env), validates via pydantic-settings
|
|
||||||
```
|
|
||||||
|
|
||||||
### `config_store.py` - Raw TOML read/write
|
|
||||||
|
|
||||||
```python
|
|
||||||
def read_raw_toml(path: Path) -> dict:
|
|
||||||
# Loads TOML for merge/update without clobbering extra sections
|
|
||||||
```
|
|
||||||
|
|
||||||
### `logging.py` - Secure logging setup
|
|
||||||
|
|
||||||
```python
|
|
||||||
def setup_logging(*, debug: bool = False) -> None:
|
|
||||||
# Configures structlog pipeline, redaction, and output formatting.
|
|
||||||
```
|
|
||||||
|
|
||||||
Environment flags:
|
|
||||||
|
|
||||||
- `TAKOPI_LOG_LEVEL` (default `info`, `debug` forces `debug`)
|
|
||||||
- `TAKOPI_LOG_FORMAT` (`console` or `json`)
|
|
||||||
- `TAKOPI_LOG_COLOR` (`1/true/yes/on` to force color, `0/false/no/off` to disable)
|
|
||||||
- `TAKOPI_LOG_FILE` (append JSON lines to a file)
|
|
||||||
- `TAKOPI_TRACE_PIPELINE` (log pipeline events at info instead of debug)
|
|
||||||
- `TAKOPI_NO_INTERACTIVE` (disable interactive prompts for CI/non-TTY environments)
|
|
||||||
- `PI_CODING_AGENT_DIR` (override Pi agent session directory base path)
|
|
||||||
|
|
||||||
CLI flag: `--debug` enables debug logging (overrides `TAKOPI_LOG_LEVEL`).
|
|
||||||
CLI flag: `--transport <id>` overrides the configured transport backend.
|
|
||||||
|
|
||||||
### `telegram/onboarding.py` - Setup validation
|
|
||||||
|
|
||||||
```python
|
|
||||||
def check_setup(backend: EngineBackend) -> SetupResult:
|
|
||||||
# Validates engine CLI on PATH and config file
|
|
||||||
|
|
||||||
def render_setup_guide(result: SetupResult):
|
|
||||||
# Displays rich panel with setup instructions
|
|
||||||
```
|
|
||||||
|
|
||||||
## Adding a Runner
|
|
||||||
|
|
||||||
See `docs/adding-a-runner.md` for the full guide and a worked example.
|
|
||||||
|
|
||||||
## Data Flow
|
|
||||||
|
|
||||||
### New Message Flow
|
|
||||||
|
|
||||||
```
|
|
||||||
Telegram Update
|
|
||||||
↓
|
|
||||||
telegram/bridge.poll_updates() drains backlog, long-polls, filters allowed chat ids
|
|
||||||
↓
|
|
||||||
telegram/bridge.run_main_loop() spawns tasks in TaskGroup
|
|
||||||
↓
|
|
||||||
router.resolve_resume(text, reply_text) → ResumeToken | None
|
|
||||||
↓
|
|
||||||
router.entry_for(resume_token) or router.entry_for_engine(override/default) → RunnerEntry
|
|
||||||
↓
|
|
||||||
runner_bridge.handle_message() spawned as task with selected runner
|
|
||||||
↓
|
|
||||||
Send initial progress message (silent)
|
|
||||||
↓
|
|
||||||
runner.run(prompt, resume_token)
|
|
||||||
├── Spawns engine subprocess (e.g., codex exec --json, pi --print --mode json)
|
|
||||||
├── Streams JSONL from stdout
|
|
||||||
├── Normalizes JSONL -> takopi events
|
|
||||||
├── Yields Takopi events (async iterator)
|
|
||||||
│ ↓
|
|
||||||
│ ProgressTracker.note_event()
|
|
||||||
│ ↓
|
|
||||||
│ ProgressEdits best-effort transport.edit(wait=False)
|
|
||||||
└── Ends with completed(resume, ok, answer)
|
|
||||||
↓
|
|
||||||
render_final() with resume line (runner-formatted)
|
|
||||||
↓
|
|
||||||
transport.send()/edit() final message, delete progress if needed
|
|
||||||
```
|
|
||||||
|
|
||||||
### Resume Flow
|
|
||||||
|
|
||||||
Same as above; auto-router polls all runners to extract resume tokens:
|
|
||||||
- Router returns first matching token (e.g. `` `claude --resume <id>` `` routes to Claude, `` `pi --session <id>` `` routes to Pi)
|
|
||||||
- Selected runner spawns with resume (e.g. `codex exec --json resume <token> -`, `pi --print --mode json --session <path> <prompt>`)
|
|
||||||
- Per-token lock serializes concurrent resumes on the same thread
|
|
||||||
|
|
||||||
## Error Handling
|
|
||||||
|
|
||||||
| Scenario | Behavior |
|
|
||||||
|----------|----------|
|
|
||||||
| `codex exec` fails (rc != 0) | Emits a warning `action` plus `completed(ok=false, error=...)` |
|
|
||||||
| `pi` fails (rc != 0) | Emits a warning `action` plus `completed(ok=false, error=...)` |
|
|
||||||
| Telegram API error | Logged, edit skipped (progress continues) |
|
|
||||||
| Cancellation | Cancel scope terminates the process group (POSIX) and renders `cancelled` |
|
|
||||||
| Errors in handler | Final render uses `status=error` and preserves resume tokens when known |
|
|
||||||
| No agent_message (empty answer) | Final shows `error` status |
|
|
||||||
|
|||||||
@@ -0,0 +1,43 @@
|
|||||||
|
# Explanation
|
||||||
|
|
||||||
|
Explanation docs answer **“how does this work?”** and **“why is it designed this way?”**
|
||||||
|
|
||||||
|
If you want step-by-step instructions, go to **[Tutorials](../tutorials/index.md)**.
|
||||||
|
If you want exact options and contracts, go to **[Reference](../reference/index.md)**.
|
||||||
|
|
||||||
|
## How Takopi works end-to-end
|
||||||
|
|
||||||
|
- Incoming Telegram message → resolve context (project/branch) → resolve resume token → select runner → stream events → render progress → send final + resume line.
|
||||||
|
|
||||||
|
Start here:
|
||||||
|
|
||||||
|
- [Architecture](architecture.md)
|
||||||
|
|
||||||
|
## Routing, sessions, and continuation
|
||||||
|
|
||||||
|
Takopi is stateless by default, but can provide “continuation” in multiple ways:
|
||||||
|
|
||||||
|
- reply-to-continue (always available)
|
||||||
|
- per-topic resume (Telegram forum topics)
|
||||||
|
- per-chat sessions (auto-resume)
|
||||||
|
|
||||||
|
- [Routing & sessions](routing-and-sessions.md)
|
||||||
|
|
||||||
|
## Plugins and extensibility
|
||||||
|
|
||||||
|
Takopi uses entrypoint-based plugins with lazy discovery so broken plugins don’t brick the CLI.
|
||||||
|
|
||||||
|
- [Plugin system](plugin-system.md)
|
||||||
|
|
||||||
|
## Codebase orientation
|
||||||
|
|
||||||
|
If you’re making changes, this is the “map of the territory”:
|
||||||
|
|
||||||
|
- [Module map](module-map.md)
|
||||||
|
|
||||||
|
## Where to look for hard rules
|
||||||
|
|
||||||
|
Explanation pages describe intent and tradeoffs. The *hard requirements* live in:
|
||||||
|
|
||||||
|
- [Reference: Specification](../reference/specification.md)
|
||||||
|
- [Reference: Plugin API](../reference/plugin-api.md)
|
||||||
@@ -0,0 +1,81 @@
|
|||||||
|
# Module map
|
||||||
|
|
||||||
|
This page is a high-level map of Takopi’s internal modules: what they do and how they fit together.
|
||||||
|
|
||||||
|
## Entry points
|
||||||
|
|
||||||
|
| Module | Responsibility |
|
||||||
|
|--------|----------------|
|
||||||
|
| `cli.py` | Typer CLI entry point; loads settings, selects engine/transport, runs the transport backend. |
|
||||||
|
| `telegram/backend.py` | Telegram transport backend: validates config, runs onboarding, builds and runs the Telegram bridge. |
|
||||||
|
|
||||||
|
## Orchestration and routing
|
||||||
|
|
||||||
|
| Module | Responsibility |
|
||||||
|
|--------|----------------|
|
||||||
|
| `runner_bridge.py` | Transport-agnostic orchestration: per-message handler, progress updates, final render, cancellation, resume coordination. |
|
||||||
|
| `router.py` | Auto-router: resolves resume tokens by polling runners; selects a runner for a message. |
|
||||||
|
| `scheduler.py` | Per-thread FIFO job queueing with serialization. |
|
||||||
|
| `transport_runtime.py` | Facade used by transports and commands to resolve messages and runners without importing internal router/project types. |
|
||||||
|
|
||||||
|
## Domain model and events
|
||||||
|
|
||||||
|
| Module | Responsibility |
|
||||||
|
|--------|----------------|
|
||||||
|
| `model.py` | Domain types: resume tokens, events, actions, run results. |
|
||||||
|
| `runner.py` | Runner protocol and event queue utilities. |
|
||||||
|
| `events.py` | Event factory helpers for building Takopi events consistently. |
|
||||||
|
|
||||||
|
## Rendering and progress
|
||||||
|
|
||||||
|
| Module | Responsibility |
|
||||||
|
|--------|----------------|
|
||||||
|
| `progress.py` | Progress tracking: reduces takopi events into progress snapshots. |
|
||||||
|
| `markdown.py` | Markdown formatting for progress/final messages; includes helpers like elapsed formatting. |
|
||||||
|
| `presenter.py` | Presenter protocol: converts `ProgressState` into transport-specific messages. |
|
||||||
|
| `transport.py` | Transport protocol: send/edit/delete abstractions and message reference types. |
|
||||||
|
|
||||||
|
## Telegram implementation
|
||||||
|
|
||||||
|
| Module | Responsibility |
|
||||||
|
|--------|----------------|
|
||||||
|
| `telegram/bridge.py` | Telegram bridge loop: polls updates, filters messages, dispatches handlers, coordinates cancellation. |
|
||||||
|
| `telegram/client.py` | Telegram API wrapper with retry/outbox semantics. |
|
||||||
|
| `telegram/render.py` | Telegram markdown rendering and trimming. |
|
||||||
|
| `telegram/onboarding.py` | Interactive setup and setup validation UX. |
|
||||||
|
| `telegram/commands/*` | In-chat command handlers (`/agent`, `/file`, `/topic`, `/ctx`, `/new`, …). |
|
||||||
|
|
||||||
|
## Plugins
|
||||||
|
|
||||||
|
| Module | Responsibility |
|
||||||
|
|--------|----------------|
|
||||||
|
| `plugins.py` | Entrypoint discovery and lazy loading (capture load errors, filter by enabled list). |
|
||||||
|
| `engines.py` | Engine backend discovery and loading via entrypoints. |
|
||||||
|
| `transports.py` | Transport backend discovery and loading via entrypoints. |
|
||||||
|
| `commands.py` | Command backend discovery and loading via entrypoints; command execution helpers. |
|
||||||
|
| `ids.py` | Shared ID regex and collision checks for plugin ids and Telegram command names. |
|
||||||
|
| `api.py` | Public plugin API boundary (`takopi.api` re-exports). |
|
||||||
|
|
||||||
|
## Runners and schemas
|
||||||
|
|
||||||
|
| Module | Responsibility |
|
||||||
|
|--------|----------------|
|
||||||
|
| `runners/*` | Engine runner implementations (Codex, Claude, OpenCode, Pi). |
|
||||||
|
| `schemas/*` | msgspec schemas / decoders for engine JSONL streams. |
|
||||||
|
|
||||||
|
## Configuration and persistence
|
||||||
|
|
||||||
|
| Module | Responsibility |
|
||||||
|
|--------|----------------|
|
||||||
|
| `settings.py` | Loads `takopi.toml` (TOML + env), validates with pydantic-settings. |
|
||||||
|
| `config_store.py` | Raw TOML read/write (merge/update without clobbering extra sections). |
|
||||||
|
| `config_migrations.py` | One-time edits to on-disk config (e.g. legacy Telegram key migration). |
|
||||||
|
|
||||||
|
## Utilities
|
||||||
|
|
||||||
|
| Module | Responsibility |
|
||||||
|
|--------|----------------|
|
||||||
|
| `utils/paths.py` | Path/command relativization helpers. |
|
||||||
|
| `utils/streams.py` | Async stream helpers (`iter_bytes_lines`, stderr draining). |
|
||||||
|
| `utils/subprocess.py` | Subprocess management helpers (terminate/kill best-effort). |
|
||||||
|
|
||||||
@@ -0,0 +1,88 @@
|
|||||||
|
# Plugin system
|
||||||
|
|
||||||
|
Takopi uses Python entrypoints to extend engines, transports, and commands.
|
||||||
|
|
||||||
|
## Why entrypoints
|
||||||
|
|
||||||
|
Entrypoints let Takopi discover plugins without hard dependencies on plugin packages.
|
||||||
|
Installed distributions declare what they provide, and Takopi can list and load them at runtime.
|
||||||
|
|
||||||
|
This makes it possible to:
|
||||||
|
|
||||||
|
- Add new engines/transports/commands without changing Takopi itself.
|
||||||
|
- Ship plugins independently.
|
||||||
|
- Keep the core CLI small.
|
||||||
|
|
||||||
|
## Why discovery is lazy
|
||||||
|
|
||||||
|
Takopi lists plugin IDs **without importing plugin code**, then imports a plugin only when:
|
||||||
|
|
||||||
|
- it is selected by routing (engine/transport), or
|
||||||
|
- it is invoked as a command, or
|
||||||
|
- you explicitly request loading via `takopi plugins --load`.
|
||||||
|
|
||||||
|
This keeps `takopi --help` fast and prevents a broken third-party plugin from bricking the CLI.
|
||||||
|
|
||||||
|
## Entrypoint rules (what Takopi expects)
|
||||||
|
|
||||||
|
Takopi uses three entrypoint groups:
|
||||||
|
|
||||||
|
```toml
|
||||||
|
[project.entry-points."takopi.engine_backends"]
|
||||||
|
myengine = "myengine.backend:BACKEND"
|
||||||
|
|
||||||
|
[project.entry-points."takopi.transport_backends"]
|
||||||
|
mytransport = "mytransport.backend:BACKEND"
|
||||||
|
|
||||||
|
[project.entry-points."takopi.command_backends"]
|
||||||
|
mycommand = "mycommand.backend:BACKEND"
|
||||||
|
```
|
||||||
|
|
||||||
|
Rules:
|
||||||
|
|
||||||
|
- The entrypoint **name** is the plugin id.
|
||||||
|
- The entrypoint value must resolve to a backend object:
|
||||||
|
- engine backend: `EngineBackend`
|
||||||
|
- transport backend: `TransportBackend`
|
||||||
|
- command backend: `CommandBackend`
|
||||||
|
- The backend object must have `id == entrypoint name`.
|
||||||
|
|
||||||
|
## Why there is an enabled list
|
||||||
|
|
||||||
|
Plugin visibility can be restricted via:
|
||||||
|
|
||||||
|
```toml
|
||||||
|
[plugins]
|
||||||
|
enabled = ["takopi-engine-acme", "takopi-transport-slack"]
|
||||||
|
```
|
||||||
|
|
||||||
|
When set, Takopi filters by **distribution name** (package metadata), not by entrypoint name.
|
||||||
|
This lets you:
|
||||||
|
|
||||||
|
- ship multiple entrypoints from one distribution, and
|
||||||
|
- enable/disable whole plugin packages predictably.
|
||||||
|
|
||||||
|
## IDs and collisions
|
||||||
|
|
||||||
|
Entrypoint names become plugin IDs and appear in user-facing surfaces (CLI subcommands, Telegram commands, `/engine` directives).
|
||||||
|
Takopi validates IDs and rejects collisions with reserved names.
|
||||||
|
|
||||||
|
Plugin IDs must match:
|
||||||
|
|
||||||
|
```
|
||||||
|
^[a-z0-9_]{1,32}$
|
||||||
|
```
|
||||||
|
|
||||||
|
Reserved IDs include core chat and CLI command names such as `cancel`, `init`, and `plugins`.
|
||||||
|
|
||||||
|
## How to debug discovery and loading
|
||||||
|
|
||||||
|
```sh
|
||||||
|
takopi plugins
|
||||||
|
takopi plugins --load
|
||||||
|
```
|
||||||
|
|
||||||
|
## Related
|
||||||
|
|
||||||
|
- [Write a plugin](../how-to/write-a-plugin.md)
|
||||||
|
- [Plugin API reference](../reference/plugin-api.md)
|
||||||
@@ -0,0 +1,44 @@
|
|||||||
|
# Routing & sessions
|
||||||
|
|
||||||
|
Takopi is **stateless by default**: each message starts a new engine session unless a resume token is present.
|
||||||
|
|
||||||
|
## Continuation (how threads persist)
|
||||||
|
|
||||||
|
Takopi supports three ways to continue a thread:
|
||||||
|
|
||||||
|
1. **Reply-to-continue** (always available)
|
||||||
|
- Reply to any bot message that contains a resume line in the footer.
|
||||||
|
- Takopi extracts the resume token and resumes that engine thread.
|
||||||
|
2. **Forum topics** (optional)
|
||||||
|
- Topics can store resume tokens per topic and auto-resume new messages in that topic.
|
||||||
|
- Topic state is stored in `telegram_topics_state.json`.
|
||||||
|
- Reset with `/new`.
|
||||||
|
3. **Chat sessions** (optional)
|
||||||
|
- Set `session_mode = "chat"` to store one resume token per chat (per sender in groups).
|
||||||
|
- State is stored in `telegram_chat_sessions_state.json`.
|
||||||
|
- Reset with `/new`.
|
||||||
|
|
||||||
|
Reply-to-continue works even if topics or chat sessions are enabled.
|
||||||
|
|
||||||
|
## Routing (how Takopi picks a runner)
|
||||||
|
|
||||||
|
For each message, Takopi:
|
||||||
|
|
||||||
|
- parses directive prefixes (`/engine`, `/project`, `@branch`) from the first non-empty line
|
||||||
|
- attempts to extract a resume token by polling available runners
|
||||||
|
- if a resume token is found, routes to the matching runner; otherwise uses the configured default engine
|
||||||
|
|
||||||
|
## Serialization (why you don’t get overlapping runs)
|
||||||
|
|
||||||
|
Takopi allows parallel runs across **different threads**, but enforces serialization within a thread:
|
||||||
|
|
||||||
|
- Telegram side: jobs are queued FIFO per thread.
|
||||||
|
- Runner side: runners enforce per-resume-token locks (so the same session can’t be resumed concurrently).
|
||||||
|
|
||||||
|
The precise invariants are specified in the [Specification](../reference/specification.md).
|
||||||
|
|
||||||
|
## Related
|
||||||
|
|
||||||
|
- [Commands & directives](../reference/commands-and-directives.md)
|
||||||
|
- [Context resolution](../reference/context-resolution.md)
|
||||||
|
|
||||||
@@ -0,0 +1,32 @@
|
|||||||
|
# Dev setup
|
||||||
|
|
||||||
|
Set up Takopi for local development and run the checks.
|
||||||
|
|
||||||
|
## Clone and run
|
||||||
|
|
||||||
|
```bash
|
||||||
|
git clone https://github.com/banteg/takopi
|
||||||
|
cd takopi
|
||||||
|
|
||||||
|
# Run directly with uv (installs deps automatically)
|
||||||
|
uv run takopi --help
|
||||||
|
```
|
||||||
|
|
||||||
|
## Install locally (optional)
|
||||||
|
|
||||||
|
```bash
|
||||||
|
uv tool install .
|
||||||
|
takopi --help
|
||||||
|
```
|
||||||
|
|
||||||
|
## Run checks
|
||||||
|
|
||||||
|
```bash
|
||||||
|
uv run pytest
|
||||||
|
uv run ruff check src tests
|
||||||
|
uv run ty check .
|
||||||
|
|
||||||
|
# Or all at once
|
||||||
|
just check
|
||||||
|
```
|
||||||
|
|
||||||
@@ -0,0 +1,59 @@
|
|||||||
|
# File transfer
|
||||||
|
|
||||||
|
Upload files into the active repo/worktree or fetch files back into Telegram.
|
||||||
|
|
||||||
|
## Enable file transfer
|
||||||
|
|
||||||
|
```toml
|
||||||
|
[transports.telegram.files]
|
||||||
|
enabled = true
|
||||||
|
auto_put = true
|
||||||
|
auto_put_mode = "upload" # upload | prompt
|
||||||
|
uploads_dir = "incoming"
|
||||||
|
allowed_user_ids = [123456789]
|
||||||
|
deny_globs = [".git/**", ".env", ".envrc", "**/*.pem", "**/.ssh/**"]
|
||||||
|
```
|
||||||
|
|
||||||
|
Notes:
|
||||||
|
|
||||||
|
- File transfer is **disabled by default**.
|
||||||
|
- If `allowed_user_ids` is empty, private chats are allowed and group usage requires admin privileges.
|
||||||
|
|
||||||
|
## Upload a file (`/file put`)
|
||||||
|
|
||||||
|
Send a document with a caption:
|
||||||
|
|
||||||
|
```
|
||||||
|
/file put <path>
|
||||||
|
```
|
||||||
|
|
||||||
|
Examples:
|
||||||
|
|
||||||
|
```
|
||||||
|
/file put docs/spec.pdf
|
||||||
|
/file put /happy-gadgets @feat/camera assets/logo.png
|
||||||
|
```
|
||||||
|
|
||||||
|
If you send a file **without a caption**, Takopi saves it to `incoming/<original_filename>`.
|
||||||
|
|
||||||
|
Use `--force` to overwrite:
|
||||||
|
|
||||||
|
```
|
||||||
|
/file put --force docs/spec.pdf
|
||||||
|
```
|
||||||
|
|
||||||
|
## Fetch a file (`/file get`)
|
||||||
|
|
||||||
|
Send:
|
||||||
|
|
||||||
|
```
|
||||||
|
/file get <path>
|
||||||
|
```
|
||||||
|
|
||||||
|
Directories are zipped automatically.
|
||||||
|
|
||||||
|
## Related
|
||||||
|
|
||||||
|
- [Commands & directives](../reference/commands-and-directives.md)
|
||||||
|
- [Config reference](../reference/config.md)
|
||||||
|
|
||||||
@@ -0,0 +1,37 @@
|
|||||||
|
# How-to guides
|
||||||
|
|
||||||
|
How-to guides are **goal-oriented recipes**. Pick the task you’re trying to accomplish and follow the steps.
|
||||||
|
|
||||||
|
If you’re learning from scratch, start with **[Tutorials](../tutorials/index.md)**.
|
||||||
|
If you need exact options and defaults, use **[Reference](../reference/index.md)**.
|
||||||
|
|
||||||
|
## Daily use
|
||||||
|
|
||||||
|
- [Switch engines](switch-engines.md) (`/codex`, `/claude`, `/opencode`, `/pi`)
|
||||||
|
- [Projects](projects.md) (register repos + run from anywhere)
|
||||||
|
- [Worktrees](worktrees.md) (run work on `@branch` without switching your main checkout)
|
||||||
|
- [Route by chat](route-by-chat.md) (dedicated chats per project)
|
||||||
|
- [Topics](topics.md) (forum threads bound to repo/branch + auto-resume)
|
||||||
|
- [Chat sessions](topics.md#chat-sessions) (auto-resume without replying)
|
||||||
|
|
||||||
|
## Messaging extras
|
||||||
|
|
||||||
|
- [Voice notes](voice-notes.md) (transcribe and run)
|
||||||
|
- [File transfer](file-transfer.md) (`/file put` and `/file get`)
|
||||||
|
- [Schedule tasks](schedule-tasks.md) (Telegram scheduled messages)
|
||||||
|
|
||||||
|
## Extending Takopi
|
||||||
|
|
||||||
|
- [Write a plugin](write-a-plugin.md) (engines, transports, commands)
|
||||||
|
- [Add a runner](add-a-runner.md) (implement a new engine backend)
|
||||||
|
- [Dev setup](dev-setup.md) (run from source, tests, linting, type checks)
|
||||||
|
|
||||||
|
## Debugging and operations
|
||||||
|
|
||||||
|
- [Troubleshooting](troubleshooting.md) (`--debug`, common gotchas, “why didn’t it route?”)
|
||||||
|
|
||||||
|
## Not sure where to go?
|
||||||
|
|
||||||
|
- If your question starts with “**How do I…**” → you’re in the right place.
|
||||||
|
- If your question starts with “**What are the exact options / defaults?**” → go to **[Reference](../reference/index.md)**.
|
||||||
|
- If your question starts with “**Why is it designed this way?**” → go to **[Explanation](../explanation/index.md)**.
|
||||||
@@ -0,0 +1,57 @@
|
|||||||
|
# Projects
|
||||||
|
|
||||||
|
Projects let you route messages to repos from anywhere using `/alias`.
|
||||||
|
|
||||||
|
## Register a repo as a project
|
||||||
|
|
||||||
|
```sh
|
||||||
|
cd ~/dev/happy-gadgets
|
||||||
|
takopi init happy-gadgets
|
||||||
|
```
|
||||||
|
|
||||||
|
This adds a project to your config:
|
||||||
|
|
||||||
|
```toml
|
||||||
|
[projects.happy-gadgets]
|
||||||
|
path = "~/dev/happy-gadgets"
|
||||||
|
```
|
||||||
|
|
||||||
|
## Target a project from chat
|
||||||
|
|
||||||
|
Send:
|
||||||
|
|
||||||
|
```
|
||||||
|
/happy-gadgets pinky-link two threads
|
||||||
|
```
|
||||||
|
|
||||||
|
## Project-specific settings
|
||||||
|
|
||||||
|
Projects can override global defaults:
|
||||||
|
|
||||||
|
```toml
|
||||||
|
[projects.happy-gadgets]
|
||||||
|
path = "~/dev/happy-gadgets"
|
||||||
|
default_engine = "claude"
|
||||||
|
worktrees_dir = ".worktrees"
|
||||||
|
worktree_base = "master"
|
||||||
|
```
|
||||||
|
|
||||||
|
If you expect to edit config while Takopi is running, enable hot reload:
|
||||||
|
|
||||||
|
```toml
|
||||||
|
watch_config = true
|
||||||
|
```
|
||||||
|
|
||||||
|
## Set a default project
|
||||||
|
|
||||||
|
If you mostly work in one repo:
|
||||||
|
|
||||||
|
```toml
|
||||||
|
default_project = "happy-gadgets"
|
||||||
|
```
|
||||||
|
|
||||||
|
## Related
|
||||||
|
|
||||||
|
- [Context resolution](../reference/context-resolution.md)
|
||||||
|
- [Worktrees](worktrees.md)
|
||||||
|
|
||||||
@@ -0,0 +1,39 @@
|
|||||||
|
# Route by chat
|
||||||
|
|
||||||
|
Bind a Telegram chat to a project so messages in that chat automatically route to the right repo.
|
||||||
|
|
||||||
|
## Capture a chat id and save it to a project
|
||||||
|
|
||||||
|
Run:
|
||||||
|
|
||||||
|
```sh
|
||||||
|
takopi chat-id --project happy-gadgets
|
||||||
|
```
|
||||||
|
|
||||||
|
Then send any message in the target chat. Takopi captures the `chat_id` and updates your config:
|
||||||
|
|
||||||
|
```toml
|
||||||
|
[projects.happy-gadgets]
|
||||||
|
path = "~/dev/happy-gadgets"
|
||||||
|
chat_id = -1001234567890
|
||||||
|
```
|
||||||
|
|
||||||
|
Messages from that chat now default to the project.
|
||||||
|
|
||||||
|
## Rules for chat ids
|
||||||
|
|
||||||
|
- Each `projects.*.chat_id` must be unique.
|
||||||
|
- A project `chat_id` must not match `transports.telegram.chat_id`.
|
||||||
|
- Telegram uses positive IDs for private chats and negative IDs for groups/supergroups.
|
||||||
|
|
||||||
|
## Capture a chat id without saving
|
||||||
|
|
||||||
|
```sh
|
||||||
|
takopi chat-id
|
||||||
|
```
|
||||||
|
|
||||||
|
## Related
|
||||||
|
|
||||||
|
- [Topics](topics.md)
|
||||||
|
- [Context resolution](../reference/context-resolution.md)
|
||||||
|
|
||||||
@@ -0,0 +1,7 @@
|
|||||||
|
# Schedule tasks
|
||||||
|
|
||||||
|
Telegram’s native message scheduling works with Takopi.
|
||||||
|
|
||||||
|
In Telegram, long-press the send button and choose **Schedule Message** to run tasks at a specific time.
|
||||||
|
You can also set up recurring schedules (daily/weekly) for automated workflows.
|
||||||
|
|
||||||
@@ -0,0 +1,43 @@
|
|||||||
|
# Switch engines
|
||||||
|
|
||||||
|
Run a one-off message on a specific engine, or set a persistent default for a chat/topic.
|
||||||
|
|
||||||
|
## Use an engine for one message
|
||||||
|
|
||||||
|
Prefix the first non-empty line with an engine directive:
|
||||||
|
|
||||||
|
```
|
||||||
|
/codex hard reset the timeline
|
||||||
|
/claude shrink and store artifacts forever
|
||||||
|
/opencode hide their paper until they reply
|
||||||
|
/pi render a diorama of this timeline
|
||||||
|
```
|
||||||
|
|
||||||
|
Directives are only parsed at the start of the first non-empty line.
|
||||||
|
|
||||||
|
## Set a default engine for the current scope
|
||||||
|
|
||||||
|
Use `/agent`:
|
||||||
|
|
||||||
|
```
|
||||||
|
/agent
|
||||||
|
/agent set claude
|
||||||
|
/agent clear
|
||||||
|
```
|
||||||
|
|
||||||
|
- Inside a forum topic, `/agent set` affects that topic.
|
||||||
|
- In normal chats, it affects the whole chat.
|
||||||
|
- In group chats, only admins can change defaults.
|
||||||
|
|
||||||
|
Selection precedence (highest to lowest): resume token → `/engine` directive → topic default → chat default → project default → global default.
|
||||||
|
|
||||||
|
## Engine installation
|
||||||
|
|
||||||
|
Takopi shells out to engine CLIs. Install them and make sure they’re on your `PATH`
|
||||||
|
(`codex`, `claude`, `opencode`, `pi`). Authentication is handled by each CLI.
|
||||||
|
|
||||||
|
## Related
|
||||||
|
|
||||||
|
- [Commands & directives](../reference/commands-and-directives.md)
|
||||||
|
- [Config reference](../reference/config.md)
|
||||||
|
|
||||||
@@ -0,0 +1,53 @@
|
|||||||
|
# Topics
|
||||||
|
|
||||||
|
Topics bind Telegram forum threads to a specific project/branch context. They can also store resume tokens and a default agent per topic.
|
||||||
|
|
||||||
|
## Enable topics
|
||||||
|
|
||||||
|
```toml
|
||||||
|
[transports.telegram.topics]
|
||||||
|
enabled = true
|
||||||
|
scope = "auto" # auto | main | projects | all
|
||||||
|
```
|
||||||
|
|
||||||
|
Your bot needs **Manage Topics** permission in the group.
|
||||||
|
|
||||||
|
If any `projects.<alias>.chat_id` are configured, topics are managed in those project chats; otherwise topics are managed in the main chat.
|
||||||
|
|
||||||
|
## Topic commands
|
||||||
|
|
||||||
|
Run these inside a topic thread:
|
||||||
|
|
||||||
|
| Command | Description |
|
||||||
|
|---------|-------------|
|
||||||
|
| `/topic <project> @branch` | Create a new topic bound to context |
|
||||||
|
| `/ctx` | Show the current binding |
|
||||||
|
| `/ctx set <project> @branch` | Update the binding |
|
||||||
|
| `/ctx clear` | Remove the binding |
|
||||||
|
| `/new` | Clear stored sessions for this topic |
|
||||||
|
|
||||||
|
In project chats, omit the project: `/topic @branch` or `/ctx set @branch`.
|
||||||
|
|
||||||
|
## Chat sessions
|
||||||
|
|
||||||
|
Chat sessions store one resume token per chat (per sender in groups) so new messages can auto-resume without replying.
|
||||||
|
|
||||||
|
Enable:
|
||||||
|
|
||||||
|
```toml
|
||||||
|
[transports.telegram]
|
||||||
|
session_mode = "chat" # stateless | chat
|
||||||
|
```
|
||||||
|
|
||||||
|
Reset the stored session with `/new`.
|
||||||
|
|
||||||
|
## State files
|
||||||
|
|
||||||
|
- Topic state: `telegram_topics_state.json`
|
||||||
|
- Chat sessions state: `telegram_chat_sessions_state.json`
|
||||||
|
- Chat defaults (e.g. `/agent`): `telegram_chat_prefs_state.json`
|
||||||
|
|
||||||
|
## Related
|
||||||
|
|
||||||
|
- [Switch engines](switch-engines.md)
|
||||||
|
- [Commands & directives](../reference/commands-and-directives.md)
|
||||||
@@ -0,0 +1,10 @@
|
|||||||
|
# Troubleshooting
|
||||||
|
|
||||||
|
If something isn’t working, rerun with debug logging enabled:
|
||||||
|
|
||||||
|
```sh
|
||||||
|
takopi --debug
|
||||||
|
```
|
||||||
|
|
||||||
|
Then check `debug.log` for errors and include it when reporting issues.
|
||||||
|
|
||||||
@@ -0,0 +1,27 @@
|
|||||||
|
# Voice notes
|
||||||
|
|
||||||
|
Enable transcription so voice notes become normal text runs.
|
||||||
|
|
||||||
|
## Enable transcription
|
||||||
|
|
||||||
|
```toml
|
||||||
|
[transports.telegram]
|
||||||
|
voice_transcription = true
|
||||||
|
voice_transcription_model = "gpt-4o-mini-transcribe" # optional
|
||||||
|
```
|
||||||
|
|
||||||
|
Set `OPENAI_API_KEY` in your environment (uses OpenAI’s transcription API).
|
||||||
|
|
||||||
|
To use a local OpenAI-compatible Whisper server, also set `OPENAI_BASE_URL`
|
||||||
|
(for example, `http://localhost:8000/v1`) and a dummy `OPENAI_API_KEY` if your server ignores it.
|
||||||
|
If your server requires a specific model name, set `voice_transcription_model` (for example, `whisper-1`).
|
||||||
|
|
||||||
|
## Behavior
|
||||||
|
|
||||||
|
When you send a voice note, Takopi transcribes it and runs the result as a normal text message.
|
||||||
|
If transcription fails, you’ll get an error message and the run is skipped.
|
||||||
|
|
||||||
|
## Related
|
||||||
|
|
||||||
|
- [Config reference](../reference/config.md)
|
||||||
|
|
||||||
@@ -0,0 +1,42 @@
|
|||||||
|
# Worktrees
|
||||||
|
|
||||||
|
Use `@branch` to run tasks in a dedicated git worktree for that branch.
|
||||||
|
|
||||||
|
## Enable worktree-based runs for a project
|
||||||
|
|
||||||
|
Add a `worktrees_dir` (and optionally a base branch) to the project:
|
||||||
|
|
||||||
|
```toml
|
||||||
|
[projects.happy-gadgets]
|
||||||
|
path = "~/dev/happy-gadgets"
|
||||||
|
worktrees_dir = ".worktrees" # relative to project path
|
||||||
|
worktree_base = "master" # base branch for new worktrees
|
||||||
|
```
|
||||||
|
|
||||||
|
## Run in a branch worktree
|
||||||
|
|
||||||
|
Send a message like:
|
||||||
|
|
||||||
|
```
|
||||||
|
/happy-gadgets @feat/memory-box freeze artifacts forever
|
||||||
|
```
|
||||||
|
|
||||||
|
## Ignore `.worktrees/` in git status
|
||||||
|
|
||||||
|
If you use the default `.worktrees/` directory inside the repo, add it to a gitignore.
|
||||||
|
One option is a global ignore:
|
||||||
|
|
||||||
|
```sh
|
||||||
|
git config --global core.excludesfile ~/.config/git/ignore
|
||||||
|
echo ".worktrees/" >> ~/.config/git/ignore
|
||||||
|
```
|
||||||
|
|
||||||
|
## Context persistence
|
||||||
|
|
||||||
|
When project/worktree context is active, Takopi includes a `ctx:` footer in messages.
|
||||||
|
When you reply, this context carries forward (you usually don’t need to repeat `/project @branch`).
|
||||||
|
|
||||||
|
## Related
|
||||||
|
|
||||||
|
- [Context resolution](../reference/context-resolution.md)
|
||||||
|
|
||||||
@@ -0,0 +1,122 @@
|
|||||||
|
# Write a plugin
|
||||||
|
|
||||||
|
Takopi supports entrypoint-based plugins for engines, transports, and commands.
|
||||||
|
|
||||||
|
## Checklist
|
||||||
|
|
||||||
|
1. Pick a plugin id (must match `^[a-z0-9_]{1,32}$`).
|
||||||
|
2. Add a Python entrypoint in your package’s `pyproject.toml`.
|
||||||
|
3. Implement a backend object (`BACKEND`) with `id == entrypoint name`.
|
||||||
|
4. Install your package and validate with `takopi plugins --load`.
|
||||||
|
|
||||||
|
## Entrypoint groups
|
||||||
|
|
||||||
|
Takopi uses three entrypoint groups:
|
||||||
|
|
||||||
|
```toml
|
||||||
|
[project.entry-points."takopi.engine_backends"]
|
||||||
|
myengine = "myengine.backend:BACKEND"
|
||||||
|
|
||||||
|
[project.entry-points."takopi.transport_backends"]
|
||||||
|
mytransport = "mytransport.backend:BACKEND"
|
||||||
|
|
||||||
|
[project.entry-points."takopi.command_backends"]
|
||||||
|
mycommand = "mycommand.backend:BACKEND"
|
||||||
|
```
|
||||||
|
|
||||||
|
## Engine backend plugin (runner)
|
||||||
|
|
||||||
|
Minimal example:
|
||||||
|
|
||||||
|
```py
|
||||||
|
# myengine/backend.py
|
||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
from pathlib import Path
|
||||||
|
|
||||||
|
from takopi.api import EngineBackend, EngineConfig, Runner
|
||||||
|
|
||||||
|
|
||||||
|
def build_runner(config: EngineConfig, config_path: Path) -> Runner:
|
||||||
|
_ = config_path
|
||||||
|
return MyEngineRunner(config)
|
||||||
|
|
||||||
|
|
||||||
|
BACKEND = EngineBackend(
|
||||||
|
id="myengine",
|
||||||
|
build_runner=build_runner,
|
||||||
|
cli_cmd="myengine",
|
||||||
|
install_cmd="pip install myengine",
|
||||||
|
)
|
||||||
|
```
|
||||||
|
|
||||||
|
Engine config is a raw table in `takopi.toml`:
|
||||||
|
|
||||||
|
```toml
|
||||||
|
[myengine]
|
||||||
|
model = "..."
|
||||||
|
```
|
||||||
|
|
||||||
|
## Transport backend plugin
|
||||||
|
|
||||||
|
Transport plugins connect Takopi to other messaging systems (Slack, Discord, …).
|
||||||
|
For most transports, delegate message handling to `handle_message()` from `takopi.api`.
|
||||||
|
|
||||||
|
## Command backend plugin
|
||||||
|
|
||||||
|
Command plugins add custom `/command` handlers. They only run when the message starts
|
||||||
|
with `/<id>` and the id does not collide with engine ids, project aliases, or reserved names.
|
||||||
|
|
||||||
|
Minimal example:
|
||||||
|
|
||||||
|
```py
|
||||||
|
# mycommand/backend.py
|
||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
from takopi.api import CommandContext, CommandResult
|
||||||
|
|
||||||
|
|
||||||
|
class MyCommand:
|
||||||
|
id = "hello"
|
||||||
|
description = "say hello"
|
||||||
|
|
||||||
|
async def handle(self, ctx: CommandContext) -> CommandResult | None:
|
||||||
|
_ = ctx
|
||||||
|
return CommandResult(text="hello")
|
||||||
|
|
||||||
|
|
||||||
|
BACKEND = MyCommand()
|
||||||
|
```
|
||||||
|
|
||||||
|
### Command plugin configuration
|
||||||
|
|
||||||
|
Configure under `[plugins.<id>]`:
|
||||||
|
|
||||||
|
```toml
|
||||||
|
[plugins.hello]
|
||||||
|
greeting = "hello"
|
||||||
|
```
|
||||||
|
|
||||||
|
The parsed dict is available as `ctx.plugin_config` in `handle()`.
|
||||||
|
|
||||||
|
## Enable/disable installed plugins
|
||||||
|
|
||||||
|
```toml
|
||||||
|
[plugins]
|
||||||
|
enabled = ["takopi-transport-slack", "takopi-engine-acme"]
|
||||||
|
```
|
||||||
|
|
||||||
|
- `enabled = []` (default) means “load all installed plugins”.
|
||||||
|
- If non-empty, only distributions with matching names are visible.
|
||||||
|
|
||||||
|
## Validate discovery and loading
|
||||||
|
|
||||||
|
```sh
|
||||||
|
takopi plugins
|
||||||
|
takopi plugins --load
|
||||||
|
```
|
||||||
|
|
||||||
|
## Related
|
||||||
|
|
||||||
|
- [Plugin system (design)](../explanation/plugin-system.md)
|
||||||
|
- [Plugin API reference](../reference/plugin-api.md)
|
||||||
+90
-19
@@ -1,31 +1,102 @@
|
|||||||
# Takopi
|
# Takopi documentation
|
||||||
|
|
||||||
Takopi connects agent CLIs to Telegram so you can run, monitor, and reply to long-running tasks from chat.
|
> Telegram bridge for coding agents (Codex, Claude Code, OpenCode, Pi).
|
||||||
It supports multiple runner backends and a pluggable transport layer, with a stable public API for extensions.
|
|
||||||
|
|
||||||
## What this site covers
|
Takopi lets you run an engine CLI in a local repo while controlling it from Telegram: send a task, stream updates, and continue safely (reply-to-continue, topics, or sessions).
|
||||||
|
|
||||||
- How to get Takopi running end-to-end
|
## Choose your path
|
||||||
- Project aliases and worktree-aware workflows
|
|
||||||
- The plugin system and stable public API surface
|
- **I’m new / I want to get it running**
|
||||||
- Architectural details and behavioral guarantees
|
- Start with **[Tutorials](tutorials/index.md)**:
|
||||||
|
- [Install & onboard](tutorials/install-and-onboard.md)
|
||||||
|
- [First run](tutorials/first-run.md)
|
||||||
|
|
||||||
|
- **I know what I want to do (enable a feature / fix a workflow)**
|
||||||
|
- Use **[How-to guides](how-to/index.md)**:
|
||||||
|
- [Projects](how-to/projects.md) and [Worktrees](how-to/worktrees.md)
|
||||||
|
- [Topics](how-to/topics.md) and [Route by chat](how-to/route-by-chat.md)
|
||||||
|
- [File transfer](how-to/file-transfer.md) and [Voice notes](how-to/voice-notes.md)
|
||||||
|
|
||||||
|
- **I need exact knobs, defaults, and contracts**
|
||||||
|
- Go straight to **[Reference](reference/index.md)**:
|
||||||
|
- [Commands & directives](reference/commands-and-directives.md)
|
||||||
|
- [Configuration](reference/config.md)
|
||||||
|
- [Specification](reference/specification.md) (normative behavior)
|
||||||
|
|
||||||
|
- **I’m trying to understand the design**
|
||||||
|
- Read **[Explanation](explanation/index.md)**:
|
||||||
|
- [Architecture](explanation/architecture.md)
|
||||||
|
- [Routing & sessions](explanation/routing-and-sessions.md)
|
||||||
|
- [Plugin system](explanation/plugin-system.md)
|
||||||
|
|
||||||
## Quick start
|
## Quick start
|
||||||
|
|
||||||
|
If you just want to see it work end-to-end:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
uv run takopi --help
|
# Install
|
||||||
|
uv tool install -U takopi
|
||||||
|
|
||||||
|
# Configure Telegram + defaults
|
||||||
|
takopi --onboard
|
||||||
|
|
||||||
|
# Run in a repo
|
||||||
|
cd /path/to/your/repo
|
||||||
|
takopi
|
||||||
```
|
```
|
||||||
|
|
||||||
## Documentation map
|
Then open Telegram and send a task to your bot.
|
||||||
|
|
||||||
- Start here: [User guide](user-guide.md)
|
## Core concepts
|
||||||
- Projects and worktrees: [Projects](projects.md)
|
|
||||||
- Plugin development: [Plugins](plugins.md) and [Public API](public-api.md)
|
|
||||||
- System behavior: [Architecture](architecture.md) and [Specification](specification.md)
|
|
||||||
- Transport details: [Telegram](transports/telegram.md)
|
|
||||||
- Contributor notes: [Developing](developing.md)
|
|
||||||
|
|
||||||
## LLM entrypoints
|
* **Engine**: the CLI that actually does the work (e.g. `codex`, `claude`, `opencode`, `pi`).
|
||||||
|
* **Project**: a named alias for a repo path (so you can run from anywhere).
|
||||||
|
* **Worktree / branch selection**: pick where work should happen (`@branch`).
|
||||||
|
* **Continuation**: how Takopi safely “continues” a run:
|
||||||
|
|
||||||
- `/llms.txt` lists the key pages and links to their Markdown mirrors.
|
* reply-to-continue (always available)
|
||||||
- `/llms-full.txt` contains the full expanded content of those pages.
|
* forum topics (thread-bound continuation)
|
||||||
|
* chat sessions (auto-resume)
|
||||||
|
* **Contract**: the stable rules (resume lines, event ordering, rendering expectations) in the
|
||||||
|
[Specification](reference/specification.md) and runner contract tests.
|
||||||
|
|
||||||
|
## For plugin authors
|
||||||
|
|
||||||
|
Start here:
|
||||||
|
|
||||||
|
* [Plugin API](reference/plugin-api.md) — **stable** `takopi.api` surface for plugins
|
||||||
|
* [Write a plugin](how-to/write-a-plugin.md)
|
||||||
|
* [Add a runner](how-to/add-a-runner.md)
|
||||||
|
|
||||||
|
If you’re contributing to core:
|
||||||
|
|
||||||
|
* [Dev setup](how-to/dev-setup.md)
|
||||||
|
* [Module map](explanation/module-map.md)
|
||||||
|
|
||||||
|
## For LLM agents
|
||||||
|
|
||||||
|
Takopi publishes an LLM-oriented index at:
|
||||||
|
|
||||||
|
* `/llms.txt` — curated entrypoints + links to Markdown mirrors
|
||||||
|
* `/llms-full.txt` — expanded “single file” context of the most important pages
|
||||||
|
|
||||||
|
In the docs, start here:
|
||||||
|
|
||||||
|
* [Reference: For agents](reference/agents/index.md)
|
||||||
|
* [Repo map](reference/agents/repo-map.md)
|
||||||
|
* [Invariants](reference/agents/invariants.md)
|
||||||
|
|
||||||
|
## Where to look when something feels “off”
|
||||||
|
|
||||||
|
* “Why didn’t it route to the right repo/branch?” → [Context resolution](reference/context-resolution.md)
|
||||||
|
* “Why didn’t it continue where I left off?” → [Commands & directives](reference/commands-and-directives.md) and [Specification](reference/specification.md)
|
||||||
|
* “Why did Telegram messages behave weirdly?” → [Telegram transport](reference/transports/telegram.md)
|
||||||
|
* “Why is it built this way?” → [Architecture](explanation/architecture.md)
|
||||||
|
|
||||||
|
## Legacy portals
|
||||||
|
|
||||||
|
These pages remain as curated pointers to preserve old links:
|
||||||
|
|
||||||
|
- [User guide](user-guide.md)
|
||||||
|
- [Plugins](plugins.md)
|
||||||
|
- [Developing](developing.md)
|
||||||
|
|||||||
+11
-296
@@ -1,305 +1,20 @@
|
|||||||
# Plugins
|
# Plugins (moved)
|
||||||
|
|
||||||
Takopi supports **entrypoint-based plugins** for:
|
This page was split into smaller Diátaxis pages.
|
||||||
|
|
||||||
- **Engine backends** (new runner implementations)
|
## Start here
|
||||||
- **Transport backends** (new chat/command transports)
|
|
||||||
- **Command backends** (custom `/command` handlers)
|
|
||||||
|
|
||||||
Plugins are **discovered lazily**: Takopi lists IDs without importing plugin code,
|
- [Write a plugin](how-to/write-a-plugin.md)
|
||||||
and loads a plugin only when it is needed (or when you explicitly request it).
|
|
||||||
|
|
||||||
This keeps `takopi --help` fast and prevents broken plugins from bricking the CLI.
|
## Design
|
||||||
|
|
||||||
See `public-api.md` for the stable API surface you should depend on.
|
- [Plugin system](explanation/plugin-system.md)
|
||||||
|
|
||||||
---
|
## Reference
|
||||||
|
|
||||||
## Entrypoint groups
|
- [Plugin API](reference/plugin-api.md)
|
||||||
|
|
||||||
Takopi uses three Python entrypoint groups:
|
## Diagnostics
|
||||||
|
|
||||||
```toml
|
- `takopi plugins` lists discovered entrypoints without loading them.
|
||||||
[project.entry-points."takopi.engine_backends"]
|
- `takopi plugins --load` loads each plugin to surface import errors.
|
||||||
myengine = "myengine.backend:BACKEND"
|
|
||||||
|
|
||||||
[project.entry-points."takopi.transport_backends"]
|
|
||||||
mytransport = "mytransport.backend:BACKEND"
|
|
||||||
|
|
||||||
[project.entry-points."takopi.command_backends"]
|
|
||||||
mycommand = "mycommand.backend:BACKEND"
|
|
||||||
```
|
|
||||||
|
|
||||||
**Rules:**
|
|
||||||
|
|
||||||
- The entrypoint **name** is the plugin ID.
|
|
||||||
- The entrypoint value must resolve to a **backend object**:
|
|
||||||
- Engine backend -> `EngineBackend`
|
|
||||||
- Transport backend -> `TransportBackend`
|
|
||||||
- Command backend -> `CommandBackend`
|
|
||||||
- The backend object **must** have `id == entrypoint name`.
|
|
||||||
|
|
||||||
Takopi validates this at load time and will report errors via `takopi plugins --load`.
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## ID rules
|
|
||||||
|
|
||||||
Plugin IDs are used in the CLI and (for engines/projects) in Telegram commands.
|
|
||||||
They must match:
|
|
||||||
|
|
||||||
```
|
|
||||||
^[a-z0-9_]{1,32}$
|
|
||||||
```
|
|
||||||
|
|
||||||
If an ID does not match, it is skipped and reported as an error.
|
|
||||||
|
|
||||||
**Reserved IDs (engines):**
|
|
||||||
|
|
||||||
- `cancel` (core chat command)
|
|
||||||
- `init`, `plugins` (CLI commands)
|
|
||||||
|
|
||||||
Engines using these IDs are skipped and reported as errors.
|
|
||||||
|
|
||||||
**Reserved IDs (commands):**
|
|
||||||
|
|
||||||
- `cancel`, `init`, `plugins`
|
|
||||||
- Any engine id or project alias (checked at runtime)
|
|
||||||
|
|
||||||
Command backends using reserved IDs are skipped and reported as errors.
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Enabling plugins
|
|
||||||
|
|
||||||
Takopi supports a simple enabled list to control which plugins are visible.
|
|
||||||
|
|
||||||
```toml
|
|
||||||
[plugins]
|
|
||||||
enabled = ["takopi-transport-slack", "takopi-engine-acme"]
|
|
||||||
```
|
|
||||||
|
|
||||||
- `enabled = []` (default) -> load all installed plugins.
|
|
||||||
- If `enabled` is non-empty, **only distributions with matching names** are visible.
|
|
||||||
- Distribution names are taken from package metadata (case-insensitive).
|
|
||||||
- If a plugin has no resolvable distribution name and an enabled list is set, it is hidden.
|
|
||||||
This enabled list affects:
|
|
||||||
|
|
||||||
- Engine subcommands registered in the CLI
|
|
||||||
- `takopi plugins` output
|
|
||||||
- Runtime resolution of engines/transports/commands
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Discovering plugins
|
|
||||||
|
|
||||||
Use the CLI to inspect plugins:
|
|
||||||
|
|
||||||
```sh
|
|
||||||
takopi plugins
|
|
||||||
takopi plugins --load
|
|
||||||
```
|
|
||||||
|
|
||||||
Behavior:
|
|
||||||
|
|
||||||
- `takopi plugins` lists discovered entrypoints **without loading them**.
|
|
||||||
- `--load` loads each plugin to validate type and surface import errors.
|
|
||||||
- Errors are shown at the end, grouped by engine/transport and distribution.
|
|
||||||
- If `[plugins] enabled` is set, entries are still listed but marked `enabled`/`disabled`.
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Engine backend plugins
|
|
||||||
|
|
||||||
Engine plugins implement a runner for a new engine CLI and expose
|
|
||||||
an `EngineBackend` object.
|
|
||||||
|
|
||||||
Minimal example:
|
|
||||||
|
|
||||||
```py
|
|
||||||
# myengine/backend.py
|
|
||||||
from __future__ import annotations
|
|
||||||
|
|
||||||
from pathlib import Path
|
|
||||||
|
|
||||||
from takopi.api import EngineBackend, EngineConfig, Runner
|
|
||||||
|
|
||||||
def build_runner(config: EngineConfig, config_path: Path) -> Runner:
|
|
||||||
_ = config_path
|
|
||||||
# Parse config if needed; raise ConfigError for invalid config.
|
|
||||||
return MyEngineRunner(config)
|
|
||||||
|
|
||||||
BACKEND = EngineBackend(
|
|
||||||
id="myengine",
|
|
||||||
build_runner=build_runner,
|
|
||||||
cli_cmd="myengine",
|
|
||||||
install_cmd="pip install myengine",
|
|
||||||
)
|
|
||||||
```
|
|
||||||
|
|
||||||
`EngineConfig` is the raw config table (dict) from `takopi.toml`:
|
|
||||||
|
|
||||||
```toml
|
|
||||||
[myengine]
|
|
||||||
model = "..."
|
|
||||||
```
|
|
||||||
|
|
||||||
Read it with `settings.engine_config("myengine", config_path=...)` in Takopi,
|
|
||||||
or just consume the dict directly in your runner builder.
|
|
||||||
|
|
||||||
See `public-api.md` for the runner contract and helper classes like
|
|
||||||
`JsonlSubprocessRunner` and `EventFactory`.
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Transport backend plugins
|
|
||||||
|
|
||||||
Transport plugins connect Takopi to new messaging systems (Slack, Discord, etc).
|
|
||||||
|
|
||||||
You must provide a `TransportBackend` object with:
|
|
||||||
|
|
||||||
- `id` and `description`
|
|
||||||
- `check_setup()` -> returns `SetupResult` (issues + config path)
|
|
||||||
- `interactive_setup()` -> optional interactive setup flow
|
|
||||||
- `lock_token()` -> token fingerprinting for config locks
|
|
||||||
- `build_and_run()` -> build transport and start the main loop
|
|
||||||
|
|
||||||
Minimal skeleton:
|
|
||||||
|
|
||||||
```py
|
|
||||||
# mytransport/backend.py
|
|
||||||
from __future__ import annotations
|
|
||||||
|
|
||||||
from pathlib import Path
|
|
||||||
|
|
||||||
from takopi.api import (
|
|
||||||
EngineBackend,
|
|
||||||
SetupResult,
|
|
||||||
TransportBackend,
|
|
||||||
TransportRuntime,
|
|
||||||
)
|
|
||||||
|
|
||||||
class MyTransportBackend:
|
|
||||||
id = "mytransport"
|
|
||||||
description = "MyTransport bot"
|
|
||||||
|
|
||||||
def check_setup(
|
|
||||||
self, engine_backend: EngineBackend, *, transport_override: str | None = None
|
|
||||||
) -> SetupResult:
|
|
||||||
_ = engine_backend, transport_override
|
|
||||||
return SetupResult(issues=[], config_path=Path("takopi.toml"))
|
|
||||||
|
|
||||||
def interactive_setup(self, *, force: bool) -> bool:
|
|
||||||
_ = force
|
|
||||||
return True
|
|
||||||
|
|
||||||
def lock_token(
|
|
||||||
self, *, transport_config: dict[str, object], config_path: Path
|
|
||||||
) -> str | None:
|
|
||||||
_ = transport_config, config_path
|
|
||||||
return None
|
|
||||||
|
|
||||||
def build_and_run(
|
|
||||||
self,
|
|
||||||
*,
|
|
||||||
transport_config: dict[str, object],
|
|
||||||
config_path: Path,
|
|
||||||
runtime: TransportRuntime,
|
|
||||||
final_notify: bool,
|
|
||||||
default_engine_override: str | None,
|
|
||||||
) -> None:
|
|
||||||
_ = (
|
|
||||||
transport_config,
|
|
||||||
config_path,
|
|
||||||
runtime,
|
|
||||||
final_notify,
|
|
||||||
default_engine_override,
|
|
||||||
)
|
|
||||||
raise NotImplementedError
|
|
||||||
|
|
||||||
BACKEND = MyTransportBackend()
|
|
||||||
```
|
|
||||||
|
|
||||||
For most transports, you will want to call `handle_message()` from `takopi.api`
|
|
||||||
inside your message loop. That function implements progress updates, resume handling,
|
|
||||||
and cancellation semantics.
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Command backend plugins
|
|
||||||
|
|
||||||
Command plugins add custom `/command` handlers. A command only runs when the
|
|
||||||
message starts with `/command` and does **not** collide with engine ids,
|
|
||||||
project aliases, or reserved command names.
|
|
||||||
|
|
||||||
Minimal example:
|
|
||||||
|
|
||||||
```py
|
|
||||||
# mycommand/backend.py
|
|
||||||
from __future__ import annotations
|
|
||||||
|
|
||||||
from takopi.api import CommandContext, CommandResult, RunRequest
|
|
||||||
|
|
||||||
class MultiCommand:
|
|
||||||
id = "multi"
|
|
||||||
description = "run the prompt on every engine"
|
|
||||||
|
|
||||||
async def handle(self, ctx: CommandContext) -> CommandResult | None:
|
|
||||||
prompt = ctx.args_text.strip()
|
|
||||||
if not prompt:
|
|
||||||
return CommandResult(text="usage: /multi <prompt>")
|
|
||||||
requests = [
|
|
||||||
RunRequest(prompt=prompt, engine=engine)
|
|
||||||
for engine in ctx.runtime.available_engine_ids()
|
|
||||||
]
|
|
||||||
results = await ctx.executor.run_many(
|
|
||||||
requests,
|
|
||||||
mode="capture",
|
|
||||||
parallel=True,
|
|
||||||
)
|
|
||||||
blocks = []
|
|
||||||
for result in results:
|
|
||||||
text = result.message.text if result.message else "no output"
|
|
||||||
blocks.append(f"## {result.engine}\n{text}")
|
|
||||||
return CommandResult(text="\n\n".join(blocks))
|
|
||||||
|
|
||||||
BACKEND = MultiCommand()
|
|
||||||
```
|
|
||||||
|
|
||||||
### Command plugin configuration
|
|
||||||
|
|
||||||
Configure command plugins under `[plugins.<id>]`:
|
|
||||||
|
|
||||||
```toml
|
|
||||||
[plugins.multi]
|
|
||||||
engines = ["codex", "claude"]
|
|
||||||
```
|
|
||||||
|
|
||||||
The parsed dict is available as `ctx.plugin_config` inside `handle()`.
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Versioning & compatibility
|
|
||||||
|
|
||||||
Takopi exposes a **stable plugin API** via `takopi.api`.
|
|
||||||
|
|
||||||
- `TAKOPI_PLUGIN_API_VERSION = 1` is the current API version.
|
|
||||||
- Depend on a compatible Takopi version range, for example:
|
|
||||||
|
|
||||||
```toml
|
|
||||||
dependencies = ["takopi>=0.14,<0.15"]
|
|
||||||
```
|
|
||||||
|
|
||||||
When the plugin API changes, Takopi will bump the API version and document
|
|
||||||
any compatibility guidance.
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Troubleshooting
|
|
||||||
|
|
||||||
Common issues:
|
|
||||||
|
|
||||||
- **Plugin missing from CLI**: check the enabled list in `[plugins] enabled`.
|
|
||||||
- **Plugin not listed**: verify entrypoint group and ID regex.
|
|
||||||
- **Load failures**: run `takopi plugins --load` and inspect errors.
|
|
||||||
- **ID mismatch**: ensure `BACKEND.id == entrypoint name`.
|
|
||||||
|
|||||||
@@ -0,0 +1,7 @@
|
|||||||
|
# For agents
|
||||||
|
|
||||||
|
These pages are **high-signal reference** for LLM agents (and humans acting like one).
|
||||||
|
|
||||||
|
- [Repo map](repo-map.md)
|
||||||
|
- [Invariants](invariants.md)
|
||||||
|
|
||||||
@@ -0,0 +1,35 @@
|
|||||||
|
# Invariants
|
||||||
|
|
||||||
|
These are the “don’t break this” rules that keep Takopi reliable.
|
||||||
|
|
||||||
|
## Runner contract
|
||||||
|
|
||||||
|
The runner contract is enforced by `tests/test_runner_contract.py`:
|
||||||
|
|
||||||
|
- Exactly one `StartedEvent`
|
||||||
|
- Exactly one `CompletedEvent`
|
||||||
|
- `CompletedEvent` is last
|
||||||
|
- `CompletedEvent.resume == StartedEvent.resume`
|
||||||
|
|
||||||
|
See also the [Plugin API](../plugin-api.md) runner contract section.
|
||||||
|
|
||||||
|
## Per-thread serialization
|
||||||
|
|
||||||
|
At most one active run may operate on the same thread/session at a time.
|
||||||
|
This is enforced both by scheduling and by per-resume-token runner locks.
|
||||||
|
|
||||||
|
Normative details live in the [Specification](../specification.md) (§5.2).
|
||||||
|
|
||||||
|
## Resume lines
|
||||||
|
|
||||||
|
Resume lines embedded in chat are the engine’s canonical resume command (e.g. `claude --resume <id>`).
|
||||||
|
|
||||||
|
- The runner is authoritative for formatting and extraction.
|
||||||
|
- Transports/rendering must preserve the resume line reliably (even when trimming/splitting).
|
||||||
|
|
||||||
|
Normative details live in the [Specification](../specification.md) (§3).
|
||||||
|
|
||||||
|
## Local contribution hygiene
|
||||||
|
|
||||||
|
- Run `just check` before code commits.
|
||||||
|
|
||||||
@@ -0,0 +1,40 @@
|
|||||||
|
# Repo map
|
||||||
|
|
||||||
|
Quick pointers for navigating the Takopi codebase.
|
||||||
|
|
||||||
|
## Where things start
|
||||||
|
|
||||||
|
- CLI entry point: `src/takopi/cli.py`
|
||||||
|
- Telegram backend entry point: `src/takopi/telegram/backend.py`
|
||||||
|
- Telegram bridge loop: `src/takopi/telegram/bridge.py`
|
||||||
|
- Transport-agnostic handler: `src/takopi/runner_bridge.py`
|
||||||
|
|
||||||
|
## Core concepts
|
||||||
|
|
||||||
|
- Domain types (resume tokens, events, actions): `src/takopi/model.py`
|
||||||
|
- Runner protocol: `src/takopi/runner.py`
|
||||||
|
- Router selection and resume polling: `src/takopi/router.py`
|
||||||
|
- Per-thread scheduling: `src/takopi/scheduler.py`
|
||||||
|
- Progress reduction and rendering: `src/takopi/progress.py`, `src/takopi/markdown.py`
|
||||||
|
|
||||||
|
## Engines and streaming
|
||||||
|
|
||||||
|
- Runner implementations: `src/takopi/runners/*`
|
||||||
|
- JSONL decoding schemas: `src/takopi/schemas/*`
|
||||||
|
|
||||||
|
## Plugins
|
||||||
|
|
||||||
|
- Public API boundary (`takopi.api`): `src/takopi/api.py`
|
||||||
|
- Entrypoint discovery + lazy loading: `src/takopi/plugins.py`
|
||||||
|
- Engine/transport/command backend loading: `src/takopi/engines.py`, `src/takopi/transports.py`, `src/takopi/commands.py`
|
||||||
|
|
||||||
|
## Configuration
|
||||||
|
|
||||||
|
- Settings model + TOML/env loading: `src/takopi/settings.py`
|
||||||
|
- Config migrations: `src/takopi/config_migrations.py`
|
||||||
|
|
||||||
|
## Docs and contracts
|
||||||
|
|
||||||
|
- Normative behavior: [Specification](../specification.md)
|
||||||
|
- Runner invariants: `tests/test_runner_contract.py`
|
||||||
|
|
||||||
@@ -0,0 +1,71 @@
|
|||||||
|
# Commands & directives
|
||||||
|
|
||||||
|
This page documents Takopi’s user-visible command surface: message directives, in-chat commands, and the CLI.
|
||||||
|
|
||||||
|
## Message directives
|
||||||
|
|
||||||
|
Takopi parses the first non-empty line of a message for a directive prefix.
|
||||||
|
|
||||||
|
| Directive | Example | Effect |
|
||||||
|
|----------|---------|--------|
|
||||||
|
| `/engine` | `/codex fix flaky test` | Select an engine for this message. |
|
||||||
|
| `/project` | `/happy-gadgets add escape-pod` | Select a project alias. |
|
||||||
|
| `@branch` | `@feat/happy-camera rewind to checkpoint` | Run in a worktree for the branch. |
|
||||||
|
| Combined | `/happy-gadgets @feat/flower-pin observe unseen` | Project + branch. |
|
||||||
|
|
||||||
|
Notes:
|
||||||
|
|
||||||
|
- Directives are only parsed at the start of the first non-empty line.
|
||||||
|
- Parsing stops at the first non-directive token.
|
||||||
|
- If a reply contains a `ctx:` line, Takopi ignores new directives and uses the reply context.
|
||||||
|
|
||||||
|
See [Context resolution](context-resolution.md) for the full rules.
|
||||||
|
|
||||||
|
## Context footer (`ctx:`)
|
||||||
|
|
||||||
|
When a run has project context, Takopi appends a footer line rendered as inline code:
|
||||||
|
|
||||||
|
- With branch: `` `ctx: <project> @<branch>` ``
|
||||||
|
- Without branch: `` `ctx: <project>` ``
|
||||||
|
|
||||||
|
This line is parsed from replies and takes precedence over new directives.
|
||||||
|
|
||||||
|
## Telegram in-chat commands
|
||||||
|
|
||||||
|
| Command | Description |
|
||||||
|
|---------|-------------|
|
||||||
|
| `/cancel` | Reply to the progress message to stop the current run. |
|
||||||
|
| `/agent` | Show/set the default agent for the current scope. |
|
||||||
|
| `/file put <path>` | Upload a document into the repo/worktree (requires file transfer enabled). |
|
||||||
|
| `/file get <path>` | Fetch a file or directory back into Telegram. |
|
||||||
|
| `/topic <project> @branch` | Create/bind a topic (topics enabled). |
|
||||||
|
| `/ctx` | Show topic context binding (topics enabled). |
|
||||||
|
| `/ctx set <project> @branch` | Update topic context binding. |
|
||||||
|
| `/ctx clear` | Remove topic context binding. |
|
||||||
|
| `/new` | Clear stored sessions for the current scope (topic/chat). |
|
||||||
|
|
||||||
|
## CLI
|
||||||
|
|
||||||
|
Takopi’s CLI is an auto-router by default; engine subcommands override the default engine.
|
||||||
|
|
||||||
|
### Commands
|
||||||
|
|
||||||
|
| Command | Description |
|
||||||
|
|---------|-------------|
|
||||||
|
| `takopi` | Start Takopi (runs onboarding if setup/config is missing and you’re in a TTY). |
|
||||||
|
| `takopi <engine>` | Run with a specific engine (e.g. `takopi codex`). |
|
||||||
|
| `takopi init <alias>` | Register the current repo as a project. |
|
||||||
|
| `takopi chat-id` | Capture the current chat id. |
|
||||||
|
| `takopi chat-id --project <alias>` | Save the captured chat id to a project. |
|
||||||
|
| `takopi plugins` | List discovered plugins without loading them. |
|
||||||
|
| `takopi plugins --load` | Load each plugin to validate types and surface import errors. |
|
||||||
|
|
||||||
|
### Common flags
|
||||||
|
|
||||||
|
| Flag | Description |
|
||||||
|
|------|-------------|
|
||||||
|
| `--onboard` | Force the interactive setup wizard before starting. |
|
||||||
|
| `--transport <id>` | Override the configured transport backend id. |
|
||||||
|
| `--debug` | Write debug logs to `debug.log`. |
|
||||||
|
| `--final-notify/--no-final-notify` | Send the final response as a new message vs an edit. |
|
||||||
|
|
||||||
@@ -0,0 +1,109 @@
|
|||||||
|
# Configuration
|
||||||
|
|
||||||
|
Takopi reads configuration from `~/.takopi/takopi.toml`.
|
||||||
|
|
||||||
|
If you expect to edit config while Takopi is running, set:
|
||||||
|
|
||||||
|
```toml
|
||||||
|
watch_config = true
|
||||||
|
```
|
||||||
|
|
||||||
|
## Top-level keys
|
||||||
|
|
||||||
|
| Key | Type | Default | Notes |
|
||||||
|
|-----|------|---------|-------|
|
||||||
|
| `watch_config` | bool | `false` | Hot-reload config changes (transport excluded). |
|
||||||
|
| `default_engine` | string | `"codex"` | Default engine id for new threads. |
|
||||||
|
| `default_project` | string\|null | `null` | Default project alias. |
|
||||||
|
| `transport` | string | `"telegram"` | Transport backend id. |
|
||||||
|
|
||||||
|
## `transports.telegram`
|
||||||
|
|
||||||
|
```toml
|
||||||
|
[transports.telegram]
|
||||||
|
bot_token = "..."
|
||||||
|
chat_id = 123
|
||||||
|
```
|
||||||
|
|
||||||
|
| Key | Type | Default | Notes |
|
||||||
|
|-----|------|---------|-------|
|
||||||
|
| `bot_token` | string | (required) | Telegram bot token from @BotFather. |
|
||||||
|
| `chat_id` | int | (required) | Default chat id. |
|
||||||
|
| `message_overflow` | `"trim"`\|`"split"` | `"trim"` | How to handle long final responses. |
|
||||||
|
| `voice_transcription` | bool | `false` | Enable voice note transcription. |
|
||||||
|
| `voice_max_bytes` | int | `10485760` | Max voice note size (bytes). |
|
||||||
|
| `voice_transcription_model` | string | `"gpt-4o-mini-transcribe"` | OpenAI transcription model name. |
|
||||||
|
| `session_mode` | `"stateless"`\|`"chat"` | `"stateless"` | Auto-resume mode. |
|
||||||
|
| `show_resume_line` | bool | `true` | Show resume line in message footer. |
|
||||||
|
|
||||||
|
### `transports.telegram.topics`
|
||||||
|
|
||||||
|
| Key | Type | Default | Notes |
|
||||||
|
|-----|------|---------|-------|
|
||||||
|
| `enabled` | bool | `false` | Enable forum-topic features. |
|
||||||
|
| `scope` | `"auto"`\|`"main"`\|`"projects"`\|`"all"` | `"auto"` | Where topics are managed. |
|
||||||
|
|
||||||
|
### `transports.telegram.files`
|
||||||
|
|
||||||
|
| Key | Type | Default | Notes |
|
||||||
|
|-----|------|---------|-------|
|
||||||
|
| `enabled` | bool | `false` | Enable `/file put` and `/file get`. |
|
||||||
|
| `auto_put` | bool | `true` | Auto-save uploads. |
|
||||||
|
| `auto_put_mode` | `"upload"`\|`"prompt"` | `"upload"` | Whether uploads also start a run. |
|
||||||
|
| `uploads_dir` | string | `"incoming"` | Relative path inside the repo/worktree. |
|
||||||
|
| `allowed_user_ids` | int[] | `[]` | Allowed senders; empty allows private chats (group usage requires admin). |
|
||||||
|
| `deny_globs` | string[] | (defaults) | Glob denylist (e.g. `.git/**`, `**/*.pem`). |
|
||||||
|
|
||||||
|
File size limits (not configurable):
|
||||||
|
|
||||||
|
- uploads: 20 MiB
|
||||||
|
- downloads: 50 MiB
|
||||||
|
|
||||||
|
## `projects.<alias>`
|
||||||
|
|
||||||
|
```toml
|
||||||
|
[projects.happy-gadgets]
|
||||||
|
path = "~/dev/happy-gadgets"
|
||||||
|
worktrees_dir = ".worktrees"
|
||||||
|
default_engine = "claude"
|
||||||
|
worktree_base = "master"
|
||||||
|
chat_id = -1001234567890
|
||||||
|
```
|
||||||
|
|
||||||
|
| Key | Type | Default | Notes |
|
||||||
|
|-----|------|---------|-------|
|
||||||
|
| `path` | string | (required) | Repo root (expands `~`). Relative paths are resolved against the config directory. |
|
||||||
|
| `worktrees_dir` | string | `".worktrees"` | Worktree root (relative to `path` unless absolute). |
|
||||||
|
| `default_engine` | string\|null | `null` | Per-project default engine. |
|
||||||
|
| `worktree_base` | string\|null | `null` | Base branch for new worktrees. |
|
||||||
|
| `chat_id` | int\|null | `null` | Bind a Telegram chat to this project. |
|
||||||
|
|
||||||
|
Legacy config note: top-level `bot_token` / `chat_id` are auto-migrated into `[transports.telegram]` on startup.
|
||||||
|
|
||||||
|
## Plugins
|
||||||
|
|
||||||
|
### `plugins.enabled`
|
||||||
|
|
||||||
|
```toml
|
||||||
|
[plugins]
|
||||||
|
enabled = ["takopi-transport-slack", "takopi-engine-acme"]
|
||||||
|
```
|
||||||
|
|
||||||
|
- `enabled = []` (default) means “load all installed plugins”.
|
||||||
|
- If non-empty, only distributions with matching names are visible (case-insensitive).
|
||||||
|
|
||||||
|
### `plugins.<id>`
|
||||||
|
|
||||||
|
Plugin-specific configuration lives under `[plugins.<id>]` and is passed to command plugins as `ctx.plugin_config`.
|
||||||
|
|
||||||
|
## Engine-specific config tables
|
||||||
|
|
||||||
|
Engines can have top-level config tables keyed by engine id, for example:
|
||||||
|
|
||||||
|
```toml
|
||||||
|
[codex]
|
||||||
|
model = "..."
|
||||||
|
```
|
||||||
|
|
||||||
|
The shape is engine-defined.
|
||||||
|
|
||||||
@@ -1,7 +1,7 @@
|
|||||||
# Projects and Worktrees
|
# Context resolution
|
||||||
|
|
||||||
This doc covers project aliases, worktree behavior, and how Takopi resolves run
|
This page documents how Takopi resolves **run context** (project, worktree/branch, engine) from messages.
|
||||||
context from messages.
|
For step-by-step usage, see [Projects](../how-to/projects.md) and [Worktrees](../how-to/worktrees.md).
|
||||||
|
|
||||||
## Overview
|
## Overview
|
||||||
|
|
||||||
@@ -13,9 +13,10 @@ worktree-based runs via `@branch`.
|
|||||||
the task in that worktree.
|
the task in that worktree.
|
||||||
- Progress/final messages include a `ctx:` footer when project context is active.
|
- Progress/final messages include a `ctx:` footer when project context is active.
|
||||||
|
|
||||||
## Config schema
|
## Config schema (relevant subset)
|
||||||
|
|
||||||
All config lives in `~/.takopi/takopi.toml`.
|
All config lives in `~/.takopi/takopi.toml`.
|
||||||
|
See [Config](config.md) for the full reference.
|
||||||
|
|
||||||
```toml
|
```toml
|
||||||
default_engine = "codex" # optional
|
default_engine = "codex" # optional
|
||||||
@@ -0,0 +1,26 @@
|
|||||||
|
# Environment variables
|
||||||
|
|
||||||
|
Takopi supports a small set of environment variables for logging and runtime behavior.
|
||||||
|
|
||||||
|
## Logging
|
||||||
|
|
||||||
|
| Variable | Description |
|
||||||
|
|----------|-------------|
|
||||||
|
| `TAKOPI_LOG_LEVEL` | Minimum log level (default `info`; `--debug` forces `debug`). |
|
||||||
|
| `TAKOPI_LOG_FORMAT` | `console` (default) or `json`. |
|
||||||
|
| `TAKOPI_LOG_COLOR` | Force color on/off (`1/true/yes/on` or `0/false/no/off`). |
|
||||||
|
| `TAKOPI_LOG_FILE` | Append JSON lines to a file. `--debug` defaults this to `debug.log`. |
|
||||||
|
| `TAKOPI_TRACE_PIPELINE` | Log pipeline events at `info` instead of `debug`. |
|
||||||
|
|
||||||
|
## CLI behavior
|
||||||
|
|
||||||
|
| Variable | Description |
|
||||||
|
|----------|-------------|
|
||||||
|
| `TAKOPI_NO_INTERACTIVE` | Disable interactive prompts (useful for CI / non-TTY). |
|
||||||
|
|
||||||
|
## Engine-specific
|
||||||
|
|
||||||
|
| Variable | Description |
|
||||||
|
|----------|-------------|
|
||||||
|
| `PI_CODING_AGENT_DIR` | Override Pi agent session directory base path. |
|
||||||
|
|
||||||
@@ -0,0 +1,65 @@
|
|||||||
|
# Reference
|
||||||
|
|
||||||
|
Reference docs are **authoritative and exact**. Use these when you need stable facts, schemas, and contracts.
|
||||||
|
|
||||||
|
If you’re trying to achieve a goal (“enable topics”, “fetch a file”), use **[How-to](../how-to/index.md)**.
|
||||||
|
If you’re trying to understand the *why*, use **[Explanation](../explanation/index.md)**.
|
||||||
|
|
||||||
|
## Most-used reference pages
|
||||||
|
|
||||||
|
- [Commands & directives](commands-and-directives.md)
|
||||||
|
- Message prefixes like `/engine`, `/project`, and `@branch`
|
||||||
|
- In-chat commands like `/cancel`, `/new`, `/ctx`, `/file …`, `/topic …`
|
||||||
|
- [Configuration](config.md)
|
||||||
|
- `takopi.toml` options and defaults
|
||||||
|
- Telegram transport options (sessions, topics, files, voice transcription)
|
||||||
|
|
||||||
|
## Normative behavior
|
||||||
|
|
||||||
|
- [Specification](specification.md)
|
||||||
|
The normative (“MUST/SHOULD/MAY”) contract for:
|
||||||
|
- resume tokens + resume lines
|
||||||
|
- event model
|
||||||
|
- progress/final message semantics
|
||||||
|
- per-thread serialization rules
|
||||||
|
|
||||||
|
## Plugins and extension contracts
|
||||||
|
|
||||||
|
- [Plugin API](plugin-api.md)
|
||||||
|
The **only** supported import surface for plugins: `takopi.api`
|
||||||
|
- [Context resolution](context-resolution.md)
|
||||||
|
How Takopi resolves project + worktree context from directives, replies, and chat ids.
|
||||||
|
|
||||||
|
## Transport reference
|
||||||
|
|
||||||
|
- [Telegram transport](transports/telegram.md)
|
||||||
|
Rate limits, outbox behavior, retries, message editing rules.
|
||||||
|
|
||||||
|
## Runner reference
|
||||||
|
|
||||||
|
These are “engine adapter” implementation details: JSONL formats, mapping rules, and emitted events.
|
||||||
|
|
||||||
|
- [Runners overview](runners/index.md)
|
||||||
|
- Claude:
|
||||||
|
- [runner.md](runners/claude/runner.md)
|
||||||
|
- [stream-json-cheatsheet.md](runners/claude/stream-json-cheatsheet.md)
|
||||||
|
- [takopi-events.md](runners/claude/takopi-events.md)
|
||||||
|
- Codex:
|
||||||
|
- [exec-json-cheatsheet.md](runners/codex/exec-json-cheatsheet.md)
|
||||||
|
- [takopi-events.md](runners/codex/takopi-events.md)
|
||||||
|
- OpenCode:
|
||||||
|
- [runner.md](runners/opencode/runner.md)
|
||||||
|
- [stream-json-cheatsheet.md](runners/opencode/stream-json-cheatsheet.md)
|
||||||
|
- [takopi-events.md](runners/opencode/takopi-events.md)
|
||||||
|
- Pi:
|
||||||
|
- [runner.md](runners/pi/runner.md)
|
||||||
|
- [stream-json-cheatsheet.md](runners/pi/stream-json-cheatsheet.md)
|
||||||
|
- [takopi-events.md](runners/pi/takopi-events.md)
|
||||||
|
|
||||||
|
## For LLM agents
|
||||||
|
|
||||||
|
If you’re an LLM agent contributing to Takopi, start here:
|
||||||
|
|
||||||
|
- [Agent entrypoint](agents/index.md)
|
||||||
|
- [Repo map](agents/repo-map.md)
|
||||||
|
- [Invariants](agents/invariants.md) (runner contract, resume handling, “don’t break this” rules)
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
# Public Plugin API
|
# Plugin API
|
||||||
|
|
||||||
Takopi's **public plugin API** is exported from:
|
Takopi’s **public plugin API** is exported from:
|
||||||
|
|
||||||
```
|
```
|
||||||
takopi.api
|
takopi.api
|
||||||
+1
-1
@@ -199,7 +199,7 @@ Claude runner implementation summary (no Takopi domain model changes):
|
|||||||
2. [x] Define `BACKEND` in `takopi/runners/claude.py`:
|
2. [x] Define `BACKEND` in `takopi/runners/claude.py`:
|
||||||
- `install_cmd`: install command for the `claude` binary
|
- `install_cmd`: install command for the `claude` binary
|
||||||
- `build_runner`: read `[claude]` config + construct runner
|
- `build_runner`: read `[claude]` config + construct runner
|
||||||
3. [x] Add new docs (this file + `claude-stream-json-cheatsheet.md`).
|
3. [x] Add new docs (this file + `stream-json-cheatsheet.md`).
|
||||||
4. [x] Add fixtures in `tests/fixtures/` (see below).
|
4. [x] Add fixtures in `tests/fixtures/` (see below).
|
||||||
5. [x] Add unit tests mirroring `tests/test_codex_*` but for Claude translation
|
5. [x] Add unit tests mirroring `tests/test_codex_*` but for Claude translation
|
||||||
and resume parsing (recommended, not required for initial handoff).
|
and resume parsing (recommended, not required for initial handoff).
|
||||||
@@ -0,0 +1,9 @@
|
|||||||
|
# Runners
|
||||||
|
|
||||||
|
Runner docs describe the **engine-specific** behavior: event shapes, JSON streaming, and integration notes.
|
||||||
|
|
||||||
|
- Claude: [Runner](claude/runner.md), [Stream JSON cheatsheet](claude/stream-json-cheatsheet.md), [Takopi events](claude/takopi-events.md)
|
||||||
|
- Codex: [Exec JSON cheatsheet](codex/exec-json-cheatsheet.md), [Takopi events](codex/takopi-events.md)
|
||||||
|
- OpenCode: [Runner](opencode/runner.md), [Stream JSON cheatsheet](opencode/stream-json-cheatsheet.md), [Takopi events](opencode/takopi-events.md)
|
||||||
|
- Pi: [Runner](pi/runner.md), [Stream JSON cheatsheet](pi/stream-json-cheatsheet.md), [Takopi events](pi/takopi-events.md)
|
||||||
|
|
||||||
@@ -44,4 +44,4 @@ OpenCode outputs JSON events with the following types:
|
|||||||
| `step_finish` | End of a step (reason: "stop" or "tool-calls" when present) |
|
| `step_finish` | End of a step (reason: "stop" or "tool-calls" when present) |
|
||||||
| `error` | Error event |
|
| `error` | Error event |
|
||||||
|
|
||||||
See [opencode-stream-json-cheatsheet.md](./opencode-stream-json-cheatsheet.md) for detailed event format documentation.
|
See [stream-json-cheatsheet.md](./stream-json-cheatsheet.md) for detailed event format documentation.
|
||||||
@@ -0,0 +1,32 @@
|
|||||||
|
# First run
|
||||||
|
|
||||||
|
You’ll run Takopi in a repo, send a task from Telegram, and learn the basic controls.
|
||||||
|
|
||||||
|
## 1) Start Takopi in a repo
|
||||||
|
|
||||||
|
```sh
|
||||||
|
cd ~/dev/your-repo
|
||||||
|
takopi
|
||||||
|
```
|
||||||
|
|
||||||
|
## 2) Send a message to your bot
|
||||||
|
|
||||||
|
Takopi streams progress in chat and posts a final message when the engine finishes.
|
||||||
|
|
||||||
|
If you want to override the default engine for a single message, prefix the first line:
|
||||||
|
|
||||||
|
```
|
||||||
|
/codex explain this code
|
||||||
|
/claude refactor this module
|
||||||
|
```
|
||||||
|
|
||||||
|
## 3) Continue or cancel
|
||||||
|
|
||||||
|
- Continue the same thread by **replying** to any bot message that contains a resume line in the footer.
|
||||||
|
- Cancel an in-flight run by clicking the cancel button or replying to the progress message with `/cancel`.
|
||||||
|
|
||||||
|
## Next
|
||||||
|
|
||||||
|
- [Projects](../how-to/projects.md) and [Worktrees](../how-to/worktrees.md)
|
||||||
|
- [Commands & directives](../reference/commands-and-directives.md)
|
||||||
|
|
||||||
@@ -0,0 +1,41 @@
|
|||||||
|
# Tutorials
|
||||||
|
|
||||||
|
Tutorials are **step-by-step learning paths**. If you’re new, start here and follow them in order.
|
||||||
|
|
||||||
|
If you already know what you want to do (e.g. “enable topics” or “fetch a file”), jump to **How-to**.
|
||||||
|
|
||||||
|
## Before you start
|
||||||
|
|
||||||
|
You’ll need:
|
||||||
|
|
||||||
|
- A Telegram account + a bot token
|
||||||
|
- Python **3.14+** and `uv`
|
||||||
|
- At least one engine CLI on your `PATH`: `codex`, `claude`, `opencode`, or `pi`
|
||||||
|
|
||||||
|
## 1) Install and onboard
|
||||||
|
|
||||||
|
Get Takopi installed, create a bot token, capture your chat id, and pick a default engine.
|
||||||
|
|
||||||
|
- [Install & onboard](install-and-onboard.md)
|
||||||
|
|
||||||
|
## 2) Your first run
|
||||||
|
|
||||||
|
Run Takopi in a repo, send your first task from Telegram, and learn the two core controls:
|
||||||
|
|
||||||
|
- reply-to-continue (resume)
|
||||||
|
- cancel a run
|
||||||
|
|
||||||
|
- [First run](first-run.md)
|
||||||
|
|
||||||
|
## What to do next
|
||||||
|
|
||||||
|
Once you can run tasks end-to-end, you’ll usually want:
|
||||||
|
|
||||||
|
- Register a repo as a **project alias**: [How-to: Projects](../how-to/projects.md)
|
||||||
|
- Run work on a branch in a dedicated **worktree**: [How-to: Worktrees](../how-to/worktrees.md)
|
||||||
|
- Keep long-running work organized in threads: [How-to: Topics](../how-to/topics.md)
|
||||||
|
|
||||||
|
When you need exact knobs and defaults:
|
||||||
|
|
||||||
|
- [Reference: Configuration](../reference/config.md)
|
||||||
|
- [Reference: Commands & directives](../reference/commands-and-directives.md)
|
||||||
@@ -0,0 +1,56 @@
|
|||||||
|
# Install & onboard
|
||||||
|
|
||||||
|
You’ll install Takopi, connect it to Telegram, and generate a working `takopi.toml`.
|
||||||
|
|
||||||
|
## Prerequisites
|
||||||
|
|
||||||
|
- A Telegram account
|
||||||
|
- Python 3.14+ and `uv`
|
||||||
|
- At least one supported engine CLI on your `PATH` (`codex`, `claude`, `opencode`, or `pi`)
|
||||||
|
|
||||||
|
## 1) Install Takopi
|
||||||
|
|
||||||
|
```sh
|
||||||
|
uv tool install -U takopi
|
||||||
|
```
|
||||||
|
|
||||||
|
## 2) Run onboarding
|
||||||
|
|
||||||
|
Start Takopi:
|
||||||
|
|
||||||
|
```sh
|
||||||
|
takopi
|
||||||
|
```
|
||||||
|
|
||||||
|
If you want to re-run onboarding later:
|
||||||
|
|
||||||
|
```sh
|
||||||
|
takopi --onboard
|
||||||
|
```
|
||||||
|
|
||||||
|
The wizard walks you through:
|
||||||
|
|
||||||
|
1. Creating a bot token via [@BotFather](https://t.me/BotFather)
|
||||||
|
2. Capturing your `chat_id` (it listens for a message from you)
|
||||||
|
3. Choosing a default engine
|
||||||
|
|
||||||
|
Your configuration lives at `~/.takopi/takopi.toml`.
|
||||||
|
|
||||||
|
## 3) Verify minimal config
|
||||||
|
|
||||||
|
After onboarding you should have something like:
|
||||||
|
|
||||||
|
```toml
|
||||||
|
default_engine = "codex"
|
||||||
|
transport = "telegram"
|
||||||
|
|
||||||
|
[transports.telegram]
|
||||||
|
bot_token = "123456789:ABCdefGHIjklMNOpqrsTUVwxyz"
|
||||||
|
chat_id = 123456789
|
||||||
|
```
|
||||||
|
|
||||||
|
## Next
|
||||||
|
|
||||||
|
- [First run](first-run.md)
|
||||||
|
- [Config reference](../reference/config.md)
|
||||||
|
|
||||||
+19
-539
@@ -1,546 +1,26 @@
|
|||||||
# Takopi User Guide
|
# Takopi User Guide (moved)
|
||||||
|
|
||||||
Takopi is a command-line tool that lets you control coding agents—like Codex, Claude, and others—through Telegram. Send a message, and takopi runs the agent in your repo, streaming progress back to your chat. It supports multi-repo workflows, git worktrees, and per-project routing.
|
This guide has been reorganized into smaller Diátaxis pages.
|
||||||
|
|
||||||
This guide starts simple and layers on features as you go. Jump to any section or read straight through.
|
## Start here
|
||||||
|
|
||||||
## Prerequisites
|
- [Install & onboard](tutorials/install-and-onboard.md)
|
||||||
|
- [First run](tutorials/first-run.md)
|
||||||
|
|
||||||
Before you begin, make sure you have:
|
## Common tasks
|
||||||
|
|
||||||
- A Telegram account
|
- [Switch engines](how-to/switch-engines.md)
|
||||||
- Python 3.14+ and `uv` installed
|
- [Projects](how-to/projects.md)
|
||||||
- At least one supported agent CLI installed and on your `PATH` (codex, claude, opencode, pi)
|
- [Worktrees](how-to/worktrees.md)
|
||||||
- Basic familiarity with git (especially if you plan to use worktrees)
|
- [Route by chat](how-to/route-by-chat.md)
|
||||||
|
- [Topics](how-to/topics.md)
|
||||||
|
- [Voice notes](how-to/voice-notes.md)
|
||||||
|
- [File transfer](how-to/file-transfer.md)
|
||||||
|
- [Schedule tasks](how-to/schedule-tasks.md)
|
||||||
|
- [Troubleshooting](how-to/troubleshooting.md)
|
||||||
|
|
||||||
## Key concepts
|
## Reference
|
||||||
|
|
||||||
A few terms you'll see throughout:
|
- [Configuration](reference/config.md)
|
||||||
|
- [Commands & directives](reference/commands-and-directives.md)
|
||||||
| Term | Meaning |
|
- [Context resolution](reference/context-resolution.md)
|
||||||
|------|---------|
|
|
||||||
| **Engine** | A coding agent backend (Codex, Claude, opencode, pi) |
|
|
||||||
| **Project** | A repo/workdir alias used for routing (can set default engine, worktrees, chat ID) |
|
|
||||||
| **Worktree** | A git feature that lets you check out multiple branches simultaneously in separate directories |
|
|
||||||
| **Topic** | A Telegram forum thread bound to a project/branch; stores per-topic resume tokens |
|
|
||||||
| **Resume token** | State that allows an engine to continue from where it left off |
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## How conversations work
|
|
||||||
|
|
||||||
Takopi is **stateless by default**. Each message starts a new engine session unless a resume token is present.
|
|
||||||
|
|
||||||
To continue a session:
|
|
||||||
|
|
||||||
- **Reply** to a bot message. Takopi reads the resume token from the footer and resumes that session.
|
|
||||||
- **Forum topics** (optional) store resume tokens per topic and auto-resume for new messages in that topic.
|
|
||||||
Reset with `/new`.
|
|
||||||
- **Chat sessions** (optional) store one resume token per chat (per sender in groups) so new messages
|
|
||||||
auto-resume without replying. Enable with `session_mode = "chat"` and reset with `/new`.
|
|
||||||
State is stored in `telegram_chat_sessions_state.json`.
|
|
||||||
You can hide resume lines by setting `[transports.telegram].show_resume_line = false`
|
|
||||||
when auto-resume is available and a project context is resolved.
|
|
||||||
|
|
||||||
Reply-to-continue always works, even if chat sessions or topics are enabled.
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 1. Installation and setup
|
|
||||||
|
|
||||||
Install takopi with:
|
|
||||||
|
|
||||||
```sh
|
|
||||||
uv tool install -U takopi
|
|
||||||
```
|
|
||||||
|
|
||||||
Run it once to start the onboarding wizard:
|
|
||||||
|
|
||||||
```sh
|
|
||||||
takopi
|
|
||||||
```
|
|
||||||
|
|
||||||
The wizard walks you through:
|
|
||||||
|
|
||||||
1. Creating a Telegram bot token via [@BotFather](https://t.me/BotFather)
|
|
||||||
2. Capturing your `chat_id` (the wizard listens for a message from you)
|
|
||||||
3. Choosing a default engine
|
|
||||||
|
|
||||||
To re-run onboarding later, use `takopi --onboard`.
|
|
||||||
|
|
||||||
Your configuration is stored at `~/.takopi/takopi.toml`.
|
|
||||||
|
|
||||||
### Minimal configuration
|
|
||||||
|
|
||||||
After onboarding, your config looks something like this:
|
|
||||||
|
|
||||||
```toml
|
|
||||||
default_engine = "codex"
|
|
||||||
transport = "telegram"
|
|
||||||
|
|
||||||
[transports.telegram]
|
|
||||||
bot_token = "123456789:ABCdefGHIjklMNOpqrsTUVwxyz"
|
|
||||||
chat_id = 123456789
|
|
||||||
```
|
|
||||||
|
|
||||||
Optional: split long final responses instead of trimming them:
|
|
||||||
|
|
||||||
```toml
|
|
||||||
[transports.telegram]
|
|
||||||
message_overflow = "split" # trim | split
|
|
||||||
```
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 2. Your first handoff
|
|
||||||
|
|
||||||
The simplest workflow:
|
|
||||||
|
|
||||||
1. `cd` into any git repository
|
|
||||||
2. Run `takopi`
|
|
||||||
3. Send a message to your bot
|
|
||||||
|
|
||||||
Takopi streams progress in the chat and sends a final response when the agent finishes.
|
|
||||||
|
|
||||||
### Basic controls
|
|
||||||
|
|
||||||
- **Reply** to a bot message to continue the session (takopi reads the resume token in the footer)
|
|
||||||
- **Cancel** a run by clicking the cancel button or replying to the progress message with `/cancel`
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 3. Switching engines
|
|
||||||
|
|
||||||
Prefix your message with an engine directive to override the default:
|
|
||||||
|
|
||||||
```
|
|
||||||
/codex hard reset the timeline
|
|
||||||
/claude shrink and store artifacts forever
|
|
||||||
/opencode hide their paper until they reply
|
|
||||||
/pi render a diorama of this timeline
|
|
||||||
```
|
|
||||||
|
|
||||||
Directives are only parsed at the start of the first non-empty line.
|
|
||||||
|
|
||||||
### Default agent per chat or topic
|
|
||||||
|
|
||||||
Use `/agent` to view or set a persistent default for the current scope:
|
|
||||||
|
|
||||||
```
|
|
||||||
/agent
|
|
||||||
/agent set claude
|
|
||||||
/agent clear
|
|
||||||
```
|
|
||||||
|
|
||||||
- Inside a forum topic, `/agent set` affects that topic.
|
|
||||||
- In normal chats, it affects the whole chat.
|
|
||||||
- In group chats, only admins can change defaults.
|
|
||||||
|
|
||||||
Precedence (highest to lowest): resume token → `/engine` directive → topic default → chat default → project default → global default.
|
|
||||||
|
|
||||||
### Setting up engines
|
|
||||||
|
|
||||||
Takopi shells out to the agent CLIs. Install them and make sure they're on your `PATH`
|
|
||||||
(codex, claude, opencode, pi). Authentication is handled by each CLI (login,
|
|
||||||
config files, or environment variables).
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 4. Projects
|
|
||||||
|
|
||||||
For repos you work with often, register them as projects:
|
|
||||||
|
|
||||||
```sh
|
|
||||||
cd ~/dev/happy-gadgets
|
|
||||||
takopi init happy-gadgets
|
|
||||||
```
|
|
||||||
|
|
||||||
This adds a project entry to your config (for example):
|
|
||||||
|
|
||||||
```toml
|
|
||||||
[projects.happy-gadgets]
|
|
||||||
path = "~/dev/happy-gadgets"
|
|
||||||
```
|
|
||||||
|
|
||||||
Now you can target it from anywhere using the `/project` directive:
|
|
||||||
|
|
||||||
```
|
|
||||||
/happy-gadgets pinky-link two threads
|
|
||||||
```
|
|
||||||
|
|
||||||
If you expect to add or edit projects while takopi is running, enable config
|
|
||||||
watching so changes are picked up automatically:
|
|
||||||
|
|
||||||
```toml
|
|
||||||
watch_config = true
|
|
||||||
```
|
|
||||||
|
|
||||||
### Project-specific settings
|
|
||||||
|
|
||||||
Projects can override global defaults:
|
|
||||||
|
|
||||||
```toml
|
|
||||||
[projects.happy-gadgets]
|
|
||||||
path = "~/dev/happy-gadgets"
|
|
||||||
default_engine = "claude"
|
|
||||||
worktrees_dir = ".worktrees"
|
|
||||||
worktree_base = "master"
|
|
||||||
```
|
|
||||||
|
|
||||||
### Setting a default project
|
|
||||||
|
|
||||||
If you mostly work in one repo:
|
|
||||||
|
|
||||||
```toml
|
|
||||||
default_project = "happy-gadgets"
|
|
||||||
```
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 5. Worktrees
|
|
||||||
|
|
||||||
Worktrees let you work on multiple branches without switching back and forth. Use `@branch` to run a task in a dedicated worktree:
|
|
||||||
|
|
||||||
```
|
|
||||||
/happy-gadgets @feat/memory-box freeze artifacts forever
|
|
||||||
```
|
|
||||||
|
|
||||||
Takopi creates (or reuses) a worktree at:
|
|
||||||
|
|
||||||
```
|
|
||||||
<worktrees_root>/<branch>
|
|
||||||
```
|
|
||||||
|
|
||||||
`worktrees_root` is `<project.path>/<worktrees_dir>` unless `worktrees_dir` is an
|
|
||||||
absolute path. If the branch matches the repo's current branch, Takopi runs in the
|
|
||||||
main repo instead of creating a new worktree.
|
|
||||||
|
|
||||||
### Worktree configuration
|
|
||||||
|
|
||||||
```toml
|
|
||||||
[projects.happy-gadgets]
|
|
||||||
path = "~/dev/happy-gadgets"
|
|
||||||
worktrees_dir = ".worktrees" # relative to project path
|
|
||||||
worktree_base = "master" # base branch for new worktrees
|
|
||||||
```
|
|
||||||
|
|
||||||
To avoid `.worktrees/` showing up as untracked, add it to your global gitignore:
|
|
||||||
|
|
||||||
```sh
|
|
||||||
git config --global core.excludesfile ~/.config/git/ignore
|
|
||||||
echo ".worktrees/" >> ~/.config/git/ignore
|
|
||||||
```
|
|
||||||
|
|
||||||
### Context persistence
|
|
||||||
|
|
||||||
Takopi adds a `ctx:` footer to messages with project and branch info. When you reply, this context carries forward—no need to repeat `/project @branch` each time.
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 6. Per-project chat routing
|
|
||||||
|
|
||||||
Give each project its own Telegram chat:
|
|
||||||
|
|
||||||
```sh
|
|
||||||
takopi chat-id --project happy-gadgets
|
|
||||||
```
|
|
||||||
|
|
||||||
Send any message in the target chat. Takopi captures the `chat_id` and updates your config:
|
|
||||||
|
|
||||||
```toml
|
|
||||||
[projects.happy-gadgets]
|
|
||||||
path = "~/dev/happy-gadgets"
|
|
||||||
chat_id = -1001234567890
|
|
||||||
```
|
|
||||||
|
|
||||||
Messages from that chat automatically route to the project.
|
|
||||||
|
|
||||||
### Rules for chat IDs
|
|
||||||
|
|
||||||
- Each `projects.*.chat_id` must be unique
|
|
||||||
- Project chat IDs must not match `transports.telegram.chat_id`
|
|
||||||
- Telegram uses positive IDs for private chats and negative IDs for groups/supergroups
|
|
||||||
|
|
||||||
### Capture a chat ID without saving
|
|
||||||
|
|
||||||
To see a chat ID without writing to config:
|
|
||||||
|
|
||||||
```sh
|
|
||||||
takopi chat-id
|
|
||||||
```
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 7. Topics
|
|
||||||
|
|
||||||
Topics bind Telegram forum threads to specific project/branch contexts. They also preserve resume tokens and can store a default agent per topic.
|
|
||||||
|
|
||||||
### Enabling topics
|
|
||||||
|
|
||||||
```toml
|
|
||||||
[transports.telegram.topics]
|
|
||||||
enabled = true
|
|
||||||
```
|
|
||||||
|
|
||||||
Your bot needs **Manage Topics** permission in the group.
|
|
||||||
|
|
||||||
If any `projects.<alias>.chat_id` are configured, topics are managed in those
|
|
||||||
project chats; otherwise topics are managed in the main chat.
|
|
||||||
|
|
||||||
### Topic behavior
|
|
||||||
|
|
||||||
```
|
|
||||||
┌────────────────────────────┐
|
|
||||||
│ takopi projects │
|
|
||||||
├────────────────────────────┤
|
|
||||||
│ takopi @master │
|
|
||||||
│ takopi @feat/topics │
|
|
||||||
│ happy-gadgets @master │
|
|
||||||
│ happy-gadgets @feat/camera │
|
|
||||||
└────────────────────────────┘
|
|
||||||
```
|
|
||||||
|
|
||||||
Each project can have its own forum-enabled supergroup. Topics still
|
|
||||||
include the project name for consistency, but the project is inferred from the
|
|
||||||
chat. Regular messages in that chat also infer the project, so `/project` is
|
|
||||||
usually optional.
|
|
||||||
|
|
||||||
```
|
|
||||||
┌────────────────────────────────┐ ┌───────────────────────────────────┐
|
|
||||||
│ takopi │ │ happy-gadgets │
|
|
||||||
├────────────────────────────────┤ ├───────────────────────────────────┤
|
|
||||||
│ takopi @master │ │ happy-gadgets @master │
|
|
||||||
│ takopi @feat/topics │ │ happy-gadgets @feat/happy-camera │
|
|
||||||
│ takopi @feat/voice │ │ happy-gadgets @feat/memory-box │
|
|
||||||
└────────────────────────────────┘ └───────────────────────────────────┘
|
|
||||||
```
|
|
||||||
|
|
||||||
### Topic commands
|
|
||||||
|
|
||||||
Run these inside a topic thread:
|
|
||||||
|
|
||||||
| Command | Description |
|
|
||||||
|---------|-------------|
|
|
||||||
| `/topic <project> @branch` | Create a new topic bound to context |
|
|
||||||
| `/ctx` | Show the current binding |
|
|
||||||
| `/ctx set <project> @branch` | Update the binding |
|
|
||||||
| `/ctx clear` | Remove the binding |
|
|
||||||
| `/new` | Clear resume tokens for this topic |
|
|
||||||
|
|
||||||
In project chats, omit the project: `/topic @branch` or `/ctx set @branch`.
|
|
||||||
|
|
||||||
### Configuration examples
|
|
||||||
|
|
||||||
**Main chat only:**
|
|
||||||
|
|
||||||
```toml
|
|
||||||
[transports.telegram]
|
|
||||||
chat_id = -1001234567890
|
|
||||||
# show_resume_line = false
|
|
||||||
|
|
||||||
[transports.telegram.topics]
|
|
||||||
enabled = true
|
|
||||||
```
|
|
||||||
|
|
||||||
**Project chats:**
|
|
||||||
|
|
||||||
```toml
|
|
||||||
[transports.telegram]
|
|
||||||
chat_id = 123456789 # main chat (private, for non-project messages)
|
|
||||||
# show_resume_line = false
|
|
||||||
|
|
||||||
[transports.telegram.topics]
|
|
||||||
enabled = true
|
|
||||||
|
|
||||||
[projects.takopi]
|
|
||||||
path = "~/dev/takopi"
|
|
||||||
chat_id = -1001111111111 # forum-enabled group
|
|
||||||
```
|
|
||||||
|
|
||||||
Topic state is stored in `telegram_topics_state.json` next to your config file. Chat defaults live in `telegram_chat_prefs_state.json`.
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 8. Voice notes
|
|
||||||
|
|
||||||
Dictate tasks instead of typing:
|
|
||||||
|
|
||||||
```toml
|
|
||||||
[transports.telegram]
|
|
||||||
voice_transcription = true
|
|
||||||
voice_transcription_model = "gpt-4o-mini-transcribe" # optional
|
|
||||||
```
|
|
||||||
|
|
||||||
Set `OPENAI_API_KEY` in your environment (uses OpenAI's transcription API with the
|
|
||||||
`gpt-4o-mini-transcribe` model by default). To use a local OpenAI-compatible
|
|
||||||
Whisper server, also set `OPENAI_BASE_URL` (for example, `http://localhost:8000/v1`)
|
|
||||||
and a dummy `OPENAI_API_KEY` if your server ignores it. If your server requires a
|
|
||||||
specific model name, set `voice_transcription_model` accordingly (for example,
|
|
||||||
`whisper-1`).
|
|
||||||
|
|
||||||
When you send a voice note, takopi transcribes it and runs the result as a normal text message. If transcription fails, you'll get an error message and the run is skipped.
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 9. File transfer
|
|
||||||
|
|
||||||
Upload files into the active repo/worktree or fetch files back into Telegram.
|
|
||||||
|
|
||||||
### Upload a file
|
|
||||||
|
|
||||||
Send a document with a caption:
|
|
||||||
|
|
||||||
```
|
|
||||||
/file put <path>
|
|
||||||
```
|
|
||||||
|
|
||||||
Examples:
|
|
||||||
|
|
||||||
```
|
|
||||||
/file put docs/spec.pdf
|
|
||||||
/file put /happy-gadgets @feat/camera assets/logo.png
|
|
||||||
```
|
|
||||||
|
|
||||||
If you send a file **without a caption**, takopi saves it to:
|
|
||||||
|
|
||||||
```
|
|
||||||
incoming/<original_filename>
|
|
||||||
```
|
|
||||||
|
|
||||||
If you set `auto_put_mode = "prompt"`, a caption/question will start a run
|
|
||||||
immediately after upload, with the prompt annotated with the uploaded path.
|
|
||||||
|
|
||||||
Use `--force` to overwrite an existing file:
|
|
||||||
|
|
||||||
```
|
|
||||||
/file put --force docs/spec.pdf
|
|
||||||
```
|
|
||||||
|
|
||||||
### Fetch a file
|
|
||||||
|
|
||||||
Send:
|
|
||||||
|
|
||||||
```
|
|
||||||
/file get <path>
|
|
||||||
```
|
|
||||||
|
|
||||||
Directories are zipped automatically.
|
|
||||||
|
|
||||||
### File transfer config
|
|
||||||
|
|
||||||
```toml
|
|
||||||
[transports.telegram.files]
|
|
||||||
enabled = true
|
|
||||||
auto_put = true
|
|
||||||
auto_put_mode = "upload"
|
|
||||||
uploads_dir = "incoming"
|
|
||||||
allowed_user_ids = [123456789]
|
|
||||||
deny_globs = [".git/**", ".env", ".envrc", "**/*.pem", "**/.ssh/**"]
|
|
||||||
```
|
|
||||||
|
|
||||||
Notes:
|
|
||||||
- File transfer is **disabled by default**.
|
|
||||||
- If `allowed_user_ids` is empty, private chats are allowed and group usage requires admin privileges.
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 10. Configuration reference
|
|
||||||
|
|
||||||
Full example with all options:
|
|
||||||
|
|
||||||
```toml
|
|
||||||
# Global defaults
|
|
||||||
default_engine = "codex"
|
|
||||||
default_project = "takopi"
|
|
||||||
transport = "telegram"
|
|
||||||
watch_config = true # hot-reload on config changes (except transport)
|
|
||||||
|
|
||||||
[transports.telegram]
|
|
||||||
bot_token = "123456789:ABCdefGHIjklMNOpqrsTUVwxyz"
|
|
||||||
chat_id = 123456789
|
|
||||||
voice_transcription = true
|
|
||||||
voice_transcription_model = "gpt-4o-mini-transcribe"
|
|
||||||
session_mode = "stateless" # or "chat" for auto-resume per chat
|
|
||||||
show_resume_line = true
|
|
||||||
|
|
||||||
[transports.telegram.files]
|
|
||||||
enabled = true
|
|
||||||
auto_put = true
|
|
||||||
auto_put_mode = "upload"
|
|
||||||
uploads_dir = "incoming"
|
|
||||||
allowed_user_ids = [123456789]
|
|
||||||
deny_globs = [".git/**", ".env", ".envrc", "**/*.pem", "**/.ssh/**"]
|
|
||||||
|
|
||||||
[transports.telegram.topics]
|
|
||||||
enabled = true
|
|
||||||
scope = "auto"
|
|
||||||
|
|
||||||
# Project definitions
|
|
||||||
[projects.takopi]
|
|
||||||
path = "~/dev/takopi"
|
|
||||||
default_engine = "codex"
|
|
||||||
worktrees_dir = ".worktrees"
|
|
||||||
worktree_base = "master"
|
|
||||||
# chat_id = -1001234567890 # optional: dedicated chat
|
|
||||||
|
|
||||||
[projects.happy-planet]
|
|
||||||
path = "~/dev/happy-planet"
|
|
||||||
default_engine = "claude"
|
|
||||||
worktrees_dir = "~/.takopi/worktrees/happy-planet"
|
|
||||||
worktree_base = "develop"
|
|
||||||
```
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 11. Command cheatsheet
|
|
||||||
|
|
||||||
### Message directives
|
|
||||||
|
|
||||||
| Directive | Example | Description |
|
|
||||||
|-----------|---------|-------------|
|
|
||||||
| `/engine` | `/codex make threads resolve their differences` | Use a specific engine |
|
|
||||||
| `/project` | `/happy-gadgets add escape-pod` | Target a project |
|
|
||||||
| `@branch` | `@feat/happy-camera rewind to checkpoint` | Run in a worktree |
|
|
||||||
| Combined | `/happy-gadgets @feat/flower-pin observe unseen` | Project + branch |
|
|
||||||
|
|
||||||
### In-chat commands
|
|
||||||
|
|
||||||
| Command | Description |
|
|
||||||
|---------|-------------|
|
|
||||||
| `/cancel` | Reply to the progress message to stop the current run |
|
|
||||||
| `/file put <path>` | Upload a document into the repo/worktree |
|
|
||||||
| `/file get <path>` | Fetch a file (directories are zipped) |
|
|
||||||
| `/agent` | Show/set the default agent for the current scope |
|
|
||||||
| `/topic <project> @branch` | Create/bind a topic |
|
|
||||||
| `/ctx` | Show current context |
|
|
||||||
| `/ctx set <project> @branch` | Update context binding |
|
|
||||||
| `/ctx clear` | Remove context binding |
|
|
||||||
| `/new` | Clear stored resume tokens (topic or chat session) |
|
|
||||||
|
|
||||||
### CLI commands
|
|
||||||
|
|
||||||
| Command | Description |
|
|
||||||
|---------|-------------|
|
|
||||||
| `takopi` | Start the bot (runs onboarding if first time) |
|
|
||||||
| `takopi --onboard` | Re-run onboarding wizard |
|
|
||||||
| `takopi init <alias>` | Register current directory as a project |
|
|
||||||
| `takopi chat-id` | Capture a chat ID |
|
|
||||||
| `takopi chat-id --project <alias>` | Set a project's chat ID |
|
|
||||||
| `takopi --debug` | Write debug logs to `debug.log` |
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 12. Tips
|
|
||||||
|
|
||||||
### Schedule tasks with Telegram
|
|
||||||
|
|
||||||
Telegram has a native message scheduling feature that works seamlessly with takopi. Long-press the send button and choose "Schedule Message" to run tasks at a specific time. You can also set up recurring schedules (daily, weekly, etc.) for automated workflows.
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 13. Troubleshooting
|
|
||||||
|
|
||||||
If something isn't working, rerun with `takopi --debug` and check `debug.log`
|
|
||||||
for errors. Include it when reporting issues.
|
|
||||||
|
|||||||
+99
-27
@@ -34,6 +34,25 @@ markdown_extensions:
|
|||||||
|
|
||||||
plugins:
|
plugins:
|
||||||
- search
|
- search
|
||||||
|
- redirects:
|
||||||
|
redirect_maps:
|
||||||
|
architecture.md: explanation/architecture.md
|
||||||
|
specification.md: reference/specification.md
|
||||||
|
public-api.md: reference/plugin-api.md
|
||||||
|
transports/telegram.md: reference/transports/telegram.md
|
||||||
|
adding-a-runner.md: how-to/add-a-runner.md
|
||||||
|
projects.md: reference/context-resolution.md
|
||||||
|
runner/claude/claude-runner.md: reference/runners/claude/runner.md
|
||||||
|
runner/claude/claude-stream-json-cheatsheet.md: reference/runners/claude/stream-json-cheatsheet.md
|
||||||
|
runner/claude/claude-takopi-events.md: reference/runners/claude/takopi-events.md
|
||||||
|
runner/codex/exec-json-cheatsheet.md: reference/runners/codex/exec-json-cheatsheet.md
|
||||||
|
runner/codex/codex-takopi-events.md: reference/runners/codex/takopi-events.md
|
||||||
|
runner/opencode/opencode-runner.md: reference/runners/opencode/runner.md
|
||||||
|
runner/opencode/opencode-stream-json-cheatsheet.md: reference/runners/opencode/stream-json-cheatsheet.md
|
||||||
|
runner/opencode/opencode-takopi-events.md: reference/runners/opencode/takopi-events.md
|
||||||
|
runner/pi/pi-runner.md: reference/runners/pi/runner.md
|
||||||
|
runner/pi/pi-stream-json-cheatsheet.md: reference/runners/pi/stream-json-cheatsheet.md
|
||||||
|
runner/pi/pi-takopi-events.md: reference/runners/pi/takopi-events.md
|
||||||
- mkdocstrings:
|
- mkdocstrings:
|
||||||
handlers:
|
handlers:
|
||||||
python:
|
python:
|
||||||
@@ -42,36 +61,89 @@ plugins:
|
|||||||
show_source: false
|
show_source: false
|
||||||
- llmstxt:
|
- llmstxt:
|
||||||
markdown_description: |
|
markdown_description: |
|
||||||
Takopi is a Telegram bridge for coding agents. Use this index to find the most relevant docs pages.
|
Takopi is a Telegram bridge for coding agents.
|
||||||
For LLM ingestion, prefer the linked `.md` pages.
|
For LLM ingestion, prefer the linked `.md` pages (Diátaxis: Tutorials / How-to / Reference / Explanation).
|
||||||
full_output: llms-full.txt
|
full_output: llms-full.txt
|
||||||
sections:
|
sections:
|
||||||
Getting started:
|
Start here:
|
||||||
- index.md: Overview + quick start
|
- index.md: Overview + doc map
|
||||||
- user-guide.md: How to use Takopi end-to-end
|
- tutorials/install-and-onboard.md: Install Takopi + onboarding
|
||||||
- projects.md: Project aliases + worktrees
|
- tutorials/first-run.md: First run
|
||||||
Plugin development:
|
- how-to/projects.md: Projects (routing)
|
||||||
- plugins.md: Plugin system overview
|
- how-to/worktrees.md: Worktrees (@branch)
|
||||||
- public-api.md: Stable plugin API surface
|
- how-to/topics.md: Telegram topics
|
||||||
- adding-a-runner.md: Engine runner integration
|
- how-to/file-transfer.md: File transfer
|
||||||
Internals:
|
Reference:
|
||||||
- architecture.md: Core architecture
|
- reference/commands-and-directives.md: Commands, directives, and CLI flags
|
||||||
- specification.md: Behavior spec
|
- reference/config.md: Config schema and defaults
|
||||||
- developing.md: Developer guide
|
- reference/specification.md: Normative behavior spec
|
||||||
|
- reference/plugin-api.md: Stable plugin API surface
|
||||||
|
- reference/context-resolution.md: Directive parsing + worktree rules
|
||||||
|
- reference/transports/telegram.md: Telegram transport details
|
||||||
|
Contributors & agents:
|
||||||
|
- how-to/dev-setup.md: Dev setup + checks
|
||||||
|
- explanation/module-map.md: Module responsibilities
|
||||||
|
- reference/env-vars.md: Environment variables
|
||||||
|
- reference/agents/invariants.md: Critical invariants
|
||||||
|
- reference/agents/repo-map.md: Codebase entrypoints
|
||||||
Optional:
|
Optional:
|
||||||
- transports/telegram.md: Telegram transport details
|
- reference/runners/index.md: Runner docs index
|
||||||
|
- reference/runners/claude/stream-json-cheatsheet.md: Claude stream-json notes
|
||||||
|
- reference/runners/codex/exec-json-cheatsheet.md: Codex exec --json notes
|
||||||
|
|
||||||
nav:
|
nav:
|
||||||
- Home: index.md
|
- Home: index.md
|
||||||
- User guide: user-guide.md
|
- Tutorials:
|
||||||
- Projects: projects.md
|
- Overview: tutorials/index.md
|
||||||
- Plugins:
|
- Install & onboard: tutorials/install-and-onboard.md
|
||||||
- Plugin system: plugins.md
|
- First run: tutorials/first-run.md
|
||||||
- Public API: public-api.md
|
- How-to:
|
||||||
- Adding a runner: adding-a-runner.md
|
- Overview: how-to/index.md
|
||||||
- Internals:
|
- Switch engines: how-to/switch-engines.md
|
||||||
- Architecture: architecture.md
|
- Projects: how-to/projects.md
|
||||||
- Specification: specification.md
|
- Worktrees: how-to/worktrees.md
|
||||||
- Developing: developing.md
|
- Route by chat: how-to/route-by-chat.md
|
||||||
- Transports:
|
- Topics: how-to/topics.md
|
||||||
- Telegram: transports/telegram.md
|
- Voice notes: how-to/voice-notes.md
|
||||||
|
- File transfer: how-to/file-transfer.md
|
||||||
|
- Schedule tasks: how-to/schedule-tasks.md
|
||||||
|
- Troubleshooting: how-to/troubleshooting.md
|
||||||
|
- Write a plugin: how-to/write-a-plugin.md
|
||||||
|
- Add a runner: how-to/add-a-runner.md
|
||||||
|
- Dev setup: how-to/dev-setup.md
|
||||||
|
- Reference:
|
||||||
|
- Overview: reference/index.md
|
||||||
|
- Commands & directives: reference/commands-and-directives.md
|
||||||
|
- Configuration: reference/config.md
|
||||||
|
- Environment variables: reference/env-vars.md
|
||||||
|
- Specification: reference/specification.md
|
||||||
|
- Plugin API: reference/plugin-api.md
|
||||||
|
- Context resolution: reference/context-resolution.md
|
||||||
|
- Telegram transport: reference/transports/telegram.md
|
||||||
|
- Runners:
|
||||||
|
- Overview: reference/runners/index.md
|
||||||
|
- Claude:
|
||||||
|
- Runner: reference/runners/claude/runner.md
|
||||||
|
- Stream JSON cheatsheet: reference/runners/claude/stream-json-cheatsheet.md
|
||||||
|
- Takopi events: reference/runners/claude/takopi-events.md
|
||||||
|
- Codex:
|
||||||
|
- Exec JSON cheatsheet: reference/runners/codex/exec-json-cheatsheet.md
|
||||||
|
- Takopi events: reference/runners/codex/takopi-events.md
|
||||||
|
- OpenCode:
|
||||||
|
- Runner: reference/runners/opencode/runner.md
|
||||||
|
- Stream JSON cheatsheet: reference/runners/opencode/stream-json-cheatsheet.md
|
||||||
|
- Takopi events: reference/runners/opencode/takopi-events.md
|
||||||
|
- Pi:
|
||||||
|
- Runner: reference/runners/pi/runner.md
|
||||||
|
- Stream JSON cheatsheet: reference/runners/pi/stream-json-cheatsheet.md
|
||||||
|
- Takopi events: reference/runners/pi/takopi-events.md
|
||||||
|
- For agents:
|
||||||
|
- Agent entrypoint: reference/agents/index.md
|
||||||
|
- Repo map: reference/agents/repo-map.md
|
||||||
|
- Invariants: reference/agents/invariants.md
|
||||||
|
- Explanation:
|
||||||
|
- Overview: explanation/index.md
|
||||||
|
- Architecture: explanation/architecture.md
|
||||||
|
- Routing & sessions: explanation/routing-and-sessions.md
|
||||||
|
- Plugin system: explanation/plugin-system.md
|
||||||
|
- Module map: explanation/module-map.md
|
||||||
|
|||||||
@@ -63,6 +63,7 @@ docs = [
|
|||||||
"mkdocs>=1.6",
|
"mkdocs>=1.6",
|
||||||
"mkdocs-llmstxt>=0.5.0",
|
"mkdocs-llmstxt>=0.5.0",
|
||||||
"mkdocs-material>=9.7",
|
"mkdocs-material>=9.7",
|
||||||
|
"mkdocs-redirects>=1.2.2",
|
||||||
"mkdocstrings[python]>=0.29",
|
"mkdocstrings[python]>=0.29",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|||||||
@@ -46,17 +46,17 @@ register a project with `takopi init happy-gadgets`, then target it from anywher
|
|||||||
|
|
||||||
mention a branch to run an agent in a dedicated worktree `/happy-gadgets @feat/memory-box freeze artifacts forever`.
|
mention a branch to run an agent in a dedicated worktree `/happy-gadgets @feat/memory-box freeze artifacts forever`.
|
||||||
|
|
||||||
see [`docs/user-guide.md`](docs/user-guide.md) for configuration, worktrees, topics, file transfer, and more.
|
see [`docs/index.md`](docs/index.md) (or [`docs/user-guide.md`](docs/user-guide.md)) for configuration, worktrees, topics, file transfer, and more.
|
||||||
|
|
||||||
## plugins
|
## plugins
|
||||||
|
|
||||||
takopi supports entrypoint-based plugins for engines, transports, and commands.
|
takopi supports entrypoint-based plugins for engines, transports, and commands.
|
||||||
|
|
||||||
see [`docs/plugins.md`](docs/plugins.md) and [`docs/public-api.md`](docs/public-api.md).
|
see [`docs/how-to/write-a-plugin.md`](docs/how-to/write-a-plugin.md) and [`docs/reference/plugin-api.md`](docs/reference/plugin-api.md).
|
||||||
|
|
||||||
## development
|
## development
|
||||||
|
|
||||||
see [`docs/specification.md`](docs/specification.md) and [`docs/developing.md`](docs/developing.md).
|
see [`docs/reference/specification.md`](docs/reference/specification.md) and [`docs/developing.md`](docs/developing.md).
|
||||||
|
|
||||||
## community
|
## community
|
||||||
|
|
||||||
|
|||||||
@@ -544,6 +544,18 @@ wheels = [
|
|||||||
{ url = "https://files.pythonhosted.org/packages/5b/54/662a4743aa81d9582ee9339d4ffa3c8fd40a4965e033d77b9da9774d3960/mkdocs_material_extensions-1.3.1-py3-none-any.whl", hash = "sha256:adff8b62700b25cb77b53358dad940f3ef973dd6db797907c49e3c2ef3ab4e31", size = 8728, upload-time = "2023-11-22T19:09:43.465Z" },
|
{ url = "https://files.pythonhosted.org/packages/5b/54/662a4743aa81d9582ee9339d4ffa3c8fd40a4965e033d77b9da9774d3960/mkdocs_material_extensions-1.3.1-py3-none-any.whl", hash = "sha256:adff8b62700b25cb77b53358dad940f3ef973dd6db797907c49e3c2ef3ab4e31", size = 8728, upload-time = "2023-11-22T19:09:43.465Z" },
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "mkdocs-redirects"
|
||||||
|
version = "1.2.2"
|
||||||
|
source = { registry = "https://pypi.org/simple" }
|
||||||
|
dependencies = [
|
||||||
|
{ name = "mkdocs" },
|
||||||
|
]
|
||||||
|
sdist = { url = "https://files.pythonhosted.org/packages/f1/a8/6d44a6cf07e969c7420cb36ab287b0669da636a2044de38a7d2208d5a758/mkdocs_redirects-1.2.2.tar.gz", hash = "sha256:3094981b42ffab29313c2c1b8ac3969861109f58b2dd58c45fc81cd44bfa0095", size = 7162, upload-time = "2024-11-07T14:57:21.109Z" }
|
||||||
|
wheels = [
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/c4/ec/38443b1f2a3821bbcb24e46cd8ba979154417794d54baf949fefde1c2146/mkdocs_redirects-1.2.2-py3-none-any.whl", hash = "sha256:7dbfa5647b79a3589da4401403d69494bd1f4ad03b9c15136720367e1f340ed5", size = 6142, upload-time = "2024-11-07T14:57:19.143Z" },
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "mkdocstrings"
|
name = "mkdocstrings"
|
||||||
version = "1.0.0"
|
version = "1.0.0"
|
||||||
@@ -1029,6 +1041,7 @@ docs = [
|
|||||||
{ name = "mkdocs" },
|
{ name = "mkdocs" },
|
||||||
{ name = "mkdocs-llmstxt" },
|
{ name = "mkdocs-llmstxt" },
|
||||||
{ name = "mkdocs-material" },
|
{ name = "mkdocs-material" },
|
||||||
|
{ name = "mkdocs-redirects" },
|
||||||
{ name = "mkdocstrings", extra = ["python"] },
|
{ name = "mkdocstrings", extra = ["python"] },
|
||||||
]
|
]
|
||||||
|
|
||||||
@@ -1062,6 +1075,7 @@ docs = [
|
|||||||
{ name = "mkdocs", specifier = ">=1.6" },
|
{ name = "mkdocs", specifier = ">=1.6" },
|
||||||
{ name = "mkdocs-llmstxt", specifier = ">=0.5.0" },
|
{ name = "mkdocs-llmstxt", specifier = ">=0.5.0" },
|
||||||
{ name = "mkdocs-material", specifier = ">=9.7" },
|
{ name = "mkdocs-material", specifier = ">=9.7" },
|
||||||
|
{ name = "mkdocs-redirects", specifier = ">=1.2.2" },
|
||||||
{ name = "mkdocstrings", extras = ["python"], specifier = ">=0.29" },
|
{ name = "mkdocstrings", extras = ["python"], specifier = ">=0.29" },
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user