Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
68 changes: 46 additions & 22 deletions AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,16 +14,44 @@ This file provides guidance to AI agents for building Towns Protocol bots.

## Base Payload

All events include:
All events use a discriminated union based on `isDm`:

```typescript
{
userId: string; // Hex address (0x...)
spaceId: string;
channelId: string;
eventId: string; // Unique event ID (use as threadId/replyId when responding)
createdAt: Date;
}
type BasePayload =
| {
userId: string; // Hex address (0x...)
spaceId: null; // Always null for DM channels
channelId: string;
eventId: string; // Unique event ID (use as threadId/replyId when responding)
createdAt: Date;
event: StreamEvent;
isDm: true; // Discriminator: when true, spaceId is null
}
| {
userId: string; // Hex address (0x...)
spaceId: string; // Always a string for space channels
channelId: string;
eventId: string; // Unique event ID (use as threadId/replyId when responding)
createdAt: Date;
event: StreamEvent;
isDm: false; // Discriminator: when false, spaceId is string
};
```

**Type Safety:** TypeScript automatically narrows the type based on `isDm`. When `isDm` is `true`, `spaceId` is guaranteed to be `null`. When `isDm` is `false`, `spaceId` is guaranteed to be a `string`.

**Example:**

```typescript
bot.onMessage(async (handler, event) => {
if (event.isDm) {
// TypeScript knows spaceId is null here
console.log("DM channel, no space");
} else {
// TypeScript knows spaceId is string here
console.log(`Space: ${event.spaceId}`);
}
});
```

## Event Handlers
Expand Down Expand Up @@ -82,6 +110,14 @@ bot.onSlashCommand("help", async (handler, event) => {
});
```

#### Paid Commands

Add a `paid` property to your command definition with a price in USDC:

```typescript
{ name: "generate", description: "Generate AI content", paid: { price: '$0.20' } }
```

### onReaction

**When:** User adds emoji reaction
Expand Down Expand Up @@ -322,6 +358,8 @@ const hash = await execute(bot.viem, {

## External Interactions (Unprompted Messages)

`bot.start()` returns a **Hono app**. To extend with additional routes, create a new Hono app and use `.route('/', app)` per https://hono.dev/docs/guides/best-practices#building-a-larger-application

**All handler methods available on bot** (webhooks, timers, tasks):
You need data prior (channelId, spaceId, etc):

Expand All @@ -332,20 +370,6 @@ bot.hasAdminPermission(...) | bot.checkPermission(...) | bot.ban(...) | bot.unba
// Properties: bot.botId, bot.viem, bot.appAddress
```

**GitHub Integration Pattern:**

```typescript
let channelId = null;
bot.onSlashCommand("setup", async (h, e) => {
channelId = e.channelId;
});
app.post("/webhook", bot.start().jwtMiddleware, bot.start().handler);
app.post("/github", async c => {
if (channelId) await bot.sendMessage(channelId, `PR: ${c.req.json().title}`);
return c.json({ ok: true });
});
```

**Patterns:** Store channel IDs | Webhooks/timers | Call bot.\* directly | Handle errors

## Critical Notes
Expand Down
25 changes: 10 additions & 15 deletions CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -24,13 +24,8 @@ Thank you for your interest in contributing! This guide covers development setup
2. **Setup database**

```bash
# Start PostgreSQL with Docker
docker run --rm --name github-bot-db \
-e POSTGRES_USER=postgres \
-e POSTGRES_PASSWORD=postgres \
-e POSTGRES_DB=github-bot \
-p 5432:5432 \
postgres:18
bun db:up # Start PostgreSQL (data persists across restarts)
bun db:migrate # Run migrations
```

3. **Configure environment** (see Configuration section below)
Expand Down Expand Up @@ -65,14 +60,14 @@ Thank you for your interest in contributing! This guide covers development setup

#### GitHub App (Optional - Enables Real-Time Webhooks)

| Variable | Description | How to Get |
|---------------------------------| ------------------------------------------ | ---------------------------------------------------------- |
| `GITHUB_APP_ID` | GitHub App ID | GitHub App settings page |
| `GITHUB_APP_PRIVATE_KEY_BASE64` | Base64-encoded private key | Download `.pem`, encode: `base64 -i key.pem | tr -d '\n'` |
| `GITHUB_APP_CLIENT_ID` | OAuth client ID (format: `Iv1.abc123`) | GitHub App OAuth settings |
| `GITHUB_APP_CLIENT_SECRET` | OAuth client secret | GitHub App OAuth settings |
| `GITHUB_WEBHOOK_SECRET` | Webhook signature secret | Generate: `openssl rand -hex 32` |
| `GITHUB_APP_SLUG` | App URL slug (default: `towns-github-bot`) | Optional - for custom app names |
| Variable | Description | How to Get |
| ------------------------------- | ------------------------------------------ | ------------------------------------------- | ----------- |
| `GITHUB_APP_ID` | GitHub App ID | GitHub App settings page |
| `GITHUB_APP_PRIVATE_KEY_BASE64` | Base64-encoded private key | Download `.pem`, encode: `base64 -i key.pem | tr -d '\n'` |
| `GITHUB_APP_CLIENT_ID` | OAuth client ID (format: `Iv1.abc123`) | GitHub App OAuth settings |
| `GITHUB_APP_CLIENT_SECRET` | OAuth client secret | GitHub App OAuth settings |
| `GITHUB_WEBHOOK_SECRET` | Webhook signature secret | Generate: `openssl rand -hex 32` |
| `GITHUB_APP_SLUG` | App URL slug (default: `towns-github-bot`) | Optional - for custom app names |

#### Optional Configuration

Expand Down
17 changes: 10 additions & 7 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -161,15 +161,18 @@ All webhook events above except `workflow_run` (CI/CD) are also available via po

3. **Start Postgres locally for development**

You can run Postgres in Docker with a single command:
Start the database with:

```bash
docker run --rm --name github-bot-db \
-e POSTGRES_USER=postgres \
-e POSTGRES_PASSWORD=postgres \
-e POSTGRES_DB=github-bot \
-p 5432:5432 \
postgres:18
bun db:up
```

This creates a container with a named volume so your data persists across restarts.

To stop the database:

```bash
bun db:down
```

Then point your `.env` at the container:
Expand Down
Loading