acp-discord 0.2.0 → 0.4.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 CHANGED
@@ -6,12 +6,15 @@ Send a message in Discord, get AI coding assistance back — with tool call visu
6
6
 
7
7
  ## Features
8
8
 
9
- - **Slash commands & mentions** — `/ask <message>` or `@bot message`
9
+ - **Slash commands & mentions** — `/ask <message>`, `/clear`, or `@bot message`
10
10
  - **Real-time streaming** — agent responses stream into Discord with smart message splitting
11
- - **Tool call visualization** — see what the agent is doing with emoji status indicators
12
- - **Permission UI** — Discord buttons for approving/denying agent actions
11
+ - **File diffs** — see unified diffs in Discord when the agent modifies files
12
+ - **Tool call visualization** — see what the agent is doing (⏳ pending → 🔄 running → ✅ done / failed), with a ⏹️ stop button to cancel
13
+ - **Permission UI** — Discord buttons for approving/denying agent actions, with file diffs shown inline for review before approval
14
+ - **Auto-reply mode** — optionally respond to all messages in a channel, not just mentions
13
15
  - **Multi-agent support** — different channels can use different agents
14
16
  - **Daemon mode** — runs in background with auto-start (systemd/launchd)
17
+ - **Self-update** — `acp-discord update` to update in-place, auto-restarts the daemon
15
18
  - **Interactive setup** — guided `init` wizard for first-time configuration
16
19
 
17
20
  ## Prerequisites
@@ -42,11 +45,12 @@ token = "your-discord-bot-token"
42
45
  command = "claude-code"
43
46
  args = ["--acp"]
44
47
  cwd = "/path/to/your/project"
45
- idle_timeout = 600 # seconds, optional
48
+ idle_timeout = 600 # seconds before idle session is terminated (default: 600)
46
49
 
47
50
  [channels.1234567890123456]
48
51
  agent = "claude"
49
- cwd = "/override/path" # optional, per-channel override
52
+ cwd = "/override/path" # optional, per-channel working directory override
53
+ auto_reply = true # optional, respond to all messages (default: false, mention-only)
50
54
  ```
51
55
 
52
56
  ### Discord Bot Setup
@@ -74,6 +78,9 @@ acp-discord daemon status # Check if running
74
78
  # Auto-start on boot
75
79
  acp-discord daemon enable # Setup systemd (Linux) / launchd (macOS)
76
80
  acp-discord daemon disable # Remove auto-start
81
+
82
+ # Self-update
83
+ acp-discord update # Update to latest version, auto-restarts daemon
77
84
  ```
78
85
 
79
86
  ### Discord Commands
@@ -81,8 +88,11 @@ acp-discord daemon disable # Remove auto-start
81
88
  | Command | Description |
82
89
  |---------|-------------|
83
90
  | `/ask <message>` | Send a prompt to the coding agent |
91
+ | `/clear` | Clear the current session and start fresh |
84
92
  | `@bot <message>` | Mention the bot to send a prompt |
85
93
 
94
+ If a prompt is sent while the agent is already working, it gets queued and processed after the current task completes.
95
+
86
96
  ### Development
87
97
 
88
98
  ```bash
package/dist/daemon.js CHANGED
@@ -46,6 +46,7 @@ import { ClientSideConnection, ndJsonStream, PROTOCOL_VERSION } from "@agentclie
46
46
  function createAcpClient(channelId, handlers, getRequestorId) {
47
47
  return {
48
48
  async requestPermission(params) {
49
+ const diffs = extractDiffs(params.toolCall.content);
49
50
  const result = await handlers.onPermissionRequest(
50
51
  channelId,
51
52
  getRequestorId(),
@@ -58,7 +59,8 @@ function createAcpClient(channelId, handlers, getRequestorId) {
58
59
  optionId: o.optionId,
59
60
  name: o.name,
60
61
  kind: o.kind
61
- }))
62
+ })),
63
+ diffs
62
64
  );
63
65
  if (result.outcome === "selected") {
64
66
  return { outcome: { outcome: "selected", optionId: result.optionId } };
@@ -75,20 +77,27 @@ function createAcpClient(channelId, handlers, getRequestorId) {
75
77
  break;
76
78
  }
77
79
  case "tool_call": {
80
+ const toolCallDiffs = extractDiffs(update.content);
81
+ const rawVal = update.rawInput;
82
+ const rawInput = typeof rawVal === "object" && rawVal !== null && !Array.isArray(rawVal) ? rawVal : void 0;
78
83
  handlers.onToolCall(
79
84
  channelId,
80
85
  update.toolCallId,
81
86
  update.title ?? "Unknown",
82
87
  update.kind ?? "other",
83
- update.status ?? "pending"
88
+ update.status ?? "pending",
89
+ toolCallDiffs,
90
+ rawInput
84
91
  );
85
92
  break;
86
93
  }
87
94
  case "tool_call_update": {
95
+ const updateDiffs = extractDiffs(update.content);
88
96
  handlers.onToolCallUpdate(
89
97
  channelId,
90
98
  update.toolCallId,
91
- update.status ?? "in_progress"
99
+ update.status ?? "in_progress",
100
+ updateDiffs
92
101
  );
93
102
  break;
94
103
  }
@@ -96,6 +105,19 @@ function createAcpClient(channelId, handlers, getRequestorId) {
96
105
  }
97
106
  };
98
107
  }
108
+ function extractDiffs(content) {
109
+ if (!Array.isArray(content)) return [];
110
+ const diffs = [];
111
+ for (const item of content) {
112
+ if (item && typeof item === "object" && "type" in item && item.type === "diff") {
113
+ const { path, oldText, newText } = item;
114
+ if (typeof path !== "string" || typeof newText !== "string") continue;
115
+ if (oldText !== void 0 && oldText !== null && typeof oldText !== "string") continue;
116
+ diffs.push({ path, oldText: oldText ?? null, newText });
117
+ }
118
+ }
119
+ return diffs;
120
+ }
99
121
 
100
122
  // src/daemon/session-manager.ts
101
123
  var SessionManager = class {
@@ -250,54 +272,11 @@ import {
250
272
  ButtonStyle,
251
273
  EmbedBuilder
252
274
  } from "discord.js";
253
- var KIND_LABELS = {
254
- allow_once: "\u2705 Allow",
255
- allow_always: "\u2705 Always Allow",
256
- reject_once: "\u274C Reject",
257
- reject_always: "\u274C Never Allow"
258
- };
259
- var KIND_STYLES = {
260
- allow_once: ButtonStyle.Success,
261
- allow_always: ButtonStyle.Success,
262
- reject_once: ButtonStyle.Danger,
263
- reject_always: ButtonStyle.Danger
264
- };
265
- async function sendPermissionRequest(channel, toolTitle, toolKind, options, requestorId, timeoutMs = 14 * 60 * 1e3) {
266
- if (options.length === 0) {
267
- return { outcome: "cancelled" };
268
- }
269
- const embed = new EmbedBuilder().setColor(16753920).setTitle(`Permission: ${toolTitle}`).setDescription(`Tool type: \`${toolKind}\``).setTimestamp();
270
- const buttons = options.map(
271
- (opt) => new ButtonBuilder().setCustomId(`perm_${opt.optionId}`).setLabel(KIND_LABELS[opt.kind] ?? opt.name).setStyle(KIND_STYLES[opt.kind] ?? ButtonStyle.Secondary)
272
- );
273
- const rows = [];
274
- for (let i = 0; i < buttons.length; i += 5) {
275
- rows.push(new ActionRowBuilder().addComponents(buttons.slice(i, i + 5)));
276
- }
277
- const msg = await channel.send({ embeds: [embed], components: rows });
278
- return new Promise((resolve) => {
279
- const collector = msg.createMessageComponentCollector({
280
- filter: (i) => i.user.id === requestorId,
281
- time: timeoutMs
282
- });
283
- collector.on("collect", async (interaction) => {
284
- const optionId = interaction.customId.replace("perm_", "");
285
- await interaction.update({ components: [] });
286
- collector.stop("selected");
287
- resolve({ outcome: "selected", optionId });
288
- });
289
- collector.on("end", (_collected, reason) => {
290
- if (reason === "time") {
291
- msg.edit({ components: [] }).catch(() => {
292
- });
293
- resolve({ outcome: "cancelled" });
294
- }
295
- });
296
- });
297
- }
298
275
 
299
276
  // src/daemon/message-bridge.ts
277
+ import { createTwoFilesPatch } from "diff";
300
278
  var DISCORD_MAX_LENGTH = 2e3;
