# AGENTS.md — Botxona deployment rules for this bot repo

This file tells any AI coding agent (Cursor, Codex, Copilot, Claude Code, etc.) working in this
repository how this project must behave to run on **Botxona** (https://botxona.uz), a Telegram
bot hosting platform. Read this before making any change that touches configuration, networking,
storage, or dependencies. Reply to the human in plain Uzbek unless they write in another language —
most owners of bots on this platform are not developers.

<!--
  FILL IN when adapting this template for a real project:
-->
## Project overview

- **Bot name / purpose:** _[fill in — what does this bot do, in one sentence]_
- **Runtime:** _[python | node]_ — **framework:** _[aiogram 3 | python-telegram-bot | pyTelegramBotAPI | grammY | Telegraf | node-telegram-bot-api]_
- **Entry point:** _[e.g. main.py, or `npm start` → index.js]_
- **Storage:** _[SQLite under DATA_DIR | JSON under DATA_DIR | external Postgres via DATABASE_URL | none]_
- **Admin/owner identification:** _[e.g. ADMIN_IDS env var, comma-separated Telegram numeric ids]_

## Hard constraints — never violate these

These are enforced by Botxona's validator and sandbox, not just style preferences. Breaking one
either blocks deployment (🔴 red) or breaks the bot silently at runtime.

1. **Never hardcode the bot token.** Read it only from the `BOT_TOKEN` environment variable. It is
   injected by the platform at container start; it is never present in this repository.
2. **Never call `setWebhook`, and never start an HTTP server or open a listening port to receive
   Telegram updates.** This bot must use **long polling only** (`getUpdates` via the framework's
   built-in polling loop: `start_polling`, `bot.start()`, `bot.launch()`, `infinity_polling()`,
   `polling: true`). Botxona's proxy silently no-ops any `setWebhook` call — a webhook-based bot
   looks like it deployed successfully but never receives a single message. If a feature seems to
   need an inbound HTTP endpoint (e.g. a payment provider callback), it is **not supported** — poll
   that provider's API instead, or ask the maintainer how to handle it.
3. **All Telegram API calls go through `TELEGRAM_API_URL`**, never directly to
   `api.telegram.org`. Build the bot's HTTP client/session from
   `os.getenv("TELEGRAM_API_URL", "https://api.telegram.org")` (Python) or
   `process.env.TELEGRAM_API_URL || "https://api.telegram.org"` (Node) — see the exact snippet for
   this project's framework in the AI kit's `references/frameworks.md`
   (`https://botxona.uz/ai-kit/AGENTS.md` links back to the full kit, or ask for the
   `botxona-prepare` skill).
4. **All persistent writes go under `DATA_DIR` only** (`os.getenv("DATA_DIR", "./data")` /
   `process.env.DATA_DIR || "./data"`). The production container's filesystem is **read-only**
   everywhere except `DATA_DIR` and `/tmp` — writing anywhere else raises a runtime error, not just
   a lint warning. Create the directory before first use.
5. **Every dependency is pinned to an exact version** — `requirements.txt` with `==`
   (Python) or an exact/compatible version in `package.json` (Node), never `*`/`latest`/unpinned.
6. **Keep `.env.example` in sync with every environment variable the code actually reads,**
   including a short Uzbek comment for each. This is what lets a non-technical owner fill in
   correct values in the Botxona dashboard's settings form. Placeholder lines for
   `BOT_TOKEN`/`TELEGRAM_API_URL`/`DATA_DIR` stay in the file for documentation even though the
   platform fills their real values in automatically.
7. **Log to stdout/stderr only** (`print`, `console.log`, `logging` to console) — Botxona captures
   container stdout for the dashboard's log viewer. A file log, if wanted in addition, must also
   live under `DATA_DIR`.
8. **No compiled binaries or executables** in this repository (no `.exe`/`.dll`/`.so`, no
   precompiled native extensions committed, no committed `venv`/`node_modules`) — Botxona builds
   pure Python/Node.js source itself with pinned dependencies from a clean base image.
9. **Never bind to or assume port 25** is reachable — outbound SMTP is blocked at the network
   level. Use a transactional email API instead if this bot needs to send email.
10. **Don't try to defeat scale-to-zero with keep-alive pings.** An idle bot is put to sleep after
    ~10 minutes and wakes automatically (~1–3s) on the next real update — this is normal platform
    behaviour, not a bug. If this bot genuinely needs a background scheduler (reminders, digests),
    set `always_on: true` in `botxona.yaml` and use the framework's scheduler
    (APScheduler/`node-cron`/etc.) — the validator also auto-detects a scheduler and marks the bot
    always-on, but an explicit `botxona.yaml` is clearer and wins either way.

## Project layout this repo should have

```
<entry file>            # main.py, or package.json "scripts.start" → index.js
requirements.txt        # or package.json "dependencies" — every version pinned
.env.example             # every env var this code reads, with a one-line Uzbek comment each
botxona.yaml             # optional — only if runtime/entry/start/always_on need to be explicit
```

`botxona.yaml` fields (all optional):

```yaml
runtime: python        # python | node
entry: main.py          # explicit entry file, if auto-detection would be ambiguous
start: python -u bot.py # explicit full start command — wins over runtime/entry
always_on: true         # true = never sleep; false = force sleep even if a scheduler is detected
```

## How to validate before proposing a deploy

Prefer running an actual check over eyeballing the rules:

- If a Go toolchain and this `botxona` repo checkout are available:
  `cd server && GOTOOLCHAIN=auto go run ./cmd/bhvalidate <path to this project>` — prints a full
  report (🟢/🟡/🔴 per rule) exactly like the Botxona dashboard would.
- Otherwise, if `botxona_pack.py` is available (bundled with the AI kit, or fetched from
  `https://botxona.uz/ai-kit/botxona_pack.py`):
  `python3 botxona_pack.py . --check --url https://botxona.uz` — packs a ZIP, runs the same checks
  remotely, and writes a ready-to-paste fix prompt to `botxona-fix-prompt.txt` if anything needs
  attention.
- If neither tool is reachable, apply the rules above carefully and say so explicitly rather than
  claiming a check that wasn't actually run.

A 🔴 red result blocks deployment entirely; 🟡 yellow deploys but should usually be fixed; 🟢 green
is ready to go.

## How to deploy (for the human owner, not the agent)

1. Zip this project (exclude `.env`, `.git`, `venv`/`node_modules`, `__pycache__`) or push it to a
   GitHub repo Botxona can pull from.
2. Go to https://botxona.uz → **Bot qo'shish** (Add bot).
3. Paste the token from @BotFather.
4. Upload the ZIP (or connect the GitHub repo for auto-deploy on push).
5. Fill in every setting listed in `.env.example` under **Sozlamalar** (Settings).
6. Use the 15-minute free trial to confirm the bot responds, then pick a tariff and pay to keep it
   running — payment is a bank transfer of a unique amount, confirmed from the dashboard.

## When asked to add a feature that conflicts with these constraints

Say so plainly instead of silently working around the platform (e.g. "Botxona bots can't accept
inbound HTTP webhooks from a payment provider — here's a polling-based alternative instead") — a
non-technical owner needs to understand the tradeoff, not just get code that quietly doesn't work.

## Common mistakes agents make on this platform (avoid these)

- **"It builds, so it must be fine."** A build can succeed and the bot can still never receive a
  message, because it called `setWebhook` somewhere in a framework's "convenience" setup path. Grep
  for `setWebhook`/`set_webhook`/`webhook` before declaring a task done.
- **Assuming `api.telegram.org` is reachable directly.** In production it usually isn't (or isn't
  meant to be) — everything goes through `TELEGRAM_API_URL`. Code that works in local testing
  against the real Telegram API can still be wrong for Botxona if it ignores this variable.
- **Writing to a path relative to the working directory** (`open("data.json", "w")`,
  `fs.writeFileSync("state.json", ...)`) instead of joining it under `DATA_DIR`. This works fine
  locally and fails only in production, which makes it an easy miss — always join through
  `DATA_DIR` even in code you're only "testing locally" for now.
- **Leaving a real token in `.env.example`.** That file ships in the deployable archive/repo; it
  must only ever contain empty values or obviously-fake placeholders plus a comment.
- **Adding a system dependency the slim Docker base image doesn't have** to make a native library
  build (e.g. for image processing, PDF generation). Botxona's Dockerfile is auto-generated from
  `requirements.txt`/`package.json` alone and is not user-editable — prefer a pure-Python/pure-JS
  package, or a Python wheel that ships prebuilt binaries, over anything needing `apt-get install`.
- **Trying to "fix" scale-to-zero** with an external cron pinging the bot to keep it awake. This
  doesn't do anything useful (Botxona wakes a sleeping bot on the next real Telegram update in a
  few seconds anyway) and just adds unnecessary traffic; if the bot truly needs to run continuously
  (scheduled digests, background jobs), use `always_on: true` instead.

## Quick before/after examples

**Token (Python), wrong → right:**
```python
# wrong
bot = Bot(token="8123456789:AAH...")
# right
bot = Bot(token=os.environ["BOT_TOKEN"])
```

**API base (Node grammY), wrong → right:**
```js
// wrong — always talks straight to Telegram, ignores the platform proxy
const bot = new Bot(process.env.BOT_TOKEN);
// right
const bot = new Bot(process.env.BOT_TOKEN, {
  client: { apiRoot: process.env.TELEGRAM_API_URL || "https://api.telegram.org" },
});
```

**Storage (Python), wrong → right:**
```python
# wrong — lost on every redeploy, breaks entirely once the rootfs is read-only
conn = sqlite3.connect("bot.db")
# right
DATA_DIR = os.getenv("DATA_DIR", "./data")
os.makedirs(DATA_DIR, exist_ok=True)
conn = sqlite3.connect(os.path.join(DATA_DIR, "bot.db"))
```

**Updates (Python aiogram), wrong → right:**
```python
# wrong — sets a webhook; Botxona no-ops this silently, bot never receives updates
await bot.set_webhook(url="https://my-domain.example/webhook")
# right — long polling, works everywhere Botxona runs this bot
await dp.start_polling(bot)
```

## Background context (why these rules exist)

Botxona runs thousands of small Telegram bots on a handful of cheap VPS nodes by putting idle
bots to sleep and routing every bot's Telegram traffic through a per-node proxy that guarantees
ordered, at-least-once update delivery regardless of whether the platform itself is using webhooks,
polling, or synthetic test traffic upstream. Each bot's container is a locked-down sandbox
(read-only root filesystem, dropped Linux capabilities, per-tariff RAM/CPU caps, no network access
beyond the proxy) so that one bot's bug or compromise can't affect any other bot or the host.
`DATA_DIR` is the one place carved out of that sandbox specifically so bots can keep real state —
and it's the one directory that's backed up daily and restorable from the dashboard. None of this
is specific to this repository; it's the same contract for every bot Botxona hosts.
