@openacp/cli 0.5.2 → 0.6.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (96) hide show
  1. package/README.md +40 -14
  2. package/dist/action-detect-6M5GCGAU.js +15 -0
  3. package/dist/admin-IKPS5PFC.js +16 -0
  4. package/dist/agents-55NX3DHM.js +14 -0
  5. package/dist/{api-client-UN7BXQOQ.js → api-client-BH2JFHQW.js} +4 -2
  6. package/dist/{autostart-K73RQZVV.js → autostart-A7JRU4WJ.js} +6 -2
  7. package/dist/chunk-3WPG7GXA.js +134 -0
  8. package/dist/chunk-3WPG7GXA.js.map +1 -0
  9. package/dist/{chunk-NDR5JCS7.js → chunk-437NLISU.js} +2 -2
  10. package/dist/chunk-5NBWM7P6.js +438 -0
  11. package/dist/chunk-5NBWM7P6.js.map +1 -0
  12. package/dist/{chunk-F4TB4UBK.js → chunk-6Q7PZWCL.js} +171 -26
  13. package/dist/chunk-6Q7PZWCL.js.map +1 -0
  14. package/dist/chunk-7G5QKLLF.js +105 -0
  15. package/dist/chunk-7G5QKLLF.js.map +1 -0
  16. package/dist/chunk-AKIU4JBF.js +145 -0
  17. package/dist/chunk-AKIU4JBF.js.map +1 -0
  18. package/dist/{chunk-JRF4G4X7.js → chunk-DWQKUECJ.js} +13 -2
  19. package/dist/chunk-DWQKUECJ.js.map +1 -0
  20. package/dist/{chunk-KSIQZC3J.js → chunk-EVFJW45N.js} +1 -1
  21. package/dist/chunk-EVFJW45N.js.map +1 -0
  22. package/dist/chunk-H7ZMPBZC.js +203 -0
  23. package/dist/chunk-H7ZMPBZC.js.map +1 -0
  24. package/dist/chunk-I7WC6E5S.js +71 -0
  25. package/dist/chunk-I7WC6E5S.js.map +1 -0
  26. package/dist/{chunk-4ZGMSNRP.js → chunk-MHFCZGRW.js} +114 -16
  27. package/dist/chunk-MHFCZGRW.js.map +1 -0
  28. package/dist/{chunk-X6LLG7XN.js → chunk-PMGNLNSH.js} +15 -6
  29. package/dist/chunk-PMGNLNSH.js.map +1 -0
  30. package/dist/chunk-SM3G6UAX.js +122 -0
  31. package/dist/chunk-SM3G6UAX.js.map +1 -0
  32. package/dist/{chunk-IRGYTNLP.js → chunk-SPX7CKWV.js} +76 -2
  33. package/dist/chunk-SPX7CKWV.js.map +1 -0
  34. package/dist/chunk-T22OLSET.js +265 -0
  35. package/dist/chunk-T22OLSET.js.map +1 -0
  36. package/dist/chunk-THBR6OXH.js +62 -0
  37. package/dist/chunk-THBR6OXH.js.map +1 -0
  38. package/dist/{chunk-65XE66HK.js → chunk-V2V767XI.js} +373 -489
  39. package/dist/chunk-V2V767XI.js.map +1 -0
  40. package/dist/{chunk-OORPX73T.js → chunk-W3EYKZNQ.js} +17 -2
  41. package/dist/chunk-W3EYKZNQ.js.map +1 -0
  42. package/dist/{chunk-VBEWSWVL.js → chunk-YYQXWA62.js} +52 -9
  43. package/dist/chunk-YYQXWA62.js.map +1 -0
  44. package/dist/cli.js +121 -32
  45. package/dist/cli.js.map +1 -1
  46. package/dist/{config-PCPIBPUA.js → config-KF2MQWAP.js} +2 -2
  47. package/dist/config-editor-OTODXUF7.js +12 -0
  48. package/dist/{daemon-JZLFRUW6.js → daemon-U6UC7OM4.js} +3 -3
  49. package/dist/discord-SLLKRUP7.js +2034 -0
  50. package/dist/discord-SLLKRUP7.js.map +1 -0
  51. package/dist/doctor-DB5PRQ6D.js +14 -0
  52. package/dist/doctor-DB5PRQ6D.js.map +1 -0
  53. package/dist/doctor-SYWNJFYK.js +9 -0
  54. package/dist/doctor-SYWNJFYK.js.map +1 -0
  55. package/dist/index.d.ts +47 -7
  56. package/dist/index.js +11 -9
  57. package/dist/{integrate-BLETI3UO.js → integrate-VOUYBPPZ.js} +106 -3
  58. package/dist/integrate-VOUYBPPZ.js.map +1 -0
  59. package/dist/{main-AH3NCVM3.js → main-M6RH3SS5.js} +31 -23
  60. package/dist/main-M6RH3SS5.js.map +1 -0
  61. package/dist/new-session-DRRP2J7E.js +16 -0
  62. package/dist/new-session-DRRP2J7E.js.map +1 -0
  63. package/dist/session-FVFLBREJ.js +19 -0
  64. package/dist/session-FVFLBREJ.js.map +1 -0
  65. package/dist/settings-LPOLJ6SA.js +12 -0
  66. package/dist/settings-LPOLJ6SA.js.map +1 -0
  67. package/dist/{setup-7JINXQOA.js → setup-LI5CKYDK.js} +9 -5
  68. package/dist/setup-LI5CKYDK.js.map +1 -0
  69. package/dist/{tunnel-service-LEVPLXAZ.js → tunnel-service-U6V4HQOO.js} +263 -47
  70. package/dist/tunnel-service-U6V4HQOO.js.map +1 -0
  71. package/dist/{version-VC5CPXBX.js → version-ALWGGVKM.js} +2 -2
  72. package/dist/version-ALWGGVKM.js.map +1 -0
  73. package/package.json +2 -1
  74. package/dist/chunk-4ZGMSNRP.js.map +0 -1
  75. package/dist/chunk-65XE66HK.js.map +0 -1
  76. package/dist/chunk-F4TB4UBK.js.map +0 -1
  77. package/dist/chunk-IRGYTNLP.js.map +0 -1
  78. package/dist/chunk-JRF4G4X7.js.map +0 -1
  79. package/dist/chunk-KSIQZC3J.js.map +0 -1
  80. package/dist/chunk-OORPX73T.js.map +0 -1
  81. package/dist/chunk-VBEWSWVL.js.map +0 -1
  82. package/dist/chunk-X6LLG7XN.js.map +0 -1
  83. package/dist/config-editor-DDF3ZFJK.js +0 -12
  84. package/dist/doctor-N2HKKUUQ.js +0 -9
  85. package/dist/integrate-BLETI3UO.js.map +0 -1
  86. package/dist/main-AH3NCVM3.js.map +0 -1
  87. package/dist/tunnel-service-LEVPLXAZ.js.map +0 -1
  88. /package/dist/{api-client-UN7BXQOQ.js.map → action-detect-6M5GCGAU.js.map} +0 -0
  89. /package/dist/{autostart-K73RQZVV.js.map → admin-IKPS5PFC.js.map} +0 -0
  90. /package/dist/{config-PCPIBPUA.js.map → agents-55NX3DHM.js.map} +0 -0
  91. /package/dist/{config-editor-DDF3ZFJK.js.map → api-client-BH2JFHQW.js.map} +0 -0
  92. /package/dist/{daemon-JZLFRUW6.js.map → autostart-A7JRU4WJ.js.map} +0 -0
  93. /package/dist/{chunk-NDR5JCS7.js.map → chunk-437NLISU.js.map} +0 -0
  94. /package/dist/{doctor-N2HKKUUQ.js.map → config-KF2MQWAP.js.map} +0 -0
  95. /package/dist/{setup-7JINXQOA.js.map → config-editor-OTODXUF7.js.map} +0 -0
  96. /package/dist/{version-VC5CPXBX.js.map → daemon-U6UC7OM4.js.map} +0 -0
@@ -3,7 +3,7 @@ import {
3
3
  } from "./chunk-JKBFUAJK.js";
4
4
  import {
5
5
  expandHome
6
- } from "./chunk-JRF4G4X7.js";
6
+ } from "./chunk-DWQKUECJ.js";
7
7
 
8
8
  // src/core/setup.ts
9
9
  import { execFileSync } from "child_process";
