== npm search: telegram pi-package ==
@grammyjs/types@grammyjs/types
Telegram Bot API type declarations for grammYTelegram Bot API type declarations for grammY
Version 3.26.0 published 2026-04-03 by knorpelsenf
Maintainers: knorpelsenf
Keywords: grammy telegram bot api types
https://npm.im/@grammyjs/types

@mariozechner/pi-coding-agent@mariozechner/pi-coding-agent
Coding agent CLI with read, bash, edit, write tools and session managementCoding agent CLI with read, bash, edit, write tools and session management
Version 0.71.1 published 2026-05-01 by badlogic
Maintainers: badlogic mitsuhiko
Keywords: coding-agent ai llm cli tui agent
https://npm.im/@mariozechner/pi-coding-agent

@telegraf/types@telegraf/types
Type declarations for the Telegram APIType declarations for the Telegram API
Version 9.2.1 published 2025-09-21 by mkrhere
Maintainers: telegraf-owner ejnshtein wojpawlik mkrhere
Keywords: telegram telegraf bot api types typings
https://npm.im/@telegraf/types

grammygrammy
The Telegram Bot Framework.The Telegram Bot Framework.
Version 1.42.0 published 2026-04-03 by GitHub Actions
Maintainers: knorpelsenf
Keywords: telegram bot api client framework library grammy
https://npm.im/grammy

node-telegram-bot-apinode-telegram-bot-api
Telegram Bot APITelegram Bot API
Version 0.67.0 published 2025-12-13 by gochomugo
Maintainers: gochomugo yagop
Keywords: telegram telegram bot telegram bot api bot
https://npm.im/node-telegram-bot-api

@telegram-apps/signals@telegram-apps/signals
TypeScript package implementing simple JavaScript signals.TypeScript package implementing simple JavaScript signals.
Version 1.1.2 published 2025-06-09 by qbnk
Maintainers: it-tonstudio-io
Keywords: telegram-mini-apps typescript signals
https://npm.im/@telegram-apps/signals

telegraftelegraf
Modern Telegram Bot FrameworkModern Telegram Bot Framework
Version 4.16.3 published 2024-02-29 by mkrhere
Maintainers: dotcypress mkrhere wojpawlik
Keywords: telegraf telegram telegram bot api bot botapi bot framework
https://npm.im/telegraf

@llblab/pi-telegram@llblab/pi-telegram
Better Telegram DM bridge extension for piBetter Telegram DM bridge extension for pi
Version 0.4.0 published 2026-04-28 by llblab
Maintainers: llblab
Keywords: pi-package pi telegram bot extension
https://npm.im/@llblab/pi-telegram

@telegram-apps/navigation@telegram-apps/navigation
Navigation functionality for Telegram Mini Apps.Navigation functionality for Telegram Mini Apps.
Version 1.0.14 published 2025-06-09 by qbnk
Maintainers: it-tonstudio-io
Keywords: telegram-mini-apps typescript navigation
https://npm.im/@telegram-apps/navigation

@telegram-apps/sdk@telegram-apps/sdk
TypeScript Source Development Kit for Telegram Mini Apps client application.TypeScript Source Development Kit for Telegram Mini Apps client application.
Version 3.11.8 published 2025-10-08 by sorawalker
Maintainers: it-tonstudio-io
Keywords: telegram-mini-apps typescript sdk
https://npm.im/@telegram-apps/sdk

@telegram-apps/sdk-react@telegram-apps/sdk-react
React bindings for Mini Apps client SDK. Contains hooks, components and other useful tools which allow usage of React along with Mini Apps client SDK.React bindings for Mini Apps client SDK. Contains hooks, components and other useful tools which allow usage of React along with Mini Apps client SDK.
Version 3.3.9 published 2025-10-08 by sorawalker
Maintainers: it-tonstudio-io
Keywords: telegram-mini-apps typescript sdk react
https://npm.im/@telegram-apps/sdk-react

ts-essentialsts-essentials
All essential TypeScript types in one placeAll essential TypeScript types in one place
Version 10.2.0 published 2026-04-26 by GitHub Actions
Maintainers: krzkaczor beraliv ts-essentials-bot
Keywords: typescript types essentials utils toolbox toolbelt lodash underscore
https://npm.im/ts-essentials

@chat-adapter/telegram@chat-adapter/telegram
Telegram adapter for chatTelegram adapter for chat
Version 4.27.0 published 2026-04-30 by GitHub Actions
Maintainers: matt.straka vercel-release-bot cramforce
Keywords: chat telegram bot adapter
https://npm.im/@chat-adapter/telegram

@stdlib/constants-float64-pi@stdlib/constants-float64-pi
π.π.
Version 0.2.3 published 2026-02-05 by stdlib-bot
Maintainers: stdlib-bot kgryte planeshifter rreusser
Keywords: stdlib stdmath constant const mathematics math pi ieee754 double dbl floating-point float64
https://npm.im/@stdlib/constants-float64-pi

@types/node-telegram-bot-api@types/node-telegram-bot-api
TypeScript definitions for node-telegram-bot-apiTypeScript definitions for node-telegram-bot-api
Version 0.64.14 published 2026-02-27 by types
Maintainers: types
https://npm.im/@types/node-telegram-bot-api

@inceptionstack/roundhouse@inceptionstack/roundhouse
Multi-platform chat gateway that routes messages through a configured AI agentMulti-platform chat gateway that routes messages through a configured AI agent
Version 0.3.26 published 2026-05-01 by royosherove
Maintainers: royosherove
Keywords: chat gateway telegram slack discord ai agent pi bot
https://npm.im/@inceptionstack/roundhouse

@bakachoda/casino-bot@bakachoda/casino-bot
Telegram bot that sends random animated dice emojisTelegram bot that sends random animated dice emojis
Version 1.1.0 published 2026-04-23 by bakachoda
Maintainers: bakachoda
Keywords: telegram bot casino game dice telegram-bot minigame animated-emoji slots bowling basketball darts football prediction
https://npm.im/@bakachoda/casino-bot

arisaarisa
Telegram + Pi Agent modular assistantTelegram + Pi Agent modular assistant
Version 3.0.14 published 2026-04-08 by clasen
Maintainers: clasen
Keywords: telegram pi-agent assistant cli openclaw moltbot clawbot agent SOUL.md tinyclaw nullclaw picoclaw zeroclaw jarvis AGENTS.md pi.dev clasen
https://npm.im/arisa

@stdlib/constants-float64-two-pi@stdlib/constants-float64-two-pi
2π.2π.
Version 0.2.3 published 2026-02-05 by stdlib-bot
Maintainers: stdlib-bot kgryte planeshifter rreusser
Keywords: stdlib stdmath constant const mathematics math pi 2pi ieee754 double dbl precision floating-point float64
https://npm.im/@stdlib/constants-float64-two-pi

@stdlib/constants-float64-sqrt-two-pi@stdlib/constants-float64-sqrt-two-pi
Square root of 2π.Square root of 2π.
Version 0.2.3 published 2026-02-05 by stdlib-bot
Maintainers: stdlib-bot kgryte planeshifter rreusser
Keywords: stdlib stdmath constant const mathematics math pi 2pi sqrt square root ieee754 double dbl precision floating-point float64
https://npm.im/@stdlib/constants-float64-sqrt-two-pi

@lottiefiles/lottie-player@lottiefiles/lottie-player
Lottie animation and Telegram Sticker player web components.Lottie animation and Telegram Sticker player web components.
Version 2.0.12 published 2024-11-04 by jawish
Maintainers: jawish nattuadnan
Keywords: lottie animation lottiefiles web component component lit-element player telegram sticker tgs
https://npm.im/@lottiefiles/lottie-player

@stdlib/constants-float64-ln-sqrt-two-pi@stdlib/constants-float64-ln-sqrt-two-pi
Natural logarithm of the square root of 2π.Natural logarithm of the square root of 2π.
Version 0.2.3 published 2026-02-07 by stdlib-bot
Maintainers: stdlib-bot kgryte planeshifter rreusser
Keywords: stdlib stdmath constant const mathematics math pi 2pi sqrt ln natural logarithm log square root ieee754 double dbl precision floating-point float64
https://npm.im/@stdlib/constants-float64-ln-sqrt-two-pi

@mariozechner/pi-agent-core@mariozechner/pi-agent-core
General-purpose agent with transport abstraction, state management, and attachment supportGeneral-purpose agent with transport abstraction, state management, and attachment support
Version 0.71.1 published 2026-05-01 by badlogic
Maintainers: badlogic mitsuhiko
Keywords: ai agent llm transport state-management
https://npm.im/@mariozechner/pi-agent-core

telegram
NodeJS/Browser MTProto API Telegram client library,NodeJS/Browser MTProto API Telegram client library,
Version 2.26.22 published 2025-02-12 by painor
Maintainers: youtwitface painor
Keywords: telegram mtproto userbot api
https://npm.im/telegram

@aws-sdk/client-pi@aws-sdk/client-pi
AWS SDK for JavaScript Pi Client for Node.js, Browser and React NativeAWS SDK for JavaScript Pi Client for Node.js, Browser and React Native
Version 3.1041.0 published 2026-05-01 by aws-sdk-bot
Maintainers: amzn-oss aws-sdk-bot
https://npm.im/@aws-sdk/client-pi

pi-telegrampi-telegram
Pi-Telegram: Bridge Telegram bots to pi coding agent via RPCPi-Telegram: Bridge Telegram bots to pi coding agent via RPC
Version 0.3.4 published 2026-04-18 by ziphyrien
Maintainers: ziphyrien
Keywords: telegram telegram-bot pi pi-coding-agent grammy rpc ai-agent cli
https://npm.im/pi-telegram

@geminixiang/mama@geminixiang/mama
Slack bot that delegates messages to the pi coding agentSlack bot that delegates messages to the pi coding agent
Version 0.1.10 published 2026-04-02 by GitHub Actions
Maintainers: geminixiang
Keywords: agent ai bot discord slack telegram
https://npm.im/@geminixiang/mama

nannan
Native Abstractions for Node.js: C++ header for Node 0.8 -> 25 compatibilityNative Abstractions for Node.js: C++ header for Node 0.8 -> 25 compatibility
Version 2.26.2 published 2026-03-18 by kkoopa
Maintainers: kkoopa rvagg
https://npm.im/nan

@stdlib/constants-float64-half-pi@stdlib/constants-float64-half-pi
1/2 times π.1/2 times π.
Version 0.2.3 published 2026-02-07 by stdlib-bot
Maintainers: stdlib-bot kgryte planeshifter rreusser
Keywords: stdlib stdmath constant const mathematics math half pi ieee754 double dbl floating-point float64
https://npm.im/@stdlib/constants-float64-half-pi

@oh-my-pi/pi-coding-agent@oh-my-pi/pi-coding-agent
Coding agent CLI with read, bash, edit, write tools and session managementCoding agent CLI with read, bash, edit, write tools and session management
Version 14.5.14 published 2026-05-01 by can1357
Maintainers: can1357
Keywords: coding-agent ai llm cli tui agent
https://npm.im/@oh-my-pi/pi-coding-agent

@twa-dev/types@twa-dev/types
Types for Telegram Web Apps (TWA) SDKTypes for Telegram Web Apps (TWA) SDK
Version 8.0.2 published 2025-01-04 by artur_supertramp
Maintainers: qbnk artur_supertramp
Keywords: telegram typescript ts types telegram web apps telegram bot bot
https://npm.im/@twa-dev/types

mypensievemypensieve
A general-purpose, self-evolving autonomous agent OS with persistent memory across sessionsA general-purpose, self-evolving autonomous agent OS with persistent memory across sessions
Version 0.3.6 published 2026-04-22 by GitHub Actions
Maintainers: huntersreeni
Keywords: agent ai memory autonomous pensieve pi-coding-agent llm cli telegram
https://npm.im/mypensieve

== npm search: keywords:pi-package telegram ==
@llblab/pi-telegram@llblab/pi-telegram
Better Telegram DM bridge extension for piBetter Telegram DM bridge extension for pi
Version 0.4.0 published 2026-04-28 by llblab
Maintainers: llblab
Keywords: pi-package pi telegram bot extension
https://npm.im/@llblab/pi-telegram

pi-messenger-bridgepi-messenger-bridge
Bridge common messengers (Telegram, WhatsApp, Slack, Discord) into piBridge common messengers (Telegram, WhatsApp, Slack, Discord) into pi
Version 0.3.0 published 2026-03-25 by tintinweb
Maintainers: tintinweb
Keywords: pi-package pi pi-extension telegram whatsapp slack discord messenger bridge bot remote chat messaging coding-agent
https://npm.im/pi-messenger-bridge

