cli-wechat-bridge 1.0.9 → 1.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -5,6 +5,7 @@ import path from "node:path";
5
5
  import { execFileSync, spawn } from "node:child_process";
6
6
  import { fileURLToPath } from "node:url";
7
7
  import { resolveDefaultAdapterCommand, } from "../bridge/bridge-adapters.js";
8
+ import { t } from "../i18n/index.js";
8
9
  import { delay, getSharedSessionIdFromAdapterState, quoteWindowsCommandArg, } from "../bridge/bridge-adapters.shared.js";
9
10
  import { BridgeController } from "../bridge/bridge-controller.js";
10
11
  import { forwardWechatFinalReply } from "../bridge/bridge-final-reply.js";
@@ -14,7 +15,7 @@ import { buildOneTimeCode, buildWechatInboundPrompt, formatApprovalMessage, form
14
15
  import { formatUserFacingBridgeFatalError, formatUserFacingInboundError, formatWechatContextTokenStaleLogEntry, formatWechatSendFailureLogEntry, isRetryableWechatSendError, shouldForwardBridgeEventToWechat, } from "../bridge/wechat-bridge.js";
15
16
  import { BRIDGE_LOCK_FILE, BRIDGE_LOG_FILE, ensureChannelDataDir, migrateLegacyChannelFiles, } from "../wechat/channel-config.js";
16
17
  import { ensureWechatCredentials } from "../wechat/setup.js";
17
- import { listBindings, loadEmojiBindings, removeBinding, resolveEmojiCommand, setBinding, } from "./emoji-bindings.js";
18
+ import { formatBindCommandUsage, formatBindingsListMessage, isBindCommandPrefix, listBindings, loadEmojiBindings, parseEmojiBindingsCommand, removeBinding, resolveEmojiCommand, setBinding, } from "./emoji-bindings.js";
18
19
  import { classifyWechatTransportError, DEFAULT_LONG_POLL_TIMEOUT_MS, describeWechatTransportError, isWechatContextTokenStaleError, WeChatTransport, } from "../wechat/wechat-transport.js";
19
20
  import { createRuntimeHost, } from "../runtime/create-runtime-host.js";
20
21
  import { clearLocalCompanionEndpoint, clearLocalCompanionOccupancy, readLocalCompanionEndpoint, } from "../companion/local-companion-link.js";
@@ -136,22 +137,6 @@ export function parseDaemonSwitchCommand(text) {
136
137
  return null;
137
138
  }
138
139
  }
139
- export function parseEmojiBindingsCommand(text) {
140
- const trimmed = text.trim();
141
- const lower = trimmed.toLowerCase();
142
- if (lower === "/bindings") {
143
- return { type: "list" };
144
- }
145
- const bindMatch = trimmed.match(/^\/bind\s+(\[[^\]]+\])\s+(.+)$/i);
146
- if (bindMatch) {
147
- return { type: "bind", emoji: bindMatch[1], command: bindMatch[2].trim() };
148
- }
149
- const unbindMatch = trimmed.match(/^\/unbind\s+(\[[^\]]+\])$/i);
150
- if (unbindMatch) {
151
- return { type: "unbind", emoji: unbindMatch[1] };
152
- }
153
- return null;
154
- }
155
140
  export function defaultDaemonSessionStartMode(adapter) {
156
141
  return adapter === "claude" || adapter === "opencode" ? "new" : "restore";
157
142
  }
@@ -538,7 +523,7 @@ class WechatDaemon {
538
523
  status: slot.runtime.getState().status,
539
524
  cwd: this.cwd,
540
525
  companionPid: endpoint?.companionPid,
541
- pendingApproval: Boolean(slot.pendingConfirmation),
526
+ pendingApproval: slot.pendingConfirmations.length > 0,
542
527
  pendingUserInput: Boolean(slot.pendingUserInput),
543
528
  };
544
529
  }),
