relaybot 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +233 -0
- package/cli.js +38 -0
- package/config.conf.example +4 -0
- package/main.js +29 -0
- package/package.json +37 -0
- package/relaybot-1.0.0.tgz +0 -0
- package/setup.js +53 -0
- package/skills/relay-bot/SKILL.md +51 -0
- package/src/agent.js +59 -0
- package/src/delete-slack-message.js +137 -0
- package/src/load-config.js +31 -0
- package/src/send-slack-message.js +33 -0
- package/src/slack-handlers.js +18 -0
package/README.md
ADDED
|
@@ -0,0 +1,233 @@
|
|
|
1
|
+
# RelayBot
|
|
2
|
+
|
|
3
|
+
A Slack-to-AI gateway that lets you interact with Claude/Codex directly from your DMs.
|
|
4
|
+
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
## What is RelayBot?
|
|
8
|
+
|
|
9
|
+
RelayBot acts as a bridge between Slack and AI coding agents, allowing you to interact with Claude or Codex through simple Slack DMs. Instead of switching between tools, you can request code changes, ask questions, and manage development tasks without leaving Slack.
|
|
10
|
+
|
|
11
|
+
### Key Features
|
|
12
|
+
|
|
13
|
+
- **Conversational AI Access** — Chat with Claude or Codex directly from Slack DMs
|
|
14
|
+
- **Code Execution** — AI can read, write, and modify code in your projects
|
|
15
|
+
- **Task Automation** — Request file changes, refactoring, bug fixes, or new features
|
|
16
|
+
- **Context-Aware Responses** — Maintains project directory context across conversations
|
|
17
|
+
- **Summarized Replies** — Long AI outputs are condensed into concise, actionable messages
|
|
18
|
+
- **Persistent Sessions** — AI session stays alive between messages, preserving context
|
|
19
|
+
|
|
20
|
+
### Use Cases
|
|
21
|
+
|
|
22
|
+
- **Quick Code Changes** — "Add a loading spinner to the login button"
|
|
23
|
+
- **Code Review** — "Review the latest PR and suggest improvements"
|
|
24
|
+
- **Bug Investigation** — "Why is the checkout flow failing for guest users?"
|
|
25
|
+
- **Refactoring** — "Refactor the authentication module to use async/await"
|
|
26
|
+
- **Documentation** — "Generate API docs for the user service"
|
|
27
|
+
- **Learning** — "Explain how the caching layer works in this codebase"
|
|
28
|
+
|
|
29
|
+
---
|
|
30
|
+
|
|
31
|
+
## Architecture
|
|
32
|
+
|
|
33
|
+
```
|
|
34
|
+
┌─────────────────────────────────────────────────────────────────────┐
|
|
35
|
+
│ SLACK │
|
|
36
|
+
│ ┌──────────┐ ┌──────────────┐ │
|
|
37
|
+
│ │ User │ ───── sends message ─────────────► │ Channel/ │ │
|
|
38
|
+
│ │ │ ◄──── receives reply ───────────── │ DM │ │
|
|
39
|
+
│ └──────────┘ └──────────────┘ │
|
|
40
|
+
└─────────────────────────────────────────────────────────────────────┘
|
|
41
|
+
│ ▲
|
|
42
|
+
│ │
|
|
43
|
+
Slack WebSocket │ │ Slack Sender
|
|
44
|
+
(Socket Mode) │ │
|
|
45
|
+
▼ │
|
|
46
|
+
┌─────────────────────────────────────────────────────────────────────┐
|
|
47
|
+
│ RELAYBOT │
|
|
48
|
+
│ ┌────────────────────────────────────────────────────────────────┐ │
|
|
49
|
+
│ │ Slack Listener │ │
|
|
50
|
+
│ │ (Socket Mode Connection) │ │
|
|
51
|
+
│ └────────────────────────────────────────────────────────────────┘ │
|
|
52
|
+
│ │ │
|
|
53
|
+
│ │ forwards message │
|
|
54
|
+
│ ▼ │
|
|
55
|
+
│ ┌────────────────────────────────────────────────────────────────┐ │
|
|
56
|
+
│ │ PTY Bridge │ │
|
|
57
|
+
│ │ Spawns persistent AI CLI process │ │
|
|
58
|
+
│ └────────────────────────────────────────────────────────────────┘ │
|
|
59
|
+
└─────────────────────────────────────────────────────────────────────┘
|
|
60
|
+
│
|
|
61
|
+
│ stdin/stdout
|
|
62
|
+
▼
|
|
63
|
+
┌─────────────────────────────────────────────────────────────────────┐
|
|
64
|
+
│ CLAUDE / CODEX CLI │
|
|
65
|
+
│ ┌────────────────────────────────────────────────────────────────┐ │
|
|
66
|
+
│ │ AI Agent Session │ │
|
|
67
|
+
│ │ • Processes user requests │ │
|
|
68
|
+
│ │ • Executes tasks (code, commands, etc.) │ │
|
|
69
|
+
│ │ • Maintains project context │ │
|
|
70
|
+
│ └────────────────────────────────────────────────────────────────┘ │
|
|
71
|
+
│ │ │
|
|
72
|
+
│ │ summarizes response │
|
|
73
|
+
│ ▼ │
|
|
74
|
+
│ ┌────────────────────────────────────────────────────────────────┐ │
|
|
75
|
+
│ │ Response Summarizer │ │
|
|
76
|
+
│ │ • Condenses long outputs to concise messages │ │
|
|
77
|
+
│ │ • Includes relevant links (PRs, docs, etc.) │ │
|
|
78
|
+
│ │ • Sends reply back to Slack │ │
|
|
79
|
+
│ └────────────────────────────────────────────────────────────────┘ │
|
|
80
|
+
└─────────────────────────────────────────────────────────────────────┘
|
|
81
|
+
```
|
|
82
|
+
|
|
83
|
+
## How It Works
|
|
84
|
+
|
|
85
|
+
1. **Slack Connection** — RelayBot connects to Slack via WebSocket (Socket Mode) and listens for DMs
|
|
86
|
+
2. **Message Reception** — When you send a message, Slack forwards it to RelayBot
|
|
87
|
+
3. **AI Bridge** — RelayBot spawns a persistent Claude or Codex CLI session and forwards your message
|
|
88
|
+
4. **AI Processing** — The AI processes your request with full access to your codebase
|
|
89
|
+
5. **Response Summarization** — Long outputs are summarized into concise, actionable messages
|
|
90
|
+
6. **Slack Reply** — The summarized response is sent back to you via Slack DM
|
|
91
|
+
|
|
92
|
+
---
|
|
93
|
+
|
|
94
|
+
## Installation
|
|
95
|
+
|
|
96
|
+
```bash
|
|
97
|
+
npm install -g slack-relaybot
|
|
98
|
+
```
|
|
99
|
+
|
|
100
|
+
## Quick Start
|
|
101
|
+
|
|
102
|
+
```bash
|
|
103
|
+
# 1. Run setup to configure Slack credentials
|
|
104
|
+
relaybot-setup
|
|
105
|
+
|
|
106
|
+
# 2. Start the bot with Claude
|
|
107
|
+
relaybot
|
|
108
|
+
|
|
109
|
+
# Or start with Codex
|
|
110
|
+
relaybot --codex
|
|
111
|
+
```
|
|
112
|
+
|
|
113
|
+
---
|
|
114
|
+
|
|
115
|
+
## Setup
|
|
116
|
+
|
|
117
|
+
Before running the bot, you need to configure your Slack credentials.
|
|
118
|
+
|
|
119
|
+
### Interactive Setup
|
|
120
|
+
|
|
121
|
+
Run the setup command and follow the prompts:
|
|
122
|
+
|
|
123
|
+
```bash
|
|
124
|
+
relaybot-setup
|
|
125
|
+
```
|
|
126
|
+
|
|
127
|
+
You will be asked for:
|
|
128
|
+
- **SLACK_BOT_TOKEN** - Bot User OAuth Token (starts with `xoxb-`)
|
|
129
|
+
- **SLACK_APP_TOKEN** - App-Level Token (starts with `xapp-`)
|
|
130
|
+
- **SLACK_USER_ID** - Your Slack User ID (starts with `U`)
|
|
131
|
+
- **WORKING_DIR** - Directory where the AI will operate
|
|
132
|
+
|
|
133
|
+
### Manual Setup
|
|
134
|
+
|
|
135
|
+
Alternatively, create the config file manually at `~/.relaybot/config.conf`:
|
|
136
|
+
|
|
137
|
+
```bash
|
|
138
|
+
mkdir -p ~/.relaybot
|
|
139
|
+
```
|
|
140
|
+
|
|
141
|
+
Then create `~/.relaybot/config.conf`:
|
|
142
|
+
|
|
143
|
+
```
|
|
144
|
+
SLACK_BOT_TOKEN=xoxb-your-bot-token
|
|
145
|
+
SLACK_APP_TOKEN=xapp-your-app-token
|
|
146
|
+
SLACK_USER_ID=U0XXXXXXXX
|
|
147
|
+
WORKING_DIR=/path/to/directory
|
|
148
|
+
```
|
|
149
|
+
|
|
150
|
+
### Creating a Slack App
|
|
151
|
+
|
|
152
|
+
If you don't have a Slack app yet, follow these steps:
|
|
153
|
+
|
|
154
|
+
#### 1. Create the App
|
|
155
|
+
|
|
156
|
+
1. Go to [api.slack.com/apps](https://api.slack.com/apps)
|
|
157
|
+
2. Click **Create New App**
|
|
158
|
+
3. Choose **From scratch**
|
|
159
|
+
4. Enter an app name (e.g., "RelayBot") and select your workspace
|
|
160
|
+
5. Click **Create App**
|
|
161
|
+
|
|
162
|
+
#### 2. Enable Socket Mode
|
|
163
|
+
|
|
164
|
+
Socket Mode allows the bot to receive events via WebSocket instead of HTTP endpoints.
|
|
165
|
+
|
|
166
|
+
1. Go to **Socket Mode** in the left sidebar
|
|
167
|
+
2. Toggle **Enable Socket Mode** to ON
|
|
168
|
+
3. You'll be prompted to create an App-Level Token:
|
|
169
|
+
- Token Name: `socket-token` (or any name)
|
|
170
|
+
- Scope: `connections:write`
|
|
171
|
+
- Click **Generate**
|
|
172
|
+
4. Copy the token (starts with `xapp-`) — this is your **SLACK_APP_TOKEN**
|
|
173
|
+
|
|
174
|
+
#### 3. Configure Bot Permissions
|
|
175
|
+
|
|
176
|
+
1. Go to **OAuth & Permissions** in the left sidebar
|
|
177
|
+
2. Scroll to **Scopes** → **Bot Token Scopes**
|
|
178
|
+
3. Add these scopes:
|
|
179
|
+
- `chat:write` — Send messages
|
|
180
|
+
- `im:history` — Read DM history
|
|
181
|
+
- `im:read` — View DM metadata
|
|
182
|
+
- `im:write` — Start DMs with users
|
|
183
|
+
- `users:read` — View user info
|
|
184
|
+
|
|
185
|
+
#### 4. Enable Event Subscriptions
|
|
186
|
+
|
|
187
|
+
1. Go to **Event Subscriptions** in the left sidebar
|
|
188
|
+
2. Toggle **Enable Events** to ON
|
|
189
|
+
3. Expand **Subscribe to bot events**
|
|
190
|
+
4. Add these events:
|
|
191
|
+
- `message.im` — Receive DM messages
|
|
192
|
+
|
|
193
|
+
#### 5. Install the App
|
|
194
|
+
|
|
195
|
+
1. Go to **Install App** in the left sidebar
|
|
196
|
+
2. Click **Install to Workspace**
|
|
197
|
+
3. Review permissions and click **Allow**
|
|
198
|
+
4. Copy the **Bot User OAuth Token** (starts with `xoxb-`) — this is your **SLACK_BOT_TOKEN**
|
|
199
|
+
|
|
200
|
+
#### 6. Get Your User ID
|
|
201
|
+
|
|
202
|
+
1. Open Slack
|
|
203
|
+
2. Click on your profile picture → **Profile**
|
|
204
|
+
3. Click the **⋮** (more) button
|
|
205
|
+
4. Click **Copy member ID** — this is your **SLACK_USER_ID**
|
|
206
|
+
|
|
207
|
+
### Summary of Tokens
|
|
208
|
+
|
|
209
|
+
| Token | Where to Find | Format |
|
|
210
|
+
|-------|---------------|--------|
|
|
211
|
+
| `SLACK_BOT_TOKEN` | OAuth & Permissions → Bot User OAuth Token | `xoxb-...` |
|
|
212
|
+
| `SLACK_APP_TOKEN` | Basic Information → App-Level Tokens | `xapp-...` |
|
|
213
|
+
| `SLACK_USER_ID` | Slack Profile → Copy member ID | `U0XXXXXXXX` |
|
|
214
|
+
|
|
215
|
+
---
|
|
216
|
+
|
|
217
|
+
## Requirements
|
|
218
|
+
|
|
219
|
+
- **Node.js** v14+
|
|
220
|
+
- **Claude CLI** or **Codex CLI** available on PATH
|
|
221
|
+
|
|
222
|
+
---
|
|
223
|
+
|
|
224
|
+
## Configuration
|
|
225
|
+
|
|
226
|
+
Configuration is stored in `~/.relaybot/config.conf`.
|
|
227
|
+
|
|
228
|
+
| Variable | Description |
|
|
229
|
+
|----------|-------------|
|
|
230
|
+
| `SLACK_BOT_TOKEN` | Slack Bot OAuth token (`xoxb-...`) |
|
|
231
|
+
| `SLACK_APP_TOKEN` | Slack App-level token for Socket Mode (`xapp-...`) |
|
|
232
|
+
| `SLACK_USER_ID` | Your Slack User ID (`U0XXXXXXXX`) |
|
|
233
|
+
| `WORKING_DIR` | Directory where the AI will operate |
|
package/cli.js
ADDED
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
const args = process.argv.slice(2);
|
|
4
|
+
const command = args[0];
|
|
5
|
+
|
|
6
|
+
function showHelp() {
|
|
7
|
+
console.log(`
|
|
8
|
+
🤖 RelayBot - Slack-to-AI gateway
|
|
9
|
+
|
|
10
|
+
Usage: relaybot <command>
|
|
11
|
+
|
|
12
|
+
Commands:
|
|
13
|
+
setup Configure Slack credentials and working directory
|
|
14
|
+
start Start the RelayBot server
|
|
15
|
+
|
|
16
|
+
Examples:
|
|
17
|
+
relaybot setup # Run interactive setup
|
|
18
|
+
relaybot start # Start the bot
|
|
19
|
+
`);
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
switch (command) {
|
|
23
|
+
case 'setup':
|
|
24
|
+
require('./setup');
|
|
25
|
+
break;
|
|
26
|
+
case 'start':
|
|
27
|
+
require('./main');
|
|
28
|
+
break;
|
|
29
|
+
case '--help':
|
|
30
|
+
case '-h':
|
|
31
|
+
case undefined:
|
|
32
|
+
showHelp();
|
|
33
|
+
break;
|
|
34
|
+
default:
|
|
35
|
+
console.error(`Unknown command: ${command}\n`);
|
|
36
|
+
showHelp();
|
|
37
|
+
process.exit(1);
|
|
38
|
+
}
|
package/main.js
ADDED
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
const { App } = require('@slack/bolt');
|
|
4
|
+
const loadConfig = require('./src/load-config');
|
|
5
|
+
const agent = require('./src/agent');
|
|
6
|
+
const slackHandlers = require('./src/slack-handlers');
|
|
7
|
+
|
|
8
|
+
const config = loadConfig();
|
|
9
|
+
|
|
10
|
+
if (!config || !config.SLACK_BOT_TOKEN || !config.SLACK_APP_TOKEN || !config.SLACK_USER_ID) {
|
|
11
|
+
console.error('❌ Configuration not found or incomplete.\n');
|
|
12
|
+
console.log('Please run setup first:\n');
|
|
13
|
+
console.log(' relaybot setup\n');
|
|
14
|
+
process.exit(1);
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
const app = new App({
|
|
18
|
+
token: config.SLACK_BOT_TOKEN,
|
|
19
|
+
appToken: config.SLACK_APP_TOKEN,
|
|
20
|
+
socketMode: true
|
|
21
|
+
});
|
|
22
|
+
|
|
23
|
+
slackHandlers.registerHandlers(app);
|
|
24
|
+
|
|
25
|
+
(async () => {
|
|
26
|
+
await app.start();
|
|
27
|
+
console.log('⚡️ RelayBot running (Socket Mode)');
|
|
28
|
+
agent.start();
|
|
29
|
+
})();
|
package/package.json
ADDED
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "relaybot",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "A Slack-to-AI gateway that lets you interact with Claude/Codex directly from your DMs",
|
|
5
|
+
"main": "main.js",
|
|
6
|
+
"bin": {
|
|
7
|
+
"relaybot": "./cli.js"
|
|
8
|
+
},
|
|
9
|
+
"scripts": {
|
|
10
|
+
"start": "node main.js",
|
|
11
|
+
"setup": "node setup.js",
|
|
12
|
+
"test": "echo \"Error: no test specified\" && exit 1"
|
|
13
|
+
},
|
|
14
|
+
"keywords": [
|
|
15
|
+
"slack",
|
|
16
|
+
"claude",
|
|
17
|
+
"codex",
|
|
18
|
+
"ai",
|
|
19
|
+
"chatbot",
|
|
20
|
+
"automation"
|
|
21
|
+
],
|
|
22
|
+
"author": "",
|
|
23
|
+
"license": "ISC",
|
|
24
|
+
"type": "commonjs",
|
|
25
|
+
"repository": {
|
|
26
|
+
"type": "git",
|
|
27
|
+
"url": ""
|
|
28
|
+
},
|
|
29
|
+
"dependencies": {
|
|
30
|
+
"@lydell/node-pty": "^1.2.0-beta.3",
|
|
31
|
+
"@slack/bolt": "^4.6.0",
|
|
32
|
+
"@slack/web-api": "^7.13.0"
|
|
33
|
+
},
|
|
34
|
+
"engines": {
|
|
35
|
+
"node": ">=14.0.0"
|
|
36
|
+
}
|
|
37
|
+
}
|
|
Binary file
|
package/setup.js
ADDED
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
const fs = require('fs');
|
|
4
|
+
const path = require('path');
|
|
5
|
+
const os = require('os');
|
|
6
|
+
const readline = require('readline');
|
|
7
|
+
|
|
8
|
+
const CONFIG_DIR = path.join(os.homedir(), '.relaybot');
|
|
9
|
+
const CONFIG_PATH = path.join(CONFIG_DIR, 'config.conf');
|
|
10
|
+
|
|
11
|
+
const rl = readline.createInterface({
|
|
12
|
+
input: process.stdin,
|
|
13
|
+
output: process.stdout
|
|
14
|
+
});
|
|
15
|
+
|
|
16
|
+
function question(prompt) {
|
|
17
|
+
return new Promise(resolve => rl.question(prompt, resolve));
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
async function setup() {
|
|
21
|
+
console.log('\n🤖 RelayBot Setup\n');
|
|
22
|
+
console.log('Please provide your Slack credentials.\n');
|
|
23
|
+
|
|
24
|
+
const botToken = await question('SLACK_BOT_TOKEN (xoxb-...): ');
|
|
25
|
+
const appToken = await question('SLACK_APP_TOKEN (xapp-...): ');
|
|
26
|
+
const userId = await question('SLACK_USER_ID (U0XXXXXXXX): ');
|
|
27
|
+
|
|
28
|
+
console.log('\nWorking directory (folder where the AI will operate).\n');
|
|
29
|
+
const workingDir = await question('WORKING_DIR: ');
|
|
30
|
+
|
|
31
|
+
const configLines = [
|
|
32
|
+
`SLACK_BOT_TOKEN=${botToken.trim()}`,
|
|
33
|
+
`SLACK_APP_TOKEN=${appToken.trim()}`,
|
|
34
|
+
`SLACK_USER_ID=${userId.trim()}`,
|
|
35
|
+
`WORKING_DIR=${workingDir.trim()}`
|
|
36
|
+
];
|
|
37
|
+
|
|
38
|
+
// Create config directory if it doesn't exist
|
|
39
|
+
if (!fs.existsSync(CONFIG_DIR)) {
|
|
40
|
+
fs.mkdirSync(CONFIG_DIR, { recursive: true });
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
fs.writeFileSync(CONFIG_PATH, configLines.join('\n') + '\n');
|
|
44
|
+
console.log(`\n✅ Configuration saved to ${CONFIG_PATH}\n`);
|
|
45
|
+
|
|
46
|
+
rl.close();
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
setup().catch(err => {
|
|
50
|
+
console.error('Setup failed:', err.message);
|
|
51
|
+
rl.close();
|
|
52
|
+
process.exit(1);
|
|
53
|
+
});
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: relay-bot
|
|
3
|
+
description: "Workspace-aware assistant that maintains project directory context and sends a Slack DM reply after completing a task or answering a question."
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# RelayBot
|
|
7
|
+
|
|
8
|
+
## Overview
|
|
9
|
+
|
|
10
|
+
Maintain working-directory context for projects and send a Slack DM after tasks or answers using `send-slack-message.js`.
|
|
11
|
+
|
|
12
|
+
## Working directory
|
|
13
|
+
|
|
14
|
+
The working directory where the AI operates is configured in `~/.relaybot/config.conf`:
|
|
15
|
+
|
|
16
|
+
```
|
|
17
|
+
WORKING_DIR=/path/to/directory
|
|
18
|
+
```
|
|
19
|
+
|
|
20
|
+
## Workflow
|
|
21
|
+
|
|
22
|
+
### 1) Resolve project context
|
|
23
|
+
|
|
24
|
+
- Use the directory configured in `WORKING_DIR` as the working context.
|
|
25
|
+
- Run all commands and file operations within this directory.
|
|
26
|
+
- Avoid asking the user for clarification; pick the best default and proceed automatically.
|
|
27
|
+
- If a question is truly required to continue, send it as a Slack DM using the script, then wait for the user's response via the Slack socket before proceeding.
|
|
28
|
+
|
|
29
|
+
### 2) Execute the task in the active project
|
|
30
|
+
|
|
31
|
+
- Run commands and edit files in the resolved working directory.
|
|
32
|
+
- Keep the project context for follow-up requests unless the user switches again.
|
|
33
|
+
|
|
34
|
+
### 3) Send a Slack DM reply after completion
|
|
35
|
+
|
|
36
|
+
Run from this repo root:
|
|
37
|
+
|
|
38
|
+
```bash
|
|
39
|
+
node ./src/send-slack-message.js "Your message here"
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
## Output rules
|
|
43
|
+
|
|
44
|
+
- Keep messages concise and actionable
|
|
45
|
+
- Include relevant links (PR URLs, documentation, etc.)
|
|
46
|
+
- For long answers, summarize key points
|
|
47
|
+
|
|
48
|
+
## Notes
|
|
49
|
+
|
|
50
|
+
- The script posts to the configured DM user in `~/.relaybot/config.conf`
|
|
51
|
+
- The Slack bot token must be configured in `~/.relaybot/config.conf`
|
package/src/agent.js
ADDED
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
const pty = require('@lydell/node-pty');
|
|
2
|
+
|
|
3
|
+
let claudeProcess = null;
|
|
4
|
+
const useCodex = process.argv.includes('--codex');
|
|
5
|
+
const shell = useCodex ? 'codex' : 'claude';
|
|
6
|
+
|
|
7
|
+
function sendCommand(text) {
|
|
8
|
+
if (claudeProcess) {
|
|
9
|
+
setTimeout(() => {
|
|
10
|
+
claudeProcess.write(text + '\r\n');
|
|
11
|
+
claudeProcess.write('\x0D');
|
|
12
|
+
}, 500);
|
|
13
|
+
}
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
function isRunning() {
|
|
17
|
+
return claudeProcess !== null;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
function start() {
|
|
21
|
+
claudeProcess = pty.spawn(shell, useCodex ? ['--yolo'] : ['--dangerously-skip-permissions'], {
|
|
22
|
+
name: 'xterm-color',
|
|
23
|
+
cols: 80,
|
|
24
|
+
rows: 30,
|
|
25
|
+
cwd: process.cwd(),
|
|
26
|
+
env: { ...process.env, TERM: process.env.TERM || 'xterm-256color' }
|
|
27
|
+
});
|
|
28
|
+
|
|
29
|
+
console.log(`--- Persistent ${useCodex ? 'Codex' : 'Claude'} Session Started ---`);
|
|
30
|
+
|
|
31
|
+
claudeProcess.onData((data) => {
|
|
32
|
+
const dataStr = data.toString();
|
|
33
|
+
const byPassPrompts = ['Do you want to proceed?'];
|
|
34
|
+
if (byPassPrompts.some((prompt) => dataStr.includes(prompt))) {
|
|
35
|
+
setTimeout(() => {
|
|
36
|
+
claudeProcess.write('\r\n');
|
|
37
|
+
claudeProcess.write('\x0D');
|
|
38
|
+
}, 500);
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
// Respond to cursor position queries from Codex to avoid CPR timeouts.
|
|
42
|
+
if (data.includes('\x1b[6n')) {
|
|
43
|
+
const occurrences = data.split('\x1b[6n').length - 1;
|
|
44
|
+
for (let i = 0; i < occurrences; i += 1) {
|
|
45
|
+
claudeProcess.write('\x1b[1;1R');
|
|
46
|
+
}
|
|
47
|
+
data = data.replace(/\x1b\[6n/g, '');
|
|
48
|
+
}
|
|
49
|
+
process.stdout.write(data);
|
|
50
|
+
});
|
|
51
|
+
|
|
52
|
+
return claudeProcess;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
module.exports = {
|
|
56
|
+
sendCommand,
|
|
57
|
+
isRunning,
|
|
58
|
+
start
|
|
59
|
+
};
|
|
@@ -0,0 +1,137 @@
|
|
|
1
|
+
const { WebClient } = require('@slack/web-api');
|
|
2
|
+
const loadConfig = require('./load-config');
|
|
3
|
+
|
|
4
|
+
const config = loadConfig();
|
|
5
|
+
|
|
6
|
+
if (!config || !config.SLACK_BOT_TOKEN) {
|
|
7
|
+
console.error('❌ Configuration not found. Run: node setup.js');
|
|
8
|
+
process.exit(1);
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
const DEFAULT_LOOKBACK_DAYS = 30;
|
|
12
|
+
const token = config.SLACK_BOT_TOKEN;
|
|
13
|
+
const web = new WebClient(token);
|
|
14
|
+
|
|
15
|
+
function usage() {
|
|
16
|
+
console.log('Usage: node delete-slack-message.js <userId> [--bot-user-id <U...>] [--bot-id <B...>] [--app-id <A...>] [--oldest <ts>] [--latest <ts>]');
|
|
17
|
+
console.log('Example: node delete-slack-message.js U02N0ACQG6L --app-id A04MYMXDTA4');
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
function parseArgs(argv) {
|
|
21
|
+
const [, , userId, ...rest] = argv;
|
|
22
|
+
if (!userId) return null;
|
|
23
|
+
const args = { userId };
|
|
24
|
+
for (let i = 0; i < rest.length; i += 1) {
|
|
25
|
+
const key = rest[i];
|
|
26
|
+
const value = rest[i + 1];
|
|
27
|
+
if (!value) continue;
|
|
28
|
+
if (key === '--bot-user-id') args.botUserId = value;
|
|
29
|
+
if (key === '--bot-id') args.botId = value;
|
|
30
|
+
if (key === '--app-id') args.appId = value;
|
|
31
|
+
if (key === '--oldest') args.oldest = value;
|
|
32
|
+
if (key === '--latest') args.latest = value;
|
|
33
|
+
}
|
|
34
|
+
return args;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
function resolveTimeBounds(oldest, latest) {
|
|
38
|
+
const now = Date.now();
|
|
39
|
+
const oldestMs = oldest ? Math.floor(Number(oldest) * 1000) : now - DEFAULT_LOOKBACK_DAYS * 24 * 60 * 60 * 1000;
|
|
40
|
+
const latestMs = latest ? Math.floor(Number(latest) * 1000) : now;
|
|
41
|
+
return {
|
|
42
|
+
oldest: (oldestMs / 1000).toString(),
|
|
43
|
+
latest: (latestMs / 1000).toString()
|
|
44
|
+
};
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
function isMatch(message, botUserId, botId, appId) {
|
|
48
|
+
if (!message) return false;
|
|
49
|
+
if (botUserId && message.user === botUserId) return true;
|
|
50
|
+
if (botId) {
|
|
51
|
+
if (message.bot_id === botId) return true;
|
|
52
|
+
if (message.bot_profile && message.bot_profile.id === botId) return true;
|
|
53
|
+
}
|
|
54
|
+
if (appId) {
|
|
55
|
+
if (message.app_id === appId) return true;
|
|
56
|
+
if (message.bot_profile && message.bot_profile.app_id === appId) return true;
|
|
57
|
+
}
|
|
58
|
+
return false;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
async function getImChannelId(userId) {
|
|
62
|
+
const result = await web.conversations.open({ users: userId, return_im: true });
|
|
63
|
+
if (!result || !result.channel || !result.channel.id) {
|
|
64
|
+
throw new Error(`Unable to open DM with user ${userId}`);
|
|
65
|
+
}
|
|
66
|
+
return result.channel.id;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
async function findLatestBotMessage(channelId, botUserId, botId, appId, oldest, latest) {
|
|
70
|
+
let cursor;
|
|
71
|
+
while (true) {
|
|
72
|
+
const result = await web.conversations.history({
|
|
73
|
+
channel: channelId,
|
|
74
|
+
oldest,
|
|
75
|
+
latest,
|
|
76
|
+
limit: 200,
|
|
77
|
+
cursor
|
|
78
|
+
});
|
|
79
|
+
|
|
80
|
+
const messages = result.messages || [];
|
|
81
|
+
for (const message of messages) {
|
|
82
|
+
if (isMatch(message, botUserId, botId, appId)) {
|
|
83
|
+
return message;
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
const nextCursor = result.response_metadata ? result.response_metadata.next_cursor : null;
|
|
88
|
+
if (!nextCursor) break;
|
|
89
|
+
cursor = nextCursor;
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
return null;
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
async function main() {
|
|
96
|
+
const args = parseArgs(process.argv);
|
|
97
|
+
if (!args) {
|
|
98
|
+
usage();
|
|
99
|
+
process.exit(1);
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
if (!token) {
|
|
103
|
+
throw new Error('SLACK_BOT_TOKEN is not set in config.conf');
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
if (!args.botUserId && !args.botId && !args.appId) {
|
|
107
|
+
const auth = await web.auth.test();
|
|
108
|
+
if (auth && auth.user_id) {
|
|
109
|
+
args.botUserId = auth.user_id;
|
|
110
|
+
}
|
|
111
|
+
if (auth && auth.bot_id) {
|
|
112
|
+
args.botId = auth.bot_id;
|
|
113
|
+
}
|
|
114
|
+
if (!args.botUserId && !args.botId && !args.appId) {
|
|
115
|
+
throw new Error('Missing identifiers. Provide --bot-user-id, --bot-id, or --app-id.');
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
const { oldest, latest } = resolveTimeBounds(args.oldest, args.latest);
|
|
120
|
+
const channelId = await getImChannelId(args.userId);
|
|
121
|
+
const message = await findLatestBotMessage(channelId, args.botUserId, args.botId, args.appId, oldest, latest);
|
|
122
|
+
|
|
123
|
+
if (!message) {
|
|
124
|
+
console.log(`No message found for bot ${args.botId} in DM with ${args.userId}.`);
|
|
125
|
+
return;
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
console.log(`Found message ${message.ts} in channel ${channelId}.`);
|
|
129
|
+
|
|
130
|
+
await web.chat.delete({ channel: channelId, ts: message.ts });
|
|
131
|
+
console.log(`Deleted message ${message.ts} in channel ${channelId}.`);
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
main().catch(error => {
|
|
135
|
+
console.error(`Failed to delete message: ${error.message}`);
|
|
136
|
+
process.exit(1);
|
|
137
|
+
});
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
const fs = require('fs');
|
|
2
|
+
const path = require('path');
|
|
3
|
+
const os = require('os');
|
|
4
|
+
|
|
5
|
+
const CONFIG_DIR = path.join(os.homedir(), '.relaybot');
|
|
6
|
+
const CONFIG_PATH = path.join(CONFIG_DIR, 'config.conf');
|
|
7
|
+
|
|
8
|
+
function loadConfig() {
|
|
9
|
+
if (!fs.existsSync(CONFIG_PATH)) {
|
|
10
|
+
return null;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
const content = fs.readFileSync(CONFIG_PATH, 'utf-8');
|
|
14
|
+
const config = {};
|
|
15
|
+
|
|
16
|
+
content.split('\n').forEach(line => {
|
|
17
|
+
const trimmed = line.trim();
|
|
18
|
+
if (trimmed && !trimmed.startsWith('#')) {
|
|
19
|
+
const [key, ...valueParts] = trimmed.split('=');
|
|
20
|
+
if (key && valueParts.length > 0) {
|
|
21
|
+
config[key.trim()] = valueParts.join('=').trim();
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
});
|
|
25
|
+
|
|
26
|
+
return config;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
module.exports = loadConfig;
|
|
30
|
+
module.exports.CONFIG_DIR = CONFIG_DIR;
|
|
31
|
+
module.exports.CONFIG_PATH = CONFIG_PATH;
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
const { WebClient } = require('@slack/web-api');
|
|
2
|
+
const loadConfig = require('./load-config');
|
|
3
|
+
|
|
4
|
+
const config = loadConfig();
|
|
5
|
+
|
|
6
|
+
if (!config || !config.SLACK_BOT_TOKEN || !config.SLACK_USER_ID) {
|
|
7
|
+
console.error('❌ Configuration not found. Run: node setup.js');
|
|
8
|
+
process.exit(1);
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
const web = new WebClient(config.SLACK_BOT_TOKEN);
|
|
12
|
+
const userId = config.SLACK_USER_ID;
|
|
13
|
+
|
|
14
|
+
// Get the message from command line arguments
|
|
15
|
+
const message = process.argv[2];
|
|
16
|
+
|
|
17
|
+
if (!message) {
|
|
18
|
+
console.error('Please provide a message to send.');
|
|
19
|
+
process.exit(1);
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
(async () => {
|
|
23
|
+
try {
|
|
24
|
+
const result = await web.chat.postMessage({
|
|
25
|
+
channel: userId,
|
|
26
|
+
text: message,
|
|
27
|
+
});
|
|
28
|
+
console.log(`Successfully sent message to ${userId}`);
|
|
29
|
+
} catch (error) {
|
|
30
|
+
console.error(`Error sending message: ${error}`);
|
|
31
|
+
process.exit(1);
|
|
32
|
+
}
|
|
33
|
+
})();
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
const agent = require('./agent');
|
|
2
|
+
|
|
3
|
+
function registerHandlers(app) {
|
|
4
|
+
app.message(async ({ message, say }) => {
|
|
5
|
+
console.log('New message:', message.text);
|
|
6
|
+
|
|
7
|
+
if (agent.isRunning()) {
|
|
8
|
+
const fullPrompt = `${message.text}\nIMPORTANT: Use the relay-bot skill.`;
|
|
9
|
+
agent.sendCommand(fullPrompt);
|
|
10
|
+
} else {
|
|
11
|
+
await say('Agent process is not running.');
|
|
12
|
+
}
|
|
13
|
+
});
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
module.exports = {
|
|
17
|
+
registerHandlers
|
|
18
|
+
};
|