- TypeScript 99.4%
- JavaScript 0.4%
- Dockerfile 0.1%
The debug:True footer gains a last line with the model the API says answered, and the effort and thinking setting the request sent: "-# Model: claude-sonnet-5-5 · effort high · thinking on". ClaudeClient hands these back on ClaudeResponse.settings. Thinking is reported from the request rather than the response: Sonnet 5.5 returns its thinking blocks empty and also uses them for progress notes between tool calls, so a block in the response does not show that it reasoned. Make Public now posts the footer under the answer, so the public post matches the whisper. The footer rides on the pending entry beside the answer rather than inside it: the saved conversation turn and the shared public-answer context still record the answer alone, and the bot's own channel posts were never read back into history. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> |
||
|---|---|---|
| .forgejo/workflows | ||
| docker/pgadmin | ||
| docs | ||
| src | ||
| .dockerignore | ||
| .env.config | ||
| .env.development | ||
| .env.example | ||
| .env.production | ||
| .gitattributes | ||
| .gitignore | ||
| .markdownlint.json | ||
| .nvmrc | ||
| .prettierignore | ||
| .prettierrc.json | ||
| CHANGELOG.md | ||
| CLAUDE.md | ||
| docker-compose.production.yml | ||
| docker-compose.yml | ||
| Dockerfile | ||
| eslint.config.js | ||
| jest.config.js | ||
| LICENSE | ||
| package-lock.json | ||
| package.json | ||
| README.md | ||
| tsconfig.json | ||
| tsconfig.test.json | ||
| user-mappings.example.json | ||
Aufhocker Discord Bot
Aufhocker is a Discord bot that integrates with Claude AI, providing AI assistance directly within Discord servers using conversation history as context.
Features
- Claude AI Integration: Interact with Claude AI through Discord slash commands
- Private Responses by Default: All responses are private to you initially for privacy
- Make Public Button: Share responses with everyone using the "📢 Make Public" button
- Secure Sharing: Only you can make your responses public, with 1-hour expiry for security
- Conversation Context: Uses server message history to provide relevant context to Claude
- Configurable Context: Users can specify context scope (message count or time duration)
- Follow-up Questions: Each user's conversation is remembered for an hour after their last question (per channel, shared by
query-chatandespnff mcp), with prompt caching keeping follow-ups cheap - Shared Public Answers: Answers someone makes public become context for everyone in that channel; private answers never do
- ESPN Fantasy Football:
/aufhocker espnff mcpanswers questions about your league through an MCP server's tools, and/aufhocker espnff matchupposts your matchup as a score table - Built-in Help:
/aufhocker help, plusespnff help,lol helpandd2 help - Configurable Message Retention: Automatically stores and prunes message history (default 30 days, via
RETENTION_DAYS) - Admin Tools (opt-in): With
ENABLE_ADMIN_TOOLS, Claude can useweb_search(Brave) andweb_fetch(SSRF-guarded) for admin users - Rate Limiting: Built-in per-user rate limiting with admin bypass capabilities
- Admin Emergency Controls: Temporary bypasses, rate limit management, and audit trail via the
/aufadmincommand - Optional Plugins: League of Legends live-game monitor and a Diablo 2 (d2emu) Terror Zone / DClone tracker whose alerts ping only the people who signed up for them — both off by default
- Notification Sign-up Board: Buttons that let people join or leave the D2 and LoL notification channels themselves
- Comprehensive Logging: Detailed logging for debugging and monitoring
Quick Start
Prerequisites
- Node.js 22.0.0 or higher
- PostgreSQL database (or use the bundled Docker Compose stack)
- Discord Bot Token
- Claude API Key
Installation
- Clone the repository:
git clone <repository-url>
cd aufhocker
- Install dependencies:
npm install
- Set up environment variables:
cp .env.example .env
# Edit .env with your configuration
- Set up the database:
# Create database and run migrations
npm run db:migrate
- Build and start the bot:
npm run build
npm start
For development:
npm run dev
Local Development with Docker
For a quick local development setup using Docker:
- Start PostgreSQL and pgAdmin:
docker-compose up -d
-
Access the services:
- PostgreSQL:
localhost:5432 - pgAdmin:
http://localhost:5050- Email:
admin@aufhocker.local - Password:
admin
- Email:
- PostgreSQL:
-
The database is automatically initialized with the required schema.
-
To stop the services:
docker-compose down
- To reset the database (removes all data):
docker-compose down -v
docker-compose up -d
Docker Environment Variables
The following environment variables can be customized in your .env file for Docker:
| Variable | Default | Description |
|---|---|---|
POSTGRES_USER |
aufhocker |
PostgreSQL username |
POSTGRES_PASSWORD |
aufhocker_dev |
PostgreSQL password |
POSTGRES_DB |
aufhocker |
Database name |
POSTGRES_PORT |
5432 |
PostgreSQL port |
PGADMIN_EMAIL |
admin@aufhocker.local |
pgAdmin login email |
PGADMIN_PASSWORD |
admin |
pgAdmin login password |
PGADMIN_PORT |
5050 |
pgAdmin web interface port |
Configuration
Configuration is split across two files so secrets and non-secret tuning have different lifecycles:
.env(git-ignored) — secrets and deployment identity only. Copy.env.exampleto.envand fill it in..env.config(committed) — non-secret defaults: models, intervals, feature flags, pool sizes. Edit this for behaviour changes; it contains no credentials.
Both are loaded together (docker-compose env_file: [.env.config, .env]; local
runs load .env then .env.config via dotenv). Their keys are disjoint, and a
value in .env wins on any overlap. .env.development / .env.production
remain full single-file templates for setups that prefer one file.
Required Configuration
DISCORD_TOKEN: Your Discord bot tokenDISCORD_CLIENT_ID: Your Discord application client IDCLAUDE_API_KEY: Your Anthropic Claude API keyDATABASE_URL: PostgreSQL connection string
Optional Configuration
CLAUDE_MODEL: Claude model to use (default: claude-sonnet-5-5). It must accept the effort settingCLAUDE_MAX_TOKENS: Maximum tokens per request, thinking included (default: 10000)CLAUDE_CHAT_EFFORT: How hardquery-chatandespnff mcpwork:low,medium,high,xhighormax(default:high)CLAUDE_MAX_CONTEXT_TOKENS: Token budget for channel-history context sent to Claude (default: 3000)CONVERSATION_MEMORY_MINUTES: How long a follow-up still continues the asker's conversation, counted from their previous question (default: 60;0turns memory off)PROMPT_CACHE_TTL: How long Claude keeps a conversation cached for its next follow-up,5mor1h(default:1h). Saving costs 1.25x normal input for5mand 2x for1h; every cached re-read is 0.1xRETENTION_DAYS: Message and public-answer retention period in days (default: 30)ENABLE_ADMIN_TOOLS: Enable Claude tool use (web_search/web_fetch) for admin users (default: false;web_searchalso needsBRAVE_SEARCH_API_KEY)LOG_LEVEL: Logging level, one oferror,warn,info,http,verbose,debug,silly, in any case (default: info).warningreads aswarn; anything else logs atinfo, with a warning at startup
Usage
/aufhocker help lists every command the bot has turned on. Each command group
has its own detail: /aufhocker espnff help, /aufhocker lol help,
/aufhocker d2 help.
Ask Claude
/aufhocker query-chat query:<your question>- Ask Claude privately, with the last hour of channel chat as context/aufhocker query-chat query:<your question> context:20 messages- Pick a different window of chat/aufhocker query-chat query:<your question> new:True- Start a fresh conversation/aufhocker query-chat query:<your question> debug:True- Also show, under the answer, what Claude looked up round by round, the tokens in and out, and the model, effort and thinking setting it ran with. Make Public posts this footer too
Fantasy Football
/aufhocker espnff mcp query:<your question>- Ask about your ESPN fantasy league.new:Trueanddebug:Truework as they do forquery-chat/aufhocker espnff matchup [week:<n>]- Your matchup as a score table, straight from the fantasy server (no Claude call, no rate limit)/aufhocker espnff help- What the fantasy server can answer, and whether it is connected
It reads the league through the fantasy server's tools, plus any answers people made public in the channel. It never reads the channel chat.
The team's own manager (ESPNFF_OWNER_IDS) can also change the team from
here: set the lineup, put in waiver claims, and reorder them. A lineup move
happens straight away. A waiver claim or a new claim order does not: the answer
comes with a Confirm button for each change, showing the players as ESPN
has them, and nothing is sent until that person presses it. Everyone else only
ever sees the read-only tools. The fantasy server needs ESPN_WRITES set to
dry_run or live; in dry_run every step works and nothing reaches ESPN.
Diablo 2 Alerts
The D2 tracker pings you only for what you signed up for. Nobody signed up means no post. A Terror Zone alert deletes itself 15 minutes after that zone rotates out. A hosted walk goes a similar way: once D2R Clone Hunters strike their post through, the walk is over, and 30 minutes later the bot deletes their post and its own reply under it (this needs the bot to have Manage Messages in the D2 channel). A post they never strike goes 24 hours after the walk time it names. A DClone alert is deleted when its game moves on to the next step, and a spawn after 12 hours.
/aufhocker d2 tz-alerts- Pick the Terror Zones you want pings for, and when: as the zone starts, 5, 10, 15 or 20 minutes before, or any mix. A warning stays up through its zone and deletes itself 15 minutes after the zone ends/aufhocker d2 dclone-alerts- Pick DClone game modes (softcore or hardcore, ladder or non-ladder), regions (USA, EU, KR) and steps (2/6 to 5/6, and the spawn); a ping needs a mode, a region and a step you picked. Pick no steps and you get no DClone pings; the status message shows every step anyway/aufhocker d2 walk-alerts- The same, for hosted DClone walks announced by D2R Clone Hunters (whenD2EMU_WALK_SOURCE_CHANNEL_IDis set)/aufhocker d2 tz//aufhocker d2 dclone- Check the current Terror Zone, or DClone progress in all twelve Reign of the Warlock games, privately
Each menu is private and saves as you pick. Set the D2 channel to Only
@mentions so the alerts you picked are the only ones that reach you. An
admin posts a sign-up board with buttons for all three menus with
/aufadmin d2 signup-board, and a DClone status message with
/aufadmin d2 dclone-board: all twelve games in one grid, kept up to date by
the bot, never pinging anyone. Pin both.
Notification Sign-up
The D2 and LoL notification channels open to one role each. An admin posts
a board with a Diablo 2 and a League of Legends button with
/aufadmin roles signup-board, in a channel everyone can see. Clicking a
button joins that channel; clicking it again leaves. The bot needs Manage
Roles, with its role above both.
Context Parameter (Optional)
The context parameter on query-chat is optional and controls how much conversation history Claude sees:
When omitted: Defaults to the last 1 hour of messages
Message count: context:20 messages or context:50 (1-100 messages)
Time duration: context:2 hours or context:3 days (1-23 hours, 1-30 days)
Relative dates: context:yesterday or context:today
Specific dates: context:2026-01-10 (ISO format, all times in UTC)
💡 Tip: For simple questions, leave context empty to use the default (last 1 hour).
Follow-up Questions
Both query-chat and espnff mcp remember your conversation: a question asked
within an hour of your previous one (CONVERSATION_MEMORY_MINUTES) is sent
with the questions and answers before it, so "what about next week?" works.
- Each person has their own conversation per channel, and both commands share
it: ask about your lineup in
espnff mcp, then follow up inquery-chat. The first question after switching command is read at full price; follow-ups in the same command are cached again. - A follow-up adds only what was said in the channel since your last question.
Passing
context:on a follow-up pulls that window again instead. new:Truestarts over. So does a gap longer than the memory window, or a conversation reaching 20 questions.- Earlier turns are re-sent from Claude's prompt cache (
PROMPT_CACHE_TTL), at about a tenth of the normal input price.
Privacy & Sharing
🔒 Private by Default: All responses are initially private - only you can see them.
📢 Make Public: Use the "Make Public" button to share responses with everyone in the channel. A public answer also becomes context for everyone's next question in that channel, on both commands. Private answers stay in your own conversation.
🛡️ Security Features:
- Only the original user can make their response public
- The Make Public button expires after 1 hour
- Channel history and public answers are passed to Claude as data, never as instructions
Examples
/aufhocker query-chat query:What's the main topic we've been discussing?
/aufhocker query-chat query:Can you summarize what John said? context:10 messages
/aufhocker query-chat query:What decisions did we make about the project? context:2 hours
/aufhocker query-chat query:What was discussed yesterday? context:yesterday
/aufhocker espnff mcp query:How did my team do this week?
/aufhocker espnff mcp query:And who should I pick up?
Workflow:
- Use
/aufhocker query-chat query:<your question> - Optionally add
context:to specify the conversation history scope - Receive a private response: your question, then Claude's answer, exactly as Make Public would post it
- Ask a follow-up the same way; it remembers the conversation
- Optionally click "📢 Make Public" to share with everyone (within an hour)
Privacy & Security
Aufhocker is designed with privacy and security in mind:
Private Responses by Default
- All Claude responses are ephemeral (private) by default
- Only the user who asked the question can see the initial response
- This prevents accidental sharing of sensitive information
Controlled Public Sharing
- Users can choose to make responses public using the "📢 Make Public" button
- Security: Only the original user can make their own response public
- Expiry: Public sharing capability expires after 1 hour for security
- One-time: Each response can only be made public once
Context Privacy
- Message history is used appropriately for context
- The bot's own messages are excluded from context, except answers someone chose to make public
- A private answer is only ever replayed to the person who asked for it, in their own conversation
- Context is channel-specific (no cross-channel data leakage)
- Conversations are deleted once they pass the memory window; public answers follow
RETENTION_DAYSlike chat - Automatic retention policy (default 30 days, configurable via
RETENTION_DAYS) protects long-term privacy
Technical Security Features
- Response IDs include user verification to prevent unauthorized sharing
- Automatic cleanup of expired sharing tokens
- Secure button interaction handling with user validation
- Comprehensive error handling to prevent information leakage
Development
Scripts
npm run build- Build TypeScript to JavaScriptnpm run dev- Run in development mode with hot reloadnpm test- Run all testsnpm run test:watch- Run tests in watch modenpm run test:coverage- Run tests with coverage reportnpm run lint- Run ESLintnpm run lint:fix- Fix ESLint issues automatically
Testing
The project includes comprehensive testing:
- Unit Tests: Test individual components in isolation
- Property-Based Tests: Validate universal correctness properties
- Integration Tests: Test component interactions
Run tests:
npm test
Project Structure
src/
├── admin/ # Admin domain: AdminService, RateLimiter, AdminAuditService, UserMapper
├── bot/ # Thin host (AufhockerBot): infra + plugin registry dispatch
├── claude/ # Anthropic SDK client, tool executor, SSRF guard, ToolAuditService
├── commands/ # Slash-command parsing + admin command handling
├── config/ # Configuration management
├── context/ # Channel history → Claude context assembly
├── database/ # Postgres connection, message store, migrations
├── discord/ # Discord client, command registration, routing
├── message/ # Message processing and 2000-char splitting
├── plugins/ # Plugin contract + features: query/ (core chat), lol/, d2emu/
├── types/ # Shared TypeScript type definitions
├── utils/ # Cross-cutting infra only: logger, ErrorHandler, ShutdownHandler
└── test/ # Test setup and utilities
Database Schema
The bot uses PostgreSQL. Migrations under src/database/migrations/scripts/ are
the single source of truth; they are applied automatically at startup. Core
tables:
messages— message history (pruned perRETENTION_DAYS, default 30)conversation_turns— each user's private questions and answers, replayed on follow-ups (deleted once a conversation passes the memory window)public_answers— answers made public, shared as channel context (pruned perRETENTION_DAYS)user_mappings— Discord-ID → display-name mappingsadmin_audit_trail— audit log of all admin actionstemporary_rate_limit_bypasses— active/expired admin-granted bypassesschema_migrations— applied-migration bookkeeping
Plugin tables (created only by their migrations): lol_watchlist,
lol_active_games, lol_monitor_config, d2emu_config,
d2emu_subscriptions (who signed up for which D2 alerts, per server).
Error Handling
The bot includes comprehensive error handling for:
- Authentication failures (Discord/Claude)
- Rate limiting (with exponential backoff)
- Network errors (with retry logic)
- Database connection issues
- Invalid command parameters
Contributing
- Fork the repository
- Create a feature branch
- Make your changes
- Add tests for new functionality
- Ensure all tests pass
- Submit a pull request
License
This project is licensed under the MIT License - see the LICENSE file for details.
Deployment
For detailed deployment instructions, see the Deployment Guide:
- Local Development: Docker Compose setup with PostgreSQL and pgAdmin
- Production: Docker Swarm — registry-pulled image, shared Postgres, Swarm secrets (the live deployment)
- Alternative: single-host self-contained stack via
docker-compose.production.yml - Database Migrations: Schema management and updates
- Troubleshooting: Common issues and solutions
Quick links:
Admin Controls
For global administrators, Aufhocker provides emergency controls and administrative features through the separate /aufadmin command. See the Admin Controls Guide for details:
- Separate Admin Command:
/aufadmincommand with Discord Administrator permission requirement - Dual Permission System: Requires both Discord Administrator role AND global admin user ID configuration
- Temporary Rate Limit Bypasses: Grant temporary relief to users
- Rate Limit Management: Clear rate limits for users or all users
- Status Monitoring: Check rate limit status for any user
- Audit Trail: All admin actions are logged for security and compliance
Admin Commands
Important: Admin commands require both Discord Administrator permission AND being configured as a global admin in the bot settings.
/aufadmin quota bypass- Grant temporary rate limit bypass to a user/aufadmin quota revoke- Revoke temporary bypass for a user/aufadmin quota status- Check user rate limit status/aufadmin quota clear- Clear rate limits for a user or all users/aufadmin reload-mappings- Re-readuser-mappings.jsonand sync it to the database/aufadmin d2 notifications-channel- Set or view the channel D2 alerts post in/aufadmin d2 signup-board- Post the D2 alert sign-up board in that channel (then pin it)/aufadmin d2 dclone-board- Post the DClone status message in that channel (then pin it); running it again replaces it
Visibility: Non-admin users will not see the /aufadmin command in their command list. Only users with Discord Administrator role will see it, but execution still requires global admin user ID configuration.
Quick links:
Support
For issues and questions:
- Check the logs in the
logs/directory - Review the Deployment Guide troubleshooting section
- Ensure all required environment variables are set
- Check database connectivity
Security
- Never commit your
.envfile - Rotate API keys regularly
- Use environment-specific configurations
- Monitor usage and rate limits