{
  "id": "discord-bot-python",
  "type": "loadout",
  "title": "Discord bot in Python (discord.py 2)",
  "summary": "discord.py 2 rules for agents: slash commands in cogs, range-limited options, 3-second responses, manual syncing, default intents and offline tests of the payload.",
  "description": "Agents writing Discord bots in Python reach for prefix commands and the message content intent, sync the command tree on every start until Discord rate-limits them, and test nothing because everything seems to need a live gateway. This loadout sets the discord.py 2 conventions: slash commands in cogs, options described and limited in the schema, deferral for slow work, ephemeral error replies, syncing by hand to a dev guild, and the token only from the environment. A skill adds commands with logic in plain functions and offline tests of the exact payload Discord will receive. The verifier runs those tests on discord.py 2.7.1 and loads the cog without logging in.\n",
  "highlights": [
    "Slash commands in cogs; no message content intent",
    "Options described and limited with app_commands.Range",
    "Offline tests of logic and the command payload, no gateway needed",
    "Sync by hand to a dev guild, never on every start",
    "Token from the environment; default intents only"
  ],
  "version": "1.0.0",
  "license": "MIT",
  "author": {
    "name": "Agent Loadout",
    "url": "https://loadout.jnw-it.com"
  },
  "credits": [
    {
      "title": "discord.py docs: interactions API reference",
      "url": "https://discordpy.readthedocs.io/en/stable/interactions/api.html"
    },
    {
      "title": "discord.py docs: extensions and cogs",
      "url": "https://discordpy.readthedocs.io/en/stable/ext/commands/cogs.html"
    },
    {
      "title": "Discord developer docs: receiving and responding to interactions",
      "url": "https://discord.com/developers/docs/interactions/receiving-and-responding"
    },
    {
      "title": "Discord developer docs: privileged gateway intents",
      "url": "https://discord.com/developers/docs/events/gateway#privileged-intents"
    }
  ],
  "categories": [
    "bots"
  ],
  "stacks": [
    "python",
    "discord",
    "uv",
    "pytest"
  ],
  "files": [
    {
      "path": "AGENTS.md",
      "role": "instructions",
      "description": "Rules for a discord.py 2 bot: layout, commands, interactions, syncing and secrets.",
      "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/discord-bot-python/1.0.0/AGENTS.md",
      "raw_latest_url": "https://loadout.jnw-it.com/raw/discord-bot-python/latest/AGENTS.md",
      "bytes": 2267,
      "lang": "markdown",
      "content": "# AGENTS.md: Discord bot (Python, discord.py 2)\n\n## Layout\n```\nbot.py            creates the Bot, loads cogs in setup_hook, reads the token from the environment\ncogs/<name>.py    one feature per cog: app commands plus an async setup(bot) function\ntests/            pytest for command logic and the command payloads, no network\n```\nRun with `uv run python bot.py`. Tests: `uv run pytest`. Lint: `uv run ruff check` and `uv run ruff format --check`.\n\n## Commands\n- Slash commands (`app_commands`), not prefix commands that need the privileged message content intent.\n- Every command and option has a description (`description=` and `@app_commands.describe`). Names are lowercase,\n  1-32 characters.\n- Constrain options in the schema: `app_commands.Range[int, 1, 20]`, `Range[str, 1, 200]`, `Choice` lists. Discord\n  enforces these before your code runs.\n- Keep logic in plain functions that take plain values, so tests do not need Discord objects. New commands: use the\n  `discord-py-command` skill.\n\n## Interactions\n- Respond within 3 seconds. For slow work call `await interaction.response.defer()` first, then\n  `await interaction.followup.send(...)`.\n- Private replies: `ephemeral=True`. Errors go to a command `.error` handler (or `tree.on_error`): log the exception,\n  reply with a short ephemeral message, never a traceback.\n- Moderation commands set `@app_commands.default_permissions(...)` and re-check permissions in code.\n- Cooldowns on costly commands: `@app_commands.checks.cooldown(rate, per, key=...)`.\n\n## Syncing\n- Do not call `tree.sync()` on every start; it is rate limited. Sync by hand, to a dev guild while developing\n  (instant), and globally only for a release.\n- After changing a command's name, options or descriptions, remind the user to sync.\n\n## Token and intents\n- `DISCORD_TOKEN` comes from the environment or a git-ignored `.env`. Never print, log or commit it. If it leaks, the\n  user must reset it in the Developer Portal.\n- `discord.Intents.default()` is enough for slash commands. `members`, `presences` and `message_content` are\n  privileged; ask before enabling them.\n\n## Dependencies\n- `uv add \"discord.py==2.7.1\"` and commit `uv.lock`. Ask before adding anything else. Never pass user input to a\n  shell, `eval` or a SQL string.\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/discord-bot-python/1.0.0/CLAUDE.md",
      "raw_latest_url": "https://loadout.jnw-it.com/raw/discord-bot-python/latest/CLAUDE.md",
      "bytes": 393,
      "lang": "markdown",
      "content": "# CLAUDE.md\n\n@AGENTS.md\n\n## Claude Code notes\n- Skills from this loadout live in `.claude/skills/`: `discord-py-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 \"Discord bot in Python (discord.py 2)\" v1.0.0 — https://loadout.jnw-it.com/loadouts/discord-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/discord-bot-python/1.0.0/GEMINI.md",
      "raw_latest_url": "https://loadout.jnw-it.com/raw/discord-bot-python/latest/GEMINI.md",
      "bytes": 394,
      "lang": "markdown",
      "content": "# GEMINI.md\n\n@./AGENTS.md\n\n## Gemini CLI notes\n- Skills from this loadout live in `.agents/skills/`: `discord-py-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 \"Discord bot in Python (discord.py 2)\" v1.0.0 — https://loadout.jnw-it.com/loadouts/discord-bot-python/ -->\n"
    },
    {
      "path": "skills/discord-py-command/SKILL.md",
      "role": "skill",
      "description": "Add a slash command as a tested cog.",
      "skill": "discord-py-command",
      "agent": null,
      "install_to": {
        "claude-code": ".claude/skills/discord-py-command/SKILL.md",
        "codex": ".agents/skills/discord-py-command/SKILL.md",
        "cursor": ".agents/skills/discord-py-command/SKILL.md",
        "gemini-cli": ".agents/skills/discord-py-command/SKILL.md",
        "antigravity-cli": ".agents/skills/discord-py-command/SKILL.md",
        "copilot": ".agents/skills/discord-py-command/SKILL.md"
      },
      "raw_url": "https://loadout.jnw-it.com/raw/discord-bot-python/1.0.0/skills/discord-py-command/SKILL.md",
      "raw_latest_url": "https://loadout.jnw-it.com/raw/discord-bot-python/latest/skills/discord-py-command/SKILL.md",
      "bytes": 1477,
      "lang": "markdown",
      "content": "---\nname: discord-py-command\ndescription: Add a slash command to a discord.py 2 bot as a cog, with described and range-limited options, a cooldown, an error handler and offline tests of the logic and the command payload.\n---\n\n# Add a discord.py slash command\n\nExamples: `references/dice.py` (cog), `references/bot.py` (entry point) and `references/test_dice.py` (tests).\n\n1. Put the logic in a plain function that takes plain values and returns plain values (`roll(count, sides, rng)`).\n   Inject randomness, clocks and HTTP clients so tests can control them.\n2. Create or extend a cog in `cogs/<feature>.py`:\n   - `@app_commands.command(name=..., description=...)`\n   - `@app_commands.describe(...)` for every option\n   - `app_commands.Range[...]` or `Choice` for limits\n   - a cooldown for anything costly, and `@app_commands.default_permissions` for moderation\n3. Respond within 3 seconds; defer first if the work may be slow.\n4. Add a `.error` handler: cooldowns get a friendly ephemeral message; anything else is logged and gets\n   \"Something went wrong.\"\n5. Export `async def setup(bot)` and add the module to the extensions list in `bot.py`.\n6. Tests, with no network: the logic function (including bad input), and the payload Discord will receive:\n   `bot.add_cog(...)`, then `c.to_dict(bot.tree)` for each command, checking names, descriptions and limits.\n7. Run `uv run pytest`, `uv run ruff check` and `uv run ruff format --check`. Remind the user to sync commands.\n"
    },
    {
      "path": "skills/discord-py-command/references/dice.py",
      "role": "reference",
      "description": "Cog with a validated, rate-limited command.",
      "skill": "discord-py-command",
      "agent": null,
      "install_to": {
        "claude-code": ".claude/skills/discord-py-command/references/dice.py",
        "codex": ".agents/skills/discord-py-command/references/dice.py",
        "cursor": ".agents/skills/discord-py-command/references/dice.py",
        "gemini-cli": ".agents/skills/discord-py-command/references/dice.py",
        "antigravity-cli": ".agents/skills/discord-py-command/references/dice.py",
        "copilot": ".agents/skills/discord-py-command/references/dice.py"
      },
      "raw_url": "https://loadout.jnw-it.com/raw/discord-bot-python/1.0.0/skills/discord-py-command/references/dice.py",
      "raw_latest_url": "https://loadout.jnw-it.com/raw/discord-bot-python/latest/skills/discord-py-command/references/dice.py",
      "bytes": 2299,
      "lang": "markdown",
      "content": "\"\"\"Dice cog: one slash command with validated options, a cooldown and logic that can be tested offline.\"\"\"\n\nimport logging\nimport random\n\nimport discord\nfrom discord import app_commands\nfrom discord.ext import commands\n\nlog = logging.getLogger(__name__)\n\nMAX_DICE = 20\n\n\ndef roll(count: int, sides: int, rng: random.Random) -> list[int]:\n    \"\"\"Pure logic, no Discord objects: easy to test.\"\"\"\n    if not 1 <= count <= MAX_DICE:\n        raise ValueError(f\"count must be 1-{MAX_DICE}\")\n    if not 2 <= sides <= 1000:\n        raise ValueError(\"sides must be 2-1000\")\n    return [rng.randint(1, sides) for _ in range(count)]\n\n\ndef format_roll(results: list[int], sides: int) -> str:\n    total = sum(results)\n    shown = \", \".join(map(str, results)) if len(results) <= 10 else f\"{len(results)} dice\"\n    return f\"Rolled {len(results)}d{sides}: {shown} (total {total})\"\n\n\nclass Dice(commands.Cog):\n    def __init__(self, bot: commands.Bot, rng: random.Random | None = None) -> None:\n        self.bot = bot\n        self.rng = rng or random.Random()\n\n    @app_commands.command(name=\"roll\", description=\"Roll dice, for example 3 six-sided dice\")\n    @app_commands.describe(count=\"How many dice (1-20)\", sides=\"Sides per die (2-1000)\")\n    @app_commands.checks.cooldown(1, 3.0, key=lambda i: i.user.id)\n    async def roll_command(\n        self,\n        interaction: discord.Interaction,\n        count: app_commands.Range[int, 1, MAX_DICE] = 1,\n        sides: app_commands.Range[int, 2, 1000] = 6,\n    ) -> None:\n        results = roll(count, sides, self.rng)\n        await interaction.response.send_message(format_roll(results, sides))\n\n    @roll_command.error\n    async def roll_error(self, interaction: discord.Interaction, error: app_commands.AppCommandError) -> None:\n        if isinstance(error, app_commands.CommandOnCooldown):\n            message = f\"Slow down: try again in {error.retry_after:.0f}s.\"\n        else:\n            log.exception(\"roll failed\", exc_info=error)\n            message = \"Something went wrong.\"\n        if interaction.response.is_done():\n            await interaction.followup.send(message, ephemeral=True)\n        else:\n            await interaction.response.send_message(message, ephemeral=True)\n\n\nasync def setup(bot: commands.Bot) -> None:\n    await bot.add_cog(Dice(bot))\n"
    },
    {
      "path": "skills/discord-py-command/references/bot.py",
      "role": "reference",
      "description": "Entry point with default intents and manual sync.",
      "skill": "discord-py-command",
      "agent": null,
      "install_to": {
        "claude-code": ".claude/skills/discord-py-command/references/bot.py",
        "codex": ".agents/skills/discord-py-command/references/bot.py",
        "cursor": ".agents/skills/discord-py-command/references/bot.py",
        "gemini-cli": ".agents/skills/discord-py-command/references/bot.py",
        "antigravity-cli": ".agents/skills/discord-py-command/references/bot.py",
        "copilot": ".agents/skills/discord-py-command/references/bot.py"
      },
      "raw_url": "https://loadout.jnw-it.com/raw/discord-bot-python/1.0.0/skills/discord-py-command/references/bot.py",
      "raw_latest_url": "https://loadout.jnw-it.com/raw/discord-bot-python/latest/skills/discord-py-command/references/bot.py",
      "bytes": 1390,
      "lang": "markdown",
      "content": "\"\"\"Entry point: minimal intents, cogs loaded in setup_hook, token from the environment, no sync on start.\"\"\"\n\nimport asyncio\nimport logging\nimport os\n\nimport discord\nfrom discord.ext import commands\n\nEXTENSIONS = [\"cogs.dice\"]\n\n\nclass Bot(commands.Bot):\n    def __init__(self) -> None:\n        # Slash commands need no privileged intents. Do not enable message_content or members unless required.\n        super().__init__(command_prefix=commands.when_mentioned, intents=discord.Intents.default())\n\n    async def setup_hook(self) -> None:\n        for ext in EXTENSIONS:\n            await self.load_extension(ext)\n        guild_id = os.environ.get(\"DISCORD_DEV_GUILD_ID\")\n        if os.environ.get(\"SYNC_COMMANDS\") == \"1\" and guild_id:\n            # Sync by hand, to one dev guild: instant, and avoids the global sync rate limit.\n            guild = discord.Object(id=int(guild_id))\n            self.tree.copy_global_to(guild=guild)\n            synced = await self.tree.sync(guild=guild)\n            logging.info(\"synced %d commands to guild %s\", len(synced), guild_id)\n\n\nasync def main() -> None:\n    token = os.environ.get(\"DISCORD_TOKEN\")\n    if not token:\n        raise SystemExit(\"DISCORD_TOKEN is not set (put it in .env, never in code)\")\n    discord.utils.setup_logging()\n    async with Bot() as bot:\n        await bot.start(token)\n\n\nif __name__ == \"__main__\":\n    asyncio.run(main())\n"
    },
    {
      "path": "skills/discord-py-command/references/test_dice.py",
      "role": "reference",
      "description": "Offline tests of logic and payload.",
      "skill": "discord-py-command",
      "agent": null,
      "install_to": {
        "claude-code": ".claude/skills/discord-py-command/references/test_dice.py",
        "codex": ".agents/skills/discord-py-command/references/test_dice.py",
        "cursor": ".agents/skills/discord-py-command/references/test_dice.py",
        "gemini-cli": ".agents/skills/discord-py-command/references/test_dice.py",
        "antigravity-cli": ".agents/skills/discord-py-command/references/test_dice.py",
        "copilot": ".agents/skills/discord-py-command/references/test_dice.py"
      },
      "raw_url": "https://loadout.jnw-it.com/raw/discord-bot-python/1.0.0/skills/discord-py-command/references/test_dice.py",
      "raw_latest_url": "https://loadout.jnw-it.com/raw/discord-bot-python/latest/skills/discord-py-command/references/test_dice.py",
      "bytes": 1402,
      "lang": "markdown",
      "content": "import asyncio\nimport random\n\nimport discord\nimport pytest\nfrom discord.ext import commands\n\nfrom cogs.dice import Dice, format_roll, roll\n\n\ndef test_roll_is_deterministic_with_a_seeded_rng():\n    assert roll(3, 6, random.Random(42)) == roll(3, 6, random.Random(42))\n    assert all(1 <= r <= 6 for r in roll(20, 6, random.Random(1)))\n\n\n@pytest.mark.parametrize((\"count\", \"sides\"), [(0, 6), (21, 6), (1, 1), (1, 1001)])\ndef test_roll_rejects_out_of_range(count, sides):\n    with pytest.raises(ValueError):\n        roll(count, sides, random.Random(0))\n\n\ndef test_format_roll_summarises_many_dice():\n    assert format_roll([1, 2, 3], 6) == \"Rolled 3d6: 1, 2, 3 (total 6)\"\n    assert format_roll([1] * 12, 4) == \"Rolled 12d4: 12 dice (total 12)\"\n\n\ndef test_command_payload_registers_offline():\n    async def build():\n        bot = commands.Bot(command_prefix=commands.when_mentioned, intents=discord.Intents.default())\n        await bot.add_cog(Dice(bot))\n        return [c.to_dict(bot.tree) for c in bot.tree.get_commands()]\n\n    (payload,) = asyncio.run(build())\n    assert payload[\"name\"] == \"roll\"\n    options = {o[\"name\"]: o for o in payload[\"options\"]}\n    assert options[\"count\"][\"description\"] == \"How many dice (1-20)\"\n    assert (options[\"count\"][\"min_value\"], options[\"count\"][\"max_value\"]) == (1, 20)\n    assert options[\"sides\"][\"max_value\"] == 1000\n    assert not options[\"count\"][\"required\"]\n"
    }
  ],
  "requires": {
    "tools": [
      "uv",
      "python3"
    ],
    "env": [
      "DISCORD_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": "uv downloads discord.py from PyPI. The bot connects to Discord's gateway with the token from the environment; the rules forbid logging or committing it."
  },
  "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/discord-bot-python/",
  "api_url": "https://loadout.jnw-it.com/api/loadouts/discord-bot-python.json",
  "verification": {
    "level": "verified-install",
    "status": "verified",
    "label": "Verified install",
    "verified": true,
    "checked_at": "2026-10-17T09:35:26Z",
    "commit": "021470d33f1f82ab91e12f0c64c43ad15bac6118",
    "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; 7 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 7 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: 6 files, 1 skill in .claude/skills/",
          "Codex CLI: 5 files, 1 skill in .agents/skills/",
          "Cursor: 5 files, 1 skill in .agents/skills/",
          "Gemini CLI: 6 files, 1 skill in .agents/skills/",
          "Antigravity CLI: 5 files, 1 skill in .agents/skills/",
          "GitHub Copilot: 5 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": "7 files, 9.4 KB total; largest skills/discord-py-command/references/dice.py 2.2 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 discord-py-command skill's cog and tests run on discord.py 2.7.1 with no network: seven tests pass (logic, bad input, and the slash command payload with descriptions and ranges), the bot loads the cog offline, and ruff is clean",
        "details": [
          "Applied 11 loadout files at every agent's paths",
          "✓ place the examples: bash -c mkdir -p cogs tests && touch cogs/__init__.py && R=.agents/skills/discord-py-command/references && cp $R/dice.py cogs/ && cp $R/bot.py . && cp $R/test_dice.py tests/ && echo placed",
          "✓ pytest (offline): uv run --no-project --python 3.12 --with discord.py==2.7.1 --with pytest==9.1.1 python -m pytest -q -p no:cacheprovider --rootdir . --import-mode importlib -o pythonpath=. tests",
          "✓ bot loads the cog without logging in: uv run --no-project --python 3.12 --with discord.py==2.7.1 python -c import asyncio, bot\nasync def main():\n    async with bot.Bot() as b:\n        await b.setup_hook()\n        print('commands:', [c.name for c in b.tree.get_commands()])\nasyncio.run(main())",
          "✓ no token without the environment: bash -c env -u DISCORD_TOKEN uv run --no-project --python 3.12 --with discord.py==2.7.1 python bot.py; echo \"exit=$?\"",
          "✓ ruff check: uvx ruff@0.17.1 check .",
          "✓ ruff format --check: uvx ruff@0.17.1 format --check .",
          "Covers: Cog with app_commands, describe, Range limits and a per-user cooldown",
          "Covers: Error handler: friendly cooldown message, logged exceptions, ephemeral replies",
          "Covers: Command payload checked offline with to_dict(tree)",
          "Covers: Default intents, token only from the environment, no sync on start"
        ]
      }
    ]
  },
  "install": {
    "claude-code": {
      "files": [
        {
          "path": "AGENTS.md",
          "target": "AGENTS.md",
          "raw_url": "https://loadout.jnw-it.com/raw/discord-bot-python/1.0.0/AGENTS.md",
          "role": "instructions"
        },
        {
          "path": "CLAUDE.md",
          "target": "CLAUDE.md",
          "raw_url": "https://loadout.jnw-it.com/raw/discord-bot-python/1.0.0/CLAUDE.md",
          "role": "adapter"
        },
        {
          "path": "skills/discord-py-command/SKILL.md",
          "target": ".claude/skills/discord-py-command/SKILL.md",
          "raw_url": "https://loadout.jnw-it.com/raw/discord-bot-python/1.0.0/skills/discord-py-command/SKILL.md",
          "role": "skill"
        },
        {
          "path": "skills/discord-py-command/references/dice.py",
          "target": ".claude/skills/discord-py-command/references/dice.py",
          "raw_url": "https://loadout.jnw-it.com/raw/discord-bot-python/1.0.0/skills/discord-py-command/references/dice.py",
          "role": "reference"
        },
        {
          "path": "skills/discord-py-command/references/bot.py",
          "target": ".claude/skills/discord-py-command/references/bot.py",
          "raw_url": "https://loadout.jnw-it.com/raw/discord-bot-python/1.0.0/skills/discord-py-command/references/bot.py",
          "role": "reference"
        },
        {
          "path": "skills/discord-py-command/references/test_dice.py",
          "target": ".claude/skills/discord-py-command/references/test_dice.py",
          "raw_url": "https://loadout.jnw-it.com/raw/discord-bot-python/1.0.0/skills/discord-py-command/references/test_dice.py",
          "role": "reference"
        }
      ],
      "manual_steps": [],
      "zip_url": "https://loadout.jnw-it.com/download/discord-bot-python/claude-code.zip"
    },
    "codex": {
      "files": [
        {
          "path": "AGENTS.md",
          "target": "AGENTS.md",
          "raw_url": "https://loadout.jnw-it.com/raw/discord-bot-python/1.0.0/AGENTS.md",
          "role": "instructions"
        },
        {
          "path": "skills/discord-py-command/SKILL.md",
          "target": ".agents/skills/discord-py-command/SKILL.md",
          "raw_url": "https://loadout.jnw-it.com/raw/discord-bot-python/1.0.0/skills/discord-py-command/SKILL.md",
          "role": "skill"
        },
        {
          "path": "skills/discord-py-command/references/dice.py",
          "target": ".agents/skills/discord-py-command/references/dice.py",
          "raw_url": "https://loadout.jnw-it.com/raw/discord-bot-python/1.0.0/skills/discord-py-command/references/dice.py",
          "role": "reference"
        },
        {
          "path": "skills/discord-py-command/references/bot.py",
          "target": ".agents/skills/discord-py-command/references/bot.py",
          "raw_url": "https://loadout.jnw-it.com/raw/discord-bot-python/1.0.0/skills/discord-py-command/references/bot.py",
          "role": "reference"
        },
        {
          "path": "skills/discord-py-command/references/test_dice.py",
          "target": ".agents/skills/discord-py-command/references/test_dice.py",
          "raw_url": "https://loadout.jnw-it.com/raw/discord-bot-python/1.0.0/skills/discord-py-command/references/test_dice.py",
          "role": "reference"
        }
      ],
      "manual_steps": [],
      "zip_url": "https://loadout.jnw-it.com/download/discord-bot-python/codex.zip"
    },
    "cursor": {
      "files": [
        {
          "path": "AGENTS.md",
          "target": "AGENTS.md",
          "raw_url": "https://loadout.jnw-it.com/raw/discord-bot-python/1.0.0/AGENTS.md",
          "role": "instructions"
        },
        {
          "path": "skills/discord-py-command/SKILL.md",
          "target": ".agents/skills/discord-py-command/SKILL.md",
          "raw_url": "https://loadout.jnw-it.com/raw/discord-bot-python/1.0.0/skills/discord-py-command/SKILL.md",
          "role": "skill"
        },
        {
          "path": "skills/discord-py-command/references/dice.py",
          "target": ".agents/skills/discord-py-command/references/dice.py",
          "raw_url": "https://loadout.jnw-it.com/raw/discord-bot-python/1.0.0/skills/discord-py-command/references/dice.py",
          "role": "reference"
        },
        {
          "path": "skills/discord-py-command/references/bot.py",
          "target": ".agents/skills/discord-py-command/references/bot.py",
          "raw_url": "https://loadout.jnw-it.com/raw/discord-bot-python/1.0.0/skills/discord-py-command/references/bot.py",
          "role": "reference"
        },
        {
          "path": "skills/discord-py-command/references/test_dice.py",
          "target": ".agents/skills/discord-py-command/references/test_dice.py",
          "raw_url": "https://loadout.jnw-it.com/raw/discord-bot-python/1.0.0/skills/discord-py-command/references/test_dice.py",
          "role": "reference"
        }
      ],
      "manual_steps": [],
      "zip_url": "https://loadout.jnw-it.com/download/discord-bot-python/cursor.zip"
    },
    "gemini-cli": {
      "files": [
        {
          "path": "AGENTS.md",
          "target": "AGENTS.md",
          "raw_url": "https://loadout.jnw-it.com/raw/discord-bot-python/1.0.0/AGENTS.md",
          "role": "instructions"
        },
        {
          "path": "GEMINI.md",
          "target": "GEMINI.md",
          "raw_url": "https://loadout.jnw-it.com/raw/discord-bot-python/1.0.0/GEMINI.md",
          "role": "adapter"
        },
        {
          "path": "skills/discord-py-command/SKILL.md",
          "target": ".agents/skills/discord-py-command/SKILL.md",
          "raw_url": "https://loadout.jnw-it.com/raw/discord-bot-python/1.0.0/skills/discord-py-command/SKILL.md",
          "role": "skill"
        },
        {
          "path": "skills/discord-py-command/references/dice.py",
          "target": ".agents/skills/discord-py-command/references/dice.py",
          "raw_url": "https://loadout.jnw-it.com/raw/discord-bot-python/1.0.0/skills/discord-py-command/references/dice.py",
          "role": "reference"
        },
        {
          "path": "skills/discord-py-command/references/bot.py",
          "target": ".agents/skills/discord-py-command/references/bot.py",
          "raw_url": "https://loadout.jnw-it.com/raw/discord-bot-python/1.0.0/skills/discord-py-command/references/bot.py",
          "role": "reference"
        },
        {
          "path": "skills/discord-py-command/references/test_dice.py",
          "target": ".agents/skills/discord-py-command/references/test_dice.py",
          "raw_url": "https://loadout.jnw-it.com/raw/discord-bot-python/1.0.0/skills/discord-py-command/references/test_dice.py",
          "role": "reference"
        }
      ],
      "manual_steps": [],
      "zip_url": "https://loadout.jnw-it.com/download/discord-bot-python/gemini-cli.zip"
    },
    "antigravity-cli": {
      "files": [
        {
          "path": "AGENTS.md",
          "target": "AGENTS.md",
          "raw_url": "https://loadout.jnw-it.com/raw/discord-bot-python/1.0.0/AGENTS.md",
          "role": "instructions"
        },
        {
          "path": "skills/discord-py-command/SKILL.md",
          "target": ".agents/skills/discord-py-command/SKILL.md",
          "raw_url": "https://loadout.jnw-it.com/raw/discord-bot-python/1.0.0/skills/discord-py-command/SKILL.md",
          "role": "skill"
        },
        {
          "path": "skills/discord-py-command/references/dice.py",
          "target": ".agents/skills/discord-py-command/references/dice.py",
          "raw_url": "https://loadout.jnw-it.com/raw/discord-bot-python/1.0.0/skills/discord-py-command/references/dice.py",
          "role": "reference"
        },
        {
          "path": "skills/discord-py-command/references/bot.py",
          "target": ".agents/skills/discord-py-command/references/bot.py",
          "raw_url": "https://loadout.jnw-it.com/raw/discord-bot-python/1.0.0/skills/discord-py-command/references/bot.py",
          "role": "reference"
        },
        {
          "path": "skills/discord-py-command/references/test_dice.py",
          "target": ".agents/skills/discord-py-command/references/test_dice.py",
          "raw_url": "https://loadout.jnw-it.com/raw/discord-bot-python/1.0.0/skills/discord-py-command/references/test_dice.py",
          "role": "reference"
        }
      ],
      "manual_steps": [],
      "zip_url": "https://loadout.jnw-it.com/download/discord-bot-python/antigravity-cli.zip"
    },
    "copilot": {
      "files": [
        {
          "path": "AGENTS.md",
          "target": "AGENTS.md",
          "raw_url": "https://loadout.jnw-it.com/raw/discord-bot-python/1.0.0/AGENTS.md",
          "role": "instructions"
        },
        {
          "path": "skills/discord-py-command/SKILL.md",
          "target": ".agents/skills/discord-py-command/SKILL.md",
          "raw_url": "https://loadout.jnw-it.com/raw/discord-bot-python/1.0.0/skills/discord-py-command/SKILL.md",
          "role": "skill"
        },
        {
          "path": "skills/discord-py-command/references/dice.py",
          "target": ".agents/skills/discord-py-command/references/dice.py",
          "raw_url": "https://loadout.jnw-it.com/raw/discord-bot-python/1.0.0/skills/discord-py-command/references/dice.py",
          "role": "reference"
        },
        {
          "path": "skills/discord-py-command/references/bot.py",
          "target": ".agents/skills/discord-py-command/references/bot.py",
          "raw_url": "https://loadout.jnw-it.com/raw/discord-bot-python/1.0.0/skills/discord-py-command/references/bot.py",
          "role": "reference"
        },
        {
          "path": "skills/discord-py-command/references/test_dice.py",
          "target": ".agents/skills/discord-py-command/references/test_dice.py",
          "raw_url": "https://loadout.jnw-it.com/raw/discord-bot-python/1.0.0/skills/discord-py-command/references/test_dice.py",
          "role": "reference"
        }
      ],
      "manual_steps": [],
      "zip_url": "https://loadout.jnw-it.com/download/discord-bot-python/copilot.zip"
    }
  },
  "source_zip_url": "https://loadout.jnw-it.com/download/discord-bot-python/source.zip"
}