A Matrix chat bot that creates GitHub issues from thread conversations using LLM summarization.
- Have a conversation in a Matrix thread
- Mention the bot with
@bot !issue [owner/repo] [title] - Bot fetches all thread messages
- Bot uses an LLM to summarize the thread into an issue body
- Bot creates a GitHub issue
- Bot reacts with a checkmark and replies with the issue link
@bot !issue # Use room default repo, auto-generate title
@bot !issue Fix login bug # Use room default repo, specified title
@bot !issue myorg/myrepo # Use specified repo, auto-generate title
@bot !issue myorg/myrepo Fix login # Use specified repo, specified title
/
├── packages/
│ ├── core/ # Interfaces, types, LLM summarizer
│ ├── matrix-adapter/ # Matrix messenger implementation
│ └── github-adapter/ # GitHub issue tracker implementation
├── apps/
│ └── matrix-github-bot/ # Main bot application
├── scripts/ # Utility scripts (login, reset)
├── Dockerfile
└── docker-compose.yml
Reusable packages for building chat-to-issue bots:
| Package | Description | Docs |
|---|---|---|
@operator/core |
Core interfaces (Messenger, IssueTracker, ThreadSummarizer), types, OpenRouter summarizer, and logging utilities |
README |
@operator/matrix-adapter |
Matrix chat platform adapter with E2EE support | README |
@operator/github-adapter |
GitHub issue tracker adapter | README |
Ready-to-deploy bots built with the packages above:
| App | Description | Platforms | Docs |
|---|---|---|---|
matrix-github-bot |
Creates GitHub issues from Matrix thread conversations | Matrix → GitHub | README |
See the Architecture section for diagrams and examples of how to create new adapters (e.g., Slack→Jira, Discord→Linear).
# Install dependencies
pnpm install
# Build all packages
pnpm build
# Copy and configure environment
cp .env.example .env
# Edit .env with your tokens
# Run the bot
pnpm devSee apps/matrix-github-bot/README.md for detailed setup instructions.
# Install dependencies
pnpm install
# Build all packages
pnpm build
# Run in development mode
pnpm dev
# Type check
pnpm typecheck
# Lint
pnpm lint
# Run tests
pnpm test
# Clean build artifacts
pnpm cleanThe bot uses Pino for structured logging. Configure the log level via the LOG_LEVEL environment variable:
LOG_LEVEL=info # DefaultAvailable log levels (from most to least verbose):
trace- Very detailed debuggingdebug- Debug messages (command parsing, message flow)info- General operational messages (startup, shutdown, issue creation)warn- Warnings (failed to fetch events, config issues)error- Errors (API failures, handler exceptions)fatal- Fatal errors causing shutdown
Development mode (NODE_ENV !== 'production'): Logs are pretty-printed with colors via pino-pretty.
Production mode: Logs are output as JSON for parsing by log aggregators.
Example - Enable debug logging:
LOG_LEVEL=debug pnpm dev| Script | Description |
|---|---|
pnpm dev |
Start the bot in development mode |
pnpm build |
Build all packages |
pnpm typecheck |
Run TypeScript type checking |
pnpm lint |
Run ESLint |
pnpm test |
Run tests |
pnpm clean |
Clean build artifacts |
| Script | Description |
|---|---|
pnpm matrix-login |
Interactive login to get a new access token |
pnpm reset-bot |
Soft reset - delete local crypto/storage, keep current token |
pnpm reset-bot:hard |
Hard reset - delete ALL devices (invalidates token) |
When to use reset scripts:
- E2EE key conflicts ("One time key already exists"): Run
pnpm reset-botfirst. If that doesn't work, runpnpm reset-bot:hardand thenpnpm matrix-loginto get a new token. - Starting fresh: Run
pnpm reset-bot:hardfollowed bypnpm matrix-login. - Switching accounts: Update credentials in
.env, then runpnpm reset-bot.
| Script | Description |
|---|---|
pnpm docker:build |
Build the Docker image |
pnpm docker:up |
Start the bot container |
pnpm docker:down |
Stop the bot container |
# Build the image
docker-compose build
# Start the bot
docker-compose up -d
# View logs
docker-compose logs -f
# Stop the bot
docker-compose downThe project uses a provider-agnostic design with interfaces that allow swapping implementations. This makes it easy to build similar bots for different platforms (e.g., Slack→Jira, Discord→Linear).
flowchart TB
subgraph Chat["Chat Platform"]
User([User])
Thread[Thread/Conversation]
end
subgraph Bot["Bot Application"]
Handler[Command Handler]
subgraph Interfaces["Core Interfaces"]
Messenger["«interface»\nMessenger"]
Summarizer["«interface»\nThreadSummarizer"]
Tracker["«interface»\nIssueTracker"]
end
end
subgraph Implementations["Adapters (Swappable)"]
subgraph MessengerImpls["Messenger Implementations"]
Matrix[MatrixMessenger]
Slack[SlackMessenger]
Discord[DiscordMessenger]
end
subgraph SummarizerImpls["Summarizer Implementations"]
OpenRouter[OpenRouterSummarizer]
OpenAI[OpenAISummarizer]
Local[LocalLLMSummarizer]
end
subgraph TrackerImpls["IssueTracker Implementations"]
GitHub[GitHubIssueTracker]
Jira[JiraIssueTracker]
Linear[LinearIssueTracker]
end
end
subgraph External["External Services"]
ChatAPI[(Chat API)]
LLM[(LLM API)]
IssueAPI[(Issue API)]
end
User -->|"@bot !issue"| Thread
Thread --> Handler
Handler --> Messenger
Handler --> Summarizer
Handler --> Tracker
Messenger -.->|implements| Matrix
Messenger -.->|implements| Slack
Messenger -.->|implements| Discord
Summarizer -.->|implements| OpenRouter
Summarizer -.->|implements| OpenAI
Summarizer -.->|implements| Local
Tracker -.->|implements| GitHub
Tracker -.->|implements| Jira
Tracker -.->|implements| Linear
Matrix --> ChatAPI
Slack --> ChatAPI
Discord --> ChatAPI
OpenRouter --> LLM
OpenAI --> LLM
Local --> LLM
GitHub --> IssueAPI
Jira --> IssueAPI
Linear --> IssueAPI
style Interfaces fill:#e1f5fe
style Matrix fill:#90caf9
style OpenRouter fill:#90caf9
style GitHub fill:#90caf9
style Slack fill:#f5f5f5,stroke-dasharray: 5 5
style Discord fill:#f5f5f5,stroke-dasharray: 5 5
style Jira fill:#f5f5f5,stroke-dasharray: 5 5
style Linear fill:#f5f5f5,stroke-dasharray: 5 5
style OpenAI fill:#f5f5f5,stroke-dasharray: 5 5
style Local fill:#f5f5f5,stroke-dasharray: 5 5
sequenceDiagram
participant U as User
participant M as Messenger
participant H as CommandHandler
participant S as ThreadSummarizer
participant I as IssueTracker
U->>M: @bot !issue owner/repo
M->>H: MentionEvent
H->>M: fetchThreadMessages()
M-->>H: Thread (messages[])
H->>M: getUserProfile(senderId)
M-->>H: UserProfile
H->>S: summarize(thread)
S-->>H: {title, body, labels}
H->>H: buildIssueBody(summary, reporter)
H->>I: createIssue({title, body, labels})
I-->>H: {success, issue}
H->>M: addReaction(✅)
H->>M: sendReply(issue.url)
M-->>U: "Issue created: url"
classDiagram
class Messenger {
<<interface>>
+start() Promise~void~
+stop() Promise~void~
+fetchThreadMessages(roomId, threadId) Promise~Thread~
+sendMessage(roomId, content, options?) Promise~string~
+sendReply(roomId, replyToId, content) Promise~string~
+addReaction(roomId, eventId, reaction) Promise~void~
+onMention(handler) void
+getUserProfile(userId) Promise~UserProfile~
+getMessageLink(roomId, eventId) string
}
class IssueTracker {
<<interface>>
+createIssue(options) Promise~IssueCreateResult~
+getIssue(owner, repo, number) Promise~Issue~
+updateIssue(owner, repo, number, updates) Promise~IssueCreateResult~
+addComment(owner, repo, number, body) Promise~void~
}
class ThreadSummarizer {
<<interface>>
+summarize(thread, options?) Promise~SummarizeResult~
}
class Thread {
+id string
+roomId string
+messages Message[]
}
class Message {
+id string
+senderId string
+senderName string
+content string
+timestamp Date
}
class SummarizeResult {
+title string
+body string
+suggestedLabels string[]
}
Messenger ..> Thread : returns
Thread *-- Message
ThreadSummarizer ..> SummarizeResult : returns
To add support for a new platform, implement the corresponding interface:
Example: Slack → Jira bot
-
Create
packages/slack-adapter/implementingMessenger:export class SlackMessenger implements Messenger { async fetchThreadMessages(channelId, threadTs) { /* ... */ } async sendReply(channelId, threadTs, content) { /* ... */ } // ... other methods }
-
Create
packages/jira-adapter/implementingIssueTracker:export class JiraIssueTracker implements IssueTracker { async createIssue(options) { /* ... */ } // ... other methods }
-
Create
apps/slack-jira-bot/wiring them together:const messenger = new SlackMessenger(config.slack); const issueTracker = new JiraIssueTracker(config.jira); const summarizer = new OpenRouterSummarizer(config.llm); const handler = new IssueCommandHandler({ messenger, issueTracker, summarizer, // ... });
The CommandHandler and ThreadSummarizer can be reused as-is.
- Runtime: Node.js 20+
- Language: TypeScript
- Package manager: pnpm workspaces
- Matrix SDK: matrix-bot-sdk
- Matrix E2EE: @matrix-org/matrix-sdk-crypto-nodejs
- GitHub SDK: @octokit/rest
- LLM: Vercel AI SDK + OpenRouter
- Deployment: Docker
- Create GitHub issues from Matrix thread conversations
- LLM-powered thread summarization
- Support for encrypted Matrix rooms (E2EE)
- Per-room configuration for default repos and labels
- Docker deployment with persistent storage
MIT