@@ -21,8 +21,8 @@ var c = {
21
21
  var ok = (msg) => `${c.green}${c.bold}\u2713${c.reset} ${c.green}${msg}${c.reset}`;
22
22
  var warn = (msg) => `${c.yellow}\u26A0 ${msg}${c.reset}`;
23
23
  var fail = (msg) => `${c.red}\u2717 ${msg}${c.reset}`;
24
- var step = (n, title) => `
25
- ${c.cyan}${c.bold}[${n}/3]${c.reset} ${c.bold}${title}${c.reset}
24
+ var step = (n, total, title) => `
25
+ ${c.cyan}${c.bold}[${n}/${total}]${c.reset} ${c.bold}${title}${c.reset}
26
26
  `;
27
27
  var dim = (msg) => `${c.dim}${msg}${c.reset}`;
28
28
  async function validateBotToken(token) {
@@ -241,8 +241,8 @@ async function validateAgentCommand(command) {
241
241
  return false;
242
242
  }
243
243
  }
244
- async function setupTelegram() {
245
- console.log(step(1, "Telegram Bot"));
244
+ async function setupTelegram(stepNum = 1, totalSteps = 3) {
245
+ console.log(step(stepNum, totalSteps, "Telegram Bot"));
246
246
  let botToken = "";
247
247
  while (true) {
248
248
  botToken = await input({
@@ -308,6 +308,75 @@ async function setupTelegram() {
308
308
  assistantTopicId: null
309
309
  };
310
310
  }
311
+ async function validateDiscordToken(token) {
312
+ try {
313
+ const res = await fetch("https://discord.com/api/v10/users/@me", {
314
+ headers: { Authorization: `Bot ${token}` }
315
+ });
316
+ if (res.status === 200) {
317
+ const data = await res.json();
318
+ return { ok: true, username: data.username, id: data.id };
319
+ }
320
+ if (res.status === 401) {
321
+ return { ok: false, error: "Token rejected by Discord (401 Unauthorized)" };
322
+ }
323
+ return { ok: false, error: `Discord API returned ${res.status}` };
324
+ } catch (err) {
325
+ return { ok: false, error: err.message };
326
+ }
327
+ }
328
+ async function setupDiscord() {
329
+ console.log("\n\u{1F4F1} Discord Setup\n");
330
+ console.log(` ${c.bold}Quick setup:${c.reset}`);
331
+ console.log(dim(" 1. Create app at https://discord.com/developers/applications"));
332
+ console.log(dim(" 2. Go to Bot \u2192 Reset Token \u2192 copy it"));
333
+ console.log(dim(" 3. Enable Message Content Intent (Bot \u2192 Privileged Intents)"));
334
+ console.log(dim(" 4. OAuth2 \u2192 URL Generator \u2192 scopes: bot + applications.commands"));
335
+ console.log(dim(" 5. Bot Permissions: Manage Channels, Send Messages, Manage Threads, Attach Files"));
336
+ console.log(dim(" 6. Open generated URL \u2192 invite bot to your server"));
337
+ console.log("");
338
+ console.log(dim(` \u{1F4D6} Detailed guide: https://github.com/Open-ACP/OpenACP/blob/main/docs/guide/discord-setup.md`));
339
+ console.log("");
340
+ let botToken = "";
341
+ while (true) {
342
+ botToken = await input({
343
+ message: "Bot token (from Discord Developer Portal):",
344
+ validate: (val) => val.trim().length > 0 || "Token cannot be empty"
345
+ });
346
+ botToken = botToken.trim();
347
+ const result = await validateDiscordToken(botToken);
348
+ if (result.ok) {
349
+ console.log(ok(`Connected as @${result.username} (id: ${result.id})`));
350
+ break;
351
+ }
352
+ console.log(fail(result.error));
353
+ const action = await select({
354
+ message: "What to do?",
355
+ choices: [
356
+ { name: "Re-enter token", value: "retry" },
357
+ { name: "Use as-is (skip validation)", value: "skip" }
358
+ ]
359
+ });
360
+ if (action === "skip") break;
361
+ }
362
+ const guildId = await input({
363
+ message: "Guild (server) ID:",
364
+ validate: (val) => {
365
+ const trimmed = val.trim();
366
+ if (!trimmed) return "Guild ID cannot be empty";
367
+ if (!/^\d{17,20}$/.test(trimmed)) return "Guild ID must be a numeric Discord snowflake (17-20 digits)";
368
+ return true;
369
+ }
370
+ });
371
+ return {
372
+ enabled: true,
373
+ botToken,
374
+ guildId: guildId.trim(),
375
+ forumChannelId: null,
376
+ notificationChannelId: null,
377
+ assistantThreadId: null
378
+ };
379
+ }
311
380
  async function setupAgents() {
312
381
  const { AgentCatalog } = await import("./agent-catalog-T5ECPEDA.js");
313
382
  const { select: select2, checkbox } = await import("@inquirer/prompts");
@@ -386,8 +455,8 @@ async function setupAgents() {
386
455
  console.log(ok(`Default agent: ${c.bold}${defaultAgent}${c.reset}`));
387
456
  return { defaultAgent };
388
457
  }
389
- async function setupWorkspace() {
390
- console.log(step(2, "Workspace"));
458
+ async function setupWorkspace(stepNum = 2, totalSteps = 3) {
459
+ console.log(step(stepNum, totalSteps, "Workspace"));
391
460
  const baseDir = await input({
392
461
  message: "Base directory for workspaces:",
393
462
  default: "~/openacp-workspace",
@@ -395,8 +464,8 @@ async function setupWorkspace() {
395
464
  });
396
465
  return { baseDir: baseDir.trim().replace(/^['"]|['"]$/g, "") };
397
466
  }
398
- async function setupRunMode() {
399
- console.log(step(3, "Run Mode"));
467
+ async function setupRunMode(stepNum = 3, totalSteps = 3) {
468
+ console.log(step(stepNum, totalSteps, "Run Mode"));
400
469
  if (process.platform === "win32") {
401
470
  console.log(dim(" (Daemon mode not available on Windows)"));
402
471
  return { runMode: "foreground", autoStart: false };
@@ -417,7 +486,7 @@ async function setupRunMode() {
417
486
  ]
418
487
  });
419
488
  if (mode === "daemon") {
420
- const { installAutoStart, isAutoStartSupported } = await import("./autostart-K73RQZVV.js");
489
+ const { installAutoStart, isAutoStartSupported } = await import("./autostart-A7JRU4WJ.js");
421
490
  const autoStart = isAutoStartSupported();
422
491
  if (autoStart) {
423
492
  const result = installAutoStart(expandHome("~/.openacp/logs"));
@@ -441,7 +510,28 @@ ${c.cyan}${c.bold} \u2554\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550
441
510
  async function runSetup(configManager) {
442
511
  printWelcomeBanner();
443
512
  try {
444
- const telegram = await setupTelegram();
513
+ const { select: selectChannel } = await import("@inquirer/prompts");
514
+ const channelChoice = await selectChannel({
515
+ message: "Which messaging platform do you want to use?",
516
+ choices: [
517
+ { name: "Telegram", value: "telegram" },
518
+ { name: "Discord", value: "discord" },
519
+ { name: "Both", value: "both" }
520
+ ]
521
+ });
522
+ let telegram;
523
+ let discord;
524
+ const channelSteps = channelChoice === "both" ? 2 : 1;
525
+ const totalSteps = channelSteps + 2;
526
+ let currentStep = 0;
527
+ if (channelChoice === "telegram" || channelChoice === "both") {
528
+ currentStep++;
529
+ telegram = await setupTelegram(currentStep, totalSteps);
530
+ }
531
+ if (channelChoice === "discord" || channelChoice === "both") {
532
+ currentStep++;
533
+ discord = await setupDiscord();
534
+ }
445
535
  const { defaultAgent } = await setupAgents();
446
536
  {
447
537
  const { confirm } = await import("@inquirer/prompts");
@@ -451,7 +541,7 @@ async function runSetup(configManager) {
451
541
  });
452
542
  if (installClaude) {
453
543
  try {
454
- const { getIntegration } = await import("./integrate-BLETI3UO.js");
544
+ const { getIntegration } = await import("./integrate-VOUYBPPZ.js");
455
545
  const integration = getIntegration("claude");
456
546
  if (integration) {
457
547
  for (const item of integration.items) {
@@ -466,15 +556,20 @@ async function runSetup(configManager) {
466
556
  }
467
557
  }
468
558
  }
469
- const workspace = await setupWorkspace();
470
- const { runMode, autoStart } = await setupRunMode();
559
+ currentStep++;
560
+ const workspace = await setupWorkspace(currentStep, totalSteps);
561
+ currentStep++;
562
+ const { runMode, autoStart } = await setupRunMode(currentStep, totalSteps);
471
563
  const security = {
472
564
  allowedUserIds: [],
473
565
  maxConcurrentSessions: 20,
474
566
  sessionTimeoutMinutes: 60
475
567
  };
568
+ const channels = {};
569
+ if (telegram) channels.telegram = telegram;
570
+ if (discord) channels.discord = discord;
476
571
  const config = {
477
- channels: { telegram },
572
+ channels,
478
573
  agents: {},
479
574
  defaultAgent,
480
575
  workspace,
@@ -498,6 +593,7 @@ async function runSetup(configManager) {
498
593
  port: 3100,
499
594
  provider: "cloudflare",
500
595
  options: {},
596
+ maxUserTunnels: 5,
501
597
  storeTtlMinutes: 60,
502
598
  auth: { enabled: false }
503
599
  },
@@ -548,9 +644,11 @@ export {
548
644
  detectAgents,
549
645
  validateAgentCommand,
550
646
  setupTelegram,
647
+ validateDiscordToken,
648
+ setupDiscord,
551
649
  setupAgents,
552
650
  setupWorkspace,
553
651
  setupRunMode,
554
652
  runSetup
555
653
  };
556
- //# sourceMappingURL=chunk-4ZGMSNRP.js.map
654
+ //# sourceMappingURL=chunk-MHFCZGRW.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../src/core/setup.ts"],"sourcesContent":["import { execFileSync } from \"node:child_process\";\nimport { input, select } from \"@inquirer/prompts\";\nimport type { Config, ConfigManager } from \"./config.js\";\nimport { expandHome } from \"./config.js\";\nimport { commandExists } from \"./agent-dependencies.js\";\nimport type { DiscordChannelConfig } from \"../adapters/discord/types.js\";\n\n// --- ANSI colors ---\n\nconst c = {\n reset: \"\\x1b[0m\",\n bold: \"\\x1b[1m\",\n dim: \"\\x1b[2m\",\n green: \"\\x1b[32m\",\n yellow: \"\\x1b[33m\",\n red: \"\\x1b[31m\",\n cyan: \"\\x1b[36m\",\n white: \"\\x1b[37m\",\n};\n\nconst ok = (msg: string) =>\n `${c.green}${c.bold}✓${c.reset} ${c.green}${msg}${c.reset}`;\nconst warn = (msg: string) => `${c.yellow}⚠ ${msg}${c.reset}`;\nconst fail = (msg: string) => `${c.red}✗ ${msg}${c.reset}`;\nconst step = (n: number, total: number, title: string) =>\n `\\n${c.cyan}${c.bold}[${n}/${total}]${c.reset} ${c.bold}${title}${c.reset}\\n`;\nconst dim = (msg: string) => `${c.dim}${msg}${c.reset}`;\n\n// --- Telegram validation ---\n\nexport async function validateBotToken(\n token: string,\n): Promise<\n | { ok: true; botName: string; botUsername: string }\n | { ok: false; error: string }\n> {\n try {\n const res = await fetch(`https://api.telegram.org/bot${token}/getMe`);\n const data = (await res.json()) as {\n ok: boolean;\n result?: { first_name: string; username: string };\n description?: string;\n };\n if (data.ok && data.result) {\n return {\n ok: true,\n botName: data.result.first_name,\n botUsername: data.result.username,\n };\n }\n return { ok: false, error: data.description || \"Invalid token\" };\n } catch (err) {\n return { ok: false, error: (err as Error).message };\n }\n}\n\nexport async function validateChatId(\n token: string,\n chatId: number,\n): Promise<\n { ok: true; title: string; isForum: boolean } | { ok: false; error: string }\n> {\n try {\n const res = await fetch(`https://api.telegram.org/bot${token}/getChat`, {\n method: \"POST\",\n headers: { \"Content-Type\": \"application/json\" },\n body: JSON.stringify({ chat_id: chatId }),\n });\n const data = (await res.json()) as {\n ok: boolean;\n result?: { title: string; type: string; is_forum?: boolean };\n description?: string;\n };\n if (!data.ok || !data.result) {\n return { ok: false, error: data.description || \"Invalid chat ID\" };\n }\n if (data.result.type !== \"supergroup\") {\n return {\n ok: false,\n error: `Chat is \"${data.result.type}\", must be a supergroup`,\n };\n }\n return {\n ok: true,\n title: data.result.title,\n isForum: data.result.is_forum === true,\n };\n } catch (err) {\n return { ok: false, error: (err as Error).message };\n }\n}\n\nexport async function validateBotAdmin(\n token: string,\n chatId: number,\n): Promise<{ ok: true } | { ok: false; error: string }> {\n try {\n // Get bot's own user ID\n const meRes = await fetch(`https://api.telegram.org/bot${token}/getMe`);\n const meData = (await meRes.json()) as {\n ok: boolean;\n result?: { id: number };\n };\n if (!meData.ok || !meData.result) {\n return { ok: false, error: \"Could not retrieve bot info\" };\n }\n\n const res = await fetch(\n `https://api.telegram.org/bot${token}/getChatMember`,\n {\n method: \"POST\",\n headers: { \"Content-Type\": \"application/json\" },\n body: JSON.stringify({ chat_id: chatId, user_id: meData.result.id }),\n },\n );\n const data = (await res.json()) as {\n ok: boolean;\n result?: { status: string };\n description?: string;\n };\n if (!data.ok || !data.result) {\n return {\n ok: false,\n error: data.description || \"Could not check bot membership\",\n };\n }\n\n const { status } = data.result;\n if (status === \"administrator\" || status === \"creator\") {\n return { ok: true };\n }\n return {\n ok: false,\n error: `Bot is \"${status}\" in this group. It must be an admin. Please promote the bot to admin in group settings.`,\n };\n } catch (err) {\n return { ok: false, error: (err as Error).message };\n }\n}\n\n// --- Chat ID auto-detection ---\n\nfunction promptManualChatId(): Promise<number> {\n return input({\n message: \"Supergroup chat ID (e.g. -1001234567890):\",\n validate: (val) => {\n const n = Number(val.trim());\n if (isNaN(n) || !Number.isInteger(n)) return \"Chat ID must be an integer\";\n return true;\n },\n }).then((val) => Number(val.trim()));\n}\n\nasync function detectChatId(token: string): Promise<number> {\n // Clear old updates\n let lastUpdateId = 0;\n try {\n const clearRes = await fetch(\n `https://api.telegram.org/bot${token}/getUpdates?offset=-1`,\n );\n const clearData = (await clearRes.json()) as {\n ok: boolean;\n result?: Array<{ update_id: number }>;\n };\n if (clearData.ok && clearData.result?.length) {\n lastUpdateId = clearData.result[clearData.result.length - 1].update_id;\n }\n } catch {\n // ignore\n }\n\n console.log(\"\");\n console.log(` ${c.bold}If you don't have a supergroup yet:${c.reset}`);\n console.log(dim(\" 1. Open Telegram → New Group → add your bot\"));\n console.log(dim(\" 2. Group Settings → convert to Supergroup\"));\n console.log(dim(\" 3. Enable Topics in group settings\"));\n console.log(\"\");\n console.log(` ${c.bold}Then send \"hi\" in the group.${c.reset}`);\n console.log(\n dim(\n ` Listening... press ${c.reset}${c.yellow}m${c.reset}${c.dim} to enter ID manually`,\n ),\n );\n console.log(\"\");\n\n const MAX_ATTEMPTS = 120;\n const POLL_INTERVAL = 1000;\n\n // Listen for 'm' keypress to switch to manual\n let cancelled = false;\n const onKeypress = (data: Buffer) => {\n const key = data.toString();\n if (key === \"m\" || key === \"M\") {\n cancelled = true;\n }\n };\n if (process.stdin.isTTY) {\n process.stdin.setRawMode(true);\n process.stdin.resume();\n process.stdin.on(\"data\", onKeypress);\n }\n\n const cleanup = () => {\n if (process.stdin.isTTY) {\n process.stdin.removeListener(\"data\", onKeypress);\n process.stdin.setRawMode(false);\n process.stdin.pause();\n }\n };\n\n try {\n for (let i = 0; i < MAX_ATTEMPTS; i++) {\n if (cancelled) {\n cleanup();\n return promptManualChatId();\n }\n\n try {\n const offset = lastUpdateId ? lastUpdateId + 1 : 0;\n const res = await fetch(\n `https://api.telegram.org/bot${token}/getUpdates?offset=${offset}&timeout=1`,\n );\n const data = (await res.json()) as {\n ok: boolean;\n result?: Array<{\n update_id: number;\n message?: {\n chat: { id: number; title?: string; type: string };\n };\n my_chat_member?: {\n chat: { id: number; title?: string; type: string };\n };\n }>;\n };\n\n if (!data.ok || !data.result?.length) {\n await new Promise((r) => setTimeout(r, POLL_INTERVAL));\n continue;\n }\n\n const groups = new Map<number, string>();\n for (const update of data.result) {\n lastUpdateId = update.update_id;\n const chat = update.message?.chat ?? update.my_chat_member?.chat;\n if (chat && (chat.type === \"supergroup\" || chat.type === \"group\")) {\n groups.set(chat.id, chat.title ?? String(chat.id));\n }\n }\n\n if (groups.size === 1) {\n const [id, title] = [...groups.entries()][0];\n console.log(\n ok(`Group detected: ${c.bold}${title}${c.reset}${c.green} (${id})`),\n );\n cleanup();\n return id;\n }\n\n if (groups.size > 1) {\n cleanup();\n const choices = [...groups.entries()].map(([id, title]) => ({\n name: `${title} (${id})`,\n value: id,\n }));\n return select({\n message: \"Multiple groups found. Pick one:\",\n choices,\n });\n }\n } catch {\n // Network error, retry\n }\n await new Promise((r) => setTimeout(r, POLL_INTERVAL));\n }\n\n console.log(warn(\"Timed out waiting for messages.\"));\n cleanup();\n return promptManualChatId();\n } catch (err) {\n cleanup();\n throw err;\n }\n}\n\n// --- Agent detection ---\n\nconst KNOWN_AGENTS: Array<{ name: string; commands: string[] }> = [\n // claude-agent-acp is bundled as a dependency — no detection needed, but\n // kept here so detectAgents() still returns it for display purposes.\n { name: \"claude\", commands: [\"claude-agent-acp\"] },\n { name: \"codex\", commands: [\"codex\"] },\n];\n\nexport async function detectAgents(): Promise<\n Array<{ name: string; command: string }>\n> {\n const found: Array<{ name: string; command: string }> = [];\n for (const agent of KNOWN_AGENTS) {\n // Find all available commands for this agent (PATH + node_modules/.bin)\n const available: string[] = [];\n for (const cmd of agent.commands) {\n if (commandExists(cmd)) {\n available.push(cmd);\n }\n }\n if (available.length > 0) {\n // Prefer claude-agent-acp over claude/claude-code (priority order)\n found.push({ name: agent.name, command: available[0] });\n }\n }\n return found;\n}\n\nexport async function validateAgentCommand(command: string): Promise<boolean> {\n try {\n execFileSync(\"which\", [command], { stdio: \"pipe\" });\n return true;\n } catch {\n return false;\n }\n}\n\n// --- Setup steps ---\n\nexport async function setupTelegram(stepNum = 1, totalSteps = 3): Promise<Config[\"channels\"][string]> {\n console.log(step(stepNum, totalSteps, \"Telegram Bot\"));\n\n let botToken = \"\";\n\n while (true) {\n botToken = await input({\n message: \"Bot token (from @BotFather):\",\n validate: (val) => val.trim().length > 0 || \"Token cannot be empty\",\n });\n botToken = botToken.trim();\n\n const result = await validateBotToken(botToken);\n if (result.ok) {\n console.log(ok(`Connected to @${result.botUsername}`));\n break;\n }\n console.log(fail(result.error));\n const action = await select({\n message: \"What to do?\",\n choices: [\n { name: \"Re-enter token\", value: \"retry\" },\n { name: \"Use as-is (skip validation)\", value: \"skip\" },\n ],\n });\n if (action === \"skip\") break;\n }\n\n let chatId: number;\n\n while (true) {\n chatId = await detectChatId(botToken);\n\n // Validate bot can access this chat and it's a supergroup\n const chatResult = await validateChatId(botToken, chatId);\n if (!chatResult.ok) {\n console.log(fail(chatResult.error));\n console.log(\"\");\n console.log(` ${c.bold}How to fix:${c.reset}`);\n console.log(dim(\" 1. Make sure the bot is added to the group\"));\n console.log(dim(\" 2. The group must be a Supergroup (Group Settings → convert)\"));\n console.log(dim(\" 3. Send a message in the group after adding the bot\"));\n console.log(\"\");\n await input({ message: \"Press Enter to try again...\" });\n continue;\n }\n console.log(\n ok(\n `Group: ${c.bold}${chatResult.title}${c.reset}${c.green}${chatResult.isForum ? \" (Topics enabled)\" : \"\"}`,\n ),\n );\n\n // Check bot has admin privileges\n const adminResult = await validateBotAdmin(botToken, chatId);\n if (!adminResult.ok) {\n console.log(fail(adminResult.error));\n console.log(\"\");\n console.log(` ${c.bold}How to fix:${c.reset}`);\n console.log(dim(\" 1. Open the group in Telegram\"));\n console.log(dim(\" 2. Go to Group Settings → Administrators\"));\n console.log(dim(\" 3. Add the bot as an administrator\"));\n console.log(\"\");\n await input({ message: \"Press Enter to check again...\" });\n continue;\n }\n console.log(ok(\"Bot has admin privileges\"));\n break;\n }\n\n return {\n enabled: true,\n botToken,\n chatId,\n notificationTopicId: null,\n assistantTopicId: null,\n };\n}\n\n// --- Discord validation ---\n\nexport async function validateDiscordToken(token: string): Promise<\n | { ok: true; username: string; id: string }\n | { ok: false; error: string }\n> {\n try {\n const res = await fetch(\"https://discord.com/api/v10/users/@me\", {\n headers: { Authorization: `Bot ${token}` },\n });\n if (res.status === 200) {\n const data = (await res.json()) as { username: string; id: string };\n return { ok: true, username: data.username, id: data.id };\n }\n if (res.status === 401) {\n return { ok: false, error: \"Token rejected by Discord (401 Unauthorized)\" };\n }\n return { ok: false, error: `Discord API returned ${res.status}` };\n } catch (err) {\n return { ok: false, error: (err as Error).message };\n }\n}\n\nexport async function setupDiscord(): Promise<DiscordChannelConfig> {\n console.log('\\n📱 Discord Setup\\n');\n\n console.log(` ${c.bold}Quick setup:${c.reset}`);\n console.log(dim(' 1. Create app at https://discord.com/developers/applications'));\n console.log(dim(' 2. Go to Bot → Reset Token → copy it'));\n console.log(dim(' 3. Enable Message Content Intent (Bot → Privileged Intents)'));\n console.log(dim(' 4. OAuth2 → URL Generator → scopes: bot + applications.commands'));\n console.log(dim(' 5. Bot Permissions: Manage Channels, Send Messages, Manage Threads, Attach Files'));\n console.log(dim(' 6. Open generated URL → invite bot to your server'));\n console.log('');\n console.log(dim(` 📖 Detailed guide: https://github.com/Open-ACP/OpenACP/blob/main/docs/guide/discord-setup.md`));\n console.log('');\n\n let botToken = '';\n\n while (true) {\n botToken = await input({\n message: 'Bot token (from Discord Developer Portal):',\n validate: (val) => val.trim().length > 0 || 'Token cannot be empty',\n });\n botToken = botToken.trim();\n\n const result = await validateDiscordToken(botToken);\n if (result.ok) {\n console.log(ok(`Connected as @${result.username} (id: ${result.id})`));\n break;\n }\n console.log(fail(result.error));\n const action = await select({\n message: 'What to do?',\n choices: [\n { name: 'Re-enter token', value: 'retry' },\n { name: 'Use as-is (skip validation)', value: 'skip' },\n ],\n });\n if (action === 'skip') break;\n }\n\n const guildId = await input({\n message: 'Guild (server) ID:',\n validate: (val) => {\n const trimmed = val.trim();\n if (!trimmed) return 'Guild ID cannot be empty';\n if (!/^\\d{17,20}$/.test(trimmed)) return 'Guild ID must be a numeric Discord snowflake (17-20 digits)';\n return true;\n },\n });\n\n return {\n enabled: true,\n botToken,\n guildId: guildId.trim(),\n forumChannelId: null,\n notificationChannelId: null,\n assistantThreadId: null,\n };\n}\n\nexport async function setupAgents(): Promise<{\n defaultAgent: string;\n}> {\n const { AgentCatalog } = await import(\"./agent-catalog.js\");\n const { select, checkbox } = await import(\"@inquirer/prompts\");\n\n const catalog = new AgentCatalog();\n catalog.load();\n\n console.log(dim(\" Checking available agents...\"));\n await catalog.refreshRegistryIfStale();\n\n // Claude is always pre-installed (bundled dependency)\n if (!catalog.getInstalledAgent(\"claude\")) {\n const claudeRegistry = catalog.findRegistryAgent(\"claude-acp\");\n if (claudeRegistry) {\n await catalog.install(\"claude-acp\");\n } else {\n // Fallback: register bundled claude-agent-acp directly\n const { AgentStore } = await import(\"./agent-store.js\");\n const store = new AgentStore();\n store.load();\n store.addAgent(\"claude\", {\n registryId: \"claude-acp\",\n name: \"Claude Agent\",\n version: \"bundled\",\n distribution: \"npx\",\n command: \"npx\",\n args: [\"@zed-industries/claude-agent-acp\"],\n env: {},\n installedAt: new Date().toISOString(),\n binaryPath: null,\n });\n }\n }\n console.log(ok(\"Claude Agent ready\"));\n\n const available = catalog.getAvailable();\n const installed = available.filter((a) => a.installed);\n const installable = available.filter((a) => !a.installed && a.available);\n\n // Offer agent selection — show installed agents as pre-checked + disabled\n if (installed.length > 0 || installable.length > 0) {\n const choices = [\n ...installed.map((a) => ({\n name: `${a.name} (installed)`,\n value: a.key,\n checked: true,\n disabled: \"(already installed)\",\n })),\n ...installable.slice(0, 10).map((a) => ({\n name: `${a.name} (${a.distribution})`,\n value: a.key,\n checked: false,\n })),\n ];\n\n const selected = await checkbox({\n message: \"Install additional agents? (Space to select, Enter to continue)\",\n choices,\n });\n\n for (const key of selected) {\n const regAgent = catalog.findRegistryAgent(key);\n if (regAgent) {\n process.stdout.write(` Installing ${regAgent.name}... `);\n const result = await catalog.install(key);\n if (result.ok) {\n console.log(ok(\"done\"));\n } else {\n console.log(warn(`skipped: ${result.error}`));\n }\n }\n }\n }\n\n // Choose default agent\n const installedAgents = Object.keys(catalog.getInstalledEntries());\n let defaultAgent = \"claude\";\n\n if (installedAgents.length > 1) {\n defaultAgent = await select({\n message: \"Which agent should be the default?\",\n choices: installedAgents.map((key) => {\n const agent = catalog.getInstalledAgent(key)!;\n return { name: `${agent.name} (${key})`, value: key };\n }),\n default: \"claude\",\n });\n }\n\n console.log(ok(`Default agent: ${c.bold}${defaultAgent}${c.reset}`));\n return { defaultAgent };\n}\n\nexport async function setupWorkspace(stepNum = 2, totalSteps = 3): Promise<{ baseDir: string }> {\n console.log(step(stepNum, totalSteps, \"Workspace\"));\n\n const baseDir = await input({\n message: \"Base directory for workspaces:\",\n default: \"~/openacp-workspace\",\n validate: (val) => val.trim().length > 0 || \"Path cannot be empty\",\n });\n\n return { baseDir: baseDir.trim().replace(/^['\"]|['\"]$/g, \"\") };\n}\n\nexport async function setupRunMode(stepNum = 3, totalSteps = 3): Promise<{ runMode: 'foreground' | 'daemon'; autoStart: boolean }> {\n console.log(step(stepNum, totalSteps, 'Run Mode'))\n\n // Don't show daemon option on Windows\n if (process.platform === 'win32') {\n console.log(dim(' (Daemon mode not available on Windows)'))\n return { runMode: 'foreground', autoStart: false }\n }\n\n const mode = await select({\n message: 'How would you like to run OpenACP?',\n choices: [\n {\n name: 'Background (daemon)',\n value: 'daemon' as const,\n description: 'Runs silently, auto-starts on boot. Manage with: openacp status | stop | logs',\n },\n {\n name: 'Foreground (terminal)',\n value: 'foreground' as const,\n description: 'Runs in current terminal session. Start with: openacp',\n },\n ],\n })\n\n if (mode === 'daemon') {\n const { installAutoStart, isAutoStartSupported } = await import('./autostart.js')\n const autoStart = isAutoStartSupported()\n if (autoStart) {\n const result = installAutoStart(expandHome('~/.openacp/logs'))\n if (result.success) {\n console.log(ok('Auto-start on boot enabled'))\n } else {\n console.log(warn(`Auto-start failed: ${result.error}`))\n }\n }\n return { runMode: 'daemon', autoStart }\n }\n\n return { runMode: 'foreground', autoStart: false }\n}\n\n// --- Orchestrator ---\n\nfunction printWelcomeBanner(): void {\n console.log(`\n${c.cyan}${c.bold} ╔══════════════════════════════╗\n ║ Welcome to OpenACP ║\n ╚══════════════════════════════╝${c.reset}\n`);\n}\n\nexport async function runSetup(configManager: ConfigManager): Promise<boolean> {\n printWelcomeBanner();\n\n try {\n const { select: selectChannel } = await import(\"@inquirer/prompts\");\n const channelChoice = await selectChannel({\n message: 'Which messaging platform do you want to use?',\n choices: [\n { name: 'Telegram', value: 'telegram' },\n { name: 'Discord', value: 'discord' },\n { name: 'Both', value: 'both' },\n ],\n });\n\n let telegram: Config[\"channels\"][string] | undefined;\n let discord: DiscordChannelConfig | undefined;\n\n // Calculate total steps dynamically: channel(s) + workspace + run mode\n const channelSteps = channelChoice === 'both' ? 2 : 1;\n const totalSteps = channelSteps + 2; // + workspace + run mode\n\n let currentStep = 0;\n\n if (channelChoice === 'telegram' || channelChoice === 'both') {\n currentStep++;\n telegram = await setupTelegram(currentStep, totalSteps);\n }\n if (channelChoice === 'discord' || channelChoice === 'both') {\n currentStep++;\n discord = await setupDiscord();\n }\n\n const { defaultAgent } = await setupAgents();\n\n // Offer Claude CLI integration\n {\n const { confirm } = await import(\"@inquirer/prompts\");\n const installClaude = await confirm({\n message: \"Install session transfer for Claude? (enables /openacp:handoff in your terminal)\",\n default: true,\n });\n\n if (installClaude) {\n try {\n const { getIntegration } = await import(\"../cli/integrate.js\");\n const integration = getIntegration(\"claude\");\n if (integration) {\n for (const item of integration.items) {\n const result = await item.install();\n for (const log of result.logs) console.log(` ${log}`);\n }\n }\n console.log(\"Claude CLI integration installed.\\n\");\n } catch (err) {\n console.log(`Could not install Claude CLI integration: ${err instanceof Error ? err.message : err}`);\n console.log(\" You can install it later with: openacp integrate claude\\n\");\n }\n }\n }\n\n currentStep++;\n const workspace = await setupWorkspace(currentStep, totalSteps);\n currentStep++;\n const { runMode, autoStart } = await setupRunMode(currentStep, totalSteps);\n const security = {\n allowedUserIds: [] as string[],\n maxConcurrentSessions: 20,\n sessionTimeoutMinutes: 60,\n };\n\n const channels: Config[\"channels\"] = {};\n if (telegram) channels.telegram = telegram;\n if (discord) channels.discord = discord as unknown as Config[\"channels\"][string];\n\n const config: Config = {\n channels,\n agents: {},\n defaultAgent,\n workspace,\n security,\n logging: {\n level: \"info\",\n logDir: \"~/.openacp/logs\",\n maxFileSize: \"10m\",\n maxFiles: 7,\n sessionLogRetentionDays: 30,\n },\n runMode,\n autoStart,\n api: {\n port: 21420,\n host: '127.0.0.1',\n },\n sessionStore: { ttlDays: 30 },\n tunnel: {\n enabled: true,\n port: 3100,\n provider: \"cloudflare\",\n options: {},\n maxUserTunnels: 5,\n storeTtlMinutes: 60,\n auth: { enabled: false },\n },\n integrations: {},\n };\n\n try {\n await configManager.writeNew(config);\n } catch (writeErr) {\n console.log(\n fail(`Could not save config: ${(writeErr as Error).message}`),\n );\n return false;\n }\n\n console.log(\"\");\n console.log(\n ok(`Config saved to ${c.bold}${configManager.getConfigPath()}`),\n );\n\n // Pre-download cloudflared if tunnel enabled\n if (config.tunnel.enabled && config.tunnel.provider === \"cloudflare\") {\n console.log(dim(\" Ensuring cloudflared is installed...\"));\n try {\n const { ensureCloudflared } = await import(\n \"../tunnel/providers/install-cloudflared.js\"\n );\n const binPath = await ensureCloudflared();\n console.log(ok(`cloudflared ready at ${dim(binPath)}`));\n } catch (err) {\n console.log(\n warn(\n `Could not install cloudflared: ${(err as Error).message}. Tunnel may not work.`,\n ),\n );\n }\n }\n\n console.log(ok(\"Starting OpenACP...\"));\n console.log(\"\");\n\n return true;\n } catch (err) {\n if ((err as Error).name === \"ExitPromptError\") {\n console.log(dim(\"\\nSetup cancelled.\"));\n return false;\n }\n throw err;\n }\n}\n"],"mappings":";;;;;;;;AAAA,SAAS,oBAAoB;AAC7B,SAAS,OAAO,cAAc;AAQ9B,IAAM,IAAI;AAAA,EACR,OAAO;AAAA,EACP,MAAM;AAAA,EACN,KAAK;AAAA,EACL,OAAO;AAAA,EACP,QAAQ;AAAA,EACR,KAAK;AAAA,EACL,MAAM;AAAA,EACN,OAAO;AACT;AAEA,IAAM,KAAK,CAAC,QACV,GAAG,EAAE,KAAK,GAAG,EAAE,IAAI,SAAI,EAAE,KAAK,IAAI,EAAE,KAAK,GAAG,GAAG,GAAG,EAAE,KAAK;AAC3D,IAAM,OAAO,CAAC,QAAgB,GAAG,EAAE,MAAM,UAAK,GAAG,GAAG,EAAE,KAAK;AAC3D,IAAM,OAAO,CAAC,QAAgB,GAAG,EAAE,GAAG,UAAK,GAAG,GAAG,EAAE,KAAK;AACxD,IAAM,OAAO,CAAC,GAAW,OAAe,UACtC;AAAA,EAAK,EAAE,IAAI,GAAG,EAAE,IAAI,IAAI,CAAC,IAAI,KAAK,IAAI,EAAE,KAAK,IAAI,EAAE,IAAI,GAAG,KAAK,GAAG,EAAE,KAAK;AAAA;AAC3E,IAAM,MAAM,CAAC,QAAgB,GAAG,EAAE,GAAG,GAAG,GAAG,GAAG,EAAE,KAAK;AAIrD,eAAsB,iBACpB,OAIA;AACA,MAAI;AACF,UAAM,MAAM,MAAM,MAAM,+BAA+B,KAAK,QAAQ;AACpE,UAAM,OAAQ,MAAM,IAAI,KAAK;AAK7B,QAAI,KAAK,MAAM,KAAK,QAAQ;AAC1B,aAAO;AAAA,QACL,IAAI;AAAA,QACJ,SAAS,KAAK,OAAO;AAAA,QACrB,aAAa,KAAK,OAAO;AAAA,MAC3B;AAAA,IACF;AACA,WAAO,EAAE,IAAI,OAAO,OAAO,KAAK,eAAe,gBAAgB;AAAA,EACjE,SAAS,KAAK;AACZ,WAAO,EAAE,IAAI,OAAO,OAAQ,IAAc,QAAQ;AAAA,EACpD;AACF;AAEA,eAAsB,eACpB,OACA,QAGA;AACA,MAAI;AACF,UAAM,MAAM,MAAM,MAAM,+BAA+B,KAAK,YAAY;AAAA,MACtE,QAAQ;AAAA,MACR,SAAS,EAAE,gBAAgB,mBAAmB;AAAA,MAC9C,MAAM,KAAK,UAAU,EAAE,SAAS,OAAO,CAAC;AAAA,IAC1C,CAAC;AACD,UAAM,OAAQ,MAAM,IAAI,KAAK;AAK7B,QAAI,CAAC,KAAK,MAAM,CAAC,KAAK,QAAQ;AAC5B,aAAO,EAAE,IAAI,OAAO,OAAO,KAAK,eAAe,kBAAkB;AAAA,IACnE;AACA,QAAI,KAAK,OAAO,SAAS,cAAc;AACrC,aAAO;AAAA,QACL,IAAI;AAAA,QACJ,OAAO,YAAY,KAAK,OAAO,IAAI;AAAA,MACrC;AAAA,IACF;AACA,WAAO;AAAA,MACL,IAAI;AAAA,MACJ,OAAO,KAAK,OAAO;AAAA,MACnB,SAAS,KAAK,OAAO,aAAa;AAAA,IACpC;AAAA,EACF,SAAS,KAAK;AACZ,WAAO,EAAE,IAAI,OAAO,OAAQ,IAAc,QAAQ;AAAA,EACpD;AACF;AAEA,eAAsB,iBACpB,OACA,QACsD;AACtD,MAAI;AAEF,UAAM,QAAQ,MAAM,MAAM,+BAA+B,KAAK,QAAQ;AACtE,UAAM,SAAU,MAAM,MAAM,KAAK;AAIjC,QAAI,CAAC,OAAO,MAAM,CAAC,OAAO,QAAQ;AAChC,aAAO,EAAE,IAAI,OAAO,OAAO,8BAA8B;AAAA,IAC3D;AAEA,UAAM,MAAM,MAAM;AAAA,MAChB,+BAA+B,KAAK;AAAA,MACpC;AAAA,QACE,QAAQ;AAAA,QACR,SAAS,EAAE,gBAAgB,mBAAmB;AAAA,QAC9C,MAAM,KAAK,UAAU,EAAE,SAAS,QAAQ,SAAS,OAAO,OAAO,GAAG,CAAC;AAAA,MACrE;AAAA,IACF;AACA,UAAM,OAAQ,MAAM,IAAI,KAAK;AAK7B,QAAI,CAAC,KAAK,MAAM,CAAC,KAAK,QAAQ;AAC5B,aAAO;AAAA,QACL,IAAI;AAAA,QACJ,OAAO,KAAK,eAAe;AAAA,MAC7B;AAAA,IACF;AAEA,UAAM,EAAE,OAAO,IAAI,KAAK;AACxB,QAAI,WAAW,mBAAmB,WAAW,WAAW;AACtD,aAAO,EAAE,IAAI,KAAK;AAAA,IACpB;AACA,WAAO;AAAA,MACL,IAAI;AAAA,MACJ,OAAO,WAAW,MAAM;AAAA,IAC1B;AAAA,EACF,SAAS,KAAK;AACZ,WAAO,EAAE,IAAI,OAAO,OAAQ,IAAc,QAAQ;AAAA,EACpD;AACF;AAIA,SAAS,qBAAsC;AAC7C,SAAO,MAAM;AAAA,IACX,SAAS;AAAA,IACT,UAAU,CAAC,QAAQ;AACjB,YAAM,IAAI,OAAO,IAAI,KAAK,CAAC;AAC3B,UAAI,MAAM,CAAC,KAAK,CAAC,OAAO,UAAU,CAAC,EAAG,QAAO;AAC7C,aAAO;AAAA,IACT;AAAA,EACF,CAAC,EAAE,KAAK,CAAC,QAAQ,OAAO,IAAI,KAAK,CAAC,CAAC;AACrC;AAEA,eAAe,aAAa,OAAgC;AAE1D,MAAI,eAAe;AACnB,MAAI;AACF,UAAM,WAAW,MAAM;AAAA,MACrB,+BAA+B,KAAK;AAAA,IACtC;AACA,UAAM,YAAa,MAAM,SAAS,KAAK;AAIvC,QAAI,UAAU,MAAM,UAAU,QAAQ,QAAQ;AAC5C,qBAAe,UAAU,OAAO,UAAU,OAAO,SAAS,CAAC,EAAE;AAAA,IAC/D;AAAA,EACF,QAAQ;AAAA,EAER;AAEA,UAAQ,IAAI,EAAE;AACd,UAAQ,IAAI,KAAK,EAAE,IAAI,sCAAsC,EAAE,KAAK,EAAE;AACtE,UAAQ,IAAI,IAAI,yDAA+C,CAAC;AAChE,UAAQ,IAAI,IAAI,kDAA6C,CAAC;AAC9D,UAAQ,IAAI,IAAI,sCAAsC,CAAC;AACvD,UAAQ,IAAI,EAAE;AACd,UAAQ,IAAI,KAAK,EAAE,IAAI,+BAA+B,EAAE,KAAK,EAAE;AAC/D,UAAQ;AAAA,IACN;AAAA,MACE,wBAAwB,EAAE,KAAK,GAAG,EAAE,MAAM,IAAI,EAAE,KAAK,GAAG,EAAE,GAAG;AAAA,IAC/D;AAAA,EACF;AACA,UAAQ,IAAI,EAAE;AAEd,QAAM,eAAe;AACrB,QAAM,gBAAgB;AAGtB,MAAI,YAAY;AAChB,QAAM,aAAa,CAAC,SAAiB;AACnC,UAAM,MAAM,KAAK,SAAS;AAC1B,QAAI,QAAQ,OAAO,QAAQ,KAAK;AAC9B,kBAAY;AAAA,IACd;AAAA,EACF;AACA,MAAI,QAAQ,MAAM,OAAO;AACvB,YAAQ,MAAM,WAAW,IAAI;AAC7B,YAAQ,MAAM,OAAO;AACrB,YAAQ,MAAM,GAAG,QAAQ,UAAU;AAAA,EACrC;AAEA,QAAM,UAAU,MAAM;AACpB,QAAI,QAAQ,MAAM,OAAO;AACvB,cAAQ,MAAM,eAAe,QAAQ,UAAU;AAC/C,cAAQ,MAAM,WAAW,KAAK;AAC9B,cAAQ,MAAM,MAAM;AAAA,IACtB;AAAA,EACF;AAEA,MAAI;AACF,aAAS,IAAI,GAAG,IAAI,cAAc,KAAK;AACrC,UAAI,WAAW;AACb,gBAAQ;AACR,eAAO,mBAAmB;AAAA,MAC5B;AAEA,UAAI;AACF,cAAM,SAAS,eAAe,eAAe,IAAI;AACjD,cAAM,MAAM,MAAM;AAAA,UAChB,+BAA+B,KAAK,sBAAsB,MAAM;AAAA,QAClE;AACA,cAAM,OAAQ,MAAM,IAAI,KAAK;AAa7B,YAAI,CAAC,KAAK,MAAM,CAAC,KAAK,QAAQ,QAAQ;AACpC,gBAAM,IAAI,QAAQ,CAAC,MAAM,WAAW,GAAG,aAAa,CAAC;AACrD;AAAA,QACF;AAEA,cAAM,SAAS,oBAAI,IAAoB;AACvC,mBAAW,UAAU,KAAK,QAAQ;AAChC,yBAAe,OAAO;AACtB,gBAAM,OAAO,OAAO,SAAS,QAAQ,OAAO,gBAAgB;AAC5D,cAAI,SAAS,KAAK,SAAS,gBAAgB,KAAK,SAAS,UAAU;AACjE,mBAAO,IAAI,KAAK,IAAI,KAAK,SAAS,OAAO,KAAK,EAAE,CAAC;AAAA,UACnD;AAAA,QACF;AAEA,YAAI,OAAO,SAAS,GAAG;AACrB,gBAAM,CAAC,IAAI,KAAK,IAAI,CAAC,GAAG,OAAO,QAAQ,CAAC,EAAE,CAAC;AAC3C,kBAAQ;AAAA,YACN,GAAG,mBAAmB,EAAE,IAAI,GAAG,KAAK,GAAG,EAAE,KAAK,GAAG,EAAE,KAAK,KAAK,EAAE,GAAG;AAAA,UACpE;AACA,kBAAQ;AACR,iBAAO;AAAA,QACT;AAEA,YAAI,OAAO,OAAO,GAAG;AACnB,kBAAQ;AACR,gBAAM,UAAU,CAAC,GAAG,OAAO,QAAQ,CAAC,EAAE,IAAI,CAAC,CAAC,IAAI,KAAK,OAAO;AAAA,YAC1D,MAAM,GAAG,KAAK,KAAK,EAAE;AAAA,YACrB,OAAO;AAAA,UACT,EAAE;AACF,iBAAO,OAAO;AAAA,YACZ,SAAS;AAAA,YACT;AAAA,UACF,CAAC;AAAA,QACH;AAAA,MACF,QAAQ;AAAA,MAER;AACA,YAAM,IAAI,QAAQ,CAAC,MAAM,WAAW,GAAG,aAAa,CAAC;AAAA,IACvD;AAEA,YAAQ,IAAI,KAAK,iCAAiC,CAAC;AACnD,YAAQ;AACR,WAAO,mBAAmB;AAAA,EAC5B,SAAS,KAAK;AACZ,YAAQ;AACR,UAAM;AAAA,EACR;AACF;AAIA,IAAM,eAA4D;AAAA;AAAA;AAAA,EAGhE,EAAE,MAAM,UAAU,UAAU,CAAC,kBAAkB,EAAE;AAAA,EACjD,EAAE,MAAM,SAAS,UAAU,CAAC,OAAO,EAAE;AACvC;AAEA,eAAsB,eAEpB;AACA,QAAM,QAAkD,CAAC;AACzD,aAAW,SAAS,cAAc;AAEhC,UAAM,YAAsB,CAAC;AAC7B,eAAW,OAAO,MAAM,UAAU;AAChC,UAAI,cAAc,GAAG,GAAG;AACtB,kBAAU,KAAK,GAAG;AAAA,MACpB;AAAA,IACF;AACA,QAAI,UAAU,SAAS,GAAG;AAExB,YAAM,KAAK,EAAE,MAAM,MAAM,MAAM,SAAS,UAAU,CAAC,EAAE,CAAC;AAAA,IACxD;AAAA,EACF;AACA,SAAO;AACT;AAEA,eAAsB,qBAAqB,SAAmC;AAC5E,MAAI;AACF,iBAAa,SAAS,CAAC,OAAO,GAAG,EAAE,OAAO,OAAO,CAAC;AAClD,WAAO;AAAA,EACT,QAAQ;AACN,WAAO;AAAA,EACT;AACF;AAIA,eAAsB,cAAc,UAAU,GAAG,aAAa,GAAwC;AACpG,UAAQ,IAAI,KAAK,SAAS,YAAY,cAAc,CAAC;AAErD,MAAI,WAAW;AAEf,SAAO,MAAM;AACX,eAAW,MAAM,MAAM;AAAA,MACrB,SAAS;AAAA,MACT,UAAU,CAAC,QAAQ,IAAI,KAAK,EAAE,SAAS,KAAK;AAAA,IAC9C,CAAC;AACD,eAAW,SAAS,KAAK;AAEzB,UAAM,SAAS,MAAM,iBAAiB,QAAQ;AAC9C,QAAI,OAAO,IAAI;AACb,cAAQ,IAAI,GAAG,iBAAiB,OAAO,WAAW,EAAE,CAAC;AACrD;AAAA,IACF;AACA,YAAQ,IAAI,KAAK,OAAO,KAAK,CAAC;AAC9B,UAAM,SAAS,MAAM,OAAO;AAAA,MAC1B,SAAS;AAAA,MACT,SAAS;AAAA,QACP,EAAE,MAAM,kBAAkB,OAAO,QAAQ;AAAA,QACzC,EAAE,MAAM,+BAA+B,OAAO,OAAO;AAAA,MACvD;AAAA,IACF,CAAC;AACD,QAAI,WAAW,OAAQ;AAAA,EACzB;AAEA,MAAI;AAEJ,SAAO,MAAM;AACX,aAAS,MAAM,aAAa,QAAQ;AAGpC,UAAM,aAAa,MAAM,eAAe,UAAU,MAAM;AACxD,QAAI,CAAC,WAAW,IAAI;AAClB,cAAQ,IAAI,KAAK,WAAW,KAAK,CAAC;AAClC,cAAQ,IAAI,EAAE;AACd,cAAQ,IAAI,KAAK,EAAE,IAAI,cAAc,EAAE,KAAK,EAAE;AAC9C,cAAQ,IAAI,IAAI,8CAA8C,CAAC;AAC/D,cAAQ,IAAI,IAAI,qEAAgE,CAAC;AACjF,cAAQ,IAAI,IAAI,uDAAuD,CAAC;AACxE,cAAQ,IAAI,EAAE;AACd,YAAM,MAAM,EAAE,SAAS,8BAA8B,CAAC;AACtD;AAAA,IACF;AACA,YAAQ;AAAA,MACN;AAAA,QACE,UAAU,EAAE,IAAI,GAAG,WAAW,KAAK,GAAG,EAAE,KAAK,GAAG,EAAE,KAAK,GAAG,WAAW,UAAU,sBAAsB,EAAE;AAAA,MACzG;AAAA,IACF;AAGA,UAAM,cAAc,MAAM,iBAAiB,UAAU,MAAM;AAC3D,QAAI,CAAC,YAAY,IAAI;AACnB,cAAQ,IAAI,KAAK,YAAY,KAAK,CAAC;AACnC,cAAQ,IAAI,EAAE;AACd,cAAQ,IAAI,KAAK,EAAE,IAAI,cAAc,EAAE,KAAK,EAAE;AAC9C,cAAQ,IAAI,IAAI,iCAAiC,CAAC;AAClD,cAAQ,IAAI,IAAI,iDAA4C,CAAC;AAC7D,cAAQ,IAAI,IAAI,sCAAsC,CAAC;AACvD,cAAQ,IAAI,EAAE;AACd,YAAM,MAAM,EAAE,SAAS,gCAAgC,CAAC;AACxD;AAAA,IACF;AACA,YAAQ,IAAI,GAAG,0BAA0B,CAAC;AAC1C;AAAA,EACF;AAEA,SAAO;AAAA,IACL,SAAS;AAAA,IACT;AAAA,IACA;AAAA,IACA,qBAAqB;AAAA,IACrB,kBAAkB;AAAA,EACpB;AACF;AAIA,eAAsB,qBAAqB,OAGzC;AACA,MAAI;AACF,UAAM,MAAM,MAAM,MAAM,yCAAyC;AAAA,MAC/D,SAAS,EAAE,eAAe,OAAO,KAAK,GAAG;AAAA,IAC3C,CAAC;AACD,QAAI,IAAI,WAAW,KAAK;AACtB,YAAM,OAAQ,MAAM,IAAI,KAAK;AAC7B,aAAO,EAAE,IAAI,MAAM,UAAU,KAAK,UAAU,IAAI,KAAK,GAAG;AAAA,IAC1D;AACA,QAAI,IAAI,WAAW,KAAK;AACtB,aAAO,EAAE,IAAI,OAAO,OAAO,+CAA+C;AAAA,IAC5E;AACA,WAAO,EAAE,IAAI,OAAO,OAAO,wBAAwB,IAAI,MAAM,GAAG;AAAA,EAClE,SAAS,KAAK;AACZ,WAAO,EAAE,IAAI,OAAO,OAAQ,IAAc,QAAQ;AAAA,EACpD;AACF;AAEA,eAAsB,eAA8C;AAClE,UAAQ,IAAI,6BAAsB;AAElC,UAAQ,IAAI,KAAK,EAAE,IAAI,eAAe,EAAE,KAAK,EAAE;AAC/C,UAAQ,IAAI,IAAI,gEAAgE,CAAC;AACjF,UAAQ,IAAI,IAAI,kDAAwC,CAAC;AACzD,UAAQ,IAAI,IAAI,oEAA+D,CAAC;AAChF,UAAQ,IAAI,IAAI,6EAAmE,CAAC;AACpF,UAAQ,IAAI,IAAI,oFAAoF,CAAC;AACrG,UAAQ,IAAI,IAAI,0DAAqD,CAAC;AACtE,UAAQ,IAAI,EAAE;AACd,UAAQ,IAAI,IAAI,uGAAgG,CAAC;AACjH,UAAQ,IAAI,EAAE;AAEd,MAAI,WAAW;AAEf,SAAO,MAAM;AACX,eAAW,MAAM,MAAM;AAAA,MACrB,SAAS;AAAA,MACT,UAAU,CAAC,QAAQ,IAAI,KAAK,EAAE,SAAS,KAAK;AAAA,IAC9C,CAAC;AACD,eAAW,SAAS,KAAK;AAEzB,UAAM,SAAS,MAAM,qBAAqB,QAAQ;AAClD,QAAI,OAAO,IAAI;AACb,cAAQ,IAAI,GAAG,iBAAiB,OAAO,QAAQ,SAAS,OAAO,EAAE,GAAG,CAAC;AACrE;AAAA,IACF;AACA,YAAQ,IAAI,KAAK,OAAO,KAAK,CAAC;AAC9B,UAAM,SAAS,MAAM,OAAO;AAAA,MAC1B,SAAS;AAAA,MACT,SAAS;AAAA,QACP,EAAE,MAAM,kBAAkB,OAAO,QAAQ;AAAA,QACzC,EAAE,MAAM,+BAA+B,OAAO,OAAO;AAAA,MACvD;AAAA,IACF,CAAC;AACD,QAAI,WAAW,OAAQ;AAAA,EACzB;AAEA,QAAM,UAAU,MAAM,MAAM;AAAA,IAC1B,SAAS;AAAA,IACT,UAAU,CAAC,QAAQ;AACjB,YAAM,UAAU,IAAI,KAAK;AACzB,UAAI,CAAC,QAAS,QAAO;AACrB,UAAI,CAAC,cAAc,KAAK,OAAO,EAAG,QAAO;AACzC,aAAO;AAAA,IACT;AAAA,EACF,CAAC;AAED,SAAO;AAAA,IACL,SAAS;AAAA,IACT;AAAA,IACA,SAAS,QAAQ,KAAK;AAAA,IACtB,gBAAgB;AAAA,IAChB,uBAAuB;AAAA,IACvB,mBAAmB;AAAA,EACrB;AACF;AAEA,eAAsB,cAEnB;AACD,QAAM,EAAE,aAAa,IAAI,MAAM,OAAO,6BAAoB;AAC1D,QAAM,EAAE,QAAAA,SAAQ,SAAS,IAAI,MAAM,OAAO,mBAAmB;AAE7D,QAAM,UAAU,IAAI,aAAa;AACjC,UAAQ,KAAK;AAEb,UAAQ,IAAI,IAAI,gCAAgC,CAAC;AACjD,QAAM,QAAQ,uBAAuB;AAGrC,MAAI,CAAC,QAAQ,kBAAkB,QAAQ,GAAG;AACxC,UAAM,iBAAiB,QAAQ,kBAAkB,YAAY;AAC7D,QAAI,gBAAgB;AAClB,YAAM,QAAQ,QAAQ,YAAY;AAAA,IACpC,OAAO;AAEL,YAAM,EAAE,WAAW,IAAI,MAAM,OAAO,2BAAkB;AACtD,YAAM,QAAQ,IAAI,WAAW;AAC7B,YAAM,KAAK;AACX,YAAM,SAAS,UAAU;AAAA,QACvB,YAAY;AAAA,QACZ,MAAM;AAAA,QACN,SAAS;AAAA,QACT,cAAc;AAAA,QACd,SAAS;AAAA,QACT,MAAM,CAAC,kCAAkC;AAAA,QACzC,KAAK,CAAC;AAAA,QACN,cAAa,oBAAI,KAAK,GAAE,YAAY;AAAA,QACpC,YAAY;AAAA,MACd,CAAC;AAAA,IACH;AAAA,EACF;AACA,UAAQ,IAAI,GAAG,oBAAoB,CAAC;AAEpC,QAAM,YAAY,QAAQ,aAAa;AACvC,QAAM,YAAY,UAAU,OAAO,CAAC,MAAM,EAAE,SAAS;AACrD,QAAM,cAAc,UAAU,OAAO,CAAC,MAAM,CAAC,EAAE,aAAa,EAAE,SAAS;AAGvE,MAAI,UAAU,SAAS,KAAK,YAAY,SAAS,GAAG;AAClD,UAAM,UAAU;AAAA,MACd,GAAG,UAAU,IAAI,CAAC,OAAO;AAAA,QACvB,MAAM,GAAG,EAAE,IAAI;AAAA,QACf,OAAO,EAAE;AAAA,QACT,SAAS;AAAA,QACT,UAAU;AAAA,MACZ,EAAE;AAAA,MACF,GAAG,YAAY,MAAM,GAAG,EAAE,EAAE,IAAI,CAAC,OAAO;AAAA,QACtC,MAAM,GAAG,EAAE,IAAI,KAAK,EAAE,YAAY;AAAA,QAClC,OAAO,EAAE;AAAA,QACT,SAAS;AAAA,MACX,EAAE;AAAA,IACJ;AAEA,UAAM,WAAW,MAAM,SAAS;AAAA,MAC9B,SAAS;AAAA,MACT;AAAA,IACF,CAAC;AAED,eAAW,OAAO,UAAU;AAC1B,YAAM,WAAW,QAAQ,kBAAkB,GAAG;AAC9C,UAAI,UAAU;AACZ,gBAAQ,OAAO,MAAM,gBAAgB,SAAS,IAAI,MAAM;AACxD,cAAM,SAAS,MAAM,QAAQ,QAAQ,GAAG;AACxC,YAAI,OAAO,IAAI;AACb,kBAAQ,IAAI,GAAG,MAAM,CAAC;AAAA,QACxB,OAAO;AACL,kBAAQ,IAAI,KAAK,YAAY,OAAO,KAAK,EAAE,CAAC;AAAA,QAC9C;AAAA,MACF;AAAA,IACF;AAAA,EACF;AAGA,QAAM,kBAAkB,OAAO,KAAK,QAAQ,oBAAoB,CAAC;AACjE,MAAI,eAAe;AAEnB,MAAI,gBAAgB,SAAS,GAAG;AAC9B,mBAAe,MAAMA,QAAO;AAAA,MAC1B,SAAS;AAAA,MACT,SAAS,gBAAgB,IAAI,CAAC,QAAQ;AACpC,cAAM,QAAQ,QAAQ,kBAAkB,GAAG;AAC3C,eAAO,EAAE,MAAM,GAAG,MAAM,IAAI,KAAK,GAAG,KAAK,OAAO,IAAI;AAAA,MACtD,CAAC;AAAA,MACD,SAAS;AAAA,IACX,CAAC;AAAA,EACH;AAEA,UAAQ,IAAI,GAAG,kBAAkB,EAAE,IAAI,GAAG,YAAY,GAAG,EAAE,KAAK,EAAE,CAAC;AACnE,SAAO,EAAE,aAAa;AACxB;AAEA,eAAsB,eAAe,UAAU,GAAG,aAAa,GAAiC;AAC9F,UAAQ,IAAI,KAAK,SAAS,YAAY,WAAW,CAAC;AAElD,QAAM,UAAU,MAAM,MAAM;AAAA,IAC1B,SAAS;AAAA,IACT,SAAS;AAAA,IACT,UAAU,CAAC,QAAQ,IAAI,KAAK,EAAE,SAAS,KAAK;AAAA,EAC9C,CAAC;AAED,SAAO,EAAE,SAAS,QAAQ,KAAK,EAAE,QAAQ,gBAAgB,EAAE,EAAE;AAC/D;AAEA,eAAsB,aAAa,UAAU,GAAG,aAAa,GAAsE;AACjI,UAAQ,IAAI,KAAK,SAAS,YAAY,UAAU,CAAC;AAGjD,MAAI,QAAQ,aAAa,SAAS;AAChC,YAAQ,IAAI,IAAI,0CAA0C,CAAC;AAC3D,WAAO,EAAE,SAAS,cAAc,WAAW,MAAM;AAAA,EACnD;AAEA,QAAM,OAAO,MAAM,OAAO;AAAA,IACxB,SAAS;AAAA,IACT,SAAS;AAAA,MACP;AAAA,QACE,MAAM;AAAA,QACN,OAAO;AAAA,QACP,aAAa;AAAA,MACf;AAAA,MACA;AAAA,QACE,MAAM;AAAA,QACN,OAAO;AAAA,QACP,aAAa;AAAA,MACf;AAAA,IACF;AAAA,EACF,CAAC;AAED,MAAI,SAAS,UAAU;AACrB,UAAM,EAAE,kBAAkB,qBAAqB,IAAI,MAAM,OAAO,yBAAgB;AAChF,UAAM,YAAY,qBAAqB;AACvC,QAAI,WAAW;AACb,YAAM,SAAS,iBAAiB,WAAW,iBAAiB,CAAC;AAC7D,UAAI,OAAO,SAAS;AAClB,gBAAQ,IAAI,GAAG,4BAA4B,CAAC;AAAA,MAC9C,OAAO;AACL,gBAAQ,IAAI,KAAK,sBAAsB,OAAO,KAAK,EAAE,CAAC;AAAA,MACxD;AAAA,IACF;AACA,WAAO,EAAE,SAAS,UAAU,UAAU;AAAA,EACxC;AAEA,SAAO,EAAE,SAAS,cAAc,WAAW,MAAM;AACnD;AAIA,SAAS,qBAA2B;AAClC,UAAQ,IAAI;AAAA,EACZ,EAAE,IAAI,GAAG,EAAE,IAAI;AAAA;AAAA,oMAEmB,EAAE,KAAK;AAAA,CAC1C;AACD;AAEA,eAAsB,SAAS,eAAgD;AAC7E,qBAAmB;AAEnB,MAAI;AACF,UAAM,EAAE,QAAQ,cAAc,IAAI,MAAM,OAAO,mBAAmB;AAClE,UAAM,gBAAgB,MAAM,cAAc;AAAA,MACxC,SAAS;AAAA,MACT,SAAS;AAAA,QACP,EAAE,MAAM,YAAY,OAAO,WAAW;AAAA,QACtC,EAAE,MAAM,WAAW,OAAO,UAAU;AAAA,QACpC,EAAE,MAAM,QAAQ,OAAO,OAAO;AAAA,MAChC;AAAA,IACF,CAAC;AAED,QAAI;AACJ,QAAI;AAGJ,UAAM,eAAe,kBAAkB,SAAS,IAAI;AACpD,UAAM,aAAa,eAAe;AAElC,QAAI,cAAc;AAElB,QAAI,kBAAkB,cAAc,kBAAkB,QAAQ;AAC5D;AACA,iBAAW,MAAM,cAAc,aAAa,UAAU;AAAA,IACxD;AACA,QAAI,kBAAkB,aAAa,kBAAkB,QAAQ;AAC3D;AACA,gBAAU,MAAM,aAAa;AAAA,IAC/B;AAEA,UAAM,EAAE,aAAa,IAAI,MAAM,YAAY;AAG3C;AACE,YAAM,EAAE,QAAQ,IAAI,MAAM,OAAO,mBAAmB;AACpD,YAAM,gBAAgB,MAAM,QAAQ;AAAA,QAClC,SAAS;AAAA,QACT,SAAS;AAAA,MACX,CAAC;AAED,UAAI,eAAe;AACjB,YAAI;AACF,gBAAM,EAAE,eAAe,IAAI,MAAM,OAAO,yBAAqB;AAC7D,gBAAM,cAAc,eAAe,QAAQ;AAC3C,cAAI,aAAa;AACf,uBAAW,QAAQ,YAAY,OAAO;AACpC,oBAAM,SAAS,MAAM,KAAK,QAAQ;AAClC,yBAAW,OAAO,OAAO,KAAM,SAAQ,IAAI,KAAK,GAAG,EAAE;AAAA,YACvD;AAAA,UACF;AACA,kBAAQ,IAAI,qCAAqC;AAAA,QACnD,SAAS,KAAK;AACZ,kBAAQ,IAAI,6CAA6C,eAAe,QAAQ,IAAI,UAAU,GAAG,EAAE;AACnG,kBAAQ,IAAI,6DAA6D;AAAA,QAC3E;AAAA,MACF;AAAA,IACF;AAEA;AACA,UAAM,YAAY,MAAM,eAAe,aAAa,UAAU;AAC9D;AACA,UAAM,EAAE,SAAS,UAAU,IAAI,MAAM,aAAa,aAAa,UAAU;AACzE,UAAM,WAAW;AAAA,MACf,gBAAgB,CAAC;AAAA,MACjB,uBAAuB;AAAA,MACvB,uBAAuB;AAAA,IACzB;AAEA,UAAM,WAA+B,CAAC;AACtC,QAAI,SAAU,UAAS,WAAW;AAClC,QAAI,QAAS,UAAS,UAAU;AAEhC,UAAM,SAAiB;AAAA,MACrB;AAAA,MACA,QAAQ,CAAC;AAAA,MACT;AAAA,MACA;AAAA,MACA;AAAA,MACA,SAAS;AAAA,QACP,OAAO;AAAA,QACP,QAAQ;AAAA,QACR,aAAa;AAAA,QACb,UAAU;AAAA,QACV,yBAAyB;AAAA,MAC3B;AAAA,MACA;AAAA,MACA;AAAA,MACA,KAAK;AAAA,QACH,MAAM;AAAA,QACN,MAAM;AAAA,MACR;AAAA,MACA,cAAc,EAAE,SAAS,GAAG;AAAA,MAC5B,QAAQ;AAAA,QACN,SAAS;AAAA,QACT,MAAM;AAAA,QACN,UAAU;AAAA,QACV,SAAS,CAAC;AAAA,QACV,gBAAgB;AAAA,QAChB,iBAAiB;AAAA,QACjB,MAAM,EAAE,SAAS,MAAM;AAAA,MACzB;AAAA,MACA,cAAc,CAAC;AAAA,IACjB;AAEA,QAAI;AACF,YAAM,cAAc,SAAS,MAAM;AAAA,IACrC,SAAS,UAAU;AACjB,cAAQ;AAAA,QACN,KAAK,0BAA2B,SAAmB,OAAO,EAAE;AAAA,MAC9D;AACA,aAAO;AAAA,IACT;AAEA,YAAQ,IAAI,EAAE;AACd,YAAQ;AAAA,MACN,GAAG,mBAAmB,EAAE,IAAI,GAAG,cAAc,cAAc,CAAC,EAAE;AAAA,IAChE;AAGA,QAAI,OAAO,OAAO,WAAW,OAAO,OAAO,aAAa,cAAc;AACpE,cAAQ,IAAI,IAAI,wCAAwC,CAAC;AACzD,UAAI;AACF,cAAM,EAAE,kBAAkB,IAAI,MAAM,OAClC,mCACF;AACA,cAAM,UAAU,MAAM,kBAAkB;AACxC,gBAAQ,IAAI,GAAG,wBAAwB,IAAI,OAAO,CAAC,EAAE,CAAC;AAAA,MACxD,SAAS,KAAK;AACZ,gBAAQ;AAAA,UACN;AAAA,YACE,kCAAmC,IAAc,OAAO;AAAA,UAC1D;AAAA,QACF;AAAA,MACF;AAAA,IACF;AAEA,YAAQ,IAAI,GAAG,qBAAqB,CAAC;AACrC,YAAQ,IAAI,EAAE;AAEd,WAAO;AAAA,EACT,SAAS,KAAK;AACZ,QAAK,IAAc,SAAS,mBAAmB;AAC7C,cAAQ,IAAI,IAAI,oBAAoB,CAAC;AACrC,aAAO;AAAA,IACT;AACA,UAAM;AAAA,EACR;AACF;","names":["select"]}
@@ -14,6 +14,13 @@ var SYSTEMD_SERVICE_PATH = path.join(os.homedir(), ".config", "systemd", "user",
14
14
  function isAutoStartSupported() {
15
15
  return process.platform === "darwin" || process.platform === "linux";
16
16
  }
17
+ function escapeXml(str) {
18
+ return str.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;").replace(/"/g, "&quot;").replace(/'/g, "&apos;");
19
+ }
20
+ function escapeSystemdValue(str) {
21
+ const escaped = str.replace(/\\/g, "\\\\").replace(/"/g, '\\"').replace(/\$/g, "$$$$").replace(/%/g, "%%");
22
+ return `"${escaped}"`;
23
+ }
17
24
  function generateLaunchdPlist(nodePath, cliPath, logDir) {
18
25
  const logFile = path.join(logDir, "openacp.log");
19
26
  return `<?xml version="1.0" encoding="UTF-8"?>
@@ -24,8 +31,8 @@ function generateLaunchdPlist(nodePath, cliPath, logDir) {
24
31
  <string>${LAUNCHD_LABEL}</string>
25
32
  <key>ProgramArguments</key>
26
33
  <array>
27
- <string>${nodePath}</string>
28
- <string>${cliPath}</string>
34
+ <string>${escapeXml(nodePath)}</string>
35
+ <string>${escapeXml(cliPath)}</string>
29
36
  <string>--daemon-child</string>
30
37
  </array>
31
38
  <key>RunAtLoad</key>
@@ -36,9 +43,9 @@ function generateLaunchdPlist(nodePath, cliPath, logDir) {
36
43
  <false/>
37
44
  </dict>
38
45
  <key>StandardOutPath</key>
39
- <string>${logFile}</string>
46
+ <string>${escapeXml(logFile)}</string>
40
47
  <key>StandardErrorPath</key>
41
- <string>${logFile}</string>
48
+ <string>${escapeXml(logFile)}</string>
42
49
  </dict>
43
50
  </plist>
44
51
  `;
@@ -48,7 +55,7 @@ function generateSystemdUnit(nodePath, cliPath) {
48
55
  Description=OpenACP Daemon
49
56
 
50
57
  [Service]
51
- ExecStart=${nodePath} ${cliPath} --daemon-child
58
+ ExecStart=${escapeSystemdValue(nodePath)} ${escapeSystemdValue(cliPath)} --daemon-child
52
59
  Restart=on-failure
53
60
 
54
61
  [Install]
@@ -136,10 +143,12 @@ function isAutoStartInstalled() {
136
143
 
137
144
  export {
138
145
  isAutoStartSupported,
146
+ escapeXml,
147
+ escapeSystemdValue,
139
148
  generateLaunchdPlist,
140
149
  generateSystemdUnit,
141
150
  installAutoStart,
142
151
  uninstallAutoStart,
143
152
  isAutoStartInstalled
144
153
  };
145
- //# sourceMappingURL=chunk-X6LLG7XN.js.map
154
+ //# sourceMappingURL=chunk-PMGNLNSH.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../src/core/autostart.ts"],"sourcesContent":["import { execFileSync } from 'node:child_process'\nimport * as fs from 'node:fs'\nimport * as path from 'node:path'\nimport * as os from 'node:os'\nimport { createChildLogger } from './log.js'\n\nconst log = createChildLogger({ module: 'autostart' })\n\nconst LAUNCHD_LABEL = 'com.openacp.daemon'\nconst LAUNCHD_PLIST_PATH = path.join(os.homedir(), 'Library', 'LaunchAgents', `${LAUNCHD_LABEL}.plist`)\nconst SYSTEMD_SERVICE_PATH = path.join(os.homedir(), '.config', 'systemd', 'user', 'openacp.service')\n\nexport function isAutoStartSupported(): boolean {\n return process.platform === 'darwin' || process.platform === 'linux'\n}\n\nexport function escapeXml(str: string): string {\n return str\n .replace(/&/g, '&amp;')\n .replace(/</g, '&lt;')\n .replace(/>/g, '&gt;')\n .replace(/\"/g, '&quot;')\n .replace(/'/g, '&apos;')\n}\n\nexport function escapeSystemdValue(str: string): string {\n const escaped = str\n .replace(/\\\\/g, '\\\\\\\\')\n .replace(/\"/g, '\\\\\"')\n .replace(/\\$/g, '$$$$')\n .replace(/%/g, '%%')\n return `\"${escaped}\"`\n}\n\nexport function generateLaunchdPlist(nodePath: string, cliPath: string, logDir: string): string {\n const logFile = path.join(logDir, 'openacp.log')\n return `<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<!DOCTYPE plist PUBLIC \"-//Apple//DTD PLIST 1.0//EN\" \"http://www.apple.com/DTDs/PropertyList-1.0.dtd\">\n<plist version=\"1.0\">\n<dict>\n <key>Label</key>\n <string>${LAUNCHD_LABEL}</string>\n <key>ProgramArguments</key>\n <array>\n <string>${escapeXml(nodePath)}</string>\n <string>${escapeXml(cliPath)}</string>\n <string>--daemon-child</string>\n </array>\n <key>RunAtLoad</key>\n <true/>\n <key>KeepAlive</key>\n <dict>\n <key>SuccessfulExit</key>\n <false/>\n </dict>\n <key>StandardOutPath</key>\n <string>${escapeXml(logFile)}</string>\n <key>StandardErrorPath</key>\n <string>${escapeXml(logFile)}</string>\n</dict>\n</plist>\n`\n}\n\nexport function generateSystemdUnit(nodePath: string, cliPath: string): string {\n return `[Unit]\nDescription=OpenACP Daemon\n\n[Service]\nExecStart=${escapeSystemdValue(nodePath)} ${escapeSystemdValue(cliPath)} --daemon-child\nRestart=on-failure\n\n[Install]\nWantedBy=default.target\n`\n}\n\nexport function installAutoStart(logDir: string): { success: boolean; error?: string } {\n if (!isAutoStartSupported()) {\n return { success: false, error: 'Auto-start not supported on this platform' }\n }\n\n const nodePath = process.execPath\n const cliPath = path.resolve(process.argv[1])\n const resolvedLogDir = logDir.startsWith('~')\n ? path.join(os.homedir(), logDir.slice(1))\n : logDir\n\n try {\n if (process.platform === 'darwin') {\n const plist = generateLaunchdPlist(nodePath, cliPath, resolvedLogDir)\n const dir = path.dirname(LAUNCHD_PLIST_PATH)\n fs.mkdirSync(dir, { recursive: true })\n fs.writeFileSync(LAUNCHD_PLIST_PATH, plist)\n execFileSync('launchctl', ['load', LAUNCHD_PLIST_PATH], { stdio: 'pipe' })\n log.info('LaunchAgent installed')\n return { success: true }\n }\n\n if (process.platform === 'linux') {\n const unit = generateSystemdUnit(nodePath, cliPath)\n const dir = path.dirname(SYSTEMD_SERVICE_PATH)\n fs.mkdirSync(dir, { recursive: true })\n fs.writeFileSync(SYSTEMD_SERVICE_PATH, unit)\n execFileSync('systemctl', ['--user', 'daemon-reload'], { stdio: 'pipe' })\n execFileSync('systemctl', ['--user', 'enable', 'openacp'], { stdio: 'pipe' })\n log.info('systemd user service installed')\n return { success: true }\n }\n\n return { success: false, error: 'Unsupported platform' }\n } catch (e) {\n const msg = (e as Error).message\n log.error({ err: msg }, 'Failed to install auto-start')\n return { success: false, error: msg }\n }\n}\n\nexport function uninstallAutoStart(): { success: boolean; error?: string } {\n if (!isAutoStartSupported()) {\n return { success: false, error: 'Auto-start not supported on this platform' }\n }\n\n try {\n if (process.platform === 'darwin') {\n if (fs.existsSync(LAUNCHD_PLIST_PATH)) {\n try {\n execFileSync('launchctl', ['unload', LAUNCHD_PLIST_PATH], { stdio: 'pipe' })\n } catch {\n // may already be unloaded\n }\n fs.unlinkSync(LAUNCHD_PLIST_PATH)\n log.info('LaunchAgent removed')\n }\n return { success: true }\n }\n\n if (process.platform === 'linux') {\n if (fs.existsSync(SYSTEMD_SERVICE_PATH)) {\n try {\n execFileSync('systemctl', ['--user', 'disable', 'openacp'], { stdio: 'pipe' })\n } catch {\n // may already be disabled\n }\n fs.unlinkSync(SYSTEMD_SERVICE_PATH)\n execFileSync('systemctl', ['--user', 'daemon-reload'], { stdio: 'pipe' })\n log.info('systemd user service removed')\n }\n return { success: true }\n }\n\n return { success: false, error: 'Unsupported platform' }\n } catch (e) {\n const msg = (e as Error).message\n log.error({ err: msg }, 'Failed to uninstall auto-start')\n return { success: false, error: msg }\n }\n}\n\nexport function isAutoStartInstalled(): boolean {\n if (process.platform === 'darwin') {\n return fs.existsSync(LAUNCHD_PLIST_PATH)\n }\n if (process.platform === 'linux') {\n return fs.existsSync(SYSTEMD_SERVICE_PATH)\n }\n return false\n}\n"],"mappings":"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","names":[]}
@@ -0,0 +1,122 @@
1
+ import {
2
+ log
3
+ } from "./chunk-ESOPMQAY.js";
4
+
5
+ // src/adapters/discord/forums.ts
6
+ import { ChannelType } from "discord.js";
7
+ async function ensureForums(guild, config, saveConfig) {
8
+ let forumChannelId = config.forumChannelId;
9
+ let notificationChannelId = config.notificationChannelId;
10
+ let forumChannel = null;
11
+ if (forumChannelId) {
12
+ try {
13
+ const ch = guild.channels.cache.get(forumChannelId) ?? await guild.channels.fetch(forumChannelId);
14
+ if (ch && (ch.type === ChannelType.GuildForum || ch.type === ChannelType.GuildText)) {
15
+ forumChannel = ch;
16
+ log.info({ forumChannelId, type: ch.type }, "[forums] Reusing existing sessions channel");
17
+ }
18
+ } catch {
19
+ log.warn({ forumChannelId }, "[forums] Saved sessions channel not found, recreating...");
20
+ }
21
+ }
22
+ if (!forumChannel) {
23
+ if (guild.features.includes("COMMUNITY")) {
24
+ const channel = await guild.channels.create({
25
+ name: "openacp-sessions",
26
+ type: ChannelType.GuildForum
27
+ });
28
+ forumChannel = channel;
29
+ log.info({ forumChannelId: channel.id }, "[forums] Created forum channel");
30
+ } else {
31
+ const channel = await guild.channels.create({
32
+ name: "openacp-sessions",
33
+ type: ChannelType.GuildText
34
+ });
35
+ forumChannel = channel;
36
+ log.info({ forumChannelId: channel.id }, "[forums] Created text channel (Community mode not enabled, using threads fallback)");
37
+ }
38
+ await saveConfig({ channels: { discord: { forumChannelId: forumChannel.id } } });
39
+ }
40
+ let notificationChannel = null;
41
+ if (notificationChannelId) {
42
+ try {
43
+ const ch = guild.channels.cache.get(notificationChannelId) ?? await guild.channels.fetch(notificationChannelId);
44
+ if (ch && ch.type === ChannelType.GuildText) {
45
+ notificationChannel = ch;
46
+ log.info({ notificationChannelId }, "[forums] Reusing existing notification channel");
47
+ }
48
+ } catch {
49
+ log.warn({ notificationChannelId }, "[forums] Saved notification channel not found, recreating...");
50
+ }
51
+ }
52
+ if (!notificationChannel) {
53
+ const channel = await guild.channels.create({
54
+ name: "openacp-notifications",
55
+ type: ChannelType.GuildText
56
+ });
57
+ notificationChannel = channel;
58
+ await saveConfig({ channels: { discord: { notificationChannelId: channel.id } } });
59
+ log.info({ notificationChannelId: channel.id }, "[forums] Created notification channel");
60
+ }
61
+ return { forumChannel, notificationChannel };
62
+ }
63
+ async function createSessionThread(forumChannel, name) {
64
+ if (forumChannel.type === ChannelType.GuildForum) {
65
+ const thread2 = await forumChannel.threads.create({
66
+ name,
67
+ message: { content: "\u23F3 Setting up..." }
68
+ });
69
+ return thread2;
70
+ }
71
+ const textChannel = forumChannel;
72
+ const msg = await textChannel.send({ content: `\u{1F4C2} **${name}** \u2014 \u23F3 Setting up...` });
73
+ const thread = await msg.startThread({ name });
74
+ return thread;
75
+ }
76
+ async function renameSessionThread(guild, threadId, newName) {
77
+ try {
78
+ const channel = guild.channels.cache.get(threadId) ?? await guild.channels.fetch(threadId);
79
+ if (channel && "setName" in channel) {
80
+ await channel.setName(newName);
81
+ }
82
+ } catch {
83
+ }
84
+ }
85
+ async function deleteSessionThread(guild, threadId) {
86
+ try {
87
+ const channel = guild.channels.cache.get(threadId) ?? await guild.channels.fetch(threadId);
88
+ if (channel && channel.isThread()) {
89
+ const thread = channel;
90
+ if (!thread.archived) {
91
+ await thread.setArchived(true);
92
+ }
93
+ if (!thread.locked) {
94
+ await thread.setLocked(true);
95
+ }
96
+ }
97
+ } catch {
98
+ }
99
+ }
100
+ async function ensureUnarchived(thread) {
101
+ if (thread.archived) {
102
+ try {
103
+ await thread.setArchived(false);
104
+ } catch (err) {
105
+ log.warn({ err, threadId: thread.id }, "[forums] Failed to unarchive thread");
106
+ }
107
+ }
108
+ }
109
+ function buildDeepLink(guildId, channelId, messageId) {
110
+ const base = `https://discord.com/channels/${guildId}/${channelId}`;
111
+ return messageId ? `${base}/${messageId}` : base;
112
+ }
113
+
114
+ export {
115
+ ensureForums,
116
+ createSessionThread,
117
+ renameSessionThread,
118
+ deleteSessionThread,
119
+ ensureUnarchived,
120
+ buildDeepLink
121
+ };
122
+ //# sourceMappingURL=chunk-SM3G6UAX.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../src/adapters/discord/forums.ts"],"sourcesContent":["import { ChannelType } from 'discord.js'\nimport type { ForumChannel, ThreadChannel, Guild, TextChannel } from 'discord.js'\nimport { log } from '../../core/log.js'\n\n// ─── ensureForums ─────────────────────────────────────────────────────────────\n\n/**\n * Ensures both the forum channel and notification channel exist.\n * Creates them if their IDs are null, then persists the IDs via saveConfig.\n *\n * saveConfig uses nested object path: { channels: { discord: { forumChannelId: ... } } }\n */\nexport async function ensureForums(\n guild: Guild,\n config: {\n forumChannelId: string | null\n notificationChannelId: string | null\n },\n saveConfig: (updates: Record<string, unknown>) => Promise<void>,\n): Promise<{ forumChannel: ForumChannel | TextChannel; notificationChannel: TextChannel }> {\n let forumChannelId = config.forumChannelId\n let notificationChannelId = config.notificationChannelId\n\n // Ensure forum/sessions channel exists — fetch existing or create new\n let forumChannel: ForumChannel | TextChannel | null = null\n if (forumChannelId) {\n try {\n const ch = guild.channels.cache.get(forumChannelId)\n ?? await guild.channels.fetch(forumChannelId)\n if (ch && (ch.type === ChannelType.GuildForum || ch.type === ChannelType.GuildText)) {\n forumChannel = ch as ForumChannel | TextChannel\n log.info({ forumChannelId, type: ch.type }, '[forums] Reusing existing sessions channel')\n }\n } catch {\n log.warn({ forumChannelId }, '[forums] Saved sessions channel not found, recreating...')\n }\n }\n if (!forumChannel) {\n // Prefer Forum Channel (requires Community mode), fallback to Text Channel with threads\n if (guild.features.includes('COMMUNITY')) {\n const channel = await guild.channels.create({\n name: 'openacp-sessions',\n type: ChannelType.GuildForum,\n })\n forumChannel = channel as ForumChannel\n log.info({ forumChannelId: channel.id }, '[forums] Created forum channel')\n } else {\n const channel = await guild.channels.create({\n name: 'openacp-sessions',\n type: ChannelType.GuildText,\n })\n forumChannel = channel as TextChannel\n log.info({ forumChannelId: channel.id }, '[forums] Created text channel (Community mode not enabled, using threads fallback)')\n }\n await saveConfig({ channels: { discord: { forumChannelId: forumChannel.id } } })\n }\n\n // Ensure notification channel exists — fetch existing or create new\n let notificationChannel: TextChannel | null = null\n if (notificationChannelId) {\n try {\n const ch = guild.channels.cache.get(notificationChannelId)\n ?? await guild.channels.fetch(notificationChannelId)\n if (ch && ch.type === ChannelType.GuildText) {\n notificationChannel = ch as TextChannel\n log.info({ notificationChannelId }, '[forums] Reusing existing notification channel')\n }\n } catch {\n log.warn({ notificationChannelId }, '[forums] Saved notification channel not found, recreating...')\n }\n }\n if (!notificationChannel) {\n const channel = await guild.channels.create({\n name: 'openacp-notifications',\n type: ChannelType.GuildText,\n })\n notificationChannel = channel as TextChannel\n await saveConfig({ channels: { discord: { notificationChannelId: channel.id } } })\n log.info({ notificationChannelId: channel.id }, '[forums] Created notification channel')\n }\n\n return { forumChannel, notificationChannel }\n}\n\n// ─── createSessionThread ──────────────────────────────────────────────────────\n\n/**\n * Creates a new thread for a session.\n * - Forum Channel: creates a forum post (thread with initial message)\n * - Text Channel: creates a public thread\n */\nexport async function createSessionThread(\n forumChannel: ForumChannel | TextChannel,\n name: string,\n): Promise<ThreadChannel> {\n if (forumChannel.type === ChannelType.GuildForum) {\n // Forum channel: create a post (thread with initial message)\n const thread = await (forumChannel as ForumChannel).threads.create({\n name,\n message: { content: '⏳ Setting up...' },\n })\n return thread\n }\n\n // Text channel fallback: send a message first, then create a thread on it\n const textChannel = forumChannel as TextChannel\n const msg = await textChannel.send({ content: `📂 **${name}** — ⏳ Setting up...` })\n const thread = await msg.startThread({ name })\n return thread\n}\n\n// ─── renameSessionThread ──────────────────────────────────────────────────────\n\n/**\n * Fetches and renames a thread. Ignores all errors (thread may be deleted/archived).\n */\nexport async function renameSessionThread(\n guild: Guild,\n threadId: string,\n newName: string,\n): Promise<void> {\n try {\n const channel = guild.channels.cache.get(threadId)\n ?? await guild.channels.fetch(threadId)\n if (channel && 'setName' in channel) {\n await (channel as ThreadChannel).setName(newName)\n }\n } catch {\n // Ignore — thread may be deleted or archived\n }\n}\n\n// ─── deleteSessionThread ──────────────────────────────────────────────────────\n\n/**\n * Archives and locks a thread instead of permanently deleting it.\n * Unlike Telegram (which just closes a topic), Discord delete is permanent\n * and destroys all messages. Archiving preserves the conversation history.\n */\nexport async function deleteSessionThread(\n guild: Guild,\n threadId: string,\n): Promise<void> {\n try {\n const channel = guild.channels.cache.get(threadId)\n ?? await guild.channels.fetch(threadId)\n if (channel && channel.isThread()) {\n const thread = channel as ThreadChannel\n if (!thread.archived) {\n await thread.setArchived(true)\n }\n if (!thread.locked) {\n await thread.setLocked(true)\n }\n }\n } catch {\n // Ignore — thread may already be deleted or inaccessible\n }\n}\n\n// ─── ensureUnarchived ─────────────────────────────────────────────────────────\n\n/**\n * If the thread is archived, unarchives it.\n */\nexport async function ensureUnarchived(thread: ThreadChannel): Promise<void> {\n if (thread.archived) {\n try {\n await thread.setArchived(false)\n } catch (err) {\n log.warn({ err, threadId: thread.id }, '[forums] Failed to unarchive thread')\n }\n }\n}\n\n// ─── buildDeepLink ────────────────────────────────────────────────────────────\n\n/**\n * Builds a Discord deep link URL to a channel/thread, optionally to a specific message.\n */\nexport function buildDeepLink(\n guildId: string,\n channelId: string,\n messageId?: string,\n): string {\n const base = `https://discord.com/channels/${guildId}/${channelId}`\n return messageId ? `${base}/${messageId}` : base\n}\n"],"mappings":"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","names":["thread"]}
@@ -3,7 +3,7 @@ import {
3
3
  ConfigSchema,
4
4
  applyMigrations,
5
5
  expandHome
6
- } from "./chunk-JRF4G4X7.js";
6
+ } from "./chunk-DWQKUECJ.js";
7
7
 
8
8
  // src/core/doctor/index.ts
9
9
  import * as fs8 from "fs";
@@ -208,6 +208,79 @@ var telegramCheck = {
208
208
  }
209
209
  };
210
210
 
211
+ // src/core/doctor/checks/discord.ts
212
+ var BOT_TOKEN_REGEX2 = /^[A-Za-z0-9_-]{24,28}\.[A-Za-z0-9_-]{6,7}\.[A-Za-z0-9_-]{27,}$/;
213
+ var discordCheck = {
214
+ name: "Discord",
215
+ order: 3.5,
216
+ async run(ctx) {
217
+ const results = [];
218
+ if (!ctx.config) {
219
+ results.push({ status: "fail", message: "Cannot check Discord \u2014 config not loaded" });
220
+ return results;
221
+ }
222
+ const discordConfig = ctx.config.channels.discord;
223
+ if (!discordConfig || !discordConfig.enabled) {
224
+ results.push({ status: "pass", message: "Discord not enabled (skipped)" });
225
+ return results;
226
+ }
227
+ const botToken = discordConfig.botToken;
228
+ if (!botToken || !BOT_TOKEN_REGEX2.test(botToken)) {
229
+ results.push({ status: "fail", message: "Discord bot token format invalid or missing" });
230
+ return results;
231
+ }
232
+ results.push({ status: "pass", message: "Discord bot token format valid" });
233
+ let botUsername;
234
+ try {
235
+ const res = await fetch("https://discord.com/api/v10/users/@me", {
236
+ headers: { Authorization: `Bot ${botToken}` }
237
+ });
238
+ if (res.status === 200) {
239
+ const data = await res.json();
240
+ botUsername = data.username;
241
+ results.push({ status: "pass", message: `Bot token valid (@${data.username}, id: ${data.id})` });
242
+ } else if (res.status === 401) {
243
+ results.push({ status: "fail", message: "Bot token rejected by Discord (401 Unauthorized)" });
244
+ return results;
245
+ } else {
246
+ const text = await res.text();
247
+ results.push({ status: "fail", message: `Discord API error ${res.status}: ${text.slice(0, 100)}` });
248
+ return results;
249
+ }
250
+ } catch (err) {
251
+ results.push({ status: "fail", message: `Cannot reach Discord API: ${err instanceof Error ? err.message : String(err)}` });
252
+ return results;
253
+ }
254
+ const guildId = discordConfig.guildId;
255
+ if (!guildId) {
256
+ results.push({ status: "fail", message: "Guild ID not configured" });
257
+ return results;
258
+ }
259
+ try {
260
+ const res = await fetch(`https://discord.com/api/v10/guilds/${guildId}`, {
261
+ headers: { Authorization: `Bot ${botToken}` }
262
+ });
263
+ if (res.status === 200) {
264
+ const data = await res.json();
265
+ results.push({ status: "pass", message: `Bot has access to guild "${data.name}" (id: ${data.id})` });
266
+ } else if (res.status === 403) {
267
+ results.push({
268
+ status: "fail",
269
+ message: `Bot (${botUsername ?? "unknown"}) is not a member of guild ${guildId} or lacks access. Invite the bot to your server.`
270
+ });
271
+ } else if (res.status === 404) {
272
+ results.push({ status: "fail", message: `Guild ${guildId} not found \u2014 check the guild ID` });
273
+ } else {
274
+ const text = await res.text();
275
+ results.push({ status: "fail", message: `Guild check error ${res.status}: ${text.slice(0, 100)}` });
276
+ }
277
+ } catch (err) {
278
+ results.push({ status: "fail", message: `Cannot validate guild: ${err instanceof Error ? err.message : String(err)}` });
279
+ }
280
+ return results;
281
+ }
282
+ };
283
+
211
284
  // src/core/doctor/checks/storage.ts
212
285
  import * as fs3 from "fs";
213
286
  var storageCheck = {
@@ -557,6 +630,7 @@ var ALL_CHECKS = [
557
630
  configCheck,
558
631
  agentsCheck,
559
632
  telegramCheck,
633
+ discordCheck,
560
634
  storageCheck,
561
635
  workspaceCheck,
562
636
  pluginsCheck,
@@ -647,4 +721,4 @@ var DoctorEngine = class {
647
721
  export {
648
722
  DoctorEngine
649
723
  };
650
- //# sourceMappingURL=chunk-IRGYTNLP.js.map
724
+ //# sourceMappingURL=chunk-SPX7CKWV.js.map