# mira-pi

a pi extension that connects a pi agent to discord via project threads. it uses
discord.js to listen for messages and registers tools so the agent can interact
with threads.

install with `pi install npm:mira-pi`

## how it works

the extension runs inside the pi runtime. on startup it:

1. loads config from env vars, `.pi/discord.json`, and `~/.pi/discord.json`
2. restores the thread registry from `~/.mira/registry.json`
3. connects to discord via discord.js (listening for `messageCreate` events)
4. registers five tools that the agent can call

when a message arrives in a project thread, the extension either passes it to a
waiting `wait_for_message` call, queues it, or pushes it to the agent via
`pi.sendUserMessage()`.

## config

config is loaded from three sources, highest priority first:

| priority | source | notes |
|----------|--------|-------|
| 1 | env vars | `DISCORD_TOKEN`, `DISCORD_CHANNEL_ID`, `MIRA_PROJECT` |
| 2 | `.pi/discord.json` | project-local override |
| 3 | `~/.pi/discord.json` | global fallback |

```json
{
  "token": "your-bot-token",
  "channelId": "parent-channel-id",
  "project": "optional-override"
}
```

`project` defaults to the basename of the current working directory. set
`MIRA_PROJECT` or the json key to override it. placeholder values like
`"your-bot-token"` are treated as empty so example configs don't break things.

## message flow

```
discord message arrives (messageCreate)
  │
  ├─ not a thread? → ignore
  ├─ thread name ≠ project? → ignore
  ├─ author is a bot? → ignore
  │
  ├─ wait_for_message is waiting? → resolve the promise, wake the agent
  ├─ queue is empty, agent is in session? → pi.sendUserMessage() to push it
  └─ otherwise → queue it for later
```

the `wait_for_message` tool blocks until a message comes in or the timeout
expires. the queue drains into `wait_for_message` first — if there's a queued
message when the tool is called, it returns immediately.

## registry

thread metadata is persisted to `~/.mira/registry.json`:

```json
{
  "threads": {
    "my-project": {
      "threadId": "1513348392011694264",
      "project": "my-project",
      "name": "my-project",
      "parentChannelId": "1513094027011166279",
      "createdAt": "2026-06-08T00:00:00.000Z"
    }
  }
}
```

this survives restarts so `find_or_create_thread` can return existing threads
without hitting the discord api.

## tools

the extension registers these tools with the pi runtime:

| tool | description |
|------|-------------|
| `find_or_create_thread` | look up a thread in the registry or create a new one. posts a link in the parent channel on creation |
| `send_message` | send a message to a thread by id |
| `read_messages` | fetch recent messages (default 20, chronological). includes the agent's own messages |
| `list_threads` | list all tracked project threads from the registry |
| `wait_for_message` | block until a message arrives. timeout in seconds (default 300) |

## install

```bash
mkdir -p ~/.pi/agent/extensions/mira
cp index.ts package.json ~/.pi/agent/extensions/mira/
npm --prefix ~/.pi/agent/extensions/mira install --omit=dev
```

## dependencies

- `discord.js` ^14 — discord gateway client
- `@earendil-works/pi-coding-agent` (peer) — pi extension api types
- `typebox` (peer) — runtime tool parameter validation
