{
  "id": "telegram-bot-python",
  "type": "loadout",
  "title": "Telegram bot with python-telegram-bot",
  "summary": "Rules and a command skill for Telegram bots on python-telegram-bot v22: token from the environment, async-safe handlers, testable logic, escaped output, webhook secrets.",
  "description": "Telegram bots are a favourite first project for agents, and the same issues come up: a token pasted into code or a test, blocking requests calls inside async handlers, raw user text in HTML replies, no error handler, a bot that answers every message in a group. This loadout uses python-telegram-bot v22's documented patterns: an Application with registered handlers and an error handler, logic in plain functions that test without Telegram, escaped output, admin allow-lists, and run_webhook with a secret token in production.\n",
  "highlights": [
    "Token only from TELEGRAM_BOT_TOKEN; separate test bot for development",
    "No blocking calls in async handlers",
    "Logic in plain functions with unit tests; no network in tests",
    "Escaped user text in HTML replies; quiet in group chats",
    "Webhooks with secret_token in production"
  ],
  "version": "1.0.0",
  "license": "MIT",
  "author": {
    "name": "Agent Loadout",
    "url": "https://loadout.jnw-it.com"
  },
  "credits": [
    {
      "title": "python-telegram-bot documentation",
      "url": "https://docs.python-telegram-bot.org/en/stable/"
    },
    {
      "title": "python-telegram-bot wiki: webhooks",
      "url": "https://github.com/python-telegram-bot/python-telegram-bot/wiki/Webhooks"
    },
    {
      "title": "Telegram Bot API",
      "url": "https://core.telegram.org/bots/api"
    }
  ],
  "categories": [
    "bots"
  ],
  "stacks": [
    "python",
    "telegram",
    "uv",
    "pytest"
  ],
  "files": [
    {
      "path": "AGENTS.md",
      "role": "instructions",
      "description": "python-telegram-bot rules for secrets, structure and behaviour.",
      "skill": null,
      "agent": null,
      "install_to": {
        "claude-code": "AGENTS.md",
        "codex": "AGENTS.md",
        "cursor": "AGENTS.md",
        "gemini-cli": "AGENTS.md",
        "antigravity-cli": "AGENTS.md",
        "copilot": "AGENTS.md"
      },
      "raw_url": "https://loadout.jnw-it.com/raw/telegram-bot-python/1.0.0/AGENTS.md",
      "raw_latest_url": "https://loadout.jnw-it.com/raw/telegram-bot-python/latest/AGENTS.md",
      "bytes": 2065,
      "lang": "markdown",
      "content": "# AGENTS.md: Telegram bot (python-telegram-bot)\n\nTelegram bot in Python with python-telegram-bot v22 (asyncio), managed with uv.\n\n## Commands\n- Run locally (long polling): `uv run python -m bot`. Tests: `uv run pytest -q`. Lint: `uv run ruff check .`.\n- Done means: tests and ruff pass, and you tried the command in a private chat with a test bot.\n\n## Secrets\n- The bot token comes from the `TELEGRAM_BOT_TOKEN` environment variable (local: `.env`, gitignored). Never hard-code,\n  log, print or commit it, and never paste it into an issue or a test.\n- If a token may have leaked, tell the human to revoke it with @BotFather straight away.\n- Use a separate test bot for development, never the production token.\n\n## Structure\n- `bot/__main__.py` builds the `Application` and registers handlers. One module per feature in `bot/handlers/`.\n- Business logic in plain functions that take values and return values, so it can be tested without Telegram.\n- Handlers are `async def name(update: Update, context: ContextTypes.DEFAULT_TYPE) -> None`. Never block the event loop:\n  no `time.sleep`, no `requests`; use `httpx.AsyncClient` or run blocking work with `asyncio.to_thread`.\n\n## Behaviour\n- Register an error handler with `application.add_error_handler(...)`; log the exception, reply with a short apology.\n- Treat every message as untrusted input: validate arguments, cap lengths, never `eval` or build shell commands from it.\n- Group chats: respond only to commands or mentions; check `update.effective_chat.type` before doing anything noisy.\n- Admin-only commands check the user id against an allow-list from the environment.\n- Respect Telegram's rate limits: no loops that send to many chats without delays; use the job queue for broadcasts.\n- Formatting: `ParseMode.HTML` with `telegram.helpers.escape` for user text, or plain text. Never put raw user input\n  into Markdown or HTML.\n\n## Production\n- Webhooks in production: `run_webhook(..., secret_token=...)` with a secret from the environment, behind HTTPS.\n- Use the `add-bot-command` skill for new commands.\n"
    },
    {
      "path": "CLAUDE.md",
      "role": "adapter",
      "description": "Imports AGENTS.md for Claude Code.",
      "skill": null,
      "agent": "claude-code",
      "install_to": {
        "claude-code": "CLAUDE.md",
        "codex": null,
        "cursor": null,
        "gemini-cli": null,
        "antigravity-cli": null,
        "copilot": null
      },
      "raw_url": "https://loadout.jnw-it.com/raw/telegram-bot-python/1.0.0/CLAUDE.md",
      "raw_latest_url": "https://loadout.jnw-it.com/raw/telegram-bot-python/latest/CLAUDE.md",
      "bytes": 392,
      "lang": "markdown",
      "content": "# CLAUDE.md\n\n@AGENTS.md\n\n## Claude Code notes\n- Skills from this loadout live in `.claude/skills/`: `add-bot-command`. Use them when their description matches the task.\n- Follow AGENTS.md above; where this file and AGENTS.md disagree, AGENTS.md wins.\n\n<!-- From the Agent Loadout \"Telegram bot with python-telegram-bot\" v1.0.0 — https://loadout.jnw-it.com/loadouts/telegram-bot-python/ -->\n"
    },
    {
      "path": "GEMINI.md",
      "role": "adapter",
      "description": "Imports AGENTS.md for Gemini CLI.",
      "skill": null,
      "agent": "gemini-cli",
      "install_to": {
        "claude-code": null,
        "codex": null,
        "cursor": null,
        "gemini-cli": "GEMINI.md",
        "antigravity-cli": null,
        "copilot": null
      },
      "raw_url": "https://loadout.jnw-it.com/raw/telegram-bot-python/1.0.0/GEMINI.md",
      "raw_latest_url": "https://loadout.jnw-it.com/raw/telegram-bot-python/latest/GEMINI.md",
      "bytes": 393,
      "lang": "markdown",
      "content": "# GEMINI.md\n\n@./AGENTS.md\n\n## Gemini CLI notes\n- Skills from this loadout live in `.agents/skills/`: `add-bot-command`. Use them when their description matches the task.\n- Follow AGENTS.md above; where this file and AGENTS.md disagree, AGENTS.md wins.\n\n<!-- From the Agent Loadout \"Telegram bot with python-telegram-bot\" v1.0.0 — https://loadout.jnw-it.com/loadouts/telegram-bot-python/ -->\n"
    },
    {
      "path": "skills/add-bot-command/SKILL.md",
      "role": "skill",
      "description": "New command with testable logic and checks.",
      "skill": "add-bot-command",
      "agent": null,
      "install_to": {
        "claude-code": ".claude/skills/add-bot-command/SKILL.md",
        "codex": ".agents/skills/add-bot-command/SKILL.md",
        "cursor": ".agents/skills/add-bot-command/SKILL.md",
        "gemini-cli": ".agents/skills/add-bot-command/SKILL.md",
        "antigravity-cli": ".agents/skills/add-bot-command/SKILL.md",
        "copilot": ".agents/skills/add-bot-command/SKILL.md"
      },
      "raw_url": "https://loadout.jnw-it.com/raw/telegram-bot-python/1.0.0/skills/add-bot-command/SKILL.md",
      "raw_latest_url": "https://loadout.jnw-it.com/raw/telegram-bot-python/latest/skills/add-bot-command/SKILL.md",
      "bytes": 1095,
      "lang": "markdown",
      "content": "---\nname: add-bot-command\ndescription: Add a python-telegram-bot command with logic in a testable plain function, validated arguments, escaped output and a unit test. Use when adding or changing a bot command.\n---\n\n# Add a bot command\n\n1. Write the logic as a plain function first (no Telegram types), like `parse_split` in `references/split.py`.\n   It validates input and raises `ValueError` with a message the user can read.\n2. Write the handler: read `context.args`, call the function, reply with the result. Catch `ValueError` and reply with\n   its message plus usage. Escape any user text with `telegram.helpers.escape` if you reply in HTML.\n3. Register it in `bot/__main__.py`: `application.add_handler(CommandHandler(\"split\", split_command))`.\n4. Add the command to the BotFather command list text in the README, so the human can update it.\n5. Tests like `references/test_split.py`: the plain function (valid, invalid, edge cases) and that the application\n   registers the handler. No network, no token.\n6. Run `uv run pytest -q` and `uv run ruff check .`, then try it with the test bot.\n"
    },
    {
      "path": "skills/add-bot-command/references/split.py",
      "role": "reference",
      "description": "Example /split command: plain function plus handler.",
      "skill": "add-bot-command",
      "agent": null,
      "install_to": {
        "claude-code": ".claude/skills/add-bot-command/references/split.py",
        "codex": ".agents/skills/add-bot-command/references/split.py",
        "cursor": ".agents/skills/add-bot-command/references/split.py",
        "gemini-cli": ".agents/skills/add-bot-command/references/split.py",
        "antigravity-cli": ".agents/skills/add-bot-command/references/split.py",
        "copilot": ".agents/skills/add-bot-command/references/split.py"
      },
      "raw_url": "https://loadout.jnw-it.com/raw/telegram-bot-python/1.0.0/skills/add-bot-command/references/split.py",
      "raw_latest_url": "https://loadout.jnw-it.com/raw/telegram-bot-python/latest/skills/add-bot-command/references/split.py",
      "bytes": 1384,
      "lang": "markdown",
      "content": "\"\"\"Example command: /split <amount> <people> splits a bill. Logic is a plain function; the handler is a thin wrapper.\"\"\"\n\nfrom decimal import ROUND_HALF_UP, Decimal, InvalidOperation\n\nfrom telegram import Update\nfrom telegram.ext import Application, CommandHandler, ContextTypes\n\nUSAGE = \"Usage: /split <amount> <people>, for example /split 42.50 3\"\n\n\ndef parse_split(args: list[str]) -> str:\n    if len(args) != 2:\n        raise ValueError(USAGE)\n    try:\n        amount = Decimal(args[0].replace(\"£\", \"\"))\n        people = int(args[1])\n    except (InvalidOperation, ValueError):\n        raise ValueError(USAGE) from None\n    if amount <= 0 or amount > Decimal(\"100000\"):\n        raise ValueError(\"Amount must be between 0 and 100,000.\")\n    if not 1 <= people <= 100:\n        raise ValueError(\"People must be between 1 and 100.\")\n    share = (amount / people).quantize(Decimal(\"0.01\"), rounding=ROUND_HALF_UP)\n    return f\"£{amount:.2f} split {people} ways is £{share} each.\"\n\n\nasync def split_command(update: Update, context: ContextTypes.DEFAULT_TYPE) -> None:\n    if update.message is None:\n        return\n    try:\n        text = parse_split(context.args or [])\n    except ValueError as err:\n        text = str(err)\n    await update.message.reply_text(text)\n\n\ndef register(application: Application) -> None:\n    application.add_handler(CommandHandler(\"split\", split_command))\n"
    },
    {
      "path": "skills/add-bot-command/references/test_split.py",
      "role": "reference",
      "description": "Unit tests and a no-network registration test.",
      "skill": "add-bot-command",
      "agent": null,
      "install_to": {
        "claude-code": ".claude/skills/add-bot-command/references/test_split.py",
        "codex": ".agents/skills/add-bot-command/references/test_split.py",
        "cursor": ".agents/skills/add-bot-command/references/test_split.py",
        "gemini-cli": ".agents/skills/add-bot-command/references/test_split.py",
        "antigravity-cli": ".agents/skills/add-bot-command/references/test_split.py",
        "copilot": ".agents/skills/add-bot-command/references/test_split.py"
      },
      "raw_url": "https://loadout.jnw-it.com/raw/telegram-bot-python/1.0.0/skills/add-bot-command/references/test_split.py",
      "raw_latest_url": "https://loadout.jnw-it.com/raw/telegram-bot-python/latest/skills/add-bot-command/references/test_split.py",
      "bytes": 1140,
      "lang": "markdown",
      "content": "import pytest\nfrom telegram.ext import Application, CommandHandler\n\nfrom bot.handlers.split import USAGE, parse_split, register\n\n\ndef test_even_split():\n    assert parse_split([\"42.50\", \"3\"]) == \"£42.50 split 3 ways is £14.17 each.\"\n\n\ndef test_pound_sign_allowed():\n    assert parse_split([\"£10\", \"4\"]) == \"£10.00 split 4 ways is £2.50 each.\"\n\n\n@pytest.mark.parametrize(\"args\", [[], [\"10\"], [\"ten\", \"2\"], [\"10\", \"two\"], [\"10\", \"2\", \"3\"]])\ndef test_bad_arguments_show_usage(args):\n    with pytest.raises(ValueError, match=\"Usage\"):\n        parse_split(args)\n\n\n@pytest.mark.parametrize(\"args, message\", [([\"0\", \"2\"], \"Amount\"), ([\"10\", \"0\"], \"People\"), ([\"10\", \"1000\"], \"People\")])\ndef test_out_of_range(args, message):\n    with pytest.raises(ValueError, match=message):\n        parse_split(args)\n\n\ndef test_usage_mentions_command():\n    assert \"/split\" in USAGE\n\n\ndef test_handler_is_registered_without_network():\n    app = Application.builder().token(\"123456:TEST-TOKEN-NOT-REAL\").build()\n    register(app)\n    commands = [c for h in app.handlers[0] if isinstance(h, CommandHandler) for c in h.commands]\n    assert \"split\" in commands\n"
    }
  ],
  "requires": {
    "tools": [
      "uv"
    ],
    "env": [
      "TELEGRAM_BOT_TOKEN"
    ],
    "mcp": []
  },
  "tested": {
    "status": "untested",
    "last_run": null,
    "fixture": null,
    "per_agent": {
      "claude-code": "untested",
      "codex": "untested",
      "cursor": "untested",
      "gemini-cli": "untested",
      "antigravity-cli": "untested",
      "copilot": "untested"
    }
  },
  "security": {
    "scanned": true,
    "scanner": "intake-precheck/1",
    "verdict": "clean",
    "network": true,
    "scripts": [],
    "notes": "The bot connects to the Telegram Bot API with your token when run. Tests make no network calls and use an obviously fake token. uv downloads packages from PyPI."
  },
  "featured": false,
  "deprecated": false,
  "last_checked": "2026-10-17",
  "published": "2026-10-17",
  "updated": "2026-10-17",
  "compatibility": {
    "claude-code": "native",
    "codex": "native",
    "cursor": "native",
    "gemini-cli": "native",
    "antigravity-cli": "native",
    "copilot": "native"
  },
  "counts": {
    "rules": 1,
    "skills": 1,
    "mcp": 0
  },
  "url": "https://loadout.jnw-it.com/loadouts/telegram-bot-python/",
  "api_url": "https://loadout.jnw-it.com/api/loadouts/telegram-bot-python.json",
  "verification": {
    "level": "verified-install",
    "status": "verified",
    "label": "Verified install",
    "verified": true,
    "checked_at": "2026-10-17T08:14:40Z",
    "commit": "c77eea0ac5a582348e3cda4c6db3ff5403facd65",
    "content_current": true,
    "agent_tests": "not-run",
    "counts": {
      "pass": 9,
      "fail": 0,
      "skip": 0,
      "not_applicable": 1
    },
    "meaning": "Level 1: automated checks on the files (parse, install paths, MCP config, safety scan, references, size, licence, starter build). No AI agent was run.",
    "checks": [
      {
        "id": "listing",
        "name": "Listing",
        "status": "pass",
        "summary": "Schema valid; 6 files declared, none missing or extra",
        "details": [
          "tested.status: untested (agent tests are separate from Level 1)"
        ]
      },
      {
        "id": "parse",
        "name": "Files parse",
        "status": "pass",
        "summary": "All 6 files parse; front matter valid",
        "details": []
      },
      {
        "id": "adapters",
        "name": "Adapters",
        "status": "pass",
        "summary": "CLAUDE.md and GEMINI.md import AGENTS.md and are up to date",
        "details": [
          "CLAUDE.md: matches the generator",
          "GEMINI.md: matches the generator"
        ]
      },
      {
        "id": "install-paths",
        "name": "Install paths",
        "status": "pass",
        "summary": "Correct paths for all 6 agents; layouts re-discovered",
        "details": [
          "Claude Code: 5 files, 1 skill in .claude/skills/",
          "Codex CLI: 4 files, 1 skill in .agents/skills/",
          "Cursor: 4 files, 1 skill in .agents/skills/",
          "Gemini CLI: 5 files, 1 skill in .agents/skills/",
          "Antigravity CLI: 4 files, 1 skill in .agents/skills/",
          "GitHub Copilot: 4 files, 1 skill in .agents/skills/"
        ]
      },
      {
        "id": "mcp",
        "name": "MCP config",
        "status": "n/a",
        "summary": "No MCP servers in this loadout",
        "details": []
      },
      {
        "id": "safety",
        "name": "Safety scan",
        "status": "pass",
        "summary": "Clean: no credentials, risky shell or hidden text",
        "details": [
          "Scanner intake-precheck/1: verdict clean; network no (listing says yes)"
        ]
      },
      {
        "id": "references",
        "name": "References",
        "status": "pass",
        "summary": "1 reference between files resolves for every agent",
        "details": []
      },
      {
        "id": "size",
        "name": "Size limits",
        "status": "pass",
        "summary": "6 files, 6.3 KB total; largest AGENTS.md 2.0 KB",
        "details": [
          "Limits: 30 files, 32 KB per file, 128 KB total, 500 lines per skill or rule"
        ]
      },
      {
        "id": "licence",
        "name": "Licence",
        "status": "pass",
        "summary": "MIT, by Agent Loadout; text in the repository LICENSE",
        "details": []
      },
      {
        "id": "starter-build",
        "name": "Starter build",
        "status": "pass",
        "summary": "The add-bot-command skill's example /split command and tests drop into a python-telegram-bot v22 app: logic tested as a plain function, handler registered without a token or network, ruff clean",
        "details": [
          "Applied 9 loadout files at every agent's paths",
          "✓ place the example command and tests: bash -c mkdir -p tests && cp .agents/skills/add-bot-command/references/split.py bot/handlers/split.py && cp .agents/skills/add-bot-command/references/test_split.py tests/test_split.py && echo placed",
          "✓ pytest: uv run --no-project --python 3.12 --with python-telegram-bot==22.8 --with pytest==9.1.1 python -m pytest -q -p no:cacheprovider tests",
          "✓ ruff check: uvx ruff@0.17.1 check bot tests",
          "✓ ruff format --check: uvx ruff@0.17.1 format --check bot tests",
          "✓ no token in code: bash -c ! grep -rnE '[0-9]{8,10}:[A-Za-z0-9_-]{35}' bot tests && echo clean",
          "Covers: Logic in a plain function with readable ValueError messages; handler is a thin async wrapper",
          "Covers: Token read from TELEGRAM_BOT_TOKEN; error handler registered",
          "Covers: Handler registration tested without network or a real token",
          "Covers: ruff with the ASYNC rules clean"
        ]
      }
    ]
  },
  "install": {
    "claude-code": {
      "files": [
        {
          "path": "AGENTS.md",
          "target": "AGENTS.md",
          "raw_url": "https://loadout.jnw-it.com/raw/telegram-bot-python/1.0.0/AGENTS.md",
          "role": "instructions"
        },
        {
          "path": "CLAUDE.md",
          "target": "CLAUDE.md",
          "raw_url": "https://loadout.jnw-it.com/raw/telegram-bot-python/1.0.0/CLAUDE.md",
          "role": "adapter"
        },
        {
          "path": "skills/add-bot-command/SKILL.md",
          "target": ".claude/skills/add-bot-command/SKILL.md",
          "raw_url": "https://loadout.jnw-it.com/raw/telegram-bot-python/1.0.0/skills/add-bot-command/SKILL.md",
          "role": "skill"
        },
        {
          "path": "skills/add-bot-command/references/split.py",
          "target": ".claude/skills/add-bot-command/references/split.py",
          "raw_url": "https://loadout.jnw-it.com/raw/telegram-bot-python/1.0.0/skills/add-bot-command/references/split.py",
          "role": "reference"
        },
        {
          "path": "skills/add-bot-command/references/test_split.py",
          "target": ".claude/skills/add-bot-command/references/test_split.py",
          "raw_url": "https://loadout.jnw-it.com/raw/telegram-bot-python/1.0.0/skills/add-bot-command/references/test_split.py",
          "role": "reference"
        }
      ],
      "manual_steps": [],
      "zip_url": "https://loadout.jnw-it.com/download/telegram-bot-python/claude-code.zip"
    },
    "codex": {
      "files": [
        {
          "path": "AGENTS.md",
          "target": "AGENTS.md",
          "raw_url": "https://loadout.jnw-it.com/raw/telegram-bot-python/1.0.0/AGENTS.md",
          "role": "instructions"
        },
        {
          "path": "skills/add-bot-command/SKILL.md",
          "target": ".agents/skills/add-bot-command/SKILL.md",
          "raw_url": "https://loadout.jnw-it.com/raw/telegram-bot-python/1.0.0/skills/add-bot-command/SKILL.md",
          "role": "skill"
        },
        {
          "path": "skills/add-bot-command/references/split.py",
          "target": ".agents/skills/add-bot-command/references/split.py",
          "raw_url": "https://loadout.jnw-it.com/raw/telegram-bot-python/1.0.0/skills/add-bot-command/references/split.py",
          "role": "reference"
        },
        {
          "path": "skills/add-bot-command/references/test_split.py",
          "target": ".agents/skills/add-bot-command/references/test_split.py",
          "raw_url": "https://loadout.jnw-it.com/raw/telegram-bot-python/1.0.0/skills/add-bot-command/references/test_split.py",
          "role": "reference"
        }
      ],
      "manual_steps": [],
      "zip_url": "https://loadout.jnw-it.com/download/telegram-bot-python/codex.zip"
    },
    "cursor": {
      "files": [
        {
          "path": "AGENTS.md",
          "target": "AGENTS.md",
          "raw_url": "https://loadout.jnw-it.com/raw/telegram-bot-python/1.0.0/AGENTS.md",
          "role": "instructions"
        },
        {
          "path": "skills/add-bot-command/SKILL.md",
          "target": ".agents/skills/add-bot-command/SKILL.md",
          "raw_url": "https://loadout.jnw-it.com/raw/telegram-bot-python/1.0.0/skills/add-bot-command/SKILL.md",
          "role": "skill"
        },
        {
          "path": "skills/add-bot-command/references/split.py",
          "target": ".agents/skills/add-bot-command/references/split.py",
          "raw_url": "https://loadout.jnw-it.com/raw/telegram-bot-python/1.0.0/skills/add-bot-command/references/split.py",
          "role": "reference"
        },
        {
          "path": "skills/add-bot-command/references/test_split.py",
          "target": ".agents/skills/add-bot-command/references/test_split.py",
          "raw_url": "https://loadout.jnw-it.com/raw/telegram-bot-python/1.0.0/skills/add-bot-command/references/test_split.py",
          "role": "reference"
        }
      ],
      "manual_steps": [],
      "zip_url": "https://loadout.jnw-it.com/download/telegram-bot-python/cursor.zip"
    },
    "gemini-cli": {
      "files": [
        {
          "path": "AGENTS.md",
          "target": "AGENTS.md",
          "raw_url": "https://loadout.jnw-it.com/raw/telegram-bot-python/1.0.0/AGENTS.md",
          "role": "instructions"
        },
        {
          "path": "GEMINI.md",
          "target": "GEMINI.md",
          "raw_url": "https://loadout.jnw-it.com/raw/telegram-bot-python/1.0.0/GEMINI.md",
          "role": "adapter"
        },
        {
          "path": "skills/add-bot-command/SKILL.md",
          "target": ".agents/skills/add-bot-command/SKILL.md",
          "raw_url": "https://loadout.jnw-it.com/raw/telegram-bot-python/1.0.0/skills/add-bot-command/SKILL.md",
          "role": "skill"
        },
        {
          "path": "skills/add-bot-command/references/split.py",
          "target": ".agents/skills/add-bot-command/references/split.py",
          "raw_url": "https://loadout.jnw-it.com/raw/telegram-bot-python/1.0.0/skills/add-bot-command/references/split.py",
          "role": "reference"
        },
        {
          "path": "skills/add-bot-command/references/test_split.py",
          "target": ".agents/skills/add-bot-command/references/test_split.py",
          "raw_url": "https://loadout.jnw-it.com/raw/telegram-bot-python/1.0.0/skills/add-bot-command/references/test_split.py",
          "role": "reference"
        }
      ],
      "manual_steps": [],
      "zip_url": "https://loadout.jnw-it.com/download/telegram-bot-python/gemini-cli.zip"
    },
    "antigravity-cli": {
      "files": [
        {
          "path": "AGENTS.md",
          "target": "AGENTS.md",
          "raw_url": "https://loadout.jnw-it.com/raw/telegram-bot-python/1.0.0/AGENTS.md",
          "role": "instructions"
        },
        {
          "path": "skills/add-bot-command/SKILL.md",
          "target": ".agents/skills/add-bot-command/SKILL.md",
          "raw_url": "https://loadout.jnw-it.com/raw/telegram-bot-python/1.0.0/skills/add-bot-command/SKILL.md",
          "role": "skill"
        },
        {
          "path": "skills/add-bot-command/references/split.py",
          "target": ".agents/skills/add-bot-command/references/split.py",
          "raw_url": "https://loadout.jnw-it.com/raw/telegram-bot-python/1.0.0/skills/add-bot-command/references/split.py",
          "role": "reference"
        },
        {
          "path": "skills/add-bot-command/references/test_split.py",
          "target": ".agents/skills/add-bot-command/references/test_split.py",
          "raw_url": "https://loadout.jnw-it.com/raw/telegram-bot-python/1.0.0/skills/add-bot-command/references/test_split.py",
          "role": "reference"
        }
      ],
      "manual_steps": [],
      "zip_url": "https://loadout.jnw-it.com/download/telegram-bot-python/antigravity-cli.zip"
    },
    "copilot": {
      "files": [
        {
          "path": "AGENTS.md",
          "target": "AGENTS.md",
          "raw_url": "https://loadout.jnw-it.com/raw/telegram-bot-python/1.0.0/AGENTS.md",
          "role": "instructions"
        },
        {
          "path": "skills/add-bot-command/SKILL.md",
          "target": ".agents/skills/add-bot-command/SKILL.md",
          "raw_url": "https://loadout.jnw-it.com/raw/telegram-bot-python/1.0.0/skills/add-bot-command/SKILL.md",
          "role": "skill"
        },
        {
          "path": "skills/add-bot-command/references/split.py",
          "target": ".agents/skills/add-bot-command/references/split.py",
          "raw_url": "https://loadout.jnw-it.com/raw/telegram-bot-python/1.0.0/skills/add-bot-command/references/split.py",
          "role": "reference"
        },
        {
          "path": "skills/add-bot-command/references/test_split.py",
          "target": ".agents/skills/add-bot-command/references/test_split.py",
          "raw_url": "https://loadout.jnw-it.com/raw/telegram-bot-python/1.0.0/skills/add-bot-command/references/test_split.py",
          "role": "reference"
        }
      ],
      "manual_steps": [],
      "zip_url": "https://loadout.jnw-it.com/download/telegram-bot-python/copilot.zip"
    }
  },
  "source_zip_url": "https://loadout.jnw-it.com/download/telegram-bot-python/source.zip"
}