wechat-ai 0.1.0 → 0.1.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -1,28 +1,28 @@
1
- # wx-ai
1
+ # wechat-ai
2
2
 
3
3
  微信 AI 机器人 — 一条命令连接微信与任意 AI 模型。
4
4
 
5
5
  <p align="center">
6
- <img src="docs/screenshot.png" width="800" alt="wx-ai screenshot" />
6
+ <img src="docs/screenshot.png" width="800" alt="wechat-ai screenshot" />
7
7
  </p>
8
8
 
9
9
  ```bash
10
- npm i -g wx-ai
11
- wx-ai set qwen sk-xxx
12
- wx-ai
10
+ npm i -g wechat-ai
11
+ wechat-ai set qwen sk-xxx
12
+ wechat-ai
13
13
  ```
14
14
 
15
15
  ## 支持模型
16
16
 
17
17
  | 模型 | 默认版本 | 设置 Key |
18
18
  |------|---------|---------|
19
- | 通义千问 (Qwen) | qwen-plus | `wx-ai set qwen <key>` |
20
- | DeepSeek | deepseek-chat | `wx-ai set deepseek <key>` |
21
- | Claude | claude-opus-4-6 (Agent) | `wx-ai set claude <key>` |
22
- | GPT | gpt-4o | `wx-ai set gpt <key>` |
23
- | Gemini | gemini-2.0-flash | `wx-ai set gemini <key>` |
24
- | MiniMax | MiniMax-Text-01 | `wx-ai set minimax <key>` |
25
- | 智谱 (GLM) | glm-4-plus | `wx-ai set zhipu <key>` |
19
+ | 通义千问 (Qwen) | qwen-plus | `wechat-ai set qwen <key>` |
20
+ | DeepSeek | deepseek-chat | `wechat-ai set deepseek <key>` |
21
+ | Claude | claude-opus-4-6 (Agent) | `wechat-ai set claude <key>` |
22
+ | GPT | gpt-4o | `wechat-ai set gpt <key>` |
23
+ | Gemini | gemini-2.0-flash | `wechat-ai set gemini <key>` |
24
+ | MiniMax | MiniMax-Text-01 | `wechat-ai set minimax <key>` |
25
+ | 智谱 (GLM) | glm-4-plus | `wechat-ai set glm <key>` |
26
26
 
27
27
  支持任何 OpenAI 兼容 API,编辑 `~/.wai/config.json` 即可添加。
28
28
 
@@ -32,23 +32,24 @@ Claude 通过 [Agent SDK](https://github.com/anthropics/claude-agent-sdk-typescr
32
32
 
33
33
  ```bash
34
34
  # 方式一:直接运行(无需安装)
35
- npx wx-ai
35
+ npx wechat-ai
36
36
 
37
37
  # 方式二:全局安装
38
- npm i -g wx-ai
38
+ npm i -g wechat-ai
39
39
 
40
40
  # 方式三:克隆源码
41
- git clone https://github.com/anxiong2025/wx-ai.git
42
- cd wx-ai && npm install && npm run build && node dist/cli.js
41
+ git clone https://github.com/anxiong2025/wechat-ai.git
42
+ cd wechat-ai && npm install && npm run build && node dist/cli.js
43
43
  ```
44
44
 
45
45
  ## 命令
46
46
 
47
47
  ```bash
48
- wx-ai # 启动(首次自动弹出二维码)
49
- wx-ai set <模型> <key> # 保存 API Key
50
- wx-ai use <模型> # 设置默认模型
51
- wx-ai config # 查看配置(Key 已脱敏)
48
+ wechat-ai # 启动(首次自动弹出二维码)
49
+ wechat-ai set <模型> <key> # 保存 API Key
50
+ wechat-ai use <模型> # 设置默认模型
51
+ wechat-ai config # 查看配置(Key 已脱敏)
52
+ wechat-ai update # 更新到最新版
52
53
  ```
53
54
 
54
55
  ### 微信内指令
@@ -64,7 +65,7 @@ wx-ai config # 查看配置(Key 已脱敏)
64
65
  ## 架构
65
66
 
66
67
  ```
67
- 微信 ──ilink──> wx-ai 网关 ──路由──> AI 模型
68
+ 微信 ──ilink──> wechat-ai 网关 ──路由──> AI 模型
68
69
  │ │
69
70
  会话管理 ┌────┴────┐
70
71
  模型路由 │ │
@@ -104,10 +105,22 @@ src/
104
105
  - [x] 多模型切换 (`/model`)
105
106
  - [x] 输入状态提示
106
107
  - [x] 7 个内置模型
108
+ - [x] npm 发布
107
109
  - [ ] 图片/文件收发
108
110
  - [ ] Telegram / Discord 渠道
109
111
  - [ ] MCP 支持
110
- - [ ] npm 发布
112
+
113
+ ## API Key 获取
114
+
115
+ | 模型 | 申请地址 |
116
+ |------|---------|
117
+ | 通义千问 (Qwen) | https://dashscope.console.aliyun.com/apiKey |
118
+ | DeepSeek | https://platform.deepseek.com/api_keys |
119
+ | Claude | https://console.anthropic.com/settings/keys |
120
+ | GPT | https://platform.openai.com/api-keys |
121
+ | Gemini | https://aistudio.google.com/apikey |
122
+ | MiniMax | https://platform.minimaxi.com/user-center/basic-information/interface-key |
123
+ | 智谱 (GLM) | https://open.bigmodel.cn/usercenter/apikeys |
111
124
 
112
125
  ## 协议
113
126
 
@@ -42,11 +42,11 @@ var DEFAULT_CONFIG = {
42
42
  model: "MiniMax-Text-01",
43
43
  apiKeyEnv: "MINIMAX_API_KEY"
44
44
  },
45
- zhipu: {
45
+ glm: {
46
46
  type: "openai-compatible",
47
47
  baseUrl: "https://open.bigmodel.cn/api/paas/v4",
48
48
  model: "glm-4-plus",
49
- apiKeyEnv: "ZHIPU_API_KEY"
49
+ apiKeyEnv: "GLM_API_KEY"
50
50
  }
51
51
  },
52
52
  channels: {
@@ -70,7 +70,23 @@ async function loadConfig() {
70
70
  return { ...DEFAULT_CONFIG };
71
71
  }
72
72
  const raw = await readFile(CONFIG_PATH, "utf-8");
73
- return { ...DEFAULT_CONFIG, ...JSON.parse(raw) };
73
+ const user = JSON.parse(raw);
74
+ const providers = { ...DEFAULT_CONFIG.providers };
75
+ if (user.providers) {
76
+ for (const [key, val] of Object.entries(user.providers)) {
77
+ providers[key] = val;
78
+ }
79
+ }
80
+ const config = { ...DEFAULT_CONFIG, ...user, providers };
81
+ if (config.providers.zhipu) {
82
+ if (!config.providers.glm) {
83
+ config.providers.glm = { ...config.providers.zhipu, apiKeyEnv: "GLM_API_KEY" };
84
+ }
85
+ delete config.providers.zhipu;
86
+ if (config.defaultProvider === "zhipu") config.defaultProvider = "glm";
87
+ await saveConfig(config);
88
+ }
89
+ return config;
74
90
  }
75
91
  async function saveConfig(config) {
76
92
  await ensureDir(WAI_DIR);
@@ -581,11 +597,17 @@ var OpenAICompatibleProvider = class {
581
597
 
582
598
  // src/gateway.ts
583
599
  var log4 = createLogger("\u7F51\u5173");
600
+ var DEBOUNCE_MS = 1500;
584
601
  var Gateway = class {
585
602
  channels = /* @__PURE__ */ new Map();
586
603
  providers = /* @__PURE__ */ new Map();
587
604
  config;
605
+ // Debounce buffer: accumulates messages within DEBOUNCE_MS window
606
+ buffers = /* @__PURE__ */ new Map();
607
+ // Whether AI is currently processing for a given user
588
608
  processing = /* @__PURE__ */ new Set();
609
+ // Queue for messages that arrive while AI is processing
610
+ queues = /* @__PURE__ */ new Map();
589
611
  constructor(config) {
590
612
  this.config = config;
591
613
  }
@@ -639,18 +661,56 @@ var Gateway = class {
639
661
  await Promise.allSettled(stops);
640
662
  log4.info("\u5DF2\u5173\u95ED");
641
663
  }
642
- async handleMessage(msg) {
643
- const lockKey = `${msg.channel}:${msg.senderId}`;
644
- if (this.processing.has(lockKey)) {
645
- log4.warn(`\u8DF3\u8FC7\u6D88\u606F (\u4E0A\u6761\u4ECD\u5728\u5904\u7406)`);
664
+ handleMessage(msg) {
665
+ if (msg.text.startsWith("/")) {
666
+ this.handleCommand(msg);
646
667
  return;
647
668
  }
648
- this.processing.add(lockKey);
649
- try {
650
- if (msg.text.startsWith("/")) {
651
- await this.handleCommand(msg);
652
- return;
669
+ const key = `${msg.channel}:${msg.senderId}`;
670
+ if (this.processing.has(key)) {
671
+ const queue = this.queues.get(key) || [];
672
+ queue.push(msg);
673
+ this.queues.set(key, queue);
674
+ log4.info(`\u6D88\u606F\u5DF2\u6392\u961F (AI\u5904\u7406\u4E2D), \u961F\u5217\u957F\u5EA6: ${queue.length}`);
675
+ return;
676
+ }
677
+ const existing = this.buffers.get(key);
678
+ if (existing) {
679
+ clearTimeout(existing.timer);
680
+ existing.messages.push(msg);
681
+ existing.timer = setTimeout(() => this.flushBuffer(key), DEBOUNCE_MS);
682
+ } else {
683
+ this.buffers.set(key, {
684
+ messages: [msg],
685
+ timer: setTimeout(() => this.flushBuffer(key), DEBOUNCE_MS)
686
+ });
687
+ }
688
+ }
689
+ async flushBuffer(key) {
690
+ const buf = this.buffers.get(key);
691
+ if (!buf || buf.messages.length === 0) return;
692
+ this.buffers.delete(key);
693
+ const merged = this.mergeMessages(buf.messages);
694
+ await this.processMessage(merged);
695
+ const queue = this.queues.get(key);
696
+ if (queue && queue.length > 0) {
697
+ this.queues.delete(key);
698
+ for (const msg of queue) {
699
+ this.handleMessage(msg);
653
700
  }
701
+ }
702
+ }
703
+ mergeMessages(messages) {
704
+ if (messages.length === 1) return messages[0];
705
+ const last = messages[messages.length - 1];
706
+ const mergedText = messages.map((m) => m.text).join("\n");
707
+ log4.info(`\u5408\u5E76 ${messages.length} \u6761\u6D88\u606F`);
708
+ return { ...last, text: mergedText };
709
+ }
710
+ async processMessage(msg) {
711
+ const key = `${msg.channel}:${msg.senderId}`;
712
+ this.processing.add(key);
713
+ try {
654
714
  const providerName = this.config.userRoutes?.[msg.senderId] || this.config.defaultProvider;
655
715
  const provider = this.providers.get(providerName);
656
716
  if (!provider) {
@@ -690,7 +750,7 @@ var Gateway = class {
690
750
  } catch {
691
751
  }
692
752
  } finally {
693
- this.processing.delete(lockKey);
753
+ this.processing.delete(key);
694
754
  }
695
755
  }
696
756
  async handleCommand(msg) {
@@ -711,12 +771,13 @@ var Gateway = class {
711
771
  \u7528\u6CD5: /model <\u540D\u79F0>`,
712
772
  replyToken: msg.replyToken
713
773
  });
