Token Resolution
discli needs a Discord bot token to authenticate with the Discord API. The token is resolved through a four-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{DISCORD_TOKEN\nenv var set?}
H -- Yes --> I[Use fallback env var value]
H -- No --> J[Error: No token\nprovided]
style C fill:#059669,color:#fff
style E fill:#059669,color:#fff
style G fill:#059669,color:#fff
style I fill:#059669,color:#fff
style J 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 | ~/.discli/config.json file | {"token": "Bot_ABC123"} |
| 4 (lowest) | DISCORD_TOKEN environment variable | export DISCORD_TOKEN=Bot_ABC123 |
The first source that provides a non-empty value wins. If none of the sources has a token, discli exits with an error:
Error: No token provided. Use --token, set DISCORD_BOT_TOKEN (or DISCORD_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, and finally to DISCORD_TOKEN.
# 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") if not token: token = os.environ.get("DISCORD_TOKEN") ctx.obj["token"] = tokenStage 2 — Client (client.py): Before running the Discord action, resolve_token() verifies that a token was found following the same priority chain. If no token was configured, 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 env_token = os.environ.get("DISCORD_TOKEN") if env_token: return env_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.
On macOS and Linux, discli automatically ensures ~/.discli/config.json has restrictive 0600 permissions and ~/.discli/ has 0700 permissions so only your user can read the config file.
Why this order
The four-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 (DISCORD_BOT_TOKEN) 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"Environment variable fallback (DISCORD_TOKEN) is supported for environments or containers that use the generic Discord token variable name. It is ranked below the config file so that existing discli configurations are not accidentally shadowed by other bots running in the same environment.
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. |