agentcord 0.1.6 → 0.1.8

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -53,7 +53,11 @@ import {
53
53
  Routes
54
54
  } from "discord.js";
55
55
  function getCommandDefinitions() {
56
- const claude = new SlashCommandBuilder().setName("claude").setDescription("Manage Claude Code sessions").addSubcommand((sub) => sub.setName("new").setDescription("Create a new Claude Code session").addStringOption((opt) => opt.setName("name").setDescription("Session name").setRequired(true)).addStringOption((opt) => opt.setName("directory").setDescription("Working directory (default: configured default)"))).addSubcommand((sub) => sub.setName("resume").setDescription("Resume an existing Claude Code session from terminal").addStringOption((opt) => opt.setName("session-id").setDescription("Claude Code session UUID").setRequired(true).setAutocomplete(true)).addStringOption((opt) => opt.setName("name").setDescription("Name for the Discord channel").setRequired(true)).addStringOption((opt) => opt.setName("directory").setDescription("Working directory (default: configured default)"))).addSubcommand((sub) => sub.setName("list").setDescription("List active sessions")).addSubcommand((sub) => sub.setName("end").setDescription("End the session in this channel")).addSubcommand((sub) => sub.setName("continue").setDescription("Continue the last conversation")).addSubcommand((sub) => sub.setName("stop").setDescription("Stop current generation")).addSubcommand((sub) => sub.setName("output").setDescription("Show recent conversation output").addIntegerOption((opt) => opt.setName("lines").setDescription("Number of lines (default 50)").setMinValue(1).setMaxValue(500))).addSubcommand((sub) => sub.setName("attach").setDescription("Show tmux attach command for terminal access")).addSubcommand((sub) => sub.setName("sync").setDescription("Reconnect orphaned tmux sessions")).addSubcommand((sub) => sub.setName("model").setDescription("Change the model for this session").addStringOption((opt) => opt.setName("model").setDescription("Model name (e.g. claude-sonnet-4-5-20250929)").setRequired(true))).addSubcommand((sub) => sub.setName("verbose").setDescription("Toggle showing tool calls and results in this session"));
56
+ const claude = new SlashCommandBuilder().setName("claude").setDescription("Manage Claude Code sessions").addSubcommand((sub) => sub.setName("new").setDescription("Create a new Claude Code session").addStringOption((opt) => opt.setName("name").setDescription("Session name").setRequired(true)).addStringOption((opt) => opt.setName("directory").setDescription("Working directory (default: configured default)"))).addSubcommand((sub) => sub.setName("resume").setDescription("Resume an existing Claude Code session from terminal").addStringOption((opt) => opt.setName("session-id").setDescription("Claude Code session UUID").setRequired(true).setAutocomplete(true)).addStringOption((opt) => opt.setName("name").setDescription("Name for the Discord channel").setRequired(true)).addStringOption((opt) => opt.setName("directory").setDescription("Working directory (default: configured default)"))).addSubcommand((sub) => sub.setName("list").setDescription("List active sessions")).addSubcommand((sub) => sub.setName("end").setDescription("End the session in this channel")).addSubcommand((sub) => sub.setName("continue").setDescription("Continue the last conversation")).addSubcommand((sub) => sub.setName("stop").setDescription("Stop current generation")).addSubcommand((sub) => sub.setName("output").setDescription("Show recent conversation output").addIntegerOption((opt) => opt.setName("lines").setDescription("Number of lines (default 50)").setMinValue(1).setMaxValue(500))).addSubcommand((sub) => sub.setName("attach").setDescription("Show tmux attach command for terminal access")).addSubcommand((sub) => sub.setName("sync").setDescription("Reconnect orphaned tmux sessions")).addSubcommand((sub) => sub.setName("model").setDescription("Change the model for this session").addStringOption((opt) => opt.setName("model").setDescription("Model name (e.g. claude-sonnet-4-5-20250929)").setRequired(true))).addSubcommand((sub) => sub.setName("verbose").setDescription("Toggle showing tool calls and results in this session")).addSubcommand((sub) => sub.setName("mode").setDescription("Set session mode (auto/plan/normal)").addStringOption((opt) => opt.setName("mode").setDescription("Session mode").setRequired(true).addChoices(
57
+ { name: "Auto \u2014 full autonomy", value: "auto" },
58
+ { name: "Plan \u2014 plan before executing", value: "plan" },
59
+ { name: "Normal \u2014 ask before destructive ops", value: "normal" }
60
+ )));
57
61
  const shell = new SlashCommandBuilder().setName("shell").setDescription("Run shell commands in the session directory").addSubcommand((sub) => sub.setName("run").setDescription("Execute a shell command").addStringOption((opt) => opt.setName("command").setDescription("Command to run").setRequired(true))).addSubcommand((sub) => sub.setName("processes").setDescription("List running processes")).addSubcommand((sub) => sub.setName("kill").setDescription("Kill a running process").addIntegerOption((opt) => opt.setName("pid").setDescription("Process ID to kill").setRequired(true)));
58
62
  const agent = new SlashCommandBuilder().setName("agent").setDescription("Manage agent personas").addSubcommand((sub) => sub.setName("use").setDescription("Switch to an agent persona").addStringOption((opt) => opt.setName("persona").setDescription("Agent persona name").setRequired(true).addChoices(
59
63
  { name: "Code Reviewer", value: "code-reviewer" },
@@ -65,11 +69,33 @@ function getCommandDefinitions() {
65
69
  { name: "General", value: "general" }
66
70
  ))).addSubcommand((sub) => sub.setName("list").setDescription("List available agent personas")).addSubcommand((sub) => sub.setName("clear").setDescription("Clear agent persona"));
67
71
  const project = new SlashCommandBuilder().setName("project").setDescription("Configure project settings").addSubcommand((sub) => sub.setName("personality").setDescription("Set a custom personality for this project").addStringOption((opt) => opt.setName("prompt").setDescription("System prompt for the project").setRequired(true))).addSubcommand((sub) => sub.setName("personality-show").setDescription("Show the current project personality")).addSubcommand((sub) => sub.setName("personality-clear").setDescription("Clear the project personality")).addSubcommand((sub) => sub.setName("skill-add").setDescription("Add a skill (prompt template) to this project").addStringOption((opt) => opt.setName("name").setDescription("Skill name").setRequired(true)).addStringOption((opt) => opt.setName("prompt").setDescription("Prompt template (use {input} for placeholder)").setRequired(true))).addSubcommand((sub) => sub.setName("skill-remove").setDescription("Remove a skill").addStringOption((opt) => opt.setName("name").setDescription("Skill name").setRequired(true))).addSubcommand((sub) => sub.setName("skill-list").setDescription("List all skills for this project")).addSubcommand((sub) => sub.setName("skill-run").setDescription("Execute a skill").addStringOption((opt) => opt.setName("name").setDescription("Skill name").setRequired(true)).addStringOption((opt) => opt.setName("input").setDescription("Input to pass to the skill template"))).addSubcommand((sub) => sub.setName("mcp-add").setDescription("Add an MCP server to this project").addStringOption((opt) => opt.setName("name").setDescription("Server name").setRequired(true)).addStringOption((opt) => opt.setName("command").setDescription("Command to run (e.g. npx my-mcp-server)").setRequired(true)).addStringOption((opt) => opt.setName("args").setDescription("Arguments (comma-separated)"))).addSubcommand((sub) => sub.setName("mcp-remove").setDescription("Remove an MCP server").addStringOption((opt) => opt.setName("name").setDescription("Server name").setRequired(true))).addSubcommand((sub) => sub.setName("mcp-list").setDescription("List configured MCP servers")).addSubcommand((sub) => sub.setName("info").setDescription("Show project configuration"));
72
+ const plugin = new SlashCommandBuilder().setName("plugin").setDescription("Manage Claude Code plugins").addSubcommand((sub) => sub.setName("browse").setDescription("Browse available plugins from marketplaces").addStringOption((opt) => opt.setName("search").setDescription("Filter by name or keyword"))).addSubcommand((sub) => sub.setName("install").setDescription("Install a plugin").addStringOption((opt) => opt.setName("plugin").setDescription("Plugin name (e.g. feature-dev@claude-plugins-official)").setRequired(true).setAutocomplete(true)).addStringOption((opt) => opt.setName("scope").setDescription("Installation scope (default: user)").addChoices(
73
+ { name: "User \u2014 available everywhere", value: "user" },
74
+ { name: "Project \u2014 this project only", value: "project" },
75
+ { name: "Local \u2014 this directory only", value: "local" }
76
+ ))).addSubcommand((sub) => sub.setName("remove").setDescription("Uninstall a plugin").addStringOption((opt) => opt.setName("plugin").setDescription("Plugin ID").setRequired(true).setAutocomplete(true)).addStringOption((opt) => opt.setName("scope").setDescription("Scope to uninstall from (default: user)").addChoices(
77
+ { name: "User", value: "user" },
78
+ { name: "Project", value: "project" },
79
+ { name: "Local", value: "local" }
80
+ ))).addSubcommand((sub) => sub.setName("list").setDescription("List installed plugins")).addSubcommand((sub) => sub.setName("info").setDescription("Show detailed info for a plugin").addStringOption((opt) => opt.setName("plugin").setDescription("Plugin name or ID").setRequired(true).setAutocomplete(true))).addSubcommand((sub) => sub.setName("enable").setDescription("Enable a disabled plugin").addStringOption((opt) => opt.setName("plugin").setDescription("Plugin ID").setRequired(true).setAutocomplete(true)).addStringOption((opt) => opt.setName("scope").setDescription("Scope (default: user)").addChoices(
81
+ { name: "User", value: "user" },
82
+ { name: "Project", value: "project" },
83
+ { name: "Local", value: "local" }
84
+ ))).addSubcommand((sub) => sub.setName("disable").setDescription("Disable a plugin").addStringOption((opt) => opt.setName("plugin").setDescription("Plugin ID").setRequired(true).setAutocomplete(true)).addStringOption((opt) => opt.setName("scope").setDescription("Scope (default: user)").addChoices(
85
+ { name: "User", value: "user" },
86
+ { name: "Project", value: "project" },
87
+ { name: "Local", value: "local" }
88
+ ))).addSubcommand((sub) => sub.setName("update").setDescription("Update a plugin to latest version").addStringOption((opt) => opt.setName("plugin").setDescription("Plugin ID").setRequired(true).setAutocomplete(true)).addStringOption((opt) => opt.setName("scope").setDescription("Scope (default: user)").addChoices(
89
+ { name: "User", value: "user" },
90
+ { name: "Project", value: "project" },
91
+ { name: "Local", value: "local" }
92
+ ))).addSubcommand((sub) => sub.setName("marketplace-add").setDescription("Add a plugin marketplace").addStringOption((opt) => opt.setName("source").setDescription("GitHub repo (owner/repo) or git URL").setRequired(true))).addSubcommand((sub) => sub.setName("marketplace-remove").setDescription("Remove a marketplace").addStringOption((opt) => opt.setName("name").setDescription("Marketplace name").setRequired(true).setAutocomplete(true))).addSubcommand((sub) => sub.setName("marketplace-list").setDescription("List registered marketplaces")).addSubcommand((sub) => sub.setName("marketplace-update").setDescription("Update marketplace catalogs").addStringOption((opt) => opt.setName("name").setDescription("Specific marketplace (or all if omitted)").setAutocomplete(true)));
68
93
  return [
69
94
  claude.toJSON(),
70
95
  shell.toJSON(),
71
96
  agent.toJSON(),
72
- project.toJSON()
97
+ project.toJSON(),
98
+ plugin.toJSON()
73
99
  ];
74
100
  }
75
101
  async function registerCommands() {
@@ -100,8 +126,8 @@ import {
100
126
  ChannelType
101
127
  } from "discord.js";
102
128
  import { readdirSync, statSync, createReadStream } from "fs";
103
- import { join as join3, basename } from "path";
104
- import { homedir as homedir2 } from "os";
129
+ import { join as join4, basename } from "path";
130
+ import { homedir as homedir3 } from "os";
105
131
  import { createInterface } from "readline";
106
132
 
107
133
  // src/session-manager.ts
@@ -241,6 +267,9 @@ async function saveProjects() {
241
267
  function getProject(name) {
242
268
  return projects[name];
243
269
  }
270
+ function getProjectByCategoryId(categoryId) {
271
+ return Object.values(projects).find((p) => p.categoryId === categoryId);
272
+ }
244
273
  function getOrCreateProject(name, directory, categoryId) {
245
274
  if (!projects[name]) {
246
275
  projects[name] = {
@@ -427,14 +456,19 @@ function detectNumberedOptions(text) {
427
456
  const options = [];
428
457
  const optionRegex = /^\s*(\d+)[.)]\s+(.+)$/;
429
458
  let firstOptionLine = -1;
459
+ let lastOptionLine = -1;
430
460
  for (let i = 0; i < lines.length; i++) {
431
461
  const match = lines[i].match(optionRegex);
432
462
  if (match) {
433
463
  if (firstOptionLine === -1) firstOptionLine = i;
464
+ lastOptionLine = i;
434
465
  options.push(match[2].trim());
435
466
  }
436
467
  }
437
468
  if (options.length < 2 || options.length > 6) return null;
469
+ if (options.some((o) => o.length > 80)) return null;
470
+ const linesAfter = lines.slice(lastOptionLine + 1).filter((l) => l.trim()).length;
471
+ if (linesAfter > 3) return null;
438
472
  const preamble = lines.slice(0, firstOptionLine).join(" ").toLowerCase();
439
473
  const hasQuestion = /\?\s*$/.test(preamble.trim()) || /\b(which|choose|select|pick|prefer|would you like|how would you|what approach|option)\b/.test(preamble);
440
474
  return hasQuestion ? options : null;
@@ -446,6 +480,11 @@ function detectYesNoPrompt(text) {
446
480
 
447
481
  // src/session-manager.ts
448
482
  var SESSION_PREFIX = "claude-";
483
+ var MODE_PROMPTS = {
484
+ auto: "",
485
+ plan: "You MUST use EnterPlanMode at the start of every task. Present your plan for user approval before making any code changes. Do not write or edit files until the user approves the plan.",
486
+ normal: "Before performing destructive or significant operations (deleting files, running dangerous commands, making large refactors, writing to many files), use AskUserQuestion to confirm with the user first. Ask for explicit approval before proceeding with changes."
487
+ };
449
488
  var sessionStore = new Store("sessions.json");
450
489
  var sessions = /* @__PURE__ */ new Map();
451
490
  var channelToSession = /* @__PURE__ */ new Map();
@@ -473,6 +512,7 @@ async function loadSessions() {
473
512
  sessions.set(s.id, {
474
513
  ...s,
475
514
  verbose: s.verbose ?? false,
515
+ mode: s.mode ?? "auto",
476
516
  isGenerating: false
477
517
  });
478
518
  channelToSession.set(s.channelId, s.id);
@@ -499,6 +539,7 @@ async function saveSessions() {
499
539
  model: s.model,
500
540
  agentPersona: s.agentPersona,
501
541
  verbose: s.verbose || void 0,
542
+ mode: s.mode !== "auto" ? s.mode : void 0,
502
543
  createdAt: s.createdAt,
503
544
  lastActivity: s.lastActivity,
504
545
  messageCount: s.messageCount,
@@ -532,6 +573,7 @@ async function createSession(name, directory, channelId, projectName, claudeSess
532
573
  tmuxName,
533
574
  claudeSessionId,
534
575
  verbose: false,
576
+ mode: "auto",
535
577
  isGenerating: false,
536
578
  createdAt: Date.now(),
537
579
  lastActivity: Date.now(),
@@ -601,6 +643,13 @@ function setVerbose(sessionId, verbose) {
601
643
  saveSessions();
602
644
  }
603
645
  }
646
+ function setMode(sessionId, mode) {
647
+ const session = sessions.get(sessionId);
648
+ if (session) {
649
+ session.mode = mode;
650
+ saveSessions();
651
+ }
652
+ }
604
653
  function setAgentPersona(sessionId, persona) {
605
654
  const session = sessions.get(sessionId);
606
655
  if (session) {
@@ -616,6 +665,8 @@ function buildSystemPrompt(session) {
616
665
  const agent = getAgent(session.agentPersona);
617
666
  if (agent?.systemPrompt) parts.push(agent.systemPrompt);
618
667
  }
668
+ const modePrompt = MODE_PROMPTS[session.mode];
669
+ if (modePrompt) parts.push(modePrompt);
619
670
  if (parts.length > 0) {
620
671
  return { type: "preset", preset: "claude_code", append: parts.join("\n\n") };
621
672
  }
@@ -630,9 +681,23 @@ async function* sendPrompt(sessionId, prompt) {
630
681
  session.isGenerating = true;
631
682
  session.lastActivity = Date.now();
632
683
  const systemPrompt = buildSystemPrompt(session);
684
+ let queryPrompt;
685
+ if (typeof prompt === "string") {
686
+ queryPrompt = prompt;
687
+ } else {
688
+ const userMessage = {
689
+ type: "user",
690
+ message: { role: "user", content: prompt },
691
+ parent_tool_use_id: null,
692
+ session_id: ""
693
+ };
694
+ queryPrompt = (async function* () {
695
+ yield userMessage;
696
+ })();
697
+ }
633
698
  try {
634
699
  const stream = query({
635
- prompt,
700
+ prompt: queryPrompt,
636
701
  options: {
637
702
  cwd: session.directory,
638
703
  resume: session.claudeSessionId,
@@ -756,16 +821,158 @@ async function listTmuxSessions() {
756
821
  }
757
822
  }
758
823
 
824
+ // src/plugin-manager.ts
825
+ import { execFile as execFile2 } from "child_process";
826
+ import { readFile as readFile3 } from "fs/promises";
827
+ import { join as join3 } from "path";
828
+ import { homedir as homedir2 } from "os";
829
+ var PLUGINS_DIR = join3(homedir2(), ".claude", "plugins");
830
+ var MARKETPLACES_DIR = join3(PLUGINS_DIR, "marketplaces");
831
+ function runClaude(args, cwd) {
832
+ return new Promise((resolve2) => {
833
+ execFile2("claude", args, {
834
+ cwd: cwd || process.cwd(),
835
+ timeout: 12e4,
836
+ env: { ...process.env }
837
+ }, (error, stdout, stderr) => {
838
+ resolve2({
839
+ stdout: stdout || "",
840
+ stderr: stderr || "",
841
+ code: error ? error.code ?? 1 : 0
842
+ });
843
+ });
844
+ });
845
+ }
846
+ var CACHE_TTL_MS = 3e4;
847
+ var installedCache = null;
848
+ var availableCache = null;
849
+ var marketplaceCache = null;
850
+ function invalidateCache() {
851
+ installedCache = null;
852
+ availableCache = null;
853
+ marketplaceCache = null;
854
+ }
855
+ async function listInstalled() {
856
+ if (installedCache && Date.now() - installedCache.ts < CACHE_TTL_MS) {
857
+ return installedCache.data;
858
+ }
859
+ const result = await runClaude(["plugin", "list", "--json"]);
860
+ if (result.code !== 0) throw new Error(`Failed to list plugins: ${result.stderr}`);
861
+ const data = JSON.parse(result.stdout);
862
+ installedCache = { data, ts: Date.now() };
863
+ return data;
864
+ }
865
+ async function listAvailable() {
866
+ if (availableCache && Date.now() - availableCache.ts < CACHE_TTL_MS) {
867
+ return availableCache.data;
868
+ }
869
+ const result = await runClaude(["plugin", "list", "--available", "--json"]);
870
+ if (result.code !== 0) throw new Error(`Failed to list available plugins: ${result.stderr}`);
871
+ const data = JSON.parse(result.stdout);
872
+ availableCache = { data, ts: Date.now() };
873
+ return data;
874
+ }
875
+ async function installPlugin(pluginId, scope, cwd) {
876
+ const result = await runClaude(["plugin", "install", pluginId, "-s", scope], cwd);
877
+ invalidateCache();
878
+ if (result.code !== 0) throw new Error(result.stderr || result.stdout || "Install failed");
879
+ return result.stdout.trim() || "Plugin installed successfully.";
880
+ }
881
+ async function uninstallPlugin(pluginId, scope, cwd) {
882
+ const result = await runClaude(["plugin", "uninstall", pluginId, "-s", scope], cwd);
883
+ invalidateCache();
884
+ if (result.code !== 0) throw new Error(result.stderr || result.stdout || "Uninstall failed");
885
+ return result.stdout.trim() || "Plugin uninstalled successfully.";
886
+ }
887
+ async function enablePlugin(pluginId, scope, cwd) {
888
+ const result = await runClaude(["plugin", "enable", pluginId, "-s", scope], cwd);
889
+ invalidateCache();
890
+ if (result.code !== 0) throw new Error(result.stderr || result.stdout || "Enable failed");
891
+ return result.stdout.trim() || "Plugin enabled.";
892
+ }
893
+ async function disablePlugin(pluginId, scope, cwd) {
894
+ const result = await runClaude(["plugin", "disable", pluginId, "-s", scope], cwd);
895
+ invalidateCache();
896
+ if (result.code !== 0) throw new Error(result.stderr || result.stdout || "Disable failed");
897
+ return result.stdout.trim() || "Plugin disabled.";
898
+ }
899
+ async function updatePlugin(pluginId, scope, cwd) {
900
+ const result = await runClaude(["plugin", "update", pluginId, "-s", scope], cwd);
901
+ invalidateCache();
902
+ if (result.code !== 0) throw new Error(result.stderr || result.stdout || "Update failed");
903
+ return result.stdout.trim() || "Plugin updated.";
904
+ }
905
+ async function listMarketplaces() {
906
+ if (marketplaceCache && Date.now() - marketplaceCache.ts < CACHE_TTL_MS) {
907
+ return marketplaceCache.data;
908
+ }
909
+ const result = await runClaude(["plugin", "marketplace", "list", "--json"]);
910
+ if (result.code !== 0) throw new Error(`Failed to list marketplaces: ${result.stderr}`);
911
+ const data = JSON.parse(result.stdout);
912
+ marketplaceCache = { data, ts: Date.now() };
913
+ return data;
914
+ }
915
+ async function addMarketplace(source) {
916
+ const result = await runClaude(["plugin", "marketplace", "add", source]);
917
+ invalidateCache();
918
+ if (result.code !== 0) throw new Error(result.stderr || result.stdout || "Add marketplace failed");
919
+ return result.stdout.trim() || "Marketplace added.";
920
+ }
921
+ async function removeMarketplace(name) {
922
+ const result = await runClaude(["plugin", "marketplace", "remove", name]);
923
+ invalidateCache();
924
+ if (result.code !== 0) throw new Error(result.stderr || result.stdout || "Remove marketplace failed");
925
+ return result.stdout.trim() || "Marketplace removed.";
926
+ }
927
+ async function updateMarketplaces(name) {
928
+ const args = ["plugin", "marketplace", "update"];
929
+ if (name) args.push(name);
930
+ const result = await runClaude(args);
931
+ invalidateCache();
932
+ if (result.code !== 0) throw new Error(result.stderr || result.stdout || "Update marketplace failed");
933
+ return result.stdout.trim() || "Marketplace(s) updated.";
934
+ }
935
+ async function getPluginDetail(pluginName, marketplaceName) {
936
+ try {
937
+ const marketplacePath = join3(MARKETPLACES_DIR, marketplaceName, ".claude-plugin", "marketplace.json");
938
+ const raw = await readFile3(marketplacePath, "utf-8");
939
+ const catalog = JSON.parse(raw);
940
+ return catalog.plugins.find((p) => p.name === pluginName) || null;
941
+ } catch {
942
+ return null;
943
+ }
944
+ }
945
+
759
946
  // src/output-handler.ts
760
947
  import {
948
+ AttachmentBuilder,
761
949
  EmbedBuilder,
762
950
  ActionRowBuilder,
763
951
  ButtonBuilder,
764
952
  ButtonStyle,
765
953
  StringSelectMenuBuilder
766
954
  } from "discord.js";
955
+ import { existsSync as existsSync4 } from "fs";
767
956
  var expandableStore = /* @__PURE__ */ new Map();
768
957
  var expandCounter = 0;
958
+ var pendingAnswersStore = /* @__PURE__ */ new Map();
959
+ var questionCountStore = /* @__PURE__ */ new Map();
960
+ function setPendingAnswer(sessionId, questionIndex, answer) {
961
+ if (!pendingAnswersStore.has(sessionId)) {
962
+ pendingAnswersStore.set(sessionId, /* @__PURE__ */ new Map());
963
+ }
964
+ pendingAnswersStore.get(sessionId).set(questionIndex, answer);
965
+ }
966
+ function getPendingAnswers(sessionId) {
967
+ return pendingAnswersStore.get(sessionId);
968
+ }
969
+ function clearPendingAnswers(sessionId) {
970
+ pendingAnswersStore.delete(sessionId);
971
+ questionCountStore.delete(sessionId);
972
+ }
973
+ function getQuestionCount(sessionId) {
974
+ return questionCountStore.get(sessionId) || 0;
975
+ }
769
976
  setInterval(() => {
770
977
  const now = Date.now();
771
978
  const TTL = 10 * 60 * 1e3;
@@ -806,6 +1013,20 @@ function makeOptionButtons(sessionId, options) {
806
1013
  }
807
1014
  return rows;
808
1015
  }
1016
+ function makeModeButtons(sessionId, currentMode) {
1017
+ const modes = [
1018
+ { id: "auto", label: "\u26A1 Auto" },
1019
+ { id: "plan", label: "\u{1F4CB} Plan" },
1020
+ { id: "normal", label: "\u{1F6E1}\uFE0F Normal" }
1021
+ ];
1022
+ const row = new ActionRowBuilder();
1023
+ for (const m of modes) {
1024
+ row.addComponents(
1025
+ new ButtonBuilder().setCustomId(`mode:${sessionId}:${m.id}`).setLabel(m.label).setStyle(m.id === currentMode ? ButtonStyle.Primary : ButtonStyle.Secondary).setDisabled(m.id === currentMode)
1026
+ );
1027
+ }
1028
+ return row;
1029
+ }
809
1030
  function makeYesNoButtons(sessionId) {
810
1031
  return new ActionRowBuilder().addComponents(
811
1032
  new ButtonBuilder().setCustomId(`confirm:${sessionId}:yes`).setLabel("Yes").setStyle(ButtonStyle.Success),
@@ -931,6 +1152,20 @@ var MessageStreamer = class {
931
1152
  }
932
1153
  }
933
1154
  };
1155
+ var IMAGE_EXTENSIONS = /* @__PURE__ */ new Set([".png", ".jpg", ".jpeg", ".gif", ".webp", ".svg", ".bmp"]);
1156
+ function extractImagePath(toolName, toolInput) {
1157
+ try {
1158
+ const data = JSON.parse(toolInput);
1159
+ if (toolName === "Write" || toolName === "Read") {
1160
+ const filePath = data.file_path;
1161
+ if (filePath && IMAGE_EXTENSIONS.has(filePath.slice(filePath.lastIndexOf(".")).toLowerCase())) {
1162
+ return filePath;
1163
+ }
1164
+ }
1165
+ } catch {
1166
+ }
1167
+ return null;
1168
+ }
934
1169
  var USER_FACING_TOOLS = /* @__PURE__ */ new Set([
935
1170
  "AskUserQuestion",
936
1171
  "EnterPlanMode",
@@ -957,21 +1192,29 @@ function renderAskUserQuestion(toolInput, sessionId) {
957
1192
  const data = JSON.parse(toolInput);
958
1193
  const questions = data.questions;
959
1194
  if (!questions?.length) return null;
1195
+ const isMulti = questions.length > 1;
1196
+ if (isMulti) {
1197
+ clearPendingAnswers(sessionId);
1198
+ questionCountStore.set(sessionId, questions.length);
1199
+ }
960
1200
  const embeds = [];
961
1201
  const components = [];
962
- for (const q of questions) {
1202
+ const btnPrefix = isMulti ? "pick" : "answer";
1203
+ const selectPrefix = isMulti ? "pick-select" : "answer-select";
1204
+ for (let qi = 0; qi < questions.length; qi++) {
1205
+ const q = questions[qi];
963
1206
  const embed = new EmbedBuilder().setColor(15965202).setTitle(q.header || "Question").setDescription(q.question);
964
1207
  if (q.options?.length) {
965
1208
  if (q.options.length <= 4) {
966
1209
  const row = new ActionRowBuilder();
967
1210
  for (let i = 0; i < q.options.length; i++) {
968
1211
  row.addComponents(
969
- new ButtonBuilder().setCustomId(`answer:${sessionId}:${q.options[i].label}`).setLabel(q.options[i].label.slice(0, 80)).setStyle(i === 0 ? ButtonStyle.Primary : ButtonStyle.Secondary)
1212
+ new ButtonBuilder().setCustomId(`${btnPrefix}:${sessionId}:${qi}:${q.options[i].label}`).setLabel(q.options[i].label.slice(0, 80)).setStyle(i === 0 ? ButtonStyle.Primary : ButtonStyle.Secondary)
970
1213
  );
971
1214
  }
972
1215
  components.push(row);
973
1216
  } else {
974
- const menu = new StringSelectMenuBuilder().setCustomId(`answer-select:${sessionId}`).setPlaceholder("Select an option...");
1217
+ const menu = new StringSelectMenuBuilder().setCustomId(`${selectPrefix}:${sessionId}:${qi}`).setPlaceholder("Select an option...");
975
1218
  for (const opt of q.options) {
976
1219
  menu.addOptions({
977
1220
  label: opt.label.slice(0, 100),
@@ -986,6 +1229,13 @@ function renderAskUserQuestion(toolInput, sessionId) {
986
1229
  }
987
1230
  embeds.push(embed);
988
1231
  }
1232
+ if (isMulti) {
1233
+ components.push(
1234
+ new ActionRowBuilder().addComponents(
1235
+ new ButtonBuilder().setCustomId(`submit-answers:${sessionId}`).setLabel("Submit Answers").setStyle(ButtonStyle.Success)
1236
+ )
1237
+ );
1238
+ }
989
1239
  return { embeds, components };
990
1240
  } catch {
991
1241
  return null;
@@ -1021,11 +1271,12 @@ function renderTaskListEmbed(resultText) {
1021
1271
  }
1022
1272
  return new EmbedBuilder().setColor(10181046).setTitle("\u{1F4CB} Task Board").setDescription(truncate(formatted, 4e3));
1023
1273
  }
1024
- async function handleOutputStream(stream, channel, sessionId, verbose = false) {
1274
+ async function handleOutputStream(stream, channel, sessionId, verbose = false, mode = "auto") {
1025
1275
  const streamer = new MessageStreamer(channel, sessionId);
1026
1276
  let currentToolName = null;
1027
1277
  let currentToolInput = "";
1028
1278
  let lastFinishedToolName = null;
1279
+ let pendingImagePath = null;
1029
1280
  channel.sendTyping().catch(() => {
1030
1281
  });
1031
1282
  const typingInterval = setInterval(() => {
@@ -1087,6 +1338,7 @@ ${displayInput}
1087
1338
  await channel.send({ embeds: [embed], components });
1088
1339
  }
1089
1340
  }
1341
+ pendingImagePath = extractImagePath(currentToolName, currentToolInput);
1090
1342
  lastFinishedToolName = currentToolName;
1091
1343
  currentToolName = null;
1092
1344
  currentToolInput = "";
@@ -1094,6 +1346,17 @@ ${displayInput}
1094
1346
  }
1095
1347
  }
1096
1348
  if (message.type === "user") {
1349
+ if (pendingImagePath && existsSync4(pendingImagePath)) {
1350
+ try {
1351
+ await streamer.finalize();
1352
+ const attachment = new AttachmentBuilder(pendingImagePath);
1353
+ await channel.send({ files: [attachment] });
1354
+ } catch {
1355
+ }
1356
+ pendingImagePath = null;
1357
+ } else {
1358
+ pendingImagePath = null;
1359
+ }
1097
1360
  const showResult = verbose || lastFinishedToolName !== null && TASK_TOOLS.has(lastFinishedToolName);
1098
1361
  if (!showResult) continue;
1099
1362
  await streamer.finalize();
@@ -1151,7 +1414,8 @@ ${displayResult}
1151
1414
  const embed = new EmbedBuilder().setColor(isSuccess ? 3066993 : 15158332).setTitle(isSuccess ? "Completed" : "Error").addFields(
1152
1415
  { name: "Cost", value: `$${cost}`, inline: true },
1153
1416
  { name: "Duration", value: duration, inline: true },
1154
- { name: "Turns", value: `${turns}`, inline: true }
1417
+ { name: "Turns", value: `${turns}`, inline: true },
1418
+ { name: "Mode", value: { auto: "\u26A1 Auto", plan: "\u{1F4CB} Plan", normal: "\u{1F6E1}\uFE0F Normal" }[mode] || "\u26A1 Auto", inline: true }
1155
1419
  );
1156
1420
  if (result.session_id) {
1157
1421
  embed.setFooter({ text: `Session: ${result.session_id}` });
@@ -1167,6 +1431,7 @@ ${displayResult}
1167
1431
  } else if (detectYesNoPrompt(checkText)) {
1168
1432
  components.push(makeYesNoButtons(sessionId));
1169
1433
  }
1434
+ components.push(makeModeButtons(sessionId, mode));
1170
1435
  components.push(makeCompletionButtons(sessionId));
1171
1436
  await channel.send({ embeds: [embed], components });
1172
1437
  }
@@ -1337,13 +1602,23 @@ async function handleClaude(interaction) {
1337
1602
  return handleClaudeModel(interaction);
1338
1603
  case "verbose":
1339
1604
  return handleClaudeVerbose(interaction);
1605
+ case "mode":
1606
+ return handleClaudeMode(interaction);
1340
1607
  default:
1341
1608
  await interaction.reply({ content: `Unknown subcommand: ${sub}`, ephemeral: true });
1342
1609
  }
1343
1610
  }
1344
1611
  async function handleClaudeNew(interaction) {
1345
1612
  const name = interaction.options.getString("name", true);
1346
- const directory = interaction.options.getString("directory") || config.defaultDirectory;
1613
+ let directory = interaction.options.getString("directory");
1614
+ if (!directory) {
1615
+ const parentId = interaction.channel?.parentId;
1616
+ if (parentId) {
1617
+ const project = getProjectByCategoryId(parentId);
1618
+ if (project) directory = project.directory;
1619
+ }
1620
+ directory = directory || config.defaultDirectory;
1621
+ }
1347
1622
  await interaction.deferReply();
1348
1623
  let channel;
1349
1624
  try {
@@ -1385,7 +1660,7 @@ async function handleClaudeNew(interaction) {
1385
1660
  }
1386
1661
  }
1387
1662
  function discoverLocalSessions() {
1388
- const claudeDir = join3(homedir2(), ".claude", "projects");
1663
+ const claudeDir = join4(homedir3(), ".claude", "projects");
1389
1664
  const results = [];
1390
1665
  let projectDirs;
1391
1666
  try {
@@ -1394,7 +1669,7 @@ function discoverLocalSessions() {
1394
1669
  return [];
1395
1670
  }
1396
1671
  for (const projDir of projectDirs) {
1397
- const projPath = join3(claudeDir, projDir);
1672
+ const projPath = join4(claudeDir, projDir);
1398
1673
  let files;
1399
1674
  try {
1400
1675
  files = readdirSync(projPath);
@@ -1408,7 +1683,7 @@ function discoverLocalSessions() {
1408
1683
  const sessionId = file.replace(".jsonl", "");
1409
1684
  if (!/^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i.test(sessionId)) continue;
1410
1685
  try {
1411
- const mtime = statSync(join3(projPath, file)).mtimeMs;
1686
+ const mtime = statSync(join4(projPath, file)).mtimeMs;
1412
1687
  results.push({ id: sessionId, project, mtime, firstMessage: "" });
1413
1688
  } catch {
1414
1689
  continue;
@@ -1419,7 +1694,7 @@ function discoverLocalSessions() {
1419
1694
  return results;
1420
1695
  }
1421
1696
  async function getFirstUserMessage(sessionId) {
1422
- const claudeDir = join3(homedir2(), ".claude", "projects");
1697
+ const claudeDir = join4(homedir3(), ".claude", "projects");
1423
1698
  let projectDirs;
1424
1699
  try {
1425
1700
  projectDirs = readdirSync(claudeDir);
@@ -1427,7 +1702,7 @@ async function getFirstUserMessage(sessionId) {
1427
1702
  return "";
1428
1703
  }
1429
1704
  for (const projDir of projectDirs) {
1430
- const filePath = join3(claudeDir, projDir, `${sessionId}.jsonl`);
1705
+ const filePath = join4(claudeDir, projDir, `${sessionId}.jsonl`);
1431
1706
  try {
1432
1707
  statSync(filePath);
1433
1708
  } catch {
@@ -1560,7 +1835,8 @@ async function handleClaudeList(interaction) {
1560
1835
  for (const [project, projectSessions] of grouped) {
1561
1836
  const lines = projectSessions.map((s) => {
1562
1837
  const status = s.isGenerating ? "\u{1F7E2} generating" : "\u26AA idle";
1563
- return `**${s.id}** \u2014 ${status} | ${formatUptime(s.createdAt)} uptime | ${s.messageCount} msgs | $${s.totalCost.toFixed(4)} | ${formatLastActivity(s.lastActivity)}`;
1838
+ const modeEmoji = { auto: "\u26A1", plan: "\u{1F4CB}", normal: "\u{1F6E1}\uFE0F" }[s.mode] || "\u26A1";
1839
+ return `**${s.id}** \u2014 ${status} ${modeEmoji} ${s.mode} | ${formatUptime(s.createdAt)} uptime | ${s.messageCount} msgs | $${s.totalCost.toFixed(4)} | ${formatLastActivity(s.lastActivity)}`;
1564
1840
  });
1565
1841
  embed.addFields({ name: `\u{1F4C1} ${project}`, value: lines.join("\n") });
1566
1842
  }
@@ -1596,7 +1872,7 @@ async function handleClaudeContinue(interaction) {
1596
1872
  const channel = interaction.channel;
1597
1873
  const stream = continueSession(session.id);
1598
1874
  await interaction.editReply("Continuing...");
1599
- await handleOutputStream(stream, channel, session.id, session.verbose);
1875
+ await handleOutputStream(stream, channel, session.id, session.verbose, session.mode);
1600
1876
  } catch (err) {
1601
1877
  await interaction.editReply(`Error: ${err.message}`);
1602
1878
  }
@@ -1697,6 +1973,24 @@ async function handleClaudeVerbose(interaction) {
1697
1973
  ephemeral: true
1698
1974
  });
1699
1975
  }
1976
+ var MODE_LABELS = {
1977
+ auto: "\u26A1 Auto \u2014 full autonomy, no confirmations",
1978
+ plan: "\u{1F4CB} Plan \u2014 always plans before executing changes",
1979
+ normal: "\u{1F6E1}\uFE0F Normal \u2014 asks before destructive operations"
1980
+ };
1981
+ async function handleClaudeMode(interaction) {
1982
+ const session = getSessionByChannel(interaction.channelId);
1983
+ if (!session) {
1984
+ await interaction.reply({ content: "No session in this channel.", ephemeral: true });
1985
+ return;
1986
+ }
1987
+ const mode = interaction.options.getString("mode", true);
1988
+ setMode(session.id, mode);
1989
+ await interaction.reply({
1990
+ content: `Mode set to **${MODE_LABELS[mode]}**`,
1991
+ ephemeral: true
1992
+ });
1993
+ }
1700
1994
  async function handleShell(interaction) {
1701
1995
  if (!isUserAllowed(interaction.user.id, config.allowedUsers, config.allowAllUsers)) {
1702
1996
  await interaction.reply({ content: "You are not authorized.", ephemeral: true });
@@ -1860,7 +2154,7 @@ ${list}`, ephemeral: true });
1860
2154
  const channel = interaction.channel;
1861
2155
  await interaction.editReply(`Running skill **${name}**...`);
1862
2156
  const stream = sendPrompt(session.id, expanded);
1863
- await handleOutputStream(stream, channel, session.id, session.verbose);
2157
+ await handleOutputStream(stream, channel, session.id, session.verbose, session.mode);
1864
2158
  } catch (err) {
1865
2159
  await interaction.editReply(`Error: ${err.message}`);
1866
2160
  }
@@ -1928,9 +2222,377 @@ ${list}`, ephemeral: true });
1928
2222
  }
1929
2223
  }
1930
2224
  }
2225
+ async function handlePlugin(interaction) {
2226
+ if (!isUserAllowed(interaction.user.id, config.allowedUsers, config.allowAllUsers)) {
2227
+ await interaction.reply({ content: "You are not authorized.", ephemeral: true });
2228
+ return;
2229
+ }
2230
+ const sub = interaction.options.getSubcommand();
2231
+ switch (sub) {
2232
+ case "browse":
2233
+ return handlePluginBrowse(interaction);
2234
+ case "install":
2235
+ return handlePluginInstall(interaction);
2236
+ case "remove":
2237
+ return handlePluginRemove(interaction);
2238
+ case "list":
2239
+ return handlePluginList(interaction);
2240
+ case "info":
2241
+ return handlePluginInfo(interaction);
2242
+ case "enable":
2243
+ return handlePluginEnable(interaction);
2244
+ case "disable":
2245
+ return handlePluginDisable(interaction);
2246
+ case "update":
2247
+ return handlePluginUpdate(interaction);
2248
+ case "marketplace-add":
2249
+ return handleMarketplaceAdd(interaction);
2250
+ case "marketplace-remove":
2251
+ return handleMarketplaceRemove(interaction);
2252
+ case "marketplace-list":
2253
+ return handleMarketplaceList(interaction);
2254
+ case "marketplace-update":
2255
+ return handleMarketplaceUpdate(interaction);
2256
+ default:
2257
+ await interaction.reply({ content: `Unknown subcommand: ${sub}`, ephemeral: true });
2258
+ }
2259
+ }
2260
+ function resolveScopeAndCwd(interaction) {
2261
+ const scope = interaction.options.getString("scope") || "user";
2262
+ if (scope === "user") return { scope };
2263
+ const session = getSessionByChannel(interaction.channelId);
2264
+ if (!session) {
2265
+ return { scope, error: `Scope \`${scope}\` requires an active session. Run this from a session channel, or use \`user\` scope.` };
2266
+ }
2267
+ return { scope, cwd: session.directory };
2268
+ }
2269
+ async function handlePluginBrowse(interaction) {
2270
+ await interaction.deferReply({ ephemeral: true });
2271
+ try {
2272
+ const search = interaction.options.getString("search")?.toLowerCase();
2273
+ const { installed, available } = await listAvailable();
2274
+ const installedIds = new Set(installed.map((p) => p.id));
2275
+ let filtered = available;
2276
+ if (search) {
2277
+ filtered = available.filter((p) => p.name.toLowerCase().includes(search) || p.description.toLowerCase().includes(search) || p.marketplaceName.toLowerCase().includes(search));
2278
+ }
2279
+ filtered.sort((a, b) => (b.installCount ?? 0) - (a.installCount ?? 0));
2280
+ if (filtered.length === 0) {
2281
+ await interaction.editReply("No plugins found matching your search.");
2282
+ return;
2283
+ }
2284
+ const shown = filtered.slice(0, 15);
2285
+ const embed = new EmbedBuilder2().setColor(8141549).setTitle("Available Plugins").setDescription(`Showing ${shown.length} of ${filtered.length} plugins. Use \`/plugin install\` to install.`);
2286
+ for (const p of shown) {
2287
+ const status = installedIds.has(p.pluginId) ? " \u2705" : "";
2288
+ const count = p.installCount ? ` | ${p.installCount.toLocaleString()} installs` : "";
2289
+ embed.addFields({
2290
+ name: `${p.name}${status}`,
2291
+ value: `${truncate(p.description, 150)}
2292
+ *${p.marketplaceName}*${count}`
2293
+ });
2294
+ }
2295
+ await interaction.editReply({ embeds: [embed] });
2296
+ } catch (err) {
2297
+ await interaction.editReply(`Error: ${err.message}`);
2298
+ }
2299
+ }
2300
+ async function handlePluginInstall(interaction) {
2301
+ const pluginId = interaction.options.getString("plugin", true);
2302
+ const { scope, cwd, error } = resolveScopeAndCwd(interaction);
2303
+ if (error) {
2304
+ await interaction.reply({ content: error, ephemeral: true });
2305
+ return;
2306
+ }
2307
+ await interaction.deferReply({ ephemeral: true });
2308
+ try {
2309
+ const result = await installPlugin(pluginId, scope, cwd);
2310
+ const embed = new EmbedBuilder2().setColor(3066993).setTitle("Plugin Installed").setDescription(`**${pluginId}** installed with \`${scope}\` scope.`).addFields({ name: "Output", value: truncate(result, 1e3) || "Done." });
2311
+ await interaction.editReply({ embeds: [embed] });
2312
+ log(`Plugin "${pluginId}" installed (scope=${scope}) by ${interaction.user.tag}`);
2313
+ } catch (err) {
2314
+ await interaction.editReply(`Failed to install: ${err.message}`);
2315
+ }
2316
+ }
2317
+ async function handlePluginRemove(interaction) {
2318
+ const pluginId = interaction.options.getString("plugin", true);
2319
+ const { scope, cwd, error } = resolveScopeAndCwd(interaction);
2320
+ if (error) {
2321
+ await interaction.reply({ content: error, ephemeral: true });
2322
+ return;
2323
+ }
2324
+ await interaction.deferReply({ ephemeral: true });
2325
+ try {
2326
+ const result = await uninstallPlugin(pluginId, scope, cwd);
2327
+ await interaction.editReply(`Plugin **${pluginId}** removed.
2328
+ ${truncate(result, 500)}`);
2329
+ log(`Plugin "${pluginId}" removed (scope=${scope}) by ${interaction.user.tag}`);
2330
+ } catch (err) {
2331
+ await interaction.editReply(`Failed to remove: ${err.message}`);
2332
+ }
2333
+ }
2334
+ async function handlePluginList(interaction) {
2335
+ await interaction.deferReply({ ephemeral: true });
2336
+ try {
2337
+ const plugins = await listInstalled();
2338
+ if (plugins.length === 0) {
2339
+ await interaction.editReply("No plugins installed.");
2340
+ return;
2341
+ }
2342
+ const embed = new EmbedBuilder2().setColor(3447003).setTitle(`Installed Plugins (${plugins.length})`);
2343
+ for (const p of plugins) {
2344
+ const icon = p.enabled ? "\u2705" : "\u274C";
2345
+ const scopeLabel = p.scope.charAt(0).toUpperCase() + p.scope.slice(1);
2346
+ const project = p.projectPath ? `
2347
+ Project: \`${p.projectPath}\`` : "";
2348
+ embed.addFields({
2349
+ name: `${icon} ${p.id}`,
2350
+ value: `v${p.version} | ${scopeLabel} scope${project}`
2351
+ });
2352
+ }
2353
+ await interaction.editReply({ embeds: [embed] });
2354
+ } catch (err) {
2355
+ await interaction.editReply(`Error: ${err.message}`);
2356
+ }
2357
+ }
2358
+ async function handlePluginInfo(interaction) {
2359
+ const pluginId = interaction.options.getString("plugin", true);
2360
+ await interaction.deferReply({ ephemeral: true });
2361
+ try {
2362
+ const parts = pluginId.split("@");
2363
+ const pluginName = parts[0];
2364
+ const marketplaceName = parts[1];
2365
+ const installed = await listInstalled();
2366
+ const installedEntry = installed.find((p) => p.id === pluginId);
2367
+ let detail = null;
2368
+ if (marketplaceName) {
2369
+ detail = await getPluginDetail(pluginName, marketplaceName);
2370
+ }
2371
+ const embed = new EmbedBuilder2().setColor(15965202).setTitle(`Plugin: ${pluginName}`);
2372
+ if (detail) {
2373
+ embed.setDescription(detail.description);
2374
+ if (detail.author) {
2375
+ embed.addFields({ name: "Author", value: detail.author.name, inline: true });
2376
+ }
2377
+ if (detail.category) {
2378
+ embed.addFields({ name: "Category", value: detail.category, inline: true });
2379
+ }
2380
+ if (detail.version) {
2381
+ embed.addFields({ name: "Version", value: detail.version, inline: true });
2382
+ }
2383
+ if (detail.tags?.length) {
2384
+ embed.addFields({ name: "Tags", value: detail.tags.join(", "), inline: false });
2385
+ }
2386
+ if (detail.homepage) {
2387
+ embed.addFields({ name: "Homepage", value: detail.homepage, inline: false });
2388
+ }
2389
+ if (detail.lspServers) {
2390
+ embed.addFields({ name: "LSP Servers", value: Object.keys(detail.lspServers).join(", "), inline: true });
2391
+ }
2392
+ if (detail.mcpServers) {
2393
+ embed.addFields({ name: "MCP Servers", value: Object.keys(detail.mcpServers).join(", "), inline: true });
2394
+ }
2395
+ }
2396
+ if (installedEntry) {
2397
+ const icon = installedEntry.enabled ? "\u2705 Enabled" : "\u274C Disabled";
2398
+ embed.addFields(
2399
+ { name: "Status", value: `${icon} | v${installedEntry.version}`, inline: true },
2400
+ { name: "Scope", value: installedEntry.scope, inline: true },
2401
+ { name: "Installed", value: new Date(installedEntry.installedAt).toLocaleDateString(), inline: true }
2402
+ );
2403
+ } else {
2404
+ embed.addFields({ name: "Status", value: "Not installed", inline: true });
2405
+ }
2406
+ if (marketplaceName) {
2407
+ embed.setFooter({ text: `Marketplace: ${marketplaceName}` });
2408
+ }
2409
+ await interaction.editReply({ embeds: [embed] });
2410
+ } catch (err) {
2411
+ await interaction.editReply(`Error: ${err.message}`);
2412
+ }
2413
+ }
2414
+ async function handlePluginEnable(interaction) {
2415
+ const pluginId = interaction.options.getString("plugin", true);
2416
+ const { scope, cwd, error } = resolveScopeAndCwd(interaction);
2417
+ if (error) {
2418
+ await interaction.reply({ content: error, ephemeral: true });
2419
+ return;
2420
+ }
2421
+ await interaction.deferReply({ ephemeral: true });
2422
+ try {
2423
+ await enablePlugin(pluginId, scope, cwd);
2424
+ await interaction.editReply(`Plugin **${pluginId}** enabled (\`${scope}\` scope).`);
2425
+ log(`Plugin "${pluginId}" enabled (scope=${scope}) by ${interaction.user.tag}`);
2426
+ } catch (err) {
2427
+ await interaction.editReply(`Failed to enable: ${err.message}`);
2428
+ }
2429
+ }
2430
+ async function handlePluginDisable(interaction) {
2431
+ const pluginId = interaction.options.getString("plugin", true);
2432
+ const { scope, cwd, error } = resolveScopeAndCwd(interaction);
2433
+ if (error) {
2434
+ await interaction.reply({ content: error, ephemeral: true });
2435
+ return;
2436
+ }
2437
+ await interaction.deferReply({ ephemeral: true });
2438
+ try {
2439
+ await disablePlugin(pluginId, scope, cwd);
2440
+ await interaction.editReply(`Plugin **${pluginId}** disabled (\`${scope}\` scope).`);
2441
+ log(`Plugin "${pluginId}" disabled (scope=${scope}) by ${interaction.user.tag}`);
2442
+ } catch (err) {
2443
+ await interaction.editReply(`Failed to disable: ${err.message}`);
2444
+ }
2445
+ }
2446
+ async function handlePluginUpdate(interaction) {
2447
+ const pluginId = interaction.options.getString("plugin", true);
2448
+ const { scope, cwd, error } = resolveScopeAndCwd(interaction);
2449
+ if (error) {
2450
+ await interaction.reply({ content: error, ephemeral: true });
2451
+ return;
2452
+ }
2453
+ await interaction.deferReply({ ephemeral: true });
2454
+ try {
2455
+ const result = await updatePlugin(pluginId, scope, cwd);
2456
+ await interaction.editReply(`Plugin **${pluginId}** updated.
2457
+ ${truncate(result, 500)}`);
2458
+ log(`Plugin "${pluginId}" updated (scope=${scope}) by ${interaction.user.tag}`);
2459
+ } catch (err) {
2460
+ await interaction.editReply(`Failed to update: ${err.message}`);
2461
+ }
2462
+ }
2463
+ async function handleMarketplaceAdd(interaction) {
2464
+ const source = interaction.options.getString("source", true);
2465
+ await interaction.deferReply({ ephemeral: true });
2466
+ try {
2467
+ const result = await addMarketplace(source);
2468
+ await interaction.editReply(`Marketplace added from \`${source}\`.
2469
+ ${truncate(result, 500)}`);
2470
+ log(`Marketplace "${source}" added by ${interaction.user.tag}`);
2471
+ } catch (err) {
2472
+ await interaction.editReply(`Failed to add marketplace: ${err.message}`);
2473
+ }
2474
+ }
2475
+ async function handleMarketplaceRemove(interaction) {
2476
+ const name = interaction.options.getString("name", true);
2477
+ await interaction.deferReply({ ephemeral: true });
2478
+ try {
2479
+ const result = await removeMarketplace(name);
2480
+ await interaction.editReply(`Marketplace **${name}** removed.
2481
+ ${truncate(result, 500)}`);
2482
+ log(`Marketplace "${name}" removed by ${interaction.user.tag}`);
2483
+ } catch (err) {
2484
+ await interaction.editReply(`Failed to remove marketplace: ${err.message}`);
2485
+ }
2486
+ }
2487
+ async function handleMarketplaceList(interaction) {
2488
+ await interaction.deferReply({ ephemeral: true });
2489
+ try {
2490
+ const marketplaces = await listMarketplaces();
2491
+ if (marketplaces.length === 0) {
2492
+ await interaction.editReply("No marketplaces registered.");
2493
+ return;
2494
+ }
2495
+ const embed = new EmbedBuilder2().setColor(10181046).setTitle(`Marketplaces (${marketplaces.length})`);
2496
+ for (const m of marketplaces) {
2497
+ const source = m.repo || m.url || m.source;
2498
+ embed.addFields({
2499
+ name: m.name,
2500
+ value: `Source: \`${source}\`
2501
+ Path: \`${m.installLocation}\``
2502
+ });
2503
+ }
2504
+ await interaction.editReply({ embeds: [embed] });
2505
+ } catch (err) {
2506
+ await interaction.editReply(`Error: ${err.message}`);
2507
+ }
2508
+ }
2509
+ async function handleMarketplaceUpdate(interaction) {
2510
+ const name = interaction.options.getString("name") || void 0;
2511
+ await interaction.deferReply({ ephemeral: true });
2512
+ try {
2513
+ const result = await updateMarketplaces(name);
2514
+ await interaction.editReply(`Marketplace${name ? ` **${name}**` : "s"} updated.
2515
+ ${truncate(result, 500)}`);
2516
+ log(`Marketplace${name ? ` "${name}"` : "s"} updated by ${interaction.user.tag}`);
2517
+ } catch (err) {
2518
+ await interaction.editReply(`Failed to update: ${err.message}`);
2519
+ }
2520
+ }
2521
+ async function handlePluginAutocomplete(interaction) {
2522
+ const sub = interaction.options.getSubcommand();
2523
+ const focused = interaction.options.getFocused().toLowerCase();
2524
+ try {
2525
+ if (sub === "install" || sub === "info" && !focused.includes("@")) {
2526
+ const { available } = await listAvailable();
2527
+ const filtered = focused ? available.filter((p) => p.name.toLowerCase().includes(focused) || p.pluginId.toLowerCase().includes(focused) || p.description.toLowerCase().includes(focused)) : available;
2528
+ filtered.sort((a, b) => (b.installCount ?? 0) - (a.installCount ?? 0));
2529
+ const choices = filtered.slice(0, 25).map((p) => ({
2530
+ name: `${p.name} (${p.marketplaceName})`.slice(0, 100),
2531
+ value: p.pluginId
2532
+ }));
2533
+ await interaction.respond(choices);
2534
+ } else if (["remove", "enable", "disable", "update", "info"].includes(sub)) {
2535
+ const installed = await listInstalled();
2536
+ const filtered = focused ? installed.filter((p) => p.id.toLowerCase().includes(focused)) : installed;
2537
+ const choices = filtered.slice(0, 25).map((p) => ({
2538
+ name: `${p.id} (v${p.version}, ${p.scope})`.slice(0, 100),
2539
+ value: p.id
2540
+ }));
2541
+ await interaction.respond(choices);
2542
+ } else if (sub === "marketplace-remove" || sub === "marketplace-update") {
2543
+ const marketplaces = await listMarketplaces();
2544
+ const filtered = focused ? marketplaces.filter((m) => m.name.toLowerCase().includes(focused)) : marketplaces;
2545
+ const choices = filtered.slice(0, 25).map((m) => ({
2546
+ name: m.name,
2547
+ value: m.name
2548
+ }));
2549
+ await interaction.respond(choices);
2550
+ } else {
2551
+ await interaction.respond([]);
2552
+ }
2553
+ } catch {
2554
+ await interaction.respond([]);
2555
+ }
2556
+ }
1931
2557
 
1932
2558
  // src/message-handler.ts
2559
+ import sharp from "sharp";
2560
+ var SUPPORTED_IMAGE_TYPES = /* @__PURE__ */ new Set([
2561
+ "image/jpeg",
2562
+ "image/png",
2563
+ "image/gif",
2564
+ "image/webp"
2565
+ ]);
2566
+ var MAX_IMAGE_SIZE = 20 * 1024 * 1024;
2567
+ var MAX_BASE64_BYTES = 5 * 1024 * 1024;
1933
2568
  var userLastMessage = /* @__PURE__ */ new Map();
2569
+ async function resizeImageToFit(buf, mediaType) {
2570
+ if (buf.length <= MAX_BASE64_BYTES) return buf;
2571
+ const isJpeg = mediaType === "image/jpeg";
2572
+ const format = isJpeg ? "jpeg" : "webp";
2573
+ let img = sharp(buf);
2574
+ const meta = await img.metadata();
2575
+ const width = meta.width || 1;
2576
+ const height = meta.height || 1;
2577
+ let scale = 1;
2578
+ for (let i = 0; i < 5; i++) {
2579
+ scale *= 0.7;
2580
+ const resized = await sharp(buf).resize(Math.round(width * scale), Math.round(height * scale), { fit: "inside" })[format]({ quality: 80 }).toBuffer();
2581
+ if (resized.length <= MAX_BASE64_BYTES) return resized;
2582
+ }
2583
+ return sharp(buf).resize(Math.round(width * scale * 0.5), Math.round(height * scale * 0.5), { fit: "inside" }).jpeg({ quality: 60 }).toBuffer();
2584
+ }
2585
+ async function fetchImageAsBase64(url, mediaType) {
2586
+ const res = await fetch(url);
2587
+ if (!res.ok) throw new Error(`Failed to download image: ${res.status}`);
2588
+ let buf = Buffer.from(await res.arrayBuffer());
2589
+ if (buf.length > MAX_BASE64_BYTES) {
2590
+ buf = await resizeImageToFit(buf, mediaType);
2591
+ const newType = mediaType === "image/jpeg" ? "image/jpeg" : "image/webp";
2592
+ return { data: buf.toString("base64"), mediaType: newType };
2593
+ }
2594
+ return { data: buf.toString("base64"), mediaType };
2595
+ }
1934
2596
  async function handleMessage(message) {
1935
2597
  if (message.author.bot) return;
1936
2598
  const session = getSessionByChannel(message.channelId);
@@ -1959,12 +2621,42 @@ async function handleMessage(message) {
1959
2621
  return;
1960
2622
  }
1961
2623
  }
1962
- const content = message.content.trim();
1963
- if (!content) return;
2624
+ const text = message.content.trim();
2625
+ const imageAttachments = message.attachments.filter(
2626
+ (a) => a.contentType && SUPPORTED_IMAGE_TYPES.has(a.contentType) && a.size <= MAX_IMAGE_SIZE
2627
+ );
2628
+ if (!text && imageAttachments.size === 0) return;
1964
2629
  try {
1965
2630
  const channel = message.channel;
1966
- const stream = sendPrompt(session.id, content);
1967
- await handleOutputStream(stream, channel, session.id, session.verbose);
2631
+ let prompt;
2632
+ if (imageAttachments.size === 0) {
2633
+ prompt = text;
2634
+ } else {
2635
+ const blocks = [];
2636
+ const imageResults = await Promise.allSettled(
2637
+ imageAttachments.map((a) => fetchImageAsBase64(a.url, a.contentType))
2638
+ );
2639
+ for (const result of imageResults) {
2640
+ if (result.status === "fulfilled") {
2641
+ blocks.push({
2642
+ type: "image",
2643
+ source: {
2644
+ type: "base64",
2645
+ media_type: result.value.mediaType,
2646
+ data: result.value.data
2647
+ }
2648
+ });
2649
+ }
2650
+ }
2651
+ if (text) {
2652
+ blocks.push({ type: "text", text });
2653
+ } else if (blocks.length > 0) {
2654
+ blocks.push({ type: "text", text: "What is in this image?" });
2655
+ }
2656
+ prompt = blocks;
2657
+ }
2658
+ const stream = sendPrompt(session.id, prompt);
2659
+ await handleOutputStream(stream, channel, session.id, session.verbose, session.mode);
1968
2660
  } catch (err) {
1969
2661
  await message.reply({
1970
2662
  content: `Error: ${err.message}`,
@@ -1974,6 +2666,12 @@ async function handleMessage(message) {
1974
2666
  }
1975
2667
 
1976
2668
  // src/button-handler.ts
2669
+ import {
2670
+ ActionRowBuilder as ActionRowBuilder2,
2671
+ ButtonBuilder as ButtonBuilder2,
2672
+ ButtonStyle as ButtonStyle2,
2673
+ StringSelectMenuBuilder as StringSelectMenuBuilder2
2674
+ } from "discord.js";
1977
2675
  async function handleButton(interaction) {
1978
2676
  if (!isUserAllowed(interaction.user.id, config.allowedUsers, config.allowAllUsers)) {
1979
2677
  await interaction.reply({ content: "Not authorized.", ephemeral: true });
@@ -2005,7 +2703,7 @@ async function handleButton(interaction) {
2005
2703
  const channel = interaction.channel;
2006
2704
  const stream = continueSession(sessionId);
2007
2705
  await interaction.editReply("Continuing...");
2008
- await handleOutputStream(stream, channel, sessionId, session.verbose);
2706
+ await handleOutputStream(stream, channel, sessionId, session.verbose, session.mode);
2009
2707
  } catch (err) {
2010
2708
  await interaction.editReply(`Error: ${err.message}`);
2011
2709
  }
@@ -2039,7 +2737,79 @@ ${display}
2039
2737
  const channel = interaction.channel;
2040
2738
  const stream = sendPrompt(sessionId, optionText);
2041
2739
  await interaction.editReply(`Selected option ${optionIndex + 1}`);
2042
- await handleOutputStream(stream, channel, sessionId, session.verbose);
2740
+ await handleOutputStream(stream, channel, sessionId, session.verbose, session.mode);
2741
+ } catch (err) {
2742
+ await interaction.editReply(`Error: ${err.message}`);
2743
+ }
2744
+ return;
2745
+ }
2746
+ if (customId.startsWith("pick:")) {
2747
+ const parts = customId.split(":");
2748
+ const sessionId = parts[1];
2749
+ const questionIndex = parseInt(parts[2], 10);
2750
+ const answer = parts.slice(3).join(":");
2751
+ const session = getSession(sessionId);
2752
+ if (!session) {
2753
+ await interaction.reply({ content: "Session not found.", ephemeral: true });
2754
+ return;
2755
+ }
2756
+ setPendingAnswer(sessionId, questionIndex, answer);
2757
+ const totalQuestions = getQuestionCount(sessionId);
2758
+ const pending = getPendingAnswers(sessionId);
2759
+ const answeredCount = pending?.size || 0;
2760
+ try {
2761
+ const original = interaction.message;
2762
+ const updatedComponents = original.components.map((row) => {
2763
+ const firstComponent = row.components?.[0];
2764
+ if (!firstComponent?.customId?.startsWith("pick:")) return row;
2765
+ const rowQi = parseInt(firstComponent.customId.split(":")[2], 10);
2766
+ if (rowQi !== questionIndex) return row;
2767
+ const newRow = new ActionRowBuilder2();
2768
+ for (const btn of row.components) {
2769
+ const btnAnswer = btn.customId.split(":").slice(3).join(":");
2770
+ const isSelected = btnAnswer === answer;
2771
+ newRow.addComponents(
2772
+ new ButtonBuilder2().setCustomId(btn.customId).setLabel(btn.label).setStyle(isSelected ? ButtonStyle2.Success : ButtonStyle2.Secondary)
2773
+ );
2774
+ }
2775
+ return newRow;
2776
+ });
2777
+ await original.edit({ components: updatedComponents });
2778
+ } catch {
2779
+ }
2780
+ await interaction.reply({
2781
+ content: `Selected for Q${questionIndex + 1}: **${truncate(answer, 100)}** (${answeredCount}/${totalQuestions} answered)`,
2782
+ ephemeral: true
2783
+ });
2784
+ return;
2785
+ }
2786
+ if (customId.startsWith("submit-answers:")) {
2787
+ const sessionId = customId.slice(15);
2788
+ const session = getSession(sessionId);
2789
+ if (!session) {
2790
+ await interaction.reply({ content: "Session not found.", ephemeral: true });
2791
+ return;
2792
+ }
2793
+ const totalQuestions = getQuestionCount(sessionId);
2794
+ const pending = getPendingAnswers(sessionId);
2795
+ if (!pending || pending.size === 0) {
2796
+ await interaction.reply({ content: "No answers selected yet. Pick an answer for each question first.", ephemeral: true });
2797
+ return;
2798
+ }
2799
+ const answerLines = [];
2800
+ for (let i = 0; i < totalQuestions; i++) {
2801
+ const ans = pending.get(i);
2802
+ answerLines.push(`Q${i + 1}: ${ans || "(no answer)"}`);
2803
+ }
2804
+ const combined = answerLines.join("\n");
2805
+ clearPendingAnswers(sessionId);
2806
+ await interaction.deferReply();
2807
+ try {
2808
+ const channel = interaction.channel;
2809
+ const stream = sendPrompt(sessionId, combined);
2810
+ await interaction.editReply(`Submitted answers:
2811
+ ${combined}`);
2812
+ await handleOutputStream(stream, channel, sessionId, session.verbose, session.mode);
2043
2813
  } catch (err) {
2044
2814
  await interaction.editReply(`Error: ${err.message}`);
2045
2815
  }
@@ -2048,7 +2818,8 @@ ${display}
2048
2818
  if (customId.startsWith("answer:")) {
2049
2819
  const parts = customId.split(":");
2050
2820
  const sessionId = parts[1];
2051
- const answer = parts.slice(2).join(":");
2821
+ const hasQuestionIndex = /^\d+$/.test(parts[2]);
2822
+ const answer = hasQuestionIndex ? parts.slice(3).join(":") : parts.slice(2).join(":");
2052
2823
  const session = getSession(sessionId);
2053
2824
  if (!session) {
2054
2825
  await interaction.reply({ content: "Session not found.", ephemeral: true });
@@ -2059,7 +2830,7 @@ ${display}
2059
2830
  const channel = interaction.channel;
2060
2831
  const stream = sendPrompt(sessionId, answer);
2061
2832
  await interaction.editReply(`Answered: **${truncate(answer, 100)}**`);
2062
- await handleOutputStream(stream, channel, sessionId, session.verbose);
2833
+ await handleOutputStream(stream, channel, sessionId, session.verbose, session.mode);
2063
2834
  } catch (err) {
2064
2835
  await interaction.editReply(`Error: ${err.message}`);
2065
2836
  }
@@ -2079,12 +2850,45 @@ ${display}
2079
2850
  const channel = interaction.channel;
2080
2851
  const stream = sendPrompt(sessionId, answer);
2081
2852
  await interaction.editReply(`Answered: ${answer}`);
2082
- await handleOutputStream(stream, channel, sessionId, session.verbose);
2853
+ await handleOutputStream(stream, channel, sessionId, session.verbose, session.mode);
2083
2854
  } catch (err) {
2084
2855
  await interaction.editReply(`Error: ${err.message}`);
2085
2856
  }
2086
2857
  return;
2087
2858
  }
2859
+ if (customId.startsWith("mode:")) {
2860
+ const parts = customId.split(":");
2861
+ const sessionId = parts[1];
2862
+ const newMode = parts[2];
2863
+ const session = getSession(sessionId);
2864
+ if (!session) {
2865
+ await interaction.reply({ content: "Session not found.", ephemeral: true });
2866
+ return;
2867
+ }
2868
+ setMode(sessionId, newMode);
2869
+ const labels = {
2870
+ auto: "\u26A1 Auto \u2014 full autonomy",
2871
+ plan: "\u{1F4CB} Plan \u2014 plans before changes",
2872
+ normal: "\u{1F6E1}\uFE0F Normal \u2014 asks before destructive ops"
2873
+ };
2874
+ await interaction.reply({
2875
+ content: `Mode switched to **${labels[newMode]}**`,
2876
+ ephemeral: true
2877
+ });
2878
+ try {
2879
+ const original = interaction.message;
2880
+ const updatedComponents = original.components.map((row) => {
2881
+ const first = row.components?.[0];
2882
+ if (first?.customId?.startsWith("mode:")) {
2883
+ return makeModeButtons(sessionId, newMode);
2884
+ }
2885
+ return row;
2886
+ });
2887
+ await original.edit({ components: updatedComponents });
2888
+ } catch {
2889
+ }
2890
+ return;
2891
+ }
2088
2892
  await interaction.reply({ content: "Unknown button.", ephemeral: true });
2089
2893
  }
2090
2894
  async function handleSelectMenu(interaction) {
@@ -2093,8 +2897,48 @@ async function handleSelectMenu(interaction) {
2093
2897
  return;
2094
2898
  }
2095
2899
  const customId = interaction.customId;
2900
+ if (customId.startsWith("pick-select:")) {
2901
+ const parts = customId.split(":");
2902
+ const sessionId = parts[1];
2903
+ const questionIndex = parseInt(parts[2], 10);
2904
+ const selected = interaction.values[0];
2905
+ const session = getSession(sessionId);
2906
+ if (!session) {
2907
+ await interaction.reply({ content: "Session not found.", ephemeral: true });
2908
+ return;
2909
+ }
2910
+ setPendingAnswer(sessionId, questionIndex, selected);
2911
+ const totalQuestions = getQuestionCount(sessionId);
2912
+ const pending = getPendingAnswers(sessionId);
2913
+ const answeredCount = pending?.size || 0;
2914
+ try {
2915
+ const original = interaction.message;
2916
+ const updatedComponents = original.components.map((row) => {
2917
+ const comp = row.components?.[0];
2918
+ if (comp?.customId !== customId) return row;
2919
+ const menu = new StringSelectMenuBuilder2().setCustomId(customId).setPlaceholder(`Selected: ${selected.slice(0, 80)}`);
2920
+ for (const opt of comp.options) {
2921
+ menu.addOptions({
2922
+ label: opt.label,
2923
+ description: opt.description || void 0,
2924
+ value: opt.value,
2925
+ default: opt.value === selected
2926
+ });
2927
+ }
2928
+ return new ActionRowBuilder2().addComponents(menu);
2929
+ });
2930
+ await original.edit({ components: updatedComponents });
2931
+ } catch {
2932
+ }
2933
+ await interaction.reply({
2934
+ content: `Selected for Q${questionIndex + 1}: **${truncate(selected, 100)}** (${answeredCount}/${totalQuestions} answered)`,
2935
+ ephemeral: true
2936
+ });
2937
+ return;
2938
+ }
2096
2939
  if (customId.startsWith("answer-select:")) {
2097
- const sessionId = customId.slice(14);
2940
+ const afterPrefix = customId.slice(14);
2941
+ const sessionId = afterPrefix.includes(":") ? afterPrefix.split(":")[0] : afterPrefix;
2098
2942
  const selected = interaction.values[0];
2099
2943
  const session = getSession(sessionId);
2100
2944
  if (!session) {
@@ -2106,7 +2950,7 @@ async function handleSelectMenu(interaction) {
2106
2950
  const channel = interaction.channel;
2107
2951
  const stream = sendPrompt(sessionId, selected);
2108
2952
  await interaction.editReply(`Answered: **${truncate(selected, 100)}**`);
2109
- await handleOutputStream(stream, channel, sessionId, session.verbose);
2953
+ await handleOutputStream(stream, channel, sessionId, session.verbose, session.mode);
2110
2954
  } catch (err) {
2111
2955
  await interaction.editReply(`Error: ${err.message}`);
2112
2956
  }
@@ -2125,7 +2969,7 @@ async function handleSelectMenu(interaction) {
2125
2969
  const channel = interaction.channel;
2126
2970
  const stream = sendPrompt(sessionId, selected);
2127
2971
  await interaction.editReply(`Selected: ${truncate(selected, 100)}`);
2128
- await handleOutputStream(stream, channel, sessionId, session.verbose);
2972
+ await handleOutputStream(stream, channel, sessionId, session.verbose, session.mode);
2129
2973
  } catch (err) {
2130
2974
  await interaction.editReply(`Error: ${err.message}`);
2131
2975
  }
@@ -2210,12 +3054,17 @@ async function startBot() {
2210
3054
  return await handleAgent(interaction);
2211
3055
  case "project":
2212
3056
  return await handleProject(interaction);
3057
+ case "plugin":
3058
+ return await handlePlugin(interaction);
2213
3059
  }
2214
3060
  }
2215
3061
  if (interaction.isAutocomplete()) {
2216
3062
  if (interaction.commandName === "claude") {
2217
3063
  return await handleClaudeAutocomplete(interaction);
2218
3064
  }
3065
+ if (interaction.commandName === "plugin") {
3066
+ return await handlePluginAutocomplete(interaction);
3067
+ }
2219
3068
  }
2220
3069
  if (interaction.isButton()) {
2221
3070
  return await handleButton(interaction);
package/dist/cli.js CHANGED
@@ -18,7 +18,7 @@ switch (command) {
18
18
  console.log("Run \x1B[36magentcord setup\x1B[0m to configure.\n");
19
19
  process.exit(1);
20
20
  }
21
- const { startBot } = await import("./bot-RT4CJNKG.js");
21
+ const { startBot } = await import("./bot-MUOL7CRV.js");
22
22
  console.log("agentcord starting...");
23
23
  await startBot();
24
24
  break;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "agentcord",
3
- "version": "0.1.6",
3
+ "version": "0.1.8",
4
4
  "type": "module",
5
5
  "description": "Discord bot for managing AI coding agent sessions (Claude Code, Codex, and more)",
6
6
  "bin": {
@@ -50,7 +50,8 @@
50
50
  "@anthropic-ai/claude-agent-sdk": "^0.2.36",
51
51
  "@clack/prompts": "^1.0.0",
52
52
  "discord.js": "^14.16.3",
53
- "dotenv": "^17.2.4"
53
+ "dotenv": "^17.2.4",
54
+ "sharp": "^0.34.5"
54
55
  },
55
56
  "devDependencies": {
56
57
  "@types/node": "^22.10.0",