Skip to content
Agentmart
Submit a tool

CLAUDE.md · yagop/node-telegram-bot-api

CLAUDE.md from yagop/node-telegram-bot-api: 184 lines covering What this is, Commands, Architecture, Critical invariants and more.

Template by yagop

IndexedMIT★ 9,214
Write a review

Install

Works with Claude Code. Read the repository before you give any tool access to your data.

curl -fsSL https://raw.githubusercontent.com/yagop/node-telegram-bot-api/master/CLAUDE.md -o CLAUDE.md

Read it and adapt it to your project before use.

Get the weekly diff

The weekly diff: one email a week with new agent tools, notable updates and grade changes, plus security alerts for tools you follow. Double opt-in, one-click unsubscribe.

We use your email only for this newsletter. Privacy

About

The CLAUDE.md file that yagop/node-telegram-bot-api (9,214 stars) keeps for coding agents. It has 184 lines in 6 sections: What this is; Commands; Architecture; Critical invariants; Testing; Project skills (.claude/skills/). Use it as a model for your own project's instructions.

Use cases: prompt-engineering

No reviews yet. Installed it? Reviews come from verified installers only. How reviews work

What's in it

A CLAUDE.md file: project memory Claude Code loads at the start of every session.

For
Claude Code
Length
184 lines
File
CLAUDE.md

Sections

  1. What this is
  2. Commands
  3. Architecture
  4. Critical invariants
  5. Testing
  6. Project skills (.claude/skills/)

