cord-bot 1.1.0 → 1.1.2

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "cord-bot",
3
- "version": "1.1.0",
3
+ "version": "1.1.2",
4
4
  "description": "Discord bot that bridges messages to Claude Code sessions",
5
5
  "repository": {
6
6
  "type": "git",
@@ -340,6 +340,16 @@ cord buttons 123456789 "Delete all archived items older than 30 days?" \
340
340
 
341
341
  ---
342
342
 
343
+ ## Auto-Complete Behavior
344
+
345
+ When a user adds a ✅ reaction to the **last message** in a thread, Cord automatically:
346
+ 1. Detects the reaction
347
+ 2. Updates the thread starter message to "✅ Done"
348
+
349
+ This provides a quick way for users to signal "conversation complete" without explicit commands.
350
+
351
+ ---
352
+
343
353
  ## HTTP API
344
354
 
345
355
  For advanced use cases (webhooks, external scripts), see [HTTP-API.md](./HTTP-API.md).
package/src/bot.ts CHANGED
@@ -29,6 +29,7 @@ const client = new Client({
29
29
  GatewayIntentBits.Guilds,
30
30
  GatewayIntentBits.GuildMessages,
31
31
  GatewayIntentBits.MessageContent,
32
+ GatewayIntentBits.GuildMessageReactions,
32
33
  ],
33
34
  });
34
35
 
@@ -129,19 +130,33 @@ client.on(Events.MessageCreate, async (message: Message) => {
129
130
 
130
131
  log(`New mention from ${message.author.tag}`);
131
132
 
132
- // Create a thread for the conversation
133
+ // Post status message in channel, then create thread from it
134
+ // This allows us to update the status message later (Processing... → Done)
135
+ let statusMessage;
133
136
  let thread;
134
137
  try {
138
+ // Post status message in the channel
139
+ statusMessage = await (message.channel as TextChannel).send('🤖 Processing...');
140
+
135
141
  // Generate thread name from message content
136
142
  const rawText = message.content.replace(/<@!?\d+>/g, '').trim();
137
143
  const threadName = rawText.length > 50
138
144
  ? rawText.slice(0, 47) + '...'
139
145
  : rawText || 'New conversation';
140
146
 
141
- thread = await message.startThread({
147
+ // Create thread from the status message
148
+ thread = await statusMessage.startThread({
142
149
  name: threadName,
143
150
  autoArchiveDuration: ThreadAutoArchiveDuration.OneDay,
144
151
  });
152
+
153
+ // Copy the original message content into the thread for context
154
+ // (excluding the bot mention and the status message)
155
+ const originalMessages = await message.channel.messages.fetch({ limit: 10 });
156
+ const userMessage = originalMessages.find(m => m.id === message.id);
157
+ if (userMessage) {
158
+ await thread.send(`**${message.author.tag}:** ${rawText}`);
159
+ }
145
160
  } catch (error) {
146
161
  log(`Failed to create thread: ${error}`);
147
162
  await message.reply('Failed to start thread. Try again?');
@@ -152,6 +167,7 @@ client.on(Events.MessageCreate, async (message: Message) => {
152
167
  const sessionId = crypto.randomUUID();
153
168
 
154
169
  // Store the thread → session mapping
170
+ // Note: thread.id === statusMessage.id because thread was created from that message
155
171
  db.run(
156
172
  'INSERT INTO threads (thread_id, session_id) VALUES (?, ?)',
157
173
  [thread.id, sessionId]
@@ -176,6 +192,49 @@ client.on(Events.MessageCreate, async (message: Message) => {
176
192
  });
177
193
  });
178
194
 
195
+ // =========================================================================
196
+ // REACTION HANDLER: ✅ on last message marks thread as done
197
+ // =========================================================================
198
+ client.on(Events.MessageReactionAdd, async (reaction, user) => {
199
+ // Ignore bot reactions
200
+ if (user.bot) return;
201
+
202
+ // Only handle ✅ reactions
203
+ if (reaction.emoji.name !== '✅') return;
204
+
205
+ // Only handle reactions in threads
206
+ const channel = reaction.message.channel;
207
+ if (!channel.isThread()) return;
208
+
209
+ try {
210
+ const thread = channel;
211
+ const parentChannelId = thread.parentId;
212
+ if (!parentChannelId) return;
213
+
214
+ // Check if this is the last message in the thread
215
+ const messages = await thread.messages.fetch({ limit: 1 });
216
+ const lastMessage = messages.first();
217
+
218
+ if (!lastMessage || lastMessage.id !== reaction.message.id) {
219
+ // Reaction is not on the last message, ignore
220
+ return;
221
+ }
222
+
223
+ log(`✅ reaction on last message in thread ${thread.id}`);
224
+
225
+ // Update thread starter message to "Done"
226
+ // The thread ID equals the starter message ID (thread was created from that message)
227
+ const parentChannel = await client.channels.fetch(parentChannelId);
228
+ if (parentChannel?.isTextBased()) {
229
+ const starterMessage = await (parentChannel as TextChannel).messages.fetch(thread.id);
230
+ await starterMessage.edit('✅ Done');
231
+ log(`Thread ${thread.id} marked as Done`);
232
+ }
233
+ } catch (error) {
234
+ log(`Failed to mark thread done: ${error}`);
235
+ }
236
+ });
237
+
179
238
  // Start the bot
180
239
  const token = process.env.DISCORD_BOT_TOKEN;
181
240
  if (!token) {