@@ -559,6 +544,13 @@ class WechatDaemon {
559
544
  log(`Working directory: ${this.cwd}`);
560
545
  log("Switch from WeChat with /codex, /claude, or /opencode.");
561
546
  appendDaemonLog(`started: cwd=${this.cwd}`);
547
+ const activeSlot = this.getActiveSlot();
548
+ const welcomeText = t("daemon.welcome", {
549
+ cwd: this.cwd,
550
+ adapter: activeSlot?.adapter ?? "none",
551
+ bindings: formatBindingsListMessage(listBindings()),
552
+ });
553
+ await this.queueWechatMessage(this.authorizedUserId, welcomeText);
562
554
  while (!this.shutdownPromise) {
563
555
  let pollResult;
564
556
  try {
@@ -773,7 +765,7 @@ class WechatDaemon {
773
765
  outputBatcher: new OutputBatcher(async (text) => {
774
766
  await this.queueWechatMessage(this.authorizedUserId, prefixDaemonAdapterMessage(adapter, text));
775
767
  }),
776
- pendingConfirmation: null,
768
+ pendingConfirmations: [],
777
769
  pendingUserInput: null,
778
770
  activeTask: null,
779
771
  lastOutputAt: 0,
@@ -789,7 +781,7 @@ class WechatDaemon {
789
781
  async startFreshSlotSession(slot) {
790
782
  await slot.outputBatcher.flushNow();
791
783
  slot.outputBatcher.clear();
792
- slot.pendingConfirmation = null;
784
+ slot.pendingConfirmations = [];
793
785
  slot.pendingUserInput = null;
794
786
  slot.activeTask = null;
795
787
  if (slot.adapter === "claude") {
@@ -806,8 +798,8 @@ class WechatDaemon {
806
798
  handleSlotEvent(slot, event) {
807
799
  slot.controller.syncLocalClientEndpoint();
808
800
  const adapterState = slot.runtime.getState();
809
- if (slot.pendingConfirmation && !adapterState.pendingApproval) {
810
- slot.pendingConfirmation = null;
801
+ if (slot.pendingConfirmations.length > 0 && !adapterState.pendingApproval) {
802
+ slot.pendingConfirmations = [];
811
803
  }
812
804
  if (slot.pendingUserInput && !adapterState.pendingUserInput) {
813
805
  slot.pendingUserInput = null;
@@ -869,7 +861,7 @@ class WechatDaemon {
869
861
  case "approval_required":
870
862
  this.trackWechatForwardTask(slot.outputBatcher.flushNow().then(async () => {
871
863
  const pending = toPendingApproval(event);
872
- slot.pendingConfirmation = pending;
864
+ slot.pendingConfirmations.push(pending);
873
865
  appendDaemonLog(`approval_required: adapter=${slot.adapter} source=${pending.source} command=${truncatePreview(pending.commandPreview)}`);
874
866
  await this.queueWechatMessage(this.authorizedUserId, prefixDaemonAdapterMessage(slot.adapter, formatApprovalMessage(pending, adapterState)), "approval_required");
875
867
  }));
@@ -918,14 +910,14 @@ class WechatDaemon {
918
910
  break;
919
911
  case "task_complete":
920
912
  this.trackWechatForwardTask(slot.outputBatcher.flushNow().then(() => {
921
- slot.pendingConfirmation = null;
913
+ slot.pendingConfirmations = [];
922
914
  slot.pendingUserInput = null;
923
915
  slot.activeTask = null;
924
916
  }));
925
917
  break;
926
918
  case "task_failed":
927
919
  this.trackWechatForwardTask(slot.outputBatcher.flushNow().then(async () => {
928
- slot.pendingConfirmation = null;
920
+ slot.pendingConfirmations = [];
929
921
  slot.pendingUserInput = null;
930
922
  slot.activeTask = null;
931
923
  await this.queueWechatMessage(this.authorizedUserId, prefixDaemonAdapterMessage(slot.adapter, formatTaskFailedMessage(slot.adapter, event.message)), "task_failed");
@@ -934,7 +926,7 @@ class WechatDaemon {
934
926
  case "fatal_error":
935
927
  logError(`${slot.adapter}: ${event.message}`);
936
928
  appendDaemonLog(`fatal_error: adapter=${slot.adapter} message=${event.message}`);
937
- slot.pendingConfirmation = null;
929
+ slot.pendingConfirmations = [];
938
930
  slot.pendingUserInput = null;
939
931
  slot.activeTask = null;
940
932
  this.trackWechatForwardTask(slot.outputBatcher.flushNow().then(async () => {
@@ -1000,6 +992,10 @@ class WechatDaemon {
1000
992
  await this.handleEmojiBindingsCommand(message.senderId, bindingsCmd);
1001
993
  return;
1002
994
  }
995
+ if (isBindCommandPrefix(message.text)) {
996
+ await this.queueWechatMessage(message.senderId, formatBindCommandUsage());
997
+ return;
998
+ }
1003
999
  const slot = this.getActiveSlot();
1004
1000
  if (!slot) {
1005
1001
  await this.queueWechatMessage(message.senderId, formatNoActiveAdapterMessage());
@@ -1007,15 +1003,15 @@ class WechatDaemon {
1007
1003
  }
1008
1004
  const command = parseWechatControlCommand(message.text, {
1009
1005
  adapter: slot.adapter,
1010
- hasPendingConfirmation: Boolean(slot.pendingConfirmation),
1006
+ hasPendingConfirmation: slot.pendingConfirmations.length > 0,
1011
1007
  hasPendingUserInput: Boolean(slot.pendingUserInput),
1012
1008
  });
1013
1009
  if (command) {
1014
1010
  await this.handleSystemCommand(message, slot, command);
1015
1011
  return;
1016
1012
  }
1017
- if (slot.pendingConfirmation) {
1018
- await this.queueWechatMessage(message.senderId, prefixDaemonAdapterMessage(slot.adapter, formatPendingApprovalReminder(slot.pendingConfirmation, slot.runtime.getState())));
1013
+ if (slot.pendingConfirmations.length > 0) {
1014
+ await this.queueWechatMessage(message.senderId, prefixDaemonAdapterMessage(slot.adapter, formatPendingApprovalReminder(slot.pendingConfirmations[0], slot.runtime.getState())));
1019
1015
  return;
1020
1016
  }
1021
1017
  if (slot.pendingUserInput) {
@@ -1032,13 +1028,7 @@ class WechatDaemon {
1032
1028
  async handleEmojiBindingsCommand(senderId, cmd) {
1033
1029
  switch (cmd.type) {
1034
1030
  case "list": {
1035
- const map = listBindings();
1036
- if (map.size === 0) {
1037
- await this.queueWechatMessage(senderId, "No emoji bindings configured.");
1038
- return;
1039
- }
1040
- const lines = Array.from(map.entries()).map(([emoji, command]) => `${emoji} → ${command}`);
1041
- await this.queueWechatMessage(senderId, `Emoji bindings:\n${lines.join("\n")}`);
1031
+ await this.queueWechatMessage(senderId, formatBindingsListMessage(listBindings()));
1042
1032
  return;
1043
1033
  }
1044
1034
  case "bind": {
@@ -1070,7 +1060,7 @@ class WechatDaemon {
1070
1060
  }
1071
1061
  await activeSlot.outputBatcher.flushNow();
1072
1062
  activeSlot.outputBatcher.clear();
1073
- activeSlot.pendingConfirmation = null;
1063
+ activeSlot.pendingConfirmations = [];
1074
1064
  activeSlot.pendingUserInput = null;
1075
1065
  await activeSlot.runtime.createSession();
1076
1066
  appendDaemonLog(`new_session: adapter=${activeSlot.adapter}`);
@@ -1085,14 +1075,14 @@ class WechatDaemon {
1085
1075
  case "reset":
1086
1076
  await activeSlot.outputBatcher.flushNow();
1087
1077
  activeSlot.outputBatcher.clear();
1088
- activeSlot.pendingConfirmation = null;
1078
+ activeSlot.pendingConfirmations = [];
1089
1079
  activeSlot.pendingUserInput = null;
1090
1080
  await activeSlot.runtime.reset();
1091
1081
  appendDaemonLog(`reset: adapter=${activeSlot.adapter}`);
1092
1082
  await this.queueWechatMessage(message.senderId, prefixDaemonAdapterMessage(activeSlot.adapter, "Worker session has been reset."));
1093
1083
  return;
1094
1084
  case "confirm":
1095
- await this.confirmPendingApproval(message, activeSlot, command.code);
1085
+ await this.confirmPendingApproval(message, activeSlot);
1096
1086
  return;
1097
1087
  case "deny":
1098
1088
  await this.denyPendingApproval(message, activeSlot);
@@ -1102,51 +1092,42 @@ class WechatDaemon {
1102
1092
  return;
1103
1093
  }
1104
1094
  }
1105
- async confirmPendingApproval(message, activeSlot, code) {
1106
- const slot = this.resolvePendingApprovalSlot(activeSlot, code);
1107
- if (!slot) {
1108
- await this.queueWechatMessage(message.senderId, "No matching pending approval request.");
1109
- return;
1110
- }
1111
- const pending = slot.pendingConfirmation;
1112
- if (!pending) {
1095
+ async confirmPendingApproval(message, activeSlot) {
1096
+ const slot = this.resolvePendingApprovalSlot(activeSlot);
1097
+ if (!slot || slot.pendingConfirmations.length === 0) {
1113
1098
  await this.queueWechatMessage(message.senderId, "No pending approval request.");
1114
1099
  return;
1115
1100
  }
1116
- if (pending.code !== code && this.countPendingApprovals() > 1) {
1117
- await this.queueWechatMessage(message.senderId, "Confirmation code did not match.");
1118
- return;
1119
- }
1120
- const confirmed = await slot.runtime.resolveApproval("confirm");
1121
- if (!confirmed) {
1101
+ const count = await slot.runtime.resolveAllApprovals("confirm");
1102
+ if (!count) {
1122
1103
  await this.queueWechatMessage(message.senderId, prefixDaemonAdapterMessage(slot.adapter, "The worker could not apply this approval request."));
1123
1104
  return;
1124
1105
  }
1125
- slot.pendingConfirmation = null;
1106
+ const preview = slot.pendingConfirmations[0]?.commandPreview ?? "";
1107
+ slot.pendingConfirmations = [];
1126
1108
  slot.activeTask = {
1127
1109
  startedAt: Date.now(),
1128
- inputPreview: pending.commandPreview,
1110
+ inputPreview: preview,
1129
1111
  };
1130
- appendDaemonLog(`approval_confirmed: adapter=${slot.adapter} command=${truncatePreview(pending.commandPreview)}`);
1131
- await this.queueWechatMessage(message.senderId, prefixDaemonAdapterMessage(slot.adapter, "Approval confirmed. Continuing..."));
1112
+ appendDaemonLog(`approval_confirmed: adapter=${slot.adapter} count=${count} command=${truncatePreview(preview)}`);
1113
+ await this.queueWechatMessage(message.senderId, prefixDaemonAdapterMessage(slot.adapter, count > 1
1114
+ ? `${count} approvals confirmed. Continuing...`
1115
+ : "Approval confirmed. Continuing..."));
1132
1116
  }
1133
1117
  async denyPendingApproval(message, activeSlot) {
1134
- const slot = activeSlot.pendingConfirmation || this.countPendingApprovals() !== 1
1135
- ? activeSlot
1136
- : Array.from(this.slots.values()).find((entry) => entry.pendingConfirmation) ?? activeSlot;
1137
- const pending = slot.pendingConfirmation;
1138
- if (!pending) {
1118
+ const slot = this.resolvePendingApprovalSlot(activeSlot);
1119
+ if (!slot || slot.pendingConfirmations.length === 0) {
1139
1120
  await this.queueWechatMessage(message.senderId, "No pending approval request.");
1140
1121
  return;
1141
1122
  }
1142
- const denied = await slot.runtime.resolveApproval("deny");
1143
- if (!denied) {
1123
+ const count = await slot.runtime.resolveAllApprovals("deny");
1124
+ if (!count) {
1144
1125
  await this.queueWechatMessage(message.senderId, prefixDaemonAdapterMessage(slot.adapter, "The worker could not deny this approval request cleanly."));
1145
1126
  return;
1146
1127
  }
1147
- slot.pendingConfirmation = null;
1148
- appendDaemonLog(`approval_denied: adapter=${slot.adapter} command=${truncatePreview(pending.commandPreview)}`);
1149
- await this.queueWechatMessage(message.senderId, prefixDaemonAdapterMessage(slot.adapter, "Approval denied."));
1128
+ slot.pendingConfirmations = [];
1129
+ appendDaemonLog(`approval_denied: adapter=${slot.adapter} count=${count}`);
1130
+ await this.queueWechatMessage(message.senderId, prefixDaemonAdapterMessage(slot.adapter, count > 1 ? `${count} approvals denied.` : "Approval denied."));
1150
1131
  }
1151
1132
  async answerPendingUserInput(message, activeSlot, raw) {
1152
1133
  const pending = activeSlot.pendingUserInput;
@@ -1172,18 +1153,18 @@ class WechatDaemon {
1172
1153
  appendDaemonLog(`user_input_answered: adapter=${activeSlot.adapter} preview=${parsed.preview}`);
1173
1154
  await this.queueWechatMessage(message.senderId, prefixDaemonAdapterMessage(activeSlot.adapter, "Answer submitted. Continuing..."));
1174
1155
  }
1175
- resolvePendingApprovalSlot(activeSlot, code) {
1176
- if (code) {
1177
- return (Array.from(this.slots.values()).find((slot) => slot.pendingConfirmation?.code === code) ?? null);
1178
- }
1179
- if (activeSlot.pendingConfirmation && this.countPendingApprovals() <= 1) {
1156
+ resolvePendingApprovalSlot(activeSlot) {
1157
+ if (activeSlot.pendingConfirmations.length > 0) {
1180
1158
  return activeSlot;
1181
1159
  }
1182
- return null;
1160
+ return (Array.from(this.slots.values()).find((slot) => slot.pendingConfirmations.length > 0) ?? null);
1183
1161
  }
1184
1162
  countPendingApprovals() {
1185
- return Array.from(this.slots.values()).filter((slot) => slot.pendingConfirmation)
1186
- .length;
1163
+ let count = 0;
1164
+ for (const slot of this.slots.values()) {
1165
+ count += slot.pendingConfirmations.length;
1166
+ }
1167
+ return count;
1187
1168
  }
1188
1169
  getActiveSlot() {
1189
1170
  if (!this.activeAdapter) {
@@ -0,0 +1,32 @@
1
+ import { messages as zhMessages } from "./messages-zh.js";
2
+ import { messages as enMessages } from "./messages-en.js";
3
+ const catalogs = {
4
+ zh: zhMessages,
5
+ en: enMessages,
6
+ };
7
+ let currentLocale = "zh";
8
+ export function getLocale() {
9
+ return currentLocale;
10
+ }
11
+ export function setLocale(locale) {
12
+ currentLocale = locale;
13
+ }
14
+ export function initLocaleFromEnv(env = process.env) {
15
+ const value = env.CLI_BRIDGE_LANG?.trim().toLowerCase();
16
+ if (value === "en" || value === "english") {
17
+ currentLocale = "en";
18
+ }
19
+ else {
20
+ currentLocale = "zh";
21
+ }
22
+ }
23
+ export function t(key, params) {
24
+ const catalog = catalogs[currentLocale];
25
+ let message = catalog[key] ?? catalogs.en[key] ?? key;
26
+ if (params) {
27
+ for (const [name, value] of Object.entries(params)) {
28
+ message = message.replaceAll(`{${name}}`, String(value));
29
+ }
30
+ }
31
+ return message;
32
+ }
@@ -0,0 +1,48 @@
1
+ export const messages = {
2
+ // === Bridge welcome & control ===
3
+ "bridge.welcome": "WeChat Bridge ready ({adapter}).\nCWD: {cwd}\n\nCommands: /stop, /confirm, /deny, /status, /new\n{bindings}\n\nManage: /bind [emoji] cmd, /unbind [emoji], /bindings",
4
+ "bridge.stopped": "Bridge stopped.",
5
+ "bridge.interrupt.sent": "Interrupt signal sent.",
6
+ "bridge.interrupt.notBusy": "No active task to interrupt.",
7
+ "bridge.reset.done": "Worker session has been reset.",
8
+ "bridge.status.notRunning": "{adapter} adapter is not running.",
9
+ "bridge.status.busy": "{adapter} is still working on the previous request. Wait for it to finish or send /interrupt.",
10
+ "bridge.newSession.done": "New session started.",
11
+ "bridge.fatalError": "Bridge error: {message}",
12
+ // === Daemon welcome & control ===
13
+ "daemon.welcome": "WeChat Daemon ready.\nCWD: {cwd}\nActive: {adapter}\n\nCommands: /claude, /codex, /opencode, /stop, /confirm, /deny, /status\n{bindings}\n\nManage: /bind [emoji] cmd, /unbind [emoji], /bindings",
14
+ "daemon.noActiveAdapter": "No active terminal is selected. Send /codex, /claude, or /opencode to start one.",
15
+ "daemon.switchResult.new": "Started a new visible CLI.",
16
+ "daemon.switchResult.reused": "Reused the existing visible CLI.",
17
+ // === Approval ===
18
+ "approval.prompt": "{adapter} permission request.\n{summary}\n\nReply /confirm or /deny.",
19
+ "approval.confirmed": "Approval confirmed. Continuing...",
20
+ "approval.denied": "Approval denied.",
21
+ "approval.noPending": "No pending approval request.",
22
+ "approval.batchConfirmed": "Batch confirmed {count} approval(s).",
23
+ "approval.batchDenied": "Batch denied {count} approval(s).",
24
+ // === Hook health check ===
25
+ "hook.healthCheck.warning": "[Warning] No hook events received from Claude after 15s.\nThe hook system may not be working — Claude output will not reach WeChat.",
26
+ "hook.healthCheck.logHint": "\nCheck: {logPath}",
27
+ "hook.healthCheck.fixes": "\nCommon fixes:\n- Ensure Node.js >= 22.6.0: node --version\n- Reinstall: npm install -g cli-wechat-bridge@latest\n- Check firewall: allow localhost TCP connections",
28
+ // === PTY fallback ===
29
+ "pty.fallback.warning": "[Warning] PTY unavailable, using fallback mode. Terminal rendering may be degraded.\n",
30
+ // === Spawn diagnostic ===
31
+ "spawn.diagnostic.title": "Failed to start CLI process: {target}\nError: {error}",
32
+ "spawn.diagnostic.fixesHeader": "\nPossible fixes:",
33
+ "spawn.diagnostic.nodePty": "- The node-pty native module is incompatible with your Node.js version.\n- Run: npm rebuild node-pty\n- Or reinstall: npm install -g cli-wechat-bridge@latest",
34
+ "spawn.diagnostic.xcode": "- Ensure Xcode CLI tools are installed: xcode-select --install",
35
+ "spawn.diagnostic.notFound": "- The command \"{target}\" was not found on PATH.\n- Verify it is installed and accessible from your terminal.",
36
+ "spawn.diagnostic.generic": "- Reinstall: npm install -g cli-wechat-bridge@latest",
37
+ "spawn.diagnostic.nodeVersion": "- Ensure Node.js >= 22.6.0: node --version",
38
+ "spawn.diagnostic.winAdmin": "- If using ConPTY, try running as Administrator.",
39
+ // === Emoji bindings ===
40
+ "binding.usage": "Invalid format.\nUsage: /bind [emoji] command\nExample: /bind 🚀 deploy --prod",
41
+ "binding.bound": "Bound {emoji} → {command}",
42
+ "binding.unbound": "Unbound {emoji}.",
43
+ "binding.notFound": "No binding found for {emoji}.",
44
+ "binding.listEmpty": "No emoji bindings configured.",
45
+ "binding.listHeader": "Emoji bindings:",
46
+ // === Version checker ===
47
+ "update.available": "[Update Available] Version {latest} is available (current: {current}).\nRun: npm install -g cli-wechat-bridge@latest",
48
+ };
@@ -0,0 +1,48 @@
1
+ export const messages = {
2
+ // === 桥接欢迎 & 控制 ===
3
+ "bridge.welcome": "微信桥接就绪 ({adapter})。\n工作目录: {cwd}\n\n命令: /stop, /confirm, /deny, /status, /new\n{bindings}\n\n管理: /bind [表情] 命令, /unbind [表情], /bindings",
4
+ "bridge.stopped": "桥接已停止。",
5
+ "bridge.interrupt.sent": "已发送中断信号。",
6
+ "bridge.interrupt.notBusy": "没有正在执行的任务可中断。",
7
+ "bridge.reset.done": "会话已重置。",
8
+ "bridge.status.notRunning": "{adapter} 适配器未运行。",
9
+ "bridge.status.busy": "{adapter} 仍在处理上一个请求。请等待完成或发送 /interrupt。",
10
+ "bridge.newSession.done": "已开始新会话。",
11
+ "bridge.fatalError": "桥接错误: {message}",
12
+ // === Daemon 欢迎 & 控制 ===
13
+ "daemon.welcome": "微信 Daemon 就绪。\n工作目录: {cwd}\n当前: {adapter}\n\n命令: /claude, /codex, /opencode, /stop, /confirm, /deny, /status\n{bindings}\n\n管理: /bind [表情] 命令, /unbind [表情], /bindings",
14
+ "daemon.noActiveAdapter": "未选择活跃终端。发送 /codex、/claude 或 /opencode 启动一个。",
15
+ "daemon.switchResult.new": "已启动新的可见 CLI。",
16
+ "daemon.switchResult.reused": "已复用现有的可见 CLI。",
17
+ // === 审批 ===
18
+ "approval.prompt": "{adapter} 权限请求。\n{summary}\n\n回复 /confirm 或 /deny。",
19
+ "approval.confirmed": "已确认审批,继续执行...",
20
+ "approval.denied": "已拒绝审批。",
21
+ "approval.noPending": "没有待处理的审批请求。",
22
+ "approval.batchConfirmed": "已批量确认 {count} 个审批。",
23
+ "approval.batchDenied": "已批量拒绝 {count} 个审批。",
24
+ // === Hook 健康检查 ===
25
+ "hook.healthCheck.warning": "[警告] 15 秒内未收到 Claude hook 事件。\nhook 系统可能未正常工作 — Claude 的输出将无法到达微信。",
26
+ "hook.healthCheck.logHint": "\n查看日志: {logPath}",
27
+ "hook.healthCheck.fixes": "\n常见修复方法:\n- 确保 Node.js >= 22.6.0: node --version\n- 重新安装: npm install -g cli-wechat-bridge@latest\n- 检查防火墙: 允许 localhost TCP 连接",
28
+ // === PTY 回退 ===
29
+ "pty.fallback.warning": "[警告] PTY 不可用,已切换到回退模式。终端渲染可能受限。\n",
30
+ // === 启动诊断 ===
31
+ "spawn.diagnostic.title": "无法启动 CLI 进程: {target}\n错误: {error}",
32
+ "spawn.diagnostic.fixesHeader": "\n可能的修复方法:",
33
+ "spawn.diagnostic.nodePty": "- node-pty 原生模块与当前 Node.js 版本不兼容。\n- 运行: npm rebuild node-pty\n- 或重新安装: npm install -g cli-wechat-bridge@latest",
34
+ "spawn.diagnostic.xcode": "- 确保已安装 Xcode 命令行工具: xcode-select --install",
35
+ "spawn.diagnostic.notFound": "- 命令 \"{target}\" 未在 PATH 中找到。\n- 请确认已安装并可从终端访问。",
36
+ "spawn.diagnostic.generic": "- 重新安装: npm install -g cli-wechat-bridge@latest",
37
+ "spawn.diagnostic.nodeVersion": "- 确保 Node.js >= 22.6.0: node --version",
38
+ "spawn.diagnostic.winAdmin": "- 如使用 ConPTY,请尝试以管理员身份运行。",
39
+ // === Emoji 绑定 ===
40
+ "binding.usage": "格式错误。\n用法: /bind [表情] 命令\n示例: /bind 🚀 deploy --prod",
41
+ "binding.bound": "已绑定 {emoji} → {command}",
42
+ "binding.unbound": "已解绑 {emoji}。",
43
+ "binding.notFound": "未找到 {emoji} 的绑定。",
44
+ "binding.listEmpty": "未配置 emoji 绑定。",
45
+ "binding.listHeader": "Emoji 绑定列表:",
46
+ // === 版本检查 ===
47
+ "update.available": "[有新版本] 版本 {latest} 可用(当前: {current})。\n运行: npm install -g cli-wechat-bridge@latest",
48
+ };
@@ -34,6 +34,9 @@ export class LegacyAdapterRuntime {
34
34
  async resolveApproval(action) {
35
35
  return await this.adapter.resolveApproval(action);
36
36
  }
37
+ async resolveAllApprovals(action) {
38
+ return await this.adapter.resolveAllApprovals(action);
39
+ }
37
40
  async submitUserInput(answers) {
38
41
  return await this.adapter.submitUserInput(answers);
39
42
  }
@@ -3,6 +3,7 @@ import fs from "node:fs";
3
3
  import os from "node:os";
4
4
  import path from "node:path";
5
5
  import { fileURLToPath } from "node:url";
6
+ import { initLocaleFromEnv } from "../i18n/index.js";
6
7
  const MODULE_DIR = path.dirname(fileURLToPath(import.meta.url));
7
8
  const PROJECT_DIR = path.resolve(MODULE_DIR, "..", "..");
8
9
  export const DEFAULT_BASE_URL = process.env.WECHAT_ILINK_BASE_URL?.trim() || "https://ilinkai.weixin.qq.com";
@@ -24,6 +25,7 @@ export const WORKSPACES_DIR = path.join(CHANNEL_DATA_DIR, "workspaces");
24
25
  export const INBOUND_MESSAGE_CLAIMS_DIR = path.join(CHANNEL_DATA_DIR, "inbound-message-claims");
25
26
  export const INBOUND_ATTACHMENTS_DIR = path.join(CHANNEL_DATA_DIR, "inbound-attachments");
26
27
  export const EMOJI_BINDINGS_FILE = path.join(CHANNEL_DATA_DIR, "emoji-bindings.json");
28
+ initLocaleFromEnv();
27
29
  const LEGACY_GLOBAL_CHANNEL_DATA_DIR = path.join(os.homedir(), ".claude", "channels", "wechat");
28
30
  const LEGACY_REPO_CHANNEL_DATA_DIR = path.join(PROJECT_DIR, "~", ".claude", "channels", "wechat");
29
31
  const LEGACY_ENV_CHANNEL_DATA_DIR = process.env.CLAUDE_WECHAT_CHANNEL_DATA_DIR?.trim()
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "cli-wechat-bridge",
3
- "version": "1.0.9",
3
+ "version": "1.1.0",
4
4
  "description": "CLI WeChat Bridge for Codex, Claude Code, OpenCode, and PowerShell over WeChat",
5
5
  "type": "module",
6
6
  "files": [