@gonzih/cc-discord 0.1.7 → 0.1.9

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/dist/bot.d.ts CHANGED
@@ -3,8 +3,8 @@
3
3
  * One ClaudeProcess per channel (or channel:thread) — sessions are isolated per channel.
4
4
  */
5
5
  import { Redis } from "ioredis";
6
- /** Prepend [MM-DD HH:mm] so Claude knows when the message was received. */
7
- export declare function stampPrompt(text: string, now?: Date): string;
6
+ /** Prepend [DayOfWeek HH:MM] username: so Claude knows when the message was received and from whom. */
7
+ export declare function stampPrompt(text: string, username?: string, now?: Date): string;
8
8
  export interface DiscordBotOptions {
9
9
  discordToken: string;
10
10
  claudeToken?: string;
@@ -37,6 +37,13 @@ export declare class CcDiscordBot {
37
37
  private channelNamespaceMap;
38
38
  private storeSnowflake;
39
39
  reverseSnowflakeLookup(n: number): string | undefined;
40
+ /** Persist a channelId → {namespace, repoUrl} mapping to Redis. */
41
+ private persistChannelMapping;
42
+ /**
43
+ * Load persisted channel→namespace mappings from Redis and repopulate
44
+ * channelNamespaceMap + routedChannelIds. Call once on startup after the notifier is ready.
45
+ */
46
+ loadChannelMappings(): Promise<void>;
40
47
  /** Session key: "channelId" or "channelId:threadId" for threads */
41
48
  private sessionKey;
42
49
  /** Get the channel/thread for sending messages */
@@ -71,7 +78,8 @@ export declare class CcDiscordBot {
71
78
  * and start the meta-agent for `repoUrl`. Fire-and-forget after sending the confirmation message.
72
79
  */
73
80
  private createChannelForRepo;
74
- /** Write a message to the Redis chat log. Fire-and-forget. */
81
+ /** Write a message to the Redis chat log. Fire-and-forget.
82
+ * Pass `ns` to write under a specific namespace; defaults to the bot's primary namespace. */
75
83
  private writeChatMessage;
76
84
  /** Returns the last channelId that sent a message. */
77
85
  getLastActiveChannelId(): string | undefined;
package/dist/bot.js CHANGED
@@ -14,6 +14,10 @@ import { getCurrentToken } from "./tokens.js";
14
14
  import { writeChatLog } from "./notifier.js";
15
15
  import { CronManager } from "./cron.js";
16
16
  import { parseChannelCreateIntent, ensureMetaAgent, routeToMetaAgent } from "./router.js";
17
+ /** Redis key for persisting a Discord channelId → namespace mapping across restarts. */
18
+ function discordChannelKey(channelId) {
19
+ return `cca:discord:channel:${channelId}`;
20
+ }
17
21
  /** Convert a Discord snowflake string to a safe 53-bit integer for CronManager compatibility. */
18
22
  function snowflakeToInt(id) {
19
23
  // Discord snowflakes are up to 2^63, beyond Number.MAX_SAFE_INTEGER.
@@ -37,13 +41,14 @@ function computeCostUsd(usage) {
37
41
  const FLUSH_DELAY_MS = 800;
38
42
  // Discord typing indicator: re-send every 9s (indicator expires after ~10s)
39
43
  const TYPING_INTERVAL_MS = 9000;
40
- /** Prepend [MM-DD HH:mm] so Claude knows when the message was received. */
41
- export function stampPrompt(text, now = new Date()) {
42
- const mm = String(now.getMonth() + 1).padStart(2, "0");
43
- const dd = String(now.getDate()).padStart(2, "0");
44
+ const DAYS = ["Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday"];
45
+ /** Prepend [DayOfWeek HH:MM] username: so Claude knows when the message was received and from whom. */
46
+ export function stampPrompt(text, username, now = new Date()) {
47
+ const day = DAYS[now.getDay()];
44
48
  const hh = String(now.getHours()).padStart(2, "0");
45
49
  const min = String(now.getMinutes()).padStart(2, "0");
46
- return `[${mm}-${dd} ${hh}:${min}] ${text}`;
50
+ const header = username ? `[${day} ${hh}:${min}] ${username}: ` : `[${day} ${hh}:${min}] `;
51
+ return header + text;
47
52
  }
48
53
  function formatTokens(n) {
49
54
  if (n >= 1000)
@@ -164,6 +169,49 @@ export class CcDiscordBot {
164
169
  reverseSnowflakeLookup(n) {
165
170
  return this.snowflakeMap.get(n);
166
171
  }
172
+ /** Persist a channelId → {namespace, repoUrl} mapping to Redis. */
173
+ persistChannelMapping(channelId, namespace, repoUrl) {
174
+ if (!this.redis)
175
+ return;
176
+ const key = discordChannelKey(channelId);
177
+ const value = JSON.stringify({ namespace, repoUrl });
178
+ this.redis.set(key, value).catch((err) => {
179
+ console.warn(`[bot] persistChannelMapping failed for ${channelId}:`, err.message);
180
+ });
181
+ }
182
+ /**
183
+ * Load persisted channel→namespace mappings from Redis and repopulate
184
+ * channelNamespaceMap + routedChannelIds. Call once on startup after the notifier is ready.
185
+ */
186
+ async loadChannelMappings() {
187
+ if (!this.redis)
188
+ return;
189
+ let keys;
190
+ try {
191
+ keys = await this.redis.keys("cca:discord:channel:*");
192
+ }
193
+ catch (err) {
194
+ console.warn("[bot] loadChannelMappings keys scan failed:", err.message);
195
+ return;
196
+ }
197
+ for (const key of keys) {
198
+ try {
199
+ const raw = await this.redis.get(key);
200
+ if (!raw)
201
+ continue;
202
+ const { namespace, repoUrl } = JSON.parse(raw);
203
+ const channelId = key.slice("cca:discord:channel:".length);
204
+ if (!this.channelNamespaceMap.has(channelId)) {
205
+ this.channelNamespaceMap.set(channelId, { namespace, repoUrl });
206
+ this.opts.registerRoutedChannelId?.(namespace, channelId);
207
+ console.log(`[bot] restored channel mapping: ${channelId} → ${namespace}`);
208
+ }
209
+ }
210
+ catch (err) {
211
+ console.warn(`[bot] loadChannelMappings: failed to parse ${key}:`, err.message);
212
+ }
213
+ }
214
+ }
167
215
  /** Session key: "channelId" or "channelId:threadId" for threads */
168
216
  sessionKey(channelId, threadId) {
169
217
  return threadId ? `${channelId}:${threadId}` : channelId;
@@ -283,10 +331,11 @@ export class CcDiscordBot {
283
331
  // Channel registered via createChannelForRepo or /channel — route directly to its meta-agent
284
332
  const mappedNs = this.channelNamespaceMap.get(effectiveChannelId);
285
333
  if (mappedNs && this.redis) {
286
- this.writeChatMessage("user", "discord", text, effectiveChannelId);
334
+ this.writeChatMessage("user", "discord", text, effectiveChannelId, mappedNs.namespace);
287
335
  this.opts.registerRoutedChannelId?.(mappedNs.namespace, effectiveChannelId);
336
+ const username = msg.member?.displayName ?? msg.author.username;
288
337
  try {
289
- await routeToMetaAgent(mappedNs.namespace, text, this.redis);
338
+ await routeToMetaAgent(mappedNs.namespace, stampPrompt(text, username, msg.createdAt), this.redis);
290
339
  }
291
340
  catch (err) {
292
341
  await msg.channel.send(`Failed to route to ${mappedNs.namespace}: ${err.message}`).catch(() => { });
@@ -295,9 +344,10 @@ export class CcDiscordBot {
295
344
  }
296
345
  // Local Claude session
297
346
  const session = this.getOrCreateSession(effectiveChannelId, msg.channel);
347
+ const username = msg.member?.displayName ?? msg.author.username;
298
348
  try {
299
349
  session.currentPrompt = text;
300
- session.claude.sendPrompt(stampPrompt(text));
350
+ session.claude.sendPrompt(stampPrompt(text, username, msg.createdAt));
301
351
  this.startTyping(effectiveChannelId, msg.channel, session);
302
352
  this.writeChatMessage("user", "discord", text, effectiveChannelId);
303
353
  }
@@ -317,7 +367,8 @@ export class CcDiscordBot {
317
367
  }
318
368
  const session = this.getOrCreateSession(channelId, channel);
319
369
  session.currentPrompt = transcript;
320
- session.claude.sendPrompt(stampPrompt(transcript));
370
+ const voiceUsername = msg.member?.displayName ?? msg.author.username;
371
+ session.claude.sendPrompt(stampPrompt(transcript, voiceUsername, msg.createdAt));
321
372
  this.startTyping(channelId, channel, session);
322
373
  this.writeChatMessage("user", "discord", transcript, channelId);
323
374
  }
@@ -342,8 +393,9 @@ export class CcDiscordBot {
342
393
  try {
343
394
  const base64Data = await fetchAsBase64(imageUrl);
344
395
  const caption = msg.content.trim() || "";
396
+ const imgUsername = msg.member?.displayName ?? msg.author.username;
345
397
  const session = this.getOrCreateSession(channelId, channel);
346
- session.claude.sendImage(base64Data, contentType, stampPrompt(caption));
398
+ session.claude.sendImage(base64Data, contentType, stampPrompt(caption, imgUsername, msg.createdAt));
347
399
  this.startTyping(channelId, channel, session);
348
400
  }
349
401
  catch (err) {
@@ -637,6 +689,7 @@ export class CcDiscordBot {
637
689
  const newChannel = await guild.channels.create({ name: namespace, type: ChannelType.GuildText, parent: resolveCategoryId(guild) });
638
690
  this.channelNamespaceMap.set(newChannel.id, { namespace, repoUrl });
639
691
  this.opts.registerRoutedChannelId?.(namespace, newChannel.id);
692
+ this.persistChannelMapping(newChannel.id, namespace, repoUrl);
640
693
  await interaction.editReply(`Created <#${newChannel.id}> — messages there route to the ${repoUrl} meta-agent`);
641
694
  // Start meta-agent in the background
642
695
  if (this.redis) {
@@ -780,6 +833,7 @@ export class CcDiscordBot {
780
833
  }
781
834
  this.channelNamespaceMap.set(newChannel.id, { namespace, repoUrl });
782
835
  this.opts.registerRoutedChannelId?.(namespace, newChannel.id);
836
+ this.persistChannelMapping(newChannel.id, namespace, repoUrl);
783
837
  await channel.send(`Created <#${newChannel.id}> — messages there route to the ${repoUrl} meta-agent`).catch(() => { });
784
838
  // Start meta-agent in the background after acknowledging the user
785
839
  if (this.redis) {
@@ -790,8 +844,9 @@ export class CcDiscordBot {
790
844
  });
791
845
  }
792
846
  }
793
- /** Write a message to the Redis chat log. Fire-and-forget. */
794
- writeChatMessage(role, source, content, channelId) {
847
+ /** Write a message to the Redis chat log. Fire-and-forget.
848
+ * Pass `ns` to write under a specific namespace; defaults to the bot's primary namespace. */
849
+ writeChatMessage(role, source, content, channelId, ns) {
795
850
  if (!this.redis)
796
851
  return;
797
852
  const msg = {
@@ -802,7 +857,7 @@ export class CcDiscordBot {
802
857
  timestamp: new Date().toISOString(),
803
858
  chatId: snowflakeToInt(channelId),
804
859
  };
805
- writeChatLog(this.redis, this.namespace, msg);
860
+ writeChatLog(this.redis, ns ?? this.namespace, msg);
806
861
  }
807
862
  /** Returns the last channelId that sent a message. */
808
863
  getLastActiveChannelId() {
package/dist/index.js CHANGED
@@ -95,6 +95,10 @@ const bot = new CcDiscordBot({
95
95
  });
96
96
  const notifier = startNotifier(bot, notifyChannelId, namespace, sharedRedis, (channelId, text) => handleUserMessageFn?.(channelId, text), (channelId, text) => forwardNotificationFn?.(channelId, text), () => getLastActiveChannelIdFn(), (n) => bot.reverseSnowflakeLookup(n));
97
97
  console.log(`[notifier] started for namespace=${namespace} notifyChannelId=${notifyChannelId ?? "dynamic"}`);
98
+ // Restore persisted channel→namespace mappings so routing survives restarts
99
+ bot.loadChannelMappings().catch((err) => {
100
+ console.warn("[cc-discord] loadChannelMappings failed:", err.message);
101
+ });
98
102
  // Wire closures now that bot is constructed
99
103
  getLastActiveChannelIdFn = () => bot.getLastActiveChannelId();
100
104
  handleUserMessageFn = (channelId, text) => { void bot.handleUserMessage(channelId, text); };
@@ -37,11 +37,19 @@ export declare function parseNotification(raw: string): ParsedNotification | nul
37
37
  * Fire-and-forget — errors are logged but not thrown.
38
38
  */
39
39
  export declare function writeChatLog(redis: Redis, namespace: string, msg: ChatMessage): void;
40
+ /**
41
+ * Resolve the target Discord channelId for a notification.
42
+ * When chatId is set and a reverse-lookup function is available, prefer the originating channel.
43
+ * Falls back to notifyChannelId, then getActiveChannelId.
44
+ */
45
+ export declare function resolveNotifyChannel(chatId: number | undefined, notifyChannelId: string | null, getActiveChannelId?: () => string | undefined, reverseSnowflakeLookup?: (n: number) => string | undefined): string | undefined;
40
46
  export interface NotifierHandle {
41
47
  /**
42
48
  * Register the originating Discord channel ID for a routed namespace.
43
49
  * When the meta-agent for `namespace` publishes a response, it will be
44
50
  * forwarded to `channelId`.
51
+ * Also subscribes to notifyChannel(namespace) and chatIncomingChannel(namespace)
52
+ * so notifications and UI messages for that namespace are received.
45
53
  */
46
54
  registerRoutedChannelId: (namespace: string, channelId: string) => void;
47
55
  }
package/dist/notifier.js CHANGED
@@ -91,7 +91,7 @@ export function writeChatLog(redis, namespace, msg) {
91
91
  * When chatId is set and a reverse-lookup function is available, prefer the originating channel.
92
92
  * Falls back to notifyChannelId, then getActiveChannelId.
93
93
  */
94
- function resolveNotifyChannel(chatId, notifyChannelId, getActiveChannelId, reverseSnowflakeLookup) {
94
+ export function resolveNotifyChannel(chatId, notifyChannelId, getActiveChannelId, reverseSnowflakeLookup) {
95
95
  if (chatId != null && reverseSnowflakeLookup) {
96
96
  const resolved = reverseSnowflakeLookup(chatId);
97
97
  if (resolved)
@@ -112,8 +112,10 @@ function resolveNotifyChannel(chatId, notifyChannelId, getActiveChannelId, rever
112
112
  * @param reverseSnowflakeLookup - Optional callback to resolve a chatId integer to a Discord channelId
113
113
  */
114
114
  export function startNotifier(bot, notifyChannelId, namespace, redis, handleUserMessage, forwardNotification, getActiveChannelId, reverseSnowflakeLookup) {
115
- // Per-namespace channelId registry
115
+ // Per-namespace channelId registry — maps routed namespace → Discord channelId
116
116
  const routedChannelIds = new Map();
117
+ // Track which namespaces we've already subscribed to (to avoid duplicate subscribe calls)
118
+ const subscribedNamespaces = new Set();
117
119
  const sub = redis.duplicate({
118
120
  retryStrategy: (times) => {
119
121
  const delay = Math.min(1000 * Math.pow(2, times - 1), 30_000);
@@ -127,25 +129,39 @@ export function startNotifier(bot, notifyChannelId, namespace, redis, handleUser
127
129
  sub.on("close", () => {
128
130
  log("info", "subscriber disconnected, will reconnect with backoff");
129
131
  });
130
- // notifyChannel(namespace) forward job completion notifications to Discord
131
- sub.subscribe(notifyChannel(namespace), (err) => {
132
- if (err) {
133
- log("error", `subscribe ${notifyChannel(namespace)} failed:`, err.message);
134
- }
135
- else {
136
- log("info", `subscribed to ${notifyChannel(namespace)}`);
137
- }
138
- });
139
- // chatIncomingChannel(namespace) — messages from UI
140
- sub.subscribe(chatIncomingChannel(namespace), (err) => {
141
- if (err) {
142
- log("error", `subscribe ${chatIncomingChannel(namespace)} failed:`, err.message);
143
- }
144
- else {
145
- log("info", `subscribed to ${chatIncomingChannel(namespace)}`);
146
- }
147
- });
148
- // chatOutgoingChannel("*") meta-agent stdout lines
132
+ // Reverse map: Redis channel string → namespace (for O(1) lookup in message handler)
133
+ const channelToNamespace = new Map();
134
+ function subscribeNamespace(ns) {
135
+ if (subscribedNamespaces.has(ns))
136
+ return;
137
+ subscribedNamespaces.add(ns);
138
+ const notifyCh = notifyChannel(ns);
139
+ const incomingCh = chatIncomingChannel(ns);
140
+ channelToNamespace.set(notifyCh, ns);
141
+ channelToNamespace.set(incomingCh, ns);
142
+ sub.subscribe(notifyCh, (err) => {
143
+ if (err) {
144
+ log("error", `subscribe ${notifyCh} failed:`, err.message);
145
+ }
146
+ else {
147
+ log("info", `subscribed to ${notifyCh}`);
148
+ }
149
+ });
150
+ sub.subscribe(incomingCh, (err) => {
151
+ if (err) {
152
+ log("error", `subscribe ${incomingCh} failed:`, err.message);
153
+ }
154
+ else {
155
+ log("info", `subscribed to ${incomingCh}`);
156
+ }
157
+ });
158
+ }
159
+ function resolveSubscribedNamespace(channel) {
160
+ return channelToNamespace.get(channel);
161
+ }
162
+ // Subscribe to the primary namespace immediately
163
+ subscribeNamespace(namespace);
164
+ // chatOutgoingChannel("*") — meta-agent stdout lines for ALL namespaces
149
165
  sub.psubscribe(chatOutgoingChannel("*"), (err) => {
150
166
  if (err) {
151
167
  log("error", `psubscribe ${chatOutgoingChannel("*")} failed:`, err.message);
@@ -186,7 +202,12 @@ export function startNotifier(bot, notifyChannelId, namespace, redis, handleUser
186
202
  const content = parsed.content;
187
203
  if (!content)
188
204
  return;
189
- const targetChannelId = routedChannelIds.get(ns) ?? notifyChannelId ?? getActiveChannelId?.();
205
+ // For the primary namespace, fall back to notifyChannelId / getActiveChannelId.
206
+ // For any other namespace, ONLY use the registered channelId — never fall back to
207
+ // the primary channel, as that would cause cross-namespace leakage.
208
+ const targetChannelId = ns === namespace
209
+ ? (routedChannelIds.get(ns) ?? notifyChannelId ?? getActiveChannelId?.())
210
+ : routedChannelIds.get(ns);
190
211
  if (targetChannelId == null) {
191
212
  log("warn", `meta-agent output: no channelId for namespace=${ns}, dropping line`);
192
213
  return;
@@ -254,23 +275,37 @@ export function startNotifier(bot, notifyChannelId, namespace, redis, handleUser
254
275
  void pollNotifyList();
255
276
  }, 5_000);
256
277
  sub.on("message", (channel, message) => {
257
- const notifyCh = notifyChannel(namespace);
258
- const incomingCh = chatIncomingChannel(namespace);
278
+ // Determine which namespace this channel belongs to
279
+ const ns = resolveSubscribedNamespace(channel);
280
+ if (!ns)
281
+ return;
282
+ const isPrimary = ns === namespace;
283
+ const notifyCh = notifyChannel(ns);
284
+ const incomingCh = chatIncomingChannel(ns);
259
285
  if (channel === notifyCh) {
260
286
  const notification = parseNotification(message);
261
287
  if (notification === null)
262
288
  return; // routing excludes discord
263
- const targetId = resolveNotifyChannel(notification.chatId, notifyChannelId, getActiveChannelId, reverseSnowflakeLookup);
289
+ let targetId;
290
+ if (isPrimary) {
291
+ targetId = resolveNotifyChannel(notification.chatId, notifyChannelId, getActiveChannelId, reverseSnowflakeLookup);
292
+ }
293
+ else {
294
+ // For routed namespaces, only use the registered channelId — no fallback to primary
295
+ targetId = notification.chatId != null && reverseSnowflakeLookup
296
+ ? (reverseSnowflakeLookup(notification.chatId) ?? routedChannelIds.get(ns))
297
+ : routedChannelIds.get(ns);
298
+ }
264
299
  if (targetId != null) {
265
300
  bot.sendToChannelById(targetId, notification.text).catch((err) => {
266
- log("warn", "notify send failed:", err.message);
301
+ log("warn", `notify send failed (ns=${ns}):`, err.message);
267
302
  });
268
303
  if (forwardNotification) {
269
304
  forwardNotification(targetId, notification.text);
270
305
  }
271
306
  }
272
307
  else {
273
- log("warn", "notify: no channelId available, dropping notification");
308
+ log("warn", `notify: no channelId available for ns=${ns}, dropping notification`);
274
309
  }
275
310
  return;
276
311
  }
@@ -287,11 +322,13 @@ export function startNotifier(bot, notifyChannelId, namespace, redis, handleUser
287
322
  catch {
288
323
  // raw string message — use as-is
289
324
  }
290
- const targetChannelId = notifyChannelId ?? getActiveChannelId?.();
325
+ const targetChannelId = isPrimary
326
+ ? (notifyChannelId ?? getActiveChannelId?.())
327
+ : routedChannelIds.get(ns);
291
328
  if (targetChannelId !== undefined) {
292
329
  // Echo to Discord so the user sees UI messages
293
330
  bot.sendToChannelById(targetChannelId, `[from UI]: ${content}`).catch((err) => {
294
- log("warn", "sendToChannelById (UI echo) failed:", err.message);
331
+ log("warn", `sendToChannelById (UI echo) failed (ns=${ns}):`, err.message);
295
332
  });
296
333
  // Log the incoming message
297
334
  const inMsg = {
@@ -302,12 +339,12 @@ export function startNotifier(bot, notifyChannelId, namespace, redis, handleUser
302
339
  timestamp: originalTimestamp ?? new Date().toISOString(),
303
340
  chatId: 0, // no numeric chatId for Discord — stored by channelId string
304
341
  };
305
- writeChatLog(redis, namespace, inMsg);
342
+ writeChatLog(redis, ns, inMsg);
306
343
  // Check if a meta-agent is running; if so, route there instead
307
344
  void (async () => {
308
345
  let routedToMetaAgent = false;
309
346
  try {
310
- const statusRaw = await redis.get(metaAgentStatusKey(namespace));
347
+ const statusRaw = await redis.get(metaAgentStatusKey(ns));
311
348
  if (statusRaw) {
312
349
  const status = JSON.parse(statusRaw);
313
350
  if (status.status === "running") {
@@ -316,14 +353,14 @@ export function startNotifier(bot, notifyChannelId, namespace, redis, handleUser
316
353
  content,
317
354
  timestamp: new Date().toISOString(),
318
355
  });
319
- await redis.rpush(metaInputKey(namespace), entry);
320
- log("info", `cca:chat:incoming: routed to meta-agent for namespace ${namespace}`);
356
+ await redis.rpush(metaInputKey(ns), entry);
357
+ log("info", `cca:chat:incoming: routed to meta-agent for namespace ${ns}`);
321
358
  routedToMetaAgent = true;
322
359
  }
323
360
  }
324
361
  }
325
362
  catch (err) {
326
- log("warn", "meta-agent status check failed:", err.message);
363
+ log("warn", `meta-agent status check failed (ns=${ns}):`, err.message);
327
364
  }
328
365
  if (!routedToMetaAgent && handleUserMessage) {
329
366
  handleUserMessage(targetChannelId, content);
@@ -331,13 +368,16 @@ export function startNotifier(bot, notifyChannelId, namespace, redis, handleUser
331
368
  })();
332
369
  }
333
370
  else {
334
- log("warn", "cca:chat:incoming: no active channelId to route message to");
371
+ log("warn", `cca:chat:incoming: no active channelId for ns=${ns}, dropping message`);
335
372
  }
336
373
  }
337
374
  });
338
375
  return {
339
376
  registerRoutedChannelId: (ns, channelId) => {
340
377
  routedChannelIds.set(ns, channelId);
378
+ // Subscribe to this namespace's Redis channels so we receive its notifications
379
+ // and incoming UI messages. No-op if already subscribed.
380
+ subscribeNamespace(ns);
341
381
  },
342
382
  };
343
383
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@gonzih/cc-discord",
3
- "version": "0.1.7",
3
+ "version": "0.1.9",
4
4
  "description": "Claude Code Discord bot — chat with Claude Code via Discord",
5
5
  "type": "module",
6
6
  "bin": {