@unlinearity/cli-wechat-bridge 1.1.6 → 1.2.1
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/LICENSE.txt +661 -661
- package/README.md +468 -475
- package/bin/_run-entry.mjs +29 -6
- package/bin/wechat-check-update.mjs +6 -1
- package/bin/wechat-claude.mjs +6 -1
- package/bin/wechat-codex.mjs +6 -1
- package/bin/wechat-daemon.mjs +6 -1
- package/bin/wechat-opencode.mjs +6 -1
- package/bin/wechat-pi.mjs +6 -1
- package/bin/wechat-setup.mjs +6 -1
- package/bin/wecom-check-update.mjs +10 -0
- package/bin/wecom-claude.mjs +10 -0
- package/bin/wecom-codex.mjs +10 -0
- package/bin/wecom-daemon.mjs +10 -0
- package/bin/wecom-opencode.mjs +10 -0
- package/bin/wecom-pi.mjs +10 -0
- package/bin/wecom-setup.mjs +10 -0
- package/dist/bridge/adapter-control.js +58 -0
- package/dist/bridge/bridge-adapters.claude.js +161 -4
- package/dist/bridge/bridge-adapters.codex.js +206 -14
- package/dist/bridge/bridge-adapters.core.js +25 -1
- package/dist/bridge/bridge-adapters.opencode.js +298 -20
- package/dist/bridge/bridge-adapters.pi.js +45 -0
- package/dist/bridge/bridge-adapters.shared.js +6 -0
- package/dist/bridge/bridge-controller.js +6 -0
- package/dist/bridge/bridge-final-reply.js +6 -0
- package/dist/bridge/bridge-process-reaper.js +41 -2
- package/dist/bridge/bridge-state.js +16 -3
- package/dist/bridge/bridge-utils.js +6 -31
- package/dist/bridge/claude-hook.js +6 -0
- package/dist/bridge/claude-native-controls.js +145 -0
- package/dist/bridge/codex-visible-thread-proxy.js +140 -0
- package/dist/bridge/native-terminal-control.js +271 -0
- package/dist/bridge/opencode-v2-compat.js +450 -0
- package/dist/bridge/wechat-bridge.js +380 -171
- package/dist/bridge/wechat-forwarding.js +6 -0
- package/dist/bridge/wechat-outbound-queue.js +6 -0
- package/dist/channels/wechat/bridge-final-reply.js +13 -22
- package/dist/channels/wechat/index.js +6 -0
- package/dist/channels/wechat/wechat-driver.js +191 -0
- package/dist/channels/wechat/wechat-forwarding.js +18 -0
- package/dist/channels/wechat/wechat-outbound-queue.js +49 -3
- package/dist/channels/wecom/bridge-final-reply.js +32 -0
- package/dist/channels/wecom/setup-cli.js +12 -0
- package/dist/channels/wecom/setup.js +182 -0
- package/dist/channels/wecom/wecom-channel-port.js +45 -0
- package/dist/channels/wecom/wecom-config.js +72 -0
- package/dist/channels/wecom/wecom-driver.js +106 -0
- package/dist/channels/wecom/wecom-media.js +147 -0
- package/dist/channels/wecom/wecom-message.js +149 -0
- package/dist/channels/wecom/wecom-transport.js +314 -0
- package/dist/commands/check-update.js +6 -0
- package/dist/companion/codex-remote-client.js +30 -3
- package/dist/companion/codex-visible-client-link.js +107 -0
- package/dist/companion/local-companion-link.js +6 -0
- package/dist/companion/local-companion-start.js +62 -11
- package/dist/companion/local-companion.js +25 -1
- package/dist/companion/opencode-tui-bridge-plugin.js +6 -0
- package/dist/companion/opencode-v2-tui-bridge-plugin.js +93 -0
- package/dist/companion/pi-tui-bridge-extension.js +62 -3
- package/dist/core/bridge-control.js +10 -0
- package/dist/core/bridge-defer.js +25 -0
- package/dist/core/channel-driver.js +1 -0
- package/dist/core/channel-types.js +3 -1
- package/dist/core/conversation-routing.js +58 -0
- package/dist/core/direct-run.js +23 -0
- package/dist/core/inbound-message-claims.js +67 -0
- package/dist/core/outbound-target.js +32 -0
- package/dist/core/text-utils.js +6 -0
- package/dist/core/turn-coordinator.js +85 -0
- package/dist/daemon/daemon-link.js +9 -0
- package/dist/daemon/emoji-bindings.js +6 -0
- package/dist/daemon/wechat-daemon.js +675 -122
- package/dist/i18n/index.js +6 -0
- package/dist/i18n/messages-en.js +9 -1
- package/dist/i18n/messages-zh.js +9 -1
- package/dist/runtime/create-runtime-host.js +6 -0
- package/dist/runtime/legacy-adapter-runtime.js +15 -0
- package/dist/runtime/runtime-types.js +3 -0
- package/dist/utils/atomic-file.js +6 -0
- package/dist/utils/doctor.js +36 -10
- package/dist/utils/version-checker.js +17 -11
- package/dist/wechat/channel-config.js +6 -0
- package/dist/wechat/setup.js +119 -13
- package/dist/wechat/wechat-channel.js +6 -0
- package/dist/wechat/wechat-transport.js +169 -58
- package/package.json +16 -4
- package/scripts/ensure-node-pty-permissions.mjs +73 -73
|
@@ -1,3 +1,9 @@
|
|
|
1
|
+
// SPDX-License-Identifier: AGPL-3.0-or-later
|
|
2
|
+
// Copyright (c) 2026 UNLINEARITY <unlinearity@gmail.com>
|
|
3
|
+
// Source: https://github.com/UNLINEARITY/CLI-WeChat-Bridge
|
|
4
|
+
// This file is part of CLI-WeChat-Bridge. Modifications and derivative works
|
|
5
|
+
// must be released under AGPL-3.0-or-later with full source code; see
|
|
6
|
+
// LICENSE.txt. Network services built on it must offer source to users.
|
|
1
7
|
import crypto from "node:crypto";
|
|
2
8
|
import { createCipheriv, createDecipheriv } from "node:crypto";
|
|
3
9
|
import fs from "node:fs";
|
|
@@ -332,6 +338,15 @@ async function apiFetch(params) {
|
|
|
332
338
|
const url = new URL(params.endpoint, base).toString();
|
|
333
339
|
const controller = new AbortController();
|
|
334
340
|
const timer = setTimeout(() => controller.abort(), params.timeoutMs);
|
|
341
|
+
const abortFromCaller = () => controller.abort(params.signal?.reason);
|
|
342
|
+
if (params.signal) {
|
|
343
|
+
if (params.signal.aborted) {
|
|
344
|
+
abortFromCaller();
|
|
345
|
+
}
|
|
346
|
+
else {
|
|
347
|
+
params.signal.addEventListener("abort", abortFromCaller, { once: true });
|
|
348
|
+
}
|
|
349
|
+
}
|
|
335
350
|
try {
|
|
336
351
|
const res = await fetch(url, {
|
|
337
352
|
method: "POST",
|
|
@@ -353,6 +368,9 @@ async function apiFetch(params) {
|
|
|
353
368
|
clearTimeout(timer);
|
|
354
369
|
throw err;
|
|
355
370
|
}
|
|
371
|
+
finally {
|
|
372
|
+
params.signal?.removeEventListener("abort", abortFromCaller);
|
|
373
|
+
}
|
|
356
374
|
}
|
|
357
375
|
function encryptAesEcb(plaintext, key) {
|
|
358
376
|
const cipher = createCipheriv("aes-128-ecb", key, null);
|
|
@@ -806,12 +824,15 @@ function buildInboundAttachmentFilePath(params) {
|
|
|
806
824
|
const uniquePrefix = `${timestamp.replace(/[:.]/g, "-")}-${crypto.randomBytes(4).toString("hex")}`;
|
|
807
825
|
return path.join(directory, `${uniquePrefix}-${safeFileName}`);
|
|
808
826
|
}
|
|
827
|
+
const WECHAT_TYPING_TIMEOUT_MS = 8_000;
|
|
809
828
|
export class WeChatTransport {
|
|
810
829
|
logger;
|
|
811
830
|
recentMessageKeys = new Set();
|
|
812
831
|
recentMessageOrder = [];
|
|
813
832
|
contextTokenCache;
|
|
814
833
|
syncBuffer = "";
|
|
834
|
+
stopped = false;
|
|
835
|
+
activePollControllers = new Set();
|
|
815
836
|
constructor(logger) {
|
|
816
837
|
this.logger = logger;
|
|
817
838
|
migrateLegacyChannelFiles((message) => this.logger.log(message));
|
|
@@ -855,73 +876,96 @@ export class WeChatTransport {
|
|
|
855
876
|
? "Reset sync state and cleared cached context tokens."
|
|
856
877
|
: "Reset sync state.";
|
|
857
878
|
}
|
|
879
|
+
stop() {
|
|
880
|
+
if (this.stopped) {
|
|
881
|
+
return;
|
|
882
|
+
}
|
|
883
|
+
this.stopped = true;
|
|
884
|
+
for (const controller of this.activePollControllers) {
|
|
885
|
+
controller.abort();
|
|
886
|
+
}
|
|
887
|
+
this.activePollControllers.clear();
|
|
888
|
+
}
|
|
858
889
|
async pollMessages(options = {}) {
|
|
890
|
+
if (this.stopped) {
|
|
891
|
+
return { messages: [], ignoredBacklogCount: 0 };
|
|
892
|
+
}
|
|
859
893
|
const timeoutMs = options.timeoutMs ?? DEFAULT_LONG_POLL_TIMEOUT_MS;
|
|
860
894
|
const account = this.requireAccount();
|
|
861
|
-
|
|
862
|
-
|
|
863
|
-
|
|
864
|
-
this.
|
|
865
|
-
response
|
|
866
|
-
|
|
867
|
-
|
|
868
|
-
|
|
869
|
-
}
|
|
870
|
-
const isError = (response.ret !== undefined && response.ret !== 0) ||
|
|
871
|
-
(response.errcode !== undefined && response.errcode !== 0);
|
|
872
|
-
if (isError) {
|
|
873
|
-
throw new Error(`getUpdates failed: ret=${response.ret} errcode=${response.errcode} errmsg=${response.errmsg ?? ""}`);
|
|
874
|
-
}
|
|
875
|
-
if (response.get_updates_buf) {
|
|
876
|
-
this.syncBuffer = response.get_updates_buf;
|
|
877
|
-
this.saveSyncBuffer(this.syncBuffer);
|
|
878
|
-
}
|
|
879
|
-
const messages = [];
|
|
880
|
-
let ignoredBacklogCount = 0;
|
|
881
|
-
for (const rawMessage of response.msgs ?? []) {
|
|
882
|
-
if (rawMessage.message_type !== MSG_TYPE_USER) {
|
|
883
|
-
continue;
|
|
895
|
+
const controller = new AbortController();
|
|
896
|
+
this.activePollControllers.add(controller);
|
|
897
|
+
try {
|
|
898
|
+
let response = await this.getUpdates(account, timeoutMs, controller.signal);
|
|
899
|
+
if (isWechatSyncSessionTimeout(response) && this.syncBuffer && !this.stopped) {
|
|
900
|
+
this.logger.log("WeChat sync session timed out. Clearing local sync cursor and retrying once.");
|
|
901
|
+
this.clearSyncBuffer();
|
|
902
|
+
response = await this.getUpdates(account, timeoutMs, controller.signal);
|
|
884
903
|
}
|
|
885
|
-
|
|
886
|
-
|
|
887
|
-
continue;
|
|
904
|
+
if (this.stopped) {
|
|
905
|
+
return { messages: [], ignoredBacklogCount: 0 };
|
|
888
906
|
}
|
|
889
|
-
|
|
890
|
-
|
|
891
|
-
this.cacheContextToken(senderId, rawMessage.context_token);
|
|
907
|
+
if (isWechatSyncSessionTimeout(response)) {
|
|
908
|
+
throw new Error('WeChat session timed out. Run "wechat-setup" to log in again.');
|
|
892
909
|
}
|
|
893
|
-
|
|
894
|
-
|
|
895
|
-
|
|
896
|
-
|
|
897
|
-
if (typeof options.minCreatedAtMs === "number" &&
|
|
898
|
-
Number.isFinite(createdAtMs) &&
|
|
899
|
-
createdAtMs > 0 &&
|
|
900
|
-
createdAtMs < options.minCreatedAtMs) {
|
|
901
|
-
ignoredBacklogCount += 1;
|
|
902
|
-
continue;
|
|
910
|
+
const isError = (response.ret !== undefined && response.ret !== 0) ||
|
|
911
|
+
(response.errcode !== undefined && response.errcode !== 0);
|
|
912
|
+
if (isError) {
|
|
913
|
+
throw new Error(`getUpdates failed: ret=${response.ret} errcode=${response.errcode} errmsg=${response.errmsg ?? ""}`);
|
|
903
914
|
}
|
|
904
|
-
|
|
905
|
-
|
|
906
|
-
|
|
915
|
+
if (response.get_updates_buf) {
|
|
916
|
+
this.syncBuffer = response.get_updates_buf;
|
|
917
|
+
this.saveSyncBuffer(this.syncBuffer);
|
|
907
918
|
}
|
|
908
|
-
|
|
909
|
-
|
|
919
|
+
const messages = [];
|
|
920
|
+
let ignoredBacklogCount = 0;
|
|
921
|
+
for (const rawMessage of response.msgs ?? []) {
|
|
922
|
+
if (rawMessage.message_type !== MSG_TYPE_USER) {
|
|
923
|
+
continue;
|
|
924
|
+
}
|
|
925
|
+
const extracted = extractInboundMessageContent(rawMessage);
|
|
926
|
+
if (!extracted.text && extracted.attachments.length === 0) {
|
|
927
|
+
continue;
|
|
928
|
+
}
|
|
929
|
+
const senderId = rawMessage.from_user_id ?? "unknown";
|
|
930
|
+
if (rawMessage.context_token) {
|
|
931
|
+
this.cacheContextToken(senderId, rawMessage.context_token);
|
|
932
|
+
}
|
|
933
|
+
// Filter pre-start backlog BEFORE claiming/remembering the message so a
|
|
934
|
+
// skipped message is not permanently locked away from other processes.
|
|
935
|
+
// A missing create_time_ms is treated as fresh rather than dropped.
|
|
936
|
+
const createdAtMs = rawMessage.create_time_ms ?? 0;
|
|
937
|
+
if (typeof options.minCreatedAtMs === "number" &&
|
|
938
|
+
Number.isFinite(createdAtMs) &&
|
|
939
|
+
createdAtMs > 0 &&
|
|
940
|
+
createdAtMs < options.minCreatedAtMs) {
|
|
941
|
+
ignoredBacklogCount += 1;
|
|
942
|
+
continue;
|
|
943
|
+
}
|
|
944
|
+
const messageKey = buildMessageKey(rawMessage);
|
|
945
|
+
if (!this.rememberMessage(messageKey)) {
|
|
946
|
+
continue;
|
|
947
|
+
}
|
|
948
|
+
if (!tryClaimInboundMessage(buildScopedMessageClaimKey(account.accountId, messageKey))) {
|
|
949
|
+
continue;
|
|
950
|
+
}
|
|
951
|
+
const { attachments, failureLines } = await this.downloadInboundAttachments(extracted.attachments, rawMessage);
|
|
952
|
+
const text = appendInboundAttachmentFailureText(extracted.text, failureLines);
|
|
953
|
+
messages.push({
|
|
954
|
+
senderId,
|
|
955
|
+
sender: normalizeSender(senderId),
|
|
956
|
+
sessionId: rawMessage.session_id ?? "",
|
|
957
|
+
text,
|
|
958
|
+
attachments,
|
|
959
|
+
contextToken: rawMessage.context_token,
|
|
960
|
+
createdAt: formatTimestamp(rawMessage.create_time_ms),
|
|
961
|
+
createdAtMs,
|
|
962
|
+
});
|
|
910
963
|
}
|
|
911
|
-
|
|
912
|
-
|
|
913
|
-
|
|
914
|
-
|
|
915
|
-
sender: normalizeSender(senderId),
|
|
916
|
-
sessionId: rawMessage.session_id ?? "",
|
|
917
|
-
text,
|
|
918
|
-
attachments,
|
|
919
|
-
contextToken: rawMessage.context_token,
|
|
920
|
-
createdAt: formatTimestamp(rawMessage.create_time_ms),
|
|
921
|
-
createdAtMs,
|
|
922
|
-
});
|
|
964
|
+
return { messages, ignoredBacklogCount };
|
|
965
|
+
}
|
|
966
|
+
finally {
|
|
967
|
+
this.activePollControllers.delete(controller);
|
|
923
968
|
}
|
|
924
|
-
return { messages, ignoredBacklogCount };
|
|
925
969
|
}
|
|
926
970
|
async downloadInboundAttachments(descriptors, rawMessage) {
|
|
927
971
|
const attachments = [];
|
|
@@ -963,6 +1007,72 @@ export class WeChatTransport {
|
|
|
963
1007
|
}
|
|
964
1008
|
return { attachments, failureLines };
|
|
965
1009
|
}
|
|
1010
|
+
/** Typing status values for {@link sendTyping}. 1 = typing, 2 = cancel. */
|
|
1011
|
+
static TYPING_STATUS = { TYPING: 1, CANCEL: 2 };
|
|
1012
|
+
/**
|
|
1013
|
+
* Fetch the per-user typing ticket from ilink/bot/getconfig.
|
|
1014
|
+
* Returns "" when the endpoint fails or reports no ticket.
|
|
1015
|
+
*/
|
|
1016
|
+
async fetchTypingTicket(recipientId) {
|
|
1017
|
+
const account = this.requireAccount();
|
|
1018
|
+
const resolvedRecipientId = recipientId?.trim() ||
|
|
1019
|
+
[...this.contextTokenCache.keys()].at(-1);
|
|
1020
|
+
if (!resolvedRecipientId) {
|
|
1021
|
+
return "";
|
|
1022
|
+
}
|
|
1023
|
+
try {
|
|
1024
|
+
const raw = await apiFetch({
|
|
1025
|
+
baseUrl: account.baseUrl,
|
|
1026
|
+
endpoint: "ilink/bot/getconfig",
|
|
1027
|
+
body: JSON.stringify({
|
|
1028
|
+
ilink_user_id: resolvedRecipientId,
|
|
1029
|
+
...(this.contextTokenCache.has(resolvedRecipientId)
|
|
1030
|
+
? { context_token: this.contextTokenCache.get(resolvedRecipientId) }
|
|
1031
|
+
: {}),
|
|
1032
|
+
}),
|
|
1033
|
+
token: account.token,
|
|
1034
|
+
timeoutMs: WECHAT_TYPING_TIMEOUT_MS,
|
|
1035
|
+
});
|
|
1036
|
+
const resp = JSON.parse(raw);
|
|
1037
|
+
return resp.ret === 0 ? (resp.typing_ticket ?? "") : "";
|
|
1038
|
+
}
|
|
1039
|
+
catch {
|
|
1040
|
+
return "";
|
|
1041
|
+
}
|
|
1042
|
+
}
|
|
1043
|
+
/**
|
|
1044
|
+
* Send or cancel a "typing…" indicator. Failures are logged only; the
|
|
1045
|
+
* indicator is best-effort and never blocks message flow.
|
|
1046
|
+
*/
|
|
1047
|
+
async sendTyping(recipientId, typingTicket, status) {
|
|
1048
|
+
if (!typingTicket) {
|
|
1049
|
+
return false;
|
|
1050
|
+
}
|
|
1051
|
+
const account = this.requireAccount();
|
|
1052
|
+
try {
|
|
1053
|
+
const raw = await apiFetch({
|
|
1054
|
+
baseUrl: account.baseUrl,
|
|
1055
|
+
endpoint: "ilink/bot/sendtyping",
|
|
1056
|
+
body: JSON.stringify({
|
|
1057
|
+
ilink_user_id: recipientId,
|
|
1058
|
+
typing_ticket: typingTicket,
|
|
1059
|
+
status,
|
|
1060
|
+
}),
|
|
1061
|
+
token: account.token,
|
|
1062
|
+
timeoutMs: WECHAT_TYPING_TIMEOUT_MS,
|
|
1063
|
+
});
|
|
1064
|
+
const resp = JSON.parse(raw);
|
|
1065
|
+
if (resp.ret !== undefined && resp.ret !== 0) {
|
|
1066
|
+
this.logger.logError(`sendTyping ret=${resp.ret} errmsg=${resp.errmsg ?? "(none)"}`);
|
|
1067
|
+
return false;
|
|
1068
|
+
}
|
|
1069
|
+
return true;
|
|
1070
|
+
}
|
|
1071
|
+
catch (error) {
|
|
1072
|
+
this.logger.logError(`sendTyping failed: ${error instanceof Error ? error.message : String(error)}`);
|
|
1073
|
+
return false;
|
|
1074
|
+
}
|
|
1075
|
+
}
|
|
966
1076
|
async sendText(senderId, text) {
|
|
967
1077
|
const trimmed = text.trim();
|
|
968
1078
|
if (!trimmed) {
|
|
@@ -1166,7 +1276,7 @@ export class WeChatTransport {
|
|
|
1166
1276
|
}
|
|
1167
1277
|
return stat;
|
|
1168
1278
|
}
|
|
1169
|
-
async getUpdates(account, timeoutMs) {
|
|
1279
|
+
async getUpdates(account, timeoutMs, signal) {
|
|
1170
1280
|
try {
|
|
1171
1281
|
const raw = await apiFetch({
|
|
1172
1282
|
baseUrl: account.baseUrl,
|
|
@@ -1177,6 +1287,7 @@ export class WeChatTransport {
|
|
|
1177
1287
|
}),
|
|
1178
1288
|
token: account.token,
|
|
1179
1289
|
timeoutMs,
|
|
1290
|
+
signal,
|
|
1180
1291
|
});
|
|
1181
1292
|
return JSON.parse(raw);
|
|
1182
1293
|
}
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@unlinearity/cli-wechat-bridge",
|
|
3
|
-
"version": "1.1
|
|
4
|
-
"description": "CLI
|
|
3
|
+
"version": "1.2.1",
|
|
4
|
+
"description": "CLI bridge for Codex, Claude Code, OpenCode, and Pi over WeChat and WeCom",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"files": [
|
|
7
7
|
"bin/",
|
|
@@ -12,6 +12,7 @@
|
|
|
12
12
|
],
|
|
13
13
|
"keywords": [
|
|
14
14
|
"wechat",
|
|
15
|
+
"wecom",
|
|
15
16
|
"cli",
|
|
16
17
|
"bridge",
|
|
17
18
|
"codex",
|
|
@@ -40,13 +41,24 @@
|
|
|
40
41
|
"wechat-pi": "bin/wechat-pi.mjs",
|
|
41
42
|
"wechat-daemon": "bin/wechat-daemon.mjs",
|
|
42
43
|
"wechat-setup": "bin/wechat-setup.mjs",
|
|
43
|
-
"wechat-check-update": "bin/wechat-check-update.mjs"
|
|
44
|
+
"wechat-check-update": "bin/wechat-check-update.mjs",
|
|
45
|
+
"wecom-codex": "bin/wecom-codex.mjs",
|
|
46
|
+
"wecom-claude": "bin/wecom-claude.mjs",
|
|
47
|
+
"wecom-opencode": "bin/wecom-opencode.mjs",
|
|
48
|
+
"wecom-pi": "bin/wecom-pi.mjs",
|
|
49
|
+
"wecom-daemon": "bin/wecom-daemon.mjs",
|
|
50
|
+
"wecom-setup": "bin/wecom-setup.mjs",
|
|
51
|
+
"wecom-check-update": "bin/wecom-check-update.mjs"
|
|
44
52
|
},
|
|
45
53
|
"dependencies": {
|
|
46
54
|
"@modelcontextprotocol/sdk": "^1.30.0",
|
|
47
55
|
"@opencode-ai/sdk": "^1.18.0",
|
|
56
|
+
"@opencode/client": "^2.0.18",
|
|
57
|
+
"@wecom/aibot-node-sdk": "1.0.7",
|
|
58
|
+
"@xterm/headless": "^6.0.0",
|
|
48
59
|
"node-pty": "^1.1.0",
|
|
49
|
-
"qrcode-terminal": "^0.12.0"
|
|
60
|
+
"qrcode-terminal": "^0.12.0",
|
|
61
|
+
"ws": "^8.22.0"
|
|
50
62
|
},
|
|
51
63
|
"overrides": {
|
|
52
64
|
"@hono/node-server": "2.1.1",
|
|
@@ -1,73 +1,73 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
2
|
-
|
|
3
|
-
// Ensure the bundled node-pty `spawn-helper` is executable.
|
|
4
|
-
//
|
|
5
|
-
// node-pty ships its prebuilt `spawn-helper` binaries with mode 0644 (no exec
|
|
6
|
-
// bit) in the published tarball. On macOS/Linux that makes `posix_spawnp`
|
|
7
|
-
// fail when node-pty tries to fork a PTY, so the bridge silently drops to the
|
|
8
|
-
// non-PTY fallback. The Claude adapter relies on PTY interactive mode, so the
|
|
9
|
-
// fallback surfaces to users as:
|
|
10
|
-
//
|
|
11
|
-
// Error: Input must be provided ... when using --print
|
|
12
|
-
//
|
|
13
|
-
// Restoring the exec bit after install fixes PTY spawning. This runs on
|
|
14
|
-
// postinstall and is idempotent; it never fails the install.
|
|
15
|
-
|
|
16
|
-
import fs from "node:fs";
|
|
17
|
-
import path from "node:path";
|
|
18
|
-
import { createRequire } from "node:module";
|
|
19
|
-
|
|
20
|
-
if (process.platform === "win32") {
|
|
21
|
-
process.exit(0);
|
|
22
|
-
}
|
|
23
|
-
|
|
24
|
-
const require = createRequire(import.meta.url);
|
|
25
|
-
|
|
26
|
-
let nodePtyDir;
|
|
27
|
-
try {
|
|
28
|
-
nodePtyDir = path.dirname(require.resolve("node-pty/package.json"));
|
|
29
|
-
} catch {
|
|
30
|
-
// node-pty is optional in unsupported environments; nothing to fix.
|
|
31
|
-
process.exit(0);
|
|
32
|
-
}
|
|
33
|
-
|
|
34
|
-
const candidates = [
|
|
35
|
-
"prebuilds/darwin-arm64/spawn-helper",
|
|
36
|
-
"prebuilds/darwin-x64/spawn-helper",
|
|
37
|
-
"prebuilds/linux-x64/spawn-helper",
|
|
38
|
-
"prebuilds/linux-arm64/spawn-helper",
|
|
39
|
-
"build/Release/spawn-helper",
|
|
40
|
-
];
|
|
41
|
-
|
|
42
|
-
let fixed = 0;
|
|
43
|
-
for (const relative of candidates) {
|
|
44
|
-
const helperPath = path.join(nodePtyDir, relative);
|
|
45
|
-
let stats;
|
|
46
|
-
try {
|
|
47
|
-
stats = fs.statSync(helperPath);
|
|
48
|
-
} catch {
|
|
49
|
-
continue;
|
|
50
|
-
}
|
|
51
|
-
if (!stats.isFile()) {
|
|
52
|
-
continue;
|
|
53
|
-
}
|
|
54
|
-
if ((stats.mode & 0o111) !== 0) {
|
|
55
|
-
continue;
|
|
56
|
-
}
|
|
57
|
-
try {
|
|
58
|
-
fs.chmodSync(helperPath, stats.mode | 0o755);
|
|
59
|
-
fixed += 1;
|
|
60
|
-
} catch (error) {
|
|
61
|
-
console.warn(
|
|
62
|
-
`[cli-wechat-bridge] Could not make node-pty spawn-helper executable: ${helperPath}\n` +
|
|
63
|
-
` ${error instanceof Error ? error.message : String(error)}\n` +
|
|
64
|
-
" PTY mode may be unavailable; run --doctor for details.",
|
|
65
|
-
);
|
|
66
|
-
}
|
|
67
|
-
}
|
|
68
|
-
|
|
69
|
-
if (fixed > 0) {
|
|
70
|
-
console.log(
|
|
71
|
-
`[cli-wechat-bridge] Restored executable bit on ${fixed} node-pty spawn-helper binary(ies).`,
|
|
72
|
-
);
|
|
73
|
-
}
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
// Ensure the bundled node-pty `spawn-helper` is executable.
|
|
4
|
+
//
|
|
5
|
+
// node-pty ships its prebuilt `spawn-helper` binaries with mode 0644 (no exec
|
|
6
|
+
// bit) in the published tarball. On macOS/Linux that makes `posix_spawnp`
|
|
7
|
+
// fail when node-pty tries to fork a PTY, so the bridge silently drops to the
|
|
8
|
+
// non-PTY fallback. The Claude adapter relies on PTY interactive mode, so the
|
|
9
|
+
// fallback surfaces to users as:
|
|
10
|
+
//
|
|
11
|
+
// Error: Input must be provided ... when using --print
|
|
12
|
+
//
|
|
13
|
+
// Restoring the exec bit after install fixes PTY spawning. This runs on
|
|
14
|
+
// postinstall and is idempotent; it never fails the install.
|
|
15
|
+
|
|
16
|
+
import fs from "node:fs";
|
|
17
|
+
import path from "node:path";
|
|
18
|
+
import { createRequire } from "node:module";
|
|
19
|
+
|
|
20
|
+
if (process.platform === "win32") {
|
|
21
|
+
process.exit(0);
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
const require = createRequire(import.meta.url);
|
|
25
|
+
|
|
26
|
+
let nodePtyDir;
|
|
27
|
+
try {
|
|
28
|
+
nodePtyDir = path.dirname(require.resolve("node-pty/package.json"));
|
|
29
|
+
} catch {
|
|
30
|
+
// node-pty is optional in unsupported environments; nothing to fix.
|
|
31
|
+
process.exit(0);
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
const candidates = [
|
|
35
|
+
"prebuilds/darwin-arm64/spawn-helper",
|
|
36
|
+
"prebuilds/darwin-x64/spawn-helper",
|
|
37
|
+
"prebuilds/linux-x64/spawn-helper",
|
|
38
|
+
"prebuilds/linux-arm64/spawn-helper",
|
|
39
|
+
"build/Release/spawn-helper",
|
|
40
|
+
];
|
|
41
|
+
|
|
42
|
+
let fixed = 0;
|
|
43
|
+
for (const relative of candidates) {
|
|
44
|
+
const helperPath = path.join(nodePtyDir, relative);
|
|
45
|
+
let stats;
|
|
46
|
+
try {
|
|
47
|
+
stats = fs.statSync(helperPath);
|
|
48
|
+
} catch {
|
|
49
|
+
continue;
|
|
50
|
+
}
|
|
51
|
+
if (!stats.isFile()) {
|
|
52
|
+
continue;
|
|
53
|
+
}
|
|
54
|
+
if ((stats.mode & 0o111) !== 0) {
|
|
55
|
+
continue;
|
|
56
|
+
}
|
|
57
|
+
try {
|
|
58
|
+
fs.chmodSync(helperPath, stats.mode | 0o755);
|
|
59
|
+
fixed += 1;
|
|
60
|
+
} catch (error) {
|
|
61
|
+
console.warn(
|
|
62
|
+
`[cli-wechat-bridge] Could not make node-pty spawn-helper executable: ${helperPath}\n` +
|
|
63
|
+
` ${error instanceof Error ? error.message : String(error)}\n` +
|
|
64
|
+
" PTY mode may be unavailable; run --doctor for details.",
|
|
65
|
+
);
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
if (fixed > 0) {
|
|
70
|
+
console.log(
|
|
71
|
+
`[cli-wechat-bridge] Restored executable bit on ${fixed} node-pty spawn-helper binary(ies).`,
|
|
72
|
+
);
|
|
73
|
+
}
|