@pi-unipi/notify@pi-unipi/notify
Cross-platform notification extension for Pi — native OS, Gotify, and Telegram notifications for agent lifecycle eventsCross-platform notification extension for Pi — native OS, Gotify, and Telegram notifications for agent lifecycle events
Version 0.1.10 published 2026-05-01 by neuron-mr-white
Maintainers: neuron-mr-white
Keywords: pi-package pi-extension pi-coding-agent unipi notify notifications
https://npm.im/@pi-unipi/notify

pi-bash-confirmpi-bash-confirm
Pi package for confirming bash commands before execution with Telegram notificationsPi package for confirming bash commands before execution with Telegram notifications
Version 2.0.0 published 2026-04-03 by matteo.collina
Maintainers: matteo.collina
Keywords: pi-package bash confirmation security telegram notifications
https://npm.im/pi-bash-confirm

pi-telebridgepi-telebridge
A pi extension that creates a two-way relay between your active pi coding agent session and a Telegram bot.A pi extension that creates a two-way relay between your active pi coding agent session and a Telegram bot.
Version 1.4.0 published 2026-03-14 by acarerdinc
Maintainers: acarerdinc
Keywords: pi-package pi-extension pi telegram telegram-bot
https://npm.im/pi-telebridge

@samfp/pi-telegram-bot@samfp/pi-telegram-bot
Telegram bot exposing pi as a conversational coding agent. Chat with pi in Telegram with streaming responses, tool execution, threaded sessions, and model switching.Telegram bot exposing pi as a conversational coding agent. Chat with pi in Telegram with streaming responses, tool execution, threaded sessions, and model switching.
Version 0.1.0 published 2026-04-19 by samfp
Maintainers: samfp
Keywords: pi-package app
https://npm.im/@samfp/pi-telegram-bot

@e9n/pi-channels@e9n/pi-channels
Two-way channel extension for pi — route messages between agents and Telegram, webhooks, and custom adaptersTwo-way channel extension for pi — route messages between agents and Telegram, webhooks, and custom adapters
Version 0.2.1 published 2026-04-26 by e9n
Maintainers: e9n
Keywords: pi-package
https://npm.im/@e9n/pi-channels

@brain0pia/pi-notify@brain0pia/pi-notify
Pi package that sends Telegram notifications after each completed agent response.Pi package that sends Telegram notifications after each completed agent response.
Version 0.2.0 published 2026-03-14 by brain0pia
Maintainers: brain0pia
Keywords: pi-package pi-extension telegram notifications
https://npm.im/@brain0pia/pi-notify

@0xkobold/pi-gateway@0xkobold/pi-gateway
Hermes-style messaging gateway for pi - multi-platform agent with sessions, security, and background tasksHermes-style messaging gateway for pi - multi-platform agent with sessions, security, and background tasks
Version 0.6.0 published 2026-04-09 by moikapy
Maintainers: moikapy
Keywords: pi-package pi-extension gateway messaging hermes multi-platform
https://npm.im/@0xkobold/pi-gateway

context-modecontext-mode
MCP plugin that saves 98% of your context window. Works with Claude Code, Gemini CLI, VS Code Copilot, OpenCode, and Codex CLI. Sandboxed code execution, FTS5 knowledge base, and intent-driven search.MCP plugin that saves 98% of your context window. Works with Claude Code, Gemini CLI, VS Code Copilot, OpenCode, and Codex CLI. Sandboxed code execution, FTS5 knowledge base, and intent-driven search.
Version 1.0.103 published 2026-04-28 by mksglu
Maintainers: mksglu
Keywords: mcp model-context-protocol claude claude-code gemini-cli vscode-copilot opencode openclaw codex-cli context-window sandbox code-execution fts5 bm25 pi-package
https://npm.im/context-mode

pi-subagentspi-subagents
Pi extension for delegating tasks to subagents with chains, parallel execution, and TUI clarificationPi extension for delegating tasks to subagents with chains, parallel execution, and TUI clarification
Version 0.21.4 published 2026-05-01 by nicopreme
Maintainers: nicopreme
Keywords: pi-package pi pi-coding-agent subagents ai agents cli
https://npm.im/pi-subagents

pi-mcp-adapterpi-mcp-adapter
MCP (Model Context Protocol) adapter extension for Pi coding agentMCP (Model Context Protocol) adapter extension for Pi coding agent
Version 2.5.3 published 2026-05-01 by nicopreme
Maintainers: nicopreme
Keywords: pi-package pi mcp model-context-protocol ai coding-agent extension claude llm
https://npm.im/pi-mcp-adapter

@pi-unipi/unipi@pi-unipi/unipi
All-in-one extension suite for Pi coding agentAll-in-one extension suite for Pi coding agent
Version 0.1.17 published 2026-05-01 by neuron-mr-white
Maintainers: neuron-mr-white
Keywords: pi-package pi-extension pi-coding-agent unipi
https://npm.im/@pi-unipi/unipi

@a5c-ai/babysitter-pi@a5c-ai/babysitter-pi
Babysitter package for Pi Coding AgentBabysitter package for Pi Coding Agent
Version 0.1.3 published 2026-04-04 by tmuskal
Maintainers: tmuskal benihak
Keywords: pi pi-package babysitter orchestration ai-agent
https://npm.im/@a5c-ai/babysitter-pi

@ollama/pi-web-search@ollama/pi-web-search
Web search and fetch tools for Pi agent - uses Ollama's web search and fetch APIsWeb search and fetch tools for Pi agent - uses Ollama's web search and fetch APIs
Version 0.0.5 published 2026-03-28 by parthollama
Maintainers: jmorgan mchiang0610 drifkin bruce-ollama parthollama
Keywords: pi-package pi-extension web-search ollama
https://npm.im/@ollama/pi-web-search

@plannotator/pi-extension@plannotator/pi-extension
Plannotator extension for Pi coding agent - interactive plan review with visual annotationPlannotator extension for Pi coding agent - interactive plan review with visual annotation
Version 0.19.5 published 2026-05-01 by GitHub Actions
Maintainers: backnotprop
Keywords: pi-package plannotator plan-review ai-agent coding-agent
https://npm.im/@plannotator/pi-extension

pi-web-accesspi-web-access
Web search, URL fetching, GitHub repo cloning, PDF extraction, YouTube video understanding, and local video analysis for Pi coding agentWeb search, URL fetching, GitHub repo cloning, PDF extraction, YouTube video understanding, and local video analysis for Pi coding agent
Version 0.10.6 published 2026-04-04 by nicopreme
Maintainers: nicopreme
Keywords: pi-package pi pi-coding-agent extension web-search perplexity fetch scraping
https://npm.im/pi-web-access

taskplanetaskplane
AI agent orchestration for pi — parallel task execution with checkpoint disciplineAI agent orchestration for pi — parallel task execution with checkpoint discipline
Version 0.28.4 published 2026-04-21 by henrylach
Maintainers: henrylach
Keywords: pi-package ai agent orchestration task-runner parallel
https://npm.im/taskplane

pi-markdown-previewpi-markdown-preview
Rendered markdown + LaTeX preview for pi, with terminal, browser, and PDF outputRendered markdown + LaTeX preview for pi, with terminal, browser, and PDF output
Version 0.9.7 published 2026-04-29 by omacl
Maintainers: omacl
Keywords: pi-package
https://npm.im/pi-markdown-preview

pi-lenspi-lens
Real-time code feedback for pi — LSP, linters, formatters, type-checking, structural analysis & boobooReal-time code feedback for pi — LSP, linters, formatters, type-checking, structural analysis & booboo
Version 3.8.34 published 2026-05-01 by apmantza
Maintainers: apmantza
Keywords: pi pi-extension pi-package linter biome ast-grep ruff typescript code-quality feedback type-coverage jscpd knip
https://npm.im/pi-lens

pi-gsdpi-gsd
Get Shit Done - Unofficial port of the renowned AI-native project-planning spec-driven toolkitGet Shit Done - Unofficial port of the renowned AI-native project-planning spec-driven toolkit
Version 2.1.4 published 2026-04-26 by GitHub Actions
Maintainers: fulgidus
Keywords: ai agent planning cli workflow get-shit-done gsd productivity project-management milestones phases spec pi-package
https://npm.im/pi-gsd

pi-xai-voicepi-xai-voice
Pi extension for xAI voice and audio workflowsPi extension for xAI voice and audio workflows
Version 0.1.0 published 2026-04-22 by luxusai
Maintainers: luxusai
Keywords: pi-package
https://npm.im/pi-xai-voice

pi-powerline-footerpi-powerline-footer
Powerline-style status bar extension for pi coding agentPowerline-style status bar extension for pi coding agent
Version 0.4.20 published 2026-04-26 by nicopreme
Maintainers: nicopreme
Keywords: pi-package pi coding-agent powerline status-bar extension
https://npm.im/pi-powerline-footer

