@openacp/cli 0.6.9 → 0.6.10

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.
@@ -5,16 +5,24 @@ import {
5
5
  expandHome
6
6
  } from "./chunk-33RP6K2O.js";
7
7
 
8
- // src/core/setup.ts
9
- import { execFileSync } from "child_process";
8
+ // src/core/setup/wizard.ts
9
+ import * as clack9 from "@clack/prompts";
10
+
11
+ // src/core/setup/types.ts
12
+ var ONBOARD_SECTION_OPTIONS = [
13
+ { value: "channels", label: "Channels", hint: "Link/update messaging platforms" },
14
+ { value: "agents", label: "Agents", hint: "Install agents, change default" },
15
+ { value: "workspace", label: "Workspace", hint: "Set workspace directory" },
16
+ { value: "runMode", label: "Run mode", hint: "Foreground/daemon, auto-start" },
17
+ { value: "integrations", label: "Integrations", hint: "Claude CLI session transfer" }
18
+ ];
19
+ var CHANNEL_META = {
20
+ telegram: { label: "Telegram", method: "Bot API" },
21
+ discord: { label: "Discord", method: "Bot API" }
22
+ };
23
+
24
+ // src/core/setup/helpers.ts
10
25
  import * as clack from "@clack/prompts";
11
- function guardCancel(value) {
12
- if (clack.isCancel(value)) {
13
- clack.cancel("Setup cancelled.");
14
- process.exit(0);
15
- }
16
- return value;
17
- }
18
26
  var c = {
19
27
  reset: "\x1B[0m",
20
28
  bold: "\x1B[1m",
@@ -32,6 +40,67 @@ var step = (n, total, title) => `
32
40
  ${c.cyan}${c.bold}[${n}/${total}]${c.reset} ${c.bold}${title}${c.reset}
33
41
  `;
34
42
  var dim = (msg) => `${c.dim}${msg}${c.reset}`;
43
+ function guardCancel(value) {
44
+ if (clack.isCancel(value)) {
45
+ clack.cancel("Setup cancelled.");
46
+ process.exit(0);
47
+ }
48
+ return value;
49
+ }
50
+ function applyGradient(text4) {
51
+ const colors = [135, 99, 63, 33, 39, 44, 44];
52
+ const lines = text4.split("\n");
53
+ return lines.map((line, i) => {
54
+ const colorIdx = Math.min(i, colors.length - 1);
55
+ return `\x1B[38;5;${colors[colorIdx]}m${line}\x1B[0m`;
56
+ }).join("\n");
57
+ }
58
+ var BANNER = `
59
+ \u2588\u2588\u2588\u2588\u2588\u2588\u2557 \u2588\u2588\u2588\u2588\u2588\u2588\u2557 \u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2557\u2588\u2588\u2588\u2557 \u2588\u2588\u2557 \u2588\u2588\u2588\u2588\u2588\u2557 \u2588\u2588\u2588\u2588\u2588\u2588\u2557\u2588\u2588\u2588\u2588\u2588\u2588\u2557
60
+ \u2588\u2588\u2554\u2550\u2550\u2550\u2588\u2588\u2557\u2588\u2588\u2554\u2550\u2550\u2588\u2588\u2557\u2588\u2588\u2554\u2550\u2550\u2550\u2550\u255D\u2588\u2588\u2588\u2588\u2557 \u2588\u2588\u2551\u2588\u2588\u2554\u2550\u2550\u2588\u2588\u2557\u2588\u2588\u2554\u2550\u2550\u2550\u2550\u255D\u2588\u2588\u2554\u2550\u2550\u2588\u2588\u2557
61
+ \u2588\u2588\u2551 \u2588\u2588\u2551\u2588\u2588\u2588\u2588\u2588\u2588\u2554\u255D\u2588\u2588\u2588\u2588\u2588\u2557 \u2588\u2588\u2554\u2588\u2588\u2557 \u2588\u2588\u2551\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2551\u2588\u2588\u2551 \u2588\u2588\u2588\u2588\u2588\u2588\u2554\u255D
62
+ \u2588\u2588\u2551 \u2588\u2588\u2551\u2588\u2588\u2554\u2550\u2550\u2550\u255D \u2588\u2588\u2554\u2550\u2550\u255D \u2588\u2588\u2551\u255A\u2588\u2588\u2557\u2588\u2588\u2551\u2588\u2588\u2554\u2550\u2550\u2588\u2588\u2551\u2588\u2588\u2551 \u2588\u2588\u2554\u2550\u2550\u2550\u255D
63
+ \u255A\u2588\u2588\u2588\u2588\u2588\u2588\u2554\u255D\u2588\u2588\u2551 \u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2557\u2588\u2588\u2551 \u255A\u2588\u2588\u2588\u2588\u2551\u2588\u2588\u2551 \u2588\u2588\u2551\u255A\u2588\u2588\u2588\u2588\u2588\u2588\u2557\u2588\u2588\u2551
64
+ \u255A\u2550\u2550\u2550\u2550\u2550\u255D \u255A\u2550\u255D \u255A\u2550\u2550\u2550\u2550\u2550\u2550\u255D\u255A\u2550\u255D \u255A\u2550\u2550\u2550\u255D\u255A\u2550\u255D \u255A\u2550\u255D \u255A\u2550\u2550\u2550\u2550\u2550\u255D\u255A\u2550\u255D
65
+ `;
66
+ async function printStartBanner() {
67
+ let version = "0.0.0";
68
+ try {
69
+ const { getCurrentVersion } = await import("./version-NQZBM5M7.js");
70
+ version = getCurrentVersion();
71
+ } catch {
72
+ }
73
+ console.log(applyGradient(BANNER));
74
+ console.log(`${c.dim} AI coding agents, anywhere. v${version}${c.reset}
75
+ `);
76
+ }
77
+ function summarizeConfig(config) {
78
+ const lines = [];
79
+ const channelStatuses = [];
80
+ for (const [id, meta] of Object.entries({
81
+ telegram: "Telegram",
82
+ discord: "Discord"
83
+ })) {
84
+ const ch = config.channels[id];
85
+ if (ch?.enabled) {
86
+ channelStatuses.push(`${meta} (enabled)`);
87
+ } else if (ch && Object.keys(ch).length > 1) {
88
+ channelStatuses.push(`${meta} (disabled)`);
89
+ } else {
90
+ channelStatuses.push(`${meta} (not configured)`);
91
+ }
92
+ }
93
+ lines.push(`Channels: ${channelStatuses.join(", ")}`);
94
+ lines.push(`Default agent: ${config.defaultAgent}`);
95
+ lines.push(`Workspace: ${config.workspace.baseDir}`);
96
+ lines.push(`Run mode: ${config.runMode}${config.autoStart ? " (auto-start)" : ""}`);
97
+ return lines.join("\n");
98
+ }
99
+
100
+ // src/core/setup/setup-telegram.ts
101
+ import * as clack2 from "@clack/prompts";
102
+
103
+ // src/core/setup/validation.ts
35
104
  async function validateBotToken(token) {
36
105
  try {
37
106
  const res = await fetch(`https://api.telegram.org/bot${token}/getMe`);
@@ -108,9 +177,28 @@ async function validateBotAdmin(token, chatId) {
108
177
  return { ok: false, error: err.message };
109
178
  }
110
179
  }
180
+ async function validateDiscordToken(token) {
181
+ try {
182
+ const res = await fetch("https://discord.com/api/v10/users/@me", {
183
+ headers: { Authorization: `Bot ${token}` }
184
+ });
185
+ if (res.status === 200) {
186
+ const data = await res.json();
187
+ return { ok: true, username: data.username, id: data.id };
188
+ }
189
+ if (res.status === 401) {
190
+ return { ok: false, error: "Token rejected by Discord (401 Unauthorized)" };
191
+ }
192
+ return { ok: false, error: `Discord API returned ${res.status}` };
193
+ } catch (err) {
194
+ return { ok: false, error: err.message };
195
+ }
196
+ }
197
+
198
+ // src/core/setup/setup-telegram.ts
111
199
  async function promptManualChatId() {
112
200
  const val = guardCancel(
113
- await clack.text({
201
+ await clack2.text({
114
202
  message: "Supergroup chat ID (e.g. -1001234567890):",
115
203
  validate: (val2) => {
116
204
  const n = Number((val2 ?? "").toString().trim());
@@ -206,7 +294,7 @@ async function detectChatId(token) {
206
294
  value: id
207
295
  }));
208
296
  return guardCancel(
209
- await clack.select({
297
+ await clack2.select({
210
298
  message: "Multiple groups found. Pick one:",
211
299
  options
212
300
  })
@@ -224,47 +312,69 @@ async function detectChatId(token) {
224
312
  throw err;
225
313
  }
226
314
  }
227
- var KNOWN_AGENTS = [
228
- // claude-agent-acp is bundled as a dependency — no detection needed, but
229
- // kept here so detectAgents() still returns it for display purposes.
230
- { name: "claude", commands: ["claude-agent-acp"] },
231
- { name: "codex", commands: ["codex"] }
232
- ];
233
- async function detectAgents() {
234
- const found = [];
235
- for (const agent of KNOWN_AGENTS) {
236
- const available = [];
237
- for (const cmd of agent.commands) {
238
- if (commandExists(cmd)) {
239
- available.push(cmd);
240
- }
315
+ async function detectAndValidateChatId(botToken) {
316
+ while (true) {
317
+ const chatId = await detectChatId(botToken);
318
+ const chatResult = await validateChatId(botToken, chatId);
319
+ if (!chatResult.ok) {
320
+ console.log(fail(chatResult.error));
321
+ console.log("");
322
+ console.log(` ${c.bold}How to fix:${c.reset}`);
323
+ console.log(dim(" 1. Make sure the bot is added to the group"));
324
+ console.log(dim(" 2. The group must be a Supergroup (Group Settings \u2192 convert)"));
325
+ console.log(dim(" 3. Send a message in the group after adding the bot"));
326
+ console.log("");
327
+ guardCancel(await clack2.text({ message: "Press Enter to try again..." }));
328
+ continue;
241
329
  }
242
- if (available.length > 0) {
243
- found.push({ name: agent.name, command: available[0] });
330
+ console.log(
331
+ ok(
332
+ `Group: ${c.bold}${chatResult.title}${c.reset}${c.green}${chatResult.isForum ? " (Topics enabled)" : ""}`
333
+ )
334
+ );
335
+ const adminResult = await validateBotAdmin(botToken, chatId);
336
+ if (!adminResult.ok) {
337
+ console.log(fail(adminResult.error));
338
+ console.log("");
339
+ console.log(` ${c.bold}How to fix:${c.reset}`);
340
+ console.log(dim(" 1. Open the group in Telegram"));
341
+ console.log(dim(" 2. Go to Group Settings \u2192 Administrators"));
342
+ console.log(dim(" 3. Add the bot as an administrator"));
343
+ console.log("");
344
+ guardCancel(await clack2.text({ message: "Press Enter to check again..." }));
345
+ continue;
244
346
  }
347
+ console.log(ok("Bot has admin privileges"));
348
+ return chatId;
245
349
  }
246
- return found;
247
350
  }
248
- async function validateAgentCommand(command) {
249
- try {
250
- execFileSync("which", [command], { stdio: "pipe" });
251
- return true;
252
- } catch {
253
- return false;
351
+ async function setupTelegram(opts) {
352
+ const { existing, stepNum, totalSteps } = opts ?? {};
353
+ if (stepNum != null && totalSteps != null) {
354
+ console.log(step(stepNum, totalSteps, "Telegram Bot"));
254
355
  }
255
- }
256
- async function setupTelegram(stepNum = 1, totalSteps = 3) {
257
- console.log(step(stepNum, totalSteps, "Telegram Bot"));
258
356
  let botToken = "";
357
+ const existingToken = existing?.botToken;
259
358
  while (true) {
260
- botToken = guardCancel(
261
- await clack.text({
262
- message: "Bot token (from @BotFather):",
263
- validate: (val) => (val ?? "").toString().trim().length > 0 ? void 0 : "Token cannot be empty"
359
+ const tokenInput = guardCancel(
360
+ await clack2.text({
361
+ message: existingToken ? "Bot token (from @BotFather) \u2014 leave blank to keep current:" : "Bot token (from @BotFather):",
362
+ ...existingToken ? { placeholder: "Leave blank to keep current" } : {},
363
+ validate: (val) => {
364
+ if (existingToken && (val ?? "").toString().trim().length === 0) return void 0;
365
+ if ((val ?? "").toString().trim().length > 0) return void 0;
366
+ return "Token cannot be empty";
367
+ }
264
368
  })
265
369
  );
266
- botToken = botToken.trim();
267
- const s = clack.spinner();
370
+ const keptExisting = existingToken && !tokenInput.trim();
371
+ botToken = tokenInput.trim() || existingToken || "";
372
+ if (!botToken) continue;
373
+ if (keptExisting) {
374
+ console.log(ok("Keeping current bot token"));
375
+ break;
376
+ }
377
+ const s = clack2.spinner();
268
378
  s.start("Validating token...");
269
379
  const result = await validateBotToken(botToken);
270
380
  s.stop("Token validated");
@@ -274,7 +384,7 @@ async function setupTelegram(stepNum = 1, totalSteps = 3) {
274
384
  }
275
385
  console.log(fail(result.error));
276
386
  const action = guardCancel(
277
- await clack.select({
387
+ await clack2.select({
278
388
  message: "What to do?",
279
389
  options: [
280
390
  { label: "Re-enter token", value: "retry" },
@@ -285,67 +395,52 @@ async function setupTelegram(stepNum = 1, totalSteps = 3) {
285
395
  if (action === "skip") break;
286
396
  }
287
397
  let chatId;
288
- while (true) {
289
- chatId = await detectChatId(botToken);
290
- const chatResult = await validateChatId(botToken, chatId);
291
- if (!chatResult.ok) {
292
- console.log(fail(chatResult.error));
293
- console.log("");
294
- console.log(` ${c.bold}How to fix:${c.reset}`);
295
- console.log(dim(" 1. Make sure the bot is added to the group"));
296
- console.log(dim(" 2. The group must be a Supergroup (Group Settings \u2192 convert)"));
297
- console.log(dim(" 3. Send a message in the group after adding the bot"));
298
- console.log("");
299
- guardCancel(await clack.text({ message: "Press Enter to try again..." }));
300
- continue;
301
- }
302
- console.log(
303
- ok(
304
- `Group: ${c.bold}${chatResult.title}${c.reset}${c.green}${chatResult.isForum ? " (Topics enabled)" : ""}`
305
- )
398
+ const existingChatId = existing?.chatId;
399
+ if (existingChatId && existingChatId !== 0) {
400
+ const chatIdAction = guardCancel(
401
+ await clack2.select({
402
+ message: `Group chat ID: ${existingChatId}`,
403
+ options: [
404
+ { value: "keep", label: "Keep current" },
405
+ { value: "manual", label: "Enter new chat ID" },
406
+ { value: "detect", label: "Auto-detect from group" }
407
+ ],
408
+ initialValue: "keep"
409
+ })
306
410
  );
307
- const adminResult = await validateBotAdmin(botToken, chatId);
308
- if (!adminResult.ok) {
309
- console.log(fail(adminResult.error));
310
- console.log("");
311
- console.log(` ${c.bold}How to fix:${c.reset}`);
312
- console.log(dim(" 1. Open the group in Telegram"));
313
- console.log(dim(" 2. Go to Group Settings \u2192 Administrators"));
314
- console.log(dim(" 3. Add the bot as an administrator"));
315
- console.log("");
316
- guardCancel(await clack.text({ message: "Press Enter to check again..." }));
317
- continue;
411
+ if (chatIdAction === "keep") {
412
+ chatId = existingChatId;
413
+ console.log(ok("Keeping current group chat ID"));
414
+ } else if (chatIdAction === "manual") {
415
+ chatId = await promptManualChatId();
416
+ const chatResult = await validateChatId(botToken, chatId);
417
+ if (chatResult.ok) {
418
+ console.log(ok(`Group: ${c.bold}${chatResult.title}${c.reset}${c.green}${chatResult.isForum ? " (Topics enabled)" : ""}`));
419
+ } else {
420
+ console.log(fail(chatResult.error));
421
+ }
422
+ } else {
423
+ chatId = await detectAndValidateChatId(botToken);
318
424
  }
319
- console.log(ok("Bot has admin privileges"));
320
- break;
425
+ } else {
426
+ chatId = await detectAndValidateChatId(botToken);
321
427
  }
322
428
  return {
323
429
  enabled: true,
324
430
  botToken,
325
431
  chatId,
326
- notificationTopicId: null,
327
- assistantTopicId: null
432
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
433
+ notificationTopicId: existing?.notificationTopicId ?? null,
434
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
435
+ assistantTopicId: existing?.assistantTopicId ?? null
328
436
  };
329
437
  }
330
- async function validateDiscordToken(token) {
331
- try {
332
- const res = await fetch("https://discord.com/api/v10/users/@me", {
333
- headers: { Authorization: `Bot ${token}` }
334
- });
335
- if (res.status === 200) {
336
- const data = await res.json();
337
- return { ok: true, username: data.username, id: data.id };
338
- }
339
- if (res.status === 401) {
340
- return { ok: false, error: "Token rejected by Discord (401 Unauthorized)" };
341
- }
342
- return { ok: false, error: `Discord API returned ${res.status}` };
343
- } catch (err) {
344
- return { ok: false, error: err.message };
345
- }
346
- }
347
- async function setupDiscord() {
348
- console.log("\n\u{1F4F1} Discord Setup\n");
438
+
439
+ // src/core/setup/setup-discord.ts
440
+ import * as clack3 from "@clack/prompts";
441
+ async function setupDiscord(opts) {
442
+ const { existing } = opts ?? {};
443
+ console.log("\nDiscord Setup\n");
349
444
  console.log(` ${c.bold}Quick setup:${c.reset}`);
350
445
  console.log(dim(" 1. Create app at https://discord.com/developers/applications"));
351
446
  console.log(dim(" 2. Go to Bot \u2192 Reset Token \u2192 copy it"));
@@ -354,18 +449,30 @@ async function setupDiscord() {
354
449
  console.log(dim(" 5. Bot Permissions: Manage Channels, Send Messages, Manage Threads, Attach Files"));
355
450
  console.log(dim(" 6. Open generated URL \u2192 invite bot to your server"));
356
451
  console.log("");
357
- console.log(dim(` \u{1F4D6} Detailed guide: https://github.com/Open-ACP/OpenACP/blob/main/docs/guide/discord-setup.md`));
452
+ console.log(dim(` Detailed guide: https://github.com/Open-ACP/OpenACP/blob/main/docs/guide/discord-setup.md`));
358
453
  console.log("");
359
454
  let botToken = "";
455
+ const existingToken = existing?.botToken;
360
456
  while (true) {
361
- botToken = guardCancel(
362
- await clack.text({
363
- message: "Bot token (from Discord Developer Portal):",
364
- validate: (val) => (val ?? "").toString().trim().length > 0 ? void 0 : "Token cannot be empty"
457
+ const tokenInput = guardCancel(
458
+ await clack3.text({
459
+ message: existingToken ? "Bot token (from Discord Developer Portal) \u2014 leave blank to keep current:" : "Bot token (from Discord Developer Portal):",
460
+ ...existingToken ? { placeholder: "Leave blank to keep current" } : {},
461
+ validate: (val) => {
462
+ if (existingToken && (val ?? "").toString().trim().length === 0) return void 0;
463
+ if ((val ?? "").toString().trim().length > 0) return void 0;
464
+ return "Token cannot be empty";
465
+ }
365
466
  })
366
467
  );
367
- botToken = botToken.trim();
368
- const s = clack.spinner();
468
+ const keptExisting = existingToken && !tokenInput.trim();
469
+ botToken = tokenInput.trim() || existingToken || "";
470
+ if (!botToken) continue;
471
+ if (keptExisting) {
472
+ console.log(ok("Keeping current bot token"));
473
+ break;
474
+ }
475
+ const s = clack3.spinner();
369
476
  s.start("Validating token...");
370
477
  const result = await validateDiscordToken(botToken);
371
478
  s.stop("Token validated");
@@ -375,7 +482,7 @@ async function setupDiscord() {
375
482
  }
376
483
  console.log(fail(result.error));
377
484
  const action = guardCancel(
378
- await clack.select({
485
+ await clack3.select({
379
486
  message: "What to do?",
380
487
  options: [
381
488
  { label: "Re-enter token", value: "retry" },
@@ -385,33 +492,69 @@ async function setupDiscord() {
385
492
  );
386
493
  if (action === "skip") break;
387
494
  }
388
- const guildId = guardCancel(
389
- await clack.text({
390
- message: "Guild (server) ID:",
495
+ const guildIdInput = guardCancel(
496
+ await clack3.text({
497
+ message: existing?.guildId ? "Guild (server) ID \u2014 leave blank to keep current:" : "Guild (server) ID:",
498
+ ...existing?.guildId ? { placeholder: `Current: ${existing.guildId}` } : {},
391
499
  validate: (val) => {
392
500
  const trimmed = (val ?? "").toString().trim();
501
+ if (existing?.guildId && !trimmed) return void 0;
393
502
  if (!trimmed) return "Guild ID cannot be empty";
394
503
  if (!/^\d{17,20}$/.test(trimmed)) return "Guild ID must be a numeric Discord snowflake (17-20 digits)";
395
504
  return void 0;
396
505
  }
397
506
  })
398
507
  );
508
+ const guildId = guildIdInput.trim() || existing?.guildId || "";
399
509
  return {
400
510
  enabled: true,
401
511
  botToken,
402
512
  guildId: guildId.trim(),
403
- forumChannelId: null,
404
- notificationChannelId: null,
405
- assistantThreadId: null
513
+ forumChannelId: existing?.forumChannelId ?? null,
514
+ notificationChannelId: existing?.notificationChannelId ?? null,
515
+ assistantThreadId: existing?.assistantThreadId ?? null
406
516
  };
407
517
  }
518
+
519
+ // src/core/setup/setup-agents.ts
520
+ import { execFileSync } from "child_process";
521
+ import * as clack4 from "@clack/prompts";
522
+ var KNOWN_AGENTS = [
523
+ // claude-agent-acp is bundled as a dependency — no detection needed, but
524
+ // kept here so detectAgents() still returns it for display purposes.
525
+ { name: "claude", commands: ["claude-agent-acp"] },
526
+ { name: "codex", commands: ["codex"] }
527
+ ];
528
+ async function detectAgents() {
529
+ const found = [];
530
+ for (const agent of KNOWN_AGENTS) {
531
+ const available = [];
532
+ for (const cmd of agent.commands) {
533
+ if (commandExists(cmd)) {
534
+ available.push(cmd);
535
+ }
536
+ }
537
+ if (available.length > 0) {
538
+ found.push({ name: agent.name, command: available[0] });
539
+ }
540
+ }
541
+ return found;
542
+ }
543
+ async function validateAgentCommand(command) {
544
+ try {
545
+ execFileSync("which", [command], { stdio: "pipe" });
546
+ return true;
547
+ } catch {
548
+ return false;
549
+ }
550
+ }
408
551
  async function setupAgents() {
409
552
  const { AgentCatalog } = await import("./agent-catalog-FC3HGDEQ.js");
410
553
  const { muteLogger, unmuteLogger } = await import("./log-NXABYJTT.js");
411
554
  muteLogger();
412
555
  const catalog = new AgentCatalog();
413
556
  catalog.load();
414
- const s = clack.spinner();
557
+ const s = clack4.spinner();
415
558
  s.start("Checking available agents...");
416
559
  await catalog.refreshRegistryIfStale();
417
560
  if (!catalog.getInstalledAgent("claude")) {
@@ -463,7 +606,7 @@ async function setupAgents() {
463
606
  }
464
607
  const installedKeys = installed.map((a) => a.key);
465
608
  const selected = guardCancel(
466
- await clack.autocompleteMultiselect({
609
+ await clack4.autocompleteMultiselect({
467
610
  message: "Install additional agents? (type to search, Space to select)",
468
611
  options,
469
612
  initialValues: installedKeys,
@@ -473,7 +616,7 @@ async function setupAgents() {
473
616
  for (const key of selected) {
474
617
  const regAgent = catalog.findRegistryAgent(key);
475
618
  if (regAgent) {
476
- const installSpinner = clack.spinner();
619
+ const installSpinner = clack4.spinner();
477
620
  installSpinner.start(`Installing ${regAgent.name}...`);
478
621
  muteLogger();
479
622
  const result = await catalog.install(key);
@@ -490,7 +633,7 @@ async function setupAgents() {
490
633
  let defaultAgent = "claude";
491
634
  if (installedAgents.length > 1) {
492
635
  defaultAgent = guardCancel(
493
- await clack.select({
636
+ await clack4.select({
494
637
  message: "Which agent should be the default?",
495
638
  options: installedAgents.map((key) => {
496
639
  const agent = catalog.getInstalledAgent(key);
@@ -503,25 +646,38 @@ async function setupAgents() {
503
646
  console.log(ok(`Default agent: ${c.bold}${defaultAgent}${c.reset}`));
504
647
  return { defaultAgent };
505
648
  }
506
- async function setupWorkspace(stepNum = 2, totalSteps = 3) {
507
- console.log(step(stepNum, totalSteps, "Workspace"));
649
+
650
+ // src/core/setup/setup-workspace.ts
651
+ import * as clack5 from "@clack/prompts";
652
+ async function setupWorkspace(opts) {
653
+ const { existing, stepNum, totalSteps } = opts ?? {};
654
+ if (stepNum != null && totalSteps != null) {
655
+ console.log(step(stepNum, totalSteps, "Workspace"));
656
+ }
508
657
  const baseDir = guardCancel(
509
- await clack.text({
658
+ await clack5.text({
510
659
  message: "Base directory for workspaces:",
511
- initialValue: "~/openacp-workspace",
660
+ initialValue: existing ?? "~/openacp-workspace",
512
661
  validate: (val) => (val ?? "").toString().trim().length > 0 ? void 0 : "Path cannot be empty"
513
662
  })
514
663
  );
515
664
  return { baseDir: baseDir.trim().replace(/^['"]|['"]$/g, "") };
516
665
  }
517
- async function setupRunMode(stepNum = 3, totalSteps = 3) {
518
- console.log(step(stepNum, totalSteps, "Run Mode"));
666
+
667
+ // src/core/setup/setup-run-mode.ts
668
+ import * as clack6 from "@clack/prompts";
669
+ async function setupRunMode(opts) {
670
+ const { existing, stepNum, totalSteps } = opts ?? {};
671
+ if (stepNum != null && totalSteps != null) {
672
+ console.log(step(stepNum, totalSteps, "Run Mode"));
673
+ }
519
674
  if (process.platform === "win32") {
520
675
  console.log(dim(" (Daemon mode not available on Windows)"));
521
676
  return { runMode: "foreground", autoStart: false };
522
677
  }
678
+ const initialValue = existing?.runMode === "daemon" ? "daemon" : "foreground";
523
679
  const mode = guardCancel(
524
- await clack.select({
680
+ await clack6.select({
525
681
  message: "How would you like to run OpenACP?",
526
682
  options: [
527
683
  {
@@ -534,14 +690,19 @@ async function setupRunMode(stepNum = 3, totalSteps = 3) {
534
690
  value: "foreground",
535
691
  hint: "Runs in current terminal session. Start with: openacp"
536
692
  }
537
- ]
693
+ ],
694
+ initialValue
538
695
  })
539
696
  );
697
+ const wasDaemon = existing?.runMode === "daemon";
540
698
  if (mode === "daemon") {
541
699
  const { installAutoStart, isAutoStartSupported } = await import("./autostart-X33OGMX6.js");
700
+ const { muteLogger, unmuteLogger } = await import("./log-NXABYJTT.js");
542
701
  const autoStart = isAutoStartSupported();
543
702
  if (autoStart) {
703
+ muteLogger();
544
704
  const result = installAutoStart(expandHome("~/.openacp/logs"));
705
+ unmuteLogger();
545
706
  if (result.success) {
546
707
  console.log(ok("Auto-start on boot enabled"));
547
708
  } else {
@@ -550,44 +711,181 @@ async function setupRunMode(stepNum = 3, totalSteps = 3) {
550
711
  }
551
712
  return { runMode: "daemon", autoStart };
552
713
  }
714
+ if (wasDaemon) {
715
+ const { muteLogger, unmuteLogger } = await import("./log-NXABYJTT.js");
716
+ muteLogger();
717
+ try {
718
+ const { stopDaemon } = await import("./daemon-4CS6HMB5.js");
719
+ const result = await stopDaemon();
720
+ unmuteLogger();
721
+ if (result.stopped) {
722
+ console.log(ok(`Daemon stopped (was PID ${result.pid})`));
723
+ }
724
+ } catch {
725
+ unmuteLogger();
726
+ }
727
+ muteLogger();
728
+ try {
729
+ const { uninstallAutoStart } = await import("./autostart-X33OGMX6.js");
730
+ uninstallAutoStart();
731
+ unmuteLogger();
732
+ } catch {
733
+ unmuteLogger();
734
+ }
735
+ }
553
736
  return { runMode: "foreground", autoStart: false };
554
737
  }
555
- function applyGradient(text2) {
556
- const colors = [135, 99, 63, 33, 39, 44, 44];
557
- const lines = text2.split("\n");
558
- return lines.map((line, i) => {
559
- const colorIdx = Math.min(i, colors.length - 1);
560
- return `\x1B[38;5;${colors[colorIdx]}m${line}\x1B[0m`;
561
- }).join("\n");
738
+
739
+ // src/core/setup/setup-integrations.ts
740
+ import * as clack7 from "@clack/prompts";
741
+ async function setupIntegrations(config) {
742
+ const claudeIntegration = config?.integrations?.claude;
743
+ const isInstalled = claudeIntegration?.installed === true;
744
+ const installClaude = guardCancel(
745
+ await clack7.confirm({
746
+ message: isInstalled ? "Claude CLI integration is installed. Reinstall?" : "Install session transfer for Claude? (enables /openacp:handoff in your terminal)",
747
+ initialValue: !isInstalled
748
+ })
749
+ );
750
+ if (installClaude) {
751
+ try {
752
+ const { getIntegration } = await import("./integrate-PNEHRY2I.js");
753
+ const integration = getIntegration("claude");
754
+ if (integration) {
755
+ for (const item of integration.items) {
756
+ const result = await item.install();
757
+ for (const log of result.logs) console.log(` ${log}`);
758
+ }
759
+ }
760
+ console.log("Claude CLI integration installed.\n");
761
+ } catch (err) {
762
+ console.log(`Could not install Claude CLI integration: ${err instanceof Error ? err.message : err}`);
763
+ console.log(" You can install it later with: openacp integrate claude\n");
764
+ }
765
+ }
562
766
  }
563
- var BANNER = `
564
- \u2588\u2588\u2588\u2588\u2588\u2588\u2557 \u2588\u2588\u2588\u2588\u2588\u2588\u2557 \u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2557\u2588\u2588\u2588\u2557 \u2588\u2588\u2557 \u2588\u2588\u2588\u2588\u2588\u2557 \u2588\u2588\u2588\u2588\u2588\u2588\u2557\u2588\u2588\u2588\u2588\u2588\u2588\u2557
565
- \u2588\u2588\u2554\u2550\u2550\u2550\u2588\u2588\u2557\u2588\u2588\u2554\u2550\u2550\u2588\u2588\u2557\u2588\u2588\u2554\u2550\u2550\u2550\u2550\u255D\u2588\u2588\u2588\u2588\u2557 \u2588\u2588\u2551\u2588\u2588\u2554\u2550\u2550\u2588\u2588\u2557\u2588\u2588\u2554\u2550\u2550\u2550\u2550\u255D\u2588\u2588\u2554\u2550\u2550\u2588\u2588\u2557
566
- \u2588\u2588\u2551 \u2588\u2588\u2551\u2588\u2588\u2588\u2588\u2588\u2588\u2554\u255D\u2588\u2588\u2588\u2588\u2588\u2557 \u2588\u2588\u2554\u2588\u2588\u2557 \u2588\u2588\u2551\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2551\u2588\u2588\u2551 \u2588\u2588\u2588\u2588\u2588\u2588\u2554\u255D
567
- \u2588\u2588\u2551 \u2588\u2588\u2551\u2588\u2588\u2554\u2550\u2550\u2550\u255D \u2588\u2588\u2554\u2550\u2550\u255D \u2588\u2588\u2551\u255A\u2588\u2588\u2557\u2588\u2588\u2551\u2588\u2588\u2554\u2550\u2550\u2588\u2588\u2551\u2588\u2588\u2551 \u2588\u2588\u2554\u2550\u2550\u2550\u255D
568
- \u255A\u2588\u2588\u2588\u2588\u2588\u2588\u2554\u255D\u2588\u2588\u2551 \u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2557\u2588\u2588\u2551 \u255A\u2588\u2588\u2588\u2588\u2551\u2588\u2588\u2551 \u2588\u2588\u2551\u255A\u2588\u2588\u2588\u2588\u2588\u2588\u2557\u2588\u2588\u2551
569
- \u255A\u2550\u2550\u2550\u2550\u2550\u255D \u255A\u2550\u255D \u255A\u2550\u2550\u2550\u2550\u2550\u2550\u255D\u255A\u2550\u255D \u255A\u2550\u2550\u2550\u255D\u255A\u2550\u255D \u255A\u2550\u255D \u255A\u2550\u2550\u2550\u2550\u2550\u255D\u255A\u2550\u255D
570
- `;
571
- async function printStartBanner() {
572
- let version = "0.0.0";
573
- try {
574
- const { getCurrentVersion } = await import("./version-NQZBM5M7.js");
575
- version = getCurrentVersion();
576
- } catch {
767
+
768
+ // src/core/setup/setup-channels.ts
769
+ import * as clack8 from "@clack/prompts";
770
+ function getChannelStatuses(config) {
771
+ const statuses = [];
772
+ for (const [id, meta] of Object.entries(CHANNEL_META)) {
773
+ const ch = config.channels[id];
774
+ const enabled = ch?.enabled === true;
775
+ const configured = !!ch && Object.keys(ch).length > 1;
776
+ let hint;
777
+ if (id === "telegram" && ch?.botToken && typeof ch.botToken === "string" && ch.botToken !== "YOUR_BOT_TOKEN_HERE") {
778
+ hint = `Chat ID: ${ch.chatId}`;
779
+ }
780
+ if (id === "discord" && ch?.guildId) {
781
+ hint = `Guild: ${ch.guildId}`;
782
+ }
783
+ statuses.push({ id, label: meta.label, configured, enabled, hint });
577
784
  }
578
- console.log(applyGradient(BANNER));
579
- console.log(`${c.dim} AI coding agents, anywhere. v${version}${c.reset}
580
- `);
785
+ return statuses;
581
786
  }
582
- async function printWelcomeBanner() {
583
- await printStartBanner();
787
+ function noteChannelStatus(config) {
788
+ const statuses = getChannelStatuses(config);
789
+ const lines = statuses.map((s) => {
790
+ const status = s.enabled ? "enabled" : s.configured ? "disabled" : "not configured";
791
+ const hintStr = s.hint ? ` \u2014 ${s.hint}` : "";
792
+ return ` ${s.label}: ${status}${hintStr}`;
793
+ });
794
+ console.log("");
795
+ console.log(`${c.bold} Channel status${c.reset}`);
796
+ for (const line of lines) console.log(line);
797
+ console.log("");
584
798
  }
799
+ async function promptConfiguredAction(label) {
800
+ return guardCancel(
801
+ await clack8.select({
802
+ message: `${label} already configured. What do you want to do?`,
803
+ options: [
804
+ { value: "modify", label: "Modify settings" },
805
+ { value: "disable", label: "Disable bot" },
806
+ { value: "delete", label: "Delete config" },
807
+ { value: "skip", label: "Skip (leave as-is)" }
808
+ ],
809
+ initialValue: "modify"
810
+ })
811
+ );
812
+ }
813
+ async function configureChannels(config) {
814
+ const next = structuredClone(config);
815
+ let changed = false;
816
+ noteChannelStatus(next);
817
+ while (true) {
818
+ const statuses = getChannelStatuses(next);
819
+ const options = statuses.map((s) => {
820
+ const status = s.enabled ? "enabled" : s.configured ? "disabled" : "not configured";
821
+ return {
822
+ value: s.id,
823
+ label: `${s.label} (${CHANNEL_META[s.id].method})`,
824
+ hint: status + (s.hint ? ` \xB7 ${s.hint}` : "")
825
+ };
826
+ });
827
+ const choice = guardCancel(
828
+ await clack8.select({
829
+ message: "Select a channel",
830
+ options: [
831
+ ...options,
832
+ { value: "__done__", label: "Finished" }
833
+ ]
834
+ })
835
+ );
836
+ if (choice === "__done__") break;
837
+ const channelId = choice;
838
+ const meta = CHANNEL_META[channelId];
839
+ const existing = next.channels[channelId];
840
+ const isConfigured = !!existing && Object.keys(existing).length > 1;
841
+ if (isConfigured) {
842
+ const action = await promptConfiguredAction(meta.label);
843
+ if (action === "skip") continue;
844
+ if (action === "disable") {
845
+ next.channels[channelId].enabled = false;
846
+ changed = true;
847
+ console.log(ok(`${meta.label} disabled`));
848
+ continue;
849
+ }
850
+ if (action === "delete") {
851
+ const confirmed = guardCancel(
852
+ await clack8.confirm({
853
+ message: `Delete ${meta.label} config? This cannot be undone.`,
854
+ initialValue: false
855
+ })
856
+ );
857
+ if (confirmed) {
858
+ delete next.channels[channelId];
859
+ changed = true;
860
+ console.log(ok(`${meta.label} config deleted`));
861
+ }
862
+ continue;
863
+ }
864
+ }
865
+ if (channelId === "telegram") {
866
+ const result = await setupTelegram({
867
+ existing: isConfigured ? existing : void 0
868
+ });
869
+ next.channels.telegram = result;
870
+ changed = true;
871
+ } else if (channelId === "discord") {
872
+ const result = await setupDiscord({
873
+ existing: isConfigured ? existing : void 0
874
+ });
875
+ next.channels.discord = result;
876
+ changed = true;
877
+ }
878
+ }
879
+ return { config: next, changed };
880
+ }
881
+
882
+ // src/core/setup/wizard.ts
585
883
  async function runSetup(configManager, opts) {
586
- await printWelcomeBanner();
587
- clack.intro("Let's set up OpenACP");
884
+ await printStartBanner();
885
+ clack9.intro("Let's set up OpenACP");
588
886
  try {
589
887
  const channelChoice = guardCancel(
590
- await clack.select({
888
+ await clack9.select({
591
889
  message: "Which messaging platform do you want to use?",
592
890
  options: [
593
891
  { label: "Telegram", value: "telegram" },
@@ -604,44 +902,21 @@ async function runSetup(configManager, opts) {
604
902
  let currentStep = 0;
605
903
  if (channelChoice === "telegram" || channelChoice === "both") {
606
904
  currentStep++;
607
- telegram = await setupTelegram(currentStep, totalSteps);
905
+ telegram = await setupTelegram({ stepNum: currentStep, totalSteps });
608
906
  }
609
907
  if (channelChoice === "discord" || channelChoice === "both") {
610
908
  currentStep++;
611
909
  discord = await setupDiscord();
612
910
  }
613
911
  const { defaultAgent } = await setupAgents();
614
- {
615
- const installClaude = guardCancel(
616
- await clack.confirm({
617
- message: "Install session transfer for Claude? (enables /openacp:handoff in your terminal)",
618
- initialValue: true
619
- })
620
- );
621
- if (installClaude) {
622
- try {
623
- const { getIntegration } = await import("./integrate-PNEHRY2I.js");
624
- const integration = getIntegration("claude");
625
- if (integration) {
626
- for (const item of integration.items) {
627
- const result = await item.install();
628
- for (const log of result.logs) console.log(` ${log}`);
629
- }
630
- }
631
- console.log("Claude CLI integration installed.\n");
632
- } catch (err) {
633
- console.log(`Could not install Claude CLI integration: ${err instanceof Error ? err.message : err}`);
634
- console.log(" You can install it later with: openacp integrate claude\n");
635
- }
636
- }
637
- }
912
+ await setupIntegrations();
638
913
  currentStep++;
639
- const workspace = await setupWorkspace(currentStep, totalSteps);
914
+ const workspace = await setupWorkspace({ stepNum: currentStep, totalSteps });
640
915
  let runMode = "foreground";
641
916
  let autoStart = false;
642
917
  if (!opts?.skipRunMode) {
643
918
  currentStep++;
644
- const result = await setupRunMode(currentStep, totalSteps);
919
+ const result = await setupRunMode({ stepNum: currentStep, totalSteps });
645
920
  runMode = result.runMode;
646
921
  autoStart = result.autoStart;
647
922
  }
@@ -702,7 +977,7 @@ async function runSetup(configManager, opts) {
702
977
  );
703
978
  return false;
704
979
  }
705
- clack.outro(`Config saved to ${configManager.getConfigPath()}`);
980
+ clack9.outro(`Config saved to ${configManager.getConfigPath()}`);
706
981
  if (!opts?.skipRunMode) {
707
982
  console.log(ok("Starting OpenACP..."));
708
983
  console.log("");
@@ -710,26 +985,101 @@ async function runSetup(configManager, opts) {
710
985
  return true;
711
986
  } catch (err) {
712
987
  if (err.name === "ExitPromptError") {
713
- clack.cancel("Setup cancelled.");
988
+ clack9.cancel("Setup cancelled.");
714
989
  return false;
715
990
  }
716
991
  throw err;
717
992
  }
718
993
  }
994
+ async function selectSection(hasSelection) {
995
+ return guardCancel(
996
+ await clack9.select({
997
+ message: "Select sections to configure",
998
+ options: [
999
+ ...ONBOARD_SECTION_OPTIONS,
1000
+ {
1001
+ value: "__continue",
1002
+ label: "Continue",
1003
+ hint: hasSelection ? "Done" : "Skip for now"
1004
+ }
1005
+ ],
1006
+ initialValue: ONBOARD_SECTION_OPTIONS[0].value
1007
+ })
1008
+ );
1009
+ }
1010
+ async function runReconfigure(configManager) {
1011
+ await printStartBanner();
1012
+ clack9.intro("OpenACP \u2014 Reconfigure");
1013
+ try {
1014
+ await configManager.load();
1015
+ let config = configManager.get();
1016
+ clack9.note(summarizeConfig(config), "Current configuration");
1017
+ let ranSection = false;
1018
+ while (true) {
1019
+ const choice = await selectSection(ranSection);
1020
+ if (choice === "__continue") break;
1021
+ ranSection = true;
1022
+ if (choice === "channels") {
1023
+ const result = await configureChannels(config);
1024
+ if (result.changed) {
1025
+ config = { ...config, channels: result.config.channels };
1026
+ await configManager.writeNew(config);
1027
+ }
1028
+ }
1029
+ if (choice === "agents") {
1030
+ const { defaultAgent } = await setupAgents();
1031
+ await configManager.save({ defaultAgent });
1032
+ config = configManager.get();
1033
+ }
1034
+ if (choice === "workspace") {
1035
+ const { baseDir } = await setupWorkspace({
1036
+ existing: config.workspace.baseDir
1037
+ });
1038
+ await configManager.save({ workspace: { baseDir } });
1039
+ config = configManager.get();
1040
+ }
1041
+ if (choice === "runMode") {
1042
+ const result = await setupRunMode({
1043
+ existing: { runMode: config.runMode, autoStart: config.autoStart }
1044
+ });
1045
+ await configManager.save({
1046
+ runMode: result.runMode,
1047
+ autoStart: result.autoStart
1048
+ });
1049
+ config = configManager.get();
1050
+ }
1051
+ if (choice === "integrations") {
1052
+ await setupIntegrations(config);
1053
+ }
1054
+ }
1055
+ if (!ranSection) {
1056
+ clack9.outro("No changes made.");
1057
+ return;
1058
+ }
1059
+ clack9.outro(`Config saved to ${configManager.getConfigPath()}`);
1060
+ } catch (err) {
1061
+ if (err.name === "ExitPromptError") {
1062
+ clack9.cancel("Setup cancelled.");
1063
+ return;
1064
+ }
1065
+ throw err;
1066
+ }
1067
+ }
719
1068
 
720
1069
  export {
1070
+ printStartBanner,
721
1071
  validateBotToken,
722
1072
  validateChatId,
723
1073
  validateBotAdmin,
724
- detectAgents,
725
- validateAgentCommand,
726
- setupTelegram,
727
1074
  validateDiscordToken,
1075
+ setupTelegram,
728
1076
  setupDiscord,
1077
+ detectAgents,
1078
+ validateAgentCommand,
729
1079
  setupAgents,
730
1080
  setupWorkspace,
731
1081
  setupRunMode,
732
- printStartBanner,
733
- runSetup
1082
+ runSetup,
1083
+ runReconfigure
734
1084
  };
735
- //# sourceMappingURL=chunk-BDYVCIBH.js.map
1085
+ //# sourceMappingURL=chunk-LCRLAV4G.js.map