relaybot 1.0.5 → 1.0.7

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 CHANGED
@@ -1,6 +1,6 @@
1
1
  # RelayBot
2
2
 
3
- An AI assistant that lives in your Slack DMs — powered by Claude or Codex.
3
+ An AI assistant that lives in your Slack channels and DMs — powered by Claude or Codex.
4
4
 
5
5
  <img src="demo/demo.jpg" width="30%">
6
6
 
@@ -8,11 +8,11 @@ An AI assistant that lives in your Slack DMs — powered by Claude or Codex.
8
8
 
9
9
  ## What is RelayBot?
10
10
 
11
- 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.
11
+ RelayBot acts as a bridge between Slack and AI coding agents, allowing you to interact with Claude or Codex by mentioning the bot in channels or sending a DM. Instead of switching between tools, you can request code changes, ask questions, and manage development tasks without leaving Slack.
12
12
 
13
13
  ### Key Features
14
14
 
15
- - **Conversational AI Access** — Chat with Claude or Codex directly from Slack DMs
15
+ - **Conversational AI Access** — Chat with Claude or Codex from Slack channels via mentions or via DM
16
16
  - **Code Execution** — AI can read, write, and modify code in your projects
17
17
  - **Task Automation** — Request file changes, refactoring, bug fixes, or new features
18
18
  - **Context-Aware Responses** — Maintains project directory context across conversations
@@ -37,7 +37,7 @@ RelayBot acts as a bridge between Slack and AI coding agents, allowing you to in
37
37
  │ SLACK │
38
38
  │ ┌──────────┐ ┌──────────────┐ │
39
39
  │ │ User │ ───── sends message ─────────────► │ Channel/ │ │
40
- │ │ │ ◄──── receives reply ───────────── │ DM │ │
40
+ │ │ │ ◄──── receives reply ───────────── │ Channel │ │
41
41
  │ └──────────┘ └──────────────┘ │
42
42
  └─────────────────────────────────────────────────────────────────────┘
43
43
  │ ▲
@@ -84,12 +84,34 @@ RelayBot acts as a bridge between Slack and AI coding agents, allowing you to in
84
84
 
85
85
  ## How It Works
86
86
 
87
- 1. **Slack Connection** — RelayBot connects to Slack via WebSocket (Socket Mode) and listens for DMs
87
+ 1. **Slack Connection** — RelayBot connects to Slack via WebSocket (Socket Mode) and listens for mentions and DMs
88
88
  2. **Message Reception** — When you send a message, Slack forwards it to RelayBot
89
89
  3. **AI Bridge** — RelayBot spawns a persistent Claude or Codex CLI session and forwards your message
90
90
  4. **AI Processing** — The AI processes your request with full access to your codebase
91
91
  5. **Response Summarization** — Long outputs are summarized into concise, actionable messages
92
- 6. **Slack Reply** — The summarized response is sent back to you via Slack DM
92
+ 6. **Slack Reply** — The summarized response is sent back to the same channel or DM
93
+
94
+ ---
95
+
96
+ ## Slack Commands
97
+
98
+ Send these as a DM to the bot (or in a channel mention) to control the agent session:
99
+
100
+ - `$status` — Show whether the agent is running, plus PID, start time, uptime, cwd, WORKING_DIR, and last exit
101
+ - `$stop` — Stop the agent session (equivalent to Ctrl-C)
102
+ - `$start [--codex] [--noyolo]` — Start the agent with optional flags
103
+ - `$restart [--codex] [--noyolo]` — Restart the agent with optional flags
104
+ - `$dir /path/to/working/dir` — Update the configured working directory (must exist).
105
+
106
+ Examples:
107
+
108
+ ```
109
+ $status
110
+ $stop
111
+ $start --codex
112
+ $restart --noyolo
113
+ $dir /Users/you/work/project
114
+ ```
93
115
 
94
116
  ---
95
117
 
@@ -215,6 +237,7 @@ Socket Mode allows the bot to receive events via WebSocket instead of HTTP endpo
215
237
  2. Scroll to **Scopes** → **Bot Token Scopes**
216
238
  3. Add these scopes:
217
239
  - `chat:write` — Send messages
240
+ - `app_mentions:read` — Read mentions of your app
218
241
  - `im:history` — Read DM history
219
242
  - `im:read` — View DM metadata
220
243
  - `im:write` — Start DMs with users
