relaybot 1.0.6 → 1.0.8
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 +22 -0
- package/guide.md +21 -0
- package/main.js +8 -0
- package/package.json +1 -1
- package/src/agent.js +105 -19
- package/src/send-slack-message.js +76 -20
- package/src/slack-handlers.js +120 -6
- package/.claude/settings.local.json +0 -15
- package/relaybot-1.0.0.tgz +0 -0
- package/skills/relay-bot/SKILL.md +0 -53
package/README.md
CHANGED
|
@@ -93,6 +93,28 @@ RelayBot acts as a bridge between Slack and AI coding agents, allowing you to in
|
|
|
93
93
|
|
|
94
94
|
---
|
|
95
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
|
+
```
|
|
115
|
+
|
|
116
|
+
---
|
|
117
|
+
|
|
96
118
|
## Installation
|
|
97
119
|
|
|
98
120
|
### Option 1: Install via npm (Recommended)
|
package/guide.md
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
Send a Slack DM after tasks or answers using `send-slack-message.js`, replying naturally as a bot (avoid meta summaries about what was asked/answered).
|
|
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
|
+
- Send the message itself, not a description of the message. Example:
|
|
12
|
+
- Bad: `User asked how I am; I replied I'm good and ready to help.`
|
|
13
|
+
- Good: `Doing well—ready when you are. What can I help with?`
|
|
14
|
+
|
|
15
|
+
## Quick commands
|
|
16
|
+
|
|
17
|
+
Send a Slack DM:
|
|
18
|
+
|
|
19
|
+
```bash
|
|
20
|
+
node ./src/send-slack-message.js "Your message here"
|
|
21
|
+
```
|
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/package.json
CHANGED
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
|
-
|
|
5
|
-
|
|
6
|
-
|
|
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
|
|
25
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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(
|
|
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
|
-
|
|
31
|
-
|
|
32
|
-
const
|
|
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
|
-
|
|
35
|
-
|
|
36
|
-
process.
|
|
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
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
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
|
})();
|
package/src/slack-handlers.js
CHANGED
|
@@ -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, '..', '
|
|
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,10 +23,7 @@ function storeLastMessage(message) {
|
|
|
23
23
|
}
|
|
24
24
|
|
|
25
25
|
function getPromptSuffix() {
|
|
26
|
-
|
|
27
|
-
return `\nIMPORTANT: Read and follow the instructions in ${skillPath}`;
|
|
28
|
-
}
|
|
29
|
-
return '\nIMPORTANT: Use the relay-bot skill.';
|
|
26
|
+
return `\nIMPORTANT: Read and follow the instructions in ${skillPath}`;
|
|
30
27
|
}
|
|
31
28
|
|
|
32
29
|
function stripMentions(text) {
|
|
@@ -34,6 +31,47 @@ function stripMentions(text) {
|
|
|
34
31
|
return text.replace(/<@[^>]+>/g, '').replace(/\s+/g, ' ').trim();
|
|
35
32
|
}
|
|
36
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');
|
|
40
|
+
}
|
|
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));
|
|
73
|
+
}
|
|
74
|
+
|
|
37
75
|
function registerHandlers(app) {
|
|
38
76
|
async function handleMessage({ message, say, text }) {
|
|
39
77
|
// Only respond to messages from the configured user
|
|
@@ -43,8 +81,84 @@ function registerHandlers(app) {
|
|
|
43
81
|
|
|
44
82
|
storeLastMessage(message);
|
|
45
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
|
+
|
|
46
160
|
if (agent.isRunning()) {
|
|
47
|
-
const fullPrompt =
|
|
161
|
+
const fullPrompt = trimmedText + getPromptSuffix();
|
|
48
162
|
agent.sendCommand(fullPrompt);
|
|
49
163
|
} else {
|
|
50
164
|
await say('Agent process is not running.');
|
package/relaybot-1.0.0.tgz
DELETED
|
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`
|