@aliou/pi-processes@aliou/pi-processes
![banner](https://assets.aliou.me/pi-extensions/banners/pi-processes.png)![banner](https://assets.aliou.me/pi-extensions/banners/pi-processes.png)
Version 0.8.1 published 2026-04-23 by GitHub Actions
Maintainers: aliou
Keywords: pi-package pi-extension pi processes
https://npm.im/@aliou/pi-processes

pi-btwpi-btw
A pi extension for parallel side conversations with /btwA pi extension for parallel side conversations with /btw
Version 0.3.8 published 2026-04-30 by GitHub Actions
Maintainers: dbachelder
Keywords: pi-package pi-extension pi-coding-agent agent tui
https://npm.im/pi-btw

@juicesharp/rpiv-todo@juicesharp/rpiv-todo
Pi extension: gives the model a todo list with a live overlay that survives /reload and compactionPi extension: gives the model a todo list with a live overlay that survives /reload and compaction
Version 1.0.13 published 2026-05-01 by juicesharp
Maintainers: juicesharp
Keywords: pi-package pi-extension rpiv todo
https://npm.im/@juicesharp/rpiv-todo

@juicesharp/rpiv-ask-user-question@juicesharp/rpiv-ask-user-question
Pi extension: lets the model ask you a clarifying question with structured options instead of guessingPi extension: lets the model ask you a clarifying question with structured options instead of guessing
Version 1.0.13 published 2026-05-01 by juicesharp
Maintainers: juicesharp
Keywords: pi-package pi-extension rpiv
https://npm.im/@juicesharp/rpiv-ask-user-question

@callumvass/forgeflow-dev@callumvass/forgeflow-dev
Dev pipeline for Pi — TDD implementation, code review, architecture, and Datadog investigations.Dev pipeline for Pi — TDD implementation, code review, architecture, and Datadog investigations.
Version 0.36.0 published 2026-04-12 by GitHub Actions
Maintainers: callumvass
Keywords: pi-package
https://npm.im/@callumvass/forgeflow-dev

@samfp/pi-memory@samfp/pi-memory
Persistent memory for pi — learns corrections, preferences, and patterns from sessions and injects them into future conversations.Persistent memory for pi — learns corrections, preferences, and patterns from sessions and injects them into future conversations.
Version 1.0.2 published 2026-04-24 by samfp
Maintainers: samfp
Keywords: pi-package extension memory learning preferences
https://npm.im/@samfp/pi-memory

@heart-of-gold/toolkit@heart-of-gold/toolkit
Cross-platform installer for Heart of Gold skills — works with Codex, OpenCode, Pi, Claude Code, and moreCross-platform installer for Heart of Gold skills — works with Codex, OpenCode, Pi, Claude Code, and more
Version 0.1.50 published 2026-04-26 by ondrejsvec
Maintainers: ondrejsvec
Keywords: codex claude-code opencode pi pi-package ai-agents skills agents-md agentskills harness-engineering heart-of-gold
https://npm.im/@heart-of-gold/toolkit

glimpseuiglimpseui
Native micro-UI for scripts and agents — cross-platform WebView windows with bidirectional JSON communicationNative micro-UI for scripts and agents — cross-platform WebView windows with bidirectional JSON communication
Version 0.8.0 published 2026-04-19 by haza
Maintainers: haza
Keywords: macos linux windows webview native ui webkit wkwebview webview2 gtk swift gui agent dialog prompt overlay pi-package
https://npm.im/glimpseui

pi-notifierpi-notifier
Lightweight macOS desktop notifications for pi when waiting for input or decision.Lightweight macOS desktop notifications for pi when waiting for input or decision.
Version 0.1.1 published 2026-04-07 by zheermao


**Shortlisting packages**

I need to create a shortlist of telegram packages and clarify that no audits have been done except for the bridge/gateway. I might also mention that there could be a quick audit on @llblab/pi-telegram and pi-telegram. It seems like doing a quick npm view could help gather more information on these packages. I want to make sure I'm thorough but also efficient in gathering what I need!

bash

set -e
for pkg in @llblab/pi-telegram pi-telegram pi-telebridge @samfp/pi-telegram-bot; do
  echo "== $pkg =="
  npm view "$pkg" version repository homepage description dist.unpackedSize dist.tarball maintainers --json || true
  echo
done


== @llblab/pi-telegram ==
{
  "version": "0.4.0",
  "repository": {
    "type": "git",
    "url": "git+https://github.com/llblab/pi-telegram.git"
  },
  "homepage": "https://github.com/llblab/pi-telegram",
  "description": "Better Telegram DM bridge extension for pi",
  "dist.unpackedSize": 565048,
  "dist.tarball": "https://registry.npmjs.org/@llblab/pi-telegram/-/pi-telegram-0.4.0.tgz",
  "maintainers": [
    "llblab <shlavik@gmail.com>"
  ]
}

== pi-telegram ==
{
  "version": "0.3.4",
  "description": "Pi-Telegram: Bridge Telegram bots to pi coding agent via RPC",
  "dist.unpackedSize": 280335,
  "dist.tarball": "https://registry.npmjs.org/pi-telegram/-/pi-telegram-0.3.4.tgz",
  "maintainers": [
    "ziphyrien <2697860027@qq.com>"
  ]
}

== pi-telebridge ==
{
  "version": "1.4.0",
  "repository": {
    "type": "git",
    "url": "git+https://github.com/acarerdinc/pi-telebridge.git"
  },
  "homepage": "https://github.com/acarerdinc/pi-telebridge#readme",
  "description": "A pi extension that creates a two-way relay between your active pi coding agent session and a Telegram bot.",
  "dist.unpackedSize": 28265,
  "dist.tarball": "https://registry.npmjs.org/pi-telebridge/-/pi-telebridge-1.4.0.tgz",
  "maintainers": [
    "acarerdinc <thisiserdinc@gmail.com>"
  ]
}

== @samfp/pi-telegram-bot ==
{
  "version": "0.1.0",
  "description": "Telegram bot exposing pi as a conversational coding agent. Chat with pi in Telegram with streaming responses, tool execution, threaded sessions, and model switching.",
  "dist.unpackedSize": 101172,
  "dist.tarball": "https://registry.npmjs.org/@samfp/pi-telegram-bot/-/pi-telegram-bot-0.1.0.tgz",
  "maintainers": [
    "samfp <sam.foy.painter@gmail.com>"
  ]
}


**Considering recommendations**

I need to provide concise recommendations here. It might make sense to mention options from prior audits, like supporting pi-messenger-bridge and pi-gateway for Telegram. I could also highlight dedicated Telegram options, as those could be beneficial. I want to make sure I’m clear and to the point while still giving enough detail for the user to understand their choices. Let's ensure that the recommendations are straightforward!

For Telegram, better options exist than for WhatsApp.

## Best candidates

### 1) `@llblab/pi-telegram`
- Purpose: Telegram DM bridge for Pi.
- Signal: most focused Telegram-specific Pi extension I found.
- Repo present: `llblab/pi-telegram`
- Likely best first thing to audit/install.

### 2) `pi-telebridge`
- Purpose: two-way relay between active Pi session and Telegram bot.
- Repo present: `acarerdinc/pi-telebridge`
- Smaller package than `@llblab/pi-telegram`.
- Good if you want simple relay, not a full bot platform.

### 3) `pi-telegram`
- Purpose: Telegram bot bridge via RPC.
- No repo metadata from npm view.
- Lower trust until audited harder.

### 4) `@samfp/pi-telegram-bot`
- Purpose: full Telegram conversational bot with streaming/tool execution/threaded sessions.
- No repo metadata from npm view.
- Broader feature set, more moving parts.

## Already-audited options that also support Telegram

### `pi-messenger-bridge`
- Supports Telegram + WhatsApp + Slack + Discord.
- Code looked sane.
- Tests passed.
- Dependency tree is heavy and vulnerable.

### `@0xkobold/pi-gateway`
- Supports Telegram among many adapters.
- Published npm package had clean runtime audit.
- Repo/package mismatch hurts trust.

## Quick ranking

If goal = **Telegram only**
1. `@llblab/pi-telegram`
2. `pi-telebridge`
3. `@samfp/pi-telegram-bot`
4. `pi-telegram`

If goal = **multi-platform**
1. `pi-messenger-bridge`
2. `@0xkobold/pi-gateway`

## My call
For Telegram, I’d check `@llblab/pi-telegram` first, then `pi-telebridge`.

If you want, I can now:
- audit `@llblab/pi-telegram` and `pi-telebridge`
- or pick one and install if clean


telegram lnly is fine. do all maintain session context? how do they do persistance?


**Gathering data for audit**

I need to provide an answer based on the information I already have. It seems like I should audit those repositories for persistence. So, I’ll focus on gathering the necessary data to support this process. It’s important to ensure everything is organized and accurately examined as I take this step. Let’s make sure I’ve got everything I need to effectively evaluate the situation!

set -e
base=$(mktemp -d /tmp/pi-telegram-audit-XXXXXX)
echo "$base"
cd "$base"
for repo in \
  https://github.com/llblab/pi-telegram \
  https://github.com/acarerdinc/pi-telebridge
 do
  name=$(basename "$repo")
  git clone --depth 1 "$repo" "$name" >/dev/null 2>&1 || echo "clone failed: $repo"
done
for dir in pi-telegram pi-telebridge; do
  [ -d "$dir" ] || continue
  echo "\n===== $dir ====="
  cd "$base/$dir"
  [ -f package.json ] && node -e 'const p=require("./package.json"); console.log(JSON.stringify({name:p.name,version:p.version,description:p.description,dependencies:p.dependencies,pi:p.pi,scripts:p.scripts},null,2))'
  echo '-- README head --'
  [ -f README.md ] && sed -n '1,260p' README.md
  echo '-- persistence/session search --'
  rg -n --glob '!package-lock.json' -S 'session|persist|storage|sqlite|json|thread|chatId|userId|memory|save|load|resume|context|history|state|telegram' src . | head -250 || true
  echo '-- files --'
  find . -maxdepth 3 -type f | sed 's#^./##' | sort | head -150
 done


./AGENTS.md:70:- `telegram_attach` is the canonical outbound file-delivery path for Telegram-originated requests
./AGENTS.md:76:- Treat queue handling, compaction interaction, and lifecycle-hook state transitions as regression-prone areas; validate them after changing dispatch logic
./AGENTS.md:77:- Route important runtime failures through the recent runtime event recorder so `/telegram-status` remains useful for post-mortem debugging, not just transient status-bar errors
./AGENTS.md:88:- Name extracted `/lib` modules and mirrored `/tests` suites by bare domain when the repository already supplies the Telegram scope; prefer `api.ts`, `queue.ts`, `updates.ts`, and `queue.test.ts` over redundant `telegram-*` filename prefixes
./AGENTS.md:90:- Keep composition wiring DRY with small local adapters or owning-domain contracts when repetition appears, but do not hide live mutable session state behind broad facades just to reduce repeated closures
./AGENTS.md:95:- Queue owns scheduling/lifecycle semantics: lane contracts, queue and active-turn stores, mutations, dispatch readiness/runtime, control/prompt enqueueing, session state appliers, and agent/tool lifecycle hooks; model owns model identity/thinking-level contracts, scoped resolution and CLI pattern parsing, current-model state/runtime helpers, in-flight switch state, restart eligibility, delayed abort decisions, Telegram-prefix-defaulted continuation prompt construction/queueing, and controller runtime binding over queue turns
./AGENTS.md:96:- Runtime owns session-local primitives and timers; locks owns shared `locks.json` singleton ownership, interactive takeover, stale same-`cwd` restart resume, session-replacement suspension, and ownership-drift shutdown; pi owns direct SDK imports and bound extension API ports; config owns persisted bot/session pairing state, single-user authorization, first-user pairing side effects, and inbound attachment handler config; API owns Bot API transport shapes/helpers, temp-dir/inbound-limit/runtime-error helpers, and runtime-bound chat actions
./AGENTS.md:98:- Polling owns long-poll controller state/activity/loop wiring; updates owns callback/message/edit/reaction classification/execution; routing owns inbound update composition across menus, commands, media grouping, prompt queueing, edits, and paired authorization; handlers owns inbound attachment handler matching, command/tool invocation, prompt-text injection, and fallback behavior; registration owns pi tool/command/lifecycle-hook binding
./AGENTS.md:100:- Menu owns inline menu state/cache/building, callback/action runtimes, status/thinking/model UI support, and selection planning; status owns bridge status rendering, grouped pi-side diagnostic lines, and recent-event state
./AGENTS.md:101:- Turns own Telegram prompt turn-building/editing, prompt-prefix identity, and attachment-handler output handoff into prompt content; media owns inbound text/file/media-group extraction and download assembly; attachments own outbound file queueing, narrow structural attachment turn targets, stat checks, limits, and delivery classification; setup owns token prompt, env fallback, and validation flow
./AGENTS.md:105:- Keep preview appearance logic in the rendering domain and preview transport/lifecycle logic in the preview domain so richer streaming strategies can evolve without entangling Telegram delivery state with Markdown formatting rules
./AGENTS.md:109:- Do not reintroduce shared bucket domains such as `lib/constants.ts` or `lib/types.ts`; constants, state interfaces, and concrete transport shapes should stay in their owning domains, and `index.ts` should not grow new shared magic constants
./AGENTS.md:110:- Keep remaining `index.ts` code focused on cross-domain adapter wiring that needs live extension state, pi callbacks, Telegram API ports, or status updates; do not extract one-off closures solely to reduce line count
./AGENTS.md:111:- Domain-specific queue planning, preview transport/controller behavior, rendering, Telegram API transport, menu state, and command behavior should stay in their owning domains instead of moving to `/lib/runtime.ts` solely to shrink `index.ts`
./AGENTS.md:123:- Telegram API methods currently used include polling, message editing, draft streaming, callback queries, reactions, file download, and media upload endpoints
./AGENTS.md:126:- For `/telegram-setup`, prefer the locally saved bot token over environment variables on repeat setup runs; env vars are the bootstrap path when no local token exists
./AGENTS.md:128:- Inbound files may become pi image inputs or configured attachment-handler text before queueing; outbound files must flow through `telegram_attach`
./AGENTS.md:135:- Inspect the relevant `index.ts` section before editing because most bridge behavior is stateful and cross-linked
./AGENTS.md:142:- Sync `README.md`, `CHANGELOG.md`, `BACKLOG.md`, and `/docs` whenever user-visible behavior or real open-work state changes
./CHANGELOG.md:5:- `[Locks]` Added the shared `locks.json` singleton ownership standard and documented it in `docs/locks.md`. Telegram polling ownership now lives under `@llblab/pi-telegram` in `~/.pi/agent/locks.json`, while `telegram.json` remains pure bot/user configuration. `/telegram-connect` acquires the lock, replaces stale locks, or moves live external owners here through an interactive confirmation; `/telegram-disconnect` releases it. Session initialization reads ownership state, active owners stop local polling when `locks.json` no longer points at their own `pid`/`cwd`, session replacement via `/new` suspends polling/watchers without touching explicit ownership before resuming in the new session, and a reopened pi process resumes a stale same-`cwd` lock automatically. Impact: runtime locks can be reset or moved without deleting Telegram configuration, finding the previous pi instance, or crashing on stale extension contexts.
./CHANGELOG.md:6:- `[Attachment Handlers]` Added `telegram.json` inbound attachment handlers with MIME/type matching, safe command placeholder substitution, local tool invocation, compact `[attachments] <directory>` plus `[outputs]` prompt sections, and quiet omission of empty or failed handler output. Impact: common flows such as Telegram voice transcription can happen before the agent sees the turn while keeping source attachment paths visible.
./CHANGELOG.md:13:- `[Flat Domain DAG]` The extension now uses `index.ts` as the single composition root over flat, acyclic `/lib` domains. API transport, persisted config/pairing, model control, queue/lifecycle, runtime state, commands, menu UI, polling, previews, replies, rendering, media, turns, attachments, updates, setup, status, pi SDK adapters, and registration each have explicit ownership. Impact: bridge behavior is easier to locate without introducing a second entrypoint or shared bucket modules.
./CHANGELOG.md:14:- `[Composition Root]` `index.ts` now focuses on live pi/Telegram ports, session-local state, and cross-domain wiring. Stable defaults and local adapters for rendering, preview limits, preview reply metadata, attachment limits, Telegram prompt prefixes, prompt suffixes, command control items, bot command registration, API runtime construction, polling controllers, prompt dispatch, reply delivery, setup prompts, queue/session lifecycle, agent hooks, and tool hooks live in their owning domains. Impact: the entrypoint is smaller and reads as orchestration instead of carrying domain behavior.
./CHANGELOG.md:15:- `[Type Boundaries]` Concrete Bot API transport shapes live in `api`, queued/active turn contracts live in `queue`, persisted session state lives in `config`, media download metadata lives in `media`, model selection contracts live in `model`, and domain constants stay with their owners. Narrow structural view contracts are used where a domain only needs a projection. Impact: public module interfaces are cleaner and accidental cross-domain coupling is reduced.
./CHANGELOG.md:16:- `[Runtime State]` `runtime` owns only session-local coordination primitives: queue/control/priority counters, lifecycle flags, setup guards, abort handler storage, typing-loop timers, prompt-dispatch lifecycle binding, and agent-end reset sequencing. Preview state, queue planning, command behavior, rendering, and API transport stay outside `runtime`. Impact: mutable bridge state is centralized without becoming a general-purpose behavior bucket.
./CHANGELOG.md:20:- `[Queue Core]` `queue` owns prompt/control item contracts, explicit control/priority/default lane admission rules, queue stores, active-turn state, queue mutation, dispatch readiness, prompt enqueueing, control enqueueing, session start/shutdown sequencing, and agent/tool lifecycle hooks. Impact: scheduling rules, active-turn binding, abort preservation, compaction guards, and dispatch safety are enforced in one place.
./CHANGELOG.md:22:- `[Model Control]` `model` owns model identity, thinking levels, scoped model pattern parsing/resolution/sorting, current-model state, in-flight model-switch state, restart eligibility, delayed abort decisions, continuation prompt construction, and model-switch controller runtime binding. Impact: model selection, scoped menus, and in-flight restart behavior have one cohesive home.
./CHANGELOG.md:23:- `[Commands And Menus]` `commands` owns slash-command parsing, command metadata, command-message targets, command execution modes, control-queue adapters, bot command registration, and stop/compact/status/model/help side effects. The Telegram bot command menu no longer exposes `/debug`; diagnostics live in pi-side `/telegram-status` and the pi TUI. `menu` owns inline status/model/thinking UI state, model-menu caching, callback routing, callback planning, render payloads, and menu message updates. Impact: Telegram controls can be tested without reading the full runtime composition.
./CHANGELOG.md:29:- `[Preview And Replies]` `preview` owns streaming preview lifecycle, draft/editable-message transport choices, flush scheduling, preview finalization, and assistant-message hooks, while defaulting preview reply metadata through the `replies` helper instead of threading it through `index.ts`. `replies` owns final rendered-message delivery, reply parameters, assistant-message extraction, plain/Markdown replies, interactive message delivery, and split-message reply metadata. Impact: rich previews, final replies, errors, attachment notices, and uploads are tied to the source Telegram prompt when possible and degrade safely when Telegram cannot attach the reply.
./CHANGELOG.md:30:- `[Files And Attachments]` `api` owns Bot API calls, retries, runtime error recording, temp-dir cleanup, inbound file limits, lazy bot-token clients, chat actions, and file downloads. `media` owns inbound text/media extraction, file-info normalization, media-group debounce, and download assembly. `attachments` owns outbound attachment queueing, atomic multi-file staging, stat checks, outbound limits, photo/document classification, and queued attachment sending. Impact: inbound downloads and outbound uploads are size-limited by default, large files fail predictably without leaving partial attachment batches staged, and outbound artifacts flow through `telegram_attach`.
./CHANGELOG.md:31:- `[Config And Setup]` `config` owns `telegram.json`, bot-token/allowed-user state, single-user authorization, and first-user pairing. `setup` owns token prompting, stored-token/env fallback selection, validation, and guarded setup orchestration. Impact: pairing and setup behavior stays consistent across `/telegram-setup`, `/telegram-connect`, `/start`, and update routing.
./CHANGELOG.md:35:- `[Status And Diagnostics]` `/telegram-status` now reports bridge diagnostics as grouped line-by-line pi notification sections separated by blank lines, ending with the redacted recent runtime/API event ring after connection, polling, execution, and queue state such as active turn, queue depth, queue lanes, compaction, active tool count, and pending model-switch state. Transport/API failures, polling/update failures, prompt dispatch failures, control action failures, typing failures, compaction failures, setup failures, session lifecycle failures, and attachment queue/delivery failures are recorded in the ring, while benign unchanged edit responses and empty draft-clear attempts do not pollute it. Impact: operators can diagnose bridge stalls and transport/runtime failures after the fact without exposing a Telegram-side debug command.
./CHANGELOG.md:36:- `[Package Contents]` The npm package uses an explicit allowlist and keeps the tracked lockfile. Impact: published tarballs exclude tests and internal context files while preserving predictable release contents.
./CHANGELOG.md:37:- `[Validation]` The project now ships typecheck, test, audit, package dry-run, and combined `validate` scripts plus GitHub Actions validation. Regression coverage spans rendering fixtures, queue/runtime/session behavior, command admission, lane contracts, invalid-lane rejection, setup, registration, replies, polling, updates, attachments, media, config, model resolution, preview timers, and extension-runtime flows. Impact: release checks catch type errors, dependency issues, package-content drift, rendering regressions, queue/lifecycle races, and architecture-boundary drift before publishing.
./CHANGELOG.md:39:- `[Refactor]` Reopened compression/decomposition/refactor/consistency passes tightened menu-domain message render/send plumbing, compressed repeated menu/media/attachment/runtime/API/model/queue test harness shapes, extracted shared runtime context, model-context, dispatch-event, Telegram config, deferred-response, API-response, rich-response, prompt-block, fetch-method, API response/client/fetch-restore, runtime fetch/model, model-test, queue-model, queue prompt/control/item-type, registration active-turn, menu-model, and pi API test fixtures, migrated runtime integration tests onto those fixtures, replaced ad hoc structural casts with owning-domain or test-local helpers where practical, and removed avoidable test casts from attachment, polling, update, model, reply, menu, pi-adapter, and registration suites. Impact: the codebase stays easier to extend without changing Telegram runtime behavior.
./CHANGELOG.md:43:- `[Turns]` Preserved existing attachment-path blocks and aborted-turn history context when a still-queued Telegram message is edited. Impact: caption edits no longer make queued prompts lose their downloaded file references or prior-message context.
./CHANGELOG.md:45:- `[Telegram API]` Added HTTP-status-aware Bot API response parsing, malformed-success handling, retry/backoff for 429 and 5xx Bot API responses, streaming Telegram downloads with size-limit checks, file-backed multipart upload blobs, partial-download cleanup on limit failures, startup cleanup for stale Telegram temp files, and UUID-based sanitized temp filenames. Impact: Telegram transport failures now report clearer status/description details, transient Telegram throttling/server failures get retried automatically, oversized inbound files are rejected before or during download, outbound multipart sends avoid preloading files into memory, partial and stale temp files are removed, and downloaded files are less prone to timestamp collisions or unsafe local names.
./CHANGELOG.md:49:- `[Menu]` Added short-lived model-menu input caching plus TTL/LRU cleanup for stored inline menu state. Impact: repeated `/status` and `/model` interactions do less settings/model-registry work, while old Telegram inline keyboards expire predictably instead of accumulating for the whole session.
./CHANGELOG.md:53:- `[Refactor]` Extracted Telegram slash-command parsing and command-action routing into `lib/commands.ts` with mirrored tests, and moved queued-turn text replacement for edited messages into the turn domain. Impact: command normalization/execution planning and queued edit mutations are reusable, while the entrypoint keeps less local runtime state logic.
./CHANGELOG.md:61:- `[Naming]` Renamed extracted domain modules and mirrored regression suites to use repo-scoped bare domain filenames such as `api.ts`, `queue.ts`, and `queue.test.ts` instead of repeating `telegram-*` in every path. Impact: the internal topology is easier to scan and stays aligned with the repository-level Telegram scope.
./CHANGELOG.md:62:- `[Controls]` Expanded Telegram session controls with a richer `/status` view, inline model selection, and thinking-level controls, and fixed the callback-selection path so idle model and thinking picks apply immediately instead of only becoming visible after a later Telegram interaction. Impact: more bridge configuration can be managed directly from Telegram with more predictable immediate feedback.
./CHANGELOG.md:63:- `[Queue]` Upgraded Telegram turn queueing with previews, reaction-driven prioritization/removal, media-group handling, aborted-turn history preservation, and safer dispatch gating. Impact: follow-up handling is more transparent and less prone to lifecycle races.
./CHANGELOG.md:65:- `[Runtime]` Hardened attachment delivery, polling/runtime behavior, Telegram session integration, preview-finalization and reply-transport routing into the replies domain, lazy Telegram API client routing into the Telegram API domain, turn-building extraction into its own domain, menu/model-resolution plus menu-state, pure menu-page derivation, pure menu render-payload builders, menu-message runtime, callback parsing, callback entry handling, callback mutation helpers, full model-callback planning and execution, and interface-polished callback effect ports into the menu domain, direct execute-from-update routing into the updates domain, model-switch restart glue extraction into the model-switch domain, and tool/command/lifecycle-hook registration extraction into a dedicated registration domain. Impact: the bridge is more robust as a daily Telegram frontend for pi.
./CHANGELOG.md:66:- `[Metadata]` Updated package repository metadata to point at the `llblab/pi-telegram` fork and renamed the npm package to `@llblab/pi-telegram` with public scoped publish settings. Impact: published package links no longer send users to stale upstream coordinates and the package can be published under the fork-owned npm scope.
./CHANGELOG.md:67:- `[Validation]` Added lightweight regression tests for Telegram Markdown rendering, queue/runtime/agent-loop/session/control/dispatch, replies, polling, updates, attachments, registration, turns, menu, and Telegram API/media/config helpers, including quote/list, table, link/code, mixed-link/code chunking, mixed-block chunk transitions, long multi-block, long-quote, long inline-formatting chunk boundaries, list-code-quote-prose chunk transitions, narrower monospace table rendering without outer side borders, monospace unordered and ordered list markers, flattened nested quote indentation inside one Telegram blockquote, inbound poll/pair/dispatch runtime cases, preview finalization, aborted-turn history carry-over, queued-status/model-after-agent-end sequencing, compaction gating, media-group debounce dispatch, direct menu callback planning and execution, pure menu-page derivation, pure menu render-payload builders, reaction-driven reprioritization/removal, immediate in-flight model-switch continuation, delayed abort-after-tool-completion, lazy Telegram API client routing, turn-building, and scoped-model resolution. Impact: key renderer and queue invariants now have repeatable automated coverage across the known high-risk bridge paths.
./CHANGELOG.md:70:- `[Registration]` Moved extension tool, command, and lifecycle-hook binding into the registration domain and added registration-focused regression coverage. Impact: extension wiring is easier to reason about and test without dragging full runtime state into every registration change.
./CHANGELOG.md:72:- `[Setup]` `/telegram-setup` now shows the stored bot token first, otherwise prefills from common Telegram bot environment variables before falling back to the placeholder, using an actual prefilled editor when a real default exists. Impact: repeat setup respects local saved state while first-run and secret-managed setup stay fast.
./README.md:1:# pi-telegram
./README.md:3:![pi-telegram screenshot](screenshot.png)
./README.md:5:This repository is an actively maintained fork of [`badlogic/pi-telegram`](https://github.com/badlogic/pi-telegram). It started from upstream commit [`cb34008460b6c1ca036d92322f69d87f626be0fc`](https://github.com/badlogic/pi-telegram/commit/cb34008460b6c1ca036d92322f69d87f626be0fc) and has since diverged substantially.
./README.md:17:- **Interactive UI**: Manage your session directly from Telegram. Inline buttons allow you to switch models and adjust reasoning (thinking) levels on the fly.
./README.md:18:- **In-flight Model Switching**: Change the active model mid-generation. The agent gracefully pauses, applies the new model, and restarts its response without losing context.
./README.md:21:- **File Handling & Attachments**: Send images and files to the agent, transcribe or transform inbound files with configured attachment handlers, or ask pi to generate and return artifacts. Inbound downloads and outbound attachments are size-limited by default, and outbound files are delivered automatically via the `telegram_attach` tool.
./README.md:29:pi install npm:@llblab/pi-telegram
./README.md:35:pi install git:github.com/llblab/pi-telegram
./README.md:52:/telegram-setup
./README.md:55:Paste your bot token when prompted. If a bot token is already saved in `~/.pi/agent/telegram.json`, the setup prompt shows that stored value by default. Otherwise it prefills from the first configured environment variable in `TELEGRAM_BOT_TOKEN`, `TELEGRAM_BOT_KEY`, `TELEGRAM_TOKEN`, or `TELEGRAM_KEY`. The saved config file is written with private `0600` permissions.
./README.md:57:### 3. Connect this pi session
./README.md:60:/telegram-connect
./README.md:63:The bridge is session-local: only one pi instance polls Telegram at a time. `/telegram-connect` records polling ownership in `~/.pi/agent/locks.json`; live ownership moves require confirmation, while `/new` and same-`cwd` process restarts resume automatically.
./README.md:82:- **`/status`**: View session stats, cost, and use inline buttons to change models.
./README.md:84:- **`/compact`**: Start session compaction (only works when the session is idle).
./README.md:93:- **`/telegram-setup`**: Configure or update the Telegram bot token.
./README.md:94:- **`/telegram-status`**: Check bridge status, connection, polling, execution, queue, and recent redacted runtime/API failure events.
./README.md:95:- **`/telegram-connect`**: Start polling Telegram updates in the current pi session, acquire the singleton lock, or interactively move ownership here from another live instance.
./README.md:96:- **`/telegram-disconnect`**: Stop polling in the current pi session and release the singleton lock.
./README.md:105:- Inbound images, albums, and files are saved to `~/.pi/agent/tmp/telegram`. Unhandled local file paths are included in the prompt, handled attachment output is injected into the prompt text, and inbound images are forwarded to pi as image inputs. Inbound downloads default to a 50 MiB limit and can be adjusted with `PI_TELEGRAM_INBOUND_FILE_MAX_BYTES` or `TELEGRAM_MAX_FILE_SIZE_BYTES`.
./README.md:110:`telegram.json` can define `attachmentHandlers` for common preprocessing such as voice transcription. The first matching handler runs after download and before the Telegram turn enters the pi queue.
./README.md:112:```json
./README.md:128:Matching supports `mime`, `type`, or `match`; wildcards like `audio/*` are accepted. Command placeholders are substituted as argv, not shell text: `{filename}` and `{path}` are the downloaded file path, `{basename}` is the display filename, `{mime}` is the MIME type, and `{type}` is the Telegram attachment type. Tool handlers invoke a locally available tool by name, for example `transcribe_groq`; how that tool is provided is outside pi-telegram. Local attachments stay in the prompt under `[attachments] <directory>` with relative file entries; successful handler stdout is added under `[outputs]`; failed or empty handlers simply produce no output block.
./README.md:132:If you ask pi for a file or generated artifact (e.g., _"generate a shell script and attach it"_), pi will call the `telegram_attach` tool, and the extension will send the file alongside its next Telegram reply. Outbound attachments default to a 50 MiB limit and can be adjusted with `PI_TELEGRAM_OUTBOUND_ATTACHMENT_MAX_BYTES` or `TELEGRAM_MAX_ATTACHMENT_SIZE_BYTES`.
./README.md:152:- Temporary inbound Telegram files are cleaned up on later session starts
./docs/README.md:8:- [locks.md](./locks.md) — Shared `locks.json` standard for singleton extension ownership
./lib/api.ts:3: * Wraps bot API calls, file downloads, runtime transport binding, and Telegram temp-file cleanup
./lib/api.ts:30:const TEMP_DIR = join(homedir(), ".pi", "agent", "tmp", "telegram");
./lib/api.ts:199:export interface TelegramFileDownloadOptions {
./lib/api.ts:218:  downloadFile: (
./lib/api.ts:222:    options?: TelegramFileDownloadOptions,
./lib/api.ts:236:    kind: "api" | "multipart" | "download",
./lib/api.ts:256:  downloadFile: (fileId: string, suggestedName: string) => Promise<string>;
./lib/api.ts:265:  sendChatAction: (chatId: number, action: "typing") => Promise<boolean>;
./lib/api.ts:266:  sendTypingAction: (chatId: number) => Promise<unknown>;
./lib/api.ts:268:    chatId: number,
./lib/api.ts:344:function createTelegramDownloadLimitTransform(
./lib/api.ts:347:  let downloadedBytes = 0;
./lib/api.ts:350:      downloadedBytes += chunk.byteLength;
./lib/api.ts:352:        assertTelegramFileSizeWithinLimit(downloadedBytes, maxFileSizeBytes);
./lib/api.ts:361:async function writeTelegramDownloadResponse(
./lib/api.ts:374:    createTelegramDownloadLimitTransform(maxFileSizeBytes),
./lib/api.ts:379:async function removeTelegramPartialDownload(path: string): Promise<void> {
./lib/api.ts:399:      data = (await response.json()) as TelegramApiResponse<TResponse>;
./lib/api.ts:510:      fetch(`https://api.telegram.org/bot${configuredBotToken}/${method}`, {
./lib/api.ts:512:        headers: { "content-type": "application/json" },
./lib/api.ts:530:    `https://api.telegram.org/bot${botToken}/getMe`,
./lib/api.ts:532:  return response.json() as Promise<TelegramBotIdentityResponse>;
./lib/api.ts:555:        `https://api.telegram.org/bot${configuredBotToken}/${method}`,
./lib/api.ts:567:export async function downloadTelegramFile(
./lib/api.ts:572:  options?: TelegramFileDownloadOptions,
./lib/api.ts:588:    `https://api.telegram.org/file/bot${configuredBotToken}/${file.file_path}`,
./lib/api.ts:592:    throw new Error(`Failed to download Telegram file: ${response.status}`);
./lib/api.ts:600:    await writeTelegramDownloadResponse(
./lib/api.ts:606:    await removeTelegramPartialDownload(targetPath);
./lib/api.ts:631:  sendChatAction: (chatId: number, action: TAction) => Promise<unknown>,
./lib/api.ts:633:): (chatId: number) => Promise<unknown> {
./lib/api.ts:634:  return (chatId) => sendChatAction(chatId, action);
./lib/api.ts:689:    downloadFile: async (fileId, suggestedName) => {
./lib/api.ts:691:        return await deps.client.downloadFile(
./lib/api.ts:700:        deps.recordRuntimeEvent("download", error, { suggestedName });
./lib/api.ts:714:    sendChatAction: (chatId, action) =>
./lib/api.ts:716:        chat_id: chatId,
./lib/api.ts:720:      (chatId, action) =>
./lib/api.ts:722:          chat_id: chatId,
./lib/api.ts:727:    sendMessageDraft: (chatId, draftId, text) => {
./lib/api.ts:730:        chat_id: chatId,
./lib/api.ts:780:    downloadFile: async (fileId, suggestedName, tempDir, options) => {
./lib/api.ts:781:      return downloadTelegramFile(
./lib/attachments.ts:48:  chatId: number;
./lib/attachments.ts:81:    chatId: number,
./lib/attachments.ts:103:      "telegram_attach can only be used while replying to an active Telegram turn",
./lib/attachments.ts:191:          chat_id: String(turn.chatId),
./lib/attachments.ts:204:        turn.chatId,
./docs/architecture.md:5:`pi-telegram` is a session-local pi extension that binds one Telegram DM to one running pi session. The bridge owns four main responsibilities:
./docs/architecture.md:16:Architecture shorthand: this repository uses a `Flat Domain DAG`: cohesive bridge domains live as flat `/lib/*.ts` modules, local imports must form a directed acyclic graph, shared buckets are avoided, and `index.ts` wires live pi/Telegram ports plus session state.
./docs/architecture.md:22:Naming rule: because the repository already scopes this codebase to Telegram, extracted module and test filenames use bare domain names such as `api.ts`, `queue.ts`, `updates.ts`, and `queue.test.ts` rather than repeating `telegram-*` in every filename.
./docs/architecture.md:26:- Telegram Bot API concrete transport shapes live with Telegram API helpers in `/lib/api.ts`, while persisted bot/session pairing state lives in `/lib/config.ts`; domain-owned runtime state types stay with their owners, such as queued/active turn state in `/lib/queue.ts` and preview state in `/lib/preview.ts`, while domain helpers prefer local structural `*Like` contracts instead of importing concrete wire DTOs
./docs/architecture.md:27:- Direct pi SDK imports are centralized in `/lib/pi.ts`, which exposes concrete pi SDK type exports, bound extension API runtime ports, and narrow bridge-facing helpers such as settings-manager creation plus context model/idle/pending-message/compaction adapters; `index.ts` uses this adapter namespace instead of importing `@mariozechner/pi-coding-agent` directly
./docs/architecture.md:28:- Session-local runtime primitives such as queue/control/priority ordering counters, lifecycle/dispatch flags, setup guard state, abort-handler storage/binding, typing-loop timer lifecycle, typing-loop starter binding, prompt-dispatch lifecycle/runtime adapters, and agent-end reset sequencing in `/lib/runtime.ts`; the runtime domain's essence is mutable cross-domain session coordination rather than business behavior. It exposes a grouped bridge runtime facade with named queue/lifecycle/setup/abort/typing ports that bind those primitives to one session state while remaining a cohesive state/runtime boundary, and `index.ts` still wires live Telegram API calls and status updates into those helpers. Preview-specific state, draft-support detection, and draft-id allocation live in `/lib/preview.ts`.
./docs/architecture.md:29:- Constants live in their owning domains instead of a shared constants module: API paths/inbound limits and inbound file-size env parsing in `/lib/api.ts`, outbound attachment limits and outbound attachment-size env parsing in `/lib/attachments.ts`, media-group debounce in `/lib/media.ts`, attachment-handler timeout and local tool lookup defaults in `/lib/handlers.ts`, menu cache/state bounds in `/lib/menu.ts`, preview throttle/draft bounds in `/lib/preview.ts`, typing cadence in `/lib/runtime.ts`, diagnostic ring limits in `/lib/status.ts`, Telegram prompt prefix in `/lib/turns.ts`, and system-prompt guidance in `/lib/registration.ts`.
./docs/architecture.md:30:- Queueing, narrow Telegram prompt content contracts, queue-store contracts/state helpers, active-turn state helpers, dispatch-readiness adapters, queue append/mutation runtime/controller adapters, control enqueue controllers, queue dispatch readiness/controller/runtime adapters, prompt enqueue/history planning/runtime/controllers, queue-runtime, session state appliers plus lifecycle/runtime sequencing, session start/shutdown sequencing plus hook binding, agent-start/agent-end lifecycle handling plus hook/runtime binding, and tool lifecycle handling plus tool-execution hook/runtime binding in `/lib/queue.ts`
./docs/architecture.md:31:- Model identity/thinking-level contracts, scoped model-pattern parsing/resolution/sorting, current-model store/update/runtime helpers, in-flight model-switch state helpers, restart eligibility, delayed abort decisions, Telegram-prefix defaulted continuation prompt construction, continuation queue adapters, and model-switch controller/runtime binding over queue-owned turns in `/lib/model.ts`
./docs/architecture.md:32:- Preview transport-selection, assistant-message preview lifecycle hook binding/handling, preview-finalization, preview controller state/reset helpers, preview Bot API message/rendered-chunk transport adapters, preview-controller/assistant-preview runtime binding, reply-metadata defaulting through the replies-domain helper, and preview-runtime helpers in `/lib/preview.ts`
./docs/architecture.md:34:- Preview appearance and snapshot derivation stay in `/lib/rendering.ts`, while `/lib/preview.ts` owns transport and lifecycle decisions, so richer preview strategies can evolve without entangling Markdown formatting with Telegram delivery state
./docs/architecture.md:35:- Polling request, start/stop controller state orchestration, polling activity readers, stop-condition, structural config contract, long-poll loop helpers, and poll-loop/controller runtime wiring over Telegram transport ports in `/lib/polling.ts`
./docs/architecture.md:36:- Telegram persisted config shape, config-path defaults, config file read/write helpers, mutable config-store accessors, single-user authorization, and first-user pairing side effects/runtime adapters in `/lib/config.ts`
./docs/architecture.md:38:- Telegram turn-building helpers, runtime turn-builder wiring over media download ports and media-owned downloaded-file metadata contracts, inbound attachment handler processing, queued-prompt edit runtime binding, and Node-backed image-file reads for pi image inputs in `/lib/turns.ts`
./docs/architecture.md:39:- Telegram media/text extraction, file-info normalization, downloaded-message-file metadata contracts, inbound file download assembly, media-group debounce helpers, media-group controller state, and media-group-aware authorized-message dispatch adapter wiring in `/lib/media.ts`
./docs/architecture.md:46:- Setup/token prompt, environment fallback, guarded setup runtime adapter wiring, structural setup config contract, token validation, config persistence orchestration, and setup notification helpers in `/lib/setup.ts`
./docs/architecture.md:48:- Status-bar rendering/runtime adapters, bridge status state adapters, status-message rendering and status-HTML binding, structural queue-lane status view contracts, structured redacted runtime-event recording, recent-event recorder state, recent-event line formatting, and grouped pi-side diagnostics helpers in `/lib/status.ts`
./docs/architecture.md:49:- Menu settings/model-registry access through structural ports, menu-state construction, menu runtime state/cache controller, menu-state storage pruning/refresh helpers, command open-flow branching, action runtime/state-builder adapters, menu callback handler adapters, stored callback entry/runtime routing, model-menu input-cache/state-building resolution, pure menu-page derivation, pure menu render-payload builders, menu-message runtime, callback parsing, callback mutation helpers, full model-callback planning and execution, interface-polished callback effect ports, status-thinking callback handling, and UI helpers in `/lib/menu.ts`
./docs/architecture.md:51:- Remaining `index.ts` wiring is intentionally cross-domain adapter code that closes over live extension state, pi callbacks, Telegram API ports, and status updates; keep repeated wiring DRY through small local adapter helpers or owning-domain contracts when that reduces duplication without obscuring live state, and extract more only when a boundary can move cohesive behavior into an owning domain instead of relocating one-off closures
./docs/architecture.md:54:- Mirrored domain regression coverage lives in `/tests/*.test.ts` using the same bare domain naming scheme, and architecture-invariant coverage in `/tests/invariants.test.ts` checks that the local `index.ts` plus `/lib/*.ts` import graph stays acyclic, shared bucket domains such as `lib/constants.ts` or `lib/types.ts` are not reintroduced, empty interface-extension shells stay collapsed into clearer type aliases, direct pi SDK imports stay centralized, `index.ts` source code stays free of direct Node runtime imports, local helper declarations, local arrow adapters, direct `process.env`, and direct `pi.*` receiver access, `/lib/runtime.ts` stays free of local domain imports, structural leaf domains stay free of local nominal imports, the menu domain stays on structural ports without re-exporting model, API transport stays decoupled from persisted config defaults, structural update/media domains stay decoupled from concrete API transport shapes, and attachment delivery stays decoupled from queue/inbound media/API helpers
./docs/architecture.md:58:`/telegram-setup` uses a progressive-enhancement flow for the bot token prompt:
./docs/architecture.md:60:1. Show the locally saved token from `~/.pi/agent/telegram.json` when one already exists
./docs/architecture.md:64:Because `ctx.ui.input()` only exposes placeholder text, the bridge uses `ctx.ui.editor()` whenever a real default value must appear already filled in. The persisted `telegram.json` config is written with private `0600` permissions because it contains the bot token.
./docs/architecture.md:68:Telegram bot configuration stays in `~/.pi/agent/telegram.json`; singleton runtime ownership lives separately in `~/.pi/agent/locks.json` under `@llblab/pi-telegram`. `/telegram-connect` acquires or moves that lock before polling starts, and `/telegram-disconnect` stops polling and releases it. Session start may read the existing lock and resume polling when the lock already points at the current `pid`/`cwd`; after a full pi process restart, it may also replace a stale lock from the same `cwd` and resume polling automatically. Session start does not create new ownership from an inactive lock, a live external lock, or a stale lock from another directory. Session replacement suspends polling and ownership watchers without releasing the lock, allowing the next session-start hook in the same `pid`/`cwd` to resume from the existing explicit ownership. When a live external owner exists, `/telegram-connect` asks whether to move singleton ownership to the current pi instance. Active owners poll the lock while running through a snapshotted ownership context, so long-lived timers do not touch stale pi contexts after `/new`; they stop local polling when `locks.json` no longer points at their own `pid`/`cwd`, without deleting the new owner lock. Deleting `locks.json` resets runtime ownership without deleting Telegram configuration.
./docs/architecture.md:75:2. Each update offset is persisted only after the update handler succeeds; repeated handler failures are bounded so one poisoned update cannot stall polling forever
./docs/architecture.md:78:5. Files are streamed into `~/.pi/agent/tmp/telegram` with a default 50 MiB size limit, partial-download cleanup on failures, and stale temp cleanup on session start; operators can tune the limit with `PI_TELEGRAM_INBOUND_FILE_MAX_BYTES` or `TELEGRAM_MAX_FILE_SIZE_BYTES`
./docs/architecture.md:79:6. Configured inbound attachment handlers may run on downloaded files by MIME wildcard, Telegram attachment type, or generic match selector; command handlers receive safe argv substitution for `{filename}`/`{path}`/`{basename}`/`{mime}`/`{type}`, and tool handlers invoke locally available tools by name
./docs/architecture.md:87:The bridge keeps its own Telegram queue and does not rely only on pi's internal pending-message state.
./docs/architecture.md:119:When `/stop` aborts an active Telegram turn, queued follow-up Telegram messages can be preserved as prior-user history for the next turn. This keeps later Telegram input from being silently dropped after an interrupted run.
./docs/architecture.md:154:Telegram prompt responses use explicit delivery context to attach outbound text, rich previews, errors, attachment notices, and uploads as Telegram replies to the source prompt when possible. Reply metadata is opt-in per delivery path, uses `reply_parameters` with `allow_sending_without_reply: true`, and is applied only to the first chunk of split long responses; continuation chunks are sent as normal adjacent messages. Media-group turns reply to the turn's representative `replyToMessageId`, not to every source message in the group.
./docs/architecture.md:156:Outbound files are sent only after the active Telegram turn completes, must be staged through the `telegram_attach` tool, are staged atomically per tool call, are checked against a default 50 MiB limit configurable through `PI_TELEGRAM_OUTBOUND_ATTACHMENT_MAX_BYTES` or `TELEGRAM_MAX_ATTACHMENT_SIZE_BYTES`, and use file-backed multipart blobs so large sends do not require preloading whole files into memory.
./docs/architecture.md:160:The bridge exposes Telegram-side session controls in addition to regular chat forwarding.
./docs/architecture.md:164:- `/status` for model, usage, cost, and context visibility, queued as a high-priority control item when needed
./docs/architecture.md:165:- Inline status buttons for model and thinking adjustments, applying idle selections immediately while still respecting busy-run restart rules; model-menu inputs are cached briefly and stored inline-menu states are pruned by TTL/LRU so old keyboards expire predictably
./docs/architecture.md:167:- `/compact` for Telegram-triggered pi session compaction when the bridge is idle
./docs/architecture.md:169:- `/telegram-status` for pi-side diagnostics as grouped line-by-line sections separated by blank lines: connection, polling, execution, queue, and the recent redacted runtime/API event ring. These sections include polling state, last update id, active turn source ids, pending dispatch, compaction state, active tool count, pending model-switch state, total queue depth, and queue-lane counts. The event ring records transport/API, polling/update, prompt-dispatch, control-action, typing, compaction, setup, session-lifecycle, and attachment queue/delivery failures; benign unchanged edit responses and unsupported empty draft-clear attempts are filtered out so expected preview transport noise does not obscure real failures
./docs/architecture.md:183:This behavior is intentionally limited to runs currently owned by the Telegram bridge. If pi is busy with non-Telegram work, the bridge still refuses the switch instead of hijacking unrelated session activity.
./lib/commands.ts:30:    description: "Show model, usage, cost, and context status",
./lib/commands.ts:33:  { command: "compact", description: "Compact the current pi session" },
./lib/commands.ts:123:  setAllowedUserId: (userId: number) => void;
./lib/commands.ts:125:  persistConfig: () => Promise<void>;
./lib/commands.ts:148:  chatId: number;
./lib/commands.ts:161:    chatId: number,
./lib/commands.ts:166:    chatId: number,
./lib/commands.ts:171:    chatId: number,
./lib/commands.ts:197:    chatId: message.chat.id,
./lib/commands.ts:204:    chatId: number;
./lib/commands.ts:232:    chatId: number;
./lib/commands.ts:296:      return deps.showStatus(target.chatId, target.replyToMessageId, ctx);
./lib/commands.ts:300:      return deps.openModelMenu(target.chatId, target.replyToMessageId, ctx);
./lib/commands.ts:304:      await deps.sendTextReply(target.chatId, target.replyToMessageId, text);
./lib/commands.ts:350:  setAllowedUserId: (userId: number) => void;
./lib/commands.ts:352:  persistConfig: () => Promise<void>;
./lib/commands.ts:481:    persistConfig: deps.persistConfig,
./lib/commands.ts:595:    persistConfig: deps.persistConfig,
./lib/commands.ts:714:          persistConfig: deps.persistConfig,
./docs/locks.md:3:`locks.json` is a shared registry for singleton pi extensions.
./docs/locks.md:8:~/.pi/agent/locks.json
./docs/locks.md:13:```json
./docs/locks.md:15:  "@llblab/pi-telegram": {
./docs/locks.md:28:1. `package.json/name` for npm-style pi packages
./docs/locks.md:32:For npm-style package extensions, the canonical value is the `package.json` `name`. Implementations may keep that value as a small local constant when it is clearer than runtime package introspection. The fallback rules are only for unpackaged extensions.
./docs/locks.md:37:extensions/pi-telegram/package.json name=@llblab/pi-telegram -> @llblab/pi-telegram
./docs/locks.md:38:extensions/pi-telegram/index.ts without package.json         -> pi-telegram
./docs/locks.md:39:extensions/pi-telegram.ts                                    -> pi-telegram
./docs/locks.md:44:```json
./docs/locks.md:50:`pid` is the process that currently owns the singleton runtime. `cwd` should be stored when ownership is tied to a pi session directory.
./docs/locks.md:61:Lock writes must be caused by an explicit user-initiated runtime event, such as `/wakeup-start`, `/telegram-connect`, or a confirmed takeover prompt.
./docs/locks.md:63:Extension initialization and session-start hooks may read `locks.json`, update local status, install ownership watchers, and resume local work when the existing lock already points at the current `pid`/`cwd`. After a full process restart, a session-start hook may replace a stale lock from the same `cwd` to restore explicitly requested ownership. They must not create ownership from an inactive lock, take over a live external owner, or replace a stale lock from another directory by themselves. Such locks should stay visible as state until the user runs the start/connect command. Session replacement should suspend local runtime work and ownership watchers without releasing the lock, so the next session in the same `pid`/`cwd` can resume from explicit ownership.
./docs/locks.md:69:```json
./docs/locks.md:87:- Debug data belongs in `locks.json`, not in normal status output
./docs/locks.md:91:Singleton extensions with footer/status presence should expose quiet but explicit local state. For example, pi-wakeup uses:
./docs/locks.md:103:3. On confirmation, write the current `{ "pid": ..., "cwd": ... }` to this extension's key in `locks.json`
./docs/locks.md:104:4. The previous owner must notice that `locks.json` no longer points at its own `pid`/`cwd` and stop local runtime work without deleting the new lock
./docs/locks.md:109:pi-telegram
./docs/locks.md:118:The previous owner may use `fs.watch`, mtime polling, or an existing status/timer tick. Long-lived watchers should compare against a snapshotted `pid`/`cwd` identity rather than a live pi context object, because session replacement such as `/new` makes captured contexts stale. The important contract is graceful local shutdown after ownership mismatch.
./docs/locks.md:122:Delete `~/.pi/agent/locks.json` to reset singleton runtime ownership for all participating extensions without deleting their configuration files such as `telegram.json`.
./docs/locks.md:130:- Lock file around `locks.json`, or
./docs/locks.md:136:Migrations from legacy lock files or legacy keys should be one-off cleanup work. Runtime ownership should read and write only `locks.json` under the canonical identity key.
./lib/config.ts:3: * Owns persisted bot/session pairing state, local config storage, authorization policy, and first-user pairing side effects
./lib/config.ts:19:  return join(getAgentDir(), "telegram.json");
./lib/config.ts:39:  setAllowedUserId: (userId: number) => void;
./lib/config.ts:40:  load: () => Promise<void>;
./lib/config.ts:41:  persist: (config?: TelegramConfig) => Promise<void>;
./lib/config.ts:92:    setAllowedUserId: (userId) => {
./lib/config.ts:93:      config.allowedUserId = userId;
./lib/config.ts:95:    load: async () => {
./lib/config.ts:98:    persist: async (nextConfig = config) => {
./lib/config.ts:105:  | { kind: "pair"; userId: number }
./lib/config.ts:112:  setAllowedUserId: (userId: number) => void;
./lib/config.ts:113:  persistConfig: () => Promise<void>;
./lib/config.ts:119:  setAllowedUserId: (userId: number) => void;
./lib/config.ts:120:  persistConfig: () => Promise<void>;
./lib/config.ts:125:  pairIfNeeded: (userId: number, ctx: TContext) => Promise<boolean>;
./lib/config.ts:129:  userId: number,
./lib/config.ts:133:    return { kind: "pair", userId };
./lib/config.ts:135:  if (userId === allowedUserId) {
./lib/config.ts:142:  userId: number,
./lib/config.ts:146:    userId,
./lib/config.ts:150:  deps.setAllowedUserId(authorization.userId);
./lib/config.ts:151:  await deps.persistConfig();
./lib/config.ts:160:    pairIfNeeded: (userId, ctx) =>
./lib/config.ts:161:      pairTelegramUserIfNeeded(userId, {
./lib/config.ts:165:        persistConfig: deps.persistConfig,
./index.ts:46:  const telegramQueueStore =
./index.ts:64:      getQueuedItems: telegramQueueStore.getQueuedItems,
./index.ts:78:      ...telegramQueueStore,
./index.ts:103:    downloadFile: downloadTelegramBridgeFile,
./index.ts:141:      ...telegramQueueStore,
./index.ts:208:    state: pollingControllerState,
./index.ts:213:    persistConfig: configStore.persist,
./index.ts:225:      telegramQueueStore,
./index.ts:237:      downloadFile: downloadTelegramBridgeFile,
-- files --
AGENTS.md
BACKLOG.md
CHANGELOG.md
docs/architecture.md
docs/locks.md
docs/README.md
.git/config
.git/description
.git/HEAD
.git/hooks/applypatch-msg.sample
.git/hooks/commit-msg.sample
.git/hooks/fsmonitor-watchman.sample
.git/hooks/post-update.sample
.git/hooks/pre-applypatch.sample
.git/hooks/pre-commit.sample
.git/hooks/pre-merge-commit.sample
.git/hooks/prepare-commit-msg.sample
.git/hooks/pre-push.sample
.git/hooks/pre-rebase.sample
.git/hooks/pre-receive.sample
.git/hooks/push-to-checkout.sample
.git/hooks/sendemail-validate.sample
.git/hooks/update.sample
.github/workflows/validate.yml
.gitignore
.git/index
.git/info/exclude
.git/logs/HEAD
.git/packed-refs
.git/shallow
index.ts
lib/api.ts
lib/attachments.ts
lib/commands.ts
lib/config.ts
lib/handlers.ts
lib/locks.ts
lib/media.ts
lib/menu.ts
lib/model.ts
lib/pi.ts
lib/polling.ts
lib/preview.ts
lib/queue.ts
lib/registration.ts
lib/rendering.ts
lib/replies.ts
lib/routing.ts
lib/runtime.ts
lib/setup.ts
lib/status.ts
lib/turns.ts
lib/updates.ts
package.json
package-lock.json
README.md
screenshot.png
tests/api.test.ts
tests/attachments.test.ts
tests/commands.test.ts
tests/config.test.ts
tests/handlers.test.ts
tests/invariants.test.ts
tests/locks.test.ts
tests/media.test.ts
tests/menu.test.ts
tests/model.test.ts
tests/pi.test.ts
tests/polling.test.ts
tests/preview.test.ts
tests/queue.test.ts
tests/registration.test.ts
tests/rendering.test.ts
tests/replies.test.ts
tests/routing.test.ts
tests/runtime.test.ts
tests/status.test.ts
tests/turns.test.ts
tests/updates.test.ts
tsconfig.json

[Showing lines 196-511 of 511 (50.0KB limit). Full output: /tmp/pi-bash-bcaceea588b7a198.log]


ENOENT: no such file or directory, access '/tmp/pi-telegram-audit-*/pi-telebridge/README.md'


ENOENT: no such file or directory, access '/tmp/pi-telegram-audit-*/pi-telebridge/package.json'


ENOENT: no such file or directory, access '/tmp/pi-telegram-audit-*/pi-telegram/lib/config.ts'


ENOENT: no such file or directory, access '/tmp/pi-telegram-audit-*/pi-telegram/lib/polling.ts'


ls -d /tmp/pi-telegram-audit-* && for d in /tmp/pi-telegram-audit-*; do echo "== $d =="; find "$d" -maxdepth 2 \( -name README.md -o -name package.json \) | sort; done


/tmp/pi-telegram-audit-531lLN
== /tmp/pi-telegram-audit-531lLN ==
/tmp/pi-telegram-audit-531lLN/pi-telebridge/package.json
/tmp/pi-telegram-audit-531lLN/pi-telebridge/README.md
/tmp/pi-telegram-audit-531lLN/pi-telegram/package.json
/tmp/pi-telegram-audit-531lLN/pi-telegram/README.md


# pi-telebridge

A [pi](https://github.com/badlogic/pi-mono) extension that creates a two-way relay between your active pi coding agent session and a Telegram bot. Enable it per-session with `/telegram`, then interact with your session from your phone.

- **Agent → Phone**: Every final assistant response is forwarded to your Telegram chat
- **Phone → Agent**: Your Telegram replies are injected as user messages into the session
- **Voice Messages**: Voice, audio, and video note messages are downloaded and forwarded to the agent for transcription
- **Photos**: Photos are downloaded and forwarded to the agent for viewing/analysis

Both the pi TUI and Telegram inputs coexist — you can use either at any time.

## Install

```bash
pi install npm:pi-telebridge
```

## Setup

1. Create a bot with [@BotFather](https://t.me/BotFather) on Telegram and copy the token
2. In pi, run `/telegram setup`
3. Enter your bot token when prompted
4. Send any message to your bot on Telegram — this links your chat ID
5. Done! Config is saved to `~/.pi/agent/telebridge.json`

### Environment Variables (optional)

You can skip the interactive setup by setting these beforehand:

| Variable | Purpose |
|----------|---------|
| `TELEGRAM_BOT_TOKEN` | Bot token from @BotFather |
| `TELEGRAM_CHAT_ID` | Your Telegram chat ID |

## Usage

| Command | Description |
|---------|-------------|
| `/telegram` | Toggle relay on/off for this session |
| `/telegram setup` | Guided setup: enter bot token, discover chat ID |
| `/telegram status` | Show connection state, chat ID, relay status |

When the relay is enabled:
- A **📡 TG** indicator appears in the footer
- Every assistant response is forwarded to your Telegram chat
- Messages you send to the bot are injected into the pi session
- Voice/audio messages are downloaded to `~/.pi/agent/voice_messages/` and forwarded as `[Voice message received: <path>]`
- Photos are downloaded to `~/.pi/agent/photo_messages/` and forwarded as `[Photo received: <path>]`
- If the agent is idle, your message starts a new turn; if busy, it's queued as a follow-up

## Security

- Only messages from your configured `chat_id` are accepted
- All other Telegram messages are silently ignored
- Bot token and chat ID are stored locally in `~/.pi/agent/telebridge.json`

## License

MIT


pi-telebridge

A pi extension that creates a two-way relay between your active pi coding agent session and a Telegram bot.

pi-package pi-extension pi telegram telegram-bot


/**
 * Telegram bridge config and pairing helpers
 * Owns persisted bot/session pairing state, local config storage, authorization policy, and first-user pairing side effects
 */

import { chmod, mkdir, readFile, writeFile } from "node:fs/promises";
import { homedir } from "node:os";
import { join, resolve } from "node:path";

import type { TelegramAttachmentHandlerConfig } from "./handlers.ts";

function getAgentDir(): string {
  return process.env.PI_CODING_AGENT_DIR
    ? resolve(process.env.PI_CODING_AGENT_DIR)
    : join(homedir(), ".pi", "agent");
}

function getConfigPath(): string {
  return join(getAgentDir(), "telegram.json");
}

export interface TelegramConfig {
  botToken?: string;
  botUsername?: string;
  botId?: number;
  allowedUserId?: number;
  lastUpdateId?: number;
  attachmentHandlers?: TelegramAttachmentHandlerConfig[];
}

export interface TelegramConfigStore {
  get: () => TelegramConfig;
  set: (config: TelegramConfig) => void;
  update: (mutate: (config: TelegramConfig) => void) => void;
  getBotToken: () => string | undefined;
  hasBotToken: () => boolean;
  getAllowedUserId: () => number | undefined;
  getAttachmentHandlers: () => TelegramAttachmentHandlerConfig[] | undefined;
  setAllowedUserId: (userId: number) => void;
  load: () => Promise<void>;
  persist: (config?: TelegramConfig) => Promise<void>;
}

export interface TelegramConfigStoreOptions {
  initialConfig?: TelegramConfig;
  agentDir?: string;
  configPath?: string;
}

export async function readTelegramConfig(
  configPath: string,
): Promise<TelegramConfig> {
  try {
    const content = await readFile(configPath, "utf8");
    return JSON.parse(content) as TelegramConfig;
  } catch {
    return {};
  }
}

export async function writeTelegramConfig(
  agentDir: string,
  configPath: string,
  config: TelegramConfig,
): Promise<void> {
  await mkdir(agentDir, { recursive: true });
  await writeFile(configPath, JSON.stringify(config, null, "\t") + "\n", {
    encoding: "utf8",
    mode: 0o600,
  });
  await chmod(configPath, 0o600);
}

export function createTelegramConfigStore(
  options: TelegramConfigStoreOptions = {},
): TelegramConfigStore {
  let config: TelegramConfig = options.initialConfig ?? {};
  const agentDir = options.agentDir ?? getAgentDir();
  const configPath = options.configPath ?? getConfigPath();
  return {
    get: () => config,
    set: (nextConfig) => {
      config = nextConfig;
    },
    update: (mutate) => {
      mutate(config);
    },
    getBotToken: () => config.botToken,
    hasBotToken: () => !!config.botToken,
    getAllowedUserId: () => config.allowedUserId,
    getAttachmentHandlers: () => config.attachmentHandlers,
    setAllowedUserId: (userId) => {
      config.allowedUserId = userId;
    },
    load: async () => {
      config = await readTelegramConfig(configPath);
    },
    persist: async (nextConfig = config) => {
      await writeTelegramConfig(agentDir, configPath, nextConfig);
    },
  };
}

export type TelegramAuthorizationState =
  | { kind: "pair"; userId: number }
  | { kind: "allow" }
  | { kind: "deny" };

export interface TelegramUserPairingDeps<TContext> {
  allowedUserId?: number;
  ctx: TContext;
  setAllowedUserId: (userId: number) => void;
  persistConfig: () => Promise<void>;
  updateStatus: (ctx: TContext) => void;
}

export interface TelegramUserPairingRuntimeDeps<TContext> {
  getAllowedUserId: () => number | undefined;
  setAllowedUserId: (userId: number) => void;
  persistConfig: () => Promise<void>;
  updateStatus: (ctx: TContext) => void;
}

export interface TelegramUserPairingRuntime<TContext> {
  pairIfNeeded: (userId: number, ctx: TContext) => Promise<boolean>;
}

export function getTelegramAuthorizationState(
  userId: number,
  allowedUserId?: number,
): TelegramAuthorizationState {
  if (allowedUserId === undefined) {
    return { kind: "pair", userId };
  }
  if (userId === allowedUserId) {
    return { kind: "allow" };
  }
  return { kind: "deny" };
}

export async function pairTelegramUserIfNeeded<TContext>(
  userId: number,
  deps: TelegramUserPairingDeps<TContext>,
): Promise<boolean> {
  const authorization = getTelegramAuthorizationState(
    userId,
    deps.allowedUserId,
  );
  if (authorization.kind !== "pair") return false;
  deps.setAllowedUserId(authorization.userId);
  await deps.persistConfig();
  deps.updateStatus(deps.ctx);
  return true;
}

export function createTelegramUserPairingRuntime<TContext>(
  deps: TelegramUserPairingRuntimeDeps<TContext>,
): TelegramUserPairingRuntime<TContext> {
  return {
    pairIfNeeded: (userId, ctx) =>
      pairTelegramUserIfNeeded(userId, {
        allowedUserId: deps.getAllowedUserId(),
        ctx,
        setAllowedUserId: deps.setAllowedUserId,
        persistConfig: deps.persistConfig,
        updateStatus: deps.updateStatus,
      }),
  };
}


/**
 * Telegram polling domain helpers
 * Owns polling request builders, stop conditions, and the long-poll loop runtime for Telegram updates
 */

export interface TelegramPollingConfig {
  botToken?: string;
  lastUpdateId?: number;
}

export interface TelegramUpdate {
  update_id: number;
}

// Standard Telegram DM polling does not expose ordinary message-deletion events,
// so queue removal stays reaction-driven while delete-like business updates remain defensive-only.
export const TELEGRAM_ALLOWED_UPDATES = [
  "message",
  "edited_message",
  "callback_query",
  "message_reaction",
] as const;

export function buildTelegramInitialSyncRequest(): {
  offset: number;
  limit: number;
  timeout: number;
} {
  return {
    offset: -1,
    limit: 1,
    timeout: 0,
  };
}

export function buildTelegramLongPollRequest(lastUpdateId?: number): {
  offset?: number;
  limit: number;
  timeout: number;
  allowed_updates: readonly string[];
} {
  return {
    offset: lastUpdateId !== undefined ? lastUpdateId + 1 : undefined,
    limit: 10,
    timeout: 30,
    allowed_updates: TELEGRAM_ALLOWED_UPDATES,
  };
}

export function getLatestTelegramUpdateId(
  updates: TelegramUpdate[],
): number | undefined {
  return updates.at(-1)?.update_id;
}

export function shouldStopTelegramPolling(
  signalAborted: boolean,
  error: unknown,
): boolean {
  return (
    signalAborted ||
    (error instanceof DOMException && error.name === "AbortError")
  );
}

export interface TelegramPollingStartState {
  hasBotToken: boolean;
  hasPollingPromise: boolean;
}

export interface TelegramPollingControllerState {
  pollingPromise?: Promise<void>;
  pollingController?: AbortController;
}

export function createTelegramPollingControllerState(): TelegramPollingControllerState {
  return {};
}

export function isTelegramPollingControllerActive(
  state: TelegramPollingControllerState,
): boolean {
  return !!state.pollingPromise;
}

export function createTelegramPollingActivityReader(
  state: TelegramPollingControllerState,
): () => boolean {
  return () => isTelegramPollingControllerActive(state);
}

export interface TelegramPollingRuntimeDeps<TContext> {
  hasBotToken: () => boolean;
  getPollingPromise: () => Promise<void> | undefined;
  setPollingPromise: (promise: Promise<void> | undefined) => void;
  getPollingController: () => AbortController | undefined;
  setPollingController: (controller: AbortController | undefined) => void;
  stopTypingLoop: () => unknown;
  runPollLoop: (ctx: TContext, signal: AbortSignal) => Promise<void>;
  updateStatus: (ctx: TContext) => void;
  createAbortController?: () => AbortController;
}

export type TelegramPollingControllerDeps<TContext> = Omit<
  TelegramPollingRuntimeDeps<TContext>,
  | "getPollingPromise"
  | "setPollingPromise"
  | "getPollingController"
  | "setPollingController"
> & { state?: TelegramPollingControllerState };

export interface TelegramPollingController<TContext> {
  isActive: () => boolean;
  start: (ctx: TContext) => void;
  stop: () => Promise<void>;
}

export interface TelegramPollingControllerRuntimeDeps<
  TUpdate extends TelegramUpdate,
  TContext = unknown,
> extends TelegramPollLoopRunnerDeps<TUpdate, TContext> {
  state?: TelegramPollingControllerState;
  hasBotToken: () => boolean;
  stopTypingLoop: () => unknown;
  createAbortController?: () => AbortController;
}

export function createTelegramPollingControllerRuntime<
  TUpdate extends TelegramUpdate,
  TContext = unknown,
>(
  deps: TelegramPollingControllerRuntimeDeps<TUpdate, TContext>,
): TelegramPollingController<TContext> {
  return createTelegramPollingController({
    state: deps.state,
    hasBotToken: deps.hasBotToken,
    stopTypingLoop: deps.stopTypingLoop,
    runPollLoop: createTelegramPollLoopRunner<TUpdate, TContext>({
      getConfig: deps.getConfig,
      deleteWebhook: deps.deleteWebhook,
      getUpdates: deps.getUpdates,
      persistConfig: deps.persistConfig,
      handleUpdate: deps.handleUpdate,
      updateStatus: deps.updateStatus,
      sleep: deps.sleep,
      maxUpdateFailures: deps.maxUpdateFailures,
      recordRuntimeEvent: deps.recordRuntimeEvent,
    }),
    updateStatus: deps.updateStatus,
    createAbortController: deps.createAbortController,
  });
}

export function createTelegramPollingController<TContext>(
  deps: TelegramPollingControllerDeps<TContext>,
): TelegramPollingController<TContext> {
  const state = deps.state ?? createTelegramPollingControllerState();
  const runtimeDeps: TelegramPollingRuntimeDeps<TContext> = {
    ...deps,
    getPollingPromise: () => state.pollingPromise,
    setPollingPromise: (promise) => {
      state.pollingPromise = promise;
    },
    getPollingController: () => state.pollingController,
    setPollingController: (controller) => {
      state.pollingController = controller;
    },
  };
  return {
    isActive: () => isTelegramPollingControllerActive(state),
    start: (ctx) => startTelegramPollingRuntime(ctx, runtimeDeps),
    stop: () => stopTelegramPollingRuntime(runtimeDeps),
  };
}

export function shouldStartTelegramPolling(
  state: TelegramPollingStartState,
): boolean {
  return state.hasBotToken && !state.hasPollingPromise;
}

export async function stopTelegramPollingRuntime<TContext>(
  deps: TelegramPollingRuntimeDeps<TContext>,
): Promise<void> {
  deps.stopTypingLoop();
  deps.getPollingController()?.abort();
  deps.setPollingController(undefined);
  await deps.getPollingPromise()?.catch(() => undefined);
  deps.setPollingPromise(undefined);
}

export function startTelegramPollingRuntime<TContext>(
  ctx: TContext,
  deps: TelegramPollingRuntimeDeps<TContext>,
): void {
  if (
    !shouldStartTelegramPolling({
      hasBotToken: deps.hasBotToken(),
      hasPollingPromise: !!deps.getPollingPromise(),
    })
  ) {
    return;
  }
  const controller = deps.createAbortController?.() ?? new AbortController();
  deps.setPollingController(controller);
  const promise = deps.runPollLoop(ctx, controller.signal).finally(() => {
    deps.setPollingPromise(undefined);
    deps.setPollingController(undefined);
    deps.updateStatus(ctx);
  });
  deps.setPollingPromise(promise);
  deps.updateStatus(ctx);
}

export interface TelegramRuntimeEventRecorderPort {
  recordRuntimeEvent?: (
    category: string,
    error: unknown,
    details?: Record<string, unknown>,
  ) => void;
}

export interface TelegramPollLoopDeps<
  TUpdate extends TelegramUpdate,
  TContext = unknown,
> extends TelegramRuntimeEventRecorderPort {
  ctx: TContext;
  signal: AbortSignal;
  config: TelegramPollingConfig;
  deleteWebhook: (signal: AbortSignal) => Promise<unknown>;
  getUpdates: (
    body: Record<string, unknown>,
    signal: AbortSignal,
  ) => Promise<TUpdate[]>;
  persistConfig: () => Promise<void>;
  handleUpdate: (update: TUpdate, ctx: TContext) => Promise<void>;
  onErrorStatus: (message: string) => void;
  onStatusReset: () => void;
  sleep: (ms: number) => Promise<void>;
  maxUpdateFailures?: number;
}

export interface TelegramPollLoopRunnerDeps<
  TUpdate extends TelegramUpdate,
  TContext = unknown,
> extends TelegramRuntimeEventRecorderPort {
  getConfig: () => TelegramPollingConfig;
  deleteWebhook: (signal: AbortSignal) => Promise<unknown>;
  getUpdates: (
    body: Record<string, unknown>,
    signal: AbortSignal,
  ) => Promise<TUpdate[]>;
  persistConfig: () => Promise<void>;
  handleUpdate: (update: TUpdate, ctx: TContext) => Promise<void>;
  updateStatus: (ctx: TContext, message?: string) => void;
  sleep?: (ms: number) => Promise<void>;
  maxUpdateFailures?: number;
}

export function createTelegramPollLoopRunner<
  TUpdate extends TelegramUpdate,
  TContext = unknown,
>(
  deps: TelegramPollLoopRunnerDeps<TUpdate, TContext>,
): (ctx: TContext, signal: AbortSignal) => Promise<void> {
  const sleep =
    deps.sleep ??
    ((ms: number) =>
      new Promise<void>((resolve) => {
        setTimeout(resolve, ms);
      }));
  return (ctx, signal) =>
    runTelegramPollLoop({
      ctx,
      signal,
      config: deps.getConfig(),
      deleteWebhook: deps.deleteWebhook,
      getUpdates: deps.getUpdates,
      persistConfig: deps.persistConfig,
      handleUpdate: deps.handleUpdate,
      onErrorStatus: (message) => {
        deps.updateStatus(ctx, message);
      },
      onStatusReset: () => {
        deps.updateStatus(ctx);
      },
      sleep,
      maxUpdateFailures: deps.maxUpdateFailures,
      recordRuntimeEvent: deps.recordRuntimeEvent,
    });
}

function getTelegramPollingErrorMessage(error: unknown): string {
  return error instanceof Error ? error.message : String(error);
}

export async function runTelegramPollLoop<
  TUpdate extends TelegramUpdate,
  TContext = unknown,
>(deps: TelegramPollLoopDeps<TUpdate, TContext>): Promise<void> {
  if (!deps.config.botToken) return;
  try {
    await deps.deleteWebhook(deps.signal);
  } catch {
    // ignore
  }
  if (deps.config.lastUpdateId === undefined) {
    try {
      const updates = await deps.getUpdates(
        buildTelegramInitialSyncRequest(),
        deps.signal,
      );
      const lastUpdateId = getLatestTelegramUpdateId(updates);
      if (lastUpdateId !== undefined) {
        deps.config.lastUpdateId = lastUpdateId;
        await deps.persistConfig();
      }
    } catch {
      // ignore
    }
  }
  const maxUpdateFailures = Math.max(1, deps.maxUpdateFailures ?? 3);
  const updateFailures = new Map<number, number>();
  let handledUpdateFailureRethrown = false;
  while (!deps.signal.aborted) {
    try {
      const updates = await deps.getUpdates(
        buildTelegramLongPollRequest(deps.config.lastUpdateId),
        deps.signal,
      );
      for (const update of updates) {
        try {
          await deps.handleUpdate(update, deps.ctx);
          deps.config.lastUpdateId = update.update_id;
          updateFailures.delete(update.update_id);
          await deps.persistConfig();
        } catch (error) {
          const failureCount = (updateFailures.get(update.update_id) ?? 0) + 1;
          updateFailures.set(update.update_id, failureCount);
          deps.recordRuntimeEvent?.("polling", error, {
            phase: "handleUpdate",
            updateId: update.update_id,
            failureCount,
          });
          if (failureCount < maxUpdateFailures) {
            handledUpdateFailureRethrown = true;
            throw error;
          }
          const message = getTelegramPollingErrorMessage(error);
          deps.onErrorStatus(
            `skipping Telegram update ${update.update_id} after ${failureCount} failures: ${message}`,
          );
          deps.config.lastUpdateId = update.update_id;
          updateFailures.delete(update.update_id);
          await deps.persistConfig();
        }
      }
    } catch (error) {
      if (shouldStopTelegramPolling(deps.signal.aborted, error)) return;
      if (handledUpdateFailureRethrown) {
        handledUpdateFailureRethrown = false;
      } else {
        deps.recordRuntimeEvent?.("polling", error, { phase: "loop" });
      }
      deps.onErrorStatus(getTelegramPollingErrorMessage(error));
      await deps.sleep(3000);
      deps.onStatusReset();
    }
  }
}