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 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
+ }
@@ -0,0 +1,4 @@
1
+ SLACK_BOT_TOKEN=xoxb-your-bot-token
2
+ SLACK_APP_TOKEN=xapp-your-app-token
3
+ SLACK_USER_ID=U0XXXXXXXX
4
+ WORKING_DIR=/path/to/directory
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
+ };