@openacp/cli 0.5.3 → 0.6.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.
Files changed (90) hide show
  1. package/README.md +40 -14
  2. package/dist/action-detect-6M5GCGAU.js +15 -0
  3. package/dist/admin-IKPS5PFC.js +16 -0
  4. package/dist/agents-55NX3DHM.js +14 -0
  5. package/dist/{api-client-UN7BXQOQ.js → api-client-BH2JFHQW.js} +4 -2
  6. package/dist/{autostart-K73RQZVV.js → autostart-A7JRU4WJ.js} +6 -2
  7. package/dist/chunk-3WPG7GXA.js +134 -0
  8. package/dist/chunk-3WPG7GXA.js.map +1 -0
  9. package/dist/{chunk-6DAZSKE5.js → chunk-437NLISU.js} +2 -2
  10. package/dist/chunk-5NBWM7P6.js +438 -0
  11. package/dist/chunk-5NBWM7P6.js.map +1 -0
  12. package/dist/{chunk-IQIPQTQT.js → chunk-6Q7PZWCL.js} +171 -26
  13. package/dist/chunk-6Q7PZWCL.js.map +1 -0
  14. package/dist/chunk-7G5QKLLF.js +105 -0
  15. package/dist/chunk-7G5QKLLF.js.map +1 -0
  16. package/dist/chunk-AKIU4JBF.js +145 -0
  17. package/dist/chunk-AKIU4JBF.js.map +1 -0
  18. package/dist/{chunk-2Z2XPUD5.js → chunk-DWQKUECJ.js} +12 -2
  19. package/dist/chunk-DWQKUECJ.js.map +1 -0
  20. package/dist/{chunk-KSIQZC3J.js → chunk-EVFJW45N.js} +1 -1
  21. package/dist/chunk-EVFJW45N.js.map +1 -0
  22. package/dist/chunk-H7ZMPBZC.js +203 -0
  23. package/dist/chunk-H7ZMPBZC.js.map +1 -0
  24. package/dist/chunk-I7WC6E5S.js +71 -0
  25. package/dist/chunk-I7WC6E5S.js.map +1 -0
  26. package/dist/{chunk-ECBD5I5R.js → chunk-MHFCZGRW.js} +112 -15
  27. package/dist/chunk-MHFCZGRW.js.map +1 -0
  28. package/dist/{chunk-X6LLG7XN.js → chunk-PMGNLNSH.js} +15 -6
  29. package/dist/chunk-PMGNLNSH.js.map +1 -0
  30. package/dist/chunk-SM3G6UAX.js +122 -0
  31. package/dist/chunk-SM3G6UAX.js.map +1 -0
  32. package/dist/{chunk-K53OZH5Y.js → chunk-SPX7CKWV.js} +76 -2
  33. package/dist/chunk-SPX7CKWV.js.map +1 -0
  34. package/dist/chunk-T22OLSET.js +265 -0
  35. package/dist/chunk-T22OLSET.js.map +1 -0
  36. package/dist/chunk-THBR6OXH.js +62 -0
  37. package/dist/chunk-THBR6OXH.js.map +1 -0
  38. package/dist/{chunk-LCJIPE5S.js → chunk-V2V767XI.js} +58 -440
  39. package/dist/chunk-V2V767XI.js.map +1 -0
  40. package/dist/{chunk-OORPX73T.js → chunk-W3EYKZNQ.js} +17 -2
  41. package/dist/chunk-W3EYKZNQ.js.map +1 -0
  42. package/dist/{chunk-5KYLXEG3.js → chunk-YYQXWA62.js} +52 -9
  43. package/dist/chunk-YYQXWA62.js.map +1 -0
  44. package/dist/cli.js +30 -29
  45. package/dist/cli.js.map +1 -1
  46. package/dist/{config-OH26EIWN.js → config-KF2MQWAP.js} +2 -2
  47. package/dist/config-editor-OTODXUF7.js +12 -0
  48. package/dist/{daemon-VKCONJUY.js → daemon-U6UC7OM4.js} +3 -3
  49. package/dist/discord-SLLKRUP7.js +2034 -0
  50. package/dist/discord-SLLKRUP7.js.map +1 -0
  51. package/dist/doctor-DB5PRQ6D.js +14 -0
  52. package/dist/doctor-DB5PRQ6D.js.map +1 -0
  53. package/dist/doctor-SYWNJFYK.js +9 -0
  54. package/dist/doctor-SYWNJFYK.js.map +1 -0
  55. package/dist/index.d.ts +12 -4
  56. package/dist/index.js +11 -9
  57. package/dist/{main-NEYPQHB4.js → main-M6RH3SS5.js} +30 -22
  58. package/dist/main-M6RH3SS5.js.map +1 -0
  59. package/dist/new-session-DRRP2J7E.js +16 -0
  60. package/dist/new-session-DRRP2J7E.js.map +1 -0
  61. package/dist/session-FVFLBREJ.js +19 -0
  62. package/dist/session-FVFLBREJ.js.map +1 -0
  63. package/dist/settings-LPOLJ6SA.js +12 -0
  64. package/dist/settings-LPOLJ6SA.js.map +1 -0
  65. package/dist/{setup-ZCWGOEAH.js → setup-LI5CKYDK.js} +9 -5
  66. package/dist/setup-LI5CKYDK.js.map +1 -0
  67. package/dist/{version-VC5CPXBX.js → version-ALWGGVKM.js} +2 -2
  68. package/dist/version-ALWGGVKM.js.map +1 -0
  69. package/package.json +2 -1
  70. package/dist/chunk-2Z2XPUD5.js.map +0 -1
  71. package/dist/chunk-5KYLXEG3.js.map +0 -1
  72. package/dist/chunk-ECBD5I5R.js.map +0 -1
  73. package/dist/chunk-IQIPQTQT.js.map +0 -1
  74. package/dist/chunk-K53OZH5Y.js.map +0 -1
  75. package/dist/chunk-KSIQZC3J.js.map +0 -1
  76. package/dist/chunk-LCJIPE5S.js.map +0 -1
  77. package/dist/chunk-OORPX73T.js.map +0 -1
  78. package/dist/chunk-X6LLG7XN.js.map +0 -1
  79. package/dist/config-editor-5TICUK3K.js +0 -12
  80. package/dist/doctor-X6UCE7GQ.js +0 -9
  81. package/dist/main-NEYPQHB4.js.map +0 -1
  82. /package/dist/{api-client-UN7BXQOQ.js.map → action-detect-6M5GCGAU.js.map} +0 -0
  83. /package/dist/{autostart-K73RQZVV.js.map → admin-IKPS5PFC.js.map} +0 -0
  84. /package/dist/{config-OH26EIWN.js.map → agents-55NX3DHM.js.map} +0 -0
  85. /package/dist/{config-editor-5TICUK3K.js.map → api-client-BH2JFHQW.js.map} +0 -0
  86. /package/dist/{daemon-VKCONJUY.js.map → autostart-A7JRU4WJ.js.map} +0 -0
  87. /package/dist/{chunk-6DAZSKE5.js.map → chunk-437NLISU.js.map} +0 -0
  88. /package/dist/{doctor-X6UCE7GQ.js.map → config-KF2MQWAP.js.map} +0 -0
  89. /package/dist/{setup-ZCWGOEAH.js.map → config-editor-OTODXUF7.js.map} +0 -0
  90. /package/dist/{version-VC5CPXBX.js.map → daemon-U6UC7OM4.js.map} +0 -0
