@unlinearity/cli-wechat-bridge 1.0.1 → 1.0.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.
@@ -0,0 +1,5 @@
1
+ #!/usr/bin/env node
2
+
3
+ import { runJsEntry } from "./_run-entry.mjs";
4
+
5
+ runJsEntry("dist/wechat/setup.js");
@@ -4,7 +4,7 @@ import { resolveDefaultAdapterCommand, } from "./bridge-adapters.js";
4
4
  import { delay } from "./bridge-adapters.shared.js";
5
5
  import { BridgeController } from "./bridge-controller.js";
6
6
  import { forwardWechatFinalReply } from "./bridge-final-reply.js";
7
- import { migrateLegacyChannelFiles } from "../wechat/channel-config.js";
7
+ import { ensureWechatCredentials } from "../wechat/setup.js";
8
8
  import { BridgeStateStore } from "./bridge-state.js";
9
9
  import { reapOrphanedOpencodeProcesses, reapPeerBridgeProcesses } from "./bridge-process-reaper.js";
10
10
  import { createRuntimeHost } from "../runtime/create-runtime-host.js";
@@ -196,8 +196,17 @@ function printUsageAndExit() {
196
196
  process.exit(0);
197
197
  }
198
198
  async function main() {
199
- migrateLegacyChannelFiles(log);
200
- // 非阻塞地检查更新(不影响启动速度)
199
+ const options = parseCliArgs(process.argv.slice(2));
200
+ const credentials = await ensureWechatCredentials({
201
+ requireUserId: true,
202
+ validateExisting: true,
203
+ log,
204
+ });
205
+ if (!credentials.userId) {
206
+ throw new Error("Saved WeChat credentials are missing userId.");
207
+ }
208
+ const transport = new WeChatTransport({ log, logError });
209
+ // 非阻塞地检查更新(不影响启动速度,也避免首次登录时打断二维码输出)
201
210
  setTimeout(async () => {
202
211
  try {
203
212
  const versionInfo = await checkForUpdate();
@@ -209,15 +218,6 @@ async function main() {
209
218
  // 静默失败,不影响正常使用
210
219
  }
211
220
  }, 3000); // 延迟3秒,确保不影响启动
212
- const options = parseCliArgs(process.argv.slice(2));
213
- const transport = new WeChatTransport({ log, logError });
214
- const credentials = transport.getCredentials();
215
- if (!credentials) {
216
- throw new Error('No saved WeChat credentials found. Run "bun run setup" first.');
217
- }
218
- if (!credentials.userId) {
219
- throw new Error('Saved WeChat credentials are missing userId. Run "bun run setup" again.');
220
- }
221
221
  const stateStore = new BridgeStateStore({
222
222
  ...options,
223
223
  authorizedUserId: credentials.userId,
@@ -1,10 +1,10 @@
1
1
  #!/usr/bin/env node
2
- import fs from "node:fs";
3
2
  import net from "node:net";
4
3
  import path from "node:path";
5
4
  import { spawn } from "node:child_process";
6
5
  import { fileURLToPath } from "node:url";
7
- import { BRIDGE_LOG_FILE, CREDENTIALS_FILE, migrateLegacyChannelFiles, } from "../wechat/channel-config.js";
6
+ import { BRIDGE_LOG_FILE } from "../wechat/channel-config.js";
7
+ import { ensureWechatCredentials } from "../wechat/setup.js";
8
8
  import { readBridgeLockFile, shouldAutoReclaimBridgeLock, } from "../bridge/bridge-state.js";
9
9
  import { clearLocalCompanionOccupancy, clearLocalCompanionEndpoint, readLocalCompanionEndpoint, } from "./local-companion-link.js";
10
10
  import { runCodexRemoteClient } from "./codex-remote-client.js";
@@ -54,9 +54,13 @@ export function decideLaunchAction(input) {
54
54
  failureMessage: formatSwitchFailureMessage(input.runningLock.cwd),
55
55
  };
56
56
  }
57
- if (input.endpoint &&
58
- input.endpointIsReachable &&
59
- input.companionIsAlive &&
57
+ if (!input.endpoint || !input.endpointIsReachable) {
58
+ return {
59
+ kind: "restart_unhealthy",
60
+ message: formatRestartUnhealthyMessage(input.requestedCwd),
61
+ };
62
+ }
63
+ if (input.companionIsAlive &&
60
64
  (input.endpoint.companionStatus === "stopped" ||
61
65
  input.endpoint.companionStatus === "error")) {
62
66
  return {
@@ -328,12 +332,16 @@ export async function runVisibleClient(options, runners = {}) {
328
332
  cliArgs: options.cliArgs,
329
333
  });
330
334
  }
335
+ export async function ensureCompanionStartWechatCredentials(adapter, ensureCredentials = ensureWechatCredentials) {
336
+ await ensureCredentials({
337
+ requireUserId: true,
338
+ validateExisting: true,
339
+ log: (message) => log(adapter, message),
340
+ });
341
+ }
331
342
  export async function runLocalCompanionStart(argv = process.argv.slice(2)) {
332
343
  const options = parseCliArgs(argv);
333
- migrateLegacyChannelFiles((message) => log(options.adapter, message));
334
- if (!fs.existsSync(CREDENTIALS_FILE)) {
335
- throw new Error(`Missing WeChat credentials. Run "bun run setup" first. (${CREDENTIALS_FILE})`);
336
- }
344
+ await ensureCompanionStartWechatCredentials(options.adapter);
337
345
  const ready = await ensureBridgeReady(options);
338
346
  if (!ready.shouldOpenVisibleClient) {
339
347
  return 0;
@@ -64,22 +64,38 @@ export function ensureWorkspaceChannelDir(cwd) {
64
64
  fs.mkdirSync(paths.workspaceDir, { recursive: true });
65
65
  return paths;
66
66
  }
67
+ function channelDataDirHasState() {
68
+ try {
69
+ if (!fs.existsSync(CHANNEL_DATA_DIR)) {
70
+ return false;
71
+ }
72
+ return fs.readdirSync(CHANNEL_DATA_DIR).length > 0;
73
+ }
74
+ catch {
75
+ return true;
76
+ }
77
+ }
78
+ export function shouldMigrateLegacyCredentials(params) {
79
+ return (params.legacyCredentialsExists &&
80
+ !params.credentialsExists &&
81
+ !params.channelDataDirHadState);
82
+ }
67
83
  export function migrateLegacyChannelFiles(log) {
68
84
  const migrated = [];
85
+ const channelDataDirHadState = channelDataDirHasState();
69
86
  if (!fs.existsSync(LEGACY_CREDENTIALS_FILE) &&
70
87
  !fs.existsSync(LEGACY_SYNC_BUF_FILE)) {
71
88
  return migrated;
72
89
  }
73
90
  ensureChannelDataDir();
74
- const copyIfNeeded = (fromPath, toPath, label) => {
75
- if (!fs.existsSync(fromPath) || fs.existsSync(toPath)) {
76
- return;
77
- }
78
- fs.copyFileSync(fromPath, toPath);
79
- migrated.push(label);
80
- };
81
- copyIfNeeded(LEGACY_CREDENTIALS_FILE, CREDENTIALS_FILE, "credentials");
82
- copyIfNeeded(LEGACY_SYNC_BUF_FILE, SYNC_BUF_FILE, "sync state");
91
+ if (shouldMigrateLegacyCredentials({
92
+ channelDataDirHadState,
93
+ credentialsExists: fs.existsSync(CREDENTIALS_FILE),
94
+ legacyCredentialsExists: fs.existsSync(LEGACY_CREDENTIALS_FILE),
95
+ })) {
96
+ fs.copyFileSync(LEGACY_CREDENTIALS_FILE, CREDENTIALS_FILE);
97
+ migrated.push("credentials");
98
+ }
83
99
  if (migrated.length && log) {
84
100
  log(`Migrated legacy ${migrated.join(" and ")} from ${LEGACY_CHANNEL_DATA_DIR} to ${CHANNEL_DATA_DIR}.`);
85
101
  }
@@ -2,13 +2,17 @@
2
2
  /**
3
3
  * CLI WeChat Bridge setup.
4
4
  *
5
- * Run this before starting CLI WeChat Bridge:
6
- * bun run setup
5
+ * Installed bridge commands run this automatically on first use.
6
+ * To force a relogin manually:
7
+ * wechat-setup
7
8
  */
9
+ import crypto from "node:crypto";
8
10
  import fs from "node:fs";
9
11
  import readline from "node:readline";
10
- import { BOT_TYPE, CREDENTIALS_FILE, DEFAULT_BASE_URL, ensureChannelDataDir, migrateLegacyChannelFiles, } from "./channel-config.js";
11
- function loadExistingCredentials() {
12
+ import { BOT_TYPE, CONTEXT_CACHE_FILE, CREDENTIALS_FILE, DEFAULT_BASE_URL, ensureChannelDataDir, migrateLegacyChannelFiles, SYNC_BUF_FILE, } from "./channel-config.js";
13
+ import { isWechatSyncSessionTimeout } from "./wechat-transport.js";
14
+ const CHANNEL_VERSION = "0.3.0";
15
+ export function loadExistingCredentials() {
12
16
  try {
13
17
  if (!fs.existsSync(CREDENTIALS_FILE)) {
14
18
  return null;
@@ -19,6 +23,65 @@ function loadExistingCredentials() {
19
23
  return null;
20
24
  }
21
25
  }
26
+ export function getWechatLoginRequiredReason(account, options = {}) {
27
+ if (!account) {
28
+ return "No saved WeChat credentials found.";
29
+ }
30
+ if (options.requireUserId && !account.userId) {
31
+ return "Saved WeChat credentials are missing userId.";
32
+ }
33
+ return null;
34
+ }
35
+ function randomWechatUin() {
36
+ const uint32 = crypto.randomBytes(4).readUInt32BE(0);
37
+ return Buffer.from(String(uint32), "utf-8").toString("base64");
38
+ }
39
+ export async function getStoredCredentialsInvalidReason(account, options = {}) {
40
+ const baseUrl = account.baseUrl || DEFAULT_BASE_URL;
41
+ const base = baseUrl.endsWith("/") ? baseUrl : `${baseUrl}/`;
42
+ const url = `${base}ilink/bot/getupdates`;
43
+ const body = JSON.stringify({
44
+ get_updates_buf: "",
45
+ base_info: { channel_version: CHANNEL_VERSION },
46
+ });
47
+ const controller = new AbortController();
48
+ const timer = setTimeout(() => controller.abort(), options.timeoutMs ?? 5_000);
49
+ try {
50
+ const res = await fetch(url, {
51
+ method: "POST",
52
+ headers: {
53
+ "Content-Type": "application/json",
54
+ "Content-Length": String(Buffer.byteLength(body, "utf-8")),
55
+ AuthorizationType: "ilink_bot_token",
56
+ Authorization: `Bearer ${account.token}`,
57
+ "X-WECHAT-UIN": randomWechatUin(),
58
+ },
59
+ body,
60
+ signal: controller.signal,
61
+ });
62
+ const text = await res.text();
63
+ if (res.status === 401 || res.status === 403) {
64
+ return "Saved WeChat credentials were rejected by the server.";
65
+ }
66
+ if (!res.ok) {
67
+ return null;
68
+ }
69
+ const response = JSON.parse(text);
70
+ if (isWechatSyncSessionTimeout(response)) {
71
+ return "Saved WeChat login has expired.";
72
+ }
73
+ }
74
+ catch (error) {
75
+ if (error instanceof Error && error.name === "AbortError") {
76
+ return null;
77
+ }
78
+ return null;
79
+ }
80
+ finally {
81
+ clearTimeout(timer);
82
+ }
83
+ return null;
84
+ }
22
85
  async function fetchQRCode(baseUrl) {
23
86
  const base = baseUrl.endsWith("/") ? baseUrl : `${baseUrl}/`;
24
87
  const url = `${base}ilink/bot/get_bot_qrcode?bot_type=${BOT_TYPE}`;
@@ -67,118 +130,154 @@ async function askYesNo(prompt) {
67
130
  rl.close();
68
131
  }
69
132
  }
70
- async function printQRCode(qrContent) {
133
+ async function printQRCode(qrContent, write) {
71
134
  try {
72
135
  const qrterm = await import("qrcode-terminal");
73
136
  await new Promise((resolve) => {
74
137
  qrterm.default.generate(qrContent, { small: true }, (qr) => {
75
- console.log(qr);
138
+ write(`${qr}\n`);
76
139
  resolve();
77
140
  });
78
141
  });
79
142
  }
80
143
  catch {
81
- console.log(`Open this QR code URL in a browser: ${qrContent}\n`);
144
+ write(`Open this QR code URL in a browser: ${qrContent}\n\n`);
82
145
  }
83
146
  }
84
- async function main() {
85
- migrateLegacyChannelFiles((message) => console.log(message));
86
- const existing = loadExistingCredentials();
87
- if (existing) {
88
- console.log(`Found saved account: ${existing.accountId}`);
89
- console.log(`Saved at: ${existing.savedAt}`);
90
- console.log(`Credentials file: ${CREDENTIALS_FILE}`);
91
- console.log();
92
- const shouldRelogin = await askYesNo("Log in again? (y/N) ");
93
- if (!shouldRelogin) {
94
- console.log("Keeping existing credentials.");
95
- return;
147
+ function saveCredentials(account) {
148
+ ensureChannelDataDir();
149
+ fs.writeFileSync(CREDENTIALS_FILE, JSON.stringify(account, null, 2), "utf-8");
150
+ for (const staleStateFile of [SYNC_BUF_FILE, CONTEXT_CACHE_FILE]) {
151
+ try {
152
+ fs.rmSync(staleStateFile, { force: true });
153
+ }
154
+ catch {
155
+ // Best effort cleanup.
96
156
  }
97
157
  }
98
- console.log("Fetching WeChat login QR code...\n");
99
- const qrResp = await fetchQRCode(DEFAULT_BASE_URL);
100
- await printQRCode(qrResp.qrcode_img_content);
101
- console.log("Scan the QR code in WeChat, then confirm on your phone.\n");
102
- const deadline = Date.now() + 480_000;
158
+ try {
159
+ fs.chmodSync(CREDENTIALS_FILE, 0o600);
160
+ }
161
+ catch {
162
+ // Best effort on Windows.
163
+ }
164
+ }
165
+ function printPostLoginHelp(log) {
166
+ log("This WeChat account is now authorized for the bridge.");
167
+ log("");
168
+ log("Start from any project directory with one of:");
169
+ log(" wechat-codex-start");
170
+ log(" wechat-claude-start");
171
+ log(" wechat-opencode-start");
172
+ log(" wechat-bridge-shell");
173
+ log("");
174
+ log("Manual two-terminal mode is also available:");
175
+ log(" wechat-bridge-codex + wechat-codex");
176
+ log(" wechat-bridge-claude + wechat-claude");
177
+ log(" wechat-bridge-opencode + wechat-opencode");
178
+ log("");
179
+ log("Run wechat-setup again any time you need to refresh the login.");
180
+ }
181
+ export async function runWechatLogin(options = {}) {
182
+ const baseUrl = options.baseUrl ?? DEFAULT_BASE_URL;
183
+ const log = options.log ?? ((message) => console.log(message));
184
+ const write = options.write ?? ((message) => process.stdout.write(message));
185
+ const timeoutMs = options.timeoutMs ?? 480_000;
186
+ const pollIntervalMs = options.pollIntervalMs ?? 1_000;
187
+ log("Fetching WeChat login QR code...\n");
188
+ const qrResp = await fetchQRCode(baseUrl);
189
+ await printQRCode(qrResp.qrcode_img_content, write);
190
+ log("Scan the QR code above with WeChat, then confirm the login on your phone.\n");
191
+ const deadline = Date.now() + timeoutMs;
103
192
  let scannedPrinted = false;
104
193
  while (Date.now() < deadline) {
105
- const status = await pollQRStatus(DEFAULT_BASE_URL, qrResp.qrcode);
194
+ const status = await pollQRStatus(baseUrl, qrResp.qrcode);
106
195
  switch (status.status) {
107
196
  case "wait":
108
- process.stdout.write(".");
197
+ write(".");
109
198
  break;
110
199
  case "scaned":
111
200
  if (!scannedPrinted) {
112
- console.log("\nQR code scanned. Confirm the login in WeChat...");
201
+ log("\nQR code scanned. Confirm the login in WeChat...");
113
202
  scannedPrinted = true;
114
203
  }
115
204
  break;
116
205
  case "expired":
117
- console.log("\nThe QR code expired. Run setup again.");
118
- process.exit(1);
119
- break;
206
+ throw new Error("The QR code expired. Run setup again.");
120
207
  case "confirmed": {
121
208
  if (!status.ilink_bot_id || !status.bot_token) {
122
- console.error("\nLogin failed: missing bot credentials from server.");
123
- process.exit(1);
209
+ throw new Error("Login failed: missing bot credentials from server.");
210
+ }
211
+ if (options.requireUserId && !status.ilink_user_id) {
212
+ throw new Error("Login failed: missing WeChat userId from server.");
124
213
  }
125
214
  const account = {
126
215
  token: status.bot_token,
127
- baseUrl: status.baseurl || DEFAULT_BASE_URL,
216
+ baseUrl: status.baseurl || baseUrl,
128
217
  accountId: status.ilink_bot_id,
129
218
  userId: status.ilink_user_id,
130
219
  savedAt: new Date().toISOString(),
131
220
  };
132
- ensureChannelDataDir();
133
- fs.writeFileSync(CREDENTIALS_FILE, JSON.stringify(account, null, 2), "utf-8");
134
- try {
135
- fs.chmodSync(CREDENTIALS_FILE, 0o600);
136
- }
137
- catch {
138
- // Best effort on Windows.
139
- }
140
- console.log("\nWeChat login completed.");
141
- console.log(`Account ID: ${account.accountId}`);
142
- console.log(`User ID: ${account.userId ?? "(unknown)"}`);
143
- console.log(`Credentials saved to: ${CREDENTIALS_FILE}`);
144
- console.log();
145
- console.log("No /pair step is required.");
146
- console.log("The logged-in WeChat account above becomes the only authorized bridge owner.");
147
- console.log();
148
- console.log("After installing the package globally (for example: npm install -g . or npm link),");
149
- console.log("start the WeChat bridge from any directory with:");
150
- console.log(" wechat-bridge-codex");
151
- console.log(" wechat-codex # start this in a second terminal in the same directory");
152
- console.log(" wechat-bridge-opencode");
153
- console.log(" wechat-opencode # start this in a second terminal in the same directory");
154
- console.log(" wechat-bridge-claude");
155
- console.log(" wechat-claude # start this in a second terminal in the same directory");
156
- console.log(" wechat-bridge-shell");
157
- console.log(" On Linux/macOS, wechat-bridge-shell defaults to pwsh, then bash, zsh, or sh.");
158
- console.log(" Shell mode is a headless remote executor for non-interactive commands and scripts.");
159
- console.log();
160
- console.log("Repo-local development entrypoints are still available:");
161
- console.log(" bun run bridge:codex");
162
- console.log(" bun run codex:panel");
163
- console.log(" bun run bridge:opencode");
164
- console.log(" bun run opencode:panel");
165
- console.log(" bun run opencode:start");
166
- console.log(" bun run bridge:claude");
167
- console.log(" bun run claude:companion");
168
- console.log(" bun run bridge:shell");
169
- console.log();
170
- console.log("Legacy MCP server entrypoint:");
171
- console.log(" bun run start");
172
- return;
221
+ saveCredentials(account);
222
+ log("\nWeChat login completed.");
223
+ log(`Account ID: ${account.accountId}`);
224
+ log(`User ID: ${account.userId ?? "(unknown)"}`);
225
+ log(`Credentials saved to: ${CREDENTIALS_FILE}`);
226
+ log("");
227
+ return account;
228
+ }
229
+ }
230
+ await new Promise((resolve) => setTimeout(resolve, pollIntervalMs));
231
+ }
232
+ throw new Error("Login timed out. Run setup again.");
233
+ }
234
+ export async function ensureWechatCredentials(options = {}) {
235
+ const log = options.log ?? ((message) => console.log(message));
236
+ migrateLegacyChannelFiles(log);
237
+ const existing = loadExistingCredentials();
238
+ const loginReason = getWechatLoginRequiredReason(existing, {
239
+ requireUserId: options.requireUserId,
240
+ });
241
+ if (!loginReason) {
242
+ const account = existing;
243
+ if (options.validateExisting) {
244
+ const invalidReason = await getStoredCredentialsInvalidReason(account, {
245
+ timeoutMs: options.validationTimeoutMs,
246
+ });
247
+ if (invalidReason) {
248
+ log(`${invalidReason} Starting WeChat login...`);
249
+ const login = options.login ?? (() => runWechatLogin(options));
250
+ return login();
173
251
  }
174
252
  }
175
- await new Promise((resolve) => setTimeout(resolve, 1000));
253
+ return account;
254
+ }
255
+ log(`${loginReason} Starting WeChat login...`);
256
+ const login = options.login ?? (() => runWechatLogin(options));
257
+ return login();
258
+ }
259
+ async function main() {
260
+ migrateLegacyChannelFiles((message) => console.log(message));
261
+ const existing = loadExistingCredentials();
262
+ if (existing) {
263
+ console.log(`Found saved account: ${existing.accountId}`);
264
+ console.log(`Saved at: ${existing.savedAt}`);
265
+ console.log(`Credentials file: ${CREDENTIALS_FILE}`);
266
+ console.log();
267
+ const shouldRelogin = await askYesNo("Log in again? (y/N) ");
268
+ if (!shouldRelogin) {
269
+ console.log("Keeping existing credentials.");
270
+ return;
271
+ }
176
272
  }
177
- console.log("\nLogin timed out. Run setup again.");
178
- process.exit(1);
273
+ await runWechatLogin({ requireUserId: true });
274
+ printPostLoginHelp((message) => console.log(message));
275
+ }
276
+ const isDirectRun = Boolean(import.meta.main);
277
+ if (isDirectRun) {
278
+ main().catch((err) => {
279
+ const message = err instanceof Error ? err.message : String(err);
280
+ console.error(`Error: ${message}`);
281
+ process.exit(1);
282
+ });
179
283
  }
180
- main().catch((err) => {
181
- const message = err instanceof Error ? err.message : String(err);
182
- console.error(`Error: ${message}`);
183
- process.exit(1);
184
- });
@@ -12,6 +12,7 @@ const SEND_TIMEOUT_MS = 15_000;
12
12
  const CDN_MAX_RETRIES = 3;
13
13
  const ERROR_CAUSE_DEPTH_LIMIT = 4;
14
14
  const INBOUND_MESSAGE_CLAIM_TTL_MS = 10 * 60 * 1000;
15
+ const SYNC_SESSION_TIMEOUT_ERRCODE = -14;
15
16
  const MSG_TYPE_USER = 1;
16
17
  const MSG_TYPE_BOT = 2;
17
18
  const MSG_ITEM_TEXT = 1;
@@ -24,6 +25,10 @@ const UPLOAD_MEDIA_TYPE_IMAGE = 1;
24
25
  const UPLOAD_MEDIA_TYPE_VIDEO = 2;
25
26
  const UPLOAD_MEDIA_TYPE_FILE = 3;
26
27
  const UPLOAD_MEDIA_TYPE_VOICE = 4;
28
+ export function isWechatSyncSessionTimeout(response) {
29
+ return (response.errcode === SYNC_SESSION_TIMEOUT_ERRCODE &&
30
+ /session timeout/i.test(response.errmsg ?? ""));
31
+ }
27
32
  const DEFAULT_MEDIA_UPLOAD_LIMIT_MB = {
28
33
  image: 20,
29
34
  file: 50,
@@ -199,6 +204,10 @@ export function describeWechatTransportError(error) {
199
204
  }
200
205
  export function classifyWechatTransportError(error) {
201
206
  if (error instanceof Error) {
207
+ if (/WeChat session timed out/i.test(error.message) ||
208
+ /errcode=-14\b.*session timeout/i.test(error.message)) {
209
+ return { kind: "auth", retryable: false };
210
+ }
202
211
  if (error.name === "AbortError") {
203
212
  return { kind: "timeout", retryable: true };
204
213
  }
@@ -539,7 +548,15 @@ export class WeChatTransport {
539
548
  async pollMessages(options = {}) {
540
549
  const timeoutMs = options.timeoutMs ?? DEFAULT_LONG_POLL_TIMEOUT_MS;
541
550
  const account = this.requireAccount();
542
- const response = await this.getUpdates(account, timeoutMs);
551
+ let response = await this.getUpdates(account, timeoutMs);
552
+ if (isWechatSyncSessionTimeout(response) && this.syncBuffer) {
553
+ this.logger.log("WeChat sync session timed out. Clearing local sync cursor and retrying once.");
554
+ this.clearSyncBuffer();
555
+ response = await this.getUpdates(account, timeoutMs);
556
+ }
557
+ if (isWechatSyncSessionTimeout(response)) {
558
+ throw new Error('WeChat session timed out. Run "wechat-setup" to log in again.');
559
+ }
543
560
  const isError = (response.ret !== undefined && response.ret !== 0) ||
544
561
  (response.errcode !== undefined && response.errcode !== 0);
545
562
  if (isError) {
@@ -689,7 +706,7 @@ export class WeChatTransport {
689
706
  requireAccount() {
690
707
  const account = this.getCredentials();
691
708
  if (!account) {
692
- throw new Error(`No saved WeChat credentials found. Run "bun run setup" first. Expected file: ${CREDENTIALS_FILE}`);
709
+ throw new Error(`No saved WeChat credentials found. Start a bridge command in a terminal to log in automatically, or run "wechat-setup". Expected file: ${CREDENTIALS_FILE}`);
693
710
  }
694
711
  return account;
695
712
  }
package/package.json CHANGED
@@ -1,14 +1,14 @@
1
1
  {
2
2
  "name": "@unlinearity/cli-wechat-bridge",
3
- "version": "1.0.1",
3
+ "version": "1.0.2",
4
4
  "description": "CLI WeChat Bridge for Codex, Claude Code, OpenCode, and PowerShell over WeChat",
5
5
  "type": "module",
6
- "files": [
7
- "bin/",
8
- "dist/",
9
- "README.md",
10
- "LICENSE.txt"
11
- ],
6
+ "files": [
7
+ "bin/",
8
+ "dist/",
9
+ "README.md",
10
+ "LICENSE.txt"
11
+ ],
12
12
  "keywords": [
13
13
  "wechat",
14
14
  "cli",
@@ -43,13 +43,14 @@
43
43
  "wechat-bridge-opencode": "bin/wechat-bridge-opencode.mjs",
44
44
  "wechat-opencode": "bin/wechat-opencode.mjs",
45
45
  "wechat-opencode-start": "bin/wechat-opencode-start.mjs",
46
+ "wechat-setup": "bin/wechat-setup.mjs",
46
47
  "wechat-check-update": "bin/wechat-check-update.mjs"
47
48
  },
48
- "scripts": {
49
- "clean": "node -e \"require('node:fs').rmSync('dist', { recursive: true, force: true })\"",
50
- "build": "npm run clean && tsc -p tsconfig.build.json",
51
- "prepack": "npm run build",
52
- "setup": "node --no-warnings --experimental-strip-types src/wechat/setup.ts",
49
+ "scripts": {
50
+ "clean": "node -e \"require('node:fs').rmSync('dist', { recursive: true, force: true })\"",
51
+ "build": "npm run clean && tsc -p tsconfig.build.json",
52
+ "prepack": "npm run build",
53
+ "setup": "node --no-warnings --experimental-strip-types src/wechat/setup.ts",
53
54
  "start": "node --no-warnings --experimental-strip-types src/wechat/wechat-channel.ts",
54
55
  "check": "node --no-warnings --experimental-strip-types src/wechat/wechat-channel.ts --check",
55
56
  "bridge": "node --no-warnings --experimental-strip-types src/bridge/wechat-bridge.ts",
@@ -82,8 +83,8 @@
82
83
  "@types/bun": "latest",
83
84
  "typescript": "^6.0.2"
84
85
  },
85
- "engines": {
86
- "bun": ">=1.0.0",
87
- "node": ">=24.0.0"
88
- }
89
- }
86
+ "engines": {
87
+ "bun": ">=1.0.0",
88
+ "node": ">=24.0.0"
89
+ }
90
+ }