Token Resolution
discli needs a Discord bot token to authenticate with the Discord API. The token is resolved through a three-level priority chain, giving you flexibility across different environments.
Resolution chain
flowchart TD
A[discli invoked] --> B{--token flag\nprovided?}
B -- Yes --> C[Use flag value]
B -- No --> D{DISCORD_BOT_TOKEN\nenv var set?}
D -- Yes --> E[Use env var value]
D -- No --> F{~/.discli/config.json\nhas token?}
F -- Yes --> G[Use config value]
F -- No --> H[Error: No token\nprovided]
style C fill:#059669,color:#fff
style E fill:#059669,color:#fff
style G fill:#059669,color:#fff
style H fill:#dc2626,color:#fffPriority order
| Priority | Source | Example |
|---|---|---|
| 1 (highest) | --token CLI flag | discli --token Bot_ABC123 message send ... |
| 2 | DISCORD_BOT_TOKEN environment variable | export DISCORD_BOT_TOKEN=Bot_ABC123 |
| 3 (lowest) | ~/.discli/config.json file | {"token": "Bot_ABC123"} |
The first source that provides a non-empty value wins. If none of the three sources has a token, discli exits with an error:
Error: No token provided. Use --token, set DISCORD_BOT_TOKEN, or run: discli config set token YOUR_TOKENHow it works
Token resolution happens in two stages within cli.py and client.py:
Stage 1 — CLI entry (cli.py): Click’s @click.option("--token", envvar="DISCORD_BOT_TOKEN") handles the first two levels. If --token is passed, Click uses that value. If not, Click checks the DISCORD_BOT_TOKEN environment variable. If neither is set, the token is None and discli falls back to loading from the config file.
# cli.py — main group@click.option("--token", envvar="DISCORD_BOT_TOKEN", default=None)def main(ctx, token, ...): if token is None: config = load_config() token = config.get("token") ctx.obj["token"] = tokenStage 2 — Client (client.py): Before running the Discord action, resolve_token() verifies that a token was found. If ctx.obj["token"] is still None, it raises an error.
def resolve_token(token: str | None, config: dict) -> str: if token: return token config_token = config.get("token") if config_token: return config_token raise click.ClickException("No token provided. ...")Config file format
The config file is stored at ~/.discli/config.json:
{ "token": "Bot_YOUR_TOKEN_HERE"}Manage it with the config command:
# Set your tokendiscli config set token YOUR_BOT_TOKEN
# View current configdiscli config showThe file is created automatically by discli config set. The ~/.discli/ directory is also created if it does not exist.
The config file stores the token in plain text. Ensure ~/.discli/config.json has appropriate file permissions. On shared systems, consider using the environment variable approach instead.
Why this order
The three-level priority is designed for different usage patterns:
Config file is the most convenient for day-to-day use. Set it once and forget:
discli config set token YOUR_BOT_TOKEN# Now every command works without extra flagsdiscli message send "#general" "Hello"The token persists across terminal sessions, reboots, and shell changes.
Environment variable is the standard for CI pipelines and Docker containers. It avoids writing secrets to disk:
# GitHub Actions exampleenv: DISCORD_BOT_TOKEN: ${{ secrets.DISCORD_BOT_TOKEN }}steps: - run: discli message send "#deploys" "Build ${{ github.sha }} deployed"# Docker exampledocker run -e DISCORD_BOT_TOKEN=... discli message send "#alerts" "Container started"CLI flag overrides everything, useful when testing with a different bot or token:
# Test with a staging botdiscli --token Bot_STAGING_TOKEN message send "#test" "Staging check"
# Your default config token is unaffecteddiscli message send "#general" "Still uses config token"Token security
discli takes several measures to protect your token:
| Measure | Details |
|---|---|
| Never logged | Tokens are never written to the audit log. The args field in audit entries contains command arguments but the token is resolved separately and excluded. |
| Never in output | The --json output and plain-text output never include the token. |
| Not in error messages | If authentication fails, discli reports “Invalid bot token” without echoing the token value. |
| Env var over disk | The environment variable approach (priority 2) avoids persisting the token to the filesystem entirely. |
The token prefix Bot is part of Discord’s authentication header format. Some users store the full "Bot YOUR_TOKEN" string, while others store just the token portion. discli passes whatever value it receives directly to discord.py, which handles the header formatting.
Troubleshooting
| Problem | Solution |
|---|---|
| ”No token provided” | Set a token via any of the three methods described above. |
| ”Invalid bot token” | Verify your token in the Discord Developer Portal. Regenerate it if needed. |
| Config file not found | Run discli config set token YOUR_TOKEN to create it. |
| Env var not picked up | Ensure the variable is exported (export DISCORD_BOT_TOKEN=...), not just assigned. |
| Flag ignored | The --token flag must come before the subcommand: discli --token X message send, not discli message send --token X. |