First lines (shown under the repo's MIT license)

# CLAUDE.md

This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.

## What this is

`node-telegram-bot-api` v2: a from-scratch, runtime-agnostic TypeScript Telegram Bot API client.
Node 18.2+, also runs on Bun, Deno, Cloudflare Workers, and Vercel/Deno Edge. The source is
ESM/web-standard; the **published** package is dual-module - `zshy` emits both ESM (`*.js`/`*.d.ts`)
and CJS (`*.cjs`/`*.d.cts`), exposed via the `exports` map's `import`/`require` conditions, so it can
be `import`ed or `require`d. `src/core` stays Node-free, so the edge story is unchanged. There is
**no v1 compatibility**; the v1->v2 migration cheatsheet lives in `CHANGELOG.md`.

Current development happens on `feat/v2-core`; the base/release branch is `master`.

## Commands

Bun is the primary toolchain (it is a devDependency and runs the unit suite and generator).

```sh
bun install                       # install deps (CI uses --frozen-lockfile)

npm run check                     # full local gate: typecheck (x3) + lint:core + check:edge + unit tests
npm run typecheck                 # tsc --noEmit over src (strict)
npm run typecheck:test            # tsc over test/ (needs bun types installed)
npm run lint:core                 # FAILS if src/core touches node:* or Node globals (see below)
npm run check:edge                # FAILS if src/core bundles a Node builtin (even transitively)

npm test                          # unit tests via Bun (test/unit)
npm run test:node:unit            # unit tests via Node's node:test + tsx (CI runs this on Node 22/24/26)
npm run test:e2e                  # LIVE tests against api.telegram.org (see Testing)

npm run build                     # zshy -> dist/ dual ESM+CJS (core, node, types subpaths); postbuild repairs CJS source-map refs
npm run generate:types            # regenerate generated sources from live Bot API docs
```

Run a single unit test:

```sh
bun test test/unit/transport.test.ts          # one file
bun test -t "429 then success retries"        # by test-name substring
```

## Architecture

Three source folders, one published package, exposed via subpath exports (`.`, `./node`, `./types`):

```text
src/
  core/   runtime-agnostic. Bot, Context, compose, the single Api client, Transport,
          serializeParams + encodeForm, InputFile, the markup / entity / media builders
          (optional sugar), longPoll, webhookCallback, framework webhook adapters
          (Express / Next.js), errors. Web-standard APIs only.
          -> zero node:* imports; runs on Node 18.2+ / Bun / Deno / Workers / edge.
  node/   the ONLY folder allowed to import node:*. Node-only sugar: fromPath() (fs
          uploads), createWebhookServer() (node:http -> delegates to core's
          webhookCallback), the managed run() polling runner, the DEBUG stderr sink.
  types/  the generated schema (re-exported by core): discriminated Update, the generated
          Api method signatures / *Params / *Result types, expanded MessageEntity.
```

The `exports` map points `.` at the core, `./node` at the Node helpers, and `./types` at the
schema; each subpath ships ESM + CJS with matching typings:

```jsonc
// package.json
"exports": {
  ".": {
    "import":  { "types": "./dist/core/index.d.ts",  "default": "./dist/core/index.js"  },
    "require": { "types": "./dist/core/index.d.cts", "default": "./dist/core/index.cjs" }
  },
  "./node":  { /* same shape -> dist/node/*  */ },
  "./types": { /* same shape -> dist/types/* */ }
}
```

`import { Bot } from "node-telegram-bot-api"` is the runtime-agnostic core; `import { fromPath }
from "node-telegram-bot-api/node"` opts into the Node helpers. The core-vs-node isolation is a CI
rule (`lint:core` + `check:edge`), not a package boundary, so the edge bundle never drags in a Node
builtin.

### The Api client

One **generated** `Api` class - no `Proxy`, no `RawApi`/`Api` split. One concrete method per Bot
API method, each a one-liner over a shared `request()`, taking a single params object plus an
optional trailing `AbortSignal` (`getMe(signal?)`, `sendMessage(params, signal?)`). Real methods
give correct stack traces, are greppable, and need no casts; adding a method is a regenerate, never
a hand-written body. `Bot` holds an `Api`; `ctx.api` and `bot.api` expose it. The whole client
ships regardless of which methods a bot calls (no per-method tree-shaking - a deliberate trade for
the single discoverable `api.*` namespace).

### Request pipeline (the only place serialization happens)

`Api.request` -> `serializeParams` (one `JSON.stringify` + `attach://` walk for nested files) ->
`encodeForm` -> `Transport` (injectable `fetch`, 429/transient retry with jittered backoff,
`{ok,result}` envelope unwrap). Structured fields (`reply_markup`, entities, `reply_parameters`,
media, ...) are **plain typed objects/arrays** that `serializeParams` stringifies once. Builders
(`InlineKeyboardBuilder`, `EntityBuilder`, `MediaGroupBuilder`, plus the sticker / profile-photo /
story builders) are **optional sugar** whose `.build()` returns the same plain shape - a literal
object works identically. `EntityType` is the typo-proof constant for entity kinds.

Every request goes out as form encoding: `x-www-form-urlencoded`, or `multipart/form-data` iff an
`InputFile` is present (the only thing that flips the body type). `encodeForm` has exactly three
branches: attach an `InputFile` as a part / spread a form-part composite (its JSON string + nested
parts) / set a string. `InputFile` wraps web-standard data only (`Blob | Uint8Array |
ReadableStream<Uint8Array>`); `fromPath()` (`/node`) is the only fs-backed constructor. File-bearing
params are `InputFile | string` - a bare string is always a `file_id`/URL, never a path. Nested
files (`sendMediaGroup`, sticker sets, ...) are hoisted to `attach://<name>` parts by
`serializeParams`.

### Dispatch

koa-style middleware over a per-update `Context`. `bot.use(mw)` registers middleware; `bot.on(kind)`,
`bot.command(name)`, `bot.hears(trigger)` are filter middleware in the same chain - registration
order wins, and each can wrap everything downstream via `await next()` (sessions, auth, rate-limit,
error boundaries). `Context` bundles the raw `update`, the typed `api`, a mutable `state` bag, and
chat-inferring shortcuts (`ctx.reply`, `ctx.answerCallbackQuery`). Update sources are async
generators - `longPoll(api, opts, signal)` replaces the old polling class. Two entry points share
**one** dispatch path: `bot.startPolling()` pumps `longPoll`; `bot.handleUpdate(update)` handles a
single update and is what `webhookCallback` calls.

### Webhooks

`webhookCallback(bot)` is a pure `(Request) => Promise<Response>` (Cloudflare Workers, Deno Deploy,
Vercel Edge, Bun.serve, Next.js App Router). Thin adapters mount it on an existing server:
`registerExpressWebhook`, `nodeFrameworkWebhook` (an `(req, res)` handler), `nextAppWebhook`. The
Node `createWebhookServer` (`/node`) adapts a `node:http` request and delegates to the same
callback - no duplicate request-handling logic.

### Transport & errors

`Transport` is the only module touching `fetch`; it is injectable (`opts.fetch`) so tests pass a
fake instead of monkeypatching globals. It merges the per-request timeout with the caller's signal
via `combineSignals` over `AbortSignal.timeout` (deliberately not `AbortSignal.any`, which needs
Node 18.17/20.3), unwraps the envelope, and retries: `429`s honor `retry_after` up to
`maxRetryAfterMs` (default 60s; a longer flood-wait surfaces immediately as a `TelegramApiError`),
transient failures (`NetworkError`/`TimeoutError`/5xx, classified by `isTransientError`) retry up to
`maxRetries` (default 2) with exponential jittered backoff. Opt-in proactive rate limiting
(`opts.rateLimit: { global?, perChat? }`) via a token-bucket keyed on `chat_id` - off by default,
zero overhead when unset.

Errors (`src/core/errors.ts`): a `TelegramBotError` base preserving `cause`, plus `NetworkError`,
`TimeoutError`, `ParseError`, `TelegramApiError` (structured `errorCode`/`description`/`parameters`
+ a `retryAfter` getter). Callers branch on values (`err.errorCode === 429`, `err.retryAfter`),
never on message text.

## Critical invariants

- **`src/core/api.ts` and `src/types/schemas.ts` are GENERATED** by `scripts/api-parser.ts` from
  the live Bot API docs. Do not hand-edit them; change the generator and run `npm run generate:types`.
  Adding a Bot API method = regenerate, never a hand-written method body. (See the `update-bot-api`
  skill for the full flow, including the hand-added `Bot`/`Context` sugar.)
- **`src/core` must stay Node-free.** No `node:*` imports, no bare-builtin imports, no Node globals
  (`Buffer`, `process`, `setImmediate`, ...). Enforced by both `lint:core` (static) and `check:edge`
  (bundler). Anything needing Node goes in `src/node`.
- **Keep generated runtime lists in lockstep with their source types** (e.g. `UPDATE_TYPES`,
  `MESSAGE_TYPES`) via the both-direction compile-time asserts already in place; `satisfies` alone
  misses absent keys.
- **ASCII only** in code, comments, and commit messages: use `-`, `->`, `...` - never em dashes or
  smart quotes (a commit hook rejects them; emoji are fine).

Signals

Stars
9,214
Forks
1,645
Open issues
0
Last commit (this folder)
Sep 6, 2026 (1 month ago)
Last release
Aug 24, 2026 (2 months ago)

From GitHub (fetched Oct 24, 2026).

Details

License
MIT
First seen
Oct 24, 2026
Last checked
Oct 24, 2026
Cite this page

CLAUDE.md · yagop/node-telegram-bot-api. Agent Mart. https://agentmart.dev/templates/yagop-node-telegram-bot-api-claude-md (last updated 2026-10-24)