Pixel is a helpful assistant bot for members and visitors of the Pixelbar hackerspace in Rotterdam.
- Ask whether the space is open, see upcoming events and get info about Pixelbar.
- Get an announcement when the space opens or closes.
- What you can do depends on your tier: guest, friend, member or admin.
Pixel starts on Discord. Its core doesn't depend on any platform, so other platforms (interactive ones like Telegram, outbound-only ones like Mastodon) can be added later as separate adapters.
Status: phase 1 in progress. The core, the access lists and the Discord adapter work, with
/help,/ping,/status(live from SpaceAPI),/whoamiand/admin(status,reload,level set,level get,sync,capabilities), plus open/closed announcements in Discord,/events(from the server's scheduled events),/info(short answers about Pixelbar) and/ha(list,status,set: the Home Assistant devices you may see, and switching lights and switches on and off if you hold the capability). Seedocs/architecture.mdanddocs/identity-and-access.md. To hide admin commands from other people in Discord, seedocs/discord-command-visibility.md.
| Concern | Choice |
|---|---|
| Language | TypeScript on Node.js LTS (ESM) |
| Discord | discord.js |
| Error reporting | Sentry |
| Logging | pino (JSON to stdout) |
| Task runner | just |
| Infrastructure | Terraform → Azure (later) |
| Images | GitHub Container Registry (later) |
| Tooling | pnpm, Vitest, Biome, zod |
- Node.js (the version in
.nvmrc), with pnpm throughcorepack enable - just
pnpm install
cp .env.example .env # fill in the values
cp config/admins.example.yaml config/admins.yaml # add yourself
cp config/members.example.yaml config/members.yaml
just validate-config
just register # register slash commands on your test guild
just dev # run Pixel with hot reloadNever develop against the production bot.
- Create an application at https://discord.com/developers/applications.
- Under Bot, reset the token and put it in
DISCORD_TOKEN. Put the application ID inDISCORD_APP_ID. - Create a test server, invite the bot with the
botandapplications.commandsscopes, and put the server ID inDISCORD_GUILD_ID. - Turn on Developer Mode in Discord, right-click yourself, choose Copy User ID, and add yourself to
config/admins.yamland toconfig/members.yaml(admins are members too).
Pixel can post when Pixelbar opens or closes, in two styles. Each has its own channel setting, you can use either, both or neither, and they can be the same channel:
- Live (
DISCORD_ANNOUNCE_LIVE_CHANNEL_ID): opening makes a "🟢 Pixelbar is open" post. Closing edits that same post to "🔴 Pixelbar is closed, was open from … to …". Opening again makes a new post, so a closed post never flips back. - Timeline (
DISCORD_ANNOUNCE_TIMELINE_CHANNEL_ID): a new post for every open and every close, never edited. Good for a status-only channel where you want a log of exactly when it opened and closed.
A change is posted once it has held for two checks in a row (about 30–60 seconds), so flicking the switch doesn't flood the channel. Nothing is posted when Pixel starts.
Give the bot these permissions in each channel: View Channel, Send Messages and Embed Links, plus Read Message History for the live style. Pixel checks this at startup and tells you in the logs if something is missing.
/info answers from markdown files in content/info/, one per topic. You can edit them right in GitHub, and you don't need to know any code. A file looks like this:
---
title: Becoming a member
summary: Member and Friend memberships, what they cost and how to join
order: 30
---
The text of the answer, in markdown…- The file name is the topic's name (
membership.md): lowercase letters, digits and dashes. {{announcements-channel}}in the text becomes a link to the announcements channel (set byDISCORD_ANNOUNCEMENTS_CHANNEL_ID). It's the only placeholder so far, and a misspelt one fails the checks.- Keep answers short and link to the full page on the website, which stays the source of truth for prices, rules and opening times.
- This repository is public. Never put passwords, door codes or personal details in these files.
- A broken file fails the checks on your pull request, with a message saying what's wrong.
- Changes go live on the next deploy. Adding, removing or renaming a topic also needs
just register.
Tiers come from two YAML files. They are gitignored, because they contain personal data.
config/admins.yaml: Pixel admins, each withidslike["discord:<id>"](the same shape asmembers.yaml). Each admin also needs amembers.yamlentry holding the same ids.config/members.yaml: payingmemberandfriendmemberships (Discord ID and tier), plus optional capabilities. Pixel rewrites this file when admins change tiers, keeping a.bakof the previous version, so it needs a writable location. Hand edits are still fine, and/admin reloadpicks them up.
Everyone else is a guest.
Discord IDs must be quoted strings, because unquoted numbers lose precision. Pixel won't start if a file is missing or invalid. Run just validate-config to check the files.
These are validated at startup. See .env.example and the full list.
| Variable | Description |
|---|---|
DISCORD_TOKEN |
Bot token |
DISCORD_APP_ID |
Application ID |
DISCORD_GUILD_ID |
The one guild Pixel serves |
DISCORD_ANNOUNCEMENTS_CHANNEL_ID |
Optional. Where announcements and the weekly poll are posted. /info points people at it |
DISCORD_ANNOUNCE_LIVE_CHANNEL_ID |
Optional. Channel for the live style: one post per opening, edited to "closed" when the space closes |
DISCORD_ANNOUNCE_TIMELINE_CHANNEL_ID |
Optional. Channel for the timeline style: a new post for every open and every close, never edited |
DISCORD_ROLE_MEMBER, DISCORD_ROLE_FRIEND |
Optional. A Discord role (name or ID) that each level is mirrored to. Unset means not mirrored |
HOME_ASSISTANT_URL, HOME_ASSISTANT_TOKEN |
Optional, set both or neither. How Pixel reaches Home Assistant (for example the Nabu Casa URL) and a long-lived token from a non-admin HA user |
PIXEL_HOME_ASSISTANT_DIR |
Optional. Where devices.yaml (the Home Assistant device allow-list) lives. Defaults to config/home-assistant/. Copy devices.example.yaml there when you set up Home Assistant |
PIXEL_HOME_SYNC_MINUTES |
Optional. How often (minutes) Pixel refreshes inventory.yaml, a list of everything Home Assistant has so you can copy devices into devices.yaml. It is not an allow-list. Defaults to 60, 0 for only at startup and on /admin reload |
SPACEAPI_URL |
Optional. Defaults to https://spaceapi.pixelbar.nl/ |
PIXEL_DATA_DIR |
Optional. Where Pixel keeps small bits of state (space.state, announcements.state). Defaults to data/ |
PIXEL_TIMEZONE |
Optional. The time zone /events shows times in. Defaults to Europe/Amsterdam |
PIXEL_CONTENT_DIR |
Optional. Where the /info topics live (info/*.md). Defaults to content |
SENTRY_DSN |
Optional. Error reporting and Sentry Logs are off if unset |
PIXEL_LOG_DIR |
Optional. Where the rotating log file goes (about two weeks, JSON lines). Defaults to data/logs/. Empty turns the file off. Logs also go to the console and to Sentry Logs |
Run just to list every recipe.
| Command | What it does |
|---|---|
just dev |
Run Pixel locally with hot reload |
just check |
Lint, type-check, test and enforce coverage (what CI runs) |
just test |
Run the tests |
just coverage |
Run the tests with coverage; fails below the thresholds in vitest.config.ts |
just build / just start |
Compile to dist/ and run the build |
just fmt |
Auto-format |
just validate-config |
Validate the access list files |
just register |
Register slash commands with Discord |
just command-access |
Show admin-tier commands to the admins in admins.yaml (see docs/discord-command-visibility.md) |
just docker-build |
Build the container image |
Every pull request and push to main runs CI. It runs just check (lint, type-check, tests with coverage thresholds) and the production build, and checks that the Docker image builds.
Not set up yet. The plan is a single container on Azure Container Apps, with dev and prod environments provisioned by Terraform. See Deployment.
The operations runbook covers day-to-day tasks and incidents: access lists, moderation, Home Assistant, rotating secrets, deploys and rollbacks, what to do when the bot is down or answering twice, Sentry, privacy requests and disaster recovery. Anyone who runs Pixel should read it first.
Pixel decides who gets member-level access, so its security matters. If you find a vulnerability, email the board at bestuur@pixelbar.nl instead of opening a public issue.
Pixel is a Pixelbar community project. If you use an AI coding agent, point it at AGENTS.md.
TBD