279
+ var MAX_DIFF_LINES = 150;
301
280
  function splitMessage(text, maxLength = DISCORD_MAX_LENGTH) {
302
281
  if (text.length <= maxLength) return [text];
303
282
  const chunks = [];
@@ -349,10 +328,127 @@ var STATUS_ICONS = {
349
328
  function formatToolSummary(tools) {
350
329
  const lines = [];
351
330
  for (const [, tool] of tools) {
352
- lines.push(`${STATUS_ICONS[tool.status]} ${tool.title}`);
331
+ const detail = extractToolDetail(tool.rawInput);
332
+ const suffix = detail ? ` \xB7 \`${detail}\`` : "";
333
+ lines.push(`${STATUS_ICONS[tool.status]} ${tool.title}${suffix}`);
353
334
  }
354
335
  return lines.join("\n");
355
336
  }
337
+ var MAX_DETAIL_LENGTH = 80;
338
+ var SAFE_FIELDS = ["command", "file_path", "pattern", "query", "path", "url", "description"];
339
+ function extractToolDetail(rawInput) {
340
+ if (!rawInput) return null;
341
+ for (const field of SAFE_FIELDS) {
342
+ if (typeof rawInput[field] === "string" && rawInput[field]) {
343
+ return truncate(sanitizeDetail(rawInput[field]), MAX_DETAIL_LENGTH);
344
+ }
345
+ }
346
+ return null;
347
+ }
348
+ function sanitizeDetail(text) {
349
+ return text.replace(/`/g, "'");
350
+ }
351
+ function truncate(text, max) {
352
+ const firstLine = text.split("\n")[0];
353
+ if (firstLine.length <= max) return firstLine;
354
+ return firstLine.slice(0, max - 1) + "\u2026";
355
+ }
356
+ function formatDiff(diffs, maxLines = MAX_DIFF_LINES) {
357
+ if (diffs.length === 0) return [];
358
+ const parts = [];
359
+ for (const d of diffs) {
360
+ const fileName = d.path.split("/").pop() ?? d.path;
361
+ const oldText = d.oldText ?? "";
362
+ const patch = createTwoFilesPatch(
363
+ d.oldText == null ? "/dev/null" : d.path,
364
+ d.path,
365
+ oldText,
366
+ d.newText,
367
+ void 0,
368
+ void 0,
369
+ { context: 3 }
370
+ );
371
+ const patchLines = patch.split("\n");
372
+ const startIdx = patchLines.findIndex((l) => l.startsWith("---"));
373
+ const diffLines = startIdx >= 0 ? patchLines.slice(startIdx) : patchLines;
374
+ let truncated = false;
375
+ let displayLines = diffLines;
376
+ if (diffLines.length > maxLines) {
377
+ displayLines = diffLines.slice(0, maxLines);
378
+ truncated = true;
379
+ }
380
+ let block = `**${fileName}**
381
+ \`\`\`diff
382
+ ${displayLines.join("\n")}
383
+ \`\`\``;
384
+ if (truncated) {
385
+ block += `
386
+ *... ${diffLines.length - maxLines} more lines*`;
387
+ }
388
+ parts.push(block);
389
+ }
390
+ const fullMessage = parts.join("\n\n");
391
+ return splitMessage(fullMessage);
392
+ }
393
+
394
+ // src/daemon/permission-ui.ts
395
+ var KIND_LABELS = {
396
+ allow_once: "\u2705 Allow",
397
+ allow_always: "\u2705 Always Allow",
398
+ reject_once: "\u274C Reject",
399
+ reject_always: "\u274C Never Allow"
400
+ };
401
+ var KIND_STYLES = {
402
+ allow_once: ButtonStyle.Success,
403
+ allow_always: ButtonStyle.Success,
404
+ reject_once: ButtonStyle.Danger,
405
+ reject_always: ButtonStyle.Danger
406
+ };
407
+ async function sendPermissionRequest(channel, toolTitle, toolKind, options, requestorId, diffs = [], timeoutMs = 14 * 60 * 1e3) {
408
+ if (options.length === 0) {
409
+ return { outcome: "cancelled" };
410
+ }
411
+ let diffsSent = false;
412
+ if (diffs.length > 0) {
413
+ try {
414
+ const diffMessages = formatDiff(diffs);
415
+ for (const msg2 of diffMessages) {
416
+ await channel.send(msg2);
417
+ }
418
+ diffsSent = true;
419
+ } catch (err) {
420
+ console.error("Failed to send permission diffs:", err);
421
+ }
422
+ }
423
+ const embed = new EmbedBuilder().setColor(16753920).setTitle(`Permission: ${toolTitle}`).setDescription(`Tool type: \`${toolKind}\``).setTimestamp();
424
+ const buttons = options.map(
425
+ (opt) => new ButtonBuilder().setCustomId(`perm_${opt.optionId}`).setLabel(KIND_LABELS[opt.kind] ?? opt.name).setStyle(KIND_STYLES[opt.kind] ?? ButtonStyle.Secondary)
426
+ );
427
+ const rows = [];
428
+ for (let i = 0; i < buttons.length; i += 5) {
429
+ rows.push(new ActionRowBuilder().addComponents(buttons.slice(i, i + 5)));
430
+ }
431
+ const msg = await channel.send({ embeds: [embed], components: rows });
432
+ return new Promise((resolve) => {
433
+ const collector = msg.createMessageComponentCollector({
434
+ filter: (i) => i.user.id === requestorId,
435
+ time: timeoutMs
436
+ });
437
+ collector.on("collect", async (interaction) => {
438
+ const optionId = interaction.customId.replace("perm_", "");
439
+ await interaction.update({ components: [] });
440
+ collector.stop("selected");
441
+ resolve({ outcome: "selected", optionId, diffsSent });
442
+ });
443
+ collector.on("end", (_collected, reason) => {
444
+ if (reason === "time") {
445
+ msg.edit({ components: [] }).catch(() => {
446
+ });
447
+ resolve({ outcome: "cancelled", diffsSent });
448
+ }
449
+ });
450
+ });
451
+ }
356
452
 
357
453
  // src/daemon/discord-bot.ts
358
454
  async function startDiscordBot(config) {
@@ -362,19 +458,25 @@ async function startDiscordBot(config) {
362
458
  const replyBuffers = /* @__PURE__ */ new Map();
363
459
  const replyMessages = /* @__PURE__ */ new Map();
364
460
  const flushTimers = /* @__PURE__ */ new Map();
461
+ const pendingDiffs = /* @__PURE__ */ new Map();
462
+ const permissionDiffShown = /* @__PURE__ */ new Map();
365
463
  let discordClient;
366
464
  const handlers = {
367
- onToolCall(channelId, toolCallId, title, _kind, status) {
465
+ onToolCall(channelId, toolCallId, title, _kind, status, diffs, rawInput) {
368
466
  if (!toolStates.has(channelId)) toolStates.set(channelId, /* @__PURE__ */ new Map());
369
- toolStates.get(channelId).set(toolCallId, { title, status });
467
+ toolStates.get(channelId).set(toolCallId, { title, status, rawInput });
468
+ accumulateDiffs(channelId, toolCallId, diffs);
370
469
  updateToolSummaryMessage(channelId);
470
+ if (status === "completed") sendDiffsForTool(channelId, toolCallId);
371
471
  },
372
- onToolCallUpdate(channelId, toolCallId, status) {
472
+ onToolCallUpdate(channelId, toolCallId, status, diffs) {
373
473
  const tools = toolStates.get(channelId);
374
474
  const tool = tools?.get(toolCallId);
375
475
  if (tool) {
376
476
  tool.status = status;
477
+ accumulateDiffs(channelId, toolCallId, diffs);
377
478
  updateToolSummaryMessage(channelId);
479
+ if (status === "completed") sendDiffsForTool(channelId, toolCallId);
378
480
  }
379
481
  },
380
482
  onAgentMessageChunk(channelId, text) {
@@ -382,10 +484,15 @@ async function startDiscordBot(config) {
382
484
  replyBuffers.set(channelId, current + text);
383
485
  scheduleFlushReply(channelId);
384
486
  },
385
- async onPermissionRequest(channelId, requestorId, toolCall, options) {
487
+ async onPermissionRequest(channelId, requestorId, toolCall, options, diffs) {
386
488
  const channel = await fetchChannel(channelId);
387
489
  if (!channel) return { outcome: "cancelled" };
388
- return sendPermissionRequest(channel, toolCall.title, toolCall.kind, options, requestorId);
490
+ const result = await sendPermissionRequest(channel, toolCall.title, toolCall.kind, options, requestorId, diffs);
491
+ if (result.diffsSent) {
492
+ if (!permissionDiffShown.has(channelId)) permissionDiffShown.set(channelId, /* @__PURE__ */ new Set());
493
+ permissionDiffShown.get(channelId).add(toolCall.toolCallId);
494
+ }
495
+ return result;
389
496
  },
390
497
  onPromptComplete(channelId, _stopReason) {
391
498
  flushReply(channelId, true);
@@ -394,9 +501,36 @@ async function startDiscordBot(config) {
394
501
  toolSummaryMessages.delete(channelId);
395
502
  replyBuffers.delete(channelId);
396
503
  replyMessages.delete(channelId);
504
+ pendingDiffs.delete(channelId);
505
+ permissionDiffShown.delete(channelId);
397
506
  }
398
507
  };
399
508
  const sessionManager = new SessionManager(handlers);
509
+ function accumulateDiffs(channelId, toolCallId, diffs) {
510
+ if (diffs.length === 0) return;
511
+ if (!pendingDiffs.has(channelId)) pendingDiffs.set(channelId, /* @__PURE__ */ new Map());
512
+ const channelDiffs = pendingDiffs.get(channelId);
513
+ const existing = channelDiffs.get(toolCallId) ?? [];
514
+ channelDiffs.set(toolCallId, existing.concat(diffs));
515
+ }
516
+ async function sendDiffsForTool(channelId, toolCallId) {
517
+ const shownSet = permissionDiffShown.get(channelId);
518
+ if (shownSet?.has(toolCallId)) {
519
+ shownSet.delete(toolCallId);
520
+ pendingDiffs.get(channelId)?.delete(toolCallId);
521
+ return;
522
+ }
523
+ const channelDiffs = pendingDiffs.get(channelId);
524
+ const diffs = channelDiffs?.get(toolCallId);
525
+ if (!diffs || diffs.length === 0) return;
526
+ const channel = await fetchChannel(channelId);
527
+ if (!channel) return;
528
+ const messages = formatDiff(diffs);
529
+ for (const msg of messages) {
530
+ await channel.send({ content: msg, allowedMentions: { parse: [] } });
531
+ }
532
+ channelDiffs.delete(toolCallId);
533
+ }
400
534
  async function fetchChannel(channelId) {
401
535
  const cached = discordClient.channels.cache.get(channelId);
402
536
  if (cached) return cached;
@@ -416,12 +550,13 @@ async function startDiscordBot(config) {
416
550
  const stopButton = new ActionRowBuilder2().addComponents(
417
551
  new ButtonBuilder2().setCustomId(`stop_${channelId}`).setLabel("\u23F9 Stop").setStyle(ButtonStyle2.Secondary)
418
552
  );
553
+ const noMentions = { parse: [] };
419
554
  const existing = toolSummaryMessages.get(channelId);
420
555
  if (existing) {
421
- await existing.edit({ content, components: [stopButton] }).catch(() => {
556
+ await existing.edit({ content, components: [stopButton], allowedMentions: noMentions }).catch(() => {
422
557
  });
423
558
  } else {
424
- const msg = await channel.send({ content, components: [stopButton] });
559
+ const msg = await channel.send({ content, components: [stopButton], allowedMentions: noMentions });
425
560
  toolSummaryMessages.set(channelId, msg);
426
561
  }
427
562
  }
@@ -430,7 +565,7 @@ async function startDiscordBot(config) {
430
565
  if (msg) {
431
566
  const tools = toolStates.get(channelId);
432
567
  const content = tools ? formatToolSummary(tools) : msg.content;
433
- await msg.edit({ content, components: [] }).catch(() => {
568
+ await msg.edit({ content, components: [], allowedMentions: { parse: [] } }).catch(() => {
434
569
  });
435
570
  }
436
571
  }
@@ -488,12 +623,13 @@ async function startDiscordBot(config) {
488
623
  const askCommand = new SlashCommandBuilder().setName("ask").setDescription("Ask the coding agent a question").addStringOption(
489
624
  (opt) => opt.setName("message").setDescription("Your message").setRequired(true)
490
625
  );
626
+ const clearCommand = new SlashCommandBuilder().setName("clear").setDescription("Clear the agent session and start fresh");
491
627
  const rest = new REST().setToken(config.discord.token);
492
628
  try {
493
629
  await rest.put(Routes.applicationCommands(c.application.id), {
494
- body: [askCommand.toJSON()]
630
+ body: [askCommand.toJSON(), clearCommand.toJSON()]
495
631
  });
496
- console.log("Registered /ask command");
632
+ console.log("Registered /ask and /clear commands");
497
633
  } catch (err) {
498
634
  console.error("Failed to register commands:", err);
499
635
  }
@@ -558,6 +694,22 @@ async function startDiscordBot(config) {
558
694
  });
559
695
  }
560
696
  });
697
+ discordClient.on(Events.InteractionCreate, async (interaction) => {
698
+ if (!interaction.isChatInputCommand()) return;
699
+ if (interaction.commandName !== "clear") return;
700
+ const channelId = interaction.channelId;
701
+ sessionManager.teardown(channelId);
702
+ toolStates.delete(channelId);
703
+ toolSummaryMessages.delete(channelId);
704
+ replyBuffers.delete(channelId);
705
+ replyMessages.delete(channelId);
706
+ pendingDiffs.delete(channelId);
707
+ permissionDiffShown.delete(channelId);
708
+ const timer = flushTimers.get(channelId);
709
+ if (timer) clearTimeout(timer);
710
+ flushTimers.delete(channelId);
711
+ await interaction.reply("Session cleared. Next message will start a fresh agent.");
712
+ });
561
713
  process.on("SIGTERM", () => {
562
714
  sessionManager.teardownAll();
563
715
  discordClient.destroy();
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/daemon/index.ts","../src/daemon/discord-bot.ts","../src/daemon/channel-router.ts","../src/daemon/session-manager.ts","../src/daemon/acp-client.ts","../src/daemon/permission-ui.ts","../src/daemon/message-bridge.ts"],"sourcesContent":["import { join } from \"node:path\";\nimport { homedir } from \"node:os\";\nimport { loadConfig } from \"../shared/config.js\";\nimport { writePid, removePid } from \"../cli/pid.js\";\nimport { startDiscordBot } from \"./discord-bot.js\";\n\nconst CONFIG_DIR = join(homedir(), \".acp-discord\");\nconst CONFIG_PATH = join(CONFIG_DIR, \"config.toml\");\nconst PID_PATH = join(CONFIG_DIR, \"daemon.pid\");\n\nexport async function runDaemon(): Promise<void> {\n // Load config first — if it fails, no stale PID file is left behind (#12)\n const config = loadConfig(CONFIG_PATH);\n\n writePid(PID_PATH, process.pid);\n process.on(\"exit\", () => removePid(PID_PATH));\n\n console.log(`acp-discord daemon started (PID: ${process.pid})`);\n console.log(`Loaded config: ${Object.keys(config.channels).length} channel(s)`);\n\n await startDiscordBot(config);\n}\n\nif (process.env.ACP_DISCORD_DAEMON === \"1\") {\n runDaemon().catch((err) => {\n console.error(\"Daemon failed:\", err);\n process.exit(1);\n });\n}\n","import {\n Client,\n GatewayIntentBits,\n Events,\n REST,\n Routes,\n SlashCommandBuilder,\n ActionRowBuilder,\n ButtonBuilder,\n ButtonStyle,\n type Message,\n type TextChannel,\n} from \"discord.js\";\nimport type { AppConfig } from \"../shared/types.js\";\nimport { ChannelRouter } from \"./channel-router.js\";\nimport { SessionManager } from \"./session-manager.js\";\nimport { sendPermissionRequest } from \"./permission-ui.js\";\nimport { splitMessage, formatToolSummary, type ToolStatus } from \"./message-bridge.js\";\nimport type { AcpEventHandlers } from \"./acp-client.js\";\n\nexport async function startDiscordBot(config: AppConfig): Promise<void> {\n const router = new ChannelRouter(config);\n\n // Per-channel state for display\n const toolStates = new Map<string, Map<string, { title: string; status: ToolStatus }>>();\n const toolSummaryMessages = new Map<string, Message>();\n const replyBuffers = new Map<string, string>();\n const replyMessages = new Map<string, Message>();\n const flushTimers = new Map<string, NodeJS.Timeout>();\n\n let discordClient: Client;\n\n const handlers: AcpEventHandlers = {\n onToolCall(channelId, toolCallId, title, _kind, status) {\n if (!toolStates.has(channelId)) toolStates.set(channelId, new Map());\n toolStates.get(channelId)!.set(toolCallId, { title, status: status as ToolStatus });\n updateToolSummaryMessage(channelId);\n },\n\n onToolCallUpdate(channelId, toolCallId, status) {\n const tools = toolStates.get(channelId);\n const tool = tools?.get(toolCallId);\n if (tool) {\n tool.status = status as ToolStatus;\n updateToolSummaryMessage(channelId);\n }\n },\n\n onAgentMessageChunk(channelId, text) {\n const current = replyBuffers.get(channelId) ?? \"\";\n replyBuffers.set(channelId, current + text);\n scheduleFlushReply(channelId);\n },\n\n async onPermissionRequest(channelId, requestorId, toolCall, options) {\n const channel = await fetchChannel(channelId);\n if (!channel) return { outcome: \"cancelled\" as const };\n return sendPermissionRequest(channel, toolCall.title, toolCall.kind, options, requestorId);\n },\n\n onPromptComplete(channelId, _stopReason) {\n // Final flush\n flushReply(channelId, true);\n // Remove stop button from tool summary\n removeStopButton(channelId);\n // Clear state for next turn\n toolStates.delete(channelId);\n toolSummaryMessages.delete(channelId);\n replyBuffers.delete(channelId);\n replyMessages.delete(channelId);\n },\n };\n\n const sessionManager = new SessionManager(handlers);\n\n // --- Display helpers ---\n\n async function fetchChannel(channelId: string): Promise<TextChannel | null> {\n const cached = discordClient.channels.cache.get(channelId) as TextChannel | undefined;\n if (cached) return cached;\n try {\n const fetched = await discordClient.channels.fetch(channelId);\n return fetched as TextChannel;\n } catch {\n return null;\n }\n }\n\n async function updateToolSummaryMessage(channelId: string) {\n const tools = toolStates.get(channelId);\n if (!tools) return;\n\n const content = formatToolSummary(tools);\n const channel = await fetchChannel(channelId);\n if (!channel) return;\n\n const stopButton = new ActionRowBuilder<ButtonBuilder>().addComponents(\n new ButtonBuilder()\n .setCustomId(`stop_${channelId}`)\n .setLabel(\"\\u23F9 Stop\")\n .setStyle(ButtonStyle.Secondary),\n );\n\n const existing = toolSummaryMessages.get(channelId);\n if (existing) {\n await existing.edit({ content, components: [stopButton] }).catch(() => {});\n } else {\n const msg = await channel.send({ content, components: [stopButton] });\n toolSummaryMessages.set(channelId, msg);\n }\n }\n\n async function removeStopButton(channelId: string) {\n const msg = toolSummaryMessages.get(channelId);\n if (msg) {\n const tools = toolStates.get(channelId);\n const content = tools ? formatToolSummary(tools) : msg.content;\n await msg.edit({ content, components: [] }).catch(() => {});\n }\n }\n\n function scheduleFlushReply(channelId: string) {\n if (flushTimers.has(channelId)) return;\n flushTimers.set(\n channelId,\n setTimeout(() => {\n flushTimers.delete(channelId);\n flushReply(channelId, false);\n }, 500),\n );\n }\n\n async function flushReply(channelId: string, final: boolean) {\n const timer = flushTimers.get(channelId);\n if (timer) {\n clearTimeout(timer);\n flushTimers.delete(channelId);\n }\n\n const buffer = replyBuffers.get(channelId);\n if (!buffer) return;\n\n const channel = await fetchChannel(channelId);\n if (!channel) return;\n\n if (final) {\n // Send final reply as new message(s), delete streaming message\n const existing = replyMessages.get(channelId);\n if (existing) await existing.delete().catch(() => {});\n replyMessages.delete(channelId);\n\n const chunks = splitMessage(buffer);\n for (const chunk of chunks) {\n await channel.send(chunk);\n }\n replyBuffers.delete(channelId);\n } else {\n // Streaming update: edit existing message\n const truncated = buffer.length > 2000 ? buffer.slice(buffer.length - 1900) + \"...\" : buffer;\n const existing = replyMessages.get(channelId);\n if (existing) {\n await existing.edit(truncated).catch(() => {});\n } else {\n const msg = await channel.send(truncated);\n replyMessages.set(channelId, msg);\n }\n }\n }\n\n // --- Discord client setup ---\n\n discordClient = new Client({\n intents: [\n GatewayIntentBits.Guilds,\n GatewayIntentBits.GuildMessages,\n GatewayIntentBits.MessageContent,\n ],\n });\n\n discordClient.on(Events.ClientReady, async (c) => {\n console.log(`Discord bot ready: ${c.user.tag}`);\n\n // Register /ask slash command\n const askCommand = new SlashCommandBuilder()\n .setName(\"ask\")\n .setDescription(\"Ask the coding agent a question\")\n .addStringOption((opt) =>\n opt.setName(\"message\").setDescription(\"Your message\").setRequired(true),\n );\n\n const rest = new REST().setToken(config.discord.token);\n try {\n await rest.put(Routes.applicationCommands(c.application.id), {\n body: [askCommand.toJSON()],\n });\n console.log(\"Registered /ask command\");\n } catch (err) {\n console.error(\"Failed to register commands:\", err);\n }\n });\n\n // Handle @mention messages in configured channels\n discordClient.on(Events.MessageCreate, async (message: Message) => {\n if (message.author.bot) return;\n\n const channelId = message.channelId;\n const resolved = router.resolve(channelId);\n if (!resolved) return;\n\n const isMention = message.mentions.has(discordClient.user!);\n if (!resolved.autoReply && !isMention) return;\n\n // Strip mention prefix if present\n const text = message.content.replace(/<@!?\\d+>/g, \"\").trim();\n\n if (!text) {\n await message.reply(\"Please provide a message.\");\n return;\n }\n\n if (sessionManager.isPrompting(channelId)) {\n await message.reply(\"\\u23F3 Agent is working. Your message has been queued.\");\n }\n\n try {\n await sessionManager.prompt(channelId, text, resolved.agent, message.author.id);\n } catch (err) {\n console.error(`Prompt failed for channel ${channelId}:`, err);\n await message.reply(\"An error occurred while processing your request.\").catch(() => {});\n }\n });\n\n // Handle stop button clicks\n discordClient.on(Events.InteractionCreate, async (interaction) => {\n if (!interaction.isButton()) return;\n\n if (interaction.customId.startsWith(\"stop_\")) {\n const channelId = interaction.customId.replace(\"stop_\", \"\");\n const activeRequestor = sessionManager.getActiveRequestorId(channelId);\n\n // Only the user who triggered the current prompt can stop it\n if (activeRequestor && interaction.user.id !== activeRequestor) {\n await interaction.reply({ content: \"Only the user who started this prompt can stop it.\", ephemeral: true });\n return;\n }\n\n sessionManager.cancel(channelId);\n await interaction.update({ components: [] });\n }\n });\n\n // Handle /ask command\n discordClient.on(Events.InteractionCreate, async (interaction) => {\n if (!interaction.isChatInputCommand()) return;\n if (interaction.commandName !== \"ask\") return;\n\n const channelId = interaction.channelId;\n const resolved = router.resolve(channelId);\n if (!resolved) {\n await interaction.reply({ content: \"This channel is not configured for ACP.\", ephemeral: true });\n return;\n }\n\n const text = interaction.options.getString(\"message\", true);\n await interaction.deferReply();\n\n if (sessionManager.isPrompting(channelId)) {\n await interaction.editReply(\"\\u23F3 Agent is working. Your message has been queued.\");\n } else {\n await interaction.editReply(`\\uD83D\\uDCAC Processing: ${text.slice(0, 100)}...`);\n }\n\n try {\n await sessionManager.prompt(channelId, text, resolved.agent, interaction.user.id);\n } catch (err) {\n console.error(`Prompt failed for channel ${channelId}:`, err);\n await interaction.followUp({ content: \"An error occurred while processing your request.\", ephemeral: true }).catch(() => {});\n }\n });\n\n // Graceful shutdown\n process.on(\"SIGTERM\", () => {\n sessionManager.teardownAll();\n discordClient.destroy();\n });\n\n process.on(\"SIGINT\", () => {\n sessionManager.teardownAll();\n discordClient.destroy();\n });\n\n try {\n await discordClient.login(config.discord.token);\n } catch (err: unknown) {\n const message = err instanceof Error ? err.message : String(err);\n if (message.includes(\"TOKEN_INVALID\") || message.includes(\"An invalid token was provided\")) {\n console.error(\"Error: Invalid Discord bot token. Check your config.toml.\");\n } else if (message.includes(\"ConnectTimeout\") || message.includes(\"ETIMEDOUT\") || message.includes(\"ECONNREFUSED\")) {\n console.error(\"Error: Cannot connect to Discord API. Check your network or proxy settings.\");\n console.error(\"Hint: Set HTTPS_PROXY=http://127.0.0.1:7890 if you need a proxy.\");\n } else {\n console.error(\"Error: Failed to connect to Discord:\", message);\n }\n process.exit(1);\n }\n}\n","import type { AppConfig, ResolvedChannelConfig } from \"../shared/types.js\";\nimport { resolveChannelConfig } from \"../shared/config.js\";\n\nexport class ChannelRouter {\n private config: AppConfig;\n\n constructor(config: AppConfig) {\n this.config = config;\n }\n\n resolve(channelId: string): ResolvedChannelConfig | null {\n return resolveChannelConfig(this.config, channelId);\n }\n\n isConfigured(channelId: string): boolean {\n return this.resolve(channelId) !== null;\n }\n}\n","import { spawn, type ChildProcess } from \"node:child_process\";\nimport { Readable, Writable } from \"node:stream\";\nimport { ClientSideConnection, ndJsonStream, PROTOCOL_VERSION } from \"@agentclientprotocol/sdk\";\nimport type { AgentConfig } from \"../shared/types.js\";\nimport { createAcpClient, type AcpEventHandlers } from \"./acp-client.js\";\n\ninterface ManagedSession {\n channelId: string;\n process: ChildProcess;\n connection: ClientSideConnection;\n sessionId: string;\n lastActivity: number;\n idleTimer: NodeJS.Timeout;\n prompting: boolean;\n queue: Array<{ text: string; requestorId: string }>;\n /** Set only when executePrompt begins — stable for the duration of the prompt */\n activePromptRequestorId: string;\n}\n\nexport class SessionManager {\n private sessions = new Map<string, ManagedSession>();\n private handlers: AcpEventHandlers;\n\n constructor(handlers: AcpEventHandlers) {\n this.handlers = handlers;\n }\n\n async prompt(channelId: string, text: string, agentConfig: AgentConfig, requestorId: string): Promise<string> {\n const session = await this.getOrCreate(channelId, agentConfig, requestorId);\n session.lastActivity = Date.now();\n this.resetIdleTimer(session, agentConfig.idle_timeout);\n\n if (session.prompting) {\n session.queue.push({ text, requestorId });\n return \"queued\";\n }\n\n return this.executePrompt(session, text, requestorId, agentConfig);\n }\n\n private async executePrompt(session: ManagedSession, text: string, requestorId: string, agentConfig: AgentConfig): Promise<string> {\n session.prompting = true;\n session.activePromptRequestorId = requestorId;\n try {\n const result = await session.connection.prompt({\n sessionId: session.sessionId,\n prompt: [{ type: \"text\", text }],\n });\n this.handlers.onPromptComplete(session.channelId, result.stopReason);\n return result.stopReason;\n } finally {\n session.prompting = false;\n // Process queue — await and catch to prevent unhandled rejections (#3)\n const next = session.queue.shift();\n if (next) {\n this.executePrompt(session, next.text, next.requestorId, agentConfig).catch((err) => {\n console.error(`Queued prompt failed for channel ${session.channelId}:`, err);\n });\n }\n }\n }\n\n cancel(channelId: string): void {\n const session = this.sessions.get(channelId);\n if (session) {\n session.connection.cancel({ sessionId: session.sessionId });\n }\n }\n\n private async getOrCreate(channelId: string, agentConfig: AgentConfig, requestorId: string): Promise<ManagedSession> {\n const existing = this.sessions.get(channelId);\n if (existing) return existing;\n return this.createSession(channelId, agentConfig, requestorId);\n }\n\n private async createSession(channelId: string, config: AgentConfig, requestorId: string): Promise<ManagedSession> {\n const proc = spawn(config.command, config.args, {\n stdio: [\"pipe\", \"pipe\", \"inherit\"],\n cwd: config.cwd,\n });\n\n // Handle spawn errors (ENOENT, permission denied, etc.) (#4)\n proc.on(\"error\", (err) => {\n console.error(`Agent process error for channel ${channelId}:`, err);\n const session = this.sessions.get(channelId);\n if (session?.process === proc) {\n clearTimeout(session.idleTimer);\n this.sessions.delete(channelId);\n }\n });\n\n proc.on(\"exit\", () => {\n const session = this.sessions.get(channelId);\n if (session?.process === proc) {\n this.sessions.delete(channelId);\n clearTimeout(session.idleTimer);\n }\n });\n\n // Wrap initialize/newSession in try/catch to clean up process on failure (#5)\n let connection: ClientSideConnection;\n let sessionId: string;\n try {\n const stream = ndJsonStream(\n Writable.toWeb(proc.stdin!) as WritableStream<Uint8Array>,\n Readable.toWeb(proc.stdout!) as ReadableStream<Uint8Array>,\n );\n\n const client = createAcpClient(channelId, this.handlers, () => {\n return this.sessions.get(channelId)?.activePromptRequestorId ?? requestorId;\n });\n connection = new ClientSideConnection((_agent) => client, stream);\n\n await connection.initialize({\n protocolVersion: PROTOCOL_VERSION,\n clientCapabilities: {\n fs: { readTextFile: true, writeTextFile: true },\n terminal: true,\n },\n clientInfo: {\n name: \"acp-discord\",\n title: \"ACP Discord Bot\",\n version: \"0.1.0\",\n },\n });\n\n const result = await connection.newSession({\n cwd: config.cwd,\n mcpServers: [],\n });\n sessionId = result.sessionId;\n } catch (err) {\n proc.kill();\n throw err;\n }\n\n const managed: ManagedSession = {\n channelId,\n process: proc,\n connection,\n sessionId,\n lastActivity: Date.now(),\n idleTimer: this.startIdleTimer(channelId, config.idle_timeout),\n prompting: false,\n queue: [],\n activePromptRequestorId: requestorId,\n };\n\n this.sessions.set(channelId, managed);\n return managed;\n }\n\n private startIdleTimer(channelId: string, timeoutSec: number): NodeJS.Timeout {\n return setTimeout(() => this.teardown(channelId), timeoutSec * 1000);\n }\n\n private resetIdleTimer(session: ManagedSession, timeoutSec: number): void {\n clearTimeout(session.idleTimer);\n session.idleTimer = this.startIdleTimer(session.channelId, timeoutSec);\n }\n\n teardown(channelId: string): void {\n const session = this.sessions.get(channelId);\n if (!session) return;\n clearTimeout(session.idleTimer);\n session.process.kill();\n this.sessions.delete(channelId);\n }\n\n teardownAll(): void {\n for (const channelId of this.sessions.keys()) {\n this.teardown(channelId);\n }\n }\n\n isPrompting(channelId: string): boolean {\n return this.sessions.get(channelId)?.prompting ?? false;\n }\n\n getActiveRequestorId(channelId: string): string | null {\n const session = this.sessions.get(channelId);\n if (!session?.prompting) return null;\n return session.activePromptRequestorId;\n }\n\n getActiveChannels(): string[] {\n return Array.from(this.sessions.keys());\n }\n}\n","import type {\n Client,\n RequestPermissionRequest,\n RequestPermissionResponse,\n SessionNotification,\n} from \"@agentclientprotocol/sdk\";\n\nexport interface AcpEventHandlers {\n onToolCall(channelId: string, toolCallId: string, title: string, kind: string, status: string): void;\n onToolCallUpdate(channelId: string, toolCallId: string, status: string): void;\n onAgentMessageChunk(channelId: string, text: string): void;\n onPermissionRequest(\n channelId: string,\n requestorId: string,\n toolCall: { toolCallId: string; title: string; kind: string },\n options: Array<{ optionId: string; name: string; kind: string }>,\n ): Promise<{ outcome: \"selected\"; optionId: string } | { outcome: \"cancelled\" }>;\n onPromptComplete(channelId: string, stopReason: string): void;\n}\n\nexport function createAcpClient(\n channelId: string,\n handlers: AcpEventHandlers,\n getRequestorId: () => string,\n): Client {\n return {\n async requestPermission(params: RequestPermissionRequest): Promise<RequestPermissionResponse> {\n const result = await handlers.onPermissionRequest(\n channelId,\n getRequestorId(),\n {\n toolCallId: params.toolCall.toolCallId,\n title: params.toolCall.title ?? \"Unknown\",\n kind: params.toolCall.kind ?? \"other\",\n },\n params.options.map((o: { optionId: string; name: string; kind: string }) => ({\n optionId: o.optionId,\n name: o.name,\n kind: o.kind,\n })),\n );\n\n if (result.outcome === \"selected\") {\n return { outcome: { outcome: \"selected\", optionId: result.optionId } };\n }\n return { outcome: { outcome: \"cancelled\" } };\n },\n\n async sessionUpdate(params: SessionNotification): Promise<void> {\n const update = params.update;\n switch (update.sessionUpdate) {\n case \"agent_message_chunk\": {\n if (update.content.type === \"text\") {\n handlers.onAgentMessageChunk(channelId, update.content.text);\n }\n break;\n }\n case \"tool_call\": {\n handlers.onToolCall(\n channelId,\n update.toolCallId,\n update.title ?? \"Unknown\",\n update.kind ?? \"other\",\n update.status ?? \"pending\",\n );\n break;\n }\n case \"tool_call_update\": {\n handlers.onToolCallUpdate(\n channelId,\n update.toolCallId,\n update.status ?? \"in_progress\",\n );\n break;\n }\n }\n },\n };\n}\n","import {\n ActionRowBuilder,\n ButtonBuilder,\n ButtonStyle,\n EmbedBuilder,\n type TextChannel,\n} from \"discord.js\";\n\nconst KIND_LABELS: Record<string, string> = {\n allow_once: \"\\u2705 Allow\",\n allow_always: \"\\u2705 Always Allow\",\n reject_once: \"\\u274C Reject\",\n reject_always: \"\\u274C Never Allow\",\n};\n\nconst KIND_STYLES: Record<string, ButtonStyle> = {\n allow_once: ButtonStyle.Success,\n allow_always: ButtonStyle.Success,\n reject_once: ButtonStyle.Danger,\n reject_always: ButtonStyle.Danger,\n};\n\nexport interface PermissionOption {\n optionId: string;\n name: string;\n kind: string;\n}\n\nexport async function sendPermissionRequest(\n channel: TextChannel,\n toolTitle: string,\n toolKind: string,\n options: PermissionOption[],\n requestorId: string,\n timeoutMs = 14 * 60 * 1000,\n): Promise<{ outcome: \"selected\"; optionId: string } | { outcome: \"cancelled\" }> {\n if (options.length === 0) {\n return { outcome: \"cancelled\" };\n }\n\n const embed = new EmbedBuilder()\n .setColor(0xffa500)\n .setTitle(`Permission: ${toolTitle}`)\n .setDescription(`Tool type: \\`${toolKind}\\``)\n .setTimestamp();\n\n const buttons = options.map((opt) =>\n new ButtonBuilder()\n .setCustomId(`perm_${opt.optionId}`)\n .setLabel(KIND_LABELS[opt.kind] ?? opt.name)\n .setStyle(KIND_STYLES[opt.kind] ?? ButtonStyle.Secondary),\n );\n\n // Discord allows max 5 buttons per ActionRow\n const rows: ActionRowBuilder<ButtonBuilder>[] = [];\n for (let i = 0; i < buttons.length; i += 5) {\n rows.push(new ActionRowBuilder<ButtonBuilder>().addComponents(buttons.slice(i, i + 5)));\n }\n\n const msg = await channel.send({ embeds: [embed], components: rows });\n\n return new Promise((resolve) => {\n const collector = msg.createMessageComponentCollector({\n filter: (i) => i.user.id === requestorId,\n time: timeoutMs,\n });\n\n collector.on(\"collect\", async (interaction) => {\n const optionId = interaction.customId.replace(\"perm_\", \"\");\n await interaction.update({ components: [] });\n collector.stop(\"selected\");\n resolve({ outcome: \"selected\", optionId });\n });\n\n collector.on(\"end\", (_collected, reason) => {\n if (reason === \"time\") {\n msg.edit({ components: [] }).catch(() => {});\n resolve({ outcome: \"cancelled\" });\n }\n });\n });\n}\n","const DISCORD_MAX_LENGTH = 2000;\n\nexport function splitMessage(text: string, maxLength = DISCORD_MAX_LENGTH): string[] {\n if (text.length <= maxLength) return [text];\n\n const chunks: string[] = [];\n let remaining = text;\n let inCodeBlock = false;\n let codeFence = \"\";\n\n while (remaining.length > 0) {\n if (remaining.length <= maxLength) {\n chunks.push(remaining);\n break;\n }\n\n // Find split point: prefer newline before maxLength\n let splitAt = maxLength;\n const lastNewline = remaining.lastIndexOf(\"\\n\", maxLength);\n if (lastNewline > maxLength * 0.5) {\n splitAt = lastNewline + 1;\n }\n\n let chunk = remaining.slice(0, splitAt);\n remaining = remaining.slice(splitAt);\n\n // Handle code blocks: count fences in this chunk\n const fenceMatches = chunk.match(/```\\w*/g) || [];\n for (const fence of fenceMatches) {\n if (!inCodeBlock) {\n inCodeBlock = true;\n codeFence = fence;\n } else {\n inCodeBlock = false;\n codeFence = \"\";\n }\n }\n\n // If we're inside a code block at the split, close and reopen\n if (inCodeBlock) {\n chunk += \"\\n```\";\n remaining = codeFence + \"\\n\" + remaining;\n inCodeBlock = false;\n codeFence = \"\";\n }\n\n chunks.push(chunk);\n }\n\n return chunks;\n}\n\nexport type ToolStatus = \"pending\" | \"in_progress\" | \"completed\" | \"failed\";\n\nconst STATUS_ICONS: Record<ToolStatus, string> = {\n pending: \"\\u23F3\", // ⏳\n in_progress: \"\\uD83D\\uDD04\", // 🔄\n completed: \"\\u2705\", // ✅\n failed: \"\\u274C\", // ❌\n};\n\nexport function formatToolSummary(\n tools: Map<string, { title: string; status: ToolStatus }>,\n): string {\n const lines: string[] = [];\n for (const [, tool] of tools) {\n lines.push(`${STATUS_ICONS[tool.status]} ${tool.title}`);\n }\n return lines.join(\"\\n\");\n}\n"],"mappings":"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","names":["ActionRowBuilder","ButtonBuilder","ButtonStyle","ActionRowBuilder","ButtonBuilder","ButtonStyle"]}
1
+ {"version":3,"sources":["../src/daemon/index.ts","../src/daemon/discord-bot.ts","../src/daemon/channel-router.ts","../src/daemon/session-manager.ts","../src/daemon/acp-client.ts","../src/daemon/permission-ui.ts","../src/daemon/message-bridge.ts"],"sourcesContent":["import { join } from \"node:path\";\nimport { homedir } from \"node:os\";\nimport { loadConfig } from \"../shared/config.js\";\nimport { writePid, removePid } from \"../cli/pid.js\";\nimport { startDiscordBot } from \"./discord-bot.js\";\n\nconst CONFIG_DIR = join(homedir(), \".acp-discord\");\nconst CONFIG_PATH = join(CONFIG_DIR, \"config.toml\");\nconst PID_PATH = join(CONFIG_DIR, \"daemon.pid\");\n\nexport async function runDaemon(): Promise<void> {\n // Load config first — if it fails, no stale PID file is left behind (#12)\n const config = loadConfig(CONFIG_PATH);\n\n writePid(PID_PATH, process.pid);\n process.on(\"exit\", () => removePid(PID_PATH));\n\n console.log(`acp-discord daemon started (PID: ${process.pid})`);\n console.log(`Loaded config: ${Object.keys(config.channels).length} channel(s)`);\n\n await startDiscordBot(config);\n}\n\nif (process.env.ACP_DISCORD_DAEMON === \"1\") {\n runDaemon().catch((err) => {\n console.error(\"Daemon failed:\", err);\n process.exit(1);\n });\n}\n","import {\n Client,\n GatewayIntentBits,\n Events,\n REST,\n Routes,\n SlashCommandBuilder,\n ActionRowBuilder,\n ButtonBuilder,\n ButtonStyle,\n type Message,\n type TextChannel,\n} from \"discord.js\";\nimport type { AppConfig } from \"../shared/types.js\";\nimport { ChannelRouter } from \"./channel-router.js\";\nimport { SessionManager } from \"./session-manager.js\";\nimport { sendPermissionRequest } from \"./permission-ui.js\";\nimport { splitMessage, formatToolSummary, formatDiff, type ToolStatus } from \"./message-bridge.js\";\nimport type { AcpEventHandlers, DiffContent } from \"./acp-client.js\";\n\nexport async function startDiscordBot(config: AppConfig): Promise<void> {\n const router = new ChannelRouter(config);\n\n // Per-channel state for display\n const toolStates = new Map<string, Map<string, { title: string; status: ToolStatus; rawInput?: Record<string, unknown> }>>();\n const toolSummaryMessages = new Map<string, Message>();\n const replyBuffers = new Map<string, string>();\n const replyMessages = new Map<string, Message>();\n const flushTimers = new Map<string, NodeJS.Timeout>();\n // channelId -> toolCallId -> DiffContent[]\n const pendingDiffs = new Map<string, Map<string, DiffContent[]>>();\n // channelId -> Set of toolCallIds whose diffs were already shown at permission-request time\n const permissionDiffShown = new Map<string, Set<string>>();\n\n let discordClient: Client;\n\n const handlers: AcpEventHandlers = {\n onToolCall(channelId, toolCallId, title, _kind, status, diffs, rawInput) {\n if (!toolStates.has(channelId)) toolStates.set(channelId, new Map());\n toolStates.get(channelId)!.set(toolCallId, { title, status: status as ToolStatus, rawInput });\n accumulateDiffs(channelId, toolCallId, diffs);\n updateToolSummaryMessage(channelId);\n if (status === \"completed\") sendDiffsForTool(channelId, toolCallId);\n },\n\n onToolCallUpdate(channelId, toolCallId, status, diffs) {\n const tools = toolStates.get(channelId);\n const tool = tools?.get(toolCallId);\n if (tool) {\n tool.status = status as ToolStatus;\n accumulateDiffs(channelId, toolCallId, diffs);\n updateToolSummaryMessage(channelId);\n if (status === \"completed\") sendDiffsForTool(channelId, toolCallId);\n }\n },\n\n onAgentMessageChunk(channelId, text) {\n const current = replyBuffers.get(channelId) ?? \"\";\n replyBuffers.set(channelId, current + text);\n scheduleFlushReply(channelId);\n },\n\n async onPermissionRequest(channelId, requestorId, toolCall, options, diffs) {\n const channel = await fetchChannel(channelId);\n if (!channel) return { outcome: \"cancelled\" as const };\n const result = await sendPermissionRequest(channel, toolCall.title, toolCall.kind, options, requestorId, diffs);\n if (result.diffsSent) {\n if (!permissionDiffShown.has(channelId)) permissionDiffShown.set(channelId, new Set());\n permissionDiffShown.get(channelId)!.add(toolCall.toolCallId);\n }\n return result;\n },\n\n onPromptComplete(channelId, _stopReason) {\n // Final flush\n flushReply(channelId, true);\n // Remove stop button from tool summary\n removeStopButton(channelId);\n // Clear state for next turn\n toolStates.delete(channelId);\n toolSummaryMessages.delete(channelId);\n replyBuffers.delete(channelId);\n replyMessages.delete(channelId);\n pendingDiffs.delete(channelId);\n permissionDiffShown.delete(channelId);\n },\n };\n\n const sessionManager = new SessionManager(handlers);\n\n // --- Display helpers ---\n\n function accumulateDiffs(channelId: string, toolCallId: string, diffs: DiffContent[]) {\n if (diffs.length === 0) return;\n if (!pendingDiffs.has(channelId)) pendingDiffs.set(channelId, new Map());\n const channelDiffs = pendingDiffs.get(channelId)!;\n const existing = channelDiffs.get(toolCallId) ?? [];\n channelDiffs.set(toolCallId, existing.concat(diffs));\n }\n\n async function sendDiffsForTool(channelId: string, toolCallId: string) {\n // Skip if diffs were already shown at permission-request time\n const shownSet = permissionDiffShown.get(channelId);\n if (shownSet?.has(toolCallId)) {\n shownSet.delete(toolCallId);\n pendingDiffs.get(channelId)?.delete(toolCallId);\n return;\n }\n\n const channelDiffs = pendingDiffs.get(channelId);\n const diffs = channelDiffs?.get(toolCallId);\n if (!diffs || diffs.length === 0) return;\n\n const channel = await fetchChannel(channelId);\n if (!channel) return;\n\n const messages = formatDiff(diffs);\n for (const msg of messages) {\n await channel.send({ content: msg, allowedMentions: { parse: [] as const } });\n }\n\n channelDiffs!.delete(toolCallId);\n }\n\n async function fetchChannel(channelId: string): Promise<TextChannel | null> {\n const cached = discordClient.channels.cache.get(channelId) as TextChannel | undefined;\n if (cached) return cached;\n try {\n const fetched = await discordClient.channels.fetch(channelId);\n return fetched as TextChannel;\n } catch {\n return null;\n }\n }\n\n async function updateToolSummaryMessage(channelId: string) {\n const tools = toolStates.get(channelId);\n if (!tools) return;\n\n const content = formatToolSummary(tools);\n const channel = await fetchChannel(channelId);\n if (!channel) return;\n\n const stopButton = new ActionRowBuilder<ButtonBuilder>().addComponents(\n new ButtonBuilder()\n .setCustomId(`stop_${channelId}`)\n .setLabel(\"\\u23F9 Stop\")\n .setStyle(ButtonStyle.Secondary),\n );\n\n const noMentions = { parse: [] as const };\n const existing = toolSummaryMessages.get(channelId);\n if (existing) {\n await existing.edit({ content, components: [stopButton], allowedMentions: noMentions }).catch(() => {});\n } else {\n const msg = await channel.send({ content, components: [stopButton], allowedMentions: noMentions });\n toolSummaryMessages.set(channelId, msg);\n }\n }\n\n async function removeStopButton(channelId: string) {\n const msg = toolSummaryMessages.get(channelId);\n if (msg) {\n const tools = toolStates.get(channelId);\n const content = tools ? formatToolSummary(tools) : msg.content;\n await msg.edit({ content, components: [], allowedMentions: { parse: [] as const } }).catch(() => {});\n }\n }\n\n function scheduleFlushReply(channelId: string) {\n if (flushTimers.has(channelId)) return;\n flushTimers.set(\n channelId,\n setTimeout(() => {\n flushTimers.delete(channelId);\n flushReply(channelId, false);\n }, 500),\n );\n }\n\n async function flushReply(channelId: string, final: boolean) {\n const timer = flushTimers.get(channelId);\n if (timer) {\n clearTimeout(timer);\n flushTimers.delete(channelId);\n }\n\n const buffer = replyBuffers.get(channelId);\n if (!buffer) return;\n\n const channel = await fetchChannel(channelId);\n if (!channel) return;\n\n if (final) {\n // Send final reply as new message(s), delete streaming message\n const existing = replyMessages.get(channelId);\n if (existing) await existing.delete().catch(() => {});\n replyMessages.delete(channelId);\n\n const chunks = splitMessage(buffer);\n for (const chunk of chunks) {\n await channel.send(chunk);\n }\n replyBuffers.delete(channelId);\n } else {\n // Streaming update: edit existing message\n const truncated = buffer.length > 2000 ? buffer.slice(buffer.length - 1900) + \"...\" : buffer;\n const existing = replyMessages.get(channelId);\n if (existing) {\n await existing.edit(truncated).catch(() => {});\n } else {\n const msg = await channel.send(truncated);\n replyMessages.set(channelId, msg);\n }\n }\n }\n\n // --- Discord client setup ---\n\n discordClient = new Client({\n intents: [\n GatewayIntentBits.Guilds,\n GatewayIntentBits.GuildMessages,\n GatewayIntentBits.MessageContent,\n ],\n });\n\n discordClient.on(Events.ClientReady, async (c) => {\n console.log(`Discord bot ready: ${c.user.tag}`);\n\n // Register slash commands\n const askCommand = new SlashCommandBuilder()\n .setName(\"ask\")\n .setDescription(\"Ask the coding agent a question\")\n .addStringOption((opt) =>\n opt.setName(\"message\").setDescription(\"Your message\").setRequired(true),\n );\n\n const clearCommand = new SlashCommandBuilder()\n .setName(\"clear\")\n .setDescription(\"Clear the agent session and start fresh\");\n\n const rest = new REST().setToken(config.discord.token);\n try {\n await rest.put(Routes.applicationCommands(c.application.id), {\n body: [askCommand.toJSON(), clearCommand.toJSON()],\n });\n console.log(\"Registered /ask and /clear commands\");\n } catch (err) {\n console.error(\"Failed to register commands:\", err);\n }\n });\n\n // Handle @mention messages in configured channels\n discordClient.on(Events.MessageCreate, async (message: Message) => {\n if (message.author.bot) return;\n\n const channelId = message.channelId;\n const resolved = router.resolve(channelId);\n if (!resolved) return;\n\n const isMention = message.mentions.has(discordClient.user!);\n if (!resolved.autoReply && !isMention) return;\n\n // Strip mention prefix if present\n const text = message.content.replace(/<@!?\\d+>/g, \"\").trim();\n\n if (!text) {\n await message.reply(\"Please provide a message.\");\n return;\n }\n\n if (sessionManager.isPrompting(channelId)) {\n await message.reply(\"\\u23F3 Agent is working. Your message has been queued.\");\n }\n\n try {\n await sessionManager.prompt(channelId, text, resolved.agent, message.author.id);\n } catch (err) {\n console.error(`Prompt failed for channel ${channelId}:`, err);\n await message.reply(\"An error occurred while processing your request.\").catch(() => {});\n }\n });\n\n // Handle stop button clicks\n discordClient.on(Events.InteractionCreate, async (interaction) => {\n if (!interaction.isButton()) return;\n\n if (interaction.customId.startsWith(\"stop_\")) {\n const channelId = interaction.customId.replace(\"stop_\", \"\");\n const activeRequestor = sessionManager.getActiveRequestorId(channelId);\n\n // Only the user who triggered the current prompt can stop it\n if (activeRequestor && interaction.user.id !== activeRequestor) {\n await interaction.reply({ content: \"Only the user who started this prompt can stop it.\", ephemeral: true });\n return;\n }\n\n sessionManager.cancel(channelId);\n await interaction.update({ components: [] });\n }\n });\n\n // Handle /ask command\n discordClient.on(Events.InteractionCreate, async (interaction) => {\n if (!interaction.isChatInputCommand()) return;\n if (interaction.commandName !== \"ask\") return;\n\n const channelId = interaction.channelId;\n const resolved = router.resolve(channelId);\n if (!resolved) {\n await interaction.reply({ content: \"This channel is not configured for ACP.\", ephemeral: true });\n return;\n }\n\n const text = interaction.options.getString(\"message\", true);\n await interaction.deferReply();\n\n if (sessionManager.isPrompting(channelId)) {\n await interaction.editReply(\"\\u23F3 Agent is working. Your message has been queued.\");\n } else {\n await interaction.editReply(`\\uD83D\\uDCAC Processing: ${text.slice(0, 100)}...`);\n }\n\n try {\n await sessionManager.prompt(channelId, text, resolved.agent, interaction.user.id);\n } catch (err) {\n console.error(`Prompt failed for channel ${channelId}:`, err);\n await interaction.followUp({ content: \"An error occurred while processing your request.\", ephemeral: true }).catch(() => {});\n }\n });\n\n // Handle /clear command\n discordClient.on(Events.InteractionCreate, async (interaction) => {\n if (!interaction.isChatInputCommand()) return;\n if (interaction.commandName !== \"clear\") return;\n\n const channelId = interaction.channelId;\n sessionManager.teardown(channelId);\n\n // Clean up display state\n toolStates.delete(channelId);\n toolSummaryMessages.delete(channelId);\n replyBuffers.delete(channelId);\n replyMessages.delete(channelId);\n pendingDiffs.delete(channelId);\n permissionDiffShown.delete(channelId);\n const timer = flushTimers.get(channelId);\n if (timer) clearTimeout(timer);\n flushTimers.delete(channelId);\n\n await interaction.reply(\"Session cleared. Next message will start a fresh agent.\");\n });\n\n // Graceful shutdown\n process.on(\"SIGTERM\", () => {\n sessionManager.teardownAll();\n discordClient.destroy();\n });\n\n process.on(\"SIGINT\", () => {\n sessionManager.teardownAll();\n discordClient.destroy();\n });\n\n try {\n await discordClient.login(config.discord.token);\n } catch (err: unknown) {\n const message = err instanceof Error ? err.message : String(err);\n if (message.includes(\"TOKEN_INVALID\") || message.includes(\"An invalid token was provided\")) {\n console.error(\"Error: Invalid Discord bot token. Check your config.toml.\");\n } else if (message.includes(\"ConnectTimeout\") || message.includes(\"ETIMEDOUT\") || message.includes(\"ECONNREFUSED\")) {\n console.error(\"Error: Cannot connect to Discord API. Check your network or proxy settings.\");\n console.error(\"Hint: Set HTTPS_PROXY=http://127.0.0.1:7890 if you need a proxy.\");\n } else {\n console.error(\"Error: Failed to connect to Discord:\", message);\n }\n process.exit(1);\n }\n}\n","import type { AppConfig, ResolvedChannelConfig } from \"../shared/types.js\";\nimport { resolveChannelConfig } from \"../shared/config.js\";\n\nexport class ChannelRouter {\n private config: AppConfig;\n\n constructor(config: AppConfig) {\n this.config = config;\n }\n\n resolve(channelId: string): ResolvedChannelConfig | null {\n return resolveChannelConfig(this.config, channelId);\n }\n\n isConfigured(channelId: string): boolean {\n return this.resolve(channelId) !== null;\n }\n}\n","import { spawn, type ChildProcess } from \"node:child_process\";\nimport { Readable, Writable } from \"node:stream\";\nimport { ClientSideConnection, ndJsonStream, PROTOCOL_VERSION } from \"@agentclientprotocol/sdk\";\nimport type { AgentConfig } from \"../shared/types.js\";\nimport { createAcpClient, type AcpEventHandlers } from \"./acp-client.js\";\n\ninterface ManagedSession {\n channelId: string;\n process: ChildProcess;\n connection: ClientSideConnection;\n sessionId: string;\n lastActivity: number;\n idleTimer: NodeJS.Timeout;\n prompting: boolean;\n queue: Array<{ text: string; requestorId: string }>;\n /** Set only when executePrompt begins — stable for the duration of the prompt */\n activePromptRequestorId: string;\n}\n\nexport class SessionManager {\n private sessions = new Map<string, ManagedSession>();\n private handlers: AcpEventHandlers;\n\n constructor(handlers: AcpEventHandlers) {\n this.handlers = handlers;\n }\n\n async prompt(channelId: string, text: string, agentConfig: AgentConfig, requestorId: string): Promise<string> {\n const session = await this.getOrCreate(channelId, agentConfig, requestorId);\n session.lastActivity = Date.now();\n this.resetIdleTimer(session, agentConfig.idle_timeout);\n\n if (session.prompting) {\n session.queue.push({ text, requestorId });\n return \"queued\";\n }\n\n return this.executePrompt(session, text, requestorId, agentConfig);\n }\n\n private async executePrompt(session: ManagedSession, text: string, requestorId: string, agentConfig: AgentConfig): Promise<string> {\n session.prompting = true;\n session.activePromptRequestorId = requestorId;\n try {\n const result = await session.connection.prompt({\n sessionId: session.sessionId,\n prompt: [{ type: \"text\", text }],\n });\n this.handlers.onPromptComplete(session.channelId, result.stopReason);\n return result.stopReason;\n } finally {\n session.prompting = false;\n // Process queue — await and catch to prevent unhandled rejections (#3)\n const next = session.queue.shift();\n if (next) {\n this.executePrompt(session, next.text, next.requestorId, agentConfig).catch((err) => {\n console.error(`Queued prompt failed for channel ${session.channelId}:`, err);\n });\n }\n }\n }\n\n cancel(channelId: string): void {\n const session = this.sessions.get(channelId);\n if (session) {\n session.connection.cancel({ sessionId: session.sessionId });\n }\n }\n\n private async getOrCreate(channelId: string, agentConfig: AgentConfig, requestorId: string): Promise<ManagedSession> {\n const existing = this.sessions.get(channelId);\n if (existing) return existing;\n return this.createSession(channelId, agentConfig, requestorId);\n }\n\n private async createSession(channelId: string, config: AgentConfig, requestorId: string): Promise<ManagedSession> {\n const proc = spawn(config.command, config.args, {\n stdio: [\"pipe\", \"pipe\", \"inherit\"],\n cwd: config.cwd,\n });\n\n // Handle spawn errors (ENOENT, permission denied, etc.) (#4)\n proc.on(\"error\", (err) => {\n console.error(`Agent process error for channel ${channelId}:`, err);\n const session = this.sessions.get(channelId);\n if (session?.process === proc) {\n clearTimeout(session.idleTimer);\n this.sessions.delete(channelId);\n }\n });\n\n proc.on(\"exit\", () => {\n const session = this.sessions.get(channelId);\n if (session?.process === proc) {\n this.sessions.delete(channelId);\n clearTimeout(session.idleTimer);\n }\n });\n\n // Wrap initialize/newSession in try/catch to clean up process on failure (#5)\n let connection: ClientSideConnection;\n let sessionId: string;\n try {\n const stream = ndJsonStream(\n Writable.toWeb(proc.stdin!) as WritableStream<Uint8Array>,\n Readable.toWeb(proc.stdout!) as ReadableStream<Uint8Array>,\n );\n\n const client = createAcpClient(channelId, this.handlers, () => {\n return this.sessions.get(channelId)?.activePromptRequestorId ?? requestorId;\n });\n connection = new ClientSideConnection((_agent) => client, stream);\n\n await connection.initialize({\n protocolVersion: PROTOCOL_VERSION,\n clientCapabilities: {\n fs: { readTextFile: true, writeTextFile: true },\n terminal: true,\n },\n clientInfo: {\n name: \"acp-discord\",\n title: \"ACP Discord Bot\",\n version: \"0.1.0\",\n },\n });\n\n const result = await connection.newSession({\n cwd: config.cwd,\n mcpServers: [],\n });\n sessionId = result.sessionId;\n } catch (err) {\n proc.kill();\n throw err;\n }\n\n const managed: ManagedSession = {\n channelId,\n process: proc,\n connection,\n sessionId,\n lastActivity: Date.now(),\n idleTimer: this.startIdleTimer(channelId, config.idle_timeout),\n prompting: false,\n queue: [],\n activePromptRequestorId: requestorId,\n };\n\n this.sessions.set(channelId, managed);\n return managed;\n }\n\n private startIdleTimer(channelId: string, timeoutSec: number): NodeJS.Timeout {\n return setTimeout(() => this.teardown(channelId), timeoutSec * 1000);\n }\n\n private resetIdleTimer(session: ManagedSession, timeoutSec: number): void {\n clearTimeout(session.idleTimer);\n session.idleTimer = this.startIdleTimer(session.channelId, timeoutSec);\n }\n\n teardown(channelId: string): void {\n const session = this.sessions.get(channelId);\n if (!session) return;\n clearTimeout(session.idleTimer);\n session.process.kill();\n this.sessions.delete(channelId);\n }\n\n teardownAll(): void {\n for (const channelId of this.sessions.keys()) {\n this.teardown(channelId);\n }\n }\n\n isPrompting(channelId: string): boolean {\n return this.sessions.get(channelId)?.prompting ?? false;\n }\n\n getActiveRequestorId(channelId: string): string | null {\n const session = this.sessions.get(channelId);\n if (!session?.prompting) return null;\n return session.activePromptRequestorId;\n }\n\n getActiveChannels(): string[] {\n return Array.from(this.sessions.keys());\n }\n}\n","import type {\n Client,\n RequestPermissionRequest,\n RequestPermissionResponse,\n SessionNotification,\n} from \"@agentclientprotocol/sdk\";\n\nexport interface DiffContent {\n path: string;\n oldText?: string | null;\n newText: string;\n}\n\nexport interface AcpEventHandlers {\n onToolCall(channelId: string, toolCallId: string, title: string, kind: string, status: string, diffs: DiffContent[], rawInput?: Record<string, unknown>): void;\n onToolCallUpdate(channelId: string, toolCallId: string, status: string, diffs: DiffContent[]): void;\n onAgentMessageChunk(channelId: string, text: string): void;\n onPermissionRequest(\n channelId: string,\n requestorId: string,\n toolCall: { toolCallId: string; title: string; kind: string },\n options: Array<{ optionId: string; name: string; kind: string }>,\n diffs: DiffContent[],\n ): Promise<{ outcome: \"selected\"; optionId: string } | { outcome: \"cancelled\" }>;\n onPromptComplete(channelId: string, stopReason: string): void;\n}\n\nexport function createAcpClient(\n channelId: string,\n handlers: AcpEventHandlers,\n getRequestorId: () => string,\n): Client {\n return {\n async requestPermission(params: RequestPermissionRequest): Promise<RequestPermissionResponse> {\n const diffs = extractDiffs((params.toolCall as { content?: unknown }).content);\n const result = await handlers.onPermissionRequest(\n channelId,\n getRequestorId(),\n {\n toolCallId: params.toolCall.toolCallId,\n title: params.toolCall.title ?? \"Unknown\",\n kind: params.toolCall.kind ?? \"other\",\n },\n params.options.map((o: { optionId: string; name: string; kind: string }) => ({\n optionId: o.optionId,\n name: o.name,\n kind: o.kind,\n })),\n diffs,\n );\n\n if (result.outcome === \"selected\") {\n return { outcome: { outcome: \"selected\", optionId: result.optionId } };\n }\n return { outcome: { outcome: \"cancelled\" } };\n },\n\n async sessionUpdate(params: SessionNotification): Promise<void> {\n const update = params.update;\n switch (update.sessionUpdate) {\n case \"agent_message_chunk\": {\n if (update.content.type === \"text\") {\n handlers.onAgentMessageChunk(channelId, update.content.text);\n }\n break;\n }\n case \"tool_call\": {\n const toolCallDiffs = extractDiffs(update.content);\n const rawVal = (update as Record<string, unknown>).rawInput;\n const rawInput = typeof rawVal === \"object\" && rawVal !== null && !Array.isArray(rawVal)\n ? (rawVal as Record<string, unknown>)\n : undefined;\n handlers.onToolCall(\n channelId,\n update.toolCallId,\n update.title ?? \"Unknown\",\n update.kind ?? \"other\",\n update.status ?? \"pending\",\n toolCallDiffs,\n rawInput,\n );\n break;\n }\n case \"tool_call_update\": {\n const updateDiffs = extractDiffs(update.content);\n handlers.onToolCallUpdate(\n channelId,\n update.toolCallId,\n update.status ?? \"in_progress\",\n updateDiffs,\n );\n break;\n }\n }\n },\n };\n}\n\nfunction extractDiffs(content: unknown): DiffContent[] {\n if (!Array.isArray(content)) return [];\n const diffs: DiffContent[] = [];\n for (const item of content) {\n if (item && typeof item === \"object\" && \"type\" in item && item.type === \"diff\") {\n const { path, oldText, newText } = item as Record<string, unknown>;\n if (typeof path !== \"string\" || typeof newText !== \"string\") continue;\n if (oldText !== undefined && oldText !== null && typeof oldText !== \"string\") continue;\n diffs.push({ path, oldText: (oldText as string | null) ?? null, newText });\n }\n }\n return diffs;\n}\n","import {\n ActionRowBuilder,\n ButtonBuilder,\n ButtonStyle,\n EmbedBuilder,\n type TextChannel,\n} from \"discord.js\";\nimport type { DiffContent } from \"./acp-client.js\";\nimport { formatDiff } from \"./message-bridge.js\";\n\nconst KIND_LABELS: Record<string, string> = {\n allow_once: \"\\u2705 Allow\",\n allow_always: \"\\u2705 Always Allow\",\n reject_once: \"\\u274C Reject\",\n reject_always: \"\\u274C Never Allow\",\n};\n\nconst KIND_STYLES: Record<string, ButtonStyle> = {\n allow_once: ButtonStyle.Success,\n allow_always: ButtonStyle.Success,\n reject_once: ButtonStyle.Danger,\n reject_always: ButtonStyle.Danger,\n};\n\nexport interface PermissionOption {\n optionId: string;\n name: string;\n kind: string;\n}\n\nexport async function sendPermissionRequest(\n channel: TextChannel,\n toolTitle: string,\n toolKind: string,\n options: PermissionOption[],\n requestorId: string,\n diffs: DiffContent[] = [],\n timeoutMs = 14 * 60 * 1000,\n): Promise<{ outcome: \"selected\"; optionId: string; diffsSent?: boolean } | { outcome: \"cancelled\"; diffsSent?: boolean }> {\n if (options.length === 0) {\n return { outcome: \"cancelled\" };\n }\n\n // Send diffs before the permission embed so the user can review changes\n let diffsSent = false;\n if (diffs.length > 0) {\n try {\n const diffMessages = formatDiff(diffs);\n for (const msg of diffMessages) {\n await channel.send(msg);\n }\n diffsSent = true;\n } catch (err) {\n console.error(\"Failed to send permission diffs:\", err);\n }\n }\n\n const embed = new EmbedBuilder()\n .setColor(0xffa500)\n .setTitle(`Permission: ${toolTitle}`)\n .setDescription(`Tool type: \\`${toolKind}\\``)\n .setTimestamp();\n\n const buttons = options.map((opt) =>\n new ButtonBuilder()\n .setCustomId(`perm_${opt.optionId}`)\n .setLabel(KIND_LABELS[opt.kind] ?? opt.name)\n .setStyle(KIND_STYLES[opt.kind] ?? ButtonStyle.Secondary),\n );\n\n // Discord allows max 5 buttons per ActionRow\n const rows: ActionRowBuilder<ButtonBuilder>[] = [];\n for (let i = 0; i < buttons.length; i += 5) {\n rows.push(new ActionRowBuilder<ButtonBuilder>().addComponents(buttons.slice(i, i + 5)));\n }\n\n const msg = await channel.send({ embeds: [embed], components: rows });\n\n return new Promise((resolve) => {\n const collector = msg.createMessageComponentCollector({\n filter: (i) => i.user.id === requestorId,\n time: timeoutMs,\n });\n\n collector.on(\"collect\", async (interaction) => {\n const optionId = interaction.customId.replace(\"perm_\", \"\");\n await interaction.update({ components: [] });\n collector.stop(\"selected\");\n resolve({ outcome: \"selected\", optionId, diffsSent });\n });\n\n collector.on(\"end\", (_collected, reason) => {\n if (reason === \"time\") {\n msg.edit({ components: [] }).catch(() => {});\n resolve({ outcome: \"cancelled\", diffsSent });\n }\n });\n });\n}\n","import { createTwoFilesPatch } from \"diff\";\nimport type { DiffContent } from \"./acp-client.js\";\n\nconst DISCORD_MAX_LENGTH = 2000;\nconst MAX_DIFF_LINES = 150;\n\nexport function splitMessage(text: string, maxLength = DISCORD_MAX_LENGTH): string[] {\n if (text.length <= maxLength) return [text];\n\n const chunks: string[] = [];\n let remaining = text;\n let inCodeBlock = false;\n let codeFence = \"\";\n\n while (remaining.length > 0) {\n if (remaining.length <= maxLength) {\n chunks.push(remaining);\n break;\n }\n\n // Find split point: prefer newline before maxLength\n let splitAt = maxLength;\n const lastNewline = remaining.lastIndexOf(\"\\n\", maxLength);\n if (lastNewline > maxLength * 0.5) {\n splitAt = lastNewline + 1;\n }\n\n let chunk = remaining.slice(0, splitAt);\n remaining = remaining.slice(splitAt);\n\n // Handle code blocks: count fences in this chunk\n const fenceMatches = chunk.match(/```\\w*/g) || [];\n for (const fence of fenceMatches) {\n if (!inCodeBlock) {\n inCodeBlock = true;\n codeFence = fence;\n } else {\n inCodeBlock = false;\n codeFence = \"\";\n }\n }\n\n // If we're inside a code block at the split, close and reopen\n if (inCodeBlock) {\n chunk += \"\\n```\";\n remaining = codeFence + \"\\n\" + remaining;\n inCodeBlock = false;\n codeFence = \"\";\n }\n\n chunks.push(chunk);\n }\n\n return chunks;\n}\n\nexport type ToolStatus = \"pending\" | \"in_progress\" | \"completed\" | \"failed\";\n\nconst STATUS_ICONS: Record<ToolStatus, string> = {\n pending: \"\\u23F3\", // ⏳\n in_progress: \"\\uD83D\\uDD04\", // 🔄\n completed: \"\\u2705\", // ✅\n failed: \"\\u274C\", // ❌\n};\n\nexport function formatToolSummary(\n tools: Map<string, { title: string; status: ToolStatus; rawInput?: Record<string, unknown> }>,\n): string {\n const lines: string[] = [];\n for (const [, tool] of tools) {\n const detail = extractToolDetail(tool.rawInput);\n const suffix = detail ? ` · \\`${detail}\\`` : \"\";\n lines.push(`${STATUS_ICONS[tool.status]} ${tool.title}${suffix}`);\n }\n return lines.join(\"\\n\");\n}\n\nconst MAX_DETAIL_LENGTH = 80;\n\n// Only display values from known-safe fields to avoid leaking secrets\nconst SAFE_FIELDS = [\"command\", \"file_path\", \"pattern\", \"query\", \"path\", \"url\", \"description\"];\n\nfunction extractToolDetail(rawInput?: Record<string, unknown>): string | null {\n if (!rawInput) return null;\n\n for (const field of SAFE_FIELDS) {\n if (typeof rawInput[field] === \"string\" && rawInput[field]) {\n return truncate(sanitizeDetail(rawInput[field] as string), MAX_DETAIL_LENGTH);\n }\n }\n\n return null;\n}\n\nfunction sanitizeDetail(text: string): string {\n return text.replace(/`/g, \"'\");\n}\n\nfunction truncate(text: string, max: number): string {\n // Use first line only for multiline values\n const firstLine = text.split(\"\\n\")[0];\n if (firstLine.length <= max) return firstLine;\n return firstLine.slice(0, max - 1) + \"\\u2026\";\n}\n\nexport function formatDiff(diffs: DiffContent[], maxLines = MAX_DIFF_LINES): string[] {\n if (diffs.length === 0) return [];\n\n const parts: string[] = [];\n\n for (const d of diffs) {\n const fileName = d.path.split(\"/\").pop() ?? d.path;\n const oldText = d.oldText ?? \"\";\n const patch = createTwoFilesPatch(\n d.oldText == null ? \"/dev/null\" : d.path,\n d.path,\n oldText,\n d.newText,\n undefined,\n undefined,\n { context: 3 },\n );\n\n // Remove the first two header lines (Index: and ===) if present, keep ---/+++ and hunks\n const patchLines = patch.split(\"\\n\");\n // Find the first --- line to start from\n const startIdx = patchLines.findIndex((l) => l.startsWith(\"---\"));\n const diffLines = startIdx >= 0 ? patchLines.slice(startIdx) : patchLines;\n\n let truncated = false;\n let displayLines = diffLines;\n if (diffLines.length > maxLines) {\n displayLines = diffLines.slice(0, maxLines);\n truncated = true;\n }\n\n let block = `**${fileName}**\\n\\`\\`\\`diff\\n${displayLines.join(\"\\n\")}\\n\\`\\`\\``;\n if (truncated) {\n block += `\\n*... ${diffLines.length - maxLines} more lines*`;\n }\n\n parts.push(block);\n }\n\n // Join all diff blocks and split for Discord's message limit\n const fullMessage = parts.join(\"\\n\\n\");\n return splitMessage(fullMessage);\n}\n"],"mappings":"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","names":["ActionRowBuilder","ButtonBuilder","ButtonStyle","msg","ActionRowBuilder","ButtonBuilder","ButtonStyle"]}
package/dist/index.js CHANGED
@@ -7,7 +7,7 @@ import {
7
7
  } from "./chunk-QRVSGBED.js";
8
8
 
9
9
  // src/cli/index.ts
10
- import { Command as Command3 } from "commander";
10
+ import { Command as Command4 } from "commander";
11
11
 
12
12
  // src/cli/daemon.ts
13
13
  import { Command } from "commander";
@@ -326,7 +326,7 @@ Using: ${selected.name}
326
326
  async requestPermission(params) {
327
327
  const title = params.toolCall.title ?? "Unknown";
328
328
  const kind = params.toolCall.kind ?? "other";
329
- const isSafeWrite = kind === "write_text_file" || kind === "fs";
329
+ const isSafeWrite = kind === "write_text_file" || kind === "fs" || kind === "edit";
330
330
  if (isSafeWrite && params.toolCall.locations?.length) {
331
331
  const allPathsSafe = params.toolCall.locations.every(
332
332
  (loc) => {
@@ -397,6 +397,18 @@ Using: ${selected.name}
397
397
  Please start the setup.` }
398
398
  ]
399
399
  });
400
+ if (existsSync2(CONFIG_PATH)) {
401
+ try {
402
+ const content = readFileSync(CONFIG_PATH, "utf-8");
403
+ parseConfig(content);
404
+ console.log("\n\nSetup complete! Config written to", CONFIG_PATH);
405
+ console.log("Run `npx acp-discord daemon start` to begin.");
406
+ rl.close();
407
+ proc.kill();
408
+ process.exit(0);
409
+ } catch {
410
+ }
411
+ }
400
412
  while (true) {
401
413
  const input = await askUser("\n> ");
402
414
  if (input.toLowerCase() === "exit" || input.toLowerCase() === "quit") break;
@@ -417,15 +429,77 @@ Please start the setup.` }
417
429
  }
418
430
  rl.close();
419
431
  proc.kill();
432
+ process.exit(0);
433
+ });
434
+ }
435
+
436
+ // src/cli/update.ts
437
+ import { Command as Command3 } from "commander";
438
+ import { join as join4 } from "path";
439
+ import { homedir as homedir4 } from "os";
440
+ import { execFileSync as execFileSync2 } from "child_process";
441
+ var CONFIG_DIR3 = join4(homedir4(), ".acp-discord");
442
+ var PID_PATH2 = join4(CONFIG_DIR3, "daemon.pid");
443
+ async function fetchLatestVersion() {
444
+ const res = await fetch("https://registry.npmjs.org/acp-discord/latest");
445
+ if (!res.ok) throw new Error(`npm registry returned ${res.status}`);
446
+ const data = await res.json();
447
+ return data.version;
448
+ }
449
+ function stopDaemon() {
450
+ const pid = readPid(PID_PATH2);
451
+ if (pid === null) return;
452
+ try {
453
+ process.kill(pid, "SIGTERM");
454
+ removePid(PID_PATH2);
455
+ console.log(`Stopped daemon (PID: ${pid})`);
456
+ } catch {
457
+ removePid(PID_PATH2);
458
+ }
459
+ }
460
+ function makeUpdateCommand() {
461
+ return new Command3("update").description("Update acp-discord to the latest version").action(async () => {
462
+ const current = "0.1.0";
463
+ console.log(`Current version: v${current}`);
464
+ console.log("Checking for updates...");
465
+ let latest;
466
+ try {
467
+ latest = await fetchLatestVersion();
468
+ } catch (err) {
469
+ console.error("Failed to check for updates:", err.message);
470
+ process.exit(1);
471
+ }
472
+ if (current === latest) {
473
+ console.log(`Already up to date (v${current})`);
474
+ return;
475
+ }
476
+ console.log(`Update available: v${current} \u2192 v${latest}`);
477
+ const wasRunning = isDaemonRunning(PID_PATH2);
478
+ if (wasRunning) {
479
+ console.log("Stopping daemon...");
480
+ stopDaemon();
481
+ }
482
+ console.log("Downloading latest version and restarting daemon...");
483
+ try {
484
+ execFileSync2("npx", ["--yes", "acp-discord@latest", "daemon", "start"], {
485
+ stdio: "inherit"
486
+ });
487
+ } catch {
488
+ console.error("Failed to start daemon with new version.");
489
+ console.error("You can try manually: npx acp-discord@latest daemon start");
490
+ process.exit(1);
491
+ }
492
+ console.log(`Updated to v${latest}`);
420
493
  });
421
494
  }
422
495
 
423
496
  // src/cli/index.ts
424
497
  function createCli() {
425
- const program = new Command3();
498
+ const program = new Command4();
426
499
  program.name("acp-discord").description("Discord bot for ACP coding agents").version("0.1.0");
427
500
  program.addCommand(makeInitCommand());
428
501
  program.addCommand(makeDaemonCommand());
502
+ program.addCommand(makeUpdateCommand());
429
503
  return program;
430
504
  }
431
505
 
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/cli/index.ts","../src/cli/daemon.ts","../src/cli/autostart.ts","../src/cli/init.ts","../src/shared/detect-agents.ts","../src/index.ts"],"sourcesContent":["import { Command } from \"commander\";\nimport { makeDaemonCommand } from \"./daemon.js\";\nimport { makeInitCommand } from \"./init.js\";\n\nexport function createCli(): Command {\n const program = new Command();\n\n program\n .name(\"acp-discord\")\n .description(\"Discord bot for ACP coding agents\")\n .version(\"0.1.0\");\n\n program.addCommand(makeInitCommand());\n program.addCommand(makeDaemonCommand());\n\n return program;\n}\n","import { Command } from \"commander\";\nimport { join } from \"node:path\";\nimport { homedir } from \"node:os\";\nimport { spawn } from \"node:child_process\";\nimport { openSync, closeSync } from \"node:fs\";\nimport { fileURLToPath } from \"node:url\";\nimport { isDaemonRunning, readPid, removePid } from \"./pid.js\";\nimport { enableAutostart, disableAutostart } from \"./autostart.js\";\n\nconst CONFIG_DIR = join(homedir(), \".acp-discord\");\nconst PID_PATH = join(CONFIG_DIR, \"daemon.pid\");\nconst LOG_PATH = join(CONFIG_DIR, \"daemon.log\");\nconst ERR_LOG_PATH = join(CONFIG_DIR, \"daemon.error.log\");\n\nexport function makeDaemonCommand(): Command {\n const daemon = new Command(\"daemon\").description(\"Manage the acp-discord daemon\");\n\n daemon\n .command(\"start\")\n .description(\"Start the daemon (background)\")\n .action(async () => {\n if (isDaemonRunning(PID_PATH)) {\n const pid = readPid(PID_PATH);\n console.log(`Daemon already running (PID: ${pid})`);\n process.exit(1);\n }\n removePid(PID_PATH); // clean stale\n\n // In the bundled output, both index.js and daemon.js are in dist/\n const thisDir = fileURLToPath(new URL(\".\", import.meta.url));\n const daemonEntry = join(thisDir, \"daemon.js\");\n const outFd = openSync(LOG_PATH, \"a\");\n const errFd = openSync(ERR_LOG_PATH, \"a\");\n const child = spawn(process.execPath, [daemonEntry], {\n detached: true,\n stdio: [\"ignore\", outFd, errFd],\n env: { ...process.env, ACP_DISCORD_DAEMON: \"1\" },\n });\n\n child.unref();\n closeSync(outFd);\n closeSync(errFd);\n\n // Wait briefly and verify the daemon wrote its PID file (#11)\n await new Promise((resolve) => setTimeout(resolve, 1500));\n if (isDaemonRunning(PID_PATH)) {\n const pid = readPid(PID_PATH);\n console.log(`Daemon started (PID: ${pid})`);\n process.exit(0);\n } else {\n console.error(`Daemon failed to start (forked PID: ${child.pid}).`);\n console.error(`Check logs: ${ERR_LOG_PATH}`);\n process.exit(1);\n }\n });\n\n daemon\n .command(\"run\")\n .description(\"Run the daemon in foreground (for service managers)\")\n .action(async () => {\n if (isDaemonRunning(PID_PATH)) {\n const pid = readPid(PID_PATH);\n console.log(`Daemon already running (PID: ${pid})`);\n process.exit(1);\n }\n // Import and run directly in this process\n const { runDaemon } = await import(\"../daemon/index.js\");\n await runDaemon();\n });\n\n daemon\n .command(\"stop\")\n .description(\"Stop the daemon\")\n .action(async () => {\n const pid = readPid(PID_PATH);\n if (pid === null) {\n console.log(\"Daemon is not running\");\n return;\n }\n try {\n process.kill(pid, \"SIGTERM\");\n removePid(PID_PATH);\n console.log(`Daemon stopped (PID: ${pid})`);\n } catch {\n removePid(PID_PATH);\n console.log(\"Daemon was not running (stale PID removed)\");\n }\n });\n\n daemon\n .command(\"status\")\n .description(\"Show daemon status\")\n .action(async () => {\n if (isDaemonRunning(PID_PATH)) {\n const pid = readPid(PID_PATH);\n console.log(`Daemon is running (PID: ${pid})`);\n } else {\n removePid(PID_PATH);\n console.log(\"Daemon is not running\");\n }\n });\n\n daemon\n .command(\"enable\")\n .description(\"Enable auto-start on boot\")\n .action(async () => {\n enableAutostart();\n });\n\n daemon\n .command(\"disable\")\n .description(\"Disable auto-start on boot\")\n .action(async () => {\n disableAutostart();\n });\n\n return daemon;\n}\n","import { writeFileSync, unlinkSync, existsSync, mkdirSync } from \"node:fs\";\nimport { join } from \"node:path\";\nimport { homedir, platform } from \"node:os\";\nimport { execSync } from \"node:child_process\";\n\nconst SYSTEMD_DIR = join(homedir(), \".config\", \"systemd\", \"user\");\nconst SYSTEMD_SERVICE = \"acp-discord.service\";\nconst LAUNCHD_DIR = join(homedir(), \"Library\", \"LaunchAgents\");\nconst LAUNCHD_PLIST = \"com.acp-discord.plist\";\n\nfunction getNpxPath(): string {\n try {\n return execSync(\"which npx\", { encoding: \"utf-8\" }).trim();\n } catch {\n throw new Error(\"npx not found in PATH. Ensure Node.js is installed.\");\n }\n}\n\nexport function enableAutostart(): void {\n const os = platform();\n\n if (os === \"linux\") {\n mkdirSync(SYSTEMD_DIR, { recursive: true });\n const npx = getNpxPath();\n // Use \"daemon run\" for foreground mode — correct for systemd lifecycle (#10)\n const service = `[Unit]\nDescription=acp-discord daemon\nAfter=network.target\n\n[Service]\nExecStart=${npx} acp-discord daemon run\nRestart=on-failure\nRestartSec=10\n\n[Install]\nWantedBy=default.target\n`;\n const servicePath = join(SYSTEMD_DIR, SYSTEMD_SERVICE);\n writeFileSync(servicePath, service);\n try {\n execSync(\"systemctl --user daemon-reload\");\n execSync(`systemctl --user enable ${SYSTEMD_SERVICE}`);\n } catch (err) {\n console.error(\"Failed to enable systemd service:\", err instanceof Error ? err.message : err);\n return;\n }\n console.log(`Enabled systemd service: ${servicePath}`);\n console.log(\"Run: systemctl --user start acp-discord\");\n } else if (os === \"darwin\") {\n mkdirSync(LAUNCHD_DIR, { recursive: true });\n const npx = getNpxPath();\n // Use \"daemon run\" for foreground mode — correct for launchd lifecycle (#10)\n const plist = `<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<!DOCTYPE plist PUBLIC \"-//Apple//DTD PLIST 1.0//EN\" \"http://www.apple.com/DTDs/PropertyList-1.0.dtd\">\n<plist version=\"1.0\">\n<dict>\n <key>Label</key>\n <string>com.acp-discord</string>\n <key>ProgramArguments</key>\n <array>\n <string>${npx}</string>\n <string>acp-discord</string>\n <string>daemon</string>\n <string>run</string>\n </array>\n <key>RunAtLoad</key>\n <true/>\n <key>KeepAlive</key>\n <true/>\n <key>StandardOutPath</key>\n <string>${join(homedir(), \".acp-discord\", \"daemon.log\")}</string>\n <key>StandardErrorPath</key>\n <string>${join(homedir(), \".acp-discord\", \"daemon.error.log\")}</string>\n</dict>\n</plist>`;\n const plistPath = join(LAUNCHD_DIR, LAUNCHD_PLIST);\n writeFileSync(plistPath, plist);\n console.log(`Enabled launchd service: ${plistPath}`);\n console.log(\"Run: launchctl load \" + plistPath);\n } else {\n console.error(`Auto-start not supported on ${os}. Use your OS service manager manually.`);\n }\n}\n\nexport function disableAutostart(): void {\n const os = platform();\n\n if (os === \"linux\") {\n const servicePath = join(SYSTEMD_DIR, SYSTEMD_SERVICE);\n try {\n execSync(`systemctl --user disable ${SYSTEMD_SERVICE}`);\n } catch {\n // may not be enabled\n }\n if (existsSync(servicePath)) unlinkSync(servicePath);\n try {\n execSync(\"systemctl --user daemon-reload\");\n } catch (err) {\n console.error(\"Failed to reload systemd:\", err instanceof Error ? err.message : err);\n }\n console.log(\"Disabled systemd auto-start\");\n } else if (os === \"darwin\") {\n const plistPath = join(LAUNCHD_DIR, LAUNCHD_PLIST);\n try {\n execSync(`launchctl unload ${plistPath}`);\n } catch {\n // may not be loaded\n }\n if (existsSync(plistPath)) unlinkSync(plistPath);\n console.log(\"Disabled launchd auto-start\");\n } else {\n console.error(`Auto-start not supported on ${os}.`);\n }\n}\n","import { Command } from \"commander\";\nimport { join, resolve } from \"node:path\";\nimport { homedir } from \"node:os\";\nimport { existsSync, mkdirSync, readFileSync } from \"node:fs\";\nimport { spawn } from \"node:child_process\";\nimport { Readable, Writable } from \"node:stream\";\nimport { ClientSideConnection, ndJsonStream, PROTOCOL_VERSION } from \"@agentclientprotocol/sdk\";\nimport type { Client } from \"@agentclientprotocol/sdk\";\nimport { detectInstalledAgents } from \"../shared/detect-agents.js\";\nimport { parseConfig } from \"../shared/config.js\";\n\nconst CONFIG_DIR = join(homedir(), \".acp-discord\");\nconst CONFIG_PATH = join(CONFIG_DIR, \"config.toml\");\n\n// Only auto-allow writes to the config directory during setup\nconst SAFE_WRITE_PREFIX = CONFIG_DIR;\n\nconst INIT_SYSTEM_PROMPT = `You are a setup assistant for acp-discord, a Discord bot that connects Discord channels to ACP coding agents.\n\nYour job is to help the user configure ~/.acp-discord/config.toml interactively.\n\nYou need to collect:\n1. Discord Bot Token (guide them to https://discord.com/developers/applications if needed)\n2. Default working directory (the project path the agent will work on)\n3. Channel IDs to bind (explain how to get channel IDs: right-click channel → Copy Channel ID)\n4. Reply mode per channel: ask whether the bot should respond to ALL messages in the channel (auto_reply = true) or only when @mentioned (auto_reply = false, the default)\n\nOnce you have all info, write the config file using the write_text_file tool to ${CONFIG_PATH}.\n\nConfig format (TOML):\n\\`\\`\\`toml\n[discord]\ntoken = \"<token>\"\n\n[agents.default]\ncommand = \"npx\"\nargs = [\"<acp-package>\"]\ncwd = \"<working-directory>\"\nidle_timeout = 600\n\n[channels.<channel-id>]\nagent = \"default\"\nauto_reply = false # true = respond to all messages; false = only @mentions\n\\`\\`\\`\n\nBe friendly and concise. Ask one question at a time.`;\n\nexport function makeInitCommand(): Command {\n return new Command(\"init\")\n .description(\"Interactive setup wizard\")\n .action(async () => {\n console.log(\"Welcome to acp-discord setup!\\n\");\n\n // Detect agents\n console.log(\"Detecting ACP-compatible agents...\");\n const agents = detectInstalledAgents();\n\n if (agents.length === 0) {\n console.error(\"No ACP-compatible agents found.\");\n console.error(\"Install one of: claude-code, codex, opencode, pi\");\n process.exit(1);\n }\n\n for (const agent of agents) {\n console.log(` \\u2713 ${agent.name} (found)`);\n }\n\n const selected = agents[0];\n console.log(`\\nUsing: ${selected.name}\\n`);\n console.log(\"Starting setup agent...\\n\");\n\n // Spawn ACP agent for interactive setup\n const proc = spawn(selected.command, [selected.acpPackage], {\n stdio: [\"pipe\", \"pipe\", \"inherit\"],\n });\n\n const stream = ndJsonStream(\n Writable.toWeb(proc.stdin!) as WritableStream<Uint8Array>,\n Readable.toWeb(proc.stdout!) as ReadableStream<Uint8Array>,\n );\n\n // Simple readline for user input\n const readline = await import(\"node:readline\");\n const rl = readline.createInterface({\n input: process.stdin,\n output: process.stdout,\n });\n\n const askUser = (prompt: string): Promise<string> =>\n new Promise((resolve) => rl.question(prompt, resolve));\n\n const client: Client = {\n async requestPermission(params) {\n const title = params.toolCall.title ?? \"Unknown\";\n const kind = params.toolCall.kind ?? \"other\";\n\n // Auto-allow only safe file writes within the config directory,\n // validated against actual tool locations (not spoofable title) (#2)\n const isSafeWrite = kind === \"write_text_file\" || kind === \"fs\";\n if (isSafeWrite && params.toolCall.locations?.length) {\n const allPathsSafe = params.toolCall.locations.every(\n (loc: { path: string }) => {\n const resolved = resolve(loc.path);\n return resolved.startsWith(SAFE_WRITE_PREFIX + \"/\") || resolved === SAFE_WRITE_PREFIX;\n },\n );\n if (allPathsSafe) {\n const allowOption = params.options.find((o: { kind: string }) => o.kind === \"allow_once\");\n if (allowOption) {\n return { outcome: { outcome: \"selected\" as const, optionId: allowOption.optionId } };\n }\n }\n }\n\n // For all other operations, ask the user\n console.log(`\\n--- Permission Request ---`);\n console.log(`Tool: ${title}`);\n console.log(`Type: ${kind}`);\n console.log(`Options:`);\n for (let i = 0; i < params.options.length; i++) {\n const opt = params.options[i];\n console.log(` ${i + 1}. ${opt.name} (${opt.kind})`);\n }\n\n const answer = await askUser(`Choose option (1-${params.options.length}, or 'c' to cancel): `);\n if (answer.toLowerCase() === \"c\") {\n const rejectOption = params.options.find((o: { kind: string }) => o.kind === \"reject_once\");\n if (rejectOption) {\n return { outcome: { outcome: \"selected\" as const, optionId: rejectOption.optionId } };\n }\n return { outcome: { outcome: \"cancelled\" as const } };\n }\n\n const idx = parseInt(answer, 10) - 1;\n if (idx >= 0 && idx < params.options.length) {\n return { outcome: { outcome: \"selected\" as const, optionId: params.options[idx].optionId } };\n }\n\n // Invalid input — default to reject\n const rejectOption = params.options.find((o: { kind: string }) => o.kind === \"reject_once\");\n if (rejectOption) {\n return { outcome: { outcome: \"selected\" as const, optionId: rejectOption.optionId } };\n }\n return { outcome: { outcome: \"cancelled\" as const } };\n },\n async sessionUpdate(params) {\n const update = params.update;\n if (update.sessionUpdate === \"agent_message_chunk\" && update.content.type === \"text\") {\n process.stdout.write(update.content.text);\n }\n },\n };\n\n const connection = new ClientSideConnection((_agent) => client, stream);\n\n await connection.initialize({\n protocolVersion: PROTOCOL_VERSION,\n clientCapabilities: {\n fs: { readTextFile: true, writeTextFile: true },\n terminal: false,\n },\n clientInfo: { name: \"acp-discord-init\", title: \"ACP Discord Init\", version: \"0.1.0\" },\n });\n\n mkdirSync(CONFIG_DIR, { recursive: true });\n\n const { sessionId } = await connection.newSession({\n cwd: CONFIG_DIR,\n mcpServers: [],\n });\n\n // Initial prompt\n await connection.prompt({\n sessionId,\n prompt: [\n { type: \"text\", text: INIT_SYSTEM_PROMPT },\n { type: \"text\", text: `The ACP agent package is: ${selected.acpPackage}\\nPlease start the setup.` },\n ],\n });\n\n // Interactive loop\n while (true) {\n const input = await askUser(\"\\n> \");\n if (input.toLowerCase() === \"exit\" || input.toLowerCase() === \"quit\") break;\n\n const result = await connection.prompt({\n sessionId,\n prompt: [{ type: \"text\", text: input }],\n });\n\n if (result.stopReason === \"end_turn\" && existsSync(CONFIG_PATH)) {\n // Validate the written config is structurally valid (#9)\n try {\n const content = readFileSync(CONFIG_PATH, \"utf-8\");\n parseConfig(content); // throws if invalid\n console.log(\"\\n\\nSetup complete! Config written to\", CONFIG_PATH);\n console.log(\"Run `npx acp-discord daemon start` to begin.\");\n break;\n } catch {\n // config not valid yet, continue\n }\n }\n }\n\n rl.close();\n proc.kill();\n });\n}\n","import { execFileSync } from \"node:child_process\";\n\nexport interface AgentInfo {\n name: string;\n command: string;\n acpPackage: string;\n detectCommand: string;\n detectArgs: string[];\n}\n\nexport const KNOWN_AGENTS: AgentInfo[] = [\n {\n name: \"claude-code\",\n command: \"npx\",\n acpPackage: \"@zed-industries/claude-agent-acp\",\n detectCommand: \"claude\",\n detectArgs: [\"--version\"],\n },\n {\n name: \"codex\",\n command: \"npx\",\n acpPackage: \"@openai/codex-acp\",\n detectCommand: \"codex\",\n detectArgs: [\"--version\"],\n },\n {\n name: \"opencode\",\n command: \"npx\",\n acpPackage: \"@opencode/acp\",\n detectCommand: \"opencode\",\n detectArgs: [\"--version\"],\n },\n {\n name: \"pi\",\n command: \"npx\",\n acpPackage: \"@anthropic-ai/pi-acp\",\n detectCommand: \"pi\",\n detectArgs: [\"--version\"],\n },\n];\n\nexport function detectInstalledAgents(): AgentInfo[] {\n const found: AgentInfo[] = [];\n for (const agent of KNOWN_AGENTS) {\n try {\n execFileSync(agent.detectCommand, agent.detectArgs, {\n stdio: \"ignore\",\n timeout: 5000,\n });\n found.push(agent);\n } catch {\n // not installed\n }\n }\n return found;\n}\n","import { createCli } from \"./cli/index.js\";\n\ncreateCli().parse();\n"],"mappings":"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","names":["Command","join","homedir","join","homedir","resolve","Command","join","homedir","existsSync","mkdirSync","spawn","CONFIG_DIR","join","homedir","Command","spawn","resolve","rejectOption","mkdirSync","existsSync","Command"]}
1
+ {"version":3,"sources":["../src/cli/index.ts","../src/cli/daemon.ts","../src/cli/autostart.ts","../src/cli/init.ts","../src/shared/detect-agents.ts","../src/cli/update.ts","../src/index.ts"],"sourcesContent":["import { Command } from \"commander\";\nimport { makeDaemonCommand } from \"./daemon.js\";\nimport { makeInitCommand } from \"./init.js\";\nimport { makeUpdateCommand } from \"./update.js\";\n\ndeclare const __VERSION__: string;\n\nexport function createCli(): Command {\n const program = new Command();\n\n program\n .name(\"acp-discord\")\n .description(\"Discord bot for ACP coding agents\")\n .version(__VERSION__);\n\n program.addCommand(makeInitCommand());\n program.addCommand(makeDaemonCommand());\n program.addCommand(makeUpdateCommand());\n\n return program;\n}\n","import { Command } from \"commander\";\nimport { join } from \"node:path\";\nimport { homedir } from \"node:os\";\nimport { spawn } from \"node:child_process\";\nimport { openSync, closeSync } from \"node:fs\";\nimport { fileURLToPath } from \"node:url\";\nimport { isDaemonRunning, readPid, removePid } from \"./pid.js\";\nimport { enableAutostart, disableAutostart } from \"./autostart.js\";\n\nconst CONFIG_DIR = join(homedir(), \".acp-discord\");\nconst PID_PATH = join(CONFIG_DIR, \"daemon.pid\");\nconst LOG_PATH = join(CONFIG_DIR, \"daemon.log\");\nconst ERR_LOG_PATH = join(CONFIG_DIR, \"daemon.error.log\");\n\nexport function makeDaemonCommand(): Command {\n const daemon = new Command(\"daemon\").description(\"Manage the acp-discord daemon\");\n\n daemon\n .command(\"start\")\n .description(\"Start the daemon (background)\")\n .action(async () => {\n if (isDaemonRunning(PID_PATH)) {\n const pid = readPid(PID_PATH);\n console.log(`Daemon already running (PID: ${pid})`);\n process.exit(1);\n }\n removePid(PID_PATH); // clean stale\n\n // In the bundled output, both index.js and daemon.js are in dist/\n const thisDir = fileURLToPath(new URL(\".\", import.meta.url));\n const daemonEntry = join(thisDir, \"daemon.js\");\n const outFd = openSync(LOG_PATH, \"a\");\n const errFd = openSync(ERR_LOG_PATH, \"a\");\n const child = spawn(process.execPath, [daemonEntry], {\n detached: true,\n stdio: [\"ignore\", outFd, errFd],\n env: { ...process.env, ACP_DISCORD_DAEMON: \"1\" },\n });\n\n child.unref();\n closeSync(outFd);\n closeSync(errFd);\n\n // Wait briefly and verify the daemon wrote its PID file (#11)\n await new Promise((resolve) => setTimeout(resolve, 1500));\n if (isDaemonRunning(PID_PATH)) {\n const pid = readPid(PID_PATH);\n console.log(`Daemon started (PID: ${pid})`);\n process.exit(0);\n } else {\n console.error(`Daemon failed to start (forked PID: ${child.pid}).`);\n console.error(`Check logs: ${ERR_LOG_PATH}`);\n process.exit(1);\n }\n });\n\n daemon\n .command(\"run\")\n .description(\"Run the daemon in foreground (for service managers)\")\n .action(async () => {\n if (isDaemonRunning(PID_PATH)) {\n const pid = readPid(PID_PATH);\n console.log(`Daemon already running (PID: ${pid})`);\n process.exit(1);\n }\n // Import and run directly in this process\n const { runDaemon } = await import(\"../daemon/index.js\");\n await runDaemon();\n });\n\n daemon\n .command(\"stop\")\n .description(\"Stop the daemon\")\n .action(async () => {\n const pid = readPid(PID_PATH);\n if (pid === null) {\n console.log(\"Daemon is not running\");\n return;\n }\n try {\n process.kill(pid, \"SIGTERM\");\n removePid(PID_PATH);\n console.log(`Daemon stopped (PID: ${pid})`);\n } catch {\n removePid(PID_PATH);\n console.log(\"Daemon was not running (stale PID removed)\");\n }\n });\n\n daemon\n .command(\"status\")\n .description(\"Show daemon status\")\n .action(async () => {\n if (isDaemonRunning(PID_PATH)) {\n const pid = readPid(PID_PATH);\n console.log(`Daemon is running (PID: ${pid})`);\n } else {\n removePid(PID_PATH);\n console.log(\"Daemon is not running\");\n }\n });\n\n daemon\n .command(\"enable\")\n .description(\"Enable auto-start on boot\")\n .action(async () => {\n enableAutostart();\n });\n\n daemon\n .command(\"disable\")\n .description(\"Disable auto-start on boot\")\n .action(async () => {\n disableAutostart();\n });\n\n return daemon;\n}\n","import { writeFileSync, unlinkSync, existsSync, mkdirSync } from \"node:fs\";\nimport { join } from \"node:path\";\nimport { homedir, platform } from \"node:os\";\nimport { execSync } from \"node:child_process\";\n\nconst SYSTEMD_DIR = join(homedir(), \".config\", \"systemd\", \"user\");\nconst SYSTEMD_SERVICE = \"acp-discord.service\";\nconst LAUNCHD_DIR = join(homedir(), \"Library\", \"LaunchAgents\");\nconst LAUNCHD_PLIST = \"com.acp-discord.plist\";\n\nfunction getNpxPath(): string {\n try {\n return execSync(\"which npx\", { encoding: \"utf-8\" }).trim();\n } catch {\n throw new Error(\"npx not found in PATH. Ensure Node.js is installed.\");\n }\n}\n\nexport function enableAutostart(): void {\n const os = platform();\n\n if (os === \"linux\") {\n mkdirSync(SYSTEMD_DIR, { recursive: true });\n const npx = getNpxPath();\n // Use \"daemon run\" for foreground mode — correct for systemd lifecycle (#10)\n const service = `[Unit]\nDescription=acp-discord daemon\nAfter=network.target\n\n[Service]\nExecStart=${npx} acp-discord daemon run\nRestart=on-failure\nRestartSec=10\n\n[Install]\nWantedBy=default.target\n`;\n const servicePath = join(SYSTEMD_DIR, SYSTEMD_SERVICE);\n writeFileSync(servicePath, service);\n try {\n execSync(\"systemctl --user daemon-reload\");\n execSync(`systemctl --user enable ${SYSTEMD_SERVICE}`);\n } catch (err) {\n console.error(\"Failed to enable systemd service:\", err instanceof Error ? err.message : err);\n return;\n }\n console.log(`Enabled systemd service: ${servicePath}`);\n console.log(\"Run: systemctl --user start acp-discord\");\n } else if (os === \"darwin\") {\n mkdirSync(LAUNCHD_DIR, { recursive: true });\n const npx = getNpxPath();\n // Use \"daemon run\" for foreground mode — correct for launchd lifecycle (#10)\n const plist = `<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<!DOCTYPE plist PUBLIC \"-//Apple//DTD PLIST 1.0//EN\" \"http://www.apple.com/DTDs/PropertyList-1.0.dtd\">\n<plist version=\"1.0\">\n<dict>\n <key>Label</key>\n <string>com.acp-discord</string>\n <key>ProgramArguments</key>\n <array>\n <string>${npx}</string>\n <string>acp-discord</string>\n <string>daemon</string>\n <string>run</string>\n </array>\n <key>RunAtLoad</key>\n <true/>\n <key>KeepAlive</key>\n <true/>\n <key>StandardOutPath</key>\n <string>${join(homedir(), \".acp-discord\", \"daemon.log\")}</string>\n <key>StandardErrorPath</key>\n <string>${join(homedir(), \".acp-discord\", \"daemon.error.log\")}</string>\n</dict>\n</plist>`;\n const plistPath = join(LAUNCHD_DIR, LAUNCHD_PLIST);\n writeFileSync(plistPath, plist);\n console.log(`Enabled launchd service: ${plistPath}`);\n console.log(\"Run: launchctl load \" + plistPath);\n } else {\n console.error(`Auto-start not supported on ${os}. Use your OS service manager manually.`);\n }\n}\n\nexport function disableAutostart(): void {\n const os = platform();\n\n if (os === \"linux\") {\n const servicePath = join(SYSTEMD_DIR, SYSTEMD_SERVICE);\n try {\n execSync(`systemctl --user disable ${SYSTEMD_SERVICE}`);\n } catch {\n // may not be enabled\n }\n if (existsSync(servicePath)) unlinkSync(servicePath);\n try {\n execSync(\"systemctl --user daemon-reload\");\n } catch (err) {\n console.error(\"Failed to reload systemd:\", err instanceof Error ? err.message : err);\n }\n console.log(\"Disabled systemd auto-start\");\n } else if (os === \"darwin\") {\n const plistPath = join(LAUNCHD_DIR, LAUNCHD_PLIST);\n try {\n execSync(`launchctl unload ${plistPath}`);\n } catch {\n // may not be loaded\n }\n if (existsSync(plistPath)) unlinkSync(plistPath);\n console.log(\"Disabled launchd auto-start\");\n } else {\n console.error(`Auto-start not supported on ${os}.`);\n }\n}\n","import { Command } from \"commander\";\nimport { join, resolve } from \"node:path\";\nimport { homedir } from \"node:os\";\nimport { existsSync, mkdirSync, readFileSync } from \"node:fs\";\nimport { spawn } from \"node:child_process\";\nimport { Readable, Writable } from \"node:stream\";\nimport { ClientSideConnection, ndJsonStream, PROTOCOL_VERSION } from \"@agentclientprotocol/sdk\";\nimport type { Client } from \"@agentclientprotocol/sdk\";\nimport { detectInstalledAgents } from \"../shared/detect-agents.js\";\nimport { parseConfig } from \"../shared/config.js\";\n\ndeclare const __VERSION__: string;\n\nconst CONFIG_DIR = join(homedir(), \".acp-discord\");\nconst CONFIG_PATH = join(CONFIG_DIR, \"config.toml\");\n\n// Only auto-allow writes to the config directory during setup\nconst SAFE_WRITE_PREFIX = CONFIG_DIR;\n\nconst INIT_SYSTEM_PROMPT = `You are a setup assistant for acp-discord, a Discord bot that connects Discord channels to ACP coding agents.\n\nYour job is to help the user configure ~/.acp-discord/config.toml interactively.\n\nYou need to collect:\n1. Discord Bot Token (guide them to https://discord.com/developers/applications if needed)\n2. Default working directory (the project path the agent will work on)\n3. Channel IDs to bind (explain how to get channel IDs: right-click channel → Copy Channel ID)\n4. Reply mode per channel: ask whether the bot should respond to ALL messages in the channel (auto_reply = true) or only when @mentioned (auto_reply = false, the default)\n\nOnce you have all info, write the config file using the write_text_file tool to ${CONFIG_PATH}.\n\nConfig format (TOML):\n\\`\\`\\`toml\n[discord]\ntoken = \"<token>\"\n\n[agents.default]\ncommand = \"npx\"\nargs = [\"<acp-package>\"]\ncwd = \"<working-directory>\"\nidle_timeout = 600\n\n[channels.<channel-id>]\nagent = \"default\"\nauto_reply = false # true = respond to all messages; false = only @mentions\n\\`\\`\\`\n\nBe friendly and concise. Ask one question at a time.`;\n\nexport function makeInitCommand(): Command {\n return new Command(\"init\")\n .description(\"Interactive setup wizard\")\n .action(async () => {\n console.log(\"Welcome to acp-discord setup!\\n\");\n\n // Detect agents\n console.log(\"Detecting ACP-compatible agents...\");\n const agents = detectInstalledAgents();\n\n if (agents.length === 0) {\n console.error(\"No ACP-compatible agents found.\");\n console.error(\"Install one of: claude-code, codex, opencode, pi\");\n process.exit(1);\n }\n\n for (const agent of agents) {\n console.log(` \\u2713 ${agent.name} (found)`);\n }\n\n const selected = agents[0];\n console.log(`\\nUsing: ${selected.name}\\n`);\n console.log(\"Starting setup agent...\\n\");\n\n // Spawn ACP agent for interactive setup\n const proc = spawn(selected.command, [selected.acpPackage], {\n stdio: [\"pipe\", \"pipe\", \"inherit\"],\n });\n\n const stream = ndJsonStream(\n Writable.toWeb(proc.stdin!) as WritableStream<Uint8Array>,\n Readable.toWeb(proc.stdout!) as ReadableStream<Uint8Array>,\n );\n\n // Simple readline for user input\n const readline = await import(\"node:readline\");\n const rl = readline.createInterface({\n input: process.stdin,\n output: process.stdout,\n });\n\n const askUser = (prompt: string): Promise<string> =>\n new Promise((resolve) => rl.question(prompt, resolve));\n\n const client: Client = {\n async requestPermission(params) {\n const title = params.toolCall.title ?? \"Unknown\";\n const kind = params.toolCall.kind ?? \"other\";\n\n // Auto-allow only safe file writes within the config directory,\n // validated against actual tool locations (not spoofable title) (#2)\n const isSafeWrite = kind === \"write_text_file\" || kind === \"fs\" || kind === \"edit\";\n if (isSafeWrite && params.toolCall.locations?.length) {\n const allPathsSafe = params.toolCall.locations.every(\n (loc: { path: string }) => {\n const resolved = resolve(loc.path);\n return resolved.startsWith(SAFE_WRITE_PREFIX + \"/\") || resolved === SAFE_WRITE_PREFIX;\n },\n );\n if (allPathsSafe) {\n const allowOption = params.options.find((o: { kind: string }) => o.kind === \"allow_once\");\n if (allowOption) {\n return { outcome: { outcome: \"selected\" as const, optionId: allowOption.optionId } };\n }\n }\n }\n\n // For all other operations, ask the user\n console.log(`\\n--- Permission Request ---`);\n console.log(`Tool: ${title}`);\n console.log(`Type: ${kind}`);\n console.log(`Options:`);\n for (let i = 0; i < params.options.length; i++) {\n const opt = params.options[i];\n console.log(` ${i + 1}. ${opt.name} (${opt.kind})`);\n }\n\n const answer = await askUser(`Choose option (1-${params.options.length}, or 'c' to cancel): `);\n if (answer.toLowerCase() === \"c\") {\n const rejectOption = params.options.find((o: { kind: string }) => o.kind === \"reject_once\");\n if (rejectOption) {\n return { outcome: { outcome: \"selected\" as const, optionId: rejectOption.optionId } };\n }\n return { outcome: { outcome: \"cancelled\" as const } };\n }\n\n const idx = parseInt(answer, 10) - 1;\n if (idx >= 0 && idx < params.options.length) {\n return { outcome: { outcome: \"selected\" as const, optionId: params.options[idx].optionId } };\n }\n\n // Invalid input — default to reject\n const rejectOption = params.options.find((o: { kind: string }) => o.kind === \"reject_once\");\n if (rejectOption) {\n return { outcome: { outcome: \"selected\" as const, optionId: rejectOption.optionId } };\n }\n return { outcome: { outcome: \"cancelled\" as const } };\n },\n async sessionUpdate(params) {\n const update = params.update;\n if (update.sessionUpdate === \"agent_message_chunk\" && update.content.type === \"text\") {\n process.stdout.write(update.content.text);\n }\n },\n };\n\n const connection = new ClientSideConnection((_agent) => client, stream);\n\n await connection.initialize({\n protocolVersion: PROTOCOL_VERSION,\n clientCapabilities: {\n fs: { readTextFile: true, writeTextFile: true },\n terminal: false,\n },\n clientInfo: { name: \"acp-discord-init\", title: \"ACP Discord Init\", version: __VERSION__ },\n });\n\n mkdirSync(CONFIG_DIR, { recursive: true });\n\n const { sessionId } = await connection.newSession({\n cwd: CONFIG_DIR,\n mcpServers: [],\n });\n\n // Initial prompt\n await connection.prompt({\n sessionId,\n prompt: [\n { type: \"text\", text: INIT_SYSTEM_PROMPT },\n { type: \"text\", text: `The ACP agent package is: ${selected.acpPackage}\\nPlease start the setup.` },\n ],\n });\n\n // Check if config was already written during the initial prompt\n if (existsSync(CONFIG_PATH)) {\n try {\n const content = readFileSync(CONFIG_PATH, \"utf-8\");\n parseConfig(content);\n console.log(\"\\n\\nSetup complete! Config written to\", CONFIG_PATH);\n console.log(\"Run `npx acp-discord daemon start` to begin.\");\n rl.close();\n proc.kill();\n process.exit(0);\n } catch {\n // config not valid yet, continue to interactive loop\n }\n }\n\n // Interactive loop\n while (true) {\n const input = await askUser(\"\\n> \");\n if (input.toLowerCase() === \"exit\" || input.toLowerCase() === \"quit\") break;\n\n const result = await connection.prompt({\n sessionId,\n prompt: [{ type: \"text\", text: input }],\n });\n\n if (result.stopReason === \"end_turn\" && existsSync(CONFIG_PATH)) {\n // Validate the written config is structurally valid (#9)\n try {\n const content = readFileSync(CONFIG_PATH, \"utf-8\");\n parseConfig(content); // throws if invalid\n console.log(\"\\n\\nSetup complete! Config written to\", CONFIG_PATH);\n console.log(\"Run `npx acp-discord daemon start` to begin.\");\n break;\n } catch {\n // config not valid yet, continue\n }\n }\n }\n\n rl.close();\n proc.kill();\n process.exit(0);\n });\n}\n","import { execFileSync } from \"node:child_process\";\n\nexport interface AgentInfo {\n name: string;\n command: string;\n acpPackage: string;\n detectCommand: string;\n detectArgs: string[];\n}\n\nexport const KNOWN_AGENTS: AgentInfo[] = [\n {\n name: \"claude-code\",\n command: \"npx\",\n acpPackage: \"@zed-industries/claude-agent-acp\",\n detectCommand: \"claude\",\n detectArgs: [\"--version\"],\n },\n {\n name: \"codex\",\n command: \"npx\",\n acpPackage: \"@openai/codex-acp\",\n detectCommand: \"codex\",\n detectArgs: [\"--version\"],\n },\n {\n name: \"opencode\",\n command: \"npx\",\n acpPackage: \"@opencode/acp\",\n detectCommand: \"opencode\",\n detectArgs: [\"--version\"],\n },\n {\n name: \"pi\",\n command: \"npx\",\n acpPackage: \"@anthropic-ai/pi-acp\",\n detectCommand: \"pi\",\n detectArgs: [\"--version\"],\n },\n];\n\nexport function detectInstalledAgents(): AgentInfo[] {\n const found: AgentInfo[] = [];\n for (const agent of KNOWN_AGENTS) {\n try {\n execFileSync(agent.detectCommand, agent.detectArgs, {\n stdio: \"ignore\",\n timeout: 5000,\n });\n found.push(agent);\n } catch {\n // not installed\n }\n }\n return found;\n}\n","import { Command } from \"commander\";\nimport { join } from \"node:path\";\nimport { homedir } from \"node:os\";\nimport { execFileSync } from \"node:child_process\";\nimport { isDaemonRunning, readPid, removePid } from \"./pid.js\";\n\ndeclare const __VERSION__: string;\n\nconst CONFIG_DIR = join(homedir(), \".acp-discord\");\nconst PID_PATH = join(CONFIG_DIR, \"daemon.pid\");\n\nasync function fetchLatestVersion(): Promise<string> {\n const res = await fetch(\"https://registry.npmjs.org/acp-discord/latest\");\n if (!res.ok) throw new Error(`npm registry returned ${res.status}`);\n const data = (await res.json()) as { version: string };\n return data.version;\n}\n\nfunction stopDaemon(): void {\n const pid = readPid(PID_PATH);\n if (pid === null) return;\n try {\n process.kill(pid, \"SIGTERM\");\n removePid(PID_PATH);\n console.log(`Stopped daemon (PID: ${pid})`);\n } catch {\n removePid(PID_PATH);\n }\n}\n\nexport function makeUpdateCommand(): Command {\n return new Command(\"update\")\n .description(\"Update acp-discord to the latest version\")\n .action(async () => {\n const current = __VERSION__;\n\n console.log(`Current version: v${current}`);\n console.log(\"Checking for updates...\");\n\n let latest: string;\n try {\n latest = await fetchLatestVersion();\n } catch (err) {\n console.error(\"Failed to check for updates:\", (err as Error).message);\n process.exit(1);\n }\n\n if (current === latest) {\n console.log(`Already up to date (v${current})`);\n return;\n }\n\n console.log(`Update available: v${current} → v${latest}`);\n\n const wasRunning = isDaemonRunning(PID_PATH);\n if (wasRunning) {\n console.log(\"Stopping daemon...\");\n stopDaemon();\n }\n\n // Use npx with @latest to fetch the new version and start the daemon\n // We must delegate to the new version's code, not the current process\n console.log(\"Downloading latest version and restarting daemon...\");\n try {\n execFileSync(\"npx\", [\"--yes\", \"acp-discord@latest\", \"daemon\", \"start\"], {\n stdio: \"inherit\",\n });\n } catch {\n console.error(\"Failed to start daemon with new version.\");\n console.error(\"You can try manually: npx acp-discord@latest daemon start\");\n process.exit(1);\n }\n\n console.log(`Updated to v${latest}`);\n });\n}\n","import { createCli } from \"./cli/index.js\";\n\ncreateCli().parse();\n"],"mappings":";;;;;;;;;AAAA,SAAS,WAAAA,gBAAe;;;ACAxB,SAAS,eAAe;AACxB,SAAS,QAAAC,aAAY;AACrB,SAAS,WAAAC,gBAAe;AACxB,SAAS,aAAa;AACtB,SAAS,UAAU,iBAAiB;AACpC,SAAS,qBAAqB;;;ACL9B,SAAS,eAAe,YAAY,YAAY,iBAAiB;AACjE,SAAS,YAAY;AACrB,SAAS,SAAS,gBAAgB;AAClC,SAAS,gBAAgB;AAEzB,IAAM,cAAc,KAAK,QAAQ,GAAG,WAAW,WAAW,MAAM;AAChE,IAAM,kBAAkB;AACxB,IAAM,cAAc,KAAK,QAAQ,GAAG,WAAW,cAAc;AAC7D,IAAM,gBAAgB;AAEtB,SAAS,aAAqB;AAC5B,MAAI;AACF,WAAO,SAAS,aAAa,EAAE,UAAU,QAAQ,CAAC,EAAE,KAAK;AAAA,EAC3D,QAAQ;AACN,UAAM,IAAI,MAAM,qDAAqD;AAAA,EACvE;AACF;AAEO,SAAS,kBAAwB;AACtC,QAAM,KAAK,SAAS;AAEpB,MAAI,OAAO,SAAS;AAClB,cAAU,aAAa,EAAE,WAAW,KAAK,CAAC;AAC1C,UAAM,MAAM,WAAW;AAEvB,UAAM,UAAU;AAAA;AAAA;AAAA;AAAA;AAAA,YAKR,GAAG;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAOX,UAAM,cAAc,KAAK,aAAa,eAAe;AACrD,kBAAc,aAAa,OAAO;AAClC,QAAI;AACF,eAAS,gCAAgC;AACzC,eAAS,2BAA2B,eAAe,EAAE;AAAA,IACvD,SAAS,KAAK;AACZ,cAAQ,MAAM,qCAAqC,eAAe,QAAQ,IAAI,UAAU,GAAG;AAC3F;AAAA,IACF;AACA,YAAQ,IAAI,4BAA4B,WAAW,EAAE;AACrD,YAAQ,IAAI,yCAAyC;AAAA,EACvD,WAAW,OAAO,UAAU;AAC1B,cAAU,aAAa,EAAE,WAAW,KAAK,CAAC;AAC1C,UAAM,MAAM,WAAW;AAEvB,UAAM,QAAQ;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,cAQJ,GAAG;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,YAUL,KAAK,QAAQ,GAAG,gBAAgB,YAAY,CAAC;AAAA;AAAA,YAE7C,KAAK,QAAQ,GAAG,gBAAgB,kBAAkB,CAAC;AAAA;AAAA;AAG3D,UAAM,YAAY,KAAK,aAAa,aAAa;AACjD,kBAAc,WAAW,KAAK;AAC9B,YAAQ,IAAI,4BAA4B,SAAS,EAAE;AACnD,YAAQ,IAAI,yBAAyB,SAAS;AAAA,EAChD,OAAO;AACL,YAAQ,MAAM,+BAA+B,EAAE,yCAAyC;AAAA,EAC1F;AACF;AAEO,SAAS,mBAAyB;AACvC,QAAM,KAAK,SAAS;AAEpB,MAAI,OAAO,SAAS;AAClB,UAAM,cAAc,KAAK,aAAa,eAAe;AACrD,QAAI;AACF,eAAS,4BAA4B,eAAe,EAAE;AAAA,IACxD,QAAQ;AAAA,IAER;AACA,QAAI,WAAW,WAAW,EAAG,YAAW,WAAW;AACnD,QAAI;AACF,eAAS,gCAAgC;AAAA,IAC3C,SAAS,KAAK;AACZ,cAAQ,MAAM,6BAA6B,eAAe,QAAQ,IAAI,UAAU,GAAG;AAAA,IACrF;AACA,YAAQ,IAAI,6BAA6B;AAAA,EAC3C,WAAW,OAAO,UAAU;AAC1B,UAAM,YAAY,KAAK,aAAa,aAAa;AACjD,QAAI;AACF,eAAS,oBAAoB,SAAS,EAAE;AAAA,IAC1C,QAAQ;AAAA,IAER;AACA,QAAI,WAAW,SAAS,EAAG,YAAW,SAAS;AAC/C,YAAQ,IAAI,6BAA6B;AAAA,EAC3C,OAAO;AACL,YAAQ,MAAM,+BAA+B,EAAE,GAAG;AAAA,EACpD;AACF;;;ADxGA,IAAM,aAAaC,MAAKC,SAAQ,GAAG,cAAc;AACjD,IAAM,WAAWD,MAAK,YAAY,YAAY;AAC9C,IAAM,WAAWA,MAAK,YAAY,YAAY;AAC9C,IAAM,eAAeA,MAAK,YAAY,kBAAkB;AAEjD,SAAS,oBAA6B;AAC3C,QAAM,SAAS,IAAI,QAAQ,QAAQ,EAAE,YAAY,+BAA+B;AAEhF,SACG,QAAQ,OAAO,EACf,YAAY,+BAA+B,EAC3C,OAAO,YAAY;AAClB,QAAI,gBAAgB,QAAQ,GAAG;AAC7B,YAAM,MAAM,QAAQ,QAAQ;AAC5B,cAAQ,IAAI,gCAAgC,GAAG,GAAG;AAClD,cAAQ,KAAK,CAAC;AAAA,IAChB;AACA,cAAU,QAAQ;AAGlB,UAAM,UAAU,cAAc,IAAI,IAAI,KAAK,YAAY,GAAG,CAAC;AAC3D,UAAM,cAAcA,MAAK,SAAS,WAAW;AAC7C,UAAM,QAAQ,SAAS,UAAU,GAAG;AACpC,UAAM,QAAQ,SAAS,cAAc,GAAG;AACxC,UAAM,QAAQ,MAAM,QAAQ,UAAU,CAAC,WAAW,GAAG;AAAA,MACnD,UAAU;AAAA,MACV,OAAO,CAAC,UAAU,OAAO,KAAK;AAAA,MAC9B,KAAK,EAAE,GAAG,QAAQ,KAAK,oBAAoB,IAAI;AAAA,IACjD,CAAC;AAED,UAAM,MAAM;AACZ,cAAU,KAAK;AACf,cAAU,KAAK;AAGf,UAAM,IAAI,QAAQ,CAACE,aAAY,WAAWA,UAAS,IAAI,CAAC;AACxD,QAAI,gBAAgB,QAAQ,GAAG;AAC7B,YAAM,MAAM,QAAQ,QAAQ;AAC5B,cAAQ,IAAI,wBAAwB,GAAG,GAAG;AAC1C,cAAQ,KAAK,CAAC;AAAA,IAChB,OAAO;AACL,cAAQ,MAAM,uCAAuC,MAAM,GAAG,IAAI;AAClE,cAAQ,MAAM,eAAe,YAAY,EAAE;AAC3C,cAAQ,KAAK,CAAC;AAAA,IAChB;AAAA,EACF,CAAC;AAEH,SACG,QAAQ,KAAK,EACb,YAAY,qDAAqD,EACjE,OAAO,YAAY;AAClB,QAAI,gBAAgB,QAAQ,GAAG;AAC7B,YAAM,MAAM,QAAQ,QAAQ;AAC5B,cAAQ,IAAI,gCAAgC,GAAG,GAAG;AAClD,cAAQ,KAAK,CAAC;AAAA,IAChB;AAEA,UAAM,EAAE,UAAU,IAAI,MAAM,OAAO,aAAoB;AACvD,UAAM,UAAU;AAAA,EAClB,CAAC;AAEH,SACG,QAAQ,MAAM,EACd,YAAY,iBAAiB,EAC7B,OAAO,YAAY;AAClB,UAAM,MAAM,QAAQ,QAAQ;AAC5B,QAAI,QAAQ,MAAM;AAChB,cAAQ,IAAI,uBAAuB;AACnC;AAAA,IACF;AACA,QAAI;AACF,cAAQ,KAAK,KAAK,SAAS;AAC3B,gBAAU,QAAQ;AAClB,cAAQ,IAAI,wBAAwB,GAAG,GAAG;AAAA,IAC5C,QAAQ;AACN,gBAAU,QAAQ;AAClB,cAAQ,IAAI,4CAA4C;AAAA,IAC1D;AAAA,EACF,CAAC;AAEH,SACG,QAAQ,QAAQ,EAChB,YAAY,oBAAoB,EAChC,OAAO,YAAY;AAClB,QAAI,gBAAgB,QAAQ,GAAG;AAC7B,YAAM,MAAM,QAAQ,QAAQ;AAC5B,cAAQ,IAAI,2BAA2B,GAAG,GAAG;AAAA,IAC/C,OAAO;AACL,gBAAU,QAAQ;AAClB,cAAQ,IAAI,uBAAuB;AAAA,IACrC;AAAA,EACF,CAAC;AAEH,SACG,QAAQ,QAAQ,EAChB,YAAY,2BAA2B,EACvC,OAAO,YAAY;AAClB,oBAAgB;AAAA,EAClB,CAAC;AAEH,SACG,QAAQ,SAAS,EACjB,YAAY,4BAA4B,EACxC,OAAO,YAAY;AAClB,qBAAiB;AAAA,EACnB,CAAC;AAEH,SAAO;AACT;;;AErHA,SAAS,WAAAC,gBAAe;AACxB,SAAS,QAAAC,OAAM,eAAe;AAC9B,SAAS,WAAAC,gBAAe;AACxB,SAAS,cAAAC,aAAY,aAAAC,YAAW,oBAAoB;AACpD,SAAS,SAAAC,cAAa;AACtB,SAAS,UAAU,gBAAgB;AACnC,SAAS,sBAAsB,cAAc,wBAAwB;;;ACNrE,SAAS,oBAAoB;AAUtB,IAAM,eAA4B;AAAA,EACvC;AAAA,IACE,MAAM;AAAA,IACN,SAAS;AAAA,IACT,YAAY;AAAA,IACZ,eAAe;AAAA,IACf,YAAY,CAAC,WAAW;AAAA,EAC1B;AAAA,EACA;AAAA,IACE,MAAM;AAAA,IACN,SAAS;AAAA,IACT,YAAY;AAAA,IACZ,eAAe;AAAA,IACf,YAAY,CAAC,WAAW;AAAA,EAC1B;AAAA,EACA;AAAA,IACE,MAAM;AAAA,IACN,SAAS;AAAA,IACT,YAAY;AAAA,IACZ,eAAe;AAAA,IACf,YAAY,CAAC,WAAW;AAAA,EAC1B;AAAA,EACA;AAAA,IACE,MAAM;AAAA,IACN,SAAS;AAAA,IACT,YAAY;AAAA,IACZ,eAAe;AAAA,IACf,YAAY,CAAC,WAAW;AAAA,EAC1B;AACF;AAEO,SAAS,wBAAqC;AACnD,QAAM,QAAqB,CAAC;AAC5B,aAAW,SAAS,cAAc;AAChC,QAAI;AACF,mBAAa,MAAM,eAAe,MAAM,YAAY;AAAA,QAClD,OAAO;AAAA,QACP,SAAS;AAAA,MACX,CAAC;AACD,YAAM,KAAK,KAAK;AAAA,IAClB,QAAQ;AAAA,IAER;AAAA,EACF;AACA,SAAO;AACT;;;AD1CA,IAAMC,cAAaC,MAAKC,SAAQ,GAAG,cAAc;AACjD,IAAM,cAAcD,MAAKD,aAAY,aAAa;AAGlD,IAAM,oBAAoBA;AAE1B,IAAM,qBAAqB;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,kFAUuD,WAAW;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAoBtF,SAAS,kBAA2B;AACzC,SAAO,IAAIG,SAAQ,MAAM,EACtB,YAAY,0BAA0B,EACtC,OAAO,YAAY;AAClB,YAAQ,IAAI,iCAAiC;AAG7C,YAAQ,IAAI,oCAAoC;AAChD,UAAM,SAAS,sBAAsB;AAErC,QAAI,OAAO,WAAW,GAAG;AACvB,cAAQ,MAAM,iCAAiC;AAC/C,cAAQ,MAAM,kDAAkD;AAChE,cAAQ,KAAK,CAAC;AAAA,IAChB;AAEA,eAAW,SAAS,QAAQ;AAC1B,cAAQ,IAAI,YAAY,MAAM,IAAI,UAAU;AAAA,IAC9C;AAEA,UAAM,WAAW,OAAO,CAAC;AACzB,YAAQ,IAAI;AAAA,SAAY,SAAS,IAAI;AAAA,CAAI;AACzC,YAAQ,IAAI,2BAA2B;AAGvC,UAAM,OAAOC,OAAM,SAAS,SAAS,CAAC,SAAS,UAAU,GAAG;AAAA,MAC1D,OAAO,CAAC,QAAQ,QAAQ,SAAS;AAAA,IACnC,CAAC;AAED,UAAM,SAAS;AAAA,MACb,SAAS,MAAM,KAAK,KAAM;AAAA,MAC1B,SAAS,MAAM,KAAK,MAAO;AAAA,IAC7B;AAGA,UAAM,WAAW,MAAM,OAAO,UAAe;AAC7C,UAAM,KAAK,SAAS,gBAAgB;AAAA,MAClC,OAAO,QAAQ;AAAA,MACf,QAAQ,QAAQ;AAAA,IAClB,CAAC;AAED,UAAM,UAAU,CAAC,WACf,IAAI,QAAQ,CAACC,aAAY,GAAG,SAAS,QAAQA,QAAO,CAAC;AAEvD,UAAM,SAAiB;AAAA,MACrB,MAAM,kBAAkB,QAAQ;AAC9B,cAAM,QAAQ,OAAO,SAAS,SAAS;AACvC,cAAM,OAAO,OAAO,SAAS,QAAQ;AAIrC,cAAM,cAAc,SAAS,qBAAqB,SAAS,QAAQ,SAAS;AAC5E,YAAI,eAAe,OAAO,SAAS,WAAW,QAAQ;AACpD,gBAAM,eAAe,OAAO,SAAS,UAAU;AAAA,YAC7C,CAAC,QAA0B;AACzB,oBAAM,WAAW,QAAQ,IAAI,IAAI;AACjC,qBAAO,SAAS,WAAW,oBAAoB,GAAG,KAAK,aAAa;AAAA,YACtE;AAAA,UACF;AACA,cAAI,cAAc;AAChB,kBAAM,cAAc,OAAO,QAAQ,KAAK,CAAC,MAAwB,EAAE,SAAS,YAAY;AACxF,gBAAI,aAAa;AACf,qBAAO,EAAE,SAAS,EAAE,SAAS,YAAqB,UAAU,YAAY,SAAS,EAAE;AAAA,YACrF;AAAA,UACF;AAAA,QACF;AAGA,gBAAQ,IAAI;AAAA,2BAA8B;AAC1C,gBAAQ,IAAI,SAAS,KAAK,EAAE;AAC5B,gBAAQ,IAAI,SAAS,IAAI,EAAE;AAC3B,gBAAQ,IAAI,UAAU;AACtB,iBAAS,IAAI,GAAG,IAAI,OAAO,QAAQ,QAAQ,KAAK;AAC9C,gBAAM,MAAM,OAAO,QAAQ,CAAC;AAC5B,kBAAQ,IAAI,KAAK,IAAI,CAAC,KAAK,IAAI,IAAI,KAAK,IAAI,IAAI,GAAG;AAAA,QACrD;AAEA,cAAM,SAAS,MAAM,QAAQ,oBAAoB,OAAO,QAAQ,MAAM,uBAAuB;AAC7F,YAAI,OAAO,YAAY,MAAM,KAAK;AAChC,gBAAMC,gBAAe,OAAO,QAAQ,KAAK,CAAC,MAAwB,EAAE,SAAS,aAAa;AAC1F,cAAIA,eAAc;AAChB,mBAAO,EAAE,SAAS,EAAE,SAAS,YAAqB,UAAUA,cAAa,SAAS,EAAE;AAAA,UACtF;AACA,iBAAO,EAAE,SAAS,EAAE,SAAS,YAAqB,EAAE;AAAA,QACtD;AAEA,cAAM,MAAM,SAAS,QAAQ,EAAE,IAAI;AACnC,YAAI,OAAO,KAAK,MAAM,OAAO,QAAQ,QAAQ;AAC3C,iBAAO,EAAE,SAAS,EAAE,SAAS,YAAqB,UAAU,OAAO,QAAQ,GAAG,EAAE,SAAS,EAAE;AAAA,QAC7F;AAGA,cAAM,eAAe,OAAO,QAAQ,KAAK,CAAC,MAAwB,EAAE,SAAS,aAAa;AAC1F,YAAI,cAAc;AAChB,iBAAO,EAAE,SAAS,EAAE,SAAS,YAAqB,UAAU,aAAa,SAAS,EAAE;AAAA,QACtF;AACA,eAAO,EAAE,SAAS,EAAE,SAAS,YAAqB,EAAE;AAAA,MACtD;AAAA,MACA,MAAM,cAAc,QAAQ;AAC1B,cAAM,SAAS,OAAO;AACtB,YAAI,OAAO,kBAAkB,yBAAyB,OAAO,QAAQ,SAAS,QAAQ;AACpF,kBAAQ,OAAO,MAAM,OAAO,QAAQ,IAAI;AAAA,QAC1C;AAAA,MACF;AAAA,IACF;AAEA,UAAM,aAAa,IAAI,qBAAqB,CAAC,WAAW,QAAQ,MAAM;AAEtE,UAAM,WAAW,WAAW;AAAA,MAC1B,iBAAiB;AAAA,MACjB,oBAAoB;AAAA,QAClB,IAAI,EAAE,cAAc,MAAM,eAAe,KAAK;AAAA,QAC9C,UAAU;AAAA,MACZ;AAAA,MACA,YAAY,EAAE,MAAM,oBAAoB,OAAO,oBAAoB,SAAS,QAAY;AAAA,IAC1F,CAAC;AAED,IAAAC,WAAUP,aAAY,EAAE,WAAW,KAAK,CAAC;AAEzC,UAAM,EAAE,UAAU,IAAI,MAAM,WAAW,WAAW;AAAA,MAChD,KAAKA;AAAA,MACL,YAAY,CAAC;AAAA,IACf,CAAC;AAGD,UAAM,WAAW,OAAO;AAAA,MACtB;AAAA,MACA,QAAQ;AAAA,QACN,EAAE,MAAM,QAAQ,MAAM,mBAAmB;AAAA,QACzC,EAAE,MAAM,QAAQ,MAAM,6BAA6B,SAAS,UAAU;AAAA,yBAA4B;AAAA,MACpG;AAAA,IACF,CAAC;AAGD,QAAIQ,YAAW,WAAW,GAAG;AAC3B,UAAI;AACF,cAAM,UAAU,aAAa,aAAa,OAAO;AACjD,oBAAY,OAAO;AACnB,gBAAQ,IAAI,yCAAyC,WAAW;AAChE,gBAAQ,IAAI,8CAA8C;AAC1D,WAAG,MAAM;AACT,aAAK,KAAK;AACV,gBAAQ,KAAK,CAAC;AAAA,MAChB,QAAQ;AAAA,MAER;AAAA,IACF;AAGA,WAAO,MAAM;AACX,YAAM,QAAQ,MAAM,QAAQ,MAAM;AAClC,UAAI,MAAM,YAAY,MAAM,UAAU,MAAM,YAAY,MAAM,OAAQ;AAEtE,YAAM,SAAS,MAAM,WAAW,OAAO;AAAA,QACrC;AAAA,QACA,QAAQ,CAAC,EAAE,MAAM,QAAQ,MAAM,MAAM,CAAC;AAAA,MACxC,CAAC;AAED,UAAI,OAAO,eAAe,cAAcA,YAAW,WAAW,GAAG;AAE/D,YAAI;AACF,gBAAM,UAAU,aAAa,aAAa,OAAO;AACjD,sBAAY,OAAO;AACnB,kBAAQ,IAAI,yCAAyC,WAAW;AAChE,kBAAQ,IAAI,8CAA8C;AAC1D;AAAA,QACF,QAAQ;AAAA,QAER;AAAA,MACF;AAAA,IACF;AAEA,OAAG,MAAM;AACT,SAAK,KAAK;AACV,YAAQ,KAAK,CAAC;AAAA,EAChB,CAAC;AACL;;;AEjOA,SAAS,WAAAC,gBAAe;AACxB,SAAS,QAAAC,aAAY;AACrB,SAAS,WAAAC,gBAAe;AACxB,SAAS,gBAAAC,qBAAoB;AAK7B,IAAMC,cAAaC,MAAKC,SAAQ,GAAG,cAAc;AACjD,IAAMC,YAAWF,MAAKD,aAAY,YAAY;AAE9C,eAAe,qBAAsC;AACnD,QAAM,MAAM,MAAM,MAAM,+CAA+C;AACvE,MAAI,CAAC,IAAI,GAAI,OAAM,IAAI,MAAM,yBAAyB,IAAI,MAAM,EAAE;AAClE,QAAM,OAAQ,MAAM,IAAI,KAAK;AAC7B,SAAO,KAAK;AACd;AAEA,SAAS,aAAmB;AAC1B,QAAM,MAAM,QAAQG,SAAQ;AAC5B,MAAI,QAAQ,KAAM;AAClB,MAAI;AACF,YAAQ,KAAK,KAAK,SAAS;AAC3B,cAAUA,SAAQ;AAClB,YAAQ,IAAI,wBAAwB,GAAG,GAAG;AAAA,EAC5C,QAAQ;AACN,cAAUA,SAAQ;AAAA,EACpB;AACF;AAEO,SAAS,oBAA6B;AAC3C,SAAO,IAAIC,SAAQ,QAAQ,EACxB,YAAY,0CAA0C,EACtD,OAAO,YAAY;AAClB,UAAM,UAAU;AAEhB,YAAQ,IAAI,qBAAqB,OAAO,EAAE;AAC1C,YAAQ,IAAI,yBAAyB;AAErC,QAAI;AACJ,QAAI;AACF,eAAS,MAAM,mBAAmB;AAAA,IACpC,SAAS,KAAK;AACZ,cAAQ,MAAM,gCAAiC,IAAc,OAAO;AACpE,cAAQ,KAAK,CAAC;AAAA,IAChB;AAEA,QAAI,YAAY,QAAQ;AACtB,cAAQ,IAAI,wBAAwB,OAAO,GAAG;AAC9C;AAAA,IACF;AAEA,YAAQ,IAAI,sBAAsB,OAAO,YAAO,MAAM,EAAE;AAExD,UAAM,aAAa,gBAAgBD,SAAQ;AAC3C,QAAI,YAAY;AACd,cAAQ,IAAI,oBAAoB;AAChC,iBAAW;AAAA,IACb;AAIA,YAAQ,IAAI,qDAAqD;AACjE,QAAI;AACF,MAAAE,cAAa,OAAO,CAAC,SAAS,sBAAsB,UAAU,OAAO,GAAG;AAAA,QACtE,OAAO;AAAA,MACT,CAAC;AAAA,IACH,QAAQ;AACN,cAAQ,MAAM,0CAA0C;AACxD,cAAQ,MAAM,2DAA2D;AACzE,cAAQ,KAAK,CAAC;AAAA,IAChB;AAEA,YAAQ,IAAI,eAAe,MAAM,EAAE;AAAA,EACrC,CAAC;AACL;;;ALpEO,SAAS,YAAqB;AACnC,QAAM,UAAU,IAAIC,SAAQ;AAE5B,UACG,KAAK,aAAa,EAClB,YAAY,mCAAmC,EAC/C,QAAQ,OAAW;AAEtB,UAAQ,WAAW,gBAAgB,CAAC;AACpC,UAAQ,WAAW,kBAAkB,CAAC;AACtC,UAAQ,WAAW,kBAAkB,CAAC;AAEtC,SAAO;AACT;;;AMlBA,UAAU,EAAE,MAAM;","names":["Command","join","homedir","join","homedir","resolve","Command","join","homedir","existsSync","mkdirSync","spawn","CONFIG_DIR","join","homedir","Command","spawn","resolve","rejectOption","mkdirSync","existsSync","Command","join","homedir","execFileSync","CONFIG_DIR","join","homedir","PID_PATH","Command","execFileSync","Command"]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "acp-discord",
3
- "version": "0.2.0",
3
+ "version": "0.4.0",
4
4
  "description": "Discord bot that wraps ACP protocol for coding agents",
5
5
  "type": "module",
6
6
  "bin": {
@@ -31,6 +31,7 @@
31
31
  "dependencies": {
32
32
  "@agentclientprotocol/sdk": "^0.15.0",
33
33
  "commander": "^14.0.3",
34
+ "diff": "^8.0.3",
34
35
  "discord.js": "^14.25.1",
35
36
  "smol-toml": "^1.6.0"
36
37
  },