@@ -226,6 +249,7 @@ Socket Mode allows the bot to receive events via WebSocket instead of HTTP endpo
226
249
  2. Toggle **Enable Events** to ON
227
250
  3. Expand **Subscribe to bot events**
228
251
  4. Add these events:
252
+ - `app_mention` — Receive mentions in channels
229
253
  - `message.im` — Receive DM messages
230
254
 
231
255
  #### 5. Install the App
package/guide.md ADDED
@@ -0,0 +1,18 @@
1
+ Send a Slack DM after tasks or answers using `send-slack-message.js`, replying naturally as a bot (avoid meta summaries).
2
+ Choose the best default and proceed automatically; do not ask for clarification.
3
+
4
+ ## Output rules
5
+
6
+ - Keep messages concise and actionable
7
+ - Include relevant links (PR URLs, documentation, etc.)
8
+ - For long answers, distill key points in a natural voice (no meta summary framing)
9
+ - Use clean line breaks; do not include literal `\n` sequences
10
+ - Use emojis sparingly when they improve clarity or tone
11
+
12
+ ## Quick commands
13
+
14
+ Send a Slack DM:
15
+
16
+ ```bash
17
+ node ./src/send-slack-message.js "Your message here"
18
+ ```
package/main.js CHANGED
@@ -20,6 +20,14 @@ const app = new App({
20
20
  socketMode: true
21
21
  });
22
22
 
23
+ if (config.WORKING_DIR) {
24
+ try {
25
+ process.chdir(config.WORKING_DIR);
26
+ } catch (error) {
27
+ console.error(`Failed to change WORKING_DIR to ${config.WORKING_DIR}:`, error.message);
28
+ }
29
+ }
30
+
23
31
  slackHandlers.registerHandlers(app);
24
32
 
