@openacp/cli 0.6.2 → 0.6.3

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 (63) hide show
  1. package/dist/{admin-IVQTC72V.js → admin-3ZHEO5VP.js} +8 -4
  2. package/dist/{chunk-FF4C3ZE4.js → chunk-6LSFRNHE.js} +2 -2
  3. package/dist/{chunk-2OFIWTYD.js → chunk-AVCHZESZ.js} +2 -2
  4. package/dist/{chunk-ONENT7JQ.js → chunk-FZ5BIWG5.js} +6 -6
  5. package/dist/chunk-FZ5BIWG5.js.map +1 -0
  6. package/dist/{chunk-7WQH4SOY.js → chunk-G3OHCXZG.js} +32 -22
  7. package/dist/chunk-G3OHCXZG.js.map +1 -0
  8. package/dist/{chunk-IFTYEG5J.js → chunk-HP2IJYCA.js} +27 -27
  9. package/dist/chunk-HP2IJYCA.js.map +1 -0
  10. package/dist/chunk-IER5UCY7.js +298 -0
  11. package/dist/chunk-IER5UCY7.js.map +1 -0
  12. package/dist/{chunk-IUIMBEBX.js → chunk-NXEQXRQR.js} +53 -9
  13. package/dist/chunk-NXEQXRQR.js.map +1 -0
  14. package/dist/{chunk-WQZ4RXH7.js → chunk-OHR6SBMC.js} +2 -2
  15. package/dist/chunk-PWFPTG5X.js +101 -0
  16. package/dist/chunk-PWFPTG5X.js.map +1 -0
  17. package/dist/cli.js +8 -8
  18. package/dist/{config-editor-TOZUBMO7.js → config-editor-F25HEMGL.js} +3 -3
  19. package/dist/{discord-7B5NWW5Z.js → discord-VHCBN3JJ.js} +85 -29
  20. package/dist/discord-VHCBN3JJ.js.map +1 -0
  21. package/dist/{doctor-FR5GASOQ.js → doctor-GPW5ECK6.js} +3 -3
  22. package/dist/{doctor-UOH7YCT2.js → doctor-Y3SCSVPI.js} +2 -2
  23. package/dist/index.js +6 -6
  24. package/dist/install-cloudflared-G2GUKCHA.js +32 -0
  25. package/dist/install-cloudflared-G2GUKCHA.js.map +1 -0
  26. package/dist/install-jq-7QTU7XYY.js +31 -0
  27. package/dist/install-jq-7QTU7XYY.js.map +1 -0
  28. package/dist/{integrate-QTK4PPYQ.js → integrate-O4OCR4SN.js} +9 -6
  29. package/dist/integrate-O4OCR4SN.js.map +1 -0
  30. package/dist/{main-46BVXFWI.js → main-P4X6SAPZ.js} +18 -8
  31. package/dist/main-P4X6SAPZ.js.map +1 -0
  32. package/dist/{new-session-BVNE6S3A.js → new-session-PUNUHGYP.js} +3 -3
  33. package/dist/post-upgrade-6N4JCV5S.js +79 -0
  34. package/dist/post-upgrade-6N4JCV5S.js.map +1 -0
  35. package/dist/{settings-LQ57CFY4.js → settings-OEQEZS5Y.js} +3 -2
  36. package/dist/{setup-DZXZTQRD.js → setup-7YBFKRG7.js} +4 -2
  37. package/dist/{tunnel-service-O5EKGFLO.js → tunnel-service-BMIBHUBK.js} +31 -31
  38. package/dist/tunnel-service-BMIBHUBK.js.map +1 -0
  39. package/package.json +1 -1
  40. package/dist/chunk-7WQH4SOY.js.map +0 -1
  41. package/dist/chunk-HSGUPJU5.js +0 -62
  42. package/dist/chunk-HSGUPJU5.js.map +0 -1
  43. package/dist/chunk-IFTYEG5J.js.map +0 -1
  44. package/dist/chunk-IUIMBEBX.js.map +0 -1
  45. package/dist/chunk-ONENT7JQ.js.map +0 -1
  46. package/dist/chunk-RF3DUYFO.js +0 -103
  47. package/dist/chunk-RF3DUYFO.js.map +0 -1
  48. package/dist/discord-7B5NWW5Z.js.map +0 -1
  49. package/dist/install-cloudflared-BTGUD7SW.js +0 -8
  50. package/dist/integrate-QTK4PPYQ.js.map +0 -1
  51. package/dist/main-46BVXFWI.js.map +0 -1
  52. package/dist/setup-DZXZTQRD.js.map +0 -1
  53. package/dist/tunnel-service-O5EKGFLO.js.map +0 -1
  54. /package/dist/{admin-IVQTC72V.js.map → admin-3ZHEO5VP.js.map} +0 -0
  55. /package/dist/{chunk-FF4C3ZE4.js.map → chunk-6LSFRNHE.js.map} +0 -0
  56. /package/dist/{chunk-2OFIWTYD.js.map → chunk-AVCHZESZ.js.map} +0 -0
  57. /package/dist/{chunk-WQZ4RXH7.js.map → chunk-OHR6SBMC.js.map} +0 -0
  58. /package/dist/{config-editor-TOZUBMO7.js.map → config-editor-F25HEMGL.js.map} +0 -0
  59. /package/dist/{doctor-FR5GASOQ.js.map → doctor-GPW5ECK6.js.map} +0 -0
  60. /package/dist/{doctor-UOH7YCT2.js.map → doctor-Y3SCSVPI.js.map} +0 -0
  61. /package/dist/{install-cloudflared-BTGUD7SW.js.map → new-session-PUNUHGYP.js.map} +0 -0
  62. /package/dist/{new-session-BVNE6S3A.js.map → settings-OEQEZS5Y.js.map} +0 -0
  63. /package/dist/{settings-LQ57CFY4.js.map → setup-7YBFKRG7.js.map} +0 -0