714
- } else if (this.providers.has(arg)) {
774
+ } else if (this.providers.has(arg.toLowerCase())) {
775
+ const provider = arg.toLowerCase();
715
776
  if (!this.config.userRoutes) this.config.userRoutes = {};
716
- this.config.userRoutes[msg.senderId] = arg;
777
+ this.config.userRoutes[msg.senderId] = provider;
717
778
  await channel.send({
718
779
  targetId: msg.senderId,
719
- text: `\u5DF2\u5207\u6362\u5230: ${arg}`,
780
+ text: `\u5DF2\u5207\u6362\u5230: ${provider}`,
720
781
  replyToken: msg.replyToken
721
782
  });
722
783
  } else {
@@ -733,7 +794,7 @@ var Gateway = class {
733
794
  await channel.send({
734
795
  targetId: msg.senderId,
735
796
  text: [
736
- "wx-ai \u6307\u4EE4:",
797
+ "wechat-ai \u6307\u4EE4:",
737
798
  "/model [\u540D\u79F0] - \u5207\u6362AI\u6A21\u578B",
738
799
  "/help - \u663E\u793A\u5E2E\u52A9",
739
800
  "/ping - \u68C0\u67E5\u72B6\u6001"
@@ -771,4 +832,4 @@ export {
771
832
  OpenAICompatibleProvider,
772
833
  Gateway
773
834
  };
774
- //# sourceMappingURL=chunk-PRU3A74K.js.map
835
+ //# sourceMappingURL=chunk-CP3Y2VRX.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/config.ts","../src/logger.ts","../src/channels/weixin.ts","../src/providers/claude-agent.ts","../src/providers/openai-compatible.ts","../src/gateway.ts"],"sourcesContent":["import { readFile, writeFile, mkdir } from \"node:fs/promises\";\nimport { existsSync } from \"node:fs\";\nimport { homedir } from \"node:os\";\nimport { join } from \"node:path\";\nimport type { WaiConfig } from \"./types.js\";\n\nconst WAI_DIR = join(homedir(), \".wai\");\nconst CONFIG_PATH = join(WAI_DIR, \"config.json\");\n\nconst DEFAULT_CONFIG: WaiConfig = {\n defaultProvider: \"qwen\",\n providers: {\n claude: {\n type: \"claude-agent\",\n allowedTools: [\"Read\", \"Glob\", \"Grep\", \"Bash\", \"WebSearch\", \"WebFetch\"],\n },\n qwen: {\n type: \"openai-compatible\",\n baseUrl: \"https://dashscope.aliyuncs.com/compatible-mode/v1\",\n model: \"qwen-plus\",\n apiKeyEnv: \"DASHSCOPE_API_KEY\",\n },\n deepseek: {\n type: \"openai-compatible\",\n baseUrl: \"https://api.deepseek.com/v1\",\n model: \"deepseek-chat\",\n apiKeyEnv: \"DEEPSEEK_API_KEY\",\n },\n gpt: {\n type: \"openai-compatible\",\n baseUrl: \"https://api.openai.com/v1\",\n model: \"gpt-4o\",\n apiKeyEnv: \"OPENAI_API_KEY\",\n },\n gemini: {\n type: \"openai-compatible\",\n baseUrl: \"https://generativelanguage.googleapis.com/v1beta/openai\",\n model: \"gemini-2.0-flash\",\n apiKeyEnv: \"GEMINI_API_KEY\",\n },\n minimax: {\n type: \"openai-compatible\",\n baseUrl: \"https://api.minimax.chat/v1\",\n model: \"MiniMax-Text-01\",\n apiKeyEnv: \"MINIMAX_API_KEY\",\n },\n glm: {\n type: \"openai-compatible\",\n baseUrl: \"https://open.bigmodel.cn/api/paas/v4\",\n model: \"glm-4-plus\",\n apiKeyEnv: \"GLM_API_KEY\",\n },\n },\n channels: {\n weixin: {\n type: \"weixin\",\n enabled: true,\n },\n },\n systemPrompt: \"You are a helpful AI assistant. Respond concisely.\",\n chunkSize: 4000,\n};\n\nexport async function ensureDir(dir: string) {\n if (!existsSync(dir)) {\n await mkdir(dir, { recursive: true });\n }\n}\n\nexport async function loadConfig(): Promise<WaiConfig> {\n await ensureDir(WAI_DIR);\n\n if (!existsSync(CONFIG_PATH)) {\n await writeFile(CONFIG_PATH, JSON.stringify(DEFAULT_CONFIG, null, 2));\n return { ...DEFAULT_CONFIG };\n }\n\n const raw = await readFile(CONFIG_PATH, \"utf-8\");\n const user = JSON.parse(raw) as Partial<WaiConfig>;\n\n // Deep merge: default providers + user providers (user overrides per provider)\n const providers = { ...DEFAULT_CONFIG.providers };\n if (user.providers) {\n for (const [key, val] of Object.entries(user.providers)) {\n providers[key] = val;\n }\n }\n\n const config = { ...DEFAULT_CONFIG, ...user, providers } as WaiConfig;\n\n // Migrate: zhipu → glm\n if (config.providers.zhipu) {\n if (!config.providers.glm) {\n config.providers.glm = { ...config.providers.zhipu, apiKeyEnv: \"GLM_API_KEY\" };\n }\n delete config.providers.zhipu;\n if (config.defaultProvider === \"zhipu\") config.defaultProvider = \"glm\";\n await saveConfig(config);\n }\n\n return config;\n}\n\nexport async function saveConfig(config: WaiConfig): Promise<void> {\n await ensureDir(WAI_DIR);\n await writeFile(CONFIG_PATH, JSON.stringify(config, null, 2));\n}\n\nexport function getDataDir(): string {\n return WAI_DIR;\n}\n\nexport function getAccountsDir(): string {\n return join(WAI_DIR, \"accounts\");\n}\n","const LEVELS = { debug: 0, info: 1, warn: 2, error: 3 } as const;\ntype Level = keyof typeof LEVELS;\n\nlet currentLevel: Level = \"info\";\n\nexport function setLogLevel(level: Level) {\n currentLevel = level;\n}\n\nfunction fmt(level: Level, scope: string, msg: string): string {\n const ts = new Date().toISOString().slice(11, 23);\n const tag = level.toUpperCase().padEnd(5);\n return `\\x1b[90m${ts}\\x1b[0m ${colorize(level, tag)} \\x1b[36m[${scope}]\\x1b[0m ${msg}`;\n}\n\nfunction colorize(level: Level, text: string): string {\n switch (level) {\n case \"debug\": return `\\x1b[90m${text}\\x1b[0m`;\n case \"info\": return `\\x1b[32m${text}\\x1b[0m`;\n case \"warn\": return `\\x1b[33m${text}\\x1b[0m`;\n case \"error\": return `\\x1b[31m${text}\\x1b[0m`;\n }\n}\n\nexport function createLogger(scope: string) {\n return {\n debug: (msg: string) => { if (LEVELS[currentLevel] <= 0) console.log(fmt(\"debug\", scope, msg)); },\n info: (msg: string) => { if (LEVELS[currentLevel] <= 1) console.log(fmt(\"info\", scope, msg)); },\n warn: (msg: string) => { if (LEVELS[currentLevel] <= 2) console.warn(fmt(\"warn\", scope, msg)); },\n error: (msg: string) => { if (LEVELS[currentLevel] <= 3) console.error(fmt(\"error\", scope, msg)); },\n };\n}\n","import { createLogger } from \"../logger.js\";\nimport { getAccountsDir, ensureDir } from \"../config.js\";\nimport { join } from \"node:path\";\nimport { readFile, writeFile } from \"node:fs/promises\";\nimport { existsSync } from \"node:fs\";\nimport { randomBytes, randomUUID } from \"node:crypto\";\nimport type { Channel, InboundMessage, OutboundMessage, ChannelConfig } from \"../types.js\";\n\nconst log = createLogger(\"weixin\");\n\nconst DEFAULT_BASE_URL = \"https://ilinkai.weixin.qq.com\";\nconst CHANNEL_VERSION = \"1.0.0\";\nconst API_TIMEOUT_MS = 15_000;\n\n// ── Message constants (from openclaw-weixin protocol) ──\nconst MessageType = { USER: 1, BOT: 2 } as const;\nconst MessageState = { NEW: 0, GENERATING: 1, FINISH: 2 } as const;\nconst MessageItemType = { TEXT: 1, IMAGE: 2, VOICE: 3, FILE: 4, VIDEO: 5 } as const;\n\n// ── Types ──\n\ninterface WeixinAccount {\n accountId: string;\n token: string;\n baseUrl: string;\n userId?: string;\n}\n\ninterface WeixinMessageItem {\n type: number;\n text_item?: { text: string };\n image_item?: unknown;\n voice_item?: unknown;\n file_item?: unknown;\n video_item?: unknown;\n}\n\ninterface WeixinMessage {\n seq?: number;\n message_id?: number;\n from_user_id?: string;\n to_user_id?: string;\n context_token?: string;\n item_list?: WeixinMessageItem[];\n create_time_ms?: number;\n}\n\ninterface GetUpdatesResponse {\n ret?: number;\n errmsg?: string;\n msgs?: WeixinMessage[];\n get_updates_buf?: string;\n}\n\n// ── Weixin Channel ──\n\nexport class WeixinChannel implements Channel {\n readonly name = \"weixin\";\n\n private account: WeixinAccount | null = null;\n private syncBuf = \"\";\n private running = false;\n private abortController: AbortController | null = null;\n private config: ChannelConfig;\n // Cache typing_ticket per user\n private typingTickets = new Map<string, string>();\n\n constructor(config: ChannelConfig) {\n this.config = config;\n }\n\n // ── Auth ──\n\n async login(): Promise<void> {\n const baseUrl = (this.config.baseUrl as string) || DEFAULT_BASE_URL;\n log.info(\"获取二维码中...\");\n\n const qrRes = await this.api(baseUrl, \"ilink/bot/get_bot_qrcode?bot_type=3\", null, {\n method: \"GET\",\n timeout: 10_000,\n });\n\n if (qrRes.ret !== 0) {\n throw new Error(`获取二维码失败: ${qrRes.errmsg || qrRes.ret}`);\n }\n\n const qrUrl: string = qrRes.qrcode_img_content || qrRes.data?.qrcode_img_content;\n const qrCode: string = qrRes.qrcode || qrRes.data?.qrcode;\n\n if (!qrUrl || !qrCode) {\n throw new Error(`二维码响应缺少字段: ${JSON.stringify(qrRes)}`);\n }\n\n log.info(\"请用微信扫描二维码:\");\n console.log();\n try {\n const qrTerminal = await import(\"qrcode-terminal\");\n (qrTerminal.default || qrTerminal).generate(qrUrl, { small: true });\n } catch {\n console.log(` ${qrUrl}`);\n }\n console.log();\n\n log.info(\"等待扫码...\");\n\n let attempts = 0;\n while (attempts < 60) {\n const statusRes = await this.api(\n baseUrl,\n `ilink/bot/get_qrcode_status?qrcode=${encodeURIComponent(qrCode)}`,\n null,\n { method: \"GET\", timeout: 40_000 },\n );\n\n const status = statusRes.data?.status || statusRes.status;\n\n if (status === \"confirmed\") {\n const data = statusRes.data || statusRes;\n const accountId: string = data.ilink_bot_id || data.bot_id;\n const token: string = data.bot_token || data.token;\n\n if (!accountId || !token) {\n throw new Error(\"登录成功但缺少凭证\");\n }\n\n this.account = {\n accountId,\n token,\n baseUrl: data.baseurl || baseUrl,\n userId: data.ilink_user_id,\n };\n\n await this.saveAccount();\n log.info(`登录成功!账号: ${accountId.slice(0, 8)}...`);\n return;\n }\n\n if (status === \"scaned\") {\n log.info(\"已扫码,等待确认...\");\n }\n\n if (status === \"expired\") {\n log.warn(\"二维码已过期\");\n throw new Error(\"二维码已过期\");\n }\n\n attempts++;\n await sleep(500);\n }\n\n throw new Error(\"登录超时\");\n }\n\n // ── Message loop ──\n\n async start(onMessage: (msg: InboundMessage) => void): Promise<void> {\n if (!this.account) {\n await this.loadAccount();\n }\n if (!this.account) {\n log.info(\"未找到账号,开始登录...\");\n await this.login();\n }\n\n await this.loadSyncBuf();\n this.running = true;\n log.info(`消息监听已启动 (${this.account!.accountId.slice(0, 8)}...)`);\n\n while (this.running) {\n try {\n this.abortController = new AbortController();\n const res = await this.getUpdates();\n\n if (res.ret === -14) {\n log.warn(\"会话过期,重新登录...\");\n this.account = null;\n await this.login();\n continue;\n }\n\n if (res.ret && res.ret !== 0) {\n log.warn(`拉取消息失败: ${res.errmsg || JSON.stringify(res)}`);\n await sleep(5000);\n continue;\n }\n\n if (res.get_updates_buf) {\n this.syncBuf = res.get_updates_buf;\n await this.saveSyncBuf();\n }\n\n if (res.msgs && res.msgs.length > 0) {\n for (const msg of res.msgs) {\n const text = this.extractText(msg);\n if (!text || !msg.from_user_id) continue;\n\n log.info(`收到消息 [${msg.from_user_id.slice(0, 8)}...]: ${text.slice(0, 50)}`);\n onMessage({\n id: String(msg.message_id || msg.seq || Date.now()),\n channel: \"weixin\",\n senderId: msg.from_user_id,\n text,\n replyToken: msg.context_token,\n timestamp: msg.create_time_ms || Date.now(),\n });\n }\n }\n } catch (err) {\n if (!this.running) break;\n const message = err instanceof Error ? err.message : String(err);\n if (message.includes(\"aborted\") || message.includes(\"AbortError\")) continue;\n log.error(`轮询出错: ${message}`);\n await sleep(3000);\n }\n }\n }\n\n // ── Send typing indicator ──\n\n async sendTyping(userId: string, contextToken?: string): Promise<void> {\n if (!this.account) return;\n\n try {\n // Get typing_ticket if not cached\n let ticket = this.typingTickets.get(userId);\n if (!ticket) {\n const configRes = await this.api(this.account.baseUrl, \"ilink/bot/getconfig\", {\n ilink_user_id: userId,\n context_token: contextToken,\n base_info: { channel_version: CHANNEL_VERSION },\n }, { timeout: 10_000 });\n\n ticket = configRes.typing_ticket;\n if (ticket) {\n this.typingTickets.set(userId, ticket);\n }\n }\n\n if (!ticket) return;\n\n await this.api(this.account.baseUrl, \"ilink/bot/sendtyping\", {\n ilink_user_id: userId,\n typing_ticket: ticket,\n status: 1,\n base_info: { channel_version: CHANNEL_VERSION },\n }, { timeout: 10_000 });\n\n log.debug(`已发送输入状态给 ${userId.slice(0, 8)}...`);\n } catch {\n // typing 失败不影响主流程\n }\n }\n\n // ── Send message ──\n\n async send(msg: OutboundMessage): Promise<void> {\n if (!this.account) throw new Error(\"未登录\");\n\n const chunks = this.chunkText(msg.text, 4000);\n\n for (const chunk of chunks) {\n const body = {\n msg: {\n from_user_id: \"\",\n to_user_id: msg.targetId,\n client_id: generateClientId(),\n message_type: MessageType.BOT,\n message_state: MessageState.FINISH,\n context_token: msg.replyToken || undefined,\n item_list: [{ type: MessageItemType.TEXT, text_item: { text: chunk } }],\n },\n base_info: { channel_version: CHANNEL_VERSION },\n };\n\n const res = await this.api(\n this.account.baseUrl,\n \"ilink/bot/sendmessage\",\n body,\n { timeout: API_TIMEOUT_MS },\n );\n\n // sendMessage 成功返回 {} (空对象),有错误时返回 ret + errmsg\n if (res.ret && res.ret !== 0) {\n log.error(`发送失败: ${res.errmsg || JSON.stringify(res)}`);\n }\n }\n }\n\n async stop(): Promise<void> {\n this.running = false;\n this.abortController?.abort();\n log.info(\"已停止\");\n }\n\n // ── Internal ──\n\n private async getUpdates(): Promise<GetUpdatesResponse> {\n if (!this.account) throw new Error(\"未登录\");\n\n return this.api(this.account.baseUrl, \"ilink/bot/getupdates\", {\n get_updates_buf: this.syncBuf,\n base_info: { channel_version: CHANNEL_VERSION },\n }, { timeout: 50_000 });\n }\n\n private extractText(msg: WeixinMessage): string | null {\n if (!msg.item_list?.length) return null;\n for (const item of msg.item_list) {\n if (item.type === 1 && item.text_item?.text) {\n return item.text_item.text;\n }\n }\n return null;\n }\n\n private chunkText(text: string, maxLen: number): string[] {\n if (text.length <= maxLen) return [text];\n const chunks: string[] = [];\n let remaining = text;\n while (remaining.length > 0) {\n let breakAt = remaining.lastIndexOf(\"\\n\", maxLen);\n if (breakAt <= 0) breakAt = maxLen;\n chunks.push(remaining.slice(0, breakAt));\n remaining = remaining.slice(breakAt);\n }\n return chunks;\n }\n\n private async api(\n baseUrl: string,\n path: string,\n body: unknown,\n opts: { method?: string; timeout?: number } = {},\n ): Promise<any> {\n const url = `${baseUrl.replace(/\\/$/, \"\")}/${path}`;\n const method = opts.method || \"POST\";\n const bodyStr = body ? JSON.stringify(body) : undefined;\n\n const headers: Record<string, string> = {\n \"Content-Type\": \"application/json\",\n };\n\n if (this.account?.token) {\n headers[\"AuthorizationType\"] = \"ilink_bot_token\";\n headers[\"Authorization\"] = `Bearer ${this.account.token}`;\n headers[\"X-WECHAT-UIN\"] = randomUin();\n if (bodyStr) {\n headers[\"Content-Length\"] = String(Buffer.byteLength(bodyStr, \"utf-8\"));\n }\n }\n\n const controller = new AbortController();\n const timer = setTimeout(() => controller.abort(), opts.timeout || API_TIMEOUT_MS);\n\n try {\n const res = await fetch(url, {\n method,\n headers,\n body: bodyStr,\n signal: controller.signal,\n });\n return await res.json();\n } finally {\n clearTimeout(timer);\n }\n }\n\n // ── Persistence ──\n\n private accountFile(): string {\n return join(getAccountsDir(), \"weixin.json\");\n }\n\n private syncFile(): string {\n return join(getAccountsDir(), \"weixin-sync.json\");\n }\n\n private async saveAccount(): Promise<void> {\n await ensureDir(getAccountsDir());\n await writeFile(this.accountFile(), JSON.stringify(this.account, null, 2));\n }\n\n private async loadAccount(): Promise<void> {\n const path = this.accountFile();\n if (!existsSync(path)) return;\n try {\n const raw = await readFile(path, \"utf-8\");\n this.account = JSON.parse(raw);\n log.info(`已加载账号: ${this.account!.accountId.slice(0, 8)}...`);\n } catch {\n log.warn(\"加载账号失败\");\n }\n }\n\n private async saveSyncBuf(): Promise<void> {\n await ensureDir(getAccountsDir());\n await writeFile(this.syncFile(), JSON.stringify({ get_updates_buf: this.syncBuf }));\n }\n\n private async loadSyncBuf(): Promise<void> {\n const path = this.syncFile();\n if (!existsSync(path)) return;\n try {\n const raw = await readFile(path, \"utf-8\");\n const data = JSON.parse(raw);\n this.syncBuf = data.get_updates_buf || \"\";\n } catch {\n // fresh start\n }\n }\n}\n\n// ── Helpers ──\n\nfunction randomUin(): string {\n const uint32 = randomBytes(4).readUInt32BE(0);\n return Buffer.from(String(uint32), \"utf-8\").toString(\"base64\");\n}\n\nfunction generateClientId(): string {\n return `wai-${randomUUID().replace(/-/g, \"\").slice(0, 16)}`;\n}\n\nfunction sleep(ms: number): Promise<void> {\n return new Promise((r) => setTimeout(r, ms));\n}\n","import { createLogger } from \"../logger.js\";\nimport type { Provider, ProviderOptions, ProviderConfig } from \"../types.js\";\n\nconst log = createLogger(\"claude\");\n\nconst DEFAULT_TOOLS = [\"Read\", \"Glob\", \"Grep\", \"Bash\", \"WebSearch\", \"WebFetch\"];\n\nexport class ClaudeAgentProvider implements Provider {\n readonly name = \"claude-agent\";\n private config: ProviderConfig;\n private sessions = new Map<string, string>(); // userId -> sessionId\n\n constructor(config: ProviderConfig) {\n this.config = config;\n }\n\n async query(\n prompt: string,\n sessionId: string,\n options?: ProviderOptions,\n ): Promise<string> {\n const { query } = await import(\"@anthropic-ai/claude-agent-sdk\");\n\n const allowedTools = options?.allowedTools\n || (this.config.allowedTools as string[])\n || DEFAULT_TOOLS;\n\n const existingSession = this.sessions.get(sessionId);\n const sdkOptions: Record<string, unknown> = {\n allowedTools,\n permissionMode: \"acceptEdits\" as const,\n };\n\n if (options?.maxTokens) {\n sdkOptions.maxTokens = options.maxTokens;\n }\n\n if (options?.cwd) {\n sdkOptions.cwd = options.cwd;\n }\n\n // Resume existing session for conversation continuity\n if (existingSession) {\n sdkOptions.resume = existingSession;\n }\n\n if (options?.systemPrompt) {\n sdkOptions.systemPrompt = options.systemPrompt;\n }\n\n log.info(`Querying Claude (session: ${sessionId.slice(0, 8)}...)`);\n\n let result = \"\";\n let newSessionId: string | undefined;\n\n try {\n for await (const message of query({\n prompt,\n options: sdkOptions as any,\n })) {\n // Capture session ID from init message\n if (isInitMessage(message)) {\n newSessionId = message.session_id;\n }\n\n // Capture result text\n if (isResultMessage(message)) {\n result = message.result;\n }\n\n // Capture assistant text messages for streaming\n if (isAssistantMessage(message)) {\n // accumulate text from assistant messages\n const textContent = extractText(message);\n if (textContent) {\n result = textContent;\n }\n }\n }\n } catch (err) {\n const errMsg = err instanceof Error ? err.message : String(err);\n log.error(`Claude query failed: ${errMsg}`);\n throw err;\n }\n\n // Store session for continuity\n if (newSessionId) {\n this.sessions.set(sessionId, newSessionId);\n }\n\n if (!result) {\n result = \"(No response from Claude)\";\n }\n\n log.info(`Response: ${result.length} chars`);\n return result;\n }\n}\n\n// ── Message type guards ──\n\nfunction isInitMessage(msg: any): msg is { type: \"system\"; subtype: \"init\"; session_id: string } {\n return msg?.type === \"system\" && msg?.subtype === \"init\" && typeof msg?.session_id === \"string\";\n}\n\nfunction isResultMessage(msg: any): msg is { result: string } {\n return typeof msg?.result === \"string\";\n}\n\nfunction isAssistantMessage(msg: any): msg is { type: \"assistant\"; message: { content: unknown[] } } {\n return msg?.type === \"assistant\" && msg?.message?.content;\n}\n\nfunction extractText(msg: any): string | null {\n if (!msg?.message?.content) return null;\n const parts: string[] = [];\n for (const block of msg.message.content) {\n if (block.type === \"text\" && typeof block.text === \"string\") {\n parts.push(block.text);\n }\n }\n return parts.length > 0 ? parts.join(\"\") : null;\n}\n","import { createLogger } from \"../logger.js\";\nimport type { Provider, ProviderOptions, ProviderConfig } from \"../types.js\";\n\nconst log = createLogger(\"openai-compat\");\n\ninterface ChatMessage {\n role: \"system\" | \"user\" | \"assistant\";\n content: string;\n}\n\ninterface ChatCompletionResponse {\n choices: Array<{\n message: { content: string };\n finish_reason: string;\n }>;\n usage?: { prompt_tokens: number; completion_tokens: number };\n}\n\nexport class OpenAICompatibleProvider implements Provider {\n readonly name: string;\n private config: ProviderConfig;\n private histories = new Map<string, ChatMessage[]>();\n\n constructor(name: string, config: ProviderConfig) {\n this.name = name;\n this.config = config;\n }\n\n async query(\n prompt: string,\n sessionId: string,\n options?: ProviderOptions,\n ): Promise<string> {\n const baseUrl = this.config.baseUrl;\n const apiKey = this.config.apiKey || process.env[this.config.apiKeyEnv as string || \"\"];\n const model = options?.model || (this.config.model as string);\n\n if (!baseUrl) throw new Error(`${this.name}: baseUrl is required`);\n if (!apiKey) throw new Error(`${this.name}: apiKey is required`);\n if (!model) throw new Error(`${this.name}: model is required`);\n\n // Build conversation history\n let history = this.histories.get(sessionId);\n if (!history) {\n history = [];\n this.histories.set(sessionId, history);\n }\n\n const messages: ChatMessage[] = [];\n\n // System prompt\n const systemPrompt = options?.systemPrompt || (this.config.systemPrompt as string);\n if (systemPrompt) {\n messages.push({ role: \"system\", content: systemPrompt });\n }\n\n // Conversation history (keep last N turns to stay within context)\n const maxHistory = (this.config.maxHistory as number) || 20;\n const recentHistory = history.slice(-maxHistory);\n messages.push(...recentHistory);\n\n // Current user message\n messages.push({ role: \"user\", content: prompt });\n\n log.info(`Querying ${this.name} (model: ${model}, session: ${sessionId.slice(0, 8)}...)`);\n\n const url = `${baseUrl.replace(/\\/$/, \"\")}/chat/completions`;\n\n const res = await fetch(url, {\n method: \"POST\",\n headers: {\n \"Content-Type\": \"application/json\",\n \"Authorization\": `Bearer ${apiKey}`,\n },\n body: JSON.stringify({\n model,\n messages,\n max_tokens: options?.maxTokens || (this.config.maxTokens as number) || 4096,\n temperature: (this.config.temperature as number) ?? 0.7,\n }),\n });\n\n if (!res.ok) {\n const errBody = await res.text();\n log.error(`${this.name} API error ${res.status}: ${errBody.slice(0, 200)}`);\n throw new Error(`${this.name} API error: ${res.status}`);\n }\n\n const data = (await res.json()) as ChatCompletionResponse;\n const reply = data.choices[0]?.message.content || \"(No response)\";\n\n if (data.usage) {\n log.info(`Tokens: ${data.usage.prompt_tokens} in / ${data.usage.completion_tokens} out`);\n }\n\n // Update history\n history.push({ role: \"user\", content: prompt });\n history.push({ role: \"assistant\", content: reply });\n\n log.info(`Response: ${reply.length} chars`);\n return reply;\n }\n}\n","import { createLogger } from \"./logger.js\";\nimport type {\n Channel,\n Provider,\n InboundMessage,\n WaiConfig,\n ProviderOptions,\n} from \"./types.js\";\nimport { WeixinChannel } from \"./channels/weixin.js\";\nimport { ClaudeAgentProvider } from \"./providers/claude-agent.js\";\nimport { OpenAICompatibleProvider } from \"./providers/openai-compatible.js\";\n\nconst log = createLogger(\"网关\");\n\nconst DEBOUNCE_MS = 1500;\n\ninterface MessageBuffer {\n messages: InboundMessage[];\n timer: ReturnType<typeof setTimeout>;\n}\n\nexport class Gateway {\n private channels = new Map<string, Channel>();\n private providers = new Map<string, Provider>();\n private config: WaiConfig;\n // Debounce buffer: accumulates messages within DEBOUNCE_MS window\n private buffers = new Map<string, MessageBuffer>();\n // Whether AI is currently processing for a given user\n private processing = new Set<string>();\n // Queue for messages that arrive while AI is processing\n private queues = new Map<string, InboundMessage[]>();\n\n constructor(config: WaiConfig) {\n this.config = config;\n }\n\n init(): void {\n for (const [name, chConfig] of Object.entries(this.config.channels)) {\n if (chConfig.enabled === false) continue;\n switch (chConfig.type) {\n case \"weixin\":\n this.channels.set(name, new WeixinChannel(chConfig));\n break;\n default:\n log.warn(`未知渠道类型: ${chConfig.type}`);\n }\n }\n\n for (const [name, provConfig] of Object.entries(this.config.providers)) {\n switch (provConfig.type) {\n case \"claude-agent\":\n this.providers.set(name, new ClaudeAgentProvider(provConfig));\n break;\n case \"openai-compatible\":\n this.providers.set(name, new OpenAICompatibleProvider(name, provConfig));\n break;\n default:\n log.warn(`未知模型类型: ${provConfig.type}`);\n }\n }\n\n log.info(`已初始化 ${this.channels.size} 个渠道, ${this.providers.size} 个模型`);\n }\n\n async login(channelName: string): Promise<void> {\n const channel = this.channels.get(channelName);\n if (!channel) {\n throw new Error(`渠道 \"${channelName}\" 不存在`);\n }\n await channel.login();\n }\n\n async start(): Promise<void> {\n if (this.providers.size === 0) {\n throw new Error(\"未配置任何模型\");\n }\n\n const startPromises = [...this.channels.entries()].map(([name, channel]) => {\n log.info(`启动渠道: ${name}`);\n return channel.start((msg) => this.handleMessage(msg)).catch((err) => {\n log.error(`渠道 ${name} 异常: ${err instanceof Error ? err.message : err}`);\n });\n });\n\n await Promise.all(startPromises);\n }\n\n async stop(): Promise<void> {\n log.info(\"正在关闭...\");\n const stops = [...this.channels.values()].map((ch) => ch.stop());\n await Promise.allSettled(stops);\n log.info(\"已关闭\");\n }\n\n private handleMessage(msg: InboundMessage): void {\n // Commands bypass debounce, execute immediately\n if (msg.text.startsWith(\"/\")) {\n this.handleCommand(msg);\n return;\n }\n\n const key = `${msg.channel}:${msg.senderId}`;\n\n // If AI is processing, queue the message\n if (this.processing.has(key)) {\n const queue = this.queues.get(key) || [];\n queue.push(msg);\n this.queues.set(key, queue);\n log.info(`消息已排队 (AI处理中), 队列长度: ${queue.length}`);\n return;\n }\n\n // Debounce: accumulate messages within time window\n const existing = this.buffers.get(key);\n if (existing) {\n clearTimeout(existing.timer);\n existing.messages.push(msg);\n existing.timer = setTimeout(() => this.flushBuffer(key), DEBOUNCE_MS);\n } else {\n this.buffers.set(key, {\n messages: [msg],\n timer: setTimeout(() => this.flushBuffer(key), DEBOUNCE_MS),\n });\n }\n }\n\n private async flushBuffer(key: string): Promise<void> {\n const buf = this.buffers.get(key);\n if (!buf || buf.messages.length === 0) return;\n this.buffers.delete(key);\n\n // Merge all buffered messages into one\n const merged = this.mergeMessages(buf.messages);\n await this.processMessage(merged);\n\n // After processing, check if there are queued messages\n const queue = this.queues.get(key);\n if (queue && queue.length > 0) {\n this.queues.delete(key);\n // Feed queued messages back through debounce\n for (const msg of queue) {\n this.handleMessage(msg);\n }\n }\n }\n\n private mergeMessages(messages: InboundMessage[]): InboundMessage {\n if (messages.length === 1) return messages[0]!;\n\n const last = messages[messages.length - 1]!;\n const mergedText = messages.map((m) => m.text).join(\"\\n\");\n log.info(`合并 ${messages.length} 条消息`);\n\n return { ...last, text: mergedText };\n }\n\n private async processMessage(msg: InboundMessage): Promise<void> {\n const key = `${msg.channel}:${msg.senderId}`;\n this.processing.add(key);\n\n try {\n const providerName = this.config.userRoutes?.[msg.senderId]\n || this.config.defaultProvider;\n const provider = this.providers.get(providerName);\n\n if (!provider) {\n log.error(`模型 \"${providerName}\" 未找到`);\n return;\n }\n\n log.info(`调用 ${providerName} 处理中...`);\n\n const channel = this.channels.get(msg.channel);\n if (channel && \"sendTyping\" in channel) {\n (channel as any).sendTyping(msg.senderId, msg.replyToken);\n }\n\n const options: ProviderOptions = {};\n if (this.config.systemPrompt) {\n options.systemPrompt = this.config.systemPrompt;\n }\n\n const sessionKey = `${msg.channel}:${msg.senderId}`;\n const response = await provider.query(msg.text, sessionKey, options);\n\n if (!channel) return;\n\n await channel.send({\n targetId: msg.senderId,\n text: response,\n replyToken: msg.replyToken,\n });\n\n log.info(`已回复 (${response.length} 字符)`);\n } catch (err) {\n const errMsg = err instanceof Error ? err.message : String(err);\n log.error(`处理消息失败: ${errMsg}`);\n\n try {\n const channel = this.channels.get(msg.channel);\n if (channel) {\n await channel.send({\n targetId: msg.senderId,\n text: `[出错了] 处理消息失败,请重试。`,\n replyToken: msg.replyToken,\n });\n }\n } catch {\n // swallow\n }\n } finally {\n this.processing.delete(key);\n }\n }\n\n private async handleCommand(msg: InboundMessage): Promise<void> {\n const channel = this.channels.get(msg.channel);\n if (!channel) return;\n\n const parts = msg.text.trim().split(/\\s+/);\n const cmd = parts[0]!.toLowerCase();\n const arg = parts[1];\n\n switch (cmd) {\n case \"/model\": {\n if (!arg) {\n const current = this.config.userRoutes?.[msg.senderId] || this.config.defaultProvider;\n const available = [...this.providers.keys()].join(\", \");\n await channel.send({\n targetId: msg.senderId,\n text: `当前模型: ${current}\\n可用模型: ${available}\\n用法: /model <名称>`,\n replyToken: msg.replyToken,\n });\n } else if (this.providers.has(arg.toLowerCase())) {\n const provider = arg.toLowerCase();\n if (!this.config.userRoutes) this.config.userRoutes = {};\n this.config.userRoutes[msg.senderId] = provider;\n await channel.send({\n targetId: msg.senderId,\n text: `已切换到: ${provider}`,\n replyToken: msg.replyToken,\n });\n } else {\n await channel.send({\n targetId: msg.senderId,\n text: `未知模型: ${arg}\\n可用: ${[...this.providers.keys()].join(\", \")}`,\n replyToken: msg.replyToken,\n });\n }\n break;\n }\n\n case \"/help\": {\n await channel.send({\n targetId: msg.senderId,\n text: [\n \"wechat-ai 指令:\",\n \"/model [名称] - 切换AI模型\",\n \"/help - 显示帮助\",\n \"/ping - 检查状态\",\n ].join(\"\\n\"),\n replyToken: msg.replyToken,\n });\n break;\n }\n\n case \"/ping\": {\n await channel.send({\n targetId: msg.senderId,\n text: `pong (${Date.now() - msg.timestamp}ms)`,\n replyToken: msg.replyToken,\n });\n break;\n }\n\n default: {\n await channel.send({\n targetId: msg.senderId,\n text: `未知指令: ${cmd},试试 /help`,\n replyToken: msg.replyToken,\n });\n }\n }\n }\n}\n"],"mappings":";AAAA,SAAS,UAAU,WAAW,aAAa;AAC3C,SAAS,kBAAkB;AAC3B,SAAS,eAAe;AACxB,SAAS,YAAY;AAGrB,IAAM,UAAU,KAAK,QAAQ,GAAG,MAAM;AACtC,IAAM,cAAc,KAAK,SAAS,aAAa;AAE/C,IAAM,iBAA4B;AAAA,EAChC,iBAAiB;AAAA,EACjB,WAAW;AAAA,IACT,QAAQ;AAAA,MACN,MAAM;AAAA,MACN,cAAc,CAAC,QAAQ,QAAQ,QAAQ,QAAQ,aAAa,UAAU;AAAA,IACxE;AAAA,IACA,MAAM;AAAA,MACJ,MAAM;AAAA,MACN,SAAS;AAAA,MACT,OAAO;AAAA,MACP,WAAW;AAAA,IACb;AAAA,IACA,UAAU;AAAA,MACR,MAAM;AAAA,MACN,SAAS;AAAA,MACT,OAAO;AAAA,MACP,WAAW;AAAA,IACb;AAAA,IACA,KAAK;AAAA,MACH,MAAM;AAAA,MACN,SAAS;AAAA,MACT,OAAO;AAAA,MACP,WAAW;AAAA,IACb;AAAA,IACA,QAAQ;AAAA,MACN,MAAM;AAAA,MACN,SAAS;AAAA,MACT,OAAO;AAAA,MACP,WAAW;AAAA,IACb;AAAA,IACA,SAAS;AAAA,MACP,MAAM;AAAA,MACN,SAAS;AAAA,MACT,OAAO;AAAA,MACP,WAAW;AAAA,IACb;AAAA,IACA,KAAK;AAAA,MACH,MAAM;AAAA,MACN,SAAS;AAAA,MACT,OAAO;AAAA,MACP,WAAW;AAAA,IACb;AAAA,EACF;AAAA,EACA,UAAU;AAAA,IACR,QAAQ;AAAA,MACN,MAAM;AAAA,MACN,SAAS;AAAA,IACX;AAAA,EACF;AAAA,EACA,cAAc;AAAA,EACd,WAAW;AACb;AAEA,eAAsB,UAAU,KAAa;AAC3C,MAAI,CAAC,WAAW,GAAG,GAAG;AACpB,UAAM,MAAM,KAAK,EAAE,WAAW,KAAK,CAAC;AAAA,EACtC;AACF;AAEA,eAAsB,aAAiC;AACrD,QAAM,UAAU,OAAO;AAEvB,MAAI,CAAC,WAAW,WAAW,GAAG;AAC5B,UAAM,UAAU,aAAa,KAAK,UAAU,gBAAgB,MAAM,CAAC,CAAC;AACpE,WAAO,EAAE,GAAG,eAAe;AAAA,EAC7B;AAEA,QAAM,MAAM,MAAM,SAAS,aAAa,OAAO;AAC/C,QAAM,OAAO,KAAK,MAAM,GAAG;AAG3B,QAAM,YAAY,EAAE,GAAG,eAAe,UAAU;AAChD,MAAI,KAAK,WAAW;AAClB,eAAW,CAAC,KAAK,GAAG,KAAK,OAAO,QAAQ,KAAK,SAAS,GAAG;AACvD,gBAAU,GAAG,IAAI;AAAA,IACnB;AAAA,EACF;AAEA,QAAM,SAAS,EAAE,GAAG,gBAAgB,GAAG,MAAM,UAAU;AAGvD,MAAI,OAAO,UAAU,OAAO;AAC1B,QAAI,CAAC,OAAO,UAAU,KAAK;AACzB,aAAO,UAAU,MAAM,EAAE,GAAG,OAAO,UAAU,OAAO,WAAW,cAAc;AAAA,IAC/E;AACA,WAAO,OAAO,UAAU;AACxB,QAAI,OAAO,oBAAoB,QAAS,QAAO,kBAAkB;AACjE,UAAM,WAAW,MAAM;AAAA,EACzB;AAEA,SAAO;AACT;AAEA,eAAsB,WAAW,QAAkC;AACjE,QAAM,UAAU,OAAO;AACvB,QAAM,UAAU,aAAa,KAAK,UAAU,QAAQ,MAAM,CAAC,CAAC;AAC9D;AAMO,SAAS,iBAAyB;AACvC,SAAO,KAAK,SAAS,UAAU;AACjC;;;AClHA,IAAM,SAAS,EAAE,OAAO,GAAG,MAAM,GAAG,MAAM,GAAG,OAAO,EAAE;AAGtD,IAAI,eAAsB;AAEnB,SAAS,YAAY,OAAc;AACxC,iBAAe;AACjB;AAEA,SAAS,IAAI,OAAc,OAAe,KAAqB;AAC7D,QAAM,MAAK,oBAAI,KAAK,GAAE,YAAY,EAAE,MAAM,IAAI,EAAE;AAChD,QAAM,MAAM,MAAM,YAAY,EAAE,OAAO,CAAC;AACxC,SAAO,WAAW,EAAE,WAAW,SAAS,OAAO,GAAG,CAAC,aAAa,KAAK,YAAY,GAAG;AACtF;AAEA,SAAS,SAAS,OAAc,MAAsB;AACpD,UAAQ,OAAO;AAAA,IACb,KAAK;AAAS,aAAO,WAAW,IAAI;AAAA,IACpC,KAAK;AAAS,aAAO,WAAW,IAAI;AAAA,IACpC,KAAK;AAAS,aAAO,WAAW,IAAI;AAAA,IACpC,KAAK;AAAS,aAAO,WAAW,IAAI;AAAA,EACtC;AACF;AAEO,SAAS,aAAa,OAAe;AAC1C,SAAO;AAAA,IACL,OAAO,CAAC,QAAgB;AAAE,UAAI,OAAO,YAAY,KAAK,EAAG,SAAQ,IAAI,IAAI,SAAS,OAAO,GAAG,CAAC;AAAA,IAAG;AAAA,IAChG,MAAO,CAAC,QAAgB;AAAE,UAAI,OAAO,YAAY,KAAK,EAAG,SAAQ,IAAI,IAAI,QAAS,OAAO,GAAG,CAAC;AAAA,IAAG;AAAA,IAChG,MAAO,CAAC,QAAgB;AAAE,UAAI,OAAO,YAAY,KAAK,EAAG,SAAQ,KAAK,IAAI,QAAS,OAAO,GAAG,CAAC;AAAA,IAAG;AAAA,IACjG,OAAO,CAAC,QAAgB;AAAE,UAAI,OAAO,YAAY,KAAK,EAAG,SAAQ,MAAM,IAAI,SAAS,OAAO,GAAG,CAAC;AAAA,IAAG;AAAA,EACpG;AACF;;;AC7BA,SAAS,QAAAA,aAAY;AACrB,SAAS,YAAAC,WAAU,aAAAC,kBAAiB;AACpC,SAAS,cAAAC,mBAAkB;AAC3B,SAAS,aAAa,kBAAkB;AAGxC,IAAM,MAAM,aAAa,QAAQ;AAEjC,IAAM,mBAAmB;AACzB,IAAM,kBAAkB;AACxB,IAAM,iBAAiB;AAGvB,IAAM,cAAc,EAAE,MAAM,GAAG,KAAK,EAAE;AACtC,IAAM,eAAe,EAAE,KAAK,GAAG,YAAY,GAAG,QAAQ,EAAE;AACxD,IAAM,kBAAkB,EAAE,MAAM,GAAG,OAAO,GAAG,OAAO,GAAG,MAAM,GAAG,OAAO,EAAE;AAuClE,IAAM,gBAAN,MAAuC;AAAA,EACnC,OAAO;AAAA,EAER,UAAgC;AAAA,EAChC,UAAU;AAAA,EACV,UAAU;AAAA,EACV,kBAA0C;AAAA,EAC1C;AAAA;AAAA,EAEA,gBAAgB,oBAAI,IAAoB;AAAA,EAEhD,YAAY,QAAuB;AACjC,SAAK,SAAS;AAAA,EAChB;AAAA;AAAA,EAIA,MAAM,QAAuB;AAC3B,UAAM,UAAW,KAAK,OAAO,WAAsB;AACnD,QAAI,KAAK,yCAAW;AAEpB,UAAM,QAAQ,MAAM,KAAK,IAAI,SAAS,uCAAuC,MAAM;AAAA,MACjF,QAAQ;AAAA,MACR,SAAS;AAAA,IACX,CAAC;AAED,QAAI,MAAM,QAAQ,GAAG;AACnB,YAAM,IAAI,MAAM,+CAAY,MAAM,UAAU,MAAM,GAAG,EAAE;AAAA,IACzD;AAEA,UAAM,QAAgB,MAAM,sBAAsB,MAAM,MAAM;AAC9D,UAAM,SAAiB,MAAM,UAAU,MAAM,MAAM;AAEnD,QAAI,CAAC,SAAS,CAAC,QAAQ;AACrB,YAAM,IAAI,MAAM,2DAAc,KAAK,UAAU,KAAK,CAAC,EAAE;AAAA,IACvD;AAEA,QAAI,KAAK,yDAAY;AACrB,YAAQ,IAAI;AACZ,QAAI;AACF,YAAM,aAAa,MAAM,OAAO,iBAAiB;AACjD,OAAC,WAAW,WAAW,YAAY,SAAS,OAAO,EAAE,OAAO,KAAK,CAAC;AAAA,IACpE,QAAQ;AACN,cAAQ,IAAI,KAAK,KAAK,EAAE;AAAA,IAC1B;AACA,YAAQ,IAAI;AAEZ,QAAI,KAAK,6BAAS;AAElB,QAAI,WAAW;AACf,WAAO,WAAW,IAAI;AACpB,YAAM,YAAY,MAAM,KAAK;AAAA,QAC3B;AAAA,QACA,sCAAsC,mBAAmB,MAAM,CAAC;AAAA,QAChE;AAAA,QACA,EAAE,QAAQ,OAAO,SAAS,IAAO;AAAA,MACnC;AAEA,YAAM,SAAS,UAAU,MAAM,UAAU,UAAU;AAEnD,UAAI,WAAW,aAAa;AAC1B,cAAM,OAAO,UAAU,QAAQ;AAC/B,cAAM,YAAoB,KAAK,gBAAgB,KAAK;AACpD,cAAM,QAAgB,KAAK,aAAa,KAAK;AAE7C,YAAI,CAAC,aAAa,CAAC,OAAO;AACxB,gBAAM,IAAI,MAAM,wDAAW;AAAA,QAC7B;AAEA,aAAK,UAAU;AAAA,UACb;AAAA,UACA;AAAA,UACA,SAAS,KAAK,WAAW;AAAA,UACzB,QAAQ,KAAK;AAAA,QACf;AAEA,cAAM,KAAK,YAAY;AACvB,YAAI,KAAK,+CAAY,UAAU,MAAM,GAAG,CAAC,CAAC,KAAK;AAC/C;AAAA,MACF;AAEA,UAAI,WAAW,UAAU;AACvB,YAAI,KAAK,qDAAa;AAAA,MACxB;AAEA,UAAI,WAAW,WAAW;AACxB,YAAI,KAAK,sCAAQ;AACjB,cAAM,IAAI,MAAM,sCAAQ;AAAA,MAC1B;AAEA;AACA,YAAM,MAAM,GAAG;AAAA,IACjB;AAEA,UAAM,IAAI,MAAM,0BAAM;AAAA,EACxB;AAAA;AAAA,EAIA,MAAM,MAAM,WAAyD;AACnE,QAAI,CAAC,KAAK,SAAS;AACjB,YAAM,KAAK,YAAY;AAAA,IACzB;AACA,QAAI,CAAC,KAAK,SAAS;AACjB,UAAI,KAAK,iEAAe;AACxB,YAAM,KAAK,MAAM;AAAA,IACnB;AAEA,UAAM,KAAK,YAAY;AACvB,SAAK,UAAU;AACf,QAAI,KAAK,+CAAY,KAAK,QAAS,UAAU,MAAM,GAAG,CAAC,CAAC,MAAM;AAE9D,WAAO,KAAK,SAAS;AACnB,UAAI;AACF,aAAK,kBAAkB,IAAI,gBAAgB;AAC3C,cAAM,MAAM,MAAM,KAAK,WAAW;AAElC,YAAI,IAAI,QAAQ,KAAK;AACnB,cAAI,KAAK,2DAAc;AACvB,eAAK,UAAU;AACf,gBAAM,KAAK,MAAM;AACjB;AAAA,QACF;AAEA,YAAI,IAAI,OAAO,IAAI,QAAQ,GAAG;AAC5B,cAAI,KAAK,yCAAW,IAAI,UAAU,KAAK,UAAU,GAAG,CAAC,EAAE;AACvD,gBAAM,MAAM,GAAI;AAChB;AAAA,QACF;AAEA,YAAI,IAAI,iBAAiB;AACvB,eAAK,UAAU,IAAI;AACnB,gBAAM,KAAK,YAAY;AAAA,QACzB;AAEA,YAAI,IAAI,QAAQ,IAAI,KAAK,SAAS,GAAG;AACnC,qBAAW,OAAO,IAAI,MAAM;AAC1B,kBAAM,OAAO,KAAK,YAAY,GAAG;AACjC,gBAAI,CAAC,QAAQ,CAAC,IAAI,aAAc;AAEhC,gBAAI,KAAK,6BAAS,IAAI,aAAa,MAAM,GAAG,CAAC,CAAC,SAAS,KAAK,MAAM,GAAG,EAAE,CAAC,EAAE;AAC1E,sBAAU;AAAA,cACR,IAAI,OAAO,IAAI,cAAc,IAAI,OAAO,KAAK,IAAI,CAAC;AAAA,cAClD,SAAS;AAAA,cACT,UAAU,IAAI;AAAA,cACd;AAAA,cACA,YAAY,IAAI;AAAA,cAChB,WAAW,IAAI,kBAAkB,KAAK,IAAI;AAAA,YAC5C,CAAC;AAAA,UACH;AAAA,QACF;AAAA,MACF,SAAS,KAAK;AACZ,YAAI,CAAC,KAAK,QAAS;AACnB,cAAM,UAAU,eAAe,QAAQ,IAAI,UAAU,OAAO,GAAG;AAC/D,YAAI,QAAQ,SAAS,SAAS,KAAK,QAAQ,SAAS,YAAY,EAAG;AACnE,YAAI,MAAM,6BAAS,OAAO,EAAE;AAC5B,cAAM,MAAM,GAAI;AAAA,MAClB;AAAA,IACF;AAAA,EACF;AAAA;AAAA,EAIA,MAAM,WAAW,QAAgB,cAAsC;AACrE,QAAI,CAAC,KAAK,QAAS;AAEnB,QAAI;AAEF,UAAI,SAAS,KAAK,cAAc,IAAI,MAAM;AAC1C,UAAI,CAAC,QAAQ;AACX,cAAM,YAAY,MAAM,KAAK,IAAI,KAAK,QAAQ,SAAS,uBAAuB;AAAA,UAC5E,eAAe;AAAA,UACf,eAAe;AAAA,UACf,WAAW,EAAE,iBAAiB,gBAAgB;AAAA,QAChD,GAAG,EAAE,SAAS,IAAO,CAAC;AAEtB,iBAAS,UAAU;AACnB,YAAI,QAAQ;AACV,eAAK,cAAc,IAAI,QAAQ,MAAM;AAAA,QACvC;AAAA,MACF;AAEA,UAAI,CAAC,OAAQ;AAEb,YAAM,KAAK,IAAI,KAAK,QAAQ,SAAS,wBAAwB;AAAA,QAC3D,eAAe;AAAA,QACf,eAAe;AAAA,QACf,QAAQ;AAAA,QACR,WAAW,EAAE,iBAAiB,gBAAgB;AAAA,MAChD,GAAG,EAAE,SAAS,IAAO,CAAC;AAEtB,UAAI,MAAM,oDAAY,OAAO,MAAM,GAAG,CAAC,CAAC,KAAK;AAAA,IAC/C,QAAQ;AAAA,IAER;AAAA,EACF;AAAA;AAAA,EAIA,MAAM,KAAK,KAAqC;AAC9C,QAAI,CAAC,KAAK,QAAS,OAAM,IAAI,MAAM,oBAAK;AAExC,UAAM,SAAS,KAAK,UAAU,IAAI,MAAM,GAAI;AAE5C,eAAW,SAAS,QAAQ;AAC1B,YAAM,OAAO;AAAA,QACX,KAAK;AAAA,UACH,cAAc;AAAA,UACd,YAAY,IAAI;AAAA,UAChB,WAAW,iBAAiB;AAAA,UAC5B,cAAc,YAAY;AAAA,UAC1B,eAAe,aAAa;AAAA,UAC5B,eAAe,IAAI,cAAc;AAAA,UACjC,WAAW,CAAC,EAAE,MAAM,gBAAgB,MAAM,WAAW,EAAE,MAAM,MAAM,EAAE,CAAC;AAAA,QACxE;AAAA,QACA,WAAW,EAAE,iBAAiB,gBAAgB;AAAA,MAChD;AAEA,YAAM,MAAM,MAAM,KAAK;AAAA,QACrB,KAAK,QAAQ;AAAA,QACb;AAAA,QACA;AAAA,QACA,EAAE,SAAS,eAAe;AAAA,MAC5B;AAGA,UAAI,IAAI,OAAO,IAAI,QAAQ,GAAG;AAC5B,YAAI,MAAM,6BAAS,IAAI,UAAU,KAAK,UAAU,GAAG,CAAC,EAAE;AAAA,MACxD;AAAA,IACF;AAAA,EACF;AAAA,EAEA,MAAM,OAAsB;AAC1B,SAAK,UAAU;AACf,SAAK,iBAAiB,MAAM;AAC5B,QAAI,KAAK,oBAAK;AAAA,EAChB;AAAA;AAAA,EAIA,MAAc,aAA0C;AACtD,QAAI,CAAC,KAAK,QAAS,OAAM,IAAI,MAAM,oBAAK;AAExC,WAAO,KAAK,IAAI,KAAK,QAAQ,SAAS,wBAAwB;AAAA,MAC5D,iBAAiB,KAAK;AAAA,MACtB,WAAW,EAAE,iBAAiB,gBAAgB;AAAA,IAChD,GAAG,EAAE,SAAS,IAAO,CAAC;AAAA,EACxB;AAAA,EAEQ,YAAY,KAAmC;AACrD,QAAI,CAAC,IAAI,WAAW,OAAQ,QAAO;AACnC,eAAW,QAAQ,IAAI,WAAW;AAChC,UAAI,KAAK,SAAS,KAAK,KAAK,WAAW,MAAM;AAC3C,eAAO,KAAK,UAAU;AAAA,MACxB;AAAA,IACF;AACA,WAAO;AAAA,EACT;AAAA,EAEQ,UAAU,MAAc,QAA0B;AACxD,QAAI,KAAK,UAAU,OAAQ,QAAO,CAAC,IAAI;AACvC,UAAM,SAAmB,CAAC;AAC1B,QAAI,YAAY;AAChB,WAAO,UAAU,SAAS,GAAG;AAC3B,UAAI,UAAU,UAAU,YAAY,MAAM,MAAM;AAChD,UAAI,WAAW,EAAG,WAAU;AAC5B,aAAO,KAAK,UAAU,MAAM,GAAG,OAAO,CAAC;AACvC,kBAAY,UAAU,MAAM,OAAO;AAAA,IACrC;AACA,WAAO;AAAA,EACT;AAAA,EAEA,MAAc,IACZ,SACA,MACA,MACA,OAA8C,CAAC,GACjC;AACd,UAAM,MAAM,GAAG,QAAQ,QAAQ,OAAO,EAAE,CAAC,IAAI,IAAI;AACjD,UAAM,SAAS,KAAK,UAAU;AAC9B,UAAM,UAAU,OAAO,KAAK,UAAU,IAAI,IAAI;AAE9C,UAAM,UAAkC;AAAA,MACtC,gBAAgB;AAAA,IAClB;AAEA,QAAI,KAAK,SAAS,OAAO;AACvB,cAAQ,mBAAmB,IAAI;AAC/B,cAAQ,eAAe,IAAI,UAAU,KAAK,QAAQ,KAAK;AACvD,cAAQ,cAAc,IAAI,UAAU;AACpC,UAAI,SAAS;AACX,gBAAQ,gBAAgB,IAAI,OAAO,OAAO,WAAW,SAAS,OAAO,CAAC;AAAA,MACxE;AAAA,IACF;AAEA,UAAM,aAAa,IAAI,gBAAgB;AACvC,UAAM,QAAQ,WAAW,MAAM,WAAW,MAAM,GAAG,KAAK,WAAW,cAAc;AAEjF,QAAI;AACF,YAAM,MAAM,MAAM,MAAM,KAAK;AAAA,QAC3B;AAAA,QACA;AAAA,QACA,MAAM;AAAA,QACN,QAAQ,WAAW;AAAA,MACrB,CAAC;AACD,aAAO,MAAM,IAAI,KAAK;AAAA,IACxB,UAAE;AACA,mBAAa,KAAK;AAAA,IACpB;AAAA,EACF;AAAA;AAAA,EAIQ,cAAsB;AAC5B,WAAOH,MAAK,eAAe,GAAG,aAAa;AAAA,EAC7C;AAAA,EAEQ,WAAmB;AACzB,WAAOA,MAAK,eAAe,GAAG,kBAAkB;AAAA,EAClD;AAAA,EAEA,MAAc,cAA6B;AACzC,UAAM,UAAU,eAAe,CAAC;AAChC,UAAME,WAAU,KAAK,YAAY,GAAG,KAAK,UAAU,KAAK,SAAS,MAAM,CAAC,CAAC;AAAA,EAC3E;AAAA,EAEA,MAAc,cAA6B;AACzC,UAAM,OAAO,KAAK,YAAY;AAC9B,QAAI,CAACC,YAAW,IAAI,EAAG;AACvB,QAAI;AACF,YAAM,MAAM,MAAMF,UAAS,MAAM,OAAO;AACxC,WAAK,UAAU,KAAK,MAAM,GAAG;AAC7B,UAAI,KAAK,mCAAU,KAAK,QAAS,UAAU,MAAM,GAAG,CAAC,CAAC,KAAK;AAAA,IAC7D,QAAQ;AACN,UAAI,KAAK,sCAAQ;AAAA,IACnB;AAAA,EACF;AAAA,EAEA,MAAc,cAA6B;AACzC,UAAM,UAAU,eAAe,CAAC;AAChC,UAAMC,WAAU,KAAK,SAAS,GAAG,KAAK,UAAU,EAAE,iBAAiB,KAAK,QAAQ,CAAC,CAAC;AAAA,EACpF;AAAA,EAEA,MAAc,cAA6B;AACzC,UAAM,OAAO,KAAK,SAAS;AAC3B,QAAI,CAACC,YAAW,IAAI,EAAG;AACvB,QAAI;AACF,YAAM,MAAM,MAAMF,UAAS,MAAM,OAAO;AACxC,YAAM,OAAO,KAAK,MAAM,GAAG;AAC3B,WAAK,UAAU,KAAK,mBAAmB;AAAA,IACzC,QAAQ;AAAA,IAER;AAAA,EACF;AACF;AAIA,SAAS,YAAoB;AAC3B,QAAM,SAAS,YAAY,CAAC,EAAE,aAAa,CAAC;AAC5C,SAAO,OAAO,KAAK,OAAO,MAAM,GAAG,OAAO,EAAE,SAAS,QAAQ;AAC/D;AAEA,SAAS,mBAA2B;AAClC,SAAO,OAAO,WAAW,EAAE,QAAQ,MAAM,EAAE,EAAE,MAAM,GAAG,EAAE,CAAC;AAC3D;AAEA,SAAS,MAAM,IAA2B;AACxC,SAAO,IAAI,QAAQ,CAAC,MAAM,WAAW,GAAG,EAAE,CAAC;AAC7C;;;ACtaA,IAAMG,OAAM,aAAa,QAAQ;AAEjC,IAAM,gBAAgB,CAAC,QAAQ,QAAQ,QAAQ,QAAQ,aAAa,UAAU;AAEvE,IAAM,sBAAN,MAA8C;AAAA,EAC1C,OAAO;AAAA,EACR;AAAA,EACA,WAAW,oBAAI,IAAoB;AAAA;AAAA,EAE3C,YAAY,QAAwB;AAClC,SAAK,SAAS;AAAA,EAChB;AAAA,EAEA,MAAM,MACJ,QACA,WACA,SACiB;AACjB,UAAM,EAAE,MAAM,IAAI,MAAM,OAAO,gCAAgC;AAE/D,UAAM,eAAe,SAAS,gBACxB,KAAK,OAAO,gBACb;AAEL,UAAM,kBAAkB,KAAK,SAAS,IAAI,SAAS;AACnD,UAAM,aAAsC;AAAA,MAC1C;AAAA,MACA,gBAAgB;AAAA,IAClB;AAEA,QAAI,SAAS,WAAW;AACtB,iBAAW,YAAY,QAAQ;AAAA,IACjC;AAEA,QAAI,SAAS,KAAK;AAChB,iBAAW,MAAM,QAAQ;AAAA,IAC3B;AAGA,QAAI,iBAAiB;AACnB,iBAAW,SAAS;AAAA,IACtB;AAEA,QAAI,SAAS,cAAc;AACzB,iBAAW,eAAe,QAAQ;AAAA,IACpC;AAEA,IAAAA,KAAI,KAAK,6BAA6B,UAAU,MAAM,GAAG,CAAC,CAAC,MAAM;AAEjE,QAAI,SAAS;AACb,QAAI;AAEJ,QAAI;AACF,uBAAiB,WAAW,MAAM;AAAA,QAChC;AAAA,QACA,SAAS;AAAA,MACX,CAAC,GAAG;AAEF,YAAI,cAAc,OAAO,GAAG;AAC1B,yBAAe,QAAQ;AAAA,QACzB;AAGA,YAAI,gBAAgB,OAAO,GAAG;AAC5B,mBAAS,QAAQ;AAAA,QACnB;AAGA,YAAI,mBAAmB,OAAO,GAAG;AAE/B,gBAAM,cAAc,YAAY,OAAO;AACvC,cAAI,aAAa;AACf,qBAAS;AAAA,UACX;AAAA,QACF;AAAA,MACF;AAAA,IACF,SAAS,KAAK;AACZ,YAAM,SAAS,eAAe,QAAQ,IAAI,UAAU,OAAO,GAAG;AAC9D,MAAAA,KAAI,MAAM,wBAAwB,MAAM,EAAE;AAC1C,YAAM;AAAA,IACR;AAGA,QAAI,cAAc;AAChB,WAAK,SAAS,IAAI,WAAW,YAAY;AAAA,IAC3C;AAEA,QAAI,CAAC,QAAQ;AACX,eAAS;AAAA,IACX;AAEA,IAAAA,KAAI,KAAK,aAAa,OAAO,MAAM,QAAQ;AAC3C,WAAO;AAAA,EACT;AACF;AAIA,SAAS,cAAc,KAA0E;AAC/F,SAAO,KAAK,SAAS,YAAY,KAAK,YAAY,UAAU,OAAO,KAAK,eAAe;AACzF;AAEA,SAAS,gBAAgB,KAAqC;AAC5D,SAAO,OAAO,KAAK,WAAW;AAChC;AAEA,SAAS,mBAAmB,KAAyE;AACnG,SAAO,KAAK,SAAS,eAAe,KAAK,SAAS;AACpD;AAEA,SAAS,YAAY,KAAyB;AAC5C,MAAI,CAAC,KAAK,SAAS,QAAS,QAAO;AACnC,QAAM,QAAkB,CAAC;AACzB,aAAW,SAAS,IAAI,QAAQ,SAAS;AACvC,QAAI,MAAM,SAAS,UAAU,OAAO,MAAM,SAAS,UAAU;AAC3D,YAAM,KAAK,MAAM,IAAI;AAAA,IACvB;AAAA,EACF;AACA,SAAO,MAAM,SAAS,IAAI,MAAM,KAAK,EAAE,IAAI;AAC7C;;;ACvHA,IAAMC,OAAM,aAAa,eAAe;AAejC,IAAM,2BAAN,MAAmD;AAAA,EAC/C;AAAA,EACD;AAAA,EACA,YAAY,oBAAI,IAA2B;AAAA,EAEnD,YAAY,MAAc,QAAwB;AAChD,SAAK,OAAO;AACZ,SAAK,SAAS;AAAA,EAChB;AAAA,EAEA,MAAM,MACJ,QACA,WACA,SACiB;AACjB,UAAM,UAAU,KAAK,OAAO;AAC5B,UAAM,SAAS,KAAK,OAAO,UAAU,QAAQ,IAAI,KAAK,OAAO,aAAuB,EAAE;AACtF,UAAM,QAAQ,SAAS,SAAU,KAAK,OAAO;AAE7C,QAAI,CAAC,QAAS,OAAM,IAAI,MAAM,GAAG,KAAK,IAAI,uBAAuB;AACjE,QAAI,CAAC,OAAQ,OAAM,IAAI,MAAM,GAAG,KAAK,IAAI,sBAAsB;AAC/D,QAAI,CAAC,MAAO,OAAM,IAAI,MAAM,GAAG,KAAK,IAAI,qBAAqB;AAG7D,QAAI,UAAU,KAAK,UAAU,IAAI,SAAS;AAC1C,QAAI,CAAC,SAAS;AACZ,gBAAU,CAAC;AACX,WAAK,UAAU,IAAI,WAAW,OAAO;AAAA,IACvC;AAEA,UAAM,WAA0B,CAAC;AAGjC,UAAM,eAAe,SAAS,gBAAiB,KAAK,OAAO;AAC3D,QAAI,cAAc;AAChB,eAAS,KAAK,EAAE,MAAM,UAAU,SAAS,aAAa,CAAC;AAAA,IACzD;AAGA,UAAM,aAAc,KAAK,OAAO,cAAyB;AACzD,UAAM,gBAAgB,QAAQ,MAAM,CAAC,UAAU;AAC/C,aAAS,KAAK,GAAG,aAAa;AAG9B,aAAS,KAAK,EAAE,MAAM,QAAQ,SAAS,OAAO,CAAC;AAE/C,IAAAA,KAAI,KAAK,YAAY,KAAK,IAAI,YAAY,KAAK,cAAc,UAAU,MAAM,GAAG,CAAC,CAAC,MAAM;AAExF,UAAM,MAAM,GAAG,QAAQ,QAAQ,OAAO,EAAE,CAAC;AAEzC,UAAM,MAAM,MAAM,MAAM,KAAK;AAAA,MAC3B,QAAQ;AAAA,MACR,SAAS;AAAA,QACP,gBAAgB;AAAA,QAChB,iBAAiB,UAAU,MAAM;AAAA,MACnC;AAAA,MACA,MAAM,KAAK,UAAU;AAAA,QACnB;AAAA,QACA;AAAA,QACA,YAAY,SAAS,aAAc,KAAK,OAAO,aAAwB;AAAA,QACvE,aAAc,KAAK,OAAO,eAA0B;AAAA,MACtD,CAAC;AAAA,IACH,CAAC;AAED,QAAI,CAAC,IAAI,IAAI;AACX,YAAM,UAAU,MAAM,IAAI,KAAK;AAC/B,MAAAA,KAAI,MAAM,GAAG,KAAK,IAAI,cAAc,IAAI,MAAM,KAAK,QAAQ,MAAM,GAAG,GAAG,CAAC,EAAE;AAC1E,YAAM,IAAI,MAAM,GAAG,KAAK,IAAI,eAAe,IAAI,MAAM,EAAE;AAAA,IACzD;AAEA,UAAM,OAAQ,MAAM,IAAI,KAAK;AAC7B,UAAM,QAAQ,KAAK,QAAQ,CAAC,GAAG,QAAQ,WAAW;AAElD,QAAI,KAAK,OAAO;AACd,MAAAA,KAAI,KAAK,WAAW,KAAK,MAAM,aAAa,SAAS,KAAK,MAAM,iBAAiB,MAAM;AAAA,IACzF;AAGA,YAAQ,KAAK,EAAE,MAAM,QAAQ,SAAS,OAAO,CAAC;AAC9C,YAAQ,KAAK,EAAE,MAAM,aAAa,SAAS,MAAM,CAAC;AAElD,IAAAA,KAAI,KAAK,aAAa,MAAM,MAAM,QAAQ;AAC1C,WAAO;AAAA,EACT;AACF;;;AC1FA,IAAMC,OAAM,aAAa,cAAI;AAE7B,IAAM,cAAc;AAOb,IAAM,UAAN,MAAc;AAAA,EACX,WAAW,oBAAI,IAAqB;AAAA,EACpC,YAAY,oBAAI,IAAsB;AAAA,EACtC;AAAA;AAAA,EAEA,UAAU,oBAAI,IAA2B;AAAA;AAAA,EAEzC,aAAa,oBAAI,IAAY;AAAA;AAAA,EAE7B,SAAS,oBAAI,IAA8B;AAAA,EAEnD,YAAY,QAAmB;AAC7B,SAAK,SAAS;AAAA,EAChB;AAAA,EAEA,OAAa;AACX,eAAW,CAAC,MAAM,QAAQ,KAAK,OAAO,QAAQ,KAAK,OAAO,QAAQ,GAAG;AACnE,UAAI,SAAS,YAAY,MAAO;AAChC,cAAQ,SAAS,MAAM;AAAA,QACrB,KAAK;AACH,eAAK,SAAS,IAAI,MAAM,IAAI,cAAc,QAAQ,CAAC;AACnD;AAAA,QACF;AACE,UAAAA,KAAI,KAAK,yCAAW,SAAS,IAAI,EAAE;AAAA,MACvC;AAAA,IACF;AAEA,eAAW,CAAC,MAAM,UAAU,KAAK,OAAO,QAAQ,KAAK,OAAO,SAAS,GAAG;AACtE,cAAQ,WAAW,MAAM;AAAA,QACvB,KAAK;AACH,eAAK,UAAU,IAAI,MAAM,IAAI,oBAAoB,UAAU,CAAC;AAC5D;AAAA,QACF,KAAK;AACH,eAAK,UAAU,IAAI,MAAM,IAAI,yBAAyB,MAAM,UAAU,CAAC;AACvE;AAAA,QACF;AACE,UAAAA,KAAI,KAAK,yCAAW,WAAW,IAAI,EAAE;AAAA,MACzC;AAAA,IACF;AAEA,IAAAA,KAAI,KAAK,4BAAQ,KAAK,SAAS,IAAI,wBAAS,KAAK,UAAU,IAAI,qBAAM;AAAA,EACvE;AAAA,EAEA,MAAM,MAAM,aAAoC;AAC9C,UAAM,UAAU,KAAK,SAAS,IAAI,WAAW;AAC7C,QAAI,CAAC,SAAS;AACZ,YAAM,IAAI,MAAM,iBAAO,WAAW,sBAAO;AAAA,IAC3C;AACA,UAAM,QAAQ,MAAM;AAAA,EACtB;AAAA,EAEA,MAAM,QAAuB;AAC3B,QAAI,KAAK,UAAU,SAAS,GAAG;AAC7B,YAAM,IAAI,MAAM,4CAAS;AAAA,IAC3B;AAEA,UAAM,gBAAgB,CAAC,GAAG,KAAK,SAAS,QAAQ,CAAC,EAAE,IAAI,CAAC,CAAC,MAAM,OAAO,MAAM;AAC1E,MAAAA,KAAI,KAAK,6BAAS,IAAI,EAAE;AACxB,aAAO,QAAQ,MAAM,CAAC,QAAQ,KAAK,cAAc,GAAG,CAAC,EAAE,MAAM,CAAC,QAAQ;AACpE,QAAAA,KAAI,MAAM,gBAAM,IAAI,kBAAQ,eAAe,QAAQ,IAAI,UAAU,GAAG,EAAE;AAAA,MACxE,CAAC;AAAA,IACH,CAAC;AAED,UAAM,QAAQ,IAAI,aAAa;AAAA,EACjC;AAAA,EAEA,MAAM,OAAsB;AAC1B,IAAAA,KAAI,KAAK,6BAAS;AAClB,UAAM,QAAQ,CAAC,GAAG,KAAK,SAAS,OAAO,CAAC,EAAE,IAAI,CAAC,OAAO,GAAG,KAAK,CAAC;AAC/D,UAAM,QAAQ,WAAW,KAAK;AAC9B,IAAAA,KAAI,KAAK,oBAAK;AAAA,EAChB;AAAA,EAEQ,cAAc,KAA2B;AAE/C,QAAI,IAAI,KAAK,WAAW,GAAG,GAAG;AAC5B,WAAK,cAAc,GAAG;AACtB;AAAA,IACF;AAEA,UAAM,MAAM,GAAG,IAAI,OAAO,IAAI,IAAI,QAAQ;AAG1C,QAAI,KAAK,WAAW,IAAI,GAAG,GAAG;AAC5B,YAAM,QAAQ,KAAK,OAAO,IAAI,GAAG,KAAK,CAAC;AACvC,YAAM,KAAK,GAAG;AACd,WAAK,OAAO,IAAI,KAAK,KAAK;AAC1B,MAAAA,KAAI,KAAK,oFAAwB,MAAM,MAAM,EAAE;AAC/C;AAAA,IACF;AAGA,UAAM,WAAW,KAAK,QAAQ,IAAI,GAAG;AACrC,QAAI,UAAU;AACZ,mBAAa,SAAS,KAAK;AAC3B,eAAS,SAAS,KAAK,GAAG;AAC1B,eAAS,QAAQ,WAAW,MAAM,KAAK,YAAY,GAAG,GAAG,WAAW;AAAA,IACtE,OAAO;AACL,WAAK,QAAQ,IAAI,KAAK;AAAA,QACpB,UAAU,CAAC,GAAG;AAAA,QACd,OAAO,WAAW,MAAM,KAAK,YAAY,GAAG,GAAG,WAAW;AAAA,MAC5D,CAAC;AAAA,IACH;AAAA,EACF;AAAA,EAEA,MAAc,YAAY,KAA4B;AACpD,UAAM,MAAM,KAAK,QAAQ,IAAI,GAAG;AAChC,QAAI,CAAC,OAAO,IAAI,SAAS,WAAW,EAAG;AACvC,SAAK,QAAQ,OAAO,GAAG;AAGvB,UAAM,SAAS,KAAK,cAAc,IAAI,QAAQ;AAC9C,UAAM,KAAK,eAAe,MAAM;AAGhC,UAAM,QAAQ,KAAK,OAAO,IAAI,GAAG;AACjC,QAAI,SAAS,MAAM,SAAS,GAAG;AAC7B,WAAK,OAAO,OAAO,GAAG;AAEtB,iBAAW,OAAO,OAAO;AACvB,aAAK,cAAc,GAAG;AAAA,MACxB;AAAA,IACF;AAAA,EACF;AAAA,EAEQ,cAAc,UAA4C;AAChE,QAAI,SAAS,WAAW,EAAG,QAAO,SAAS,CAAC;AAE5C,UAAM,OAAO,SAAS,SAAS,SAAS,CAAC;AACzC,UAAM,aAAa,SAAS,IAAI,CAAC,MAAM,EAAE,IAAI,EAAE,KAAK,IAAI;AACxD,IAAAA,KAAI,KAAK,gBAAM,SAAS,MAAM,qBAAM;AAEpC,WAAO,EAAE,GAAG,MAAM,MAAM,WAAW;AAAA,EACrC;AAAA,EAEA,MAAc,eAAe,KAAoC;AAC/D,UAAM,MAAM,GAAG,IAAI,OAAO,IAAI,IAAI,QAAQ;AAC1C,SAAK,WAAW,IAAI,GAAG;AAEvB,QAAI;AACF,YAAM,eAAe,KAAK,OAAO,aAAa,IAAI,QAAQ,KACrD,KAAK,OAAO;AACjB,YAAM,WAAW,KAAK,UAAU,IAAI,YAAY;AAEhD,UAAI,CAAC,UAAU;AACb,QAAAA,KAAI,MAAM,iBAAO,YAAY,sBAAO;AACpC;AAAA,MACF;AAEA,MAAAA,KAAI,KAAK,gBAAM,YAAY,wBAAS;AAEpC,YAAM,UAAU,KAAK,SAAS,IAAI,IAAI,OAAO;AAC7C,UAAI,WAAW,gBAAgB,SAAS;AACtC,QAAC,QAAgB,WAAW,IAAI,UAAU,IAAI,UAAU;AAAA,MAC1D;AAEA,YAAM,UAA2B,CAAC;AAClC,UAAI,KAAK,OAAO,cAAc;AAC5B,gBAAQ,eAAe,KAAK,OAAO;AAAA,MACrC;AAEA,YAAM,aAAa,GAAG,IAAI,OAAO,IAAI,IAAI,QAAQ;AACjD,YAAM,WAAW,MAAM,SAAS,MAAM,IAAI,MAAM,YAAY,OAAO;AAEnE,UAAI,CAAC,QAAS;AAEd,YAAM,QAAQ,KAAK;AAAA,QACjB,UAAU,IAAI;AAAA,QACd,MAAM;AAAA,QACN,YAAY,IAAI;AAAA,MAClB,CAAC;AAED,MAAAA,KAAI,KAAK,uBAAQ,SAAS,MAAM,gBAAM;AAAA,IACxC,SAAS,KAAK;AACZ,YAAM,SAAS,eAAe,QAAQ,IAAI,UAAU,OAAO,GAAG;AAC9D,MAAAA,KAAI,MAAM,yCAAW,MAAM,EAAE;AAE7B,UAAI;AACF,cAAM,UAAU,KAAK,SAAS,IAAI,IAAI,OAAO;AAC7C,YAAI,SAAS;AACX,gBAAM,QAAQ,KAAK;AAAA,YACjB,UAAU,IAAI;AAAA,YACd,MAAM;AAAA,YACN,YAAY,IAAI;AAAA,UAClB,CAAC;AAAA,QACH;AAAA,MACF,QAAQ;AAAA,MAER;AAAA,IACF,UAAE;AACA,WAAK,WAAW,OAAO,GAAG;AAAA,IAC5B;AAAA,EACF;AAAA,EAEA,MAAc,cAAc,KAAoC;AAC9D,UAAM,UAAU,KAAK,SAAS,IAAI,IAAI,OAAO;AAC7C,QAAI,CAAC,QAAS;AAEd,UAAM,QAAQ,IAAI,KAAK,KAAK,EAAE,MAAM,KAAK;AACzC,UAAM,MAAM,MAAM,CAAC,EAAG,YAAY;AAClC,UAAM,MAAM,MAAM,CAAC;AAEnB,YAAQ,KAAK;AAAA,MACX,KAAK,UAAU;AACb,YAAI,CAAC,KAAK;AACR,gBAAM,UAAU,KAAK,OAAO,aAAa,IAAI,QAAQ,KAAK,KAAK,OAAO;AACtE,gBAAM,YAAY,CAAC,GAAG,KAAK,UAAU,KAAK,CAAC,EAAE,KAAK,IAAI;AACtD,gBAAM,QAAQ,KAAK;AAAA,YACjB,UAAU,IAAI;AAAA,YACd,MAAM,6BAAS,OAAO;AAAA,4BAAW,SAAS;AAAA;AAAA,YAC1C,YAAY,IAAI;AAAA,UAClB,CAAC;AAAA,QACH,WAAW,KAAK,UAAU,IAAI,IAAI,YAAY,CAAC,GAAG;AAChD,gBAAM,WAAW,IAAI,YAAY;AACjC,cAAI,CAAC,KAAK,OAAO,WAAY,MAAK,OAAO,aAAa,CAAC;AACvD,eAAK,OAAO,WAAW,IAAI,QAAQ,IAAI;AACvC,gBAAM,QAAQ,KAAK;AAAA,YACjB,UAAU,IAAI;AAAA,YACd,MAAM,6BAAS,QAAQ;AAAA,YACvB,YAAY,IAAI;AAAA,UAClB,CAAC;AAAA,QACH,OAAO;AACL,gBAAM,QAAQ,KAAK;AAAA,YACjB,UAAU,IAAI;AAAA,YACd,MAAM,6BAAS,GAAG;AAAA,gBAAS,CAAC,GAAG,KAAK,UAAU,KAAK,CAAC,EAAE,KAAK,IAAI,CAAC;AAAA,YAChE,YAAY,IAAI;AAAA,UAClB,CAAC;AAAA,QACH;AACA;AAAA,MACF;AAAA,MAEA,KAAK,SAAS;AACZ,cAAM,QAAQ,KAAK;AAAA,UACjB,UAAU,IAAI;AAAA,UACd,MAAM;AAAA,YACJ;AAAA,YACA;AAAA,YACA;AAAA,YACA;AAAA,UACF,EAAE,KAAK,IAAI;AAAA,UACX,YAAY,IAAI;AAAA,QAClB,CAAC;AACD;AAAA,MACF;AAAA,MAEA,KAAK,SAAS;AACZ,cAAM,QAAQ,KAAK;AAAA,UACjB,UAAU,IAAI;AAAA,UACd,MAAM,SAAS,KAAK,IAAI,IAAI,IAAI,SAAS;AAAA,UACzC,YAAY,IAAI;AAAA,QAClB,CAAC;AACD;AAAA,MACF;AAAA,MAEA,SAAS;AACP,cAAM,QAAQ,KAAK;AAAA,UACjB,UAAU,IAAI;AAAA,UACd,MAAM,6BAAS,GAAG;AAAA,UAClB,YAAY,IAAI;AAAA,QAClB,CAAC;AAAA,MACH;AAAA,IACF;AAAA,EACF;AACF;","names":["join","readFile","writeFile","existsSync","log","log","log"]}
package/dist/cli.js CHANGED
@@ -5,28 +5,35 @@ import {
5
5
  loadConfig,
6
6
  saveConfig,
7
7
  setLogLevel
8
- } from "./chunk-PRU3A74K.js";
8
+ } from "./chunk-CP3Y2VRX.js";
9
9
 
10
10
  // src/cli.ts
11
+ import { readFileSync } from "fs";
12
+ import { fileURLToPath } from "url";
13
+ import { dirname, join } from "path";
14
+ var __dirname = dirname(fileURLToPath(import.meta.url));
15
+ var pkg = JSON.parse(readFileSync(join(__dirname, "..", "package.json"), "utf-8"));
16
+ var VERSION = pkg.version;
11
17
  var log = createLogger("cli");
12
18
  var HELP = `
13
- \x1B[1mwx-ai\x1B[0m \u2014 WeChat AI Bot
19
+ \x1B[1mwechat-ai\x1B[0m \u2014 WeChat AI Bot
14
20
 
15
21
  \x1B[1m\u547D\u4EE4:\x1B[0m
16
- wx-ai \u542F\u52A8 (\u9996\u6B21\u81EA\u52A8\u626B\u7801\u767B\u5F55)
17
- wx-ai set <provider> <key> \u8BBE\u7F6E\u6A21\u578B API Key
18
- wx-ai use <provider> \u8BBE\u7F6E\u9ED8\u8BA4\u6A21\u578B
19
- wx-ai config \u67E5\u770B\u5F53\u524D\u914D\u7F6E
20
- wx-ai help \u663E\u793A\u5E2E\u52A9
22
+ wechat-ai \u542F\u52A8 (\u9996\u6B21\u81EA\u52A8\u626B\u7801\u767B\u5F55)
23
+ wechat-ai set <provider> <key> \u8BBE\u7F6E\u6A21\u578B API Key
24
+ wechat-ai use <provider> \u8BBE\u7F6E\u9ED8\u8BA4\u6A21\u578B
25
+ wechat-ai config \u67E5\u770B\u5F53\u524D\u914D\u7F6E
26
+ wechat-ai update \u66F4\u65B0\u5230\u6700\u65B0\u7248
27
+ wechat-ai help \u663E\u793A\u5E2E\u52A9
21
28
 
22
29
  \x1B[1m\u8BBE\u7F6E API Key:\x1B[0m
23
- wx-ai set qwen sk-xxx \u8BBE\u7F6E\u901A\u4E49\u5343\u95EE Key
24
- wx-ai set deepseek sk-xxx \u8BBE\u7F6E DeepSeek Key
25
- wx-ai set claude sk-xxx \u8BBE\u7F6E Claude Key
30
+ wechat-ai set qwen sk-xxx \u8BBE\u7F6E\u901A\u4E49\u5343\u95EE Key
31
+ wechat-ai set deepseek sk-xxx \u8BBE\u7F6E DeepSeek Key
32
+ wechat-ai set claude sk-xxx \u8BBE\u7F6E Claude Key
26
33
 
27
34
  \x1B[1m\u8BBE\u7F6E\u9ED8\u8BA4\u6A21\u578B:\x1B[0m
28
- wx-ai use qwen \u9ED8\u8BA4\u4F7F\u7528 Qwen
29
- wx-ai use deepseek \u9ED8\u8BA4\u4F7F\u7528 DeepSeek
35
+ wechat-ai use qwen \u9ED8\u8BA4\u4F7F\u7528 Qwen
36
+ wechat-ai use deepseek \u9ED8\u8BA4\u4F7F\u7528 DeepSeek
30
37
 
31
38
  \x1B[1m\u5FAE\u4FE1\u6307\u4EE4:\x1B[0m
32
39
  /model \u67E5\u770B\u5F53\u524D\u6A21\u578B
@@ -39,6 +46,10 @@ async function main() {
39
46
  const command = args[0];
40
47
  const logLevel = process.env.WAI_LOG_LEVEL || "info";
41
48
  setLogLevel(logLevel);
49
+ if (command === "--version" || command === "-v") {
50
+ console.log(`wechat-ai v${VERSION}`);
51
+ process.exit(0);
52
+ }
42
53
  if (command === "help" || command === "--help" || command === "-h") {
43
54
  console.log(HELP);
44
55
  process.exit(0);
@@ -49,8 +60,8 @@ async function main() {
49
60
  const provider = args[1];
50
61
  const apiKey = args[2];
51
62
  if (!provider || !apiKey) {
52
- console.log("\u7528\u6CD5: wx-ai set <provider> <key>");
53
- console.log("\u793A\u4F8B: wx-ai set qwen sk-xxx");
63
+ console.log("\u7528\u6CD5: wechat-ai set <provider> <key>");
64
+ console.log("\u793A\u4F8B: wechat-ai set qwen sk-xxx");
54
65
  process.exit(1);
55
66
  }
56
67
  if (!config.providers[provider]) {
@@ -80,6 +91,18 @@ async function main() {
80
91
  console.log(`\x1B[32m\u2713\x1B[0m \u9ED8\u8BA4\u6A21\u578B\u5DF2\u5207\u6362\u5230 ${provider}`);
81
92
  break;
82
93
  }
94
+ case "update": {
95
+ const { execSync } = await import("child_process");
96
+ console.log("\u6B63\u5728\u66F4\u65B0 wechat-ai...");
97
+ try {
98
+ execSync("npm i -g wechat-ai@latest", { stdio: "inherit" });
99
+ console.log("\x1B[32m\u2713\x1B[0m \u66F4\u65B0\u5B8C\u6210");
100
+ } catch {
101
+ console.error("\x1B[31m\u2717\x1B[0m \u66F4\u65B0\u5931\u8D25\uFF0C\u8BF7\u624B\u52A8\u6267\u884C: npm i -g wechat-ai@latest");
102
+ process.exit(1);
103
+ }
104
+ break;
105
+ }
83
106
  case "config": {
84
107
  const display = JSON.parse(JSON.stringify(config));
85
108
  for (const p of Object.values(display.providers)) {
@@ -100,7 +123,7 @@ async function main() {
100
123
  };
101
124
  process.on("SIGINT", shutdown);
102
125
  process.on("SIGTERM", shutdown);
103
- log.info("\u542F\u52A8 wx-ai...");
126
+ log.info(`wechat-ai v${VERSION}`);
104
127
  await gateway.start();
105
128
  break;
106
129
  }
package/dist/cli.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/cli.ts"],"sourcesContent":["#!/usr/bin/env node\n\nimport { loadConfig, saveConfig } from \"./config.js\";\nimport { Gateway } from \"./gateway.js\";\nimport { setLogLevel, createLogger } from \"./logger.js\";\n\nconst log = createLogger(\"cli\");\n\nconst HELP = `\n \\x1b[1mwx-ai\\x1b[0m — WeChat AI Bot\n\n \\x1b[1m命令:\\x1b[0m\n wx-ai 启动 (首次自动扫码登录)\n wx-ai set <provider> <key> 设置模型 API Key\n wx-ai use <provider> 设置默认模型\n wx-ai config 查看当前配置\n wx-ai help 显示帮助\n\n \\x1b[1m设置 API Key:\\x1b[0m\n wx-ai set qwen sk-xxx 设置通义千问 Key\n wx-ai set deepseek sk-xxx 设置 DeepSeek Key\n wx-ai set claude sk-xxx 设置 Claude Key\n\n \\x1b[1m设置默认模型:\\x1b[0m\n wx-ai use qwen 默认使用 Qwen\n wx-ai use deepseek 默认使用 DeepSeek\n\n \\x1b[1m微信指令:\\x1b[0m\n /model 查看当前模型\n /model qwen 切换到 Qwen\n /model deepseek 切换到 DeepSeek\n /help 显示帮助\n`;\n\nasync function main() {\n const args = process.argv.slice(2);\n const command = args[0];\n\n const logLevel = (process.env.WAI_LOG_LEVEL || \"info\") as \"debug\" | \"info\" | \"warn\" | \"error\";\n setLogLevel(logLevel);\n\n if (command === \"help\" || command === \"--help\" || command === \"-h\") {\n console.log(HELP);\n process.exit(0);\n }\n\n const config = await loadConfig();\n\n switch (command) {\n case \"set\": {\n const provider = args[1];\n const apiKey = args[2];\n\n if (!provider || !apiKey) {\n console.log(\"用法: wx-ai set <provider> <key>\");\n console.log(\"示例: wx-ai set qwen sk-xxx\");\n process.exit(1);\n }\n\n if (!config.providers[provider]) {\n console.log(`未知模型: ${provider}`);\n console.log(`可用: ${Object.keys(config.providers).join(\", \")}`);\n process.exit(1);\n }\n\n config.providers[provider]!.apiKey = apiKey;\n await saveConfig(config);\n console.log(`\\x1b[32m✓\\x1b[0m 已保存 ${provider} 的 API Key`);\n break;\n }\n\n case \"use\": {\n const provider = args[1];\n\n if (!provider) {\n console.log(`当前默认模型: ${config.defaultProvider}`);\n console.log(`可用: ${Object.keys(config.providers).join(\", \")}`);\n break;\n }\n\n if (!config.providers[provider]) {\n console.log(`未知模型: ${provider}`);\n console.log(`可用: ${Object.keys(config.providers).join(\", \")}`);\n process.exit(1);\n }\n\n config.defaultProvider = provider;\n await saveConfig(config);\n console.log(`\\x1b[32m✓\\x1b[0m 默认模型已切换到 ${provider}`);\n break;\n }\n\n case \"config\": {\n // Hide API keys in output\n const display = JSON.parse(JSON.stringify(config));\n for (const p of Object.values(display.providers)) {\n const prov = p as Record<string, unknown>;\n if (prov.apiKey && typeof prov.apiKey === \"string\") {\n prov.apiKey = prov.apiKey.slice(0, 6) + \"...\" + prov.apiKey.slice(-4);\n }\n }\n console.log(JSON.stringify(display, null, 2));\n break;\n }\n\n default: {\n const gateway = new Gateway(config);\n gateway.init();\n\n const shutdown = async () => {\n await gateway.stop();\n process.exit(0);\n };\n process.on(\"SIGINT\", shutdown);\n process.on(\"SIGTERM\", shutdown);\n\n log.info(\"启动 wx-ai...\");\n await gateway.start();\n break;\n }\n }\n}\n\nmain().catch((err) => {\n log.error(err instanceof Error ? err.message : String(err));\n process.exit(1);\n});\n"],"mappings":"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","names":[]}
1
+ {"version":3,"sources":["../src/cli.ts"],"sourcesContent":["#!/usr/bin/env node\n\nimport { readFileSync } from \"node:fs\";\nimport { fileURLToPath } from \"node:url\";\nimport { dirname, join } from \"node:path\";\nimport { loadConfig, saveConfig } from \"./config.js\";\nimport { Gateway } from \"./gateway.js\";\nimport { setLogLevel, createLogger } from \"./logger.js\";\n\nconst __dirname = dirname(fileURLToPath(import.meta.url));\nconst pkg = JSON.parse(readFileSync(join(__dirname, \"..\", \"package.json\"), \"utf-8\"));\nconst VERSION = pkg.version as string;\n\nconst log = createLogger(\"cli\");\n\nconst HELP = `\n \\x1b[1mwechat-ai\\x1b[0m — WeChat AI Bot\n\n \\x1b[1m命令:\\x1b[0m\n wechat-ai 启动 (首次自动扫码登录)\n wechat-ai set <provider> <key> 设置模型 API Key\n wechat-ai use <provider> 设置默认模型\n wechat-ai config 查看当前配置\n wechat-ai update 更新到最新版\n wechat-ai help 显示帮助\n\n \\x1b[1m设置 API Key:\\x1b[0m\n wechat-ai set qwen sk-xxx 设置通义千问 Key\n wechat-ai set deepseek sk-xxx 设置 DeepSeek Key\n wechat-ai set claude sk-xxx 设置 Claude Key\n\n \\x1b[1m设置默认模型:\\x1b[0m\n wechat-ai use qwen 默认使用 Qwen\n wechat-ai use deepseek 默认使用 DeepSeek\n\n \\x1b[1m微信指令:\\x1b[0m\n /model 查看当前模型\n /model qwen 切换到 Qwen\n /model deepseek 切换到 DeepSeek\n /help 显示帮助\n`;\n\nasync function main() {\n const args = process.argv.slice(2);\n const command = args[0];\n\n const logLevel = (process.env.WAI_LOG_LEVEL || \"info\") as \"debug\" | \"info\" | \"warn\" | \"error\";\n setLogLevel(logLevel);\n\n if (command === \"--version\" || command === \"-v\") {\n console.log(`wechat-ai v${VERSION}`);\n process.exit(0);\n }\n\n if (command === \"help\" || command === \"--help\" || command === \"-h\") {\n console.log(HELP);\n process.exit(0);\n }\n\n const config = await loadConfig();\n\n switch (command) {\n case \"set\": {\n const provider = args[1];\n const apiKey = args[2];\n\n if (!provider || !apiKey) {\n console.log(\"用法: wechat-ai set <provider> <key>\");\n console.log(\"示例: wechat-ai set qwen sk-xxx\");\n process.exit(1);\n }\n\n if (!config.providers[provider]) {\n console.log(`未知模型: ${provider}`);\n console.log(`可用: ${Object.keys(config.providers).join(\", \")}`);\n process.exit(1);\n }\n\n config.providers[provider]!.apiKey = apiKey;\n await saveConfig(config);\n console.log(`\\x1b[32m✓\\x1b[0m 已保存 ${provider} 的 API Key`);\n break;\n }\n\n case \"use\": {\n const provider = args[1];\n\n if (!provider) {\n console.log(`当前默认模型: ${config.defaultProvider}`);\n console.log(`可用: ${Object.keys(config.providers).join(\", \")}`);\n break;\n }\n\n if (!config.providers[provider]) {\n console.log(`未知模型: ${provider}`);\n console.log(`可用: ${Object.keys(config.providers).join(\", \")}`);\n process.exit(1);\n }\n\n config.defaultProvider = provider;\n await saveConfig(config);\n console.log(`\\x1b[32m✓\\x1b[0m 默认模型已切换到 ${provider}`);\n break;\n }\n\n case \"update\": {\n const { execSync } = await import(\"node:child_process\");\n console.log(\"正在更新 wechat-ai...\");\n try {\n execSync(\"npm i -g wechat-ai@latest\", { stdio: \"inherit\" });\n console.log(\"\\x1b[32m✓\\x1b[0m 更新完成\");\n } catch {\n console.error(\"\\x1b[31m✗\\x1b[0m 更新失败,请手动执行: npm i -g wechat-ai@latest\");\n process.exit(1);\n }\n break;\n }\n\n case \"config\": {\n // Hide API keys in output\n const display = JSON.parse(JSON.stringify(config));\n for (const p of Object.values(display.providers)) {\n const prov = p as Record<string, unknown>;\n if (prov.apiKey && typeof prov.apiKey === \"string\") {\n prov.apiKey = prov.apiKey.slice(0, 6) + \"...\" + prov.apiKey.slice(-4);\n }\n }\n console.log(JSON.stringify(display, null, 2));\n break;\n }\n\n default: {\n const gateway = new Gateway(config);\n gateway.init();\n\n const shutdown = async () => {\n await gateway.stop();\n process.exit(0);\n };\n process.on(\"SIGINT\", shutdown);\n process.on(\"SIGTERM\", shutdown);\n\n log.info(`wechat-ai v${VERSION}`);\n await gateway.start();\n break;\n }\n }\n}\n\nmain().catch((err) => {\n log.error(err instanceof Error ? err.message : String(err));\n process.exit(1);\n});\n"],"mappings":"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","names":[]}
package/dist/index.d.ts CHANGED
@@ -106,13 +106,18 @@ declare class Gateway {
106
106
  private channels;
107
107
  private providers;
108
108
  private config;
109
+ private buffers;
109
110
  private processing;
111
+ private queues;
110
112
  constructor(config: WaiConfig);
111
113
  init(): void;
112
114
  login(channelName: string): Promise<void>;
113
115
  start(): Promise<void>;
114
116
  stop(): Promise<void>;
115
117
  private handleMessage;
118
+ private flushBuffer;
119
+ private mergeMessages;
120
+ private processMessage;
116
121
  private handleCommand;
117
122
  }
118
123
 
package/dist/index.js CHANGED
@@ -5,7 +5,7 @@ import {
5
5
  WeixinChannel,
6
6
  loadConfig,
7
7
  saveConfig
8
- } from "./chunk-PRU3A74K.js";
8
+ } from "./chunk-CP3Y2VRX.js";
9
9
  export {
10
10
  ClaudeAgentProvider,
11
11
  Gateway,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "wechat-ai",
3
- "version": "0.1.0",
3
+ "version": "0.1.2",
4
4
  "description": "WeChat AI Bot — Bridge WeChat to Claude, Qwen, DeepSeek and more",
5
5
  "type": "module",
6
6
  "bin": {
@@ -1 +0,0 @@
1
- {"version":3,"sources":["../src/config.ts","../src/logger.ts","../src/channels/weixin.ts","../src/providers/claude-agent.ts","../src/providers/openai-compatible.ts","../src/gateway.ts"],"sourcesContent":["import { readFile, writeFile, mkdir } from \"node:fs/promises\";\nimport { existsSync } from \"node:fs\";\nimport { homedir } from \"node:os\";\nimport { join } from \"node:path\";\nimport type { WaiConfig } from \"./types.js\";\n\nconst WAI_DIR = join(homedir(), \".wai\");\nconst CONFIG_PATH = join(WAI_DIR, \"config.json\");\n\nconst DEFAULT_CONFIG: WaiConfig = {\n defaultProvider: \"qwen\",\n providers: {\n claude: {\n type: \"claude-agent\",\n allowedTools: [\"Read\", \"Glob\", \"Grep\", \"Bash\", \"WebSearch\", \"WebFetch\"],\n },\n qwen: {\n type: \"openai-compatible\",\n baseUrl: \"https://dashscope.aliyuncs.com/compatible-mode/v1\",\n model: \"qwen-plus\",\n apiKeyEnv: \"DASHSCOPE_API_KEY\",\n },\n deepseek: {\n type: \"openai-compatible\",\n baseUrl: \"https://api.deepseek.com/v1\",\n model: \"deepseek-chat\",\n apiKeyEnv: \"DEEPSEEK_API_KEY\",\n },\n gpt: {\n type: \"openai-compatible\",\n baseUrl: \"https://api.openai.com/v1\",\n model: \"gpt-4o\",\n apiKeyEnv: \"OPENAI_API_KEY\",\n },\n gemini: {\n type: \"openai-compatible\",\n baseUrl: \"https://generativelanguage.googleapis.com/v1beta/openai\",\n model: \"gemini-2.0-flash\",\n apiKeyEnv: \"GEMINI_API_KEY\",\n },\n minimax: {\n type: \"openai-compatible\",\n baseUrl: \"https://api.minimax.chat/v1\",\n model: \"MiniMax-Text-01\",\n apiKeyEnv: \"MINIMAX_API_KEY\",\n },\n zhipu: {\n type: \"openai-compatible\",\n baseUrl: \"https://open.bigmodel.cn/api/paas/v4\",\n model: \"glm-4-plus\",\n apiKeyEnv: \"ZHIPU_API_KEY\",\n },\n },\n channels: {\n weixin: {\n type: \"weixin\",\n enabled: true,\n },\n },\n systemPrompt: \"You are a helpful AI assistant. Respond concisely.\",\n chunkSize: 4000,\n};\n\nexport async function ensureDir(dir: string) {\n if (!existsSync(dir)) {\n await mkdir(dir, { recursive: true });\n }\n}\n\nexport async function loadConfig(): Promise<WaiConfig> {\n await ensureDir(WAI_DIR);\n\n if (!existsSync(CONFIG_PATH)) {\n await writeFile(CONFIG_PATH, JSON.stringify(DEFAULT_CONFIG, null, 2));\n return { ...DEFAULT_CONFIG };\n }\n\n const raw = await readFile(CONFIG_PATH, \"utf-8\");\n return { ...DEFAULT_CONFIG, ...JSON.parse(raw) } as WaiConfig;\n}\n\nexport async function saveConfig(config: WaiConfig): Promise<void> {\n await ensureDir(WAI_DIR);\n await writeFile(CONFIG_PATH, JSON.stringify(config, null, 2));\n}\n\nexport function getDataDir(): string {\n return WAI_DIR;\n}\n\nexport function getAccountsDir(): string {\n return join(WAI_DIR, \"accounts\");\n}\n","const LEVELS = { debug: 0, info: 1, warn: 2, error: 3 } as const;\ntype Level = keyof typeof LEVELS;\n\nlet currentLevel: Level = \"info\";\n\nexport function setLogLevel(level: Level) {\n currentLevel = level;\n}\n\nfunction fmt(level: Level, scope: string, msg: string): string {\n const ts = new Date().toISOString().slice(11, 23);\n const tag = level.toUpperCase().padEnd(5);\n return `\\x1b[90m${ts}\\x1b[0m ${colorize(level, tag)} \\x1b[36m[${scope}]\\x1b[0m ${msg}`;\n}\n\nfunction colorize(level: Level, text: string): string {\n switch (level) {\n case \"debug\": return `\\x1b[90m${text}\\x1b[0m`;\n case \"info\": return `\\x1b[32m${text}\\x1b[0m`;\n case \"warn\": return `\\x1b[33m${text}\\x1b[0m`;\n case \"error\": return `\\x1b[31m${text}\\x1b[0m`;\n }\n}\n\nexport function createLogger(scope: string) {\n return {\n debug: (msg: string) => { if (LEVELS[currentLevel] <= 0) console.log(fmt(\"debug\", scope, msg)); },\n info: (msg: string) => { if (LEVELS[currentLevel] <= 1) console.log(fmt(\"info\", scope, msg)); },\n warn: (msg: string) => { if (LEVELS[currentLevel] <= 2) console.warn(fmt(\"warn\", scope, msg)); },\n error: (msg: string) => { if (LEVELS[currentLevel] <= 3) console.error(fmt(\"error\", scope, msg)); },\n };\n}\n","import { createLogger } from \"../logger.js\";\nimport { getAccountsDir, ensureDir } from \"../config.js\";\nimport { join } from \"node:path\";\nimport { readFile, writeFile } from \"node:fs/promises\";\nimport { existsSync } from \"node:fs\";\nimport { randomBytes, randomUUID } from \"node:crypto\";\nimport type { Channel, InboundMessage, OutboundMessage, ChannelConfig } from \"../types.js\";\n\nconst log = createLogger(\"weixin\");\n\nconst DEFAULT_BASE_URL = \"https://ilinkai.weixin.qq.com\";\nconst CHANNEL_VERSION = \"1.0.0\";\nconst API_TIMEOUT_MS = 15_000;\n\n// ── Message constants (from openclaw-weixin protocol) ──\nconst MessageType = { USER: 1, BOT: 2 } as const;\nconst MessageState = { NEW: 0, GENERATING: 1, FINISH: 2 } as const;\nconst MessageItemType = { TEXT: 1, IMAGE: 2, VOICE: 3, FILE: 4, VIDEO: 5 } as const;\n\n// ── Types ──\n\ninterface WeixinAccount {\n accountId: string;\n token: string;\n baseUrl: string;\n userId?: string;\n}\n\ninterface WeixinMessageItem {\n type: number;\n text_item?: { text: string };\n image_item?: unknown;\n voice_item?: unknown;\n file_item?: unknown;\n video_item?: unknown;\n}\n\ninterface WeixinMessage {\n seq?: number;\n message_id?: number;\n from_user_id?: string;\n to_user_id?: string;\n context_token?: string;\n item_list?: WeixinMessageItem[];\n create_time_ms?: number;\n}\n\ninterface GetUpdatesResponse {\n ret?: number;\n errmsg?: string;\n msgs?: WeixinMessage[];\n get_updates_buf?: string;\n}\n\n// ── Weixin Channel ──\n\nexport class WeixinChannel implements Channel {\n readonly name = \"weixin\";\n\n private account: WeixinAccount | null = null;\n private syncBuf = \"\";\n private running = false;\n private abortController: AbortController | null = null;\n private config: ChannelConfig;\n // Cache typing_ticket per user\n private typingTickets = new Map<string, string>();\n\n constructor(config: ChannelConfig) {\n this.config = config;\n }\n\n // ── Auth ──\n\n async login(): Promise<void> {\n const baseUrl = (this.config.baseUrl as string) || DEFAULT_BASE_URL;\n log.info(\"获取二维码中...\");\n\n const qrRes = await this.api(baseUrl, \"ilink/bot/get_bot_qrcode?bot_type=3\", null, {\n method: \"GET\",\n timeout: 10_000,\n });\n\n if (qrRes.ret !== 0) {\n throw new Error(`获取二维码失败: ${qrRes.errmsg || qrRes.ret}`);\n }\n\n const qrUrl: string = qrRes.qrcode_img_content || qrRes.data?.qrcode_img_content;\n const qrCode: string = qrRes.qrcode || qrRes.data?.qrcode;\n\n if (!qrUrl || !qrCode) {\n throw new Error(`二维码响应缺少字段: ${JSON.stringify(qrRes)}`);\n }\n\n log.info(\"请用微信扫描二维码:\");\n console.log();\n try {\n const qrTerminal = await import(\"qrcode-terminal\");\n (qrTerminal.default || qrTerminal).generate(qrUrl, { small: true });\n } catch {\n console.log(` ${qrUrl}`);\n }\n console.log();\n\n log.info(\"等待扫码...\");\n\n let attempts = 0;\n while (attempts < 60) {\n const statusRes = await this.api(\n baseUrl,\n `ilink/bot/get_qrcode_status?qrcode=${encodeURIComponent(qrCode)}`,\n null,\n { method: \"GET\", timeout: 40_000 },\n );\n\n const status = statusRes.data?.status || statusRes.status;\n\n if (status === \"confirmed\") {\n const data = statusRes.data || statusRes;\n const accountId: string = data.ilink_bot_id || data.bot_id;\n const token: string = data.bot_token || data.token;\n\n if (!accountId || !token) {\n throw new Error(\"登录成功但缺少凭证\");\n }\n\n this.account = {\n accountId,\n token,\n baseUrl: data.baseurl || baseUrl,\n userId: data.ilink_user_id,\n };\n\n await this.saveAccount();\n log.info(`登录成功!账号: ${accountId.slice(0, 8)}...`);\n return;\n }\n\n if (status === \"scaned\") {\n log.info(\"已扫码,等待确认...\");\n }\n\n if (status === \"expired\") {\n log.warn(\"二维码已过期\");\n throw new Error(\"二维码已过期\");\n }\n\n attempts++;\n await sleep(500);\n }\n\n throw new Error(\"登录超时\");\n }\n\n // ── Message loop ──\n\n async start(onMessage: (msg: InboundMessage) => void): Promise<void> {\n if (!this.account) {\n await this.loadAccount();\n }\n if (!this.account) {\n log.info(\"未找到账号,开始登录...\");\n await this.login();\n }\n\n await this.loadSyncBuf();\n this.running = true;\n log.info(`消息监听已启动 (${this.account!.accountId.slice(0, 8)}...)`);\n\n while (this.running) {\n try {\n this.abortController = new AbortController();\n const res = await this.getUpdates();\n\n if (res.ret === -14) {\n log.warn(\"会话过期,重新登录...\");\n this.account = null;\n await this.login();\n continue;\n }\n\n if (res.ret && res.ret !== 0) {\n log.warn(`拉取消息失败: ${res.errmsg || JSON.stringify(res)}`);\n await sleep(5000);\n continue;\n }\n\n if (res.get_updates_buf) {\n this.syncBuf = res.get_updates_buf;\n await this.saveSyncBuf();\n }\n\n if (res.msgs && res.msgs.length > 0) {\n for (const msg of res.msgs) {\n const text = this.extractText(msg);\n if (!text || !msg.from_user_id) continue;\n\n log.info(`收到消息 [${msg.from_user_id.slice(0, 8)}...]: ${text.slice(0, 50)}`);\n onMessage({\n id: String(msg.message_id || msg.seq || Date.now()),\n channel: \"weixin\",\n senderId: msg.from_user_id,\n text,\n replyToken: msg.context_token,\n timestamp: msg.create_time_ms || Date.now(),\n });\n }\n }\n } catch (err) {\n if (!this.running) break;\n const message = err instanceof Error ? err.message : String(err);\n if (message.includes(\"aborted\") || message.includes(\"AbortError\")) continue;\n log.error(`轮询出错: ${message}`);\n await sleep(3000);\n }\n }\n }\n\n // ── Send typing indicator ──\n\n async sendTyping(userId: string, contextToken?: string): Promise<void> {\n if (!this.account) return;\n\n try {\n // Get typing_ticket if not cached\n let ticket = this.typingTickets.get(userId);\n if (!ticket) {\n const configRes = await this.api(this.account.baseUrl, \"ilink/bot/getconfig\", {\n ilink_user_id: userId,\n context_token: contextToken,\n base_info: { channel_version: CHANNEL_VERSION },\n }, { timeout: 10_000 });\n\n ticket = configRes.typing_ticket;\n if (ticket) {\n this.typingTickets.set(userId, ticket);\n }\n }\n\n if (!ticket) return;\n\n await this.api(this.account.baseUrl, \"ilink/bot/sendtyping\", {\n ilink_user_id: userId,\n typing_ticket: ticket,\n status: 1,\n base_info: { channel_version: CHANNEL_VERSION },\n }, { timeout: 10_000 });\n\n log.debug(`已发送输入状态给 ${userId.slice(0, 8)}...`);\n } catch {\n // typing 失败不影响主流程\n }\n }\n\n // ── Send message ──\n\n async send(msg: OutboundMessage): Promise<void> {\n if (!this.account) throw new Error(\"未登录\");\n\n const chunks = this.chunkText(msg.text, 4000);\n\n for (const chunk of chunks) {\n const body = {\n msg: {\n from_user_id: \"\",\n to_user_id: msg.targetId,\n client_id: generateClientId(),\n message_type: MessageType.BOT,\n message_state: MessageState.FINISH,\n context_token: msg.replyToken || undefined,\n item_list: [{ type: MessageItemType.TEXT, text_item: { text: chunk } }],\n },\n base_info: { channel_version: CHANNEL_VERSION },\n };\n\n const res = await this.api(\n this.account.baseUrl,\n \"ilink/bot/sendmessage\",\n body,\n { timeout: API_TIMEOUT_MS },\n );\n\n // sendMessage 成功返回 {} (空对象),有错误时返回 ret + errmsg\n if (res.ret && res.ret !== 0) {\n log.error(`发送失败: ${res.errmsg || JSON.stringify(res)}`);\n }\n }\n }\n\n async stop(): Promise<void> {\n this.running = false;\n this.abortController?.abort();\n log.info(\"已停止\");\n }\n\n // ── Internal ──\n\n private async getUpdates(): Promise<GetUpdatesResponse> {\n if (!this.account) throw new Error(\"未登录\");\n\n return this.api(this.account.baseUrl, \"ilink/bot/getupdates\", {\n get_updates_buf: this.syncBuf,\n base_info: { channel_version: CHANNEL_VERSION },\n }, { timeout: 50_000 });\n }\n\n private extractText(msg: WeixinMessage): string | null {\n if (!msg.item_list?.length) return null;\n for (const item of msg.item_list) {\n if (item.type === 1 && item.text_item?.text) {\n return item.text_item.text;\n }\n }\n return null;\n }\n\n private chunkText(text: string, maxLen: number): string[] {\n if (text.length <= maxLen) return [text];\n const chunks: string[] = [];\n let remaining = text;\n while (remaining.length > 0) {\n let breakAt = remaining.lastIndexOf(\"\\n\", maxLen);\n if (breakAt <= 0) breakAt = maxLen;\n chunks.push(remaining.slice(0, breakAt));\n remaining = remaining.slice(breakAt);\n }\n return chunks;\n }\n\n private async api(\n baseUrl: string,\n path: string,\n body: unknown,\n opts: { method?: string; timeout?: number } = {},\n ): Promise<any> {\n const url = `${baseUrl.replace(/\\/$/, \"\")}/${path}`;\n const method = opts.method || \"POST\";\n const bodyStr = body ? JSON.stringify(body) : undefined;\n\n const headers: Record<string, string> = {\n \"Content-Type\": \"application/json\",\n };\n\n if (this.account?.token) {\n headers[\"AuthorizationType\"] = \"ilink_bot_token\";\n headers[\"Authorization\"] = `Bearer ${this.account.token}`;\n headers[\"X-WECHAT-UIN\"] = randomUin();\n if (bodyStr) {\n headers[\"Content-Length\"] = String(Buffer.byteLength(bodyStr, \"utf-8\"));\n }\n }\n\n const controller = new AbortController();\n const timer = setTimeout(() => controller.abort(), opts.timeout || API_TIMEOUT_MS);\n\n try {\n const res = await fetch(url, {\n method,\n headers,\n body: bodyStr,\n signal: controller.signal,\n });\n return await res.json();\n } finally {\n clearTimeout(timer);\n }\n }\n\n // ── Persistence ──\n\n private accountFile(): string {\n return join(getAccountsDir(), \"weixin.json\");\n }\n\n private syncFile(): string {\n return join(getAccountsDir(), \"weixin-sync.json\");\n }\n\n private async saveAccount(): Promise<void> {\n await ensureDir(getAccountsDir());\n await writeFile(this.accountFile(), JSON.stringify(this.account, null, 2));\n }\n\n private async loadAccount(): Promise<void> {\n const path = this.accountFile();\n if (!existsSync(path)) return;\n try {\n const raw = await readFile(path, \"utf-8\");\n this.account = JSON.parse(raw);\n log.info(`已加载账号: ${this.account!.accountId.slice(0, 8)}...`);\n } catch {\n log.warn(\"加载账号失败\");\n }\n }\n\n private async saveSyncBuf(): Promise<void> {\n await ensureDir(getAccountsDir());\n await writeFile(this.syncFile(), JSON.stringify({ get_updates_buf: this.syncBuf }));\n }\n\n private async loadSyncBuf(): Promise<void> {\n const path = this.syncFile();\n if (!existsSync(path)) return;\n try {\n const raw = await readFile(path, \"utf-8\");\n const data = JSON.parse(raw);\n this.syncBuf = data.get_updates_buf || \"\";\n } catch {\n // fresh start\n }\n }\n}\n\n// ── Helpers ──\n\nfunction randomUin(): string {\n const uint32 = randomBytes(4).readUInt32BE(0);\n return Buffer.from(String(uint32), \"utf-8\").toString(\"base64\");\n}\n\nfunction generateClientId(): string {\n return `wai-${randomUUID().replace(/-/g, \"\").slice(0, 16)}`;\n}\n\nfunction sleep(ms: number): Promise<void> {\n return new Promise((r) => setTimeout(r, ms));\n}\n","import { createLogger } from \"../logger.js\";\nimport type { Provider, ProviderOptions, ProviderConfig } from \"../types.js\";\n\nconst log = createLogger(\"claude\");\n\nconst DEFAULT_TOOLS = [\"Read\", \"Glob\", \"Grep\", \"Bash\", \"WebSearch\", \"WebFetch\"];\n\nexport class ClaudeAgentProvider implements Provider {\n readonly name = \"claude-agent\";\n private config: ProviderConfig;\n private sessions = new Map<string, string>(); // userId -> sessionId\n\n constructor(config: ProviderConfig) {\n this.config = config;\n }\n\n async query(\n prompt: string,\n sessionId: string,\n options?: ProviderOptions,\n ): Promise<string> {\n const { query } = await import(\"@anthropic-ai/claude-agent-sdk\");\n\n const allowedTools = options?.allowedTools\n || (this.config.allowedTools as string[])\n || DEFAULT_TOOLS;\n\n const existingSession = this.sessions.get(sessionId);\n const sdkOptions: Record<string, unknown> = {\n allowedTools,\n permissionMode: \"acceptEdits\" as const,\n };\n\n if (options?.maxTokens) {\n sdkOptions.maxTokens = options.maxTokens;\n }\n\n if (options?.cwd) {\n sdkOptions.cwd = options.cwd;\n }\n\n // Resume existing session for conversation continuity\n if (existingSession) {\n sdkOptions.resume = existingSession;\n }\n\n if (options?.systemPrompt) {\n sdkOptions.systemPrompt = options.systemPrompt;\n }\n\n log.info(`Querying Claude (session: ${sessionId.slice(0, 8)}...)`);\n\n let result = \"\";\n let newSessionId: string | undefined;\n\n try {\n for await (const message of query({\n prompt,\n options: sdkOptions as any,\n })) {\n // Capture session ID from init message\n if (isInitMessage(message)) {\n newSessionId = message.session_id;\n }\n\n // Capture result text\n if (isResultMessage(message)) {\n result = message.result;\n }\n\n // Capture assistant text messages for streaming\n if (isAssistantMessage(message)) {\n // accumulate text from assistant messages\n const textContent = extractText(message);\n if (textContent) {\n result = textContent;\n }\n }\n }\n } catch (err) {\n const errMsg = err instanceof Error ? err.message : String(err);\n log.error(`Claude query failed: ${errMsg}`);\n throw err;\n }\n\n // Store session for continuity\n if (newSessionId) {\n this.sessions.set(sessionId, newSessionId);\n }\n\n if (!result) {\n result = \"(No response from Claude)\";\n }\n\n log.info(`Response: ${result.length} chars`);\n return result;\n }\n}\n\n// ── Message type guards ──\n\nfunction isInitMessage(msg: any): msg is { type: \"system\"; subtype: \"init\"; session_id: string } {\n return msg?.type === \"system\" && msg?.subtype === \"init\" && typeof msg?.session_id === \"string\";\n}\n\nfunction isResultMessage(msg: any): msg is { result: string } {\n return typeof msg?.result === \"string\";\n}\n\nfunction isAssistantMessage(msg: any): msg is { type: \"assistant\"; message: { content: unknown[] } } {\n return msg?.type === \"assistant\" && msg?.message?.content;\n}\n\nfunction extractText(msg: any): string | null {\n if (!msg?.message?.content) return null;\n const parts: string[] = [];\n for (const block of msg.message.content) {\n if (block.type === \"text\" && typeof block.text === \"string\") {\n parts.push(block.text);\n }\n }\n return parts.length > 0 ? parts.join(\"\") : null;\n}\n","import { createLogger } from \"../logger.js\";\nimport type { Provider, ProviderOptions, ProviderConfig } from \"../types.js\";\n\nconst log = createLogger(\"openai-compat\");\n\ninterface ChatMessage {\n role: \"system\" | \"user\" | \"assistant\";\n content: string;\n}\n\ninterface ChatCompletionResponse {\n choices: Array<{\n message: { content: string };\n finish_reason: string;\n }>;\n usage?: { prompt_tokens: number; completion_tokens: number };\n}\n\nexport class OpenAICompatibleProvider implements Provider {\n readonly name: string;\n private config: ProviderConfig;\n private histories = new Map<string, ChatMessage[]>();\n\n constructor(name: string, config: ProviderConfig) {\n this.name = name;\n this.config = config;\n }\n\n async query(\n prompt: string,\n sessionId: string,\n options?: ProviderOptions,\n ): Promise<string> {\n const baseUrl = this.config.baseUrl;\n const apiKey = this.config.apiKey || process.env[this.config.apiKeyEnv as string || \"\"];\n const model = options?.model || (this.config.model as string);\n\n if (!baseUrl) throw new Error(`${this.name}: baseUrl is required`);\n if (!apiKey) throw new Error(`${this.name}: apiKey is required`);\n if (!model) throw new Error(`${this.name}: model is required`);\n\n // Build conversation history\n let history = this.histories.get(sessionId);\n if (!history) {\n history = [];\n this.histories.set(sessionId, history);\n }\n\n const messages: ChatMessage[] = [];\n\n // System prompt\n const systemPrompt = options?.systemPrompt || (this.config.systemPrompt as string);\n if (systemPrompt) {\n messages.push({ role: \"system\", content: systemPrompt });\n }\n\n // Conversation history (keep last N turns to stay within context)\n const maxHistory = (this.config.maxHistory as number) || 20;\n const recentHistory = history.slice(-maxHistory);\n messages.push(...recentHistory);\n\n // Current user message\n messages.push({ role: \"user\", content: prompt });\n\n log.info(`Querying ${this.name} (model: ${model}, session: ${sessionId.slice(0, 8)}...)`);\n\n const url = `${baseUrl.replace(/\\/$/, \"\")}/chat/completions`;\n\n const res = await fetch(url, {\n method: \"POST\",\n headers: {\n \"Content-Type\": \"application/json\",\n \"Authorization\": `Bearer ${apiKey}`,\n },\n body: JSON.stringify({\n model,\n messages,\n max_tokens: options?.maxTokens || (this.config.maxTokens as number) || 4096,\n temperature: (this.config.temperature as number) ?? 0.7,\n }),\n });\n\n if (!res.ok) {\n const errBody = await res.text();\n log.error(`${this.name} API error ${res.status}: ${errBody.slice(0, 200)}`);\n throw new Error(`${this.name} API error: ${res.status}`);\n }\n\n const data = (await res.json()) as ChatCompletionResponse;\n const reply = data.choices[0]?.message.content || \"(No response)\";\n\n if (data.usage) {\n log.info(`Tokens: ${data.usage.prompt_tokens} in / ${data.usage.completion_tokens} out`);\n }\n\n // Update history\n history.push({ role: \"user\", content: prompt });\n history.push({ role: \"assistant\", content: reply });\n\n log.info(`Response: ${reply.length} chars`);\n return reply;\n }\n}\n","import { createLogger } from \"./logger.js\";\nimport type {\n Channel,\n Provider,\n InboundMessage,\n WaiConfig,\n ProviderOptions,\n} from \"./types.js\";\nimport { WeixinChannel } from \"./channels/weixin.js\";\nimport { ClaudeAgentProvider } from \"./providers/claude-agent.js\";\nimport { OpenAICompatibleProvider } from \"./providers/openai-compatible.js\";\n\nconst log = createLogger(\"网关\");\n\nexport class Gateway {\n private channels = new Map<string, Channel>();\n private providers = new Map<string, Provider>();\n private config: WaiConfig;\n private processing = new Set<string>();\n\n constructor(config: WaiConfig) {\n this.config = config;\n }\n\n init(): void {\n for (const [name, chConfig] of Object.entries(this.config.channels)) {\n if (chConfig.enabled === false) continue;\n switch (chConfig.type) {\n case \"weixin\":\n this.channels.set(name, new WeixinChannel(chConfig));\n break;\n default:\n log.warn(`未知渠道类型: ${chConfig.type}`);\n }\n }\n\n for (const [name, provConfig] of Object.entries(this.config.providers)) {\n switch (provConfig.type) {\n case \"claude-agent\":\n this.providers.set(name, new ClaudeAgentProvider(provConfig));\n break;\n case \"openai-compatible\":\n this.providers.set(name, new OpenAICompatibleProvider(name, provConfig));\n break;\n default:\n log.warn(`未知模型类型: ${provConfig.type}`);\n }\n }\n\n log.info(`已初始化 ${this.channels.size} 个渠道, ${this.providers.size} 个模型`);\n }\n\n async login(channelName: string): Promise<void> {\n const channel = this.channels.get(channelName);\n if (!channel) {\n throw new Error(`渠道 \"${channelName}\" 不存在`);\n }\n await channel.login();\n }\n\n async start(): Promise<void> {\n if (this.providers.size === 0) {\n throw new Error(\"未配置任何模型\");\n }\n\n const startPromises = [...this.channels.entries()].map(([name, channel]) => {\n log.info(`启动渠道: ${name}`);\n return channel.start((msg) => this.handleMessage(msg)).catch((err) => {\n log.error(`渠道 ${name} 异常: ${err instanceof Error ? err.message : err}`);\n });\n });\n\n await Promise.all(startPromises);\n }\n\n async stop(): Promise<void> {\n log.info(\"正在关闭...\");\n const stops = [...this.channels.values()].map((ch) => ch.stop());\n await Promise.allSettled(stops);\n log.info(\"已关闭\");\n }\n\n private async handleMessage(msg: InboundMessage): Promise<void> {\n const lockKey = `${msg.channel}:${msg.senderId}`;\n\n if (this.processing.has(lockKey)) {\n log.warn(`跳过消息 (上条仍在处理)`);\n return;\n }\n\n this.processing.add(lockKey);\n\n try {\n if (msg.text.startsWith(\"/\")) {\n await this.handleCommand(msg);\n return;\n }\n\n const providerName = this.config.userRoutes?.[msg.senderId]\n || this.config.defaultProvider;\n const provider = this.providers.get(providerName);\n\n if (!provider) {\n log.error(`模型 \"${providerName}\" 未找到`);\n return;\n }\n\n log.info(`调用 ${providerName} 处理中...`);\n\n // 发送\"正在输入\"状态\n const channel = this.channels.get(msg.channel);\n if (channel && \"sendTyping\" in channel) {\n (channel as any).sendTyping(msg.senderId, msg.replyToken);\n }\n\n const options: ProviderOptions = {};\n if (this.config.systemPrompt) {\n options.systemPrompt = this.config.systemPrompt;\n }\n\n const sessionKey = `${msg.channel}:${msg.senderId}`;\n const response = await provider.query(msg.text, sessionKey, options);\n\n if (!channel) return;\n\n await channel.send({\n targetId: msg.senderId,\n text: response,\n replyToken: msg.replyToken,\n });\n\n log.info(`已回复 (${response.length} 字符)`);\n } catch (err) {\n const errMsg = err instanceof Error ? err.message : String(err);\n log.error(`处理消息失败: ${errMsg}`);\n\n try {\n const channel = this.channels.get(msg.channel);\n if (channel) {\n await channel.send({\n targetId: msg.senderId,\n text: `[出错了] 处理消息失败,请重试。`,\n replyToken: msg.replyToken,\n });\n }\n } catch {\n // swallow\n }\n } finally {\n this.processing.delete(lockKey);\n }\n }\n\n private async handleCommand(msg: InboundMessage): Promise<void> {\n const channel = this.channels.get(msg.channel);\n if (!channel) return;\n\n const parts = msg.text.trim().split(/\\s+/);\n const cmd = parts[0]!.toLowerCase();\n const arg = parts[1];\n\n switch (cmd) {\n case \"/model\": {\n if (!arg) {\n const current = this.config.userRoutes?.[msg.senderId] || this.config.defaultProvider;\n const available = [...this.providers.keys()].join(\", \");\n await channel.send({\n targetId: msg.senderId,\n text: `当前模型: ${current}\\n可用模型: ${available}\\n用法: /model <名称>`,\n replyToken: msg.replyToken,\n });\n } else if (this.providers.has(arg)) {\n if (!this.config.userRoutes) this.config.userRoutes = {};\n this.config.userRoutes[msg.senderId] = arg;\n await channel.send({\n targetId: msg.senderId,\n text: `已切换到: ${arg}`,\n replyToken: msg.replyToken,\n });\n } else {\n await channel.send({\n targetId: msg.senderId,\n text: `未知模型: ${arg}\\n可用: ${[...this.providers.keys()].join(\", \")}`,\n replyToken: msg.replyToken,\n });\n }\n break;\n }\n\n case \"/help\": {\n await channel.send({\n targetId: msg.senderId,\n text: [\n \"wx-ai 指令:\",\n \"/model [名称] - 切换AI模型\",\n \"/help - 显示帮助\",\n \"/ping - 检查状态\",\n ].join(\"\\n\"),\n replyToken: msg.replyToken,\n });\n break;\n }\n\n case \"/ping\": {\n await channel.send({\n targetId: msg.senderId,\n text: `pong (${Date.now() - msg.timestamp}ms)`,\n replyToken: msg.replyToken,\n });\n break;\n }\n\n default: {\n await channel.send({\n targetId: msg.senderId,\n text: `未知指令: ${cmd},试试 /help`,\n replyToken: msg.replyToken,\n });\n }\n }\n }\n}\n"],"mappings":";AAAA,SAAS,UAAU,WAAW,aAAa;AAC3C,SAAS,kBAAkB;AAC3B,SAAS,eAAe;AACxB,SAAS,YAAY;AAGrB,IAAM,UAAU,KAAK,QAAQ,GAAG,MAAM;AACtC,IAAM,cAAc,KAAK,SAAS,aAAa;AAE/C,IAAM,iBAA4B;AAAA,EAChC,iBAAiB;AAAA,EACjB,WAAW;AAAA,IACT,QAAQ;AAAA,MACN,MAAM;AAAA,MACN,cAAc,CAAC,QAAQ,QAAQ,QAAQ,QAAQ,aAAa,UAAU;AAAA,IACxE;AAAA,IACA,MAAM;AAAA,MACJ,MAAM;AAAA,MACN,SAAS;AAAA,MACT,OAAO;AAAA,MACP,WAAW;AAAA,IACb;AAAA,IACA,UAAU;AAAA,MACR,MAAM;AAAA,MACN,SAAS;AAAA,MACT,OAAO;AAAA,MACP,WAAW;AAAA,IACb;AAAA,IACA,KAAK;AAAA,MACH,MAAM;AAAA,MACN,SAAS;AAAA,MACT,OAAO;AAAA,MACP,WAAW;AAAA,IACb;AAAA,IACA,QAAQ;AAAA,MACN,MAAM;AAAA,MACN,SAAS;AAAA,MACT,OAAO;AAAA,MACP,WAAW;AAAA,IACb;AAAA,IACA,SAAS;AAAA,MACP,MAAM;AAAA,MACN,SAAS;AAAA,MACT,OAAO;AAAA,MACP,WAAW;AAAA,IACb;AAAA,IACA,OAAO;AAAA,MACL,MAAM;AAAA,MACN,SAAS;AAAA,MACT,OAAO;AAAA,MACP,WAAW;AAAA,IACb;AAAA,EACF;AAAA,EACA,UAAU;AAAA,IACR,QAAQ;AAAA,MACN,MAAM;AAAA,MACN,SAAS;AAAA,IACX;AAAA,EACF;AAAA,EACA,cAAc;AAAA,EACd,WAAW;AACb;AAEA,eAAsB,UAAU,KAAa;AAC3C,MAAI,CAAC,WAAW,GAAG,GAAG;AACpB,UAAM,MAAM,KAAK,EAAE,WAAW,KAAK,CAAC;AAAA,EACtC;AACF;AAEA,eAAsB,aAAiC;AACrD,QAAM,UAAU,OAAO;AAEvB,MAAI,CAAC,WAAW,WAAW,GAAG;AAC5B,UAAM,UAAU,aAAa,KAAK,UAAU,gBAAgB,MAAM,CAAC,CAAC;AACpE,WAAO,EAAE,GAAG,eAAe;AAAA,EAC7B;AAEA,QAAM,MAAM,MAAM,SAAS,aAAa,OAAO;AAC/C,SAAO,EAAE,GAAG,gBAAgB,GAAG,KAAK,MAAM,GAAG,EAAE;AACjD;AAEA,eAAsB,WAAW,QAAkC;AACjE,QAAM,UAAU,OAAO;AACvB,QAAM,UAAU,aAAa,KAAK,UAAU,QAAQ,MAAM,CAAC,CAAC;AAC9D;AAMO,SAAS,iBAAyB;AACvC,SAAO,KAAK,SAAS,UAAU;AACjC;;;AC5FA,IAAM,SAAS,EAAE,OAAO,GAAG,MAAM,GAAG,MAAM,GAAG,OAAO,EAAE;AAGtD,IAAI,eAAsB;AAEnB,SAAS,YAAY,OAAc;AACxC,iBAAe;AACjB;AAEA,SAAS,IAAI,OAAc,OAAe,KAAqB;AAC7D,QAAM,MAAK,oBAAI,KAAK,GAAE,YAAY,EAAE,MAAM,IAAI,EAAE;AAChD,QAAM,MAAM,MAAM,YAAY,EAAE,OAAO,CAAC;AACxC,SAAO,WAAW,EAAE,WAAW,SAAS,OAAO,GAAG,CAAC,aAAa,KAAK,YAAY,GAAG;AACtF;AAEA,SAAS,SAAS,OAAc,MAAsB;AACpD,UAAQ,OAAO;AAAA,IACb,KAAK;AAAS,aAAO,WAAW,IAAI;AAAA,IACpC,KAAK;AAAS,aAAO,WAAW,IAAI;AAAA,IACpC,KAAK;AAAS,aAAO,WAAW,IAAI;AAAA,IACpC,KAAK;AAAS,aAAO,WAAW,IAAI;AAAA,EACtC;AACF;AAEO,SAAS,aAAa,OAAe;AAC1C,SAAO;AAAA,IACL,OAAO,CAAC,QAAgB;AAAE,UAAI,OAAO,YAAY,KAAK,EAAG,SAAQ,IAAI,IAAI,SAAS,OAAO,GAAG,CAAC;AAAA,IAAG;AAAA,IAChG,MAAO,CAAC,QAAgB;AAAE,UAAI,OAAO,YAAY,KAAK,EAAG,SAAQ,IAAI,IAAI,QAAS,OAAO,GAAG,CAAC;AAAA,IAAG;AAAA,IAChG,MAAO,CAAC,QAAgB;AAAE,UAAI,OAAO,YAAY,KAAK,EAAG,SAAQ,KAAK,IAAI,QAAS,OAAO,GAAG,CAAC;AAAA,IAAG;AAAA,IACjG,OAAO,CAAC,QAAgB;AAAE,UAAI,OAAO,YAAY,KAAK,EAAG,SAAQ,MAAM,IAAI,SAAS,OAAO,GAAG,CAAC;AAAA,IAAG;AAAA,EACpG;AACF;;;AC7BA,SAAS,QAAAA,aAAY;AACrB,SAAS,YAAAC,WAAU,aAAAC,kBAAiB;AACpC,SAAS,cAAAC,mBAAkB;AAC3B,SAAS,aAAa,kBAAkB;AAGxC,IAAM,MAAM,aAAa,QAAQ;AAEjC,IAAM,mBAAmB;AACzB,IAAM,kBAAkB;AACxB,IAAM,iBAAiB;AAGvB,IAAM,cAAc,EAAE,MAAM,GAAG,KAAK,EAAE;AACtC,IAAM,eAAe,EAAE,KAAK,GAAG,YAAY,GAAG,QAAQ,EAAE;AACxD,IAAM,kBAAkB,EAAE,MAAM,GAAG,OAAO,GAAG,OAAO,GAAG,MAAM,GAAG,OAAO,EAAE;AAuClE,IAAM,gBAAN,MAAuC;AAAA,EACnC,OAAO;AAAA,EAER,UAAgC;AAAA,EAChC,UAAU;AAAA,EACV,UAAU;AAAA,EACV,kBAA0C;AAAA,EAC1C;AAAA;AAAA,EAEA,gBAAgB,oBAAI,IAAoB;AAAA,EAEhD,YAAY,QAAuB;AACjC,SAAK,SAAS;AAAA,EAChB;AAAA;AAAA,EAIA,MAAM,QAAuB;AAC3B,UAAM,UAAW,KAAK,OAAO,WAAsB;AACnD,QAAI,KAAK,yCAAW;AAEpB,UAAM,QAAQ,MAAM,KAAK,IAAI,SAAS,uCAAuC,MAAM;AAAA,MACjF,QAAQ;AAAA,MACR,SAAS;AAAA,IACX,CAAC;AAED,QAAI,MAAM,QAAQ,GAAG;AACnB,YAAM,IAAI,MAAM,+CAAY,MAAM,UAAU,MAAM,GAAG,EAAE;AAAA,IACzD;AAEA,UAAM,QAAgB,MAAM,sBAAsB,MAAM,MAAM;AAC9D,UAAM,SAAiB,MAAM,UAAU,MAAM,MAAM;AAEnD,QAAI,CAAC,SAAS,CAAC,QAAQ;AACrB,YAAM,IAAI,MAAM,2DAAc,KAAK,UAAU,KAAK,CAAC,EAAE;AAAA,IACvD;AAEA,QAAI,KAAK,yDAAY;AACrB,YAAQ,IAAI;AACZ,QAAI;AACF,YAAM,aAAa,MAAM,OAAO,iBAAiB;AACjD,OAAC,WAAW,WAAW,YAAY,SAAS,OAAO,EAAE,OAAO,KAAK,CAAC;AAAA,IACpE,QAAQ;AACN,cAAQ,IAAI,KAAK,KAAK,EAAE;AAAA,IAC1B;AACA,YAAQ,IAAI;AAEZ,QAAI,KAAK,6BAAS;AAElB,QAAI,WAAW;AACf,WAAO,WAAW,IAAI;AACpB,YAAM,YAAY,MAAM,KAAK;AAAA,QAC3B;AAAA,QACA,sCAAsC,mBAAmB,MAAM,CAAC;AAAA,QAChE;AAAA,QACA,EAAE,QAAQ,OAAO,SAAS,IAAO;AAAA,MACnC;AAEA,YAAM,SAAS,UAAU,MAAM,UAAU,UAAU;AAEnD,UAAI,WAAW,aAAa;AAC1B,cAAM,OAAO,UAAU,QAAQ;AAC/B,cAAM,YAAoB,KAAK,gBAAgB,KAAK;AACpD,cAAM,QAAgB,KAAK,aAAa,KAAK;AAE7C,YAAI,CAAC,aAAa,CAAC,OAAO;AACxB,gBAAM,IAAI,MAAM,wDAAW;AAAA,QAC7B;AAEA,aAAK,UAAU;AAAA,UACb;AAAA,UACA;AAAA,UACA,SAAS,KAAK,WAAW;AAAA,UACzB,QAAQ,KAAK;AAAA,QACf;AAEA,cAAM,KAAK,YAAY;AACvB,YAAI,KAAK,+CAAY,UAAU,MAAM,GAAG,CAAC,CAAC,KAAK;AAC/C;AAAA,MACF;AAEA,UAAI,WAAW,UAAU;AACvB,YAAI,KAAK,qDAAa;AAAA,MACxB;AAEA,UAAI,WAAW,WAAW;AACxB,YAAI,KAAK,sCAAQ;AACjB,cAAM,IAAI,MAAM,sCAAQ;AAAA,MAC1B;AAEA;AACA,YAAM,MAAM,GAAG;AAAA,IACjB;AAEA,UAAM,IAAI,MAAM,0BAAM;AAAA,EACxB;AAAA;AAAA,EAIA,MAAM,MAAM,WAAyD;AACnE,QAAI,CAAC,KAAK,SAAS;AACjB,YAAM,KAAK,YAAY;AAAA,IACzB;AACA,QAAI,CAAC,KAAK,SAAS;AACjB,UAAI,KAAK,iEAAe;AACxB,YAAM,KAAK,MAAM;AAAA,IACnB;AAEA,UAAM,KAAK,YAAY;AACvB,SAAK,UAAU;AACf,QAAI,KAAK,+CAAY,KAAK,QAAS,UAAU,MAAM,GAAG,CAAC,CAAC,MAAM;AAE9D,WAAO,KAAK,SAAS;AACnB,UAAI;AACF,aAAK,kBAAkB,IAAI,gBAAgB;AAC3C,cAAM,MAAM,MAAM,KAAK,WAAW;AAElC,YAAI,IAAI,QAAQ,KAAK;AACnB,cAAI,KAAK,2DAAc;AACvB,eAAK,UAAU;AACf,gBAAM,KAAK,MAAM;AACjB;AAAA,QACF;AAEA,YAAI,IAAI,OAAO,IAAI,QAAQ,GAAG;AAC5B,cAAI,KAAK,yCAAW,IAAI,UAAU,KAAK,UAAU,GAAG,CAAC,EAAE;AACvD,gBAAM,MAAM,GAAI;AAChB;AAAA,QACF;AAEA,YAAI,IAAI,iBAAiB;AACvB,eAAK,UAAU,IAAI;AACnB,gBAAM,KAAK,YAAY;AAAA,QACzB;AAEA,YAAI,IAAI,QAAQ,IAAI,KAAK,SAAS,GAAG;AACnC,qBAAW,OAAO,IAAI,MAAM;AAC1B,kBAAM,OAAO,KAAK,YAAY,GAAG;AACjC,gBAAI,CAAC,QAAQ,CAAC,IAAI,aAAc;AAEhC,gBAAI,KAAK,6BAAS,IAAI,aAAa,MAAM,GAAG,CAAC,CAAC,SAAS,KAAK,MAAM,GAAG,EAAE,CAAC,EAAE;AAC1E,sBAAU;AAAA,cACR,IAAI,OAAO,IAAI,cAAc,IAAI,OAAO,KAAK,IAAI,CAAC;AAAA,cAClD,SAAS;AAAA,cACT,UAAU,IAAI;AAAA,cACd;AAAA,cACA,YAAY,IAAI;AAAA,cAChB,WAAW,IAAI,kBAAkB,KAAK,IAAI;AAAA,YAC5C,CAAC;AAAA,UACH;AAAA,QACF;AAAA,MACF,SAAS,KAAK;AACZ,YAAI,CAAC,KAAK,QAAS;AACnB,cAAM,UAAU,eAAe,QAAQ,IAAI,UAAU,OAAO,GAAG;AAC/D,YAAI,QAAQ,SAAS,SAAS,KAAK,QAAQ,SAAS,YAAY,EAAG;AACnE,YAAI,MAAM,6BAAS,OAAO,EAAE;AAC5B,cAAM,MAAM,GAAI;AAAA,MAClB;AAAA,IACF;AAAA,EACF;AAAA;AAAA,EAIA,MAAM,WAAW,QAAgB,cAAsC;AACrE,QAAI,CAAC,KAAK,QAAS;AAEnB,QAAI;AAEF,UAAI,SAAS,KAAK,cAAc,IAAI,MAAM;AAC1C,UAAI,CAAC,QAAQ;AACX,cAAM,YAAY,MAAM,KAAK,IAAI,KAAK,QAAQ,SAAS,uBAAuB;AAAA,UAC5E,eAAe;AAAA,UACf,eAAe;AAAA,UACf,WAAW,EAAE,iBAAiB,gBAAgB;AAAA,QAChD,GAAG,EAAE,SAAS,IAAO,CAAC;AAEtB,iBAAS,UAAU;AACnB,YAAI,QAAQ;AACV,eAAK,cAAc,IAAI,QAAQ,MAAM;AAAA,QACvC;AAAA,MACF;AAEA,UAAI,CAAC,OAAQ;AAEb,YAAM,KAAK,IAAI,KAAK,QAAQ,SAAS,wBAAwB;AAAA,QAC3D,eAAe;AAAA,QACf,eAAe;AAAA,QACf,QAAQ;AAAA,QACR,WAAW,EAAE,iBAAiB,gBAAgB;AAAA,MAChD,GAAG,EAAE,SAAS,IAAO,CAAC;AAEtB,UAAI,MAAM,oDAAY,OAAO,MAAM,GAAG,CAAC,CAAC,KAAK;AAAA,IAC/C,QAAQ;AAAA,IAER;AAAA,EACF;AAAA;AAAA,EAIA,MAAM,KAAK,KAAqC;AAC9C,QAAI,CAAC,KAAK,QAAS,OAAM,IAAI,MAAM,oBAAK;AAExC,UAAM,SAAS,KAAK,UAAU,IAAI,MAAM,GAAI;AAE5C,eAAW,SAAS,QAAQ;AAC1B,YAAM,OAAO;AAAA,QACX,KAAK;AAAA,UACH,cAAc;AAAA,UACd,YAAY,IAAI;AAAA,UAChB,WAAW,iBAAiB;AAAA,UAC5B,cAAc,YAAY;AAAA,UAC1B,eAAe,aAAa;AAAA,UAC5B,eAAe,IAAI,cAAc;AAAA,UACjC,WAAW,CAAC,EAAE,MAAM,gBAAgB,MAAM,WAAW,EAAE,MAAM,MAAM,EAAE,CAAC;AAAA,QACxE;AAAA,QACA,WAAW,EAAE,iBAAiB,gBAAgB;AAAA,MAChD;AAEA,YAAM,MAAM,MAAM,KAAK;AAAA,QACrB,KAAK,QAAQ;AAAA,QACb;AAAA,QACA;AAAA,QACA,EAAE,SAAS,eAAe;AAAA,MAC5B;AAGA,UAAI,IAAI,OAAO,IAAI,QAAQ,GAAG;AAC5B,YAAI,MAAM,6BAAS,IAAI,UAAU,KAAK,UAAU,GAAG,CAAC,EAAE;AAAA,MACxD;AAAA,IACF;AAAA,EACF;AAAA,EAEA,MAAM,OAAsB;AAC1B,SAAK,UAAU;AACf,SAAK,iBAAiB,MAAM;AAC5B,QAAI,KAAK,oBAAK;AAAA,EAChB;AAAA;AAAA,EAIA,MAAc,aAA0C;AACtD,QAAI,CAAC,KAAK,QAAS,OAAM,IAAI,MAAM,oBAAK;AAExC,WAAO,KAAK,IAAI,KAAK,QAAQ,SAAS,wBAAwB;AAAA,MAC5D,iBAAiB,KAAK;AAAA,MACtB,WAAW,EAAE,iBAAiB,gBAAgB;AAAA,IAChD,GAAG,EAAE,SAAS,IAAO,CAAC;AAAA,EACxB;AAAA,EAEQ,YAAY,KAAmC;AACrD,QAAI,CAAC,IAAI,WAAW,OAAQ,QAAO;AACnC,eAAW,QAAQ,IAAI,WAAW;AAChC,UAAI,KAAK,SAAS,KAAK,KAAK,WAAW,MAAM;AAC3C,eAAO,KAAK,UAAU;AAAA,MACxB;AAAA,IACF;AACA,WAAO;AAAA,EACT;AAAA,EAEQ,UAAU,MAAc,QAA0B;AACxD,QAAI,KAAK,UAAU,OAAQ,QAAO,CAAC,IAAI;AACvC,UAAM,SAAmB,CAAC;AAC1B,QAAI,YAAY;AAChB,WAAO,UAAU,SAAS,GAAG;AAC3B,UAAI,UAAU,UAAU,YAAY,MAAM,MAAM;AAChD,UAAI,WAAW,EAAG,WAAU;AAC5B,aAAO,KAAK,UAAU,MAAM,GAAG,OAAO,CAAC;AACvC,kBAAY,UAAU,MAAM,OAAO;AAAA,IACrC;AACA,WAAO;AAAA,EACT;AAAA,EAEA,MAAc,IACZ,SACA,MACA,MACA,OAA8C,CAAC,GACjC;AACd,UAAM,MAAM,GAAG,QAAQ,QAAQ,OAAO,EAAE,CAAC,IAAI,IAAI;AACjD,UAAM,SAAS,KAAK,UAAU;AAC9B,UAAM,UAAU,OAAO,KAAK,UAAU,IAAI,IAAI;AAE9C,UAAM,UAAkC;AAAA,MACtC,gBAAgB;AAAA,IAClB;AAEA,QAAI,KAAK,SAAS,OAAO;AACvB,cAAQ,mBAAmB,IAAI;AAC/B,cAAQ,eAAe,IAAI,UAAU,KAAK,QAAQ,KAAK;AACvD,cAAQ,cAAc,IAAI,UAAU;AACpC,UAAI,SAAS;AACX,gBAAQ,gBAAgB,IAAI,OAAO,OAAO,WAAW,SAAS,OAAO,CAAC;AAAA,MACxE;AAAA,IACF;AAEA,UAAM,aAAa,IAAI,gBAAgB;AACvC,UAAM,QAAQ,WAAW,MAAM,WAAW,MAAM,GAAG,KAAK,WAAW,cAAc;AAEjF,QAAI;AACF,YAAM,MAAM,MAAM,MAAM,KAAK;AAAA,QAC3B;AAAA,QACA;AAAA,QACA,MAAM;AAAA,QACN,QAAQ,WAAW;AAAA,MACrB,CAAC;AACD,aAAO,MAAM,IAAI,KAAK;AAAA,IACxB,UAAE;AACA,mBAAa,KAAK;AAAA,IACpB;AAAA,EACF;AAAA;AAAA,EAIQ,cAAsB;AAC5B,WAAOH,MAAK,eAAe,GAAG,aAAa;AAAA,EAC7C;AAAA,EAEQ,WAAmB;AACzB,WAAOA,MAAK,eAAe,GAAG,kBAAkB;AAAA,EAClD;AAAA,EAEA,MAAc,cAA6B;AACzC,UAAM,UAAU,eAAe,CAAC;AAChC,UAAME,WAAU,KAAK,YAAY,GAAG,KAAK,UAAU,KAAK,SAAS,MAAM,CAAC,CAAC;AAAA,EAC3E;AAAA,EAEA,MAAc,cAA6B;AACzC,UAAM,OAAO,KAAK,YAAY;AAC9B,QAAI,CAACC,YAAW,IAAI,EAAG;AACvB,QAAI;AACF,YAAM,MAAM,MAAMF,UAAS,MAAM,OAAO;AACxC,WAAK,UAAU,KAAK,MAAM,GAAG;AAC7B,UAAI,KAAK,mCAAU,KAAK,QAAS,UAAU,MAAM,GAAG,CAAC,CAAC,KAAK;AAAA,IAC7D,QAAQ;AACN,UAAI,KAAK,sCAAQ;AAAA,IACnB;AAAA,EACF;AAAA,EAEA,MAAc,cAA6B;AACzC,UAAM,UAAU,eAAe,CAAC;AAChC,UAAMC,WAAU,KAAK,SAAS,GAAG,KAAK,UAAU,EAAE,iBAAiB,KAAK,QAAQ,CAAC,CAAC;AAAA,EACpF;AAAA,EAEA,MAAc,cAA6B;AACzC,UAAM,OAAO,KAAK,SAAS;AAC3B,QAAI,CAACC,YAAW,IAAI,EAAG;AACvB,QAAI;AACF,YAAM,MAAM,MAAMF,UAAS,MAAM,OAAO;AACxC,YAAM,OAAO,KAAK,MAAM,GAAG;AAC3B,WAAK,UAAU,KAAK,mBAAmB;AAAA,IACzC,QAAQ;AAAA,IAER;AAAA,EACF;AACF;AAIA,SAAS,YAAoB;AAC3B,QAAM,SAAS,YAAY,CAAC,EAAE,aAAa,CAAC;AAC5C,SAAO,OAAO,KAAK,OAAO,MAAM,GAAG,OAAO,EAAE,SAAS,QAAQ;AAC/D;AAEA,SAAS,mBAA2B;AAClC,SAAO,OAAO,WAAW,EAAE,QAAQ,MAAM,EAAE,EAAE,MAAM,GAAG,EAAE,CAAC;AAC3D;AAEA,SAAS,MAAM,IAA2B;AACxC,SAAO,IAAI,QAAQ,CAAC,MAAM,WAAW,GAAG,EAAE,CAAC;AAC7C;;;ACtaA,IAAMG,OAAM,aAAa,QAAQ;AAEjC,IAAM,gBAAgB,CAAC,QAAQ,QAAQ,QAAQ,QAAQ,aAAa,UAAU;AAEvE,IAAM,sBAAN,MAA8C;AAAA,EAC1C,OAAO;AAAA,EACR;AAAA,EACA,WAAW,oBAAI,IAAoB;AAAA;AAAA,EAE3C,YAAY,QAAwB;AAClC,SAAK,SAAS;AAAA,EAChB;AAAA,EAEA,MAAM,MACJ,QACA,WACA,SACiB;AACjB,UAAM,EAAE,MAAM,IAAI,MAAM,OAAO,gCAAgC;AAE/D,UAAM,eAAe,SAAS,gBACxB,KAAK,OAAO,gBACb;AAEL,UAAM,kBAAkB,KAAK,SAAS,IAAI,SAAS;AACnD,UAAM,aAAsC;AAAA,MAC1C;AAAA,MACA,gBAAgB;AAAA,IAClB;AAEA,QAAI,SAAS,WAAW;AACtB,iBAAW,YAAY,QAAQ;AAAA,IACjC;AAEA,QAAI,SAAS,KAAK;AAChB,iBAAW,MAAM,QAAQ;AAAA,IAC3B;AAGA,QAAI,iBAAiB;AACnB,iBAAW,SAAS;AAAA,IACtB;AAEA,QAAI,SAAS,cAAc;AACzB,iBAAW,eAAe,QAAQ;AAAA,IACpC;AAEA,IAAAA,KAAI,KAAK,6BAA6B,UAAU,MAAM,GAAG,CAAC,CAAC,MAAM;AAEjE,QAAI,SAAS;AACb,QAAI;AAEJ,QAAI;AACF,uBAAiB,WAAW,MAAM;AAAA,QAChC;AAAA,QACA,SAAS;AAAA,MACX,CAAC,GAAG;AAEF,YAAI,cAAc,OAAO,GAAG;AAC1B,yBAAe,QAAQ;AAAA,QACzB;AAGA,YAAI,gBAAgB,OAAO,GAAG;AAC5B,mBAAS,QAAQ;AAAA,QACnB;AAGA,YAAI,mBAAmB,OAAO,GAAG;AAE/B,gBAAM,cAAc,YAAY,OAAO;AACvC,cAAI,aAAa;AACf,qBAAS;AAAA,UACX;AAAA,QACF;AAAA,MACF;AAAA,IACF,SAAS,KAAK;AACZ,YAAM,SAAS,eAAe,QAAQ,IAAI,UAAU,OAAO,GAAG;AAC9D,MAAAA,KAAI,MAAM,wBAAwB,MAAM,EAAE;AAC1C,YAAM;AAAA,IACR;AAGA,QAAI,cAAc;AAChB,WAAK,SAAS,IAAI,WAAW,YAAY;AAAA,IAC3C;AAEA,QAAI,CAAC,QAAQ;AACX,eAAS;AAAA,IACX;AAEA,IAAAA,KAAI,KAAK,aAAa,OAAO,MAAM,QAAQ;AAC3C,WAAO;AAAA,EACT;AACF;AAIA,SAAS,cAAc,KAA0E;AAC/F,SAAO,KAAK,SAAS,YAAY,KAAK,YAAY,UAAU,OAAO,KAAK,eAAe;AACzF;AAEA,SAAS,gBAAgB,KAAqC;AAC5D,SAAO,OAAO,KAAK,WAAW;AAChC;AAEA,SAAS,mBAAmB,KAAyE;AACnG,SAAO,KAAK,SAAS,eAAe,KAAK,SAAS;AACpD;AAEA,SAAS,YAAY,KAAyB;AAC5C,MAAI,CAAC,KAAK,SAAS,QAAS,QAAO;AACnC,QAAM,QAAkB,CAAC;AACzB,aAAW,SAAS,IAAI,QAAQ,SAAS;AACvC,QAAI,MAAM,SAAS,UAAU,OAAO,MAAM,SAAS,UAAU;AAC3D,YAAM,KAAK,MAAM,IAAI;AAAA,IACvB;AAAA,EACF;AACA,SAAO,MAAM,SAAS,IAAI,MAAM,KAAK,EAAE,IAAI;AAC7C;;;ACvHA,IAAMC,OAAM,aAAa,eAAe;AAejC,IAAM,2BAAN,MAAmD;AAAA,EAC/C;AAAA,EACD;AAAA,EACA,YAAY,oBAAI,IAA2B;AAAA,EAEnD,YAAY,MAAc,QAAwB;AAChD,SAAK,OAAO;AACZ,SAAK,SAAS;AAAA,EAChB;AAAA,EAEA,MAAM,MACJ,QACA,WACA,SACiB;AACjB,UAAM,UAAU,KAAK,OAAO;AAC5B,UAAM,SAAS,KAAK,OAAO,UAAU,QAAQ,IAAI,KAAK,OAAO,aAAuB,EAAE;AACtF,UAAM,QAAQ,SAAS,SAAU,KAAK,OAAO;AAE7C,QAAI,CAAC,QAAS,OAAM,IAAI,MAAM,GAAG,KAAK,IAAI,uBAAuB;AACjE,QAAI,CAAC,OAAQ,OAAM,IAAI,MAAM,GAAG,KAAK,IAAI,sBAAsB;AAC/D,QAAI,CAAC,MAAO,OAAM,IAAI,MAAM,GAAG,KAAK,IAAI,qBAAqB;AAG7D,QAAI,UAAU,KAAK,UAAU,IAAI,SAAS;AAC1C,QAAI,CAAC,SAAS;AACZ,gBAAU,CAAC;AACX,WAAK,UAAU,IAAI,WAAW,OAAO;AAAA,IACvC;AAEA,UAAM,WAA0B,CAAC;AAGjC,UAAM,eAAe,SAAS,gBAAiB,KAAK,OAAO;AAC3D,QAAI,cAAc;AAChB,eAAS,KAAK,EAAE,MAAM,UAAU,SAAS,aAAa,CAAC;AAAA,IACzD;AAGA,UAAM,aAAc,KAAK,OAAO,cAAyB;AACzD,UAAM,gBAAgB,QAAQ,MAAM,CAAC,UAAU;AAC/C,aAAS,KAAK,GAAG,aAAa;AAG9B,aAAS,KAAK,EAAE,MAAM,QAAQ,SAAS,OAAO,CAAC;AAE/C,IAAAA,KAAI,KAAK,YAAY,KAAK,IAAI,YAAY,KAAK,cAAc,UAAU,MAAM,GAAG,CAAC,CAAC,MAAM;AAExF,UAAM,MAAM,GAAG,QAAQ,QAAQ,OAAO,EAAE,CAAC;AAEzC,UAAM,MAAM,MAAM,MAAM,KAAK;AAAA,MAC3B,QAAQ;AAAA,MACR,SAAS;AAAA,QACP,gBAAgB;AAAA,QAChB,iBAAiB,UAAU,MAAM;AAAA,MACnC;AAAA,MACA,MAAM,KAAK,UAAU;AAAA,QACnB;AAAA,QACA;AAAA,QACA,YAAY,SAAS,aAAc,KAAK,OAAO,aAAwB;AAAA,QACvE,aAAc,KAAK,OAAO,eAA0B;AAAA,MACtD,CAAC;AAAA,IACH,CAAC;AAED,QAAI,CAAC,IAAI,IAAI;AACX,YAAM,UAAU,MAAM,IAAI,KAAK;AAC/B,MAAAA,KAAI,MAAM,GAAG,KAAK,IAAI,cAAc,IAAI,MAAM,KAAK,QAAQ,MAAM,GAAG,GAAG,CAAC,EAAE;AAC1E,YAAM,IAAI,MAAM,GAAG,KAAK,IAAI,eAAe,IAAI,MAAM,EAAE;AAAA,IACzD;AAEA,UAAM,OAAQ,MAAM,IAAI,KAAK;AAC7B,UAAM,QAAQ,KAAK,QAAQ,CAAC,GAAG,QAAQ,WAAW;AAElD,QAAI,KAAK,OAAO;AACd,MAAAA,KAAI,KAAK,WAAW,KAAK,MAAM,aAAa,SAAS,KAAK,MAAM,iBAAiB,MAAM;AAAA,IACzF;AAGA,YAAQ,KAAK,EAAE,MAAM,QAAQ,SAAS,OAAO,CAAC;AAC9C,YAAQ,KAAK,EAAE,MAAM,aAAa,SAAS,MAAM,CAAC;AAElD,IAAAA,KAAI,KAAK,aAAa,MAAM,MAAM,QAAQ;AAC1C,WAAO;AAAA,EACT;AACF;;;AC1FA,IAAMC,OAAM,aAAa,cAAI;AAEtB,IAAM,UAAN,MAAc;AAAA,EACX,WAAW,oBAAI,IAAqB;AAAA,EACpC,YAAY,oBAAI,IAAsB;AAAA,EACtC;AAAA,EACA,aAAa,oBAAI,IAAY;AAAA,EAErC,YAAY,QAAmB;AAC7B,SAAK,SAAS;AAAA,EAChB;AAAA,EAEA,OAAa;AACX,eAAW,CAAC,MAAM,QAAQ,KAAK,OAAO,QAAQ,KAAK,OAAO,QAAQ,GAAG;AACnE,UAAI,SAAS,YAAY,MAAO;AAChC,cAAQ,SAAS,MAAM;AAAA,QACrB,KAAK;AACH,eAAK,SAAS,IAAI,MAAM,IAAI,cAAc,QAAQ,CAAC;AACnD;AAAA,QACF;AACE,UAAAA,KAAI,KAAK,yCAAW,SAAS,IAAI,EAAE;AAAA,MACvC;AAAA,IACF;AAEA,eAAW,CAAC,MAAM,UAAU,KAAK,OAAO,QAAQ,KAAK,OAAO,SAAS,GAAG;AACtE,cAAQ,WAAW,MAAM;AAAA,QACvB,KAAK;AACH,eAAK,UAAU,IAAI,MAAM,IAAI,oBAAoB,UAAU,CAAC;AAC5D;AAAA,QACF,KAAK;AACH,eAAK,UAAU,IAAI,MAAM,IAAI,yBAAyB,MAAM,UAAU,CAAC;AACvE;AAAA,QACF;AACE,UAAAA,KAAI,KAAK,yCAAW,WAAW,IAAI,EAAE;AAAA,MACzC;AAAA,IACF;AAEA,IAAAA,KAAI,KAAK,4BAAQ,KAAK,SAAS,IAAI,wBAAS,KAAK,UAAU,IAAI,qBAAM;AAAA,EACvE;AAAA,EAEA,MAAM,MAAM,aAAoC;AAC9C,UAAM,UAAU,KAAK,SAAS,IAAI,WAAW;AAC7C,QAAI,CAAC,SAAS;AACZ,YAAM,IAAI,MAAM,iBAAO,WAAW,sBAAO;AAAA,IAC3C;AACA,UAAM,QAAQ,MAAM;AAAA,EACtB;AAAA,EAEA,MAAM,QAAuB;AAC3B,QAAI,KAAK,UAAU,SAAS,GAAG;AAC7B,YAAM,IAAI,MAAM,4CAAS;AAAA,IAC3B;AAEA,UAAM,gBAAgB,CAAC,GAAG,KAAK,SAAS,QAAQ,CAAC,EAAE,IAAI,CAAC,CAAC,MAAM,OAAO,MAAM;AAC1E,MAAAA,KAAI,KAAK,6BAAS,IAAI,EAAE;AACxB,aAAO,QAAQ,MAAM,CAAC,QAAQ,KAAK,cAAc,GAAG,CAAC,EAAE,MAAM,CAAC,QAAQ;AACpE,QAAAA,KAAI,MAAM,gBAAM,IAAI,kBAAQ,eAAe,QAAQ,IAAI,UAAU,GAAG,EAAE;AAAA,MACxE,CAAC;AAAA,IACH,CAAC;AAED,UAAM,QAAQ,IAAI,aAAa;AAAA,EACjC;AAAA,EAEA,MAAM,OAAsB;AAC1B,IAAAA,KAAI,KAAK,6BAAS;AAClB,UAAM,QAAQ,CAAC,GAAG,KAAK,SAAS,OAAO,CAAC,EAAE,IAAI,CAAC,OAAO,GAAG,KAAK,CAAC;AAC/D,UAAM,QAAQ,WAAW,KAAK;AAC9B,IAAAA,KAAI,KAAK,oBAAK;AAAA,EAChB;AAAA,EAEA,MAAc,cAAc,KAAoC;AAC9D,UAAM,UAAU,GAAG,IAAI,OAAO,IAAI,IAAI,QAAQ;AAE9C,QAAI,KAAK,WAAW,IAAI,OAAO,GAAG;AAChC,MAAAA,KAAI,KAAK,iEAAe;AACxB;AAAA,IACF;AAEA,SAAK,WAAW,IAAI,OAAO;AAE3B,QAAI;AACF,UAAI,IAAI,KAAK,WAAW,GAAG,GAAG;AAC5B,cAAM,KAAK,cAAc,GAAG;AAC5B;AAAA,MACF;AAEA,YAAM,eAAe,KAAK,OAAO,aAAa,IAAI,QAAQ,KACrD,KAAK,OAAO;AACjB,YAAM,WAAW,KAAK,UAAU,IAAI,YAAY;AAEhD,UAAI,CAAC,UAAU;AACb,QAAAA,KAAI,MAAM,iBAAO,YAAY,sBAAO;AACpC;AAAA,MACF;AAEA,MAAAA,KAAI,KAAK,gBAAM,YAAY,wBAAS;AAGpC,YAAM,UAAU,KAAK,SAAS,IAAI,IAAI,OAAO;AAC7C,UAAI,WAAW,gBAAgB,SAAS;AACtC,QAAC,QAAgB,WAAW,IAAI,UAAU,IAAI,UAAU;AAAA,MAC1D;AAEA,YAAM,UAA2B,CAAC;AAClC,UAAI,KAAK,OAAO,cAAc;AAC5B,gBAAQ,eAAe,KAAK,OAAO;AAAA,MACrC;AAEA,YAAM,aAAa,GAAG,IAAI,OAAO,IAAI,IAAI,QAAQ;AACjD,YAAM,WAAW,MAAM,SAAS,MAAM,IAAI,MAAM,YAAY,OAAO;AAEnE,UAAI,CAAC,QAAS;AAEd,YAAM,QAAQ,KAAK;AAAA,QACjB,UAAU,IAAI;AAAA,QACd,MAAM;AAAA,QACN,YAAY,IAAI;AAAA,MAClB,CAAC;AAED,MAAAA,KAAI,KAAK,uBAAQ,SAAS,MAAM,gBAAM;AAAA,IACxC,SAAS,KAAK;AACZ,YAAM,SAAS,eAAe,QAAQ,IAAI,UAAU,OAAO,GAAG;AAC9D,MAAAA,KAAI,MAAM,yCAAW,MAAM,EAAE;AAE7B,UAAI;AACF,cAAM,UAAU,KAAK,SAAS,IAAI,IAAI,OAAO;AAC7C,YAAI,SAAS;AACX,gBAAM,QAAQ,KAAK;AAAA,YACjB,UAAU,IAAI;AAAA,YACd,MAAM;AAAA,YACN,YAAY,IAAI;AAAA,UAClB,CAAC;AAAA,QACH;AAAA,MACF,QAAQ;AAAA,MAER;AAAA,IACF,UAAE;AACA,WAAK,WAAW,OAAO,OAAO;AAAA,IAChC;AAAA,EACF;AAAA,EAEA,MAAc,cAAc,KAAoC;AAC9D,UAAM,UAAU,KAAK,SAAS,IAAI,IAAI,OAAO;AAC7C,QAAI,CAAC,QAAS;AAEd,UAAM,QAAQ,IAAI,KAAK,KAAK,EAAE,MAAM,KAAK;AACzC,UAAM,MAAM,MAAM,CAAC,EAAG,YAAY;AAClC,UAAM,MAAM,MAAM,CAAC;AAEnB,YAAQ,KAAK;AAAA,MACX,KAAK,UAAU;AACb,YAAI,CAAC,KAAK;AACR,gBAAM,UAAU,KAAK,OAAO,aAAa,IAAI,QAAQ,KAAK,KAAK,OAAO;AACtE,gBAAM,YAAY,CAAC,GAAG,KAAK,UAAU,KAAK,CAAC,EAAE,KAAK,IAAI;AACtD,gBAAM,QAAQ,KAAK;AAAA,YACjB,UAAU,IAAI;AAAA,YACd,MAAM,6BAAS,OAAO;AAAA,4BAAW,SAAS;AAAA;AAAA,YAC1C,YAAY,IAAI;AAAA,UAClB,CAAC;AAAA,QACH,WAAW,KAAK,UAAU,IAAI,GAAG,GAAG;AAClC,cAAI,CAAC,KAAK,OAAO,WAAY,MAAK,OAAO,aAAa,CAAC;AACvD,eAAK,OAAO,WAAW,IAAI,QAAQ,IAAI;AACvC,gBAAM,QAAQ,KAAK;AAAA,YACjB,UAAU,IAAI;AAAA,YACd,MAAM,6BAAS,GAAG;AAAA,YAClB,YAAY,IAAI;AAAA,UAClB,CAAC;AAAA,QACH,OAAO;AACL,gBAAM,QAAQ,KAAK;AAAA,YACjB,UAAU,IAAI;AAAA,YACd,MAAM,6BAAS,GAAG;AAAA,gBAAS,CAAC,GAAG,KAAK,UAAU,KAAK,CAAC,EAAE,KAAK,IAAI,CAAC;AAAA,YAChE,YAAY,IAAI;AAAA,UAClB,CAAC;AAAA,QACH;AACA;AAAA,MACF;AAAA,MAEA,KAAK,SAAS;AACZ,cAAM,QAAQ,KAAK;AAAA,UACjB,UAAU,IAAI;AAAA,UACd,MAAM;AAAA,YACJ;AAAA,YACA;AAAA,YACA;AAAA,YACA;AAAA,UACF,EAAE,KAAK,IAAI;AAAA,UACX,YAAY,IAAI;AAAA,QAClB,CAAC;AACD;AAAA,MACF;AAAA,MAEA,KAAK,SAAS;AACZ,cAAM,QAAQ,KAAK;AAAA,UACjB,UAAU,IAAI;AAAA,UACd,MAAM,SAAS,KAAK,IAAI,IAAI,IAAI,SAAS;AAAA,UACzC,YAAY,IAAI;AAAA,QAClB,CAAC;AACD;AAAA,MACF;AAAA,MAEA,SAAS;AACP,cAAM,QAAQ,KAAK;AAAA,UACjB,UAAU,IAAI;AAAA,UACd,MAAM,6BAAS,GAAG;AAAA,UAClB,YAAY,IAAI;AAAA,QAClB,CAAC;AAAA,MACH;AAAA,IACF;AAAA,EACF;AACF;","names":["join","readFile","writeFile","existsSync","log","log","log"]}