@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.
- package/README.md +40 -14
- package/dist/action-detect-6M5GCGAU.js +15 -0
- package/dist/admin-IKPS5PFC.js +16 -0
- package/dist/agents-55NX3DHM.js +14 -0
- package/dist/{api-client-UN7BXQOQ.js → api-client-BH2JFHQW.js} +4 -2
- package/dist/{autostart-K73RQZVV.js → autostart-A7JRU4WJ.js} +6 -2
- package/dist/chunk-3WPG7GXA.js +134 -0
- package/dist/chunk-3WPG7GXA.js.map +1 -0
- package/dist/{chunk-6DAZSKE5.js → chunk-437NLISU.js} +2 -2
- package/dist/chunk-5NBWM7P6.js +438 -0
- package/dist/chunk-5NBWM7P6.js.map +1 -0
- package/dist/{chunk-IQIPQTQT.js → chunk-6Q7PZWCL.js} +171 -26
- package/dist/chunk-6Q7PZWCL.js.map +1 -0
- package/dist/chunk-7G5QKLLF.js +105 -0
- package/dist/chunk-7G5QKLLF.js.map +1 -0
- package/dist/chunk-AKIU4JBF.js +145 -0
- package/dist/chunk-AKIU4JBF.js.map +1 -0
- package/dist/{chunk-2Z2XPUD5.js → chunk-DWQKUECJ.js} +12 -2
- package/dist/chunk-DWQKUECJ.js.map +1 -0
- package/dist/{chunk-KSIQZC3J.js → chunk-EVFJW45N.js} +1 -1
- package/dist/chunk-EVFJW45N.js.map +1 -0
- package/dist/chunk-H7ZMPBZC.js +203 -0
- package/dist/chunk-H7ZMPBZC.js.map +1 -0
- package/dist/chunk-I7WC6E5S.js +71 -0
- package/dist/chunk-I7WC6E5S.js.map +1 -0
- package/dist/{chunk-ECBD5I5R.js → chunk-MHFCZGRW.js} +112 -15
- package/dist/chunk-MHFCZGRW.js.map +1 -0
- package/dist/{chunk-X6LLG7XN.js → chunk-PMGNLNSH.js} +15 -6
- package/dist/chunk-PMGNLNSH.js.map +1 -0
- package/dist/chunk-SM3G6UAX.js +122 -0
- package/dist/chunk-SM3G6UAX.js.map +1 -0
- package/dist/{chunk-K53OZH5Y.js → chunk-SPX7CKWV.js} +76 -2
- package/dist/chunk-SPX7CKWV.js.map +1 -0
- package/dist/chunk-T22OLSET.js +265 -0
- package/dist/chunk-T22OLSET.js.map +1 -0
- package/dist/chunk-THBR6OXH.js +62 -0
- package/dist/chunk-THBR6OXH.js.map +1 -0
- package/dist/{chunk-LCJIPE5S.js → chunk-V2V767XI.js} +58 -440
- package/dist/chunk-V2V767XI.js.map +1 -0
- package/dist/{chunk-OORPX73T.js → chunk-W3EYKZNQ.js} +17 -2
- package/dist/chunk-W3EYKZNQ.js.map +1 -0
- package/dist/{chunk-5KYLXEG3.js → chunk-YYQXWA62.js} +52 -9
- package/dist/chunk-YYQXWA62.js.map +1 -0
- package/dist/cli.js +30 -29
- package/dist/cli.js.map +1 -1
- package/dist/{config-OH26EIWN.js → config-KF2MQWAP.js} +2 -2
- package/dist/config-editor-OTODXUF7.js +12 -0
- package/dist/{daemon-VKCONJUY.js → daemon-U6UC7OM4.js} +3 -3
- package/dist/discord-SLLKRUP7.js +2034 -0
- package/dist/discord-SLLKRUP7.js.map +1 -0
- package/dist/doctor-DB5PRQ6D.js +14 -0
- package/dist/doctor-DB5PRQ6D.js.map +1 -0
- package/dist/doctor-SYWNJFYK.js +9 -0
- package/dist/doctor-SYWNJFYK.js.map +1 -0
- package/dist/index.d.ts +12 -4
- package/dist/index.js +11 -9
- package/dist/{main-NEYPQHB4.js → main-M6RH3SS5.js} +30 -22
- package/dist/main-M6RH3SS5.js.map +1 -0
- package/dist/new-session-DRRP2J7E.js +16 -0
- package/dist/new-session-DRRP2J7E.js.map +1 -0
- package/dist/session-FVFLBREJ.js +19 -0
- package/dist/session-FVFLBREJ.js.map +1 -0
- package/dist/settings-LPOLJ6SA.js +12 -0
- package/dist/settings-LPOLJ6SA.js.map +1 -0
- package/dist/{setup-ZCWGOEAH.js → setup-LI5CKYDK.js} +9 -5
- package/dist/setup-LI5CKYDK.js.map +1 -0
- package/dist/{version-VC5CPXBX.js → version-ALWGGVKM.js} +2 -2
- package/dist/version-ALWGGVKM.js.map +1 -0
- package/package.json +2 -1
- package/dist/chunk-2Z2XPUD5.js.map +0 -1
- package/dist/chunk-5KYLXEG3.js.map +0 -1
- package/dist/chunk-ECBD5I5R.js.map +0 -1
- package/dist/chunk-IQIPQTQT.js.map +0 -1
- package/dist/chunk-K53OZH5Y.js.map +0 -1
- package/dist/chunk-KSIQZC3J.js.map +0 -1
- package/dist/chunk-LCJIPE5S.js.map +0 -1
- package/dist/chunk-OORPX73T.js.map +0 -1
- package/dist/chunk-X6LLG7XN.js.map +0 -1
- package/dist/config-editor-5TICUK3K.js +0 -12
- package/dist/doctor-X6UCE7GQ.js +0 -9
- package/dist/main-NEYPQHB4.js.map +0 -1
- /package/dist/{api-client-UN7BXQOQ.js.map → action-detect-6M5GCGAU.js.map} +0 -0
- /package/dist/{autostart-K73RQZVV.js.map → admin-IKPS5PFC.js.map} +0 -0
- /package/dist/{config-OH26EIWN.js.map → agents-55NX3DHM.js.map} +0 -0
- /package/dist/{config-editor-5TICUK3K.js.map → api-client-BH2JFHQW.js.map} +0 -0
- /package/dist/{daemon-VKCONJUY.js.map → autostart-A7JRU4WJ.js.map} +0 -0
- /package/dist/{chunk-6DAZSKE5.js.map → chunk-437NLISU.js.map} +0 -0
- /package/dist/{doctor-X6UCE7GQ.js.map → config-KF2MQWAP.js.map} +0 -0
- /package/dist/{setup-ZCWGOEAH.js.map → config-editor-OTODXUF7.js.map} +0 -0
- /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
|
|
91
|
-
- **
|
|
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,
|
|
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. **
|
|
107
|
-
2. **
|
|
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 #
|
|
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.
|
|
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
|
|
189
|
+
Move sessions between your terminal and messaging platforms:
|
|
164
190
|
|
|
165
|
-
**Terminal →
|
|
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
|
-
**
|
|
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
|
|
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-
|
|
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-
|
|
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-
|
|
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-
|
|
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":"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","names":[]}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import {
|
|
2
2
|
PLUGINS_DIR
|
|
3
|
-
} from "./chunk-
|
|
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-
|
|
63
|
+
//# sourceMappingURL=chunk-437NLISU.js.map
|