package/README.md CHANGED
@@ -20,7 +20,7 @@ One message, any channel, any agent.
20
20
 
21
21
  ## What is OpenACP?
22
22
 
23
- OpenACP lets you control AI coding agents (Claude Code, Codex, ...) from messaging apps like Telegram. You send a message, the agent writes code, runs commands, and streams everything back — in real time.
23
+ OpenACP lets you control AI coding agents (Claude Code, Codex, ...) from messaging apps like Telegram and Discord. You send a message, the agent writes code, runs commands, and streams everything back — in real time.
24
24
 
25
25
  It uses the [Agent Client Protocol (ACP)](https://agentclientprotocol.org/) to talk to agents. You host it on your own machine, so you own the data.
26
26
 
@@ -76,8 +76,13 @@ OpenACP follows the [Agent Client Protocol (ACP)](https://agentclientprotocol.co
76
76
 
77
77
  - **Multi-agent** — Claude Code, Codex, Gemini, Cursor, and [28+ ACP-compatible agents](#supported-agents)
78
78
  - **Telegram** — Forum topics, real-time streaming, permission buttons, skill commands
79
+ - **Discord** — Forum/thread-based sessions, slash commands, button interactions
79
80
  - **Tunnel & file viewer** — Public file/diff viewer via Cloudflare, ngrok, bore, or Tailscale
80
81
  - **Session persistence** — Resume sessions across restarts
82
+ - **Daemon mode** — Background service with auto-start on boot
83
+ - **CLI API** — Create and manage sessions from the terminal
84
+ - **Config editor** — Interactive `openacp config` for all settings
85
+ - **Setup wizard** — Interactive first-run setup with bot validation and auto-detect
81
86
  - **Plugin system** — Install channel adapters as npm packages
82
87
  - **Structured logging** — Pino with rotation, per-session log files
83
88
  - **Self-hosted** — Your keys, your data, your machine
@@ -87,8 +92,9 @@ OpenACP follows the [Agent Client Protocol (ACP)](https://agentclientprotocol.co
87
92
  ### Prerequisites
88
93
 
89
94
  - **Node.js 20+**
90
- - **A Telegram bot** — Create one via [@BotFather](https://t.me/BotFather) and save the token
91
- - **A Telegram supergroup** with Topics enabled Add your bot as admin
95
+ - **A messaging platform** — Telegram, Discord, or both:
96
+ - **Telegram**: Create a bot via [@BotFather](https://t.me/BotFather), create a supergroup with Topics enabled, add bot as admin
97
+ - **Discord**: Create a bot at [Discord Developer Portal](https://discord.com/developers/applications), invite it to your server with Manage Channels + Send Messages permissions
92
98
 
93
99
  ### Install & first run
94
100
 
@@ -98,13 +104,13 @@ openacp
98
104
  ```
99
105
 
100
106
  > **Important: `openacp` is an interactive CLI.**
101
- > The first run launches a setup wizard that asks you questions in the terminal (bot token, group selection, workspace path, etc.).
107
+ > The first run launches a setup wizard that asks you questions in the terminal (bot token, server selection, workspace path, etc.).
102
108
  > You **must run it yourself in a terminal** — it cannot be run by a script or an AI agent because it requires interactive input.
103
109
 
104
110
  The wizard will:
105
111
 
106
- 1. **Ask for your Telegram bot token** — validates it against the Telegram API
107
- 2. **Auto-detect your group** — send "hi" in the group and it picks it up, or enter the chat ID manually
112
+ 1. **Choose your channel(s)** — Telegram, Discord, or both
113
+ 2. **Configure bot credentials** — bot token and server/group ID, validated against the platform API
108
114
  3. **Set a workspace directory** — where agents will create project folders (default: `~/openacp-workspace`)
109
115
  4. **Detect installed agents** — finds Claude Code, Codex, etc.
110
116
  5. **Choose run mode** — foreground (in terminal) or background (daemon with auto-start)
@@ -134,8 +140,14 @@ openacp agents uninstall <name> # Remove an installed agent
134
140
  openacp agents info <name> # Show agent details & dependencies
135
141
  openacp agents refresh # Force-refresh the registry
136
142
 
143
+ # API (requires running daemon)
144
+ openacp api new [agent] [workspace] # Create a new session
145
+ openacp api cancel <id> # Cancel a session
146
+ openacp api status # Show active sessions
147
+ openacp api agents # List available agents
148
+
137
149
  # System
138
- openacp config # Show current config
150
+ openacp config # Interactive config editor
139
151
  openacp reset # Re-run the setup wizard
140
152
  openacp update # Update to latest version
141
153
  openacp install <plugin> # Install a plugin (e.g. @openacp/adapter-discord)
@@ -145,7 +157,9 @@ openacp plugins # List installed plugins
145
157
 
146
158
  ## Usage
147
159
 
148
- Once OpenACP is running, control it from Telegram:
160
+ Once OpenACP is running, control it from Telegram or Discord:
161
+
162
+ **Telegram** — Bot commands in your supergroup:
149
163
 
150
164
  | Command | Description |
151
165
  |---------|-------------|
@@ -156,13 +170,25 @@ Once OpenACP is running, control it from Telegram:
156
170
  | `/agents` | Browse & install agents from ACP Registry |
157
171
  | `/install <name>` | Install an agent directly |
158
172
 
159
- Each session gets its own forum topic. The agent streams responses in real time, shows tool calls, and asks for permission when needed.
173
+ Each session gets its own forum topic.
174
+
175
+ **Discord** — Slash commands in your server:
176
+
177
+ | Command | Description |
178
+ |---------|-------------|
179
+ | `/new [agent] [workspace]` | Create a new session |
180
+ | `/newchat` | New session, same agent & workspace |
181
+ | `/cancel` | Cancel current session |
182
+ | `/status` | Show session or system status |
183
+ | `/menu` | Open the control panel |
184
+
185
+ Each session gets its own thread in the sessions channel. The agent streams responses in real time, shows tool calls, and asks for permission when needed.
160
186
 
161
187
  ### Session Transfer
162
188
 
163
- Move sessions between your terminal and Telegram:
189
+ Move sessions between your terminal and messaging platforms:
164
190
 
165
- **Terminal → Telegram:**
191
+ **Terminal → Chat:**
166
192
  ```bash
167
193
  # Install integration (one-time)
168
194
  openacp integrate claude
@@ -172,8 +198,8 @@ openacp integrate claude
172
198
  openacp adopt claude <session_id> --cwd /path/to/project
173
199
  ```
174
200
 
175
- **Telegram → Terminal:**
176
- Type `/handoff` in any session topic. The bot replies with a command you can paste in your terminal to continue.
201
+ **Chat → Terminal:**
202
+ Type `/handoff` in any session topic/thread. The bot replies with a command you can paste in your terminal to continue.
177
203
 
178
204
  Sessions are not locked after transfer — you can continue from either side.
179
205
 
@@ -181,7 +207,7 @@ Sessions are not locked after transfer — you can continue from either side.
181
207
 
182
208
  - **Phase 1** — Core + Telegram + ACP agents
183
209
  - **Phase 2** — Tunnel/file viewer, session persistence, logging, plugin system
184
- - **Phase 3** — Agent skills as commands, Discord adapter, Web UI
210
+ - **Phase 3** — Agent skills as commands, Discord adapter 🚧, Web UI
185
211
  - **Phase 4** — Voice control, file/image sharing
186
212
  - **Phase 5** — WhatsApp, agent chaining, plugin marketplace
187
213
 
@@ -0,0 +1,15 @@
1
+ import {
2
+ buildActionKeyboard,
3
+ detectAction,
4
+ getAction,
5
+ removeAction,
6
+ storeAction
7
+ } from "./chunk-I7WC6E5S.js";
8
+ export {
9
+ buildActionKeyboard,
10
+ detectAction,
11
+ getAction,
12
+ removeAction,
13
+ storeAction
14
+ };
15
+ //# sourceMappingURL=action-detect-6M5GCGAU.js.map
@@ -0,0 +1,16 @@
1
+ import {
2
+ buildDangerousModeKeyboard,
3
+ handleDangerous,
4
+ handleDangerousButton,
5
+ handleRestart,
6
+ handleUpdate
7
+ } from "./chunk-7G5QKLLF.js";
8
+ import "./chunk-ESOPMQAY.js";
9
+ export {
10
+ buildDangerousModeKeyboard,
11
+ handleDangerous,
12
+ handleDangerousButton,
13
+ handleRestart,
14
+ handleUpdate
15
+ };
16
+ //# sourceMappingURL=admin-IKPS5PFC.js.map
@@ -0,0 +1,14 @@
1
+ import {
2
+ handleAgentButton,
3
+ handleAgents,
4
+ handleInstall,
5
+ showAgentsList
6
+ } from "./chunk-H7ZMPBZC.js";
7
+ import "./chunk-ESOPMQAY.js";
8
+ export {
9
+ handleAgentButton,
10
+ handleAgents,
11
+ handleInstall,
12
+ showAgentsList
13
+ };
14
+ //# sourceMappingURL=agents-55NX3DHM.js.map
@@ -1,11 +1,13 @@
1
1
  import {
2
2
  apiCall,
3
3
  readApiPort,
4
+ readApiSecret,
4
5
  removeStalePortFile
5
- } from "./chunk-OORPX73T.js";
6
+ } from "./chunk-W3EYKZNQ.js";
6
7
  export {
7
8
  apiCall,
8
9
  readApiPort,
10
+ readApiSecret,
9
11
  removeStalePortFile
10
12
  };
11
- //# sourceMappingURL=api-client-UN7BXQOQ.js.map
13
+ //# sourceMappingURL=api-client-BH2JFHQW.js.map
@@ -1,13 +1,17 @@
1
1
  import {
2
+ escapeSystemdValue,
3
+ escapeXml,
2
4
  generateLaunchdPlist,
3
5
  generateSystemdUnit,
4
6
  installAutoStart,
5
7
  isAutoStartInstalled,
6
8
  isAutoStartSupported,
7
9
  uninstallAutoStart
8
- } from "./chunk-X6LLG7XN.js";
10
+ } from "./chunk-PMGNLNSH.js";
9
11
  import "./chunk-ESOPMQAY.js";
10
12
  export {
13
+ escapeSystemdValue,
14
+ escapeXml,
11
15
  generateLaunchdPlist,
12
16
  generateSystemdUnit,
13
17
  installAutoStart,
@@ -15,4 +19,4 @@ export {
15
19
  isAutoStartSupported,
16
20
  uninstallAutoStart
17
21
  };
18
- //# sourceMappingURL=autostart-K73RQZVV.js.map
22
+ //# sourceMappingURL=autostart-A7JRU4WJ.js.map
@@ -0,0 +1,134 @@
1
+ import {
2
+ DoctorEngine
3
+ } from "./chunk-SPX7CKWV.js";
4
+ import {
5
+ log
6
+ } from "./chunk-ESOPMQAY.js";
7
+
8
+ // src/adapters/discord/commands/doctor.ts
9
+ import {
10
+ ActionRowBuilder,
11
+ ButtonBuilder,
12
+ ButtonStyle
13
+ } from "discord.js";
14
+ var pendingFixesStore = /* @__PURE__ */ new Map();
15
+ function renderReport(report) {
16
+ const icons = { pass: "\u2705", warn: "\u26A0\uFE0F", fail: "\u274C" };
17
+ const lines = ["\u{1FA7A} **OpenACP Doctor**\n"];
18
+ for (const category of report.categories) {
19
+ lines.push(`**${category.name}**`);
20
+ for (const result of category.results) {
21
+ lines.push(` ${icons[result.status]} ${result.message}`);
22
+ }
23
+ lines.push("");
24
+ }
25
+ const { passed, warnings, failed, fixed } = report.summary;
26
+ const fixedStr = fixed > 0 ? `, ${fixed} fixed` : "";
27
+ lines.push(`**Result:** ${passed} passed, ${warnings} warnings, ${failed} failed${fixedStr}`);
28
+ const components = [];
29
+ if (report.pendingFixes.length > 0) {
30
+ const row = new ActionRowBuilder();
31
+ for (let i = 0; i < Math.min(report.pendingFixes.length, 5); i++) {
32
+ row.addComponents(
33
+ new ButtonBuilder().setCustomId(`m:doctor:fix:${i}`).setLabel(`\u{1F527} Fix: ${report.pendingFixes[i].message.slice(0, 30)}`).setStyle(ButtonStyle.Primary)
34
+ );
35
+ }
36
+ components.push(row);
37
+ }
38
+ return { content: lines.join("\n"), components };
39
+ }
40
+ async function handleDoctor(interaction, _adapter) {
41
+ await interaction.deferReply({ ephemeral: true });
42
+ try {
43
+ const engine = new DoctorEngine();
44
+ const report = await engine.runAll();
45
+ const { content, components } = renderReport(report);
46
+ const storeKey = `${interaction.guildId}:${interaction.channelId}`;
47
+ if (report.pendingFixes.length > 0) {
48
+ pendingFixesStore.set(storeKey, report.pendingFixes);
49
+ }
50
+ await interaction.editReply({ content, components });
51
+ } catch (err) {
52
+ log.error({ err }, "[discord-doctor] Doctor command failed");
53
+ await interaction.editReply(
54
+ `\u274C Doctor failed: ${err instanceof Error ? err.message : String(err)}`
55
+ );
56
+ }
57
+ }
58
+ async function runDoctorInline(interaction, _adapter) {
59
+ try {
60
+ const engine = new DoctorEngine();
61
+ const report = await engine.runAll();
62
+ const { content, components } = renderReport(report);
63
+ const storeKey = `${interaction.guildId}:${interaction.channelId}`;
64
+ if (report.pendingFixes.length > 0) {
65
+ pendingFixesStore.set(storeKey, report.pendingFixes);
66
+ }
67
+ await interaction.followUp({ content, components, ephemeral: true });
68
+ } catch (err) {
69
+ log.error({ err }, "[discord-doctor] Doctor inline failed");
70
+ await interaction.followUp({
71
+ content: `\u274C Doctor failed: ${err instanceof Error ? err.message : String(err)}`,
72
+ ephemeral: true
73
+ });
74
+ }
75
+ }
76
+ async function handleDoctorButton(interaction, _adapter) {
77
+ const { customId } = interaction;
78
+ if (customId === "m:doctor") {
79
+ try {
80
+ await interaction.deferUpdate();
81
+ } catch {
82
+ }
83
+ await runDoctorInline(interaction, _adapter);
84
+ return;
85
+ }
86
+ if (customId.startsWith("m:doctor:fix:")) {
87
+ const index = parseInt(customId.replace("m:doctor:fix:", ""), 10);
88
+ const storeKey = `${interaction.guildId}:${interaction.channelId}`;
89
+ const fixes = pendingFixesStore.get(storeKey);
90
+ try {
91
+ await interaction.deferUpdate();
92
+ } catch {
93
+ }
94
+ if (!fixes || index < 0 || index >= fixes.length) {
95
+ try {
96
+ await interaction.followUp({ content: "\u26A0\uFE0F Fix no longer available.", ephemeral: true });
97
+ } catch {
98
+ }
99
+ return;
100
+ }
101
+ const pending = fixes[index];
102
+ try {
103
+ const result = await pending.fix();
104
+ if (result.success) {
105
+ const engine = new DoctorEngine();
106
+ const report = await engine.runAll();
107
+ const { content, components } = renderReport(report);
108
+ if (report.pendingFixes.length > 0) {
109
+ pendingFixesStore.set(storeKey, report.pendingFixes);
110
+ } else {
111
+ pendingFixesStore.delete(storeKey);
112
+ }
113
+ try {
114
+ await interaction.followUp({ content, components, ephemeral: true });
115
+ } catch {
116
+ }
117
+ } else {
118
+ try {
119
+ await interaction.followUp({ content: `\u274C Fix failed: ${result.message}`, ephemeral: true });
120
+ } catch {
121
+ }
122
+ }
123
+ } catch (err) {
124
+ log.error({ err, index }, "[discord-doctor] Doctor fix callback failed");
125
+ }
126
+ }
127
+ }
128
+
129
+ export {
130
+ handleDoctor,
131
+ runDoctorInline,
132
+ handleDoctorButton
133
+ };
134
+ //# sourceMappingURL=chunk-3WPG7GXA.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../src/adapters/discord/commands/doctor.ts"],"sourcesContent":["import {\n ActionRowBuilder,\n ButtonBuilder,\n ButtonStyle,\n} from 'discord.js'\nimport type { ChatInputCommandInteraction, ButtonInteraction } from 'discord.js'\nimport { DoctorEngine } from '../../../core/doctor/index.js'\nimport type { DoctorReport, PendingFix } from '../../../core/doctor/types.js'\nimport { log } from '../../../core/log.js'\n\n// TODO: Replace `any` with DiscordAdapter once Task 12 is implemented\n\n// In-memory store of pending fixes keyed by \"guildId:channelId:messageId\"\nconst pendingFixesStore = new Map<string, PendingFix[]>()\n\nfunction renderReport(report: DoctorReport): {\n content: string\n components: ActionRowBuilder<ButtonBuilder>[]\n} {\n const icons = { pass: '✅', warn: '⚠️', fail: '❌' }\n const lines: string[] = ['🩺 **OpenACP Doctor**\\n']\n\n for (const category of report.categories) {\n lines.push(`**${category.name}**`)\n for (const result of category.results) {\n lines.push(` ${icons[result.status]} ${result.message}`)\n }\n lines.push('')\n }\n\n const { passed, warnings, failed, fixed } = report.summary\n const fixedStr = fixed > 0 ? `, ${fixed} fixed` : ''\n lines.push(`**Result:** ${passed} passed, ${warnings} warnings, ${failed} failed${fixedStr}`)\n\n const components: ActionRowBuilder<ButtonBuilder>[] = []\n\n if (report.pendingFixes.length > 0) {\n const row = new ActionRowBuilder<ButtonBuilder>()\n for (let i = 0; i < Math.min(report.pendingFixes.length, 5); i++) {\n row.addComponents(\n new ButtonBuilder()\n .setCustomId(`m:doctor:fix:${i}`)\n .setLabel(`🔧 Fix: ${report.pendingFixes[i].message.slice(0, 30)}`)\n .setStyle(ButtonStyle.Primary),\n )\n }\n components.push(row)\n }\n\n return { content: lines.join('\\n'), components }\n}\n\nexport async function handleDoctor(\n interaction: ChatInputCommandInteraction,\n _adapter: any,\n): Promise<void> {\n await interaction.deferReply({ ephemeral: true })\n\n try {\n const engine = new DoctorEngine()\n const report = await engine.runAll()\n const { content, components } = renderReport(report)\n\n // Store pending fixes for button callbacks\n const storeKey = `${interaction.guildId}:${interaction.channelId}`\n if (report.pendingFixes.length > 0) {\n pendingFixesStore.set(storeKey, report.pendingFixes)\n }\n\n await interaction.editReply({ content, components })\n } catch (err) {\n log.error({ err }, '[discord-doctor] Doctor command failed')\n await interaction.editReply(\n `❌ Doctor failed: ${err instanceof Error ? err.message : String(err)}`,\n )\n }\n}\n\nexport async function runDoctorInline(\n interaction: ButtonInteraction,\n _adapter: any,\n): Promise<void> {\n try {\n const engine = new DoctorEngine()\n const report = await engine.runAll()\n const { content, components } = renderReport(report)\n\n const storeKey = `${interaction.guildId}:${interaction.channelId}`\n if (report.pendingFixes.length > 0) {\n pendingFixesStore.set(storeKey, report.pendingFixes)\n }\n\n await interaction.followUp({ content, components, ephemeral: true })\n } catch (err) {\n log.error({ err }, '[discord-doctor] Doctor inline failed')\n await interaction.followUp({\n content: `❌ Doctor failed: ${err instanceof Error ? err.message : String(err)}`,\n ephemeral: true,\n })\n }\n}\n\nexport async function handleDoctorButton(\n interaction: ButtonInteraction,\n _adapter: any,\n): Promise<void> {\n const { customId } = interaction\n\n if (customId === 'm:doctor') {\n try { await interaction.deferUpdate() } catch { /* ignore */ }\n await runDoctorInline(interaction, _adapter)\n return\n }\n\n if (customId.startsWith('m:doctor:fix:')) {\n const index = parseInt(customId.replace('m:doctor:fix:', ''), 10)\n const storeKey = `${interaction.guildId}:${interaction.channelId}`\n const fixes = pendingFixesStore.get(storeKey)\n\n try { await interaction.deferUpdate() } catch { /* ignore */ }\n\n if (!fixes || index < 0 || index >= fixes.length) {\n try { await interaction.followUp({ content: '⚠️ Fix no longer available.', ephemeral: true }) } catch { /* */ }\n return\n }\n\n const pending = fixes[index]\n try {\n const result = await pending.fix()\n if (result.success) {\n // Re-run doctor to show updated status\n const engine = new DoctorEngine()\n const report = await engine.runAll()\n const { content, components } = renderReport(report)\n\n if (report.pendingFixes.length > 0) {\n pendingFixesStore.set(storeKey, report.pendingFixes)\n } else {\n pendingFixesStore.delete(storeKey)\n }\n\n try { await interaction.followUp({ content, components, ephemeral: true }) } catch { /* ignore */ }\n } else {\n try { await interaction.followUp({ content: `❌ Fix failed: ${result.message}`, ephemeral: true }) } catch { /* */ }\n }\n } catch (err) {\n log.error({ err, index }, '[discord-doctor] Doctor fix callback failed')\n }\n }\n}\n"],"mappings":";;;;;;;;AAAA;AAAA,EACE;AAAA,EACA;AAAA,EACA;AAAA,OACK;AASP,IAAM,oBAAoB,oBAAI,IAA0B;AAExD,SAAS,aAAa,QAGpB;AACA,QAAM,QAAQ,EAAE,MAAM,UAAK,MAAM,gBAAM,MAAM,SAAI;AACjD,QAAM,QAAkB,CAAC,gCAAyB;AAElD,aAAW,YAAY,OAAO,YAAY;AACxC,UAAM,KAAK,KAAK,SAAS,IAAI,IAAI;AACjC,eAAW,UAAU,SAAS,SAAS;AACrC,YAAM,KAAK,KAAK,MAAM,OAAO,MAAM,CAAC,IAAI,OAAO,OAAO,EAAE;AAAA,IAC1D;AACA,UAAM,KAAK,EAAE;AAAA,EACf;AAEA,QAAM,EAAE,QAAQ,UAAU,QAAQ,MAAM,IAAI,OAAO;AACnD,QAAM,WAAW,QAAQ,IAAI,KAAK,KAAK,WAAW;AAClD,QAAM,KAAK,eAAe,MAAM,YAAY,QAAQ,cAAc,MAAM,UAAU,QAAQ,EAAE;AAE5F,QAAM,aAAgD,CAAC;AAEvD,MAAI,OAAO,aAAa,SAAS,GAAG;AAClC,UAAM,MAAM,IAAI,iBAAgC;AAChD,aAAS,IAAI,GAAG,IAAI,KAAK,IAAI,OAAO,aAAa,QAAQ,CAAC,GAAG,KAAK;AAChE,UAAI;AAAA,QACF,IAAI,cAAc,EACf,YAAY,gBAAgB,CAAC,EAAE,EAC/B,SAAS,kBAAW,OAAO,aAAa,CAAC,EAAE,QAAQ,MAAM,GAAG,EAAE,CAAC,EAAE,EACjE,SAAS,YAAY,OAAO;AAAA,MACjC;AAAA,IACF;AACA,eAAW,KAAK,GAAG;AAAA,EACrB;AAEA,SAAO,EAAE,SAAS,MAAM,KAAK,IAAI,GAAG,WAAW;AACjD;AAEA,eAAsB,aACpB,aACA,UACe;AACf,QAAM,YAAY,WAAW,EAAE,WAAW,KAAK,CAAC;AAEhD,MAAI;AACF,UAAM,SAAS,IAAI,aAAa;AAChC,UAAM,SAAS,MAAM,OAAO,OAAO;AACnC,UAAM,EAAE,SAAS,WAAW,IAAI,aAAa,MAAM;AAGnD,UAAM,WAAW,GAAG,YAAY,OAAO,IAAI,YAAY,SAAS;AAChE,QAAI,OAAO,aAAa,SAAS,GAAG;AAClC,wBAAkB,IAAI,UAAU,OAAO,YAAY;AAAA,IACrD;AAEA,UAAM,YAAY,UAAU,EAAE,SAAS,WAAW,CAAC;AAAA,EACrD,SAAS,KAAK;AACZ,QAAI,MAAM,EAAE,IAAI,GAAG,wCAAwC;AAC3D,UAAM,YAAY;AAAA,MAChB,yBAAoB,eAAe,QAAQ,IAAI,UAAU,OAAO,GAAG,CAAC;AAAA,IACtE;AAAA,EACF;AACF;AAEA,eAAsB,gBACpB,aACA,UACe;AACf,MAAI;AACF,UAAM,SAAS,IAAI,aAAa;AAChC,UAAM,SAAS,MAAM,OAAO,OAAO;AACnC,UAAM,EAAE,SAAS,WAAW,IAAI,aAAa,MAAM;AAEnD,UAAM,WAAW,GAAG,YAAY,OAAO,IAAI,YAAY,SAAS;AAChE,QAAI,OAAO,aAAa,SAAS,GAAG;AAClC,wBAAkB,IAAI,UAAU,OAAO,YAAY;AAAA,IACrD;AAEA,UAAM,YAAY,SAAS,EAAE,SAAS,YAAY,WAAW,KAAK,CAAC;AAAA,EACrE,SAAS,KAAK;AACZ,QAAI,MAAM,EAAE,IAAI,GAAG,uCAAuC;AAC1D,UAAM,YAAY,SAAS;AAAA,MACzB,SAAS,yBAAoB,eAAe,QAAQ,IAAI,UAAU,OAAO,GAAG,CAAC;AAAA,MAC7E,WAAW;AAAA,IACb,CAAC;AAAA,EACH;AACF;AAEA,eAAsB,mBACpB,aACA,UACe;AACf,QAAM,EAAE,SAAS,IAAI;AAErB,MAAI,aAAa,YAAY;AAC3B,QAAI;AAAE,YAAM,YAAY,YAAY;AAAA,IAAE,QAAQ;AAAA,IAAe;AAC7D,UAAM,gBAAgB,aAAa,QAAQ;AAC3C;AAAA,EACF;AAEA,MAAI,SAAS,WAAW,eAAe,GAAG;AACxC,UAAM,QAAQ,SAAS,SAAS,QAAQ,iBAAiB,EAAE,GAAG,EAAE;AAChE,UAAM,WAAW,GAAG,YAAY,OAAO,IAAI,YAAY,SAAS;AAChE,UAAM,QAAQ,kBAAkB,IAAI,QAAQ;AAE5C,QAAI;AAAE,YAAM,YAAY,YAAY;AAAA,IAAE,QAAQ;AAAA,IAAe;AAE7D,QAAI,CAAC,SAAS,QAAQ,KAAK,SAAS,MAAM,QAAQ;AAChD,UAAI;AAAE,cAAM,YAAY,SAAS,EAAE,SAAS,yCAA+B,WAAW,KAAK,CAAC;AAAA,MAAE,QAAQ;AAAA,MAAQ;AAC9G;AAAA,IACF;AAEA,UAAM,UAAU,MAAM,KAAK;AAC3B,QAAI;AACF,YAAM,SAAS,MAAM,QAAQ,IAAI;AACjC,UAAI,OAAO,SAAS;AAElB,cAAM,SAAS,IAAI,aAAa;AAChC,cAAM,SAAS,MAAM,OAAO,OAAO;AACnC,cAAM,EAAE,SAAS,WAAW,IAAI,aAAa,MAAM;AAEnD,YAAI,OAAO,aAAa,SAAS,GAAG;AAClC,4BAAkB,IAAI,UAAU,OAAO,YAAY;AAAA,QACrD,OAAO;AACL,4BAAkB,OAAO,QAAQ;AAAA,QACnC;AAEA,YAAI;AAAE,gBAAM,YAAY,SAAS,EAAE,SAAS,YAAY,WAAW,KAAK,CAAC;AAAA,QAAE,QAAQ;AAAA,QAAe;AAAA,MACpG,OAAO;AACL,YAAI;AAAE,gBAAM,YAAY,SAAS,EAAE,SAAS,sBAAiB,OAAO,OAAO,IAAI,WAAW,KAAK,CAAC;AAAA,QAAE,QAAQ;AAAA,QAAQ;AAAA,MACpH;AAAA,IACF,SAAS,KAAK;AACZ,UAAI,MAAM,EAAE,KAAK,MAAM,GAAG,6CAA6C;AAAA,IACzE;AAAA,EACF;AACF;","names":[]}
@@ -1,6 +1,6 @@
1
1
  import {
2
2
  PLUGINS_DIR
3
- } from "./chunk-2Z2XPUD5.js";
3
+ } from "./chunk-DWQKUECJ.js";
4
4
  import {
5
5
  createChildLogger
6
6
  } from "./chunk-ESOPMQAY.js";
@@ -60,4 +60,4 @@ export {
60
60
  listPlugins,
61
61
  loadAdapterFactory
62
62
  };
63
- //# sourceMappingURL=chunk-6DAZSKE5.js.map
63
+ //# sourceMappingURL=chunk-437NLISU.js.map