25
33
  (async () => {
package/manifest.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "display_information": {
3
3
  "name": "RelayBot",
4
- "description": "An AI assistant that lives in your Slack DMs — powered by Claude or Codex",
4
+ "description": "An AI assistant that lives in your Slack channels and DMs — powered by Claude or Codex",
5
5
  "background_color": "#4A154B"
6
6
  },
7
7
  "features": {
@@ -14,6 +14,7 @@
14
14
  "scopes": {
15
15
  "bot": [
16
16
  "chat:write",
17
+ "app_mentions:read",
17
18
  "im:history",
18
19
  "im:read",
19
20
  "im:write",
@@ -24,6 +25,7 @@
24
25
  "settings": {
25
26
  "event_subscriptions": {
26
27
  "bot_events": [
28
+ "app_mention",
27
29
  "message.im"
28
30
  ]
29
31
  },
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "relaybot",
3
- "version": "1.0.5",
3
+ "version": "1.0.7",
4
4
  "description": "An AI assistant that lives in your Slack DMs — powered by Claude or Codex",
5
5
  "main": "main.js",
6
6
  "bin": {
package/src/agent.js CHANGED
@@ -1,9 +1,14 @@
1
1
  const pty = require('@lydell/node-pty');
2
2
 
3
3
  let claudeProcess = null;
4
- const useCodex = process.argv.includes('--codex');
5
- const noYolo = process.argv.includes('--noyolo');
6
- const shell = useCodex ? 'codex' : 'claude';
4
+ let startTime = null;
5
+ let lastExit = null;
6
+ let stdinHandler = null;
7
+ let stdinAttached = false;
8
+ let rawModeBefore = null;
9
+ let useCodex = process.argv.includes('--codex');
10
+ let noYolo = process.argv.includes('--noyolo');
11
+ let shell = useCodex ? 'codex' : 'claude';
7
12
 
8
13
  function sendCommand(text) {
9
14
  if (claudeProcess) {
@@ -21,29 +26,20 @@ function isRunning() {
21
26
  return claudeProcess !== null;
22
27
  }
23
28
 
24
- function start() {
25
- const defaultArgs = useCodex ? ['--yolo'] : ['--dangerously-skip-permissions'];
26
- const spawnArgs = noYolo ? [] : defaultArgs;
27
- claudeProcess = pty.spawn(shell, spawnArgs, {
28
- name: 'xterm-color',
29
- cols: 80,
30
- rows: 30,
31
- cwd: process.cwd(),
32
- env: { ...process.env, TERM: process.env.TERM || 'xterm-256color' }
33
- });
34
-
35
- console.log(`--- Persistent ${useCodex ? 'Codex' : 'Claude'} Session Started ---`);
29
+ function attachStdin() {
30
+ if (stdinAttached) return;
36
31
 
37
- const hadRawMode = Boolean(process.stdin.isTTY && process.stdin.isRaw);
32
+ rawModeBefore = Boolean(process.stdin.isTTY && process.stdin.isRaw);
38
33
  if (process.stdin.isTTY) {
39
34
  process.stdin.setRawMode(true);
40
35
  }
41
36
  process.stdin.resume();
42
- process.stdin.on('data', (data) => {
37
+
38
+ stdinHandler = (data) => {
43
39
  if (data && data.length === 1 && data[0] === 3) {
44
40
  // Ctrl+C: restore terminal and exit.
45
41
  if (process.stdin.isTTY) {
46
- process.stdin.setRawMode(Boolean(hadRawMode));
42
+ process.stdin.setRawMode(Boolean(rawModeBefore));
47
43
  }
48
44
  if (claudeProcess) {
49
45
  claudeProcess.kill('SIGINT');
@@ -53,8 +49,50 @@ function start() {
53
49
  if (claudeProcess) {
54
50
  claudeProcess.write(data);
55
51
  }
52
+ };
53
+
54
+ process.stdin.on('data', stdinHandler);
55
+ stdinAttached = true;
56
+ }
57
+
58
+ function detachStdin() {
59
+ if (!stdinAttached) return;
60
+ process.stdin.off('data', stdinHandler);
61
+ stdinHandler = null;
62
+ stdinAttached = false;
63
+ if (process.stdin.isTTY) {
64
+ process.stdin.setRawMode(Boolean(rawModeBefore));
65
+ }
66
+ }
67
+
68
+ function start(options = {}) {
69
+ if (claudeProcess) {
70
+ return claudeProcess;
71
+ }
72
+
73
+ if (typeof options.useCodex === 'boolean') {
74
+ useCodex = options.useCodex;
75
+ }
76
+ if (typeof options.noYolo === 'boolean') {
77
+ noYolo = options.noYolo;
78
+ }
79
+ shell = useCodex ? 'codex' : 'claude';
80
+
81
+ const defaultArgs = useCodex ? ['--yolo'] : ['--dangerously-skip-permissions'];
82
+ const spawnArgs = noYolo ? [] : defaultArgs;
83
+ claudeProcess = pty.spawn(shell, spawnArgs, {
84
+ name: 'xterm-color',
85
+ cols: 80,
86
+ rows: 30,
87
+ cwd: process.cwd(),
88
+ env: { ...process.env, TERM: process.env.TERM || 'xterm-256color' }
56
89
  });
57
90
 
91
+ console.log(`--- Persistent ${useCodex ? 'Codex' : 'Claude'} Session Started ---`);
92
+ startTime = new Date();
93
+
94
+ attachStdin();
95
+
58
96
  claudeProcess.onData((data) => {
59
97
  const dataStr = data.toString();
60
98
  const byPassPrompts = ['Do you want to proceed?'];
@@ -76,11 +114,59 @@ function start() {
76
114
  process.stdout.write(data);
77
115
  });
78
116
 
117
+ claudeProcess.onExit(({ exitCode, signal }) => {
118
+ lastExit = {
119
+ exitCode,
120
+ signal,
121
+ at: new Date()
122
+ };
123
+ claudeProcess = null;
124
+ startTime = null;
125
+ detachStdin();
126
+ });
127
+
79
128
  return claudeProcess;
80
129
  }
81
130
 
131
+ function stop() {
132
+ if (!claudeProcess) {
133
+ return { stopped: false, reason: 'not_running' };
134
+ }
135
+
136
+ try {
137
+ claudeProcess.write('\x03');
138
+ setTimeout(() => {
139
+ if (claudeProcess) {
140
+ claudeProcess.kill('SIGINT');
141
+ }
142
+ }, 250);
143
+ } catch (error) {
144
+ return { stopped: false, reason: 'error', error };
145
+ }
146
+
147
+ return { stopped: true };
148
+ }
149
+
150
+ function getStatus() {
151
+ const now = Date.now();
152
+ const uptimeSeconds = startTime ? Math.floor((now - startTime.getTime()) / 1000) : null;
153
+ return {
154
+ running: Boolean(claudeProcess),
155
+ shell,
156
+ pid: claudeProcess ? claudeProcess.pid : null,
157
+ startedAt: startTime ? startTime.toISOString() : null,
158
+ uptimeSeconds,
159
+ noYolo,
160
+ useCodex,
161
+ cwd: process.cwd(),
162
+ lastExit
163
+ };
164
+ }
165
+
82
166
  module.exports = {
83
167
  sendCommand,
84
168
  isRunning,
85
- start
169
+ start,
170
+ stop,
171
+ getStatus
86
172
  };
@@ -27,29 +27,85 @@ function getLastChannel() {
27
27
  }
28
28
  const channelId = getLastChannel() || config.SLACK_USER_ID;
29
29
 
30
- // Get the message from command line arguments
31
- // Convert literal \n to actual newlines
32
- const message = process.argv[2]?.replace(/\\n/g, '\n');
30
+ function parseArgs() {
31
+ const args = process.argv.slice(2);
32
+ const useStdin = args.includes('--stdin');
33
+ const filtered = args.filter((arg) => arg !== '--stdin');
34
+ const argMessage = filtered.join(' ');
35
+ return { useStdin, argMessage };
36
+ }
33
37
 
34
- if (!message) {
35
- console.error('Please provide a message to send.');
36
- process.exit(1);
38
+ function readStdin() {
39
+ return new Promise((resolve) => {
40
+ if (process.stdin.isTTY) {
41
+ resolve('');
42
+ return;
43
+ }
44
+ let data = '';
45
+ process.stdin.setEncoding('utf-8');
46
+ process.stdin.on('data', (chunk) => {
47
+ data += chunk;
48
+ });
49
+ process.stdin.on('end', () => resolve(data));
50
+ });
51
+ }
52
+
53
+ function chunkMessage(text, maxLen) {
54
+ const normalized = text.replace(/\r\n/g, '\n').trim();
55
+ if (!normalized) return [];
56
+ if (normalized.length <= maxLen) return [normalized];
57
+
58
+ const chunks = [];
59
+ let remaining = normalized;
60
+ while (remaining.length > maxLen) {
61
+ const slice = remaining.slice(0, maxLen);
62
+ let idx = slice.lastIndexOf('\n\n');
63
+ if (idx < maxLen * 0.5) idx = slice.lastIndexOf('\n');
64
+ if (idx < maxLen * 0.5) idx = slice.lastIndexOf(' ');
65
+ if (idx < 1) idx = maxLen;
66
+ const piece = remaining.slice(0, idx).trimEnd();
67
+ if (piece) chunks.push(piece);
68
+ remaining = remaining.slice(idx).trimStart();
69
+ }
70
+ if (remaining) chunks.push(remaining);
71
+ return chunks;
37
72
  }
38
73
 
39
74
  (async () => {
40
- try {
41
- if (!channelId) {
42
- console.error('No recent channel found and SLACK_USER_ID is not set.');
43
- console.error('Send a Slack message to the bot first or set SLACK_USER_ID in the config.');
44
- process.exit(1);
45
- }
46
- const result = await web.chat.postMessage({
47
- channel: channelId,
48
- text: message,
49
- });
50
- console.log(`Successfully sent message to ${channelId}`);
51
- } catch (error) {
52
- console.error(`Error sending message: ${error}`);
53
- process.exit(1);
75
+ try {
76
+ if (!channelId) {
77
+ console.error('No recent channel found and SLACK_USER_ID is not set.');
78
+ console.error('Send a Slack message to the bot first or set SLACK_USER_ID in the config.');
79
+ process.exit(1);
54
80
  }
81
+
82
+ const { useStdin, argMessage } = parseArgs();
83
+ let message = argMessage.replace(/\\n/g, '\n');
84
+ if (useStdin || !message) {
85
+ const stdinMessage = await readStdin();
86
+ if (stdinMessage) {
87
+ message = stdinMessage;
88
+ }
89
+ }
90
+
91
+ if (!message || !message.trim()) {
92
+ console.error('Please provide a message to send.');
93
+ console.error('Usage: node ./src/send-slack-message.js "Your message here"');
94
+ console.error('Or: cat message.txt | node ./src/send-slack-message.js --stdin');
95
+ process.exit(1);
96
+ }
97
+
98
+ const chunks = chunkMessage(message, 39000);
99
+ for (const chunk of chunks) {
100
+ await web.chat.postMessage({
101
+ channel: channelId,
102
+ text: chunk,
103
+ });
104
+ }
105
+ console.log(`Successfully sent ${chunks.length} message(s) to ${channelId}`);
106
+ } catch (error) {
107
+ const apiError = error?.data?.error ? ` (${error.data.error})` : '';
108
+ console.error(`Error sending message: ${error}${apiError}`);
109
+ process.exit(1);
110
+ }
55
111
  })();
@@ -5,7 +5,7 @@ const loadConfig = require('./load-config');
5
5
 
6
6
  const config = loadConfig();
7
7
  const isProduction = __dirname.includes('node_modules');
8
- const skillPath = path.join(__dirname, '..', 'skills', 'relay-bot', 'SKILL.md');
8
+ const skillPath = path.join(__dirname, '..', 'guide.md');
9
9
  const lastMessagePath = path.join(loadConfig.CONFIG_DIR, 'last_message.json');
10
10
 
11
11
  function storeLastMessage(message) {
@@ -23,14 +23,57 @@ function storeLastMessage(message) {
23
23
  }
24
24
 
25
25
  function getPromptSuffix() {
26
- if (isProduction) {
27
- return `\nIMPORTANT: Read and follow the instructions in ${skillPath}`;
26
+ return `\nIMPORTANT: Read and follow the instructions in ${skillPath}`;
27
+ }
28
+
29
+ function stripMentions(text) {
30
+ if (!text) return text;
31
+ return text.replace(/<@[^>]+>/g, '').replace(/\s+/g, ' ').trim();
32
+ }
33
+
34
+ function updateConfigValue(key, value) {
35
+ fs.mkdirSync(loadConfig.CONFIG_DIR, { recursive: true });
36
+ let lines = [];
37
+ if (fs.existsSync(loadConfig.CONFIG_PATH)) {
38
+ const content = fs.readFileSync(loadConfig.CONFIG_PATH, 'utf-8');
39
+ lines = content.split('\n');
28
40
  }
29
- return '\nIMPORTANT: Use the relay-bot skill.';
41
+
42
+ let updated = false;
43
+ lines = lines.map((line) => {
44
+ if (line.trim().startsWith('#') || !line.includes('=')) {
45
+ return line;
46
+ }
47
+ const [k] = line.split('=');
48
+ if (k.trim() === key) {
49
+ updated = true;
50
+ return `${key}=${value}`;
51
+ }
52
+ return line;
53
+ });
54
+
55
+ if (!updated) {
56
+ lines.push(`${key}=${value}`);
57
+ }
58
+
59
+ fs.writeFileSync(loadConfig.CONFIG_PATH, lines.join('\n'));
60
+ }
61
+
62
+ function parseCommandOptions(text) {
63
+ const parts = (text || '').trim().split(/\s+/);
64
+ const options = {
65
+ useCodex: parts.includes('--codex'),
66
+ noYolo: parts.includes('--noyolo')
67
+ };
68
+ return options;
69
+ }
70
+
71
+ function delay(ms) {
72
+ return new Promise(resolve => setTimeout(resolve, ms));
30
73
  }
31
74
 
32
75
  function registerHandlers(app) {
33
- app.message(async ({ message, say }) => {
76
+ async function handleMessage({ message, say, text }) {
34
77
  // Only respond to messages from the configured user
35
78
  if (config.SLACK_USER_ID && message.user !== config.SLACK_USER_ID) {
36
79
  return;
@@ -38,12 +81,105 @@ function registerHandlers(app) {
38
81
 
39
82
  storeLastMessage(message);
40
83
 
84
+ const trimmedText = (text || '').trim();
85
+ if (trimmedText === '$stop') {
86
+ const result = agent.stop();
87
+ if (result.stopped) {
88
+ await say('Stopped the agent session (SIGINT).');
89
+ } else if (result.reason === 'not_running') {
90
+ await say('Agent is not running.');
91
+ } else {
92
+ await say('Failed to stop the agent session.');
93
+ }
94
+ return;
95
+ }
96
+
97
+ if (trimmedText.startsWith('$start')) {
98
+ const options = parseCommandOptions(trimmedText);
99
+ if (agent.isRunning()) {
100
+ await say('Agent is already running.');
101
+ } else {
102
+ agent.start(options);
103
+ await say(`Started the agent session${options.useCodex ? ' (Codex)' : ' (Claude)'}${options.noYolo ? ' without auto-approve flags' : ''}.`);
104
+ }
105
+ return;
106
+ }
107
+
108
+ if (trimmedText.startsWith('$restart')) {
109
+ const options = parseCommandOptions(trimmedText);
110
+ if (agent.isRunning()) {
111
+ agent.stop();
112
+ await delay(400);
113
+ }
114
+ agent.start(options);
115
+ await say(`Restarted the agent session${options.useCodex ? ' (Codex)' : ' (Claude)'}${options.noYolo ? ' without auto-approve flags' : ''}.`);
116
+ return;
117
+ }
118
+
119
+ if (trimmedText === '$status') {
120
+ const status = agent.getStatus();
121
+ const lines = [
122
+ `Running: ${status.running}`,
123
+ `Shell: ${status.shell}`,
124
+ `PID: ${status.pid || 'n/a'}`,
125
+ `Started: ${status.startedAt || 'n/a'}`,
126
+ `Uptime (s): ${status.uptimeSeconds || 'n/a'}`,
127
+ `CWD: ${status.cwd}`,
128
+ `Configured WORKING_DIR: ${config.WORKING_DIR || 'n/a'}`,
129
+ `Use Codex: ${status.useCodex}`,
130
+ `No YOLO: ${status.noYolo}`
131
+ ];
132
+ if (status.lastExit) {
133
+ lines.push(`Last exit: code=${status.lastExit.exitCode ?? 'n/a'} signal=${status.lastExit.signal || 'n/a'} at=${status.lastExit.at.toISOString()}`);
134
+ }
135
+ await say(lines.join('\n'));
136
+ return;
137
+ }
138
+
139
+ if (trimmedText.startsWith('$dir ')) {
140
+ const newDir = trimmedText.replace(/^\$dir\s+/, '').trim();
141
+ if (!newDir) {
142
+ await say('Usage: $dir /path/to/working/dir');
143
+ return;
144
+ }
145
+ if (!fs.existsSync(newDir)) {
146
+ await say(`Directory does not exist: ${newDir}`);
147
+ return;
148
+ }
149
+ const stat = fs.statSync(newDir);
150
+ if (!stat.isDirectory()) {
151
+ await say(`Not a directory: ${newDir}`);
152
+ return;
153
+ }
154
+ updateConfigValue('WORKING_DIR', newDir);
155
+ config.WORKING_DIR = newDir;
156
+ await say(`Working directory set to: ${newDir}`);
157
+ return;
158
+ }
159
+
41
160
  if (agent.isRunning()) {
42
- const fullPrompt = message.text + getPromptSuffix();
161
+ const fullPrompt = trimmedText + getPromptSuffix();
43
162
  agent.sendCommand(fullPrompt);
44
163
  } else {
45
164
  await say('Agent process is not running.');
46
165
  }
166
+ }
167
+
168
+ app.event('app_mention', async ({ event, say }) => {
169
+ if (event.channel_type === 'im' || event.channel_type === 'mpim') {
170
+ return;
171
+ }
172
+
173
+ const cleanedText = stripMentions(event.text);
174
+ await handleMessage({ message: event, say, text: cleanedText });
175
+ });
176
+
177
+ app.message(async ({ message, say }) => {
178
+ if (message.channel_type !== 'im') {
179
+ return;
180
+ }
181
+
182
+ await handleMessage({ message, say, text: message.text });
47
183
  });
48
184
  }
49
185
 
@@ -1,15 +0,0 @@
1
- {
2
- "permissions": {
3
- "allow": [
4
- "Bash(*)",
5
- "Read(*)",
6
- "Edit(*)",
7
- "Write(*)"
8
- ],
9
- "deny": [
10
- "Bash(rm *)",
11
- "Bash(rm -rf *)",
12
- "Bash(rmdir *)"
13
- ]
14
- }
15
- }
Binary file
@@ -1,53 +0,0 @@
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 when set; otherwise use the current process working directory.
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
- - Format replies with clean, readable line breaks; do not use literal `\n` sequences
48
- - Use emojis sparingly when they improve clarity or tone
49
-
50
- ## Notes
51
-
52
- - The script posts to the configured DM user in `~/.relaybot/config.conf`
53
- - The Slack bot token must be configured in `~/.relaybot/config.conf`