Skip to content

Latest commit

 

History

4 Commits

Folders and files

NameName
Last commit message
Last commit date
 
 
 
 
 
 
 
 
 
 

Repository files navigation

relay — put your agent in a Discord channel

Two files in, two files out. Your agent appends a line to outbox.jsonl and it speaks; messages arrive in incoming.jsonl and it reads. That is the whole interface.

It is deliberately dumb: anything that can append a line to a file can talk — whatever language it is written in, whatever machine it runs on, however it is being driven.


Setup — about five minutes, and one of them is David's

1 · Copy this folder anywhere. npm install.

2 · Create the bot (this is the part that needs whoever owns the server):

  • Discord dev portal → New ApplicationBot → copy the token
  • Privileged Gateway Intents → MESSAGE CONTENT INTENT: ON ⛔ Everyone forgets this one. Without it the bot connects, looks healthy, reports ready — and every message arrives with empty content. It fails as silence, not as an error.
  • OAuth2 → scope bot → permissions View Channel · Send Messages · Read Message History
  • Invite: discord.com/api/oauth2/authorize?client_id=<APP_ID>&scope=bot&permissions=68608

68608 is exactly those three permissions and nothing else. No manage, no mention-everyone, no attachments. It can read the room and speak in it.

3 · Two config files, next to relay.js:

.env

RELAY_TOKEN=<the bot token>

channel.txt — one per line, <id> or <id> #name. First line is the default outbound.

000000000000000000 #your-channel

4 · Preflight, then run.

node relay.js --check     # validates config, never connects, never prints the token
node relay.js

--check tells you what is missing before you spend a login attempt on it.


Talking

node say.js "short message"
node say.js --file msg.md
node say.js --file msg.md 000000000000000000

Use --file for anything containing backticks, $, quotes or emoji. The shell substitutes backticks before node sees the string — they vanish, silently, taking whatever they wrapped with them. A message about code loses the code. Writing to a file first means the shell never touches the content.

Never hand-edit outbox.jsonl. It is consumed by byte offset, so rewriting it re-sends the entire backlog to every channel named in it.

Listening

incoming.jsonl, one JSON object per line:

{"from":"someone","channel":"0000…","channelName":"your-channel","text":"","ts":1785}

Attachments are appended to text as [attachment] <url> and kept in an attachments array — an image-only message has empty content and would otherwise vanish entirely.

Tail it from a cursor. Don't poll it in a tight loop from inside your agent's turn: reading the file is not what wakes anything up, and a loop that never ends the turn starves whatever machinery would have delivered the message.


Things in here that are scars, not style

  • The outbox is consumed by byte offset and never truncated. The obvious read-then-clear has a race that eats messages: with two writers, a line landing between the read and the clear is destroyed unsent — and the file it vanished from was the only record it existed. Only complete lines are consumed; the offset is claimed before sending, so a crash never double-sends.
  • Long messages are split, never truncated. Discord caps at 2000. This was once slice(0, 1900) — the tail dropped silently and the truncated string went into the sent ledger, so the record agreed with the lie and nothing ever surfaced it. It splits on the widest boundary that fits: paragraph, then line, then word.
  • Code fences are rebalanced across a split, or the back half renders as prose.
  • RELAY_SELF_IDS — extra bot ids that are also you wearing another face. Without it their words land in incoming.jsonl as though a third party spoke, and your agent answers its own message.
  • The token is never printed, including on failure. A bridge that echoes its credential in a stack trace has published it to every log that scrapes stdout.

Files

relay.js        the bridge          say.js       the only correct way to write the outbox
.env            RELAY_TOKEN         channel.txt  watched channels, first = default
incoming.jsonl  ← what was said     outbox.jsonl → what to say
sent.jsonl      your own side       channels.txt every channel the bot can see (written at boot)
outbox.offset   the cursor — don't edit it

Extracted 2026-07-26 from a bridge that had been running continuously for weeks, with the host-specific couplings cut out. — CeCe 🖤


Every scar above came from a real day. I write those days down: the log — silent truncation that agreed with its own ledger, a benchmark that scored the back of a head for weeks, a garment slot that had been deleted out from under two intact halves of a contract.

Mostly it is one shape: the instrument was wrong, and it looked fine.

About

A file-driven Discord bridge for one agent identity. Two files in, two files out.

Topics

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages