No description
  • TypeScript 97.4%
  • Shell 1.2%
  • JavaScript 0.7%
  • Nix 0.4%
  • Dockerfile 0.3%
Find a file
Repository files (latest commit first)
Filename Latest commit message Latest commit date
2026-10-14 01:43:09 +01:00
deploy Refactor admin domains, add control CLI, and unify bot lifecycle tooling 2026-10-14 01:18:44 +01:00
migrations Refactor to DB-first multi-server runtime and scoped config hierarchy 2026-10-14 01:43:02 +01:00
src Refactor to DB-first multi-server runtime and scoped config hierarchy 2026-10-14 01:43:02 +01:00
tests Refactor to DB-first multi-server runtime and scoped config hierarchy 2026-10-14 01:43:02 +01:00
.dockerignore Initial IRC bot implementation 2026-10-12 16:15:37 +01:00
.env.docker.example Add minimal Docker Compose deployment setup 2026-10-13 02:18:50 +01:00
.env.example Add configurable command prefix and richer URL previews 2026-10-13 02:11:58 +01:00
.gitignore Update bootstrap config/docs for database-only bootstrap 2026-10-14 01:43:09 +01:00
arion-compose.nix Initial IRC bot implementation 2026-10-12 16:15:37 +01:00
bot.config.example.json Update bootstrap config/docs for database-only bootstrap 2026-10-14 01:43:09 +01:00
docker-compose.yml Refactor admin domains, add control CLI, and unify bot lifecycle tooling 2026-10-14 01:18:44 +01:00
Dockerfile Refactor admin domains, add control CLI, and unify bot lifecycle tooling 2026-10-14 01:18:44 +01:00
drizzle.config.ts Initial IRC bot implementation 2026-10-12 16:15:37 +01:00
live-verify-llm.mjs Refactor admin domains, add control CLI, and unify bot lifecycle tooling 2026-10-14 01:18:44 +01:00
package-lock.json Initial IRC bot implementation 2026-10-12 16:15:37 +01:00
package.json Add LLM memory modules and IRC command tests 2026-10-12 20:35:14 +01:00
README.md Update bootstrap config/docs for database-only bootstrap 2026-10-14 01:43:09 +01:00
tsconfig.json Initial IRC bot implementation 2026-10-12 16:15:37 +01:00

IRC Bot (TypeScript + Postgres)

A modular IRC bot with these commands:

  • ;tell <user|@account> <message>
  • ;in <duration> <text>
  • ;countdown <duration> [label]
  • ;addquote <user> <text>
  • ;rmquote <quote id>
  • ;quote <user> [n|text]
  • ;seen <nick>
  • ;calc <expression>
  • ;rand [max|min max]
  • ;ping
  • ;uptime
  • ;version
  • ;units <number><unit> to <unit>
  • ;help
  • ;admin <...> (authenticated session)
  • ;llm <ask|model|models>

In private query with the bot, command prefix is optional.