@@ -0,0 +1,298 @@
1
+ import {
2
+ getConfigValue,
3
+ getSafeFields,
4
+ isHotReloadable,
5
+ resolveOptions
6
+ } from "./chunk-F3AICYO4.js";
7
+ import {
8
+ log
9
+ } from "./chunk-ESOPMQAY.js";
10
+
11
+ // src/adapters/discord/commands/settings.ts
12
+ import {
13
+ ActionRowBuilder,
14
+ ButtonBuilder,
15
+ ButtonStyle
16
+ } from "discord.js";
17
+ function formatFieldLabel(field, value) {
18
+ const icons = {
19
+ agent: "\u{1F916}",
20
+ logging: "\u{1F4DD}",
21
+ tunnel: "\u{1F517}",
22
+ security: "\u{1F512}",
23
+ workspace: "\u{1F4C1}",
24
+ storage: "\u{1F4BE}",
25
+ speech: "\u{1F3A4}"
26
+ };
27
+ const icon = icons[field.group] ?? "\u2699\uFE0F";
28
+ if (field.type === "toggle") {
29
+ return `${icon} ${field.displayName}: ${value ? "ON" : "OFF"}`;
30
+ }
31
+ const displayValue = value === null || value === void 0 ? "Not set" : String(value);
32
+ return `${icon} ${field.displayName}: ${displayValue}`;
33
+ }
34
+ var SETTINGS_PAGE_SIZE = 4;
35
+ function buildSettingsRows(adapter, page = 0) {
36
+ const config = adapter.core.configManager.get();
37
+ const fields = getSafeFields();
38
+ const totalPages = Math.ceil(fields.length / SETTINGS_PAGE_SIZE);
39
+ const start = page * SETTINGS_PAGE_SIZE;
40
+ const pageFields = fields.slice(start, start + SETTINGS_PAGE_SIZE);
41
+ const rows = [];
42
+ for (const field of pageFields) {
43
+ const value = getConfigValue(config, field.path);
44
+ const label = formatFieldLabel(field, value);
45
+ let customId;
46
+ if (field.type === "toggle") {
47
+ customId = `s:toggle:${field.path}`;
48
+ } else if (field.type === "select") {
49
+ customId = `s:select:${field.path}`;
50
+ } else {
51
+ customId = `s:input:${field.path}`;
52
+ }
53
+ rows.push(
54
+ new ActionRowBuilder().addComponents(
55
+ new ButtonBuilder().setCustomId(customId).setLabel(label.slice(0, 80)).setStyle(ButtonStyle.Secondary)
56
+ )
57
+ );
58
+ }
59
+ if (totalPages > 1) {
60
+ const navRow = new ActionRowBuilder();
61
+ if (page > 0) {
62
+ navRow.addComponents(
63
+ new ButtonBuilder().setCustomId(`s:page:${page - 1}`).setLabel("\u25C0\uFE0F Previous").setStyle(ButtonStyle.Primary)
64
+ );
65
+ }
66
+ navRow.addComponents(
67
+ new ButtonBuilder().setCustomId("s:pageinfo").setLabel(`Page ${page + 1}/${totalPages}`).setStyle(ButtonStyle.Secondary).setDisabled(true)
68
+ );
69
+ if (page < totalPages - 1) {
70
+ navRow.addComponents(
71
+ new ButtonBuilder().setCustomId(`s:page:${page + 1}`).setLabel("Next \u25B6\uFE0F").setStyle(ButtonStyle.Primary)
72
+ );
73
+ }
74
+ rows.push(navRow);
75
+ }
76
+ return rows;
77
+ }
78
+ async function handleSettings(interaction, adapter) {
79
+ await interaction.deferReply({ ephemeral: true });
80
+ const rows = buildSettingsRows(adapter);
81
+ await interaction.editReply({
82
+ content: "**\u2699\uFE0F Settings**\nTap to change:",
83
+ components: rows
84
+ });
85
+ }
86
+ async function showSettingsInfo(interaction, adapter) {
87
+ const rows = buildSettingsRows(adapter);
88
+ await interaction.followUp({
89
+ content: "**\u2699\uFE0F Settings**\nTap to change:",
90
+ components: rows,
91
+ ephemeral: true
92
+ });
93
+ }
94
+ async function handleSettingsButton(interaction, adapter) {
95
+ const { customId } = interaction;
96
+ try {
97
+ if (customId.startsWith("s:toggle:")) {
98
+ const fieldPath = customId.replace("s:toggle:", "");
99
+ const config = adapter.core.configManager.get();
100
+ const currentValue = getConfigValue(config, fieldPath);
101
+ const newValue = !currentValue;
102
+ const updates = buildNestedUpdate(fieldPath, newValue);
103
+ await adapter.core.configManager.save(updates, fieldPath);
104
+ const toast = isHotReloadable(fieldPath) ? `\u2705 ${fieldPath} = ${newValue}` : `\u2705 ${fieldPath} = ${newValue} (restart needed)`;
105
+ try {
106
+ await interaction.update({
107
+ content: "**\u2699\uFE0F Settings**\nTap to change:",
108
+ components: buildSettingsRows(adapter)
109
+ });
110
+ } catch {
111
+ }
112
+ try {
113
+ await interaction.followUp({ content: toast, ephemeral: true });
114
+ } catch {
115
+ }
116
+ return;
117
+ }
118
+ if (customId.startsWith("s:select:")) {
119
+ const fieldPath = customId.replace("s:select:", "");
120
+ const config = adapter.core.configManager.get();
121
+ const fieldDef = getSafeFields().find((f) => f.path === fieldPath);
122
+ if (!fieldDef) return;
123
+ const options = resolveOptions(fieldDef, config) ?? [];
124
+ const currentValue = getConfigValue(config, fieldPath);
125
+ const rows = [];
126
+ let currentRow = new ActionRowBuilder();
127
+ let count = 0;
128
+ for (const opt of options) {
129
+ const marker = opt === String(currentValue) ? " \u2713" : "";
130
+ currentRow.addComponents(
131
+ new ButtonBuilder().setCustomId(`s:pick:${fieldPath}:${opt}`).setLabel(`${opt}${marker}`.slice(0, 80)).setStyle(opt === String(currentValue) ? ButtonStyle.Success : ButtonStyle.Secondary)
132
+ );
133
+ count++;
134
+ if (count % 3 === 0) {
135
+ rows.push(currentRow);
136
+ currentRow = new ActionRowBuilder();
137
+ }
138
+ }
139
+ if (currentRow.components.length > 0) {
140
+ rows.push(currentRow);
141
+ }
142
+ const backRow = new ActionRowBuilder().addComponents(
143
+ new ButtonBuilder().setCustomId("s:back").setLabel("\u25C0\uFE0F Back").setStyle(ButtonStyle.Primary)
144
+ );
145
+ rows.push(backRow);
146
+ try {
147
+ await interaction.update({
148
+ content: `**\u2699\uFE0F ${fieldDef.displayName}**
149
+ Select a value:`,
150
+ components: rows.slice(0, 5)
151
+ });
152
+ } catch {
153
+ }
154
+ return;
155
+ }
156
+ if (customId.startsWith("s:pick:")) {
157
+ const parts = customId.replace("s:pick:", "").split(":");
158
+ const fieldPath = parts.slice(0, -1).join(":");
159
+ const newValue = parts[parts.length - 1];
160
+ if (fieldPath === "speech.stt.provider") {
161
+ const config = adapter.core.configManager.get();
162
+ const providerConfig = config.speech?.stt?.providers?.[newValue];
163
+ if (!providerConfig?.apiKey) {
164
+ const assistantSessionId = adapter.getAssistantSessionId();
165
+ if (assistantSessionId) {
166
+ const assistantSession = adapter.core.sessionManager.getSession(assistantSessionId);
167
+ if (assistantSession) {
168
+ const prompt = `User wants to enable ${newValue} as Speech-to-Text provider, but no API key is configured yet. Guide them to get a ${newValue} API key and set it up. After they provide the key, run both commands: \`openacp config set speech.stt.providers.${newValue}.apiKey <key>\` and \`openacp config set speech.stt.provider ${newValue}\``;
169
+ await assistantSession.enqueuePrompt(prompt);
170
+ try {
171
+ await interaction.update({
172
+ content: "**\u2699\uFE0F Settings**\nTap to change:",
173
+ components: buildSettingsRows(adapter)
174
+ });
175
+ } catch {
176
+ }
177
+ try {
178
+ await interaction.followUp({ content: "\u{1F511} API key needed \u2014 check the Assistant thread.", ephemeral: true });
179
+ } catch {
180
+ }
181
+ return;
182
+ }
183
+ }
184
+ try {
185
+ await interaction.update({
186
+ content: "**\u2699\uFE0F Settings**\nTap to change:",
187
+ components: buildSettingsRows(adapter)
188
+ });
189
+ } catch {
190
+ }
191
+ try {
192
+ await interaction.followUp({ content: `\u26A0\uFE0F Set API key first: \`openacp config set speech.stt.providers.${newValue}.apiKey <key>\``, ephemeral: true });
193
+ } catch {
194
+ }
195
+ return;
196
+ }
197
+ }
198
+ const updates = buildNestedUpdate(fieldPath, newValue);
199
+ await adapter.core.configManager.save(updates, fieldPath);
200
+ try {
201
+ await interaction.update({
202
+ content: "**\u2699\uFE0F Settings**\nTap to change:",
203
+ components: buildSettingsRows(adapter)
204
+ });
205
+ } catch {
206
+ }
207
+ try {
208
+ await interaction.followUp({ content: `\u2705 ${fieldPath} = ${newValue}`, ephemeral: true });
209
+ } catch {
210
+ }
211
+ return;
212
+ }
213
+ if (customId.startsWith("s:input:")) {
214
+ const fieldPath = customId.replace("s:input:", "");
215
+ const config = adapter.core.configManager.get();
216
+ const fieldDef = getSafeFields().find((f) => f.path === fieldPath);
217
+ if (!fieldDef) return;
218
+ const currentValue = getConfigValue(config, fieldPath);
219
+ const assistantSessionId = adapter.getAssistantSessionId();
220
+ if (!assistantSessionId) {
221
+ try {
222
+ await interaction.reply({ content: "\u26A0\uFE0F Assistant is not available.", ephemeral: true });
223
+ } catch {
224
+ }
225
+ return;
226
+ }
227
+ const assistantSession = adapter.core.sessionManager.getSession(assistantSessionId);
228
+ if (!assistantSession) {
229
+ try {
230
+ await interaction.reply({ content: "\u26A0\uFE0F Assistant session not found.", ephemeral: true });
231
+ } catch {
232
+ }
233
+ return;
234
+ }
235
+ try {
236
+ await interaction.deferUpdate();
237
+ } catch {
238
+ }
239
+ const prompt = `User wants to change ${fieldDef.displayName} (config path: ${fieldPath}). Current value: ${JSON.stringify(currentValue)}. Ask them for the new value and apply it using: openacp config set ${fieldPath} <value>`;
240
+ await assistantSession.enqueuePrompt(prompt);
241
+ try {
242
+ await interaction.followUp({ content: `Delegating to assistant \u2014 check the Assistant thread.`, ephemeral: true });
243
+ } catch {
244
+ }
245
+ return;
246
+ }
247
+ if (customId.startsWith("s:page:")) {
248
+ const page = parseInt(customId.replace("s:page:", ""), 10);
249
+ try {
250
+ await interaction.update({
251
+ content: "**\u2699\uFE0F Settings**\nTap to change:",
252
+ components: buildSettingsRows(adapter, page)
253
+ });
254
+ } catch {
255
+ }
256
+ return;
257
+ }
258
+ if (customId === "s:back") {
259
+ try {
260
+ await interaction.update({
261
+ content: "**\u2699\uFE0F Settings**\nTap to change:",
262
+ components: buildSettingsRows(adapter)
263
+ });
264
+ } catch {
265
+ }
266
+ return;
267
+ }
268
+ log.warn({ customId }, "[discord-settings] Unhandled settings button");
269
+ } catch (err) {
270
+ log.error({ err, customId }, "[discord-settings] Settings button handler failed");
271
+ try {
272
+ if (!interaction.replied && !interaction.deferred) {
273
+ await interaction.reply({ content: "\u274C Settings action failed.", ephemeral: true });
274
+ } else {
275
+ await interaction.followUp({ content: "\u274C Settings action failed.", ephemeral: true });
276
+ }
277
+ } catch {
278
+ }
279
+ }
280
+ }
281
+ function buildNestedUpdate(dotPath, value) {
282
+ const parts = dotPath.split(".");
283
+ const result = {};
284
+ let target = result;
285
+ for (let i = 0; i < parts.length - 1; i++) {
286
+ target[parts[i]] = {};
287
+ target = target[parts[i]];
288
+ }
289
+ target[parts[parts.length - 1]] = value;
290
+ return result;
291
+ }
292
+
293
+ export {
294
+ handleSettings,
295
+ showSettingsInfo,
296
+ handleSettingsButton
297
+ };
298
+ //# sourceMappingURL=chunk-IER5UCY7.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../src/adapters/discord/commands/settings.ts"],"sourcesContent":["import {\n ActionRowBuilder,\n ButtonBuilder,\n ButtonStyle,\n} from 'discord.js'\nimport type { ChatInputCommandInteraction, ButtonInteraction } from 'discord.js'\nimport { log } from '../../../core/log.js'\nimport { getSafeFields, resolveOptions, getConfigValue, isHotReloadable, type ConfigFieldDef } from '../../../core/config-registry.js'\nimport type { DiscordAdapter } from '../adapter.js'\n\nfunction formatFieldLabel(field: ConfigFieldDef, value: unknown): string {\n const icons: Record<string, string> = {\n agent: '🤖', logging: '📝', tunnel: '🔗',\n security: '🔒', workspace: '📁', storage: '💾', speech: '🎤',\n }\n const icon = icons[field.group] ?? '⚙️'\n\n if (field.type === 'toggle') {\n return `${icon} ${field.displayName}: ${value ? 'ON' : 'OFF'}`\n }\n const displayValue = value === null || value === undefined ? 'Not set' : String(value)\n return `${icon} ${field.displayName}: ${displayValue}`\n}\n\nconst SETTINGS_PAGE_SIZE = 4 // 4 field rows + 1 navigation row = 5 max\n\nfunction buildSettingsRows(adapter: DiscordAdapter, page = 0): ActionRowBuilder<ButtonBuilder>[] {\n const config = adapter.core.configManager.get()\n const fields = getSafeFields()\n const totalPages = Math.ceil(fields.length / SETTINGS_PAGE_SIZE)\n const start = page * SETTINGS_PAGE_SIZE\n const pageFields = fields.slice(start, start + SETTINGS_PAGE_SIZE)\n\n const rows: ActionRowBuilder<ButtonBuilder>[] = []\n\n for (const field of pageFields) {\n const value = getConfigValue(config, field.path)\n const label = formatFieldLabel(field, value)\n\n let customId: string\n if (field.type === 'toggle') {\n customId = `s:toggle:${field.path}`\n } else if (field.type === 'select') {\n customId = `s:select:${field.path}`\n } else {\n customId = `s:input:${field.path}`\n }\n\n rows.push(\n new ActionRowBuilder<ButtonBuilder>().addComponents(\n new ButtonBuilder()\n .setCustomId(customId)\n .setLabel(label.slice(0, 80))\n .setStyle(ButtonStyle.Secondary),\n ),\n )\n }\n\n // Navigation row (if more than 1 page)\n if (totalPages > 1) {\n const navRow = new ActionRowBuilder<ButtonBuilder>()\n if (page > 0) {\n navRow.addComponents(\n new ButtonBuilder()\n .setCustomId(`s:page:${page - 1}`)\n .setLabel('◀️ Previous')\n .setStyle(ButtonStyle.Primary),\n )\n }\n navRow.addComponents(\n new ButtonBuilder()\n .setCustomId('s:pageinfo')\n .setLabel(`Page ${page + 1}/${totalPages}`)\n .setStyle(ButtonStyle.Secondary)\n .setDisabled(true),\n )\n if (page < totalPages - 1) {\n navRow.addComponents(\n new ButtonBuilder()\n .setCustomId(`s:page:${page + 1}`)\n .setLabel('Next ▶️')\n .setStyle(ButtonStyle.Primary),\n )\n }\n rows.push(navRow)\n }\n\n return rows\n}\n\nexport async function handleSettings(\n interaction: ChatInputCommandInteraction,\n adapter: DiscordAdapter,\n): Promise<void> {\n await interaction.deferReply({ ephemeral: true })\n\n const rows = buildSettingsRows(adapter)\n await interaction.editReply({\n content: '**⚙️ Settings**\\nTap to change:',\n components: rows,\n })\n}\n\nexport async function showSettingsInfo(\n interaction: ButtonInteraction,\n adapter: DiscordAdapter,\n): Promise<void> {\n const rows = buildSettingsRows(adapter)\n await interaction.followUp({\n content: '**⚙️ Settings**\\nTap to change:',\n components: rows,\n ephemeral: true,\n })\n}\n\nexport async function handleSettingsButton(\n interaction: ButtonInteraction,\n adapter: DiscordAdapter,\n): Promise<void> {\n const { customId } = interaction\n\n try {\n // Toggle buttons\n if (customId.startsWith('s:toggle:')) {\n const fieldPath = customId.replace('s:toggle:', '')\n const config = adapter.core.configManager.get()\n const currentValue = getConfigValue(config, fieldPath)\n const newValue = !currentValue\n\n const updates = buildNestedUpdate(fieldPath, newValue)\n await adapter.core.configManager.save(updates, fieldPath)\n\n const toast = isHotReloadable(fieldPath)\n ? `✅ ${fieldPath} = ${newValue}`\n : `✅ ${fieldPath} = ${newValue} (restart needed)`\n\n try {\n await interaction.update({\n content: '**⚙️ Settings**\\nTap to change:',\n components: buildSettingsRows(adapter),\n })\n } catch { /* ignore */ }\n\n try { await interaction.followUp({ content: toast, ephemeral: true }) } catch { /* ignore */ }\n return\n }\n\n // Select buttons — show options\n if (customId.startsWith('s:select:')) {\n const fieldPath = customId.replace('s:select:', '')\n const config = adapter.core.configManager.get()\n const fieldDef = getSafeFields().find((f) => f.path === fieldPath)\n if (!fieldDef) return\n\n const options = resolveOptions(fieldDef, config) ?? []\n const currentValue = getConfigValue(config, fieldPath)\n\n const rows: ActionRowBuilder<ButtonBuilder>[] = []\n let currentRow = new ActionRowBuilder<ButtonBuilder>()\n let count = 0\n\n for (const opt of options) {\n const marker = opt === String(currentValue) ? ' ✓' : ''\n currentRow.addComponents(\n new ButtonBuilder()\n .setCustomId(`s:pick:${fieldPath}:${opt}`)\n .setLabel(`${opt}${marker}`.slice(0, 80))\n .setStyle(opt === String(currentValue) ? ButtonStyle.Success : ButtonStyle.Secondary),\n )\n count++\n if (count % 3 === 0) {\n rows.push(currentRow)\n currentRow = new ActionRowBuilder<ButtonBuilder>()\n }\n }\n\n if (currentRow.components.length > 0) {\n rows.push(currentRow)\n }\n\n // Add back button\n const backRow = new ActionRowBuilder<ButtonBuilder>().addComponents(\n new ButtonBuilder()\n .setCustomId('s:back')\n .setLabel('◀️ Back')\n .setStyle(ButtonStyle.Primary),\n )\n rows.push(backRow)\n\n try {\n await interaction.update({\n content: `**⚙️ ${fieldDef.displayName}**\\nSelect a value:`,\n components: rows.slice(0, 5),\n })\n } catch { /* ignore */ }\n return\n }\n\n // Pick buttons — apply selected value\n if (customId.startsWith('s:pick:')) {\n const parts = customId.replace('s:pick:', '').split(':')\n const fieldPath = parts.slice(0, -1).join(':')\n const newValue = parts[parts.length - 1]\n\n // For speech.stt.provider: check if API key is configured\n if (fieldPath === 'speech.stt.provider') {\n const config = adapter.core.configManager.get()\n const providerConfig = config.speech?.stt?.providers?.[newValue]\n if (!providerConfig?.apiKey) {\n // No API key — delegate to assistant\n const assistantSessionId = adapter.getAssistantSessionId()\n if (assistantSessionId) {\n const assistantSession = adapter.core.sessionManager.getSession(assistantSessionId)\n if (assistantSession) {\n const prompt = `User wants to enable ${newValue} as Speech-to-Text provider, but no API key is configured yet. Guide them to get a ${newValue} API key and set it up. After they provide the key, run both commands: \\`openacp config set speech.stt.providers.${newValue}.apiKey <key>\\` and \\`openacp config set speech.stt.provider ${newValue}\\``\n await assistantSession.enqueuePrompt(prompt)\n\n try {\n await interaction.update({\n content: '**⚙️ Settings**\\nTap to change:',\n components: buildSettingsRows(adapter),\n })\n } catch { /* ignore */ }\n try { await interaction.followUp({ content: '🔑 API key needed — check the Assistant thread.', ephemeral: true }) } catch { /* ignore */ }\n return\n }\n }\n\n // No assistant — just warn\n try {\n await interaction.update({\n content: '**⚙️ Settings**\\nTap to change:',\n components: buildSettingsRows(adapter),\n })\n } catch { /* ignore */ }\n try { await interaction.followUp({ content: `⚠️ Set API key first: \\`openacp config set speech.stt.providers.${newValue}.apiKey <key>\\``, ephemeral: true }) } catch { /* ignore */ }\n return\n }\n }\n\n const updates = buildNestedUpdate(fieldPath, newValue)\n await adapter.core.configManager.save(updates, fieldPath)\n\n try {\n await interaction.update({\n content: '**⚙️ Settings**\\nTap to change:',\n components: buildSettingsRows(adapter),\n })\n } catch { /* ignore */ }\n try { await interaction.followUp({ content: `✅ ${fieldPath} = ${newValue}`, ephemeral: true }) } catch { /* ignore */ }\n return\n }\n\n // Input buttons — delegate to assistant\n if (customId.startsWith('s:input:')) {\n const fieldPath = customId.replace('s:input:', '')\n const config = adapter.core.configManager.get()\n const fieldDef = getSafeFields().find((f) => f.path === fieldPath)\n if (!fieldDef) return\n\n const currentValue = getConfigValue(config, fieldPath)\n const assistantSessionId = adapter.getAssistantSessionId()\n\n if (!assistantSessionId) {\n try { await interaction.reply({ content: '⚠️ Assistant is not available.', ephemeral: true }) } catch { /* ignore */ }\n return\n }\n\n const assistantSession = adapter.core.sessionManager.getSession(assistantSessionId)\n if (!assistantSession) {\n try { await interaction.reply({ content: '⚠️ Assistant session not found.', ephemeral: true }) } catch { /* ignore */ }\n return\n }\n\n try { await interaction.deferUpdate() } catch { /* ignore */ }\n\n const prompt = `User wants to change ${fieldDef.displayName} (config path: ${fieldPath}). Current value: ${JSON.stringify(currentValue)}. Ask them for the new value and apply it using: openacp config set ${fieldPath} <value>`\n await assistantSession.enqueuePrompt(prompt)\n\n try { await interaction.followUp({ content: `Delegating to assistant — check the Assistant thread.`, ephemeral: true }) } catch { /* ignore */ }\n return\n }\n\n // Page navigation\n if (customId.startsWith('s:page:')) {\n const page = parseInt(customId.replace('s:page:', ''), 10)\n try {\n await interaction.update({\n content: '**⚙️ Settings**\\nTap to change:',\n components: buildSettingsRows(adapter, page),\n })\n } catch { /* ignore */ }\n return\n }\n\n // Back button — return to page 0\n if (customId === 's:back') {\n try {\n await interaction.update({\n content: '**⚙️ Settings**\\nTap to change:',\n components: buildSettingsRows(adapter),\n })\n } catch { /* ignore */ }\n return\n }\n\n log.warn({ customId }, '[discord-settings] Unhandled settings button')\n } catch (err) {\n log.error({ err, customId }, '[discord-settings] Settings button handler failed')\n try {\n if (!interaction.replied && !interaction.deferred) {\n await interaction.reply({ content: '❌ Settings action failed.', ephemeral: true })\n } else {\n await interaction.followUp({ content: '❌ Settings action failed.', ephemeral: true })\n }\n } catch { /* ignore */ }\n }\n}\n\nfunction buildNestedUpdate(dotPath: string, value: unknown): Record<string, unknown> {\n const parts = dotPath.split('.')\n const result: Record<string, unknown> = {}\n let target = result\n for (let i = 0; i < parts.length - 1; i++) {\n target[parts[i]] = {}\n target = target[parts[i]] as Record<string, unknown>\n }\n target[parts[parts.length - 1]] = value\n return result\n}\n"],"mappings":"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","names":[]}
@@ -8,11 +8,6 @@ import {
8
8
  ButtonBuilder,
9
9
  ButtonStyle
10
10
  } from "discord.js";
