{"id":51146843,"url":"https://github.com/sleep3r/toodles","last_synced_at":"2026-06-26T03:30:19.935Z","repository":{"id":345060377,"uuid":"1183730355","full_name":"sleep3r/toodles","owner":"sleep3r","description":"🐩 Telegram bot wrapping gemini-cli — real-time streaming, voice transcription, file sharing \u0026 per-topic sessions. Built in Rust.","archived":false,"fork":false,"pushed_at":"2026-04-24T14:43:01.000Z","size":559,"stargazers_count":3,"open_issues_count":0,"forks_count":1,"subscribers_count":0,"default_branch":"main","last_synced_at":"2026-06-26T03:30:15.225Z","etag":null,"topics":["gemini","rust","telegram"],"latest_commit_sha":null,"homepage":"","language":"Rust","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/sleep3r.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":null,"dco":null,"cla":null}},"created_at":"2026-03-16T22:37:53.000Z","updated_at":"2026-06-16T17:11:58.000Z","dependencies_parsed_at":null,"dependency_job_id":null,"html_url":"https://github.com/sleep3r/toodles","commit_stats":null,"previous_names":["sleep3r/toodles"],"tags_count":0,"template":false,"template_full_name":null,"purl":"pkg:github/sleep3r/toodles","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/sleep3r%2Ftoodles","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/sleep3r%2Ftoodles/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/sleep3r%2Ftoodles/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/sleep3r%2Ftoodles/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/sleep3r","download_url":"https://codeload.github.com/sleep3r/toodles/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/sleep3r%2Ftoodles/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":34802384,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-05-26T15:22:16.424Z","status":"online","status_checked_at":"2026-06-26T02:00:06.560Z","response_time":106,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["gemini","rust","telegram"],"created_at":"2026-06-26T03:30:16.646Z","updated_at":"2026-06-26T03:30:19.457Z","avatar_url":"https://github.com/sleep3r.png","language":"Rust","funding_links":[],"categories":[],"sub_categories":[],"readme":"\u003cp align=\"center\"\u003e\n  \u003cimg src=\"https://em-content.zobj.net/source/apple/391/poodle_1f429.png\" width=\"96\" /\u003e\n\u003c/p\u003e\n\n\u003ch1 align=\"center\"\u003etoodles\u003c/h1\u003e\n\n\u003cp align=\"center\"\u003e\n  \u003cstrong\u003eTelegram × Gemini CLI — streamed responses, voice, file \u0026 photo sharing, local transcription\u003c/strong\u003e\n\u003c/p\u003e\n\n\u003cp align=\"center\"\u003e\n  \u003ca href=\"#-quick-start\"\u003eQuick Start\u003c/a\u003e ·\n  \u003ca href=\"#-features\"\u003eFeatures\u003c/a\u003e ·\n  \u003ca href=\"#%EF%B8%8F-configuration\"\u003eConfig\u003c/a\u003e ·\n  \u003ca href=\"#-architecture\"\u003eArchitecture\u003c/a\u003e\n\u003c/p\u003e\n\n\u003cp align=\"center\"\u003e\n  \u003cimg src=\"https://img.shields.io/badge/rust-%23000000.svg?style=for-the-badge\u0026logo=rust\u0026logoColor=white\" alt=\"Rust\" /\u003e\n  \u003cimg src=\"https://img.shields.io/badge/Telegram-2CA5E0?style=for-the-badge\u0026logo=telegram\u0026logoColor=white\" alt=\"Telegram\" /\u003e\n  \u003cimg src=\"https://img.shields.io/badge/Gemini-886FBF?style=for-the-badge\u0026logo=google-gemini\u0026logoColor=white\" alt=\"Gemini\" /\u003e\n  \u003cimg src=\"https://img.shields.io/badge/license-MIT-green?style=for-the-badge\" alt=\"License\" /\u003e\n\u003c/p\u003e\n\n---\n\nA Telegram bot written in Rust that wraps [`gemini-cli`](https://github.com/google-gemini/gemini-cli), letting you chat with Gemini AI directly from Telegram — with real-time streaming, voice transcription, photo \u0026 file analysis, and per-topic session isolation.\n\n## ✨ Features\n\n| | Feature | Details |\n|---|---|---|\n| 💬 | **Real-time streaming** | In-place draft updates while the model is generating, then final formatted commit |\n| ⏳ | **Instant feedback** | Immediate startup placeholder (`Подключаю Gemini-сессию…`) on cold starts |\n| 🛑 | **Stop generation** | Inline \"🛑 Stop\" button to cancel generation mid-stream |\n| 📝 | **Smart message splitting** | Long responses auto-split into multiple Telegram messages at newline boundaries — no truncation |\n| ⚠️ | **Error feedback** | Session startup and runtime errors are surfaced to the user (no silent failure) |\n| 📷 | **Photo analysis** | Send photos (including albums) — batched via aggregator and analyzed by Gemini Vision |\n| 📄 | **Document handling** | Send files (PDF, XLSX, etc.) — downloaded and forwarded to gemini-cli for processing |\n| 📎 | **File sharing** | Gemini can send files back via the `ATTACH_FILE:` protocol |\n| 🧩 | **Message aggregation** | Sequential messages within 1.5s are batched into a single prompt — handles albums, forwarded batches, and split messages |\n| 🔥 | **Warm session pool** | Keeps prewarmed ACP sessions to reduce first-response latency (`WARM_SESSION_POOL_SIZE`) |\n| ♻️ | **Session startup retries** | Automatic retry with backoff when ACP initialization fails transiently |\n| 🎙 | **Voice messages** | Transcribed locally via **Parakeet V3** or cloud via **OpenAI Whisper** |\n| 🧠 | **Local transcription** | Offline, no API keys — NVIDIA Parakeet ONNX (int8, ~478 MB) |\n| 📌 | **Forum topics** | Each Telegram topic gets an isolated gemini-cli session |\n| 🏷️ | **Thread auto-title** | First message sets topic title; later updates use recent-context summaries |\n| 🔄 | **Session management** | `/new` starts fresh, `/status` shows active count |\n| 🔒 | **Access control** | Optional user allowlist via `ALLOWED_USER_IDS` |\n| 🖥️ | **macOS background service** | launchd targets keep the bot running 24/7 with auto-restart |\n| 🧙 | **Setup wizard** | Interactive `--setup` generates `.env` with guided prompts |\n| 🎨 | **Customisable prompt** | System prompt configurable via `SYSTEM_PROMPT` in `.env` |\n| ✅ | **CI-gated** | `check + fmt + clippy + test` on every push/PR |\n\n## 🚀 Quick Start\n\n### Prerequisites\n\n- **Rust** ≥ 1.70 — [rustup.rs](https://rustup.rs)\n- **gemini-cli** — `npm install -g @google/gemini-cli \u0026\u0026 gemini`\n- **Telegram bot token** — [@BotFather](https://t.me/BotFather)\n- **ffmpeg** — `brew install ffmpeg` *(required for voice messages)*\n- *(Optional)* **OpenAI API key** — for cloud Whisper fallback\n\n### Install \u0026 Run\n\n```sh\ngit clone https://github.com/sleep3r/toodles\ncd toodles\n\n# Option A: Interactive setup wizard (recommended)\nmake setup\n\n# Option B: Manual config\ncp .env.example .env\n$EDITOR .env\n\n# Run\nmake run            # debug\nmake release        # optimized build\nmake run-release    # run optimized\n\n# Optional: install as macOS launchd service (24/7)\nmake service-install\n```\n\n### Run as macOS background service (launchd)\n\n```sh\nmake service-install   # build release + install + start\nmake service-status    # check launchd state\nmake service-logs      # tail bot logs\n```\n\n`service-install` copies your project `.env` into `~/.config/toodles/service.env`\nso launchd can read secrets consistently.\n\nAfter code changes:\n\n```sh\nmake service-update    # rebuild release + restart service\n```\n\nIf you change `.env`, run `make service-update` to sync it into the service env file.\n\nStop / remove service:\n\n```sh\nmake service-stop\nmake service-uninstall\n```\n\nOptional overrides (passed as Make variables):\n\n```sh\nmake LAUNCHD_LABEL=com.alex.toodles service-install\nmake TOODLES_ENV_FILE=/path/to/.env service-install\nmake LAUNCHD_WORKDIR=/Users/alexander service-install\n```\n\n## 💬 How It Works\n\n```\n ┌───────────┐        ┌──────────┐        ┌──────────────┐\n │ Telegram  │───────▶│ toodles  │───────▶│  gemini-cli  │\n │   user    │◀─ edit │  (Rust)  │◀─ pipe │  subprocess  │\n └───────────┘  msg   └──────────┘  stdout└──────────────┘\n```\n\n1. User sends a message (text, photo, document, or voice)\n2. Messages are aggregated within a 1.5s window (handles albums and split messages)\n3. On cold start, a startup status is shown while ACP session is created (or grabbed from warm pool)\n4. A draft placeholder with 🛑 **Stop** is attached and updated during generation\n5. User can press **Stop** at any time — generation is cancelled via `CancellationToken`\n6. Final response is committed with Markdown→Telegram HTML formatting and plain-text fallback\n7. Subsequent messages reuse the same topic/chat session automatically\n\n## 🎙 Voice Transcription\n\ntoodles supports two transcription backends:\n\n```\n┌────────────────────┐     ┌──────────────┐     ┌───────────┐\n│   Telegram Voice   │────▶│    ffmpeg     │────▶│ Parakeet  │──── text\n│    (OGG Opus)      │     │  (16kHz f32)  │     │   V3 🦜   │\n└────────────────────┘     └──────────────┘     └─────┬─────┘\n                                                      │ fallback\n                                                ┌─────▼─────┐\n                                                │  OpenAI    │\n                                                │ Whisper 🌐 │\n                                                └───────────┘\n```\n\n| Mode | Latency | Cost | Setup |\n|---|---|---|---|\n| **Local** (Parakeet V3) | ~2-5s | Free | `--setup` downloads 478 MB model |\n| **Cloud** (Whisper API) | ~1-3s | ~$0.006/min | Requires `OPENAI_API_KEY` |\n\nIf both are enabled, local transcription is tried first with automatic cloud fallback.\n\n## ⚙️ Configuration\n\nAll configuration is managed through environment variables or `.env`:\n\n```sh\n# Required\nTELEGRAM_BOT_TOKEN=123456:ABC-DEF...\n\n# Access control (leave empty for unrestricted)\nALLOWED_USER_IDS=123456789,987654321\n\n# Gemini CLI\nGEMINI_CLI_PATH=gemini                # path to binary\nGEMINI_CLI_COMMAND=gemini --acp       # optional full ACP command\nGEMINI_WORKING_DIR=/path/to/project   # optional cwd\nGEMINI_YOLO=true                      # optional auto-approve mode\nDRAFT_MODE=verbose                    # compact | verbose draft UX\nTHREAD_RENAME_EVERY=4                 # 0 disables auto-rename\nWARM_SESSION_POOL_SIZE=1              # 0 disables warm prewarmed pool\n\n# Optional: read additional settings from TOML\nTOODLES_CONFIG=~/.config/toodles/config.toml\n\n# System prompt — customise the bot's personality\nSYSTEM_PROMPT=You are a helpful AI assistant. Keep answers concise.\n\n# Voice — cloud (optional fallback)\nOPENAI_API_KEY=sk-...\n\n# Voice — local (recommended)\nUSE_LOCAL_TRANSCRIPTION=true\nMODELS_DIR=~/.toodles/models\n\n# Logging\nRUST_LOG=info\n```\n\n\u003e **💡 Tip:** Run `make setup` to generate this interactively!\n\n### Optional TOML config\n\nYou can also keep settings in `~/.config/toodles/config.toml`:\n\n```toml\nbot_token = \"123456:ABC-DEF...\"\ngemini_cli_command = \"gemini --acp\"\ngemini_working_dir = \"/path/to/project\"\ngemini_yolo = true\ndraft_mode = \"verbose\"\nthread_rename_every = 4\nwarm_session_pool_size = 1\n```\n\nYou can copy `config.example.toml` as a starting point.\n\n## 🤖 Bot Commands\n\n| Command | Description |\n|---|---|\n| `/start` | Get started 👋 |\n| `/new` | Start fresh 🔄 |\n| `/status` | Bot status 📊 |\n| `/thread` | Create forum thread 🧵 |\n| `/help` | Show commands 💡 |\n\n`/thread` works in forum-enabled supergroups where the bot has topic-management rights.\nYou can call `/thread` from both the main chat and existing topics; Toodles creates a new topic in the same group.\nThe first user message in a topic sets its initial title, then Toodles refreshes the title every `THREAD_RENAME_EVERY` messages using the recent message context.\n\n## 🧯 Cold-Start Tuning\n\nIf the first response sometimes takes too long:\n\n- Set `WARM_SESSION_POOL_SIZE=1` (or `2`) to keep prewarmed ACP sessions ready.\n- Keep `GEMINI_WORKING_DIR` on a local SSD path (avoid slow network mounts).\n- Check bot logs for repeated ACP initialize retries; transient failures are retried automatically.\n\nIf `/thread` fails with \"not enough rights to create a topic\", grant the bot admin permission to manage topics.\n\n## 📐 Architecture\n\n```\nsrc/\n├── main.rs             — entry point, dispatcher, bot commands\n├── config.rs           — Config from env + optional TOML (single gemini profile)\n├── session.rs          — ACP session lifecycle + per-chat/topic session mapping\n├── aggregator.rs       — message batching with debounce window + file guard ownership\n├── telegram_api.rs     — raw Telegram API (sendMessageDraft), global HTTP client\n├── setup.rs            — interactive setup wizard (--setup)\n├── transcription.rs    — Parakeet V3 engine + model download\n└── handlers/\n    ├── mod.rs           — CancelRegistry, inline stop button, draft streaming, message splitting, Markdown→HTML\n    ├── message.rs       — text message handler (with aggregation)\n    ├── document.rs      — document/file handler (download + aggregate + query)\n    ├── photo.rs         — photo handler (download + aggregate albums + query)\n    └── voice.rs         — voice handler (transcribe → query)\n```\n\n**Session lifecycle:**\n\n```mermaid\nstateDiagram-v2\n    [*] --\u003e New: /new or first message\n    New --\u003e Ready: session created\n    Ready --\u003e Query: user message\n    Query --\u003e Placeholder: ⏳ + 🛑 Stop button\n    Placeholder --\u003e Streaming: line-by-line via BufReader\n    Streaming --\u003e Cancelled: user clicks 🛑\n    Cancelled --\u003e Ready: ⬛ Generation stopped\n    Streaming --\u003e Ready: response committed (Markdown)\n    Ready --\u003e [*]: /new (reset)\n```\n\nEach chat or forum topic maps to an isolated ACP session. Queries are serialised per session via `tokio::sync::Mutex` and a per-session queue. Startup uses retries and an optional warm pool (`WARM_SESSION_POOL_SIZE`) to reduce first-token latency. During generation, the bot updates one placeholder message (draft UX), supports inline cancellation via `CancellationToken`, and commits a final Markdown→Telegram HTML response with plain-text fallback. Long responses are split across multiple Telegram messages at newline boundaries. Sequential messages and photo albums are aggregated via a 1.5s debounce window. Temporary files (photos, documents) are kept alive via `Arc\u003cTempFileGuard\u003e` until the query completes.\n\n## 🛠 Makefile\n\n```sh\nmake help          # show all targets\nmake build         # debug build\nmake release       # optimized build\nmake run           # run (debug)\nmake run-release   # run (release)\nmake setup         # interactive setup wizard\nmake test          # run tests\nmake lint          # clippy\nmake fmt           # format code\nmake clean         # clean artifacts\nmake service-install   # install/start launchd service\nmake service-sync-env  # copy .env into launchd service env\nmake service-update    # rebuild + restart launchd service\nmake service-stop      # stop launchd service\nmake service-status    # print launchd status\nmake service-logs      # tail service logs\nmake service-uninstall # remove launchd service\n```\n\n## 📄 License\n\nMIT — see [LICENSE](LICENSE).\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fsleep3r%2Ftoodles","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fsleep3r%2Ftoodles","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fsleep3r%2Ftoodles/lists"}