https://github.com/hah23255/kimi-to-im
Telegram bridge for the Kimi CLI β chat with Moonshot Kimi from your phone, sessions persist, single-user, ~1.7K LOC Python, systemd-supervised, MIT.
https://github.com/hah23255/kimi-to-im
ai-agent ai-cli asyncio chatbot cli-tool daemon gpg-signed homelab kimi-cli kimi-k2 llm moonshot-ai python self-hosted single-user systemd telegram-api telegram-bot telegram-bridge terminal-ai
Last synced: 3 days ago
JSON representation
Telegram bridge for the Kimi CLI β chat with Moonshot Kimi from your phone, sessions persist, single-user, ~1.7K LOC Python, systemd-supervised, MIT.
- Host: GitHub
- URL: https://github.com/hah23255/kimi-to-im
- Owner: hah23255
- License: mit
- Created: 2026-04-25T21:27:53.000Z (about 2 months ago)
- Default Branch: main
- Last Pushed: 2026-04-30T15:17:01.000Z (about 2 months ago)
- Last Synced: 2026-04-30T16:23:29.814Z (about 2 months ago)
- Topics: ai-agent, ai-cli, asyncio, chatbot, cli-tool, daemon, gpg-signed, homelab, kimi-cli, kimi-k2, llm, moonshot-ai, python, self-hosted, single-user, systemd, telegram-api, telegram-bot, telegram-bridge, terminal-ai
- Language: Python
- Homepage: https://github.com/hah23255/kimi-to-im
- Size: 94.7 KB
- Stars: 1
- Watchers: 0
- Forks: 0
- Open Issues: 0
-
Metadata Files:
- Readme: README.md
- Contributing: CONTRIBUTING.md
- License: LICENSE
- Security: SECURITY.md
Awesome Lists containing this project
README
# π± kimi-to-im
### Chat with [Kimi CLI](https://github.com/MoonshotAI/kimi-cli) from Telegram
**Self-hosted Β· single-user Β· ~1.7K LOC Python Β· systemd-supervised**
[](LICENSE)
[](https://www.python.org/downloads/)
[](https://www.freedesktop.org/wiki/Software/systemd/)
[](https://telegram.org/)
[](https://github.com/MoonshotAI/kimi-cli)
[](https://github.com/hah23255/kimi-to-im/actions/workflows/test.yml)
[](https://github.com/hah23255/kimi-to-im/stargazers)
[](https://github.com/hah23255/kimi-to-im/network/members)
---
## π‘ Why this exists
You already use Kimi CLI at your desk. This bridge lets you keep the **same conversation** going from your phone β over Telegram β without changing how Kimi runs locally. You send a message, the bridge spawns `kimi` on your machine, and the reply lands back in Telegram. Sessions persist per chat, so follow-ups pick up where you left off.
**Single-user, single-host, text-only by design.** No cloud component. No account system. The bot replies only to user IDs you explicitly whitelist.
---
## ποΈ Architecture
```mermaid
flowchart LR
User([π± You
on Telegram])
TG[Telegram
Bot API]
Bridge[bridge daemon
~1.7K LOC Python
systemd --user]
Kimi[kimi CLI
subprocess
per turn]
State[(state.json
chat β session)]
OAuth[~/.kimi/
credentials]
User -->|message| TG
TG -->|long-poll
getUpdates| Bridge
Bridge -->|spawn
--print -S sid| Kimi
Kimi -->|reasoning_content
+ output| Bridge
Bridge -->|sendMessage| TG
TG -->|reply| User
Bridge <-->|read/write| State
Kimi <-->|JWT auto-refresh| OAuth
style User fill:#26A5E4,color:#fff,stroke:#1a8cc4
style TG fill:#26A5E4,color:#fff,stroke:#1a8cc4
style Bridge fill:#FF6B35,color:#fff,stroke:#cc5028
style Kimi fill:#9B59B6,color:#fff,stroke:#7d3f95
style State fill:#34495E,color:#fff,stroke:#222
style OAuth fill:#34495E,color:#fff,stroke:#222
```
---
## π Turn-by-turn flow
```mermaid
sequenceDiagram
autonumber
participant U as π± User
(Telegram)
participant T as Telegram
API
participant D as bridge
daemon
participant K as kimi
CLI
participant S as state.json
U->>T: "Continue the analysisβ¦"
T-->>D: getUpdates β message
D->>D: is_authorized(user_id) ?
D->>S: lookup session_id by chat_id
S-->>D: sid = abc123β¦
D->>T: sendChatAction("typing")
par heartbeat (every 4s)
D->>T: typing
and progress notice (250s, 600s)
D-->>U: "π€ Still thinkingβ¦"
and kimi subprocess
D->>K: kimi --print -S abc123
K-->>K: K2.6 reasoning + tool calls
K-->>D: reply text
end
D->>T: sendMessage(reply)
T-->>U: π¬ Kimi's answer
D->>S: persist (no change, sid sticks)
```
---
## β‘ Quickstart (5 minutes)
```mermaid
flowchart TD
A[1\. Get bot token
from @BotFather] --> B[2\. Get your
Telegram user ID
from @userinfobot]
B --> C[3\. git clone +
install.sh]
C --> D[4\. Edit config.json
token + allowlist]
D --> E[5\. systemctl --user
start the unit]
E --> F[β
Send hello
to your bot]
style A fill:#26A5E4,color:#fff
style B fill:#26A5E4,color:#fff
style C fill:#9B59B6,color:#fff
style D fill:#9B59B6,color:#fff
style E fill:#27AE60,color:#fff
style F fill:#27AE60,color:#fff
```
You need: **Linux** with `systemd --user`, **Python 3.11+**, [`uv`](https://docs.astral.sh/uv/), and a working `kimi` CLI on your `PATH`. Full prerequisites and verification commands live in [`docs/deployment.md`](docs/deployment.md).
**1. Get a Telegram bot token.** Message [@BotFather](https://t.me/BotFather), send `/newbot`, follow the prompts. Copy the token.
**2. Get your Telegram user ID.** Message [@userinfobot](https://t.me/userinfobot). It replies with your numeric ID.
**3. Install.**
```sh
git clone https://github.com/hah23255/kimi-to-im.git ~/.kimi/plugins/telegram-bridge
cd ~/.kimi/plugins/telegram-bridge
bash install.sh
```
> Expected: a `.venv/` is created and a systemd user unit registered.
**4. Configure.**
```sh
cp config.example.json config.json
chmod 600 config.json
$EDITOR config.json # paste bot_token, add your Telegram user ID to allowed_user_ids
```
**5. Start.**
```sh
systemctl --user start kimi-telegram-bridge.service
```
> Expected: `systemctl --user is-active kimi-telegram-bridge.service` prints `active`. Send "hello" to your bot from Telegram β within ~10s the typing indicator appears, then a Kimi reply.
For a more detailed walk-through with pre-flight checks and smoke tests, see [`docs/deployment.md`](docs/deployment.md).
---
## β±οΈ Timing & timeouts
```mermaid
gantt
title Bridge timing budgets per Telegram turn
dateFormat ss
axisFormat %Ss
section Typing indicator
Refresh every 4s :active, 0, 4s
Refresh :active, 4, 4s
Refresh :active, 8, 4s
section Progress notices
"Still thinkingβ¦" @ 250s :crit, 250, 5s
"Still thinkingβ¦" @ 600s :crit, 600, 5s
section Kimi subprocess
Allowed work window :active, 0, 900s
Hard kill :crit, 900, 5s
section JWT
OAuth token (15min TTL) :active, 0, 900s
Auto-refresh :crit, 600, 1s
```
| Event | Time |
|---|---|
| Typing indicator refresh | every 4 s |
| First progress notice | 250 s (~4 min) |
| Second progress notice | 600 s (~10 min) |
| Kimi subprocess hard timeout | **900 s** (15 min, aligned with JWT TTL) |
| OAuth JWT auto-refresh cadence | every 10 min (TTL is 15 min) |
> The 15-minute ceiling is intentional β Kimi K2.6 with thinking on a 160 K-token context routinely needs 5β12 minutes per complex turn. We bound long enough for real work, short enough that truly hung subprocesses get cleaned up.
---
## π‘οΈ Defence-in-depth
```mermaid
graph TD
Inbound[Inbound
Telegram message]
Allowlist{user_id in
allowed_user_ids?}
ChatlistCheck{chat_id in
allowed_chat_ids?}
SessionCheck{session_id matches
uuid4 hex?}
Kimi[Spawn kimi]
Drop[Dropped silently
+ logged]
Inbound --> Allowlist
Allowlist -->|no| Drop
Allowlist -->|yes| ChatlistCheck
ChatlistCheck -->|no| Drop
ChatlistCheck -->|yes| SessionCheck
SessionCheck -->|no| Drop
SessionCheck -->|yes| Kimi
style Drop fill:#E74C3C,color:#fff
style Kimi fill:#27AE60,color:#fff
```
The systemd unit ships hardened by default:
| Layer | Mechanism |
|---|---|
| **Identity** | Default-deny allowlist on `allowed_user_ids` + `allowed_chat_ids` |
| **Subprocess argv** | `session_id` validated against uuid4-hex regex before being passed to `kimi` |
| **Network** | `RestrictAddressFamilies=AF_UNIX AF_INET AF_INET6` |
| **Filesystem** | `PrivateTmp`, `UMask=0077`, config 0600 |
| **Kernel surface** | `ProtectKernelTunables`, `ProtectKernelModules`, `LockPersonality` |
| **Syscalls** | `SystemCallFilter=@system-service ~@privileged ~@resources` |
| **Logs** | `httpx` INFO suppressed so bot token never lands in `bridge.log` |
| **Liveness** | `Restart=on-failure`, exit-124 timeout safety net |
Full security policy: [`SECURITY.md`](SECURITY.md). Audit findings: [`docs/security-scan.md`](docs/security-scan.md).
---
## π Repo at a glance
| | |
|---|---|
| **Language** | Python 3.11+ |
| **Source LOC** | 1,698 |
| **Test files** | 10 (full suite < 2 s) |
| **External runtime deps** | 1 (`httpx`) |
| **External system deps** | `kimi` CLI on `PATH`, systemd-user |
| **Lines per turn (avg request path)** | ~50 |
| **First-launch RAM** | ~22 MB (idle) |
| **Steady-state RAM** | ~80β200 MB depending on Telegram polling state |
```mermaid
pie title Source code distribution
"src/ daemon code" : 720
"tests/" : 880
"config + plugin glue" : 98
```
---
## π§° What it does and doesn't
This is intentionally a small, opinionated tool.
**It does:**
- β
Bridge Telegram β Kimi CLI as separate subprocesses per turn
- β
Persist session continuity per chat
- β
Run as a `systemctl --user` service with hardening
- β
Refresh the OAuth JWT automatically (10-min cadence, 15-min TTL)
- β
Stream typing indicator + progress notices for long turns
- β
Surface friendly error messages (no raw stderr leaks)
- β
Validate inputs against a default-deny allowlist
**It does NOT:**
- β Support Discord, Slack, Feishu, QQ, or any IM other than Telegram
- β Handle images, voice, or file uploads (text only)
- β Stream replies token-by-token (Kimi emits per-turn JSON, bridge sends per-turn)
- β Expose Kimi's internal tool calls or ask for permission before they run
- β Sync state between machines (one bridge per host)
- β Multi-user (architecturally single-user β by design, not laziness)
If you need any of these, this bridge is the wrong tool.
---
## βοΈ Configuration
The full reference lives in [`docs/operations.md`](docs/operations.md#configure-the-bridge). The minimum to know:
| Field | Required | Purpose |
|---|---|---|
| `telegram.bot_token` | yes | The string from BotFather. |
| `telegram.allowed_user_ids` | yes | Whitelist of Telegram user IDs. Empty = nobody can talk to the bot (default-deny). |
| `telegram.allowed_chat_ids` | recommended | Optional chat-level whitelist. Set to your DM's chat id (= your user id) so the bot won't respond inside groups. |
| `kimi.default_workdir` | no | Where Kimi runs. Defaults to Kimi's own default. |
| `kimi.model` | no | Empty = Kimi's default model. |
`config.json` is gitignored. Don't commit your token.
---
## π Documentation
| Document | Read this when... |
|---|---|
| [`docs/deployment.md`](docs/deployment.md) | You're installing for the first time. |
| [`docs/operations.md`](docs/operations.md) | You're running the bridge day-to-day, or troubleshooting. |
| [`docs/design.md`](docs/design.md) | You want to understand why the architecture looks the way it does. |
| [`docs/security-scan.md`](docs/security-scan.md) | You want the formal pre-publication audit findings. |
| [`SECURITY.md`](SECURITY.md) | You found a vulnerability or want the security policy. |
| [`CONTRIBUTING.md`](CONTRIBUTING.md) | You want to send a patch. |
---
## π€ Contributing
PRs welcome. Please read [`CONTRIBUTING.md`](CONTRIBUTING.md) and run the tests:
```sh
uv venv .venv --python 3.11
uv pip install -e ".[dev]"
.venv/bin/pytest -v
```
The full suite runs in **under 2 seconds** and is the gate for CI.
---
## π License
MIT β see [LICENSE](LICENSE).
---
**Built for one user, one host, one Telegram chat.**
**Not trying to be more than that.**
[Report an issue](https://github.com/hah23255/kimi-to-im/issues) Β· [Security policy](SECURITY.md) Β· [Discussions](https://github.com/hah23255/kimi-to-im/discussions)