Skip to content

Repository files navigation

discordctl

Control a Discord server from the command line — full, bot-driven server management with dry-run-by-default safety.

License Stars Last Commit Issues

Python discord.py uv Docker

Setup · Live smoke test · Report Bug · Request Feature


What is discordctl?

discordctl turns a Discord bot into a scriptable command-line control plane for your server. A persistent discord.py daemon holds the gateway connection and exposes a localhost-only HTTP command bus; the dctl CLI drives that bus over the shell — so you (or an automation, or an AI coding agent) can ban and edit members, manage roles, channels, categories, permissions, messages, threads, emojis, invites, and webhooks. 179 operations in all, covering the whole management surface.

It was built to let an AI agent administer a server safely, so safety is the headline: every mutating operation is dry-run by default and requires an explicit --confirm, writes are bounded by a guild allowlist and a global kill switch, and every call is appended to an audit log.


Features

Feature Description
179 operations Guild, channel, category, role, member, message, permissions, thread, emoji, invite, and webhook management — driven as dctl op <name>.
Dry-run by default Every mutation previews what it would do and changes nothing until you pass --confirm. The client cannot bypass this.
Guarded & auditable Guild allowlist, WRITE_ENABLED kill switch, owner-ban refusal, --yes-really for destructive ops, and an append-only JSONL audit log of every call.
Declarative state guild.snapshot → guild.diff → guild.apply: capture the server to JSON, edit it, apply the difference.
Localhost-only bus The control bus binds 127.0.0.1, trusts only loopback peers, and authenticates every request with a constant-time bearer check.

Quick Start

1. Clone & install

git clone https://github.com/zachlagden/discordctl.git
cd discordctl
uv sync

2. Configure

cp .env.example .env
# Set DISCORD_TOKEN, BUS_TOKEN, ALLOWED_GUILD_IDS, DEFAULT_GUILD_ID

Generate the bot invite URL and authorize it into your server (see docs/SETUP.md for the full walkthrough, including the required privileged intents):

uv run python scripts/invite_url.py <CLIENT_ID>

3. Run & drive it

uv run python -m discordctl                               # or: docker compose up -d

uv run dctl health
uv run dctl ops
uv run dctl op guild.info
uv run dctl op role.create --arg name=example             # dry-run (preview only)
uv run dctl op role.create --arg name=example --confirm   # executes

How it works

you / a script / an agent
        │  shell
        ▼
      dctl  ──HTTP POST /v1/op (bearer, 127.0.0.1)──►  control bus (aiohttp)
        ▲                                                   │
        │  JSON                                             ▼
        └──────────────────────────────────────  op registry → handler → discord.py
                                                            │
                                                            ▼
                                                       Discord API

The daemon stays connected to the gateway (warm cache, bot shows online). dctl is stateless and talks to the bus over loopback. For mutating ops the server decides the effective dry-run state from confirm + WRITE_ENABLED — a crafted request body can't force a live write.


Tech Stack

Layer Technology
Gateway daemon Python 3.11+, discord.py 2.x
Control bus aiohttp (localhost), pydantic v2
CLI dctl (stdlib argparse + urllib)
Tests / quality pytest, pytest-asyncio, ruff
Deploy / observability Docker, Docker Compose, Sentry

Project Structure

src/discordctl/
├── __main__.py        # python -m discordctl -> runs the daemon
├── config.py          # env-driven configuration
├── schemas.py         # OpRequest / OpResponse
├── daemon/            # discord.py client + aiohttp control bus
├── ops/
│   ├── registry.py    # @op decorator, BusContext, dry-run plan()
│   ├── lookup.py      # entity resolution + guild allowlist
│   ├── serialize.py   # discord objects -> JSON
│   ├── audit.py       # append-only audit writer
│   └── handlers/      # one module per domain (179 ops)
└── cli/dctl.py        # the CLI
docs/                  # SETUP.md, SMOKE.md, design spec & plan

Configuration

Loaded from .env (gitignored — never commit real values).

Variable Purpose
DISCORD_TOKEN Bot token (all privileged intents required)
BUS_TOKEN 256-bit hex bearer token for the control bus
BUS_HOST / BUS_PORT Bus bind address (keep loopback) / port
ALLOWED_GUILD_IDS Comma-separated guild allowlist (empty disables it)
DEFAULT_GUILD_ID Guild used when an op omits guild_id
WRITE_ENABLED Global kill switch; false forces every mutation to dry-run
AUDIT_PATH Path to the append-only audit log
SENTRY_DSN Optional Sentry DSN

Star History

Star History Chart

Support

If this project is useful to you, consider supporting development:

Sponsor on GitHub

License

This project is licensed under the MIT License — see the LICENCE file for details.


About

Control a Discord server from the command line — a localhost command bus and dctl CLI for full, dry-run-safe bot-driven server management.

Topics

Resources

Contributing

Stars

1 star

Watchers

0 watching

Forks

Releases

Packages

Used by

Contributors

Languages