11
- function buildDangerousModeKeyboard(sessionId, isDangerous) {
12
- return new ActionRowBuilder().addComponents(
13
- new ButtonBuilder().setCustomId(`d:${sessionId}`).setLabel(isDangerous ? "\u{1F510} Disable Dangerous Mode" : "\u2620\uFE0F Enable Dangerous Mode").setStyle(isDangerous ? ButtonStyle.Secondary : ButtonStyle.Danger)
14
- );
15
- }
16
11
  async function handleDangerous(interaction, adapter) {
17
12
  await interaction.deferReply({ ephemeral: true });
18
13
  const channelId = interaction.channelId;
@@ -49,7 +44,7 @@ async function handleDangerousButton(interaction, adapter) {
49
44
  const toastText2 = session.dangerousMode ? "\u2620\uFE0F Dangerous mode enabled \u2014 permissions auto-approved" : "\u{1F510} Dangerous mode disabled \u2014 permissions shown normally";
50
45
  try {
51
46
  await interaction.update({
52
- components: [buildDangerousModeKeyboard(sessionId, session.dangerousMode)]
47
+ components: [buildSessionControlKeyboard(sessionId, session.dangerousMode, session.voiceMode === "on")]
53
48
  });
54
49
  } catch {
55
50
  }
@@ -71,7 +66,54 @@ async function handleDangerousButton(interaction, adapter) {
71
66
  const toastText = newDangerousMode ? "\u2620\uFE0F Dangerous mode enabled \u2014 permissions auto-approved" : "\u{1F510} Dangerous mode disabled \u2014 permissions shown normally";
72
67
  try {
73
68
  await interaction.update({
74
- components: [buildDangerousModeKeyboard(sessionId, newDangerousMode)]
69
+ components: [buildSessionControlKeyboard(sessionId, newDangerousMode, false)]
70
+ });
71
+ } catch {
72
+ }
73
+ try {
74
+ await interaction.followUp({ content: toastText, ephemeral: true });
75
+ } catch {
76
+ }
77
+ }
78
+ function buildSessionControlKeyboard(sessionId, dangerousMode, voiceMode) {
79
+ return new ActionRowBuilder().addComponents(
80
+ new ButtonBuilder().setCustomId(`d:${sessionId}`).setLabel(dangerousMode ? "\u{1F510} Disable Dangerous Mode" : "\u2620\uFE0F Enable Dangerous Mode").setStyle(dangerousMode ? ButtonStyle.Secondary : ButtonStyle.Danger),
81
+ new ButtonBuilder().setCustomId(`v:${sessionId}`).setLabel(voiceMode ? "\u{1F50A} Text to Speech" : "\u{1F507} Text to Speech").setStyle(voiceMode ? ButtonStyle.Success : ButtonStyle.Secondary)
82
+ );
83
+ }
84
+ async function handleTTS(interaction, adapter) {
85
+ await interaction.deferReply({ ephemeral: true });
86
+ const channelId = interaction.channelId;
87
+ const session = adapter.core.sessionManager.getSessionByThread("discord", channelId);
88
+ if (!session) {
89
+ await interaction.editReply("\u26A0\uFE0F No active session in this channel.");
90
+ return;
91
+ }
92
+ const mode = interaction.options.getString("mode");
93
+ if (mode === "on") {
94
+ session.setVoiceMode("on");
95
+ await interaction.editReply("\u{1F50A} Text to Speech enabled for this session.");
96
+ } else if (mode === "off") {
97
+ session.setVoiceMode("off");
98
+ await interaction.editReply("\u{1F507} Text to Speech disabled.");
99
+ } else {
100
+ session.setVoiceMode("next");
101
+ await interaction.editReply("\u{1F50A} Text to Speech enabled for the next message.");
102
+ }
103
+ }
104
+ async function handleTTSButton(interaction, adapter) {
105
+ const sessionId = interaction.customId.slice(2);
106
+ const session = adapter.core.sessionManager.getSession(sessionId);
107
+ if (!session) {
108
+ await interaction.reply({ content: "\u26A0\uFE0F Session not found or not active.", ephemeral: true });
109
+ return;
110
+ }
111
+ const newMode = session.voiceMode === "on" ? "off" : "on";
112
+ session.setVoiceMode(newMode);
113
+ const toastText = newMode === "on" ? "\u{1F50A} Text to Speech enabled" : "\u{1F507} Text to Speech disabled";
114
+ try {
115
+ await interaction.update({
116
+ components: [buildSessionControlKeyboard(sessionId, session.dangerousMode, newMode === "on")]
75
117
  });
76
118
  } catch {
77
119
  }
@@ -96,10 +138,12 @@ async function handleUpdate(interaction, adapter) {
96
138
  }
97
139
 
98
140
  export {
99
- buildDangerousModeKeyboard,
100
141
  handleDangerous,
101
142
  handleDangerousButton,
143
+ buildSessionControlKeyboard,
144
+ handleTTS,
145
+ handleTTSButton,
102
146
  handleRestart,
103
147
  handleUpdate
104
148
  };
105
- //# sourceMappingURL=chunk-IUIMBEBX.js.map
149
+ //# sourceMappingURL=chunk-NXEQXRQR.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../src/adapters/discord/commands/admin.ts"],"sourcesContent":["import {\n ActionRowBuilder,\n ButtonBuilder,\n ButtonStyle,\n} from 'discord.js'\nimport type { ChatInputCommandInteraction, ButtonInteraction } from 'discord.js'\nimport { log } from '../../../core/log.js'\nimport type { DiscordAdapter } from '../adapter.js'\n\nexport async function handleDangerous(\n interaction: ChatInputCommandInteraction,\n adapter: DiscordAdapter,\n): Promise<void> {\n await interaction.deferReply({ ephemeral: true })\n\n const channelId = interaction.channelId\n const session = adapter.core.sessionManager.getSessionByThread('discord', channelId)\n\n if (session) {\n session.dangerousMode = !session.dangerousMode\n adapter.core.sessionManager.patchRecord(session.id, { dangerousMode: session.dangerousMode }).catch(() => {})\n log.info({ sessionId: session.id, dangerousMode: session.dangerousMode }, '[discord-admin] Dangerous mode toggled via command')\n\n const msg = session.dangerousMode\n ? '☠️ **Dangerous mode enabled** — All permission requests will be auto-approved.'\n : '🔐 **Dangerous mode disabled** — Permission requests will be shown normally.'\n await interaction.editReply(msg)\n return\n }\n\n // Session not in memory — update store directly\n const record = adapter.core.sessionManager.getRecordByThread('discord', channelId)\n if (!record || record.status === 'cancelled' || record.status === 'error') {\n await interaction.editReply('⚠️ No active session in this channel.')\n return\n }\n\n const newDangerousMode = !(record.dangerousMode ?? false)\n adapter.core.sessionManager.patchRecord(record.sessionId, { dangerousMode: newDangerousMode }).catch(() => {})\n log.info({ sessionId: record.sessionId, dangerousMode: newDangerousMode }, '[discord-admin] Dangerous mode toggled via command (store-only)')\n\n const msg = newDangerousMode\n ? '☠️ **Dangerous mode enabled** — All permission requests will be auto-approved.'\n : '🔐 **Dangerous mode disabled** — Permission requests will be shown normally.'\n await interaction.editReply(msg)\n}\n\nexport async function handleDangerousButton(\n interaction: ButtonInteraction,\n adapter: DiscordAdapter,\n): Promise<void> {\n const sessionId = interaction.customId.slice(2) // strip 'd:'\n const session = adapter.core.sessionManager.getSession(sessionId)\n\n // Session live in memory — toggle directly\n if (session) {\n session.dangerousMode = !session.dangerousMode\n adapter.core.sessionManager.patchRecord(sessionId, { dangerousMode: session.dangerousMode }).catch(() => {})\n log.info({ sessionId, dangerousMode: session.dangerousMode }, '[discord-admin] Dangerous mode toggled via button')\n\n const toastText = session.dangerousMode\n ? '☠️ Dangerous mode enabled — permissions auto-approved'\n : '🔐 Dangerous mode disabled — permissions shown normally'\n\n try {\n await interaction.update({\n components: [buildSessionControlKeyboard(sessionId, session.dangerousMode, session.voiceMode === 'on')],\n })\n } catch { /* ignore */ }\n\n try { await interaction.followUp({ content: toastText, ephemeral: true }) } catch { /* ignore */ }\n return\n }\n\n // Session not in memory — toggle in store\n const record = adapter.core.sessionManager.getSessionRecord(sessionId)\n if (!record || record.status === 'cancelled' || record.status === 'error') {\n await interaction.reply({ content: '⚠️ Session not found or already ended.', ephemeral: true })\n return\n }\n\n const newDangerousMode = !(record.dangerousMode ?? false)\n adapter.core.sessionManager.patchRecord(sessionId, { dangerousMode: newDangerousMode }).catch(() => {})\n log.info({ sessionId, dangerousMode: newDangerousMode }, '[discord-admin] Dangerous mode toggled via button (store-only)')\n\n const toastText = newDangerousMode\n ? '☠️ Dangerous mode enabled — permissions auto-approved'\n : '🔐 Dangerous mode disabled — permissions shown normally'\n\n try {\n // Store-only path: voiceMode unknown, default to off\n await interaction.update({\n components: [buildSessionControlKeyboard(sessionId, newDangerousMode, false)],\n })\n } catch { /* ignore */ }\n\n try { await interaction.followUp({ content: toastText, ephemeral: true }) } catch { /* ignore */ }\n}\n\n// ─── TTS ──────────────────────────────────────────────────────────────────────\n\nexport function buildSessionControlKeyboard(\n sessionId: string,\n dangerousMode: boolean,\n voiceMode: boolean,\n): ActionRowBuilder<ButtonBuilder> {\n return new ActionRowBuilder<ButtonBuilder>().addComponents(\n new ButtonBuilder()\n .setCustomId(`d:${sessionId}`)\n .setLabel(dangerousMode ? '🔐 Disable Dangerous Mode' : '☠️ Enable Dangerous Mode')\n .setStyle(dangerousMode ? ButtonStyle.Secondary : ButtonStyle.Danger),\n new ButtonBuilder()\n .setCustomId(`v:${sessionId}`)\n .setLabel(voiceMode ? '🔊 Text to Speech' : '🔇 Text to Speech')\n .setStyle(voiceMode ? ButtonStyle.Success : ButtonStyle.Secondary),\n )\n}\n\nexport async function handleTTS(\n interaction: ChatInputCommandInteraction,\n adapter: DiscordAdapter,\n): Promise<void> {\n await interaction.deferReply({ ephemeral: true })\n\n const channelId = interaction.channelId\n const session = adapter.core.sessionManager.getSessionByThread('discord', channelId)\n\n if (!session) {\n await interaction.editReply('⚠️ No active session in this channel.')\n return\n }\n\n const mode = interaction.options.getString('mode')\n\n if (mode === 'on') {\n session.setVoiceMode('on')\n await interaction.editReply('🔊 Text to Speech enabled for this session.')\n } else if (mode === 'off') {\n session.setVoiceMode('off')\n await interaction.editReply('🔇 Text to Speech disabled.')\n } else {\n session.setVoiceMode('next')\n await interaction.editReply('🔊 Text to Speech enabled for the next message.')\n }\n}\n\nexport async function handleTTSButton(\n interaction: ButtonInteraction,\n adapter: DiscordAdapter,\n): Promise<void> {\n const sessionId = interaction.customId.slice(2) // strip 'v:'\n const session = adapter.core.sessionManager.getSession(sessionId)\n\n if (!session) {\n await interaction.reply({ content: '⚠️ Session not found or not active.', ephemeral: true })\n return\n }\n\n const newMode = session.voiceMode === 'on' ? 'off' : 'on'\n session.setVoiceMode(newMode)\n\n const toastText = newMode === 'on'\n ? '🔊 Text to Speech enabled'\n : '🔇 Text to Speech disabled'\n\n try {\n await interaction.update({\n components: [buildSessionControlKeyboard(sessionId, session.dangerousMode, newMode === 'on')],\n })\n } catch { /* ignore */ }\n\n try { await interaction.followUp({ content: toastText, ephemeral: true }) } catch { /* ignore */ }\n}\n\nexport async function handleRestart(\n interaction: ChatInputCommandInteraction,\n adapter: DiscordAdapter,\n): Promise<void> {\n await interaction.deferReply({ ephemeral: true })\n\n if (!adapter.core.requestRestart) {\n await interaction.editReply('⚠️ Restart is not available (no restart handler registered).')\n return\n }\n\n await interaction.editReply('🔄 **Restarting OpenACP...**\\nRebuilding and restarting. Be back shortly.')\n await new Promise((r) => setTimeout(r, 500))\n await adapter.core.requestRestart()\n}\n\nexport async function handleUpdate(\n interaction: ChatInputCommandInteraction,\n adapter: DiscordAdapter,\n): Promise<void> {\n await interaction.deferReply({ ephemeral: true })\n // Stub: not implemented yet\n await interaction.editReply('⚠️ Update via Discord is not implemented yet. Run `npm install -g @openacp/cli@latest` in your terminal, then use `/restart`.')\n}\n"],"mappings":"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","names":["msg","toastText"]}
@@ -605,7 +605,7 @@ var tunnelCheck = {
605
605
  fixRisk: "safe",
606
606
  fix: async () => {
607
607
  try {
608
- const { ensureCloudflared } = await import("./install-cloudflared-BTGUD7SW.js");
608
+ const { ensureCloudflared } = await import("./install-cloudflared-G2GUKCHA.js");
609
609
  await ensureCloudflared();
610
610
  return { success: true, message: "installed cloudflared" };
611
611
  } catch (err) {
@@ -721,4 +721,4 @@ var DoctorEngine = class {
721
721
  export {
722
722
  DoctorEngine
723
723
  };
724
- //# sourceMappingURL=chunk-WQZ4RXH7.js.map
724
+ //# sourceMappingURL=chunk-OHR6SBMC.js.map
@@ -0,0 +1,101 @@
1
+ import {
2
+ commandExists
3
+ } from "./chunk-JKBFUAJK.js";
4
+ import {
5
+ createChildLogger
6
+ } from "./chunk-ESOPMQAY.js";
7
+
8
+ // src/core/install-binary.ts
9
+ import fs from "fs";
10
+ import path from "path";
11
+ import https from "https";
12
+ import os from "os";
13
+ import { execSync } from "child_process";
14
+ var log = createChildLogger({ module: "binary-installer" });
15
+ var BIN_DIR = path.join(os.homedir(), ".openacp", "bin");
16
+ var IS_WINDOWS = os.platform() === "win32";
17
+ function downloadFile(url, dest) {
18
+ return new Promise((resolve, reject) => {
19
+ const file = fs.createWriteStream(dest);
20
+ const cleanup = () => {
21
+ try {
22
+ if (fs.existsSync(dest)) fs.unlinkSync(dest);
23
+ } catch {
24
+ }
25
+ };
26
+ https.get(url, (response) => {
27
+ if (response.statusCode === 301 || response.statusCode === 302) {
28
+ file.close(() => {
29
+ cleanup();
30
+ downloadFile(response.headers.location, dest).then(resolve).catch(reject);
31
+ });
32
+ return;
33
+ }
34
+ if (response.statusCode !== 200) {
35
+ file.close(() => {
36
+ cleanup();
37
+ reject(new Error(`Download failed with status ${response.statusCode}`));
38
+ });
39
+ return;
40
+ }
41
+ response.pipe(file);
42
+ file.on("finish", () => file.close(() => resolve(dest)));
43
+ file.on("error", (err) => {
44
+ file.close(() => {
45
+ cleanup();
46
+ reject(err);
47
+ });
48
+ });
49
+ }).on("error", (err) => {
50
+ file.close(() => {
51
+ cleanup();
52
+ reject(err);
53
+ });
54
+ });
55
+ });
56
+ }
57
+ function getDownloadUrl(spec) {
58
+ const platform = os.platform();
59
+ const arch = os.arch();
60
+ const mapping = spec.platforms[platform];
61
+ if (!mapping) throw new Error(`${spec.name}: unsupported platform ${platform}`);
62
+ const binary = mapping[arch];
63
+ if (!binary) throw new Error(`${spec.name}: unsupported architecture ${arch} for ${platform}`);
64
+ return `${spec.githubBaseUrl}/${binary}`;
65
+ }
66
+ async function ensureBinary(spec) {
67
+ const binName = IS_WINDOWS ? `${spec.name}.exe` : spec.name;
68
+ const binPath = path.join(BIN_DIR, binName);
69
+ if (commandExists(spec.name)) {
70
+ log.debug({ name: spec.name }, "Found in PATH");
71
+ return spec.name;
72
+ }
73
+ if (fs.existsSync(binPath)) {
74
+ if (!IS_WINDOWS) fs.chmodSync(binPath, "755");
75
+ log.debug({ name: spec.name, path: binPath }, "Found in ~/.openacp/bin");
76
+ return binPath;
77
+ }
78
+ log.info({ name: spec.name }, "Not found, downloading from GitHub...");
79
+ fs.mkdirSync(BIN_DIR, { recursive: true });
80
+ const url = getDownloadUrl(spec);
81
+ const isArchive = spec.isArchive?.(url) ?? false;
82
+ const downloadDest = isArchive ? path.join(BIN_DIR, `${spec.name}.tgz`) : binPath;
83
+ await downloadFile(url, downloadDest);
84
+ if (isArchive) {
85
+ execSync(`tar -xzf "${downloadDest}" -C "${BIN_DIR}"`, { stdio: "pipe" });
86
+ try {
87
+ fs.unlinkSync(downloadDest);
88
+ } catch {
89
+ }
90
+ }
91
+ if (!IS_WINDOWS) {
92
+ fs.chmodSync(binPath, "755");
93
+ }
94
+ log.info({ name: spec.name, path: binPath }, "Installed successfully");
95
+ return binPath;
96
+ }
97
+
98
+ export {
99
+ ensureBinary
100
+ };
101
+ //# sourceMappingURL=chunk-PWFPTG5X.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../src/core/install-binary.ts"],"sourcesContent":["import fs from 'node:fs'\nimport path from 'node:path'\nimport https from 'node:https'\nimport os from 'node:os'\nimport { execSync } from 'node:child_process'\nimport { createChildLogger } from './log.js'\nimport { commandExists } from './agent-dependencies.js'\n\nconst log = createChildLogger({ module: 'binary-installer' })\n\nconst BIN_DIR = path.join(os.homedir(), '.openacp', 'bin')\nconst IS_WINDOWS = os.platform() === 'win32'\n\nexport interface BinarySpec {\n name: string\n /** GitHub base URL for releases, e.g. \"https://github.com/jqlang/jq/releases/latest/download\" */\n githubBaseUrl: string\n /** Platform → arch → filename mapping */\n platforms: Record<string, Record<string, string>>\n /** If true, downloaded file is a .tgz archive that needs extraction */\n isArchive?: (url: string) => boolean\n}\n\nfunction downloadFile(url: string, dest: string): Promise<string> {\n return new Promise((resolve, reject) => {\n const file = fs.createWriteStream(dest)\n\n const cleanup = () => {\n try { if (fs.existsSync(dest)) fs.unlinkSync(dest) } catch { /* ignore */ }\n }\n\n https.get(url, (response) => {\n if (response.statusCode === 301 || response.statusCode === 302) {\n file.close(() => {\n cleanup()\n downloadFile(response.headers.location!, dest).then(resolve).catch(reject)\n })\n return\n }\n\n if (response.statusCode !== 200) {\n file.close(() => {\n cleanup()\n reject(new Error(`Download failed with status ${response.statusCode}`))\n })\n return\n }\n\n response.pipe(file)\n file.on('finish', () => file.close(() => resolve(dest)))\n file.on('error', (err) => {\n file.close(() => {\n cleanup()\n reject(err)\n })\n })\n }).on('error', (err) => {\n file.close(() => {\n cleanup()\n reject(err)\n })\n })\n })\n}\n\nfunction getDownloadUrl(spec: BinarySpec): string {\n const platform = os.platform()\n const arch = os.arch()\n const mapping = spec.platforms[platform]\n if (!mapping) throw new Error(`${spec.name}: unsupported platform ${platform}`)\n const binary = mapping[arch]\n if (!binary) throw new Error(`${spec.name}: unsupported architecture ${arch} for ${platform}`)\n return `${spec.githubBaseUrl}/${binary}`\n}\n\n/**\n * Ensure a binary is available.\n * 1. Check PATH first (respects user's system install)\n * 2. Check ~/.openacp/bin/\n * 3. Download from GitHub releases\n */\nexport async function ensureBinary(spec: BinarySpec): Promise<string> {\n const binName = IS_WINDOWS ? `${spec.name}.exe` : spec.name\n const binPath = path.join(BIN_DIR, binName)\n\n // 1. Check PATH first\n if (commandExists(spec.name)) {\n log.debug({ name: spec.name }, 'Found in PATH')\n return spec.name\n }\n\n // 2. Check our bin directory\n if (fs.existsSync(binPath)) {\n if (!IS_WINDOWS) fs.chmodSync(binPath, '755')\n log.debug({ name: spec.name, path: binPath }, 'Found in ~/.openacp/bin')\n return binPath\n }\n\n // 3. Download\n log.info({ name: spec.name }, 'Not found, downloading from GitHub...')\n fs.mkdirSync(BIN_DIR, { recursive: true })\n\n const url = getDownloadUrl(spec)\n const isArchive = spec.isArchive?.(url) ?? false\n const downloadDest = isArchive ? path.join(BIN_DIR, `${spec.name}.tgz`) : binPath\n\n await downloadFile(url, downloadDest)\n\n if (isArchive) {\n execSync(`tar -xzf \"${downloadDest}\" -C \"${BIN_DIR}\"`, { stdio: 'pipe' })\n try { fs.unlinkSync(downloadDest) } catch { /* ignore */ }\n }\n\n if (!IS_WINDOWS) {\n fs.chmodSync(binPath, '755')\n }\n\n log.info({ name: spec.name, path: binPath }, 'Installed successfully')\n return binPath\n}\n"],"mappings":"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","names":[]}
package/dist/cli.js CHANGED
@@ -961,7 +961,7 @@ the API for live updates. When stopped, edits config file directly.
961
961
  }
962
962
  return;
963
963
  }
964
- const { runConfigEditor } = await import("./config-editor-TOZUBMO7.js");
964
+ const { runConfigEditor } = await import("./config-editor-F25HEMGL.js");
965
965
  const { ConfigManager } = await import("./config-4YSJ4NCI.js");
966
966
  const cm = new ConfigManager();
967
967
  if (!await cm.exists()) {
@@ -1151,7 +1151,7 @@ a "Handoff" slash command to Claude Code.
1151
1151
  `);
1152
1152
  return;
1153
1153
  }
1154
- const { getIntegration, listIntegrations } = await import("./integrate-QTK4PPYQ.js");
1154
+ const { getIntegration, listIntegrations } = await import("./integrate-O4OCR4SN.js");
1155
1155
  const agent = args2[1];
1156
1156
  const uninstall = args2.includes("--uninstall");
1157
1157
  if (!agent) {
@@ -1225,7 +1225,7 @@ Fixable issues can be auto-repaired when not using --dry-run.
1225
1225
  process.exit(1);
1226
1226
  }
1227
1227
  const dryRun = args2.includes("--dry-run");
1228
- const { DoctorEngine } = await import("./doctor-UOH7YCT2.js");
1228
+ const { DoctorEngine } = await import("./doctor-Y3SCSVPI.js");
1229
1229
  const engine = new DoctorEngine({ dryRun });
1230
1230
  console.log("\n\u{1FA7A} OpenACP Doctor\n");
1231
1231
  const report = await engine.runAll();
@@ -1521,7 +1521,7 @@ Run 'openacp agents' to see available agents.
1521
1521
  const { getAgentCapabilities } = await import("./agent-dependencies-QY5QSULV.js");
1522
1522
  const caps = getAgentCapabilities(result.agentKey);
1523
1523
  if (caps.integration) {
1524
- const { installIntegration } = await import("./integrate-QTK4PPYQ.js");
1524
+ const { installIntegration } = await import("./integrate-O4OCR4SN.js");
1525
1525
  const intResult = await installIntegration(result.agentKey, caps.integration);
1526
1526
  if (intResult.success) {
1527
1527
  console.log(` \x1B[32m\u2713\x1B[0m Handoff integration installed for ${result.agentKey}`);
@@ -1563,7 +1563,7 @@ async function agentsUninstall(name, help = false) {
1563
1563
  const { getAgentCapabilities } = await import("./agent-dependencies-QY5QSULV.js");
1564
1564
  const caps = getAgentCapabilities(name);
1565
1565
  if (caps.integration) {
1566
- const { uninstallIntegration } = await import("./integrate-QTK4PPYQ.js");
1566
+ const { uninstallIntegration } = await import("./integrate-O4OCR4SN.js");
1567
1567
  await uninstallIntegration(name, caps.integration);
1568
1568
  console.log(` \x1B[32m\u2713\x1B[0m Handoff integration removed for ${name}`);
1569
1569
  }
@@ -1770,7 +1770,7 @@ async function cmdDefault(command2) {
1770
1770
  const { ConfigManager } = await import("./config-4YSJ4NCI.js");
1771
1771
  const cm = new ConfigManager();
1772
1772
  if (!await cm.exists()) {
1773
- const { runSetup } = await import("./setup-DZXZTQRD.js");
1773
+ const { runSetup } = await import("./setup-7YBFKRG7.js");
1774
1774
  const shouldStart = await runSetup(cm);
1775
1775
  if (!shouldStart) process.exit(0);
1776
1776
  }
@@ -1788,7 +1788,7 @@ async function cmdDefault(command2) {
1788
1788
  }
1789
1789
  const { markRunning } = await import("./daemon-I6XMRQ6P.js");
1790
1790
  markRunning();
1791
- const { startServer } = await import("./main-46BVXFWI.js");
1791
+ const { startServer } = await import("./main-P4X6SAPZ.js");
1792
1792
  await startServer();
1793
1793
  }
1794
1794
 
@@ -1818,7 +1818,7 @@ var commands = {
1818
1818
  "agents": () => cmdAgents(args),
1819
1819
  "tunnel": () => cmdTunnel(args),
1820
1820
  "--daemon-child": async () => {
1821
- const { startServer } = await import("./main-46BVXFWI.js");
1821
+ const { startServer } = await import("./main-P4X6SAPZ.js");
1822
1822
  await startServer();
1823
1823
  }
1824
1824
  };