Additional behavior:

  • multi-channel connect + runtime join/part
  • accepts invites (configurable)
  • dynamically loaded modules from src/modules/*
  • per-channel module enable/disable state persisted in Postgres
  • URL previewing with rich adapters:
    • Wikipedia summary
    • Reddit post metadata
    • Hacker News item metadata
    • generic title/description fallback
  • chat history logging for future LLM/OpenRouter integration
  • OpenRouter-backed LLM module with token-efficient context windowing
  • channel-scoped memory extraction (channel.* / channel.<user>.*) with no cross-channel leakage

Stack

  • TypeScript
  • irc-framework
  • PostgreSQL + Drizzle ORM
  • SQL migrations (migrations/*.sql)
  • Docker + Arion

Admin Auth

Admin control is password-based and works through private query with the bot.

Bootstrap once (before any admin password exists):

  • register admin <password>

Login/logout:

  • login admin <password>
  • logout admin

Admin sessions expire after 5 hours, then require another login.

After login, admin command works in query:

  • ;admin diag (query/PM only)
  • ;admin setting list
  • ;admin setting get <key>
  • ;admin setting set <key> <value>
  • ;admin setting set channels #a #b #c
  • ;admin setting set accept_invites true|false
  • ;admin do join #channel
  • ;admin do part #channel
  • ;admin module list #channel
  • ;admin module enable #channel <module>
  • ;admin module disable #channel <module>
  • ;admin config list llm
  • ;admin config get llm <api_key|model|extractor_model|system_prompt>
  • ;admin config set llm <api_key|model|extractor_model|system_prompt> <value>
  • ;admin config unset llm api_key
  • ;admin llm ...
  • ;admin memory show #channel [user]
  • ;admin memory summary #channel
  • ;admin user ignore <nick>
  • ;admin user ignore <nick>#<channel> (channel-scoped)
  • ;admin user unignore <nick|nick#channel>
  • ;admin user list [#channel]

Config keys currently supported:

  • channels
  • accept_invites
  • url_preview
  • command_prefix

LLM module

The llm module is dynamically loaded and disabled by default per-channel.

Enable it in a private admin query:

  • ;admin module enable #channel llm

User-facing commands:

  • ;llm ask <question>
  • ;llm model
  • ;llm models [filter]

Token efficiency strategy:

  • always include the latest 50 messages from channel/private history
  • include channel-scoped memory facts (channel.*, channel.<user>.*)
  • include a rolling 24h channel summary (resets automatically)
  • constrained output token budget per request

Default model is deepseek/deepseek-v4-pro (Deepseek V4 Pro), configurable at runtime.

Default extractor model is openai/gpt-4.1-nano (cheap), also configurable.

Channel memory keys

  • channel.<key> for channel-wide facts
  • channel.<user>.<key> for user-specific facts within that channel

The retriever only reads memory from the current channel to avoid leakage across channels.

Memory extraction is refreshed periodically from chat_history in the background (every ~5 minutes), so new facts are learned continually without rescanning the full log each request.

Quick start

  1. Copy config template:
cp bot.config.example.json bot.config.json
  1. Edit bot.config.json (only databaseUrl).

  2. Start Postgres locally (example with Docker):

docker run --name ircbot-postgres -e POSTGRES_USER=ircbot -e POSTGRES_PASSWORD=ircbot -e POSTGRES_DB=ircbot -p 5432:5432 -d postgres:16-alpine
  1. Install deps and run migrations:
npm install
npm run db:migrate -- --config ./bot.config.json
  1. Configure at least one IRC server identity in DB, then run bot:
npm run dev -- --config ./bot.config.json --control server create main irc.example.net relaybot 6697
npm run dev -- --config ./bot.config.json --control setting set main channels "#example"
npm run dev -- --config ./bot.config.json

Control CLI (connected or disconnected)

Use control mode without connecting to IRC:

npm run dev -- --config ./bot.config.json --control observe
npm run dev -- --config ./bot.config.json --control server list
npm run dev -- --config ./bot.config.json --control server set main nick relaybot2
npm run dev -- --config ./bot.config.json --control setting list main
npm run dev -- --config ./bot.config.json --control setting set main channels "#chan1 #chan2"
npm run dev -- --config ./bot.config.json --control user ignore main alice#chan1
npm run dev -- --config ./bot.config.json --control user list main

If the bot is already running, it polls settings from DB and applies changes live (channels/prefix/invites/url preview) within ~15s.

Start/stop/restart (local or docker)

Use one wrapper with a mode flag:

./deploy/botctl.sh --mode local start
./deploy/botctl.sh --mode local restart
./deploy/botctl.sh --mode local status
./deploy/botctl.sh --mode local logs

./deploy/botctl.sh --mode docker start
./deploy/botctl.sh --mode docker restart
./deploy/botctl.sh --mode docker status
./deploy/botctl.sh --mode docker logs bot

Docker compose (minimal VPS)

  1. Ensure bot.config.json exists and has Docker DB host (postgres) in databaseUrl.
  2. Start stack:
./deploy/botctl.sh --mode docker start
  1. Follow logs:
./deploy/botctl.sh --mode docker logs bot

Optional systemd wrapper for compose stack:

sudo cp deploy/irc-bot-compose.service /etc/systemd/system/
sudo systemctl daemon-reload
sudo systemctl enable --now irc-bot-compose

Migrations

  • Generate migration templates from schema: npm run db:generate
  • Apply committed migrations: npm run db:migrate

URL adapters

The URL feature follows a modular adapter chain, inspired by Sopel-like URL handlers:

  1. Wikipedia-specific summary API
  2. Reddit JSON metadata
  3. Hacker News item API
  4. Derpibooru image API metadata
  5. Danbooru post API metadata
  6. Gelbooru post API metadata
  7. Forgejo/Gitea repo/issue/PR/commit API metadata
  8. generic HTML title/metadata
  9. optional Playwright fallback (URL_PREVIEW_PLAYWRIGHT=1)

Derpibooru requests can use bot_config.derpibooru_api_key when present.

Reddit previews can use OAuth when REDDIT_CLIENT_ID and REDDIT_CLIENT_SECRET are configured, which allows richer metadata (score, upvote ratio, comments, flair, media URL, created time) even when anonymous Reddit JSON endpoints are blocked.

Playwright fallback is optional and only used when enabled.

Chat history

Every inbound and outbound message is persisted to chat_history with metadata (target, nick, account, tags, timestamps, command marker). This is intended to provide a durable context layer for future OpenRouter/RAG integration.

Arion

With Arion installed:

cp bot.config.example.json bot.config.json
arion up

This starts:

  • postgres (persistent volume pgdata)
  • bot (builds from local Dockerfile, runs migrations on startup)