claude-codex-wechat 0.1.13 → 0.1.16
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 +29 -0
- package/config.example.json +8 -0
- package/dist/server/cli.js +438 -79
- package/dist/web/assets/index-DxiX_R2K.js +10 -0
- package/dist/web/index.html +1 -1
- package/package.json +5 -1
- package/dist/web/assets/index-BW-_vdVB.js +0 -10
package/dist/server/cli.js
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
import { createRequire as __cjsCreateRequire } from 'node:module'; const require = __cjsCreateRequire(import.meta.url);
|
|
3
3
|
|
|
4
4
|
// src/cli.ts
|
|
5
|
-
import { existsSync as
|
|
5
|
+
import { existsSync as existsSync10, mkdirSync as mkdirSync7, readFileSync as readFileSync8, writeFileSync as writeFileSync7 } from "node:fs";
|
|
6
6
|
import { dirname as dirname12, join as join12 } from "node:path";
|
|
7
7
|
import { fileURLToPath } from "node:url";
|
|
8
8
|
|
|
@@ -80,6 +80,7 @@ function normalizeBridgeConfig(raw, env, path) {
|
|
|
80
80
|
return {
|
|
81
81
|
wechat: normalizeWechatConfig(record.wechat, env),
|
|
82
82
|
bridge: normalizeBridgeDefaultsConfig(record.bridge),
|
|
83
|
+
tunnel: normalizeTunnelConfig(record.tunnel),
|
|
83
84
|
providers: normalizeProvidersConfig(record.providers, env)
|
|
84
85
|
};
|
|
85
86
|
}
|
|
@@ -103,6 +104,17 @@ function normalizeBridgeDefaultsConfig(raw) {
|
|
|
103
104
|
...defaultWorkspace ? { defaultWorkspace } : {}
|
|
104
105
|
};
|
|
105
106
|
}
|
|
107
|
+
function normalizeTunnelConfig(raw) {
|
|
108
|
+
const record = raw && typeof raw === "object" ? raw : {};
|
|
109
|
+
const relayRecord = record.relay && typeof record.relay === "object" ? record.relay : void 0;
|
|
110
|
+
const relay = {
|
|
111
|
+
serverUrl: typeof relayRecord?.serverUrl === "string" && relayRecord.serverUrl.trim() ? relayRecord.serverUrl : "wss://wechat.style520.com/agent",
|
|
112
|
+
...typeof relayRecord?.authToken === "string" && relayRecord.authToken.trim() ? { authToken: relayRecord.authToken } : {}
|
|
113
|
+
};
|
|
114
|
+
return {
|
|
115
|
+
relay
|
|
116
|
+
};
|
|
117
|
+
}
|
|
106
118
|
function normalizeProviderCommand(raw, envFallback) {
|
|
107
119
|
const record = raw && typeof raw === "object" ? raw : {};
|
|
108
120
|
const command = typeof record.command === "string" && record.command ? record.command : typeof envFallback === "string" && envFallback ? envFallback : void 0;
|
|
@@ -125,6 +137,8 @@ function normalizeWechatConfig(raw, env) {
|
|
|
125
137
|
|
|
126
138
|
// src/daemon/configPersistence.ts
|
|
127
139
|
import { mkdir, readFile, rm, writeFile } from "node:fs/promises";
|
|
140
|
+
import { randomBytes } from "node:crypto";
|
|
141
|
+
import { existsSync as existsSync2, mkdirSync, readFileSync as readFileSync2, writeFileSync } from "node:fs";
|
|
128
142
|
import { dirname } from "node:path";
|
|
129
143
|
async function persistWechatCredentialsToConfigFile(input) {
|
|
130
144
|
const currentConfig = await readConfigFile(input.configPath);
|
|
@@ -152,18 +166,51 @@ async function deleteConfigFile(configPath) {
|
|
|
152
166
|
}
|
|
153
167
|
async function persistBridgeDefaultsToConfigFile(input) {
|
|
154
168
|
const currentConfig = await readConfigFile(input.configPath);
|
|
169
|
+
const currentBridge = isRecord(currentConfig.bridge) ? currentConfig.bridge : void 0;
|
|
155
170
|
const nextConfig = {
|
|
156
171
|
...currentConfig,
|
|
157
172
|
bridge: {
|
|
158
|
-
...
|
|
159
|
-
defaultProvider: input.defaultProvider,
|
|
160
|
-
defaultWorkspace: input.defaultWorkspace
|
|
161
|
-
}
|
|
173
|
+
...currentBridge ?? {},
|
|
174
|
+
...input.defaultProvider ? { defaultProvider: input.defaultProvider } : {},
|
|
175
|
+
...input.defaultWorkspace ? { defaultWorkspace: input.defaultWorkspace } : {}
|
|
176
|
+
},
|
|
177
|
+
...input.tunnel ? {
|
|
178
|
+
tunnel: {
|
|
179
|
+
...input.tunnel.relay ? {
|
|
180
|
+
relay: {
|
|
181
|
+
...input.tunnel.relay.serverUrl ? { serverUrl: input.tunnel.relay.serverUrl } : {},
|
|
182
|
+
...input.tunnel.relay.authToken ? { authToken: input.tunnel.relay.authToken } : {}
|
|
183
|
+
}
|
|
184
|
+
} : {}
|
|
185
|
+
}
|
|
186
|
+
} : {}
|
|
162
187
|
};
|
|
163
188
|
await mkdir(dirname(input.configPath), { recursive: true });
|
|
164
189
|
await writeFile(input.configPath, `${JSON.stringify(nextConfig, null, 2)}
|
|
165
190
|
`, "utf8");
|
|
166
191
|
}
|
|
192
|
+
function ensureRelayAuthTokenSync(input) {
|
|
193
|
+
const currentConfig = readConfigFileSync(input.configPath);
|
|
194
|
+
const currentTunnel = isRecord(currentConfig.tunnel) ? currentConfig.tunnel : void 0;
|
|
195
|
+
const currentRelay = isRecord(currentTunnel?.relay) ? currentTunnel.relay : void 0;
|
|
196
|
+
const existing = typeof currentRelay?.authToken === "string" && currentRelay.authToken.trim() ? currentRelay.authToken.trim() : "";
|
|
197
|
+
if (existing) return existing;
|
|
198
|
+
const authToken = `clrt_${randomBytes(12).toString("hex")}`;
|
|
199
|
+
const nextConfig = {
|
|
200
|
+
...currentConfig,
|
|
201
|
+
tunnel: {
|
|
202
|
+
...currentTunnel ?? {},
|
|
203
|
+
relay: {
|
|
204
|
+
...currentRelay ?? {},
|
|
205
|
+
authToken
|
|
206
|
+
}
|
|
207
|
+
}
|
|
208
|
+
};
|
|
209
|
+
mkdirSync(dirname(input.configPath), { recursive: true });
|
|
210
|
+
writeFileSync(input.configPath, `${JSON.stringify(nextConfig, null, 2)}
|
|
211
|
+
`, "utf8");
|
|
212
|
+
return authToken;
|
|
213
|
+
}
|
|
167
214
|
async function persistProviderCommandsToConfigFile(input) {
|
|
168
215
|
const currentConfig = await readConfigFile(input.configPath);
|
|
169
216
|
const normalizedProviders = normalizeProvidersForPersistence(input.providers);
|
|
@@ -185,6 +232,17 @@ async function readConfigFile(path) {
|
|
|
185
232
|
throw error;
|
|
186
233
|
}
|
|
187
234
|
}
|
|
235
|
+
function readConfigFileSync(path) {
|
|
236
|
+
try {
|
|
237
|
+
if (!existsSync2(path)) return {};
|
|
238
|
+
const raw = readFileSync2(path, "utf8");
|
|
239
|
+
const parsed = JSON.parse(raw);
|
|
240
|
+
return isRecord(parsed) ? parsed : {};
|
|
241
|
+
} catch (error) {
|
|
242
|
+
if (isMissingFileError(error)) return {};
|
|
243
|
+
throw error;
|
|
244
|
+
}
|
|
245
|
+
}
|
|
188
246
|
function isRecord(value) {
|
|
189
247
|
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
190
248
|
}
|
|
@@ -202,7 +260,7 @@ function normalizeProvidersForPersistence(providers) {
|
|
|
202
260
|
}
|
|
203
261
|
|
|
204
262
|
// src/channels/weixin-direct/weixinStateStore.ts
|
|
205
|
-
import { existsSync as
|
|
263
|
+
import { existsSync as existsSync3, mkdirSync as mkdirSync2, readFileSync as readFileSync3, writeFileSync as writeFileSync2 } from "node:fs";
|
|
206
264
|
import { dirname as dirname2 } from "node:path";
|
|
207
265
|
var PUSH_QUOTA_LIMIT = 10;
|
|
208
266
|
var PUSH_WINDOW_MS = 24 * 60 * 60 * 1e3;
|
|
@@ -296,9 +354,9 @@ var FileWeixinStateStore = class {
|
|
|
296
354
|
return this.readState().bridge?.weixinChannel ?? {};
|
|
297
355
|
}
|
|
298
356
|
readState() {
|
|
299
|
-
if (!
|
|
357
|
+
if (!existsSync3(this.configPath)) return {};
|
|
300
358
|
try {
|
|
301
|
-
const raw = JSON.parse(
|
|
359
|
+
const raw = JSON.parse(readFileSync3(this.configPath, "utf8"));
|
|
302
360
|
return raw && typeof raw === "object" ? raw : {};
|
|
303
361
|
} catch {
|
|
304
362
|
return {};
|
|
@@ -309,8 +367,8 @@ var FileWeixinStateStore = class {
|
|
|
309
367
|
...state,
|
|
310
368
|
bridge: { ...state.bridge ?? {}, weixinChannel: channel }
|
|
311
369
|
};
|
|
312
|
-
|
|
313
|
-
|
|
370
|
+
mkdirSync2(dirname2(this.configPath), { recursive: true });
|
|
371
|
+
writeFileSync2(this.configPath, `${JSON.stringify(next, null, 2)}
|
|
314
372
|
`, "utf8");
|
|
315
373
|
}
|
|
316
374
|
};
|
|
@@ -608,7 +666,7 @@ async function normalizeClaudeSessionFileForResume(sessionPath) {
|
|
|
608
666
|
// src/providers/codex/nativeSessions.ts
|
|
609
667
|
import Database from "better-sqlite3";
|
|
610
668
|
import { readdir as readdir2, readFile as readFile3, stat as stat2 } from "node:fs/promises";
|
|
611
|
-
import { existsSync as
|
|
669
|
+
import { existsSync as existsSync4 } from "node:fs";
|
|
612
670
|
import { homedir as homedir3 } from "node:os";
|
|
613
671
|
import { basename as basename2, join as join3 } from "node:path";
|
|
614
672
|
function resolveCodexSessionsRoot(env = process.env) {
|
|
@@ -710,7 +768,7 @@ async function readCodexSessionIndex(env) {
|
|
|
710
768
|
function readCodexThreadMetadata(env) {
|
|
711
769
|
const dbPath = join3(env.CODEX_HOME || join3(env.HOME || homedir3(), ".codex"), "state_5.sqlite");
|
|
712
770
|
const metadata = /* @__PURE__ */ new Map();
|
|
713
|
-
if (!
|
|
771
|
+
if (!existsSync4(dbPath)) return metadata;
|
|
714
772
|
let db = null;
|
|
715
773
|
try {
|
|
716
774
|
db = new Database(dbPath, { readonly: true });
|
|
@@ -814,7 +872,7 @@ async function upsertCodexSessionIndexEntry(input, env = process.env) {
|
|
|
814
872
|
|
|
815
873
|
// src/providers/codex/nativeThreads.ts
|
|
816
874
|
import Database2 from "better-sqlite3";
|
|
817
|
-
import { existsSync as
|
|
875
|
+
import { existsSync as existsSync5 } from "node:fs";
|
|
818
876
|
import { readFile as readFile5, writeFile as writeFile4 } from "node:fs/promises";
|
|
819
877
|
import { homedir as homedir5 } from "node:os";
|
|
820
878
|
import { join as join5 } from "node:path";
|
|
@@ -824,7 +882,7 @@ function resolveCodexStateDbPath(env = process.env) {
|
|
|
824
882
|
async function syncCodexThreadForResume(input) {
|
|
825
883
|
const env = input.env ?? process.env;
|
|
826
884
|
const dbPath = resolveCodexStateDbPath(env);
|
|
827
|
-
if (!
|
|
885
|
+
if (!existsSync5(dbPath)) return false;
|
|
828
886
|
for (let attempt = 0; attempt < 50; attempt += 1) {
|
|
829
887
|
const db = new Database2(dbPath);
|
|
830
888
|
try {
|
|
@@ -863,7 +921,7 @@ async function syncCodexThreadForResume(input) {
|
|
|
863
921
|
return false;
|
|
864
922
|
}
|
|
865
923
|
async function syncCodexRolloutSessionMeta(rolloutPath) {
|
|
866
|
-
if (!
|
|
924
|
+
if (!existsSync5(rolloutPath)) return;
|
|
867
925
|
const content = await readFile5(rolloutPath, "utf8");
|
|
868
926
|
const lines = content.split(/\r?\n/);
|
|
869
927
|
if (!lines[0]?.trim()) return;
|
|
@@ -1405,6 +1463,24 @@ async function resolveRecoverableProviderResumeHistorySynced(session) {
|
|
|
1405
1463
|
});
|
|
1406
1464
|
}
|
|
1407
1465
|
|
|
1466
|
+
// src/admin/tunnelRoutes.ts
|
|
1467
|
+
function registerTunnelRoutes(input) {
|
|
1468
|
+
const getStatus = async () => await input.tunnelManager.getStatus();
|
|
1469
|
+
input.app.get("/api/tunnel/status", getStatus);
|
|
1470
|
+
const start = async () => {
|
|
1471
|
+
await persistBridgeDefaultsToConfigFile({
|
|
1472
|
+
configPath: input.configPath,
|
|
1473
|
+
tunnel: input.defaults.tunnel
|
|
1474
|
+
});
|
|
1475
|
+
return await input.tunnelManager.start();
|
|
1476
|
+
};
|
|
1477
|
+
input.app.post("/api/tunnel/start", start);
|
|
1478
|
+
const stop = async () => {
|
|
1479
|
+
return await input.tunnelManager.stop();
|
|
1480
|
+
};
|
|
1481
|
+
input.app.post("/api/tunnel/stop", stop);
|
|
1482
|
+
}
|
|
1483
|
+
|
|
1408
1484
|
// src/admin/settingsRoutes.ts
|
|
1409
1485
|
function registerSettingsRoutes(input) {
|
|
1410
1486
|
input.app.get("/api/settings", async () => input.defaults);
|
|
@@ -1412,22 +1488,52 @@ function registerSettingsRoutes(input) {
|
|
|
1412
1488
|
const current = { ...input.defaults };
|
|
1413
1489
|
const next = normalizeSettings({
|
|
1414
1490
|
...current,
|
|
1415
|
-
...request.body
|
|
1491
|
+
...request.body,
|
|
1492
|
+
tunnel: {
|
|
1493
|
+
...current.tunnel,
|
|
1494
|
+
...request.body.tunnel && typeof request.body.tunnel === "object" ? request.body.tunnel : {},
|
|
1495
|
+
relay: {
|
|
1496
|
+
...current.tunnel?.relay ?? {},
|
|
1497
|
+
...request.body.tunnel && typeof request.body.tunnel === "object" && "relay" in request.body.tunnel && request.body.tunnel.relay && typeof request.body.tunnel.relay === "object" ? request.body.tunnel.relay : {}
|
|
1498
|
+
}
|
|
1499
|
+
}
|
|
1416
1500
|
}, current.defaultWorkspace);
|
|
1501
|
+
if (!next.tunnel.relay?.authToken) {
|
|
1502
|
+
const authToken = ensureRelayAuthTokenSync({ configPath: input.configPath });
|
|
1503
|
+
next.tunnel = {
|
|
1504
|
+
...next.tunnel,
|
|
1505
|
+
relay: {
|
|
1506
|
+
...next.tunnel.relay ?? {},
|
|
1507
|
+
authToken
|
|
1508
|
+
}
|
|
1509
|
+
};
|
|
1510
|
+
}
|
|
1417
1511
|
input.defaults.defaultProvider = next.defaultProvider;
|
|
1418
1512
|
input.defaults.defaultWorkspace = next.defaultWorkspace;
|
|
1513
|
+
input.defaults.tunnel = next.tunnel;
|
|
1419
1514
|
await persistBridgeDefaultsToConfigFile({
|
|
1420
1515
|
configPath: input.configPath,
|
|
1421
1516
|
defaultProvider: next.defaultProvider,
|
|
1422
|
-
defaultWorkspace: next.defaultWorkspace
|
|
1517
|
+
defaultWorkspace: next.defaultWorkspace,
|
|
1518
|
+
tunnel: next.tunnel
|
|
1423
1519
|
});
|
|
1424
1520
|
return { ok: true };
|
|
1425
1521
|
});
|
|
1426
1522
|
}
|
|
1427
1523
|
function normalizeSettings(input, defaultWorkspace) {
|
|
1524
|
+
const tunnel = input.tunnel && typeof input.tunnel === "object" ? input.tunnel : {};
|
|
1525
|
+
const relay = tunnel.relay && typeof tunnel.relay === "object" ? tunnel.relay : {};
|
|
1428
1526
|
return {
|
|
1429
1527
|
defaultProvider: input.defaultProvider === "codex" ? "codex" : "claude-code",
|
|
1430
|
-
defaultWorkspace: typeof input.defaultWorkspace === "string" && input.defaultWorkspace.trim() ? input.defaultWorkspace : defaultWorkspace
|
|
1528
|
+
defaultWorkspace: typeof input.defaultWorkspace === "string" && input.defaultWorkspace.trim() ? input.defaultWorkspace : defaultWorkspace,
|
|
1529
|
+
tunnel: {
|
|
1530
|
+
...typeof relay.serverUrl === "string" && relay.serverUrl.trim() || typeof relay.authToken === "string" && relay.authToken.trim() || !("relay" in tunnel) ? {
|
|
1531
|
+
relay: {
|
|
1532
|
+
serverUrl: typeof relay.serverUrl === "string" && relay.serverUrl.trim() ? relay.serverUrl : "wss://wechat.style520.com/agent",
|
|
1533
|
+
...typeof relay.authToken === "string" && relay.authToken.trim() ? { authToken: relay.authToken } : {}
|
|
1534
|
+
}
|
|
1535
|
+
} : {}
|
|
1536
|
+
}
|
|
1431
1537
|
};
|
|
1432
1538
|
}
|
|
1433
1539
|
|
|
@@ -1967,7 +2073,7 @@ function chunkText(text, limit) {
|
|
|
1967
2073
|
}
|
|
1968
2074
|
|
|
1969
2075
|
// src/channels/weixin-direct/mediaDownloader.ts
|
|
1970
|
-
import { mkdirSync as
|
|
2076
|
+
import { mkdirSync as mkdirSync3, writeFileSync as writeFileSync3 } from "node:fs";
|
|
1971
2077
|
import { dirname as dirname6 } from "node:path";
|
|
1972
2078
|
|
|
1973
2079
|
// src/channels/weixin-direct/mediaCrypto.ts
|
|
@@ -2031,8 +2137,8 @@ var WeixinMediaDownloader = class {
|
|
|
2031
2137
|
} catch {
|
|
2032
2138
|
return { ok: false, reason: "decrypt_failed" };
|
|
2033
2139
|
}
|
|
2034
|
-
|
|
2035
|
-
|
|
2140
|
+
mkdirSync3(dirname6(input.destPath), { recursive: true });
|
|
2141
|
+
writeFileSync3(input.destPath, plaintext);
|
|
2036
2142
|
return { ok: true, localPath: input.destPath, bytes: plaintext.length };
|
|
2037
2143
|
}
|
|
2038
2144
|
};
|
|
@@ -3114,9 +3220,9 @@ var BRIDGE_COMMAND_HELP_GROUPS = [
|
|
|
3114
3220
|
entries: [
|
|
3115
3221
|
{ command: "/help", desc: "\u663E\u793A\u672C\u5E2E\u52A9" },
|
|
3116
3222
|
{ command: "/status", desc: "\u67E5\u770B\u5F53\u524D\u4F1A\u8BDD\uFF08provider\u3001\u5DE5\u4F5C\u76EE\u5F55\u3001\u72B6\u6001\uFF09" },
|
|
3117
|
-
{ command: "/new", desc: "\
|
|
3223
|
+
{ command: "/new", desc: "\u6309\u5F53\u524D provider \u65B0\u5EFA\u4F1A\u8BDD" },
|
|
3118
3224
|
{ command: "/new claude|codex", desc: "\u65B0\u5EFA\u6307\u5B9A provider \u4F1A\u8BDD" },
|
|
3119
|
-
{ command: "/new <\u76EE\u5F55>", desc: "\u5728\u6307\u5B9A\u76EE\u5F55\
|
|
3225
|
+
{ command: "/new <\u76EE\u5F55>", desc: "\u5728\u6307\u5B9A\u76EE\u5F55\u6309\u5F53\u524D provider \u65B0\u5EFA\u4F1A\u8BDD" },
|
|
3120
3226
|
{ command: "/new claude:<\u76EE\u5F55>", desc: "\u65B0\u5EFA\u6307\u5B9A provider + \u6307\u5B9A\u76EE\u5F55\u4F1A\u8BDD" },
|
|
3121
3227
|
{ command: "/stop", desc: "\u4E2D\u65AD\u5F53\u524D\u6B63\u5728\u751F\u6210\u7684\u56DE\u590D\uFF08\u4F1A\u8BDD\u4FDD\u7559\uFF09" }
|
|
3122
3228
|
]
|
|
@@ -3132,8 +3238,11 @@ var BRIDGE_COMMAND_HELP_GROUPS = [
|
|
|
3132
3238
|
]
|
|
3133
3239
|
}
|
|
3134
3240
|
];
|
|
3135
|
-
function buildBridgeCommandHelpMarkdown() {
|
|
3241
|
+
function buildBridgeCommandHelpMarkdown(input) {
|
|
3136
3242
|
const lines = ["**\u53EF\u7528\u547D\u4EE4**", "", BRIDGE_COMMAND_HELP_INTRO];
|
|
3243
|
+
if (input?.publicUrl) {
|
|
3244
|
+
lines.push("", `**\u8BBF\u95EE\u5730\u5740**`, "", `[${input.publicUrl}](${input.publicUrl})`);
|
|
3245
|
+
}
|
|
3137
3246
|
for (const group of BRIDGE_COMMAND_HELP_GROUPS) {
|
|
3138
3247
|
lines.push("", `**${group.title}**${group.note ? `\uFF08${group.note}\uFF09` : ""}`);
|
|
3139
3248
|
for (const entry of group.entries) {
|
|
@@ -3144,7 +3253,7 @@ function buildBridgeCommandHelpMarkdown() {
|
|
|
3144
3253
|
}
|
|
3145
3254
|
|
|
3146
3255
|
// src/session/currentConversationStore.ts
|
|
3147
|
-
import { existsSync as
|
|
3256
|
+
import { existsSync as existsSync6, mkdirSync as mkdirSync4, readFileSync as readFileSync4, writeFileSync as writeFileSync4 } from "node:fs";
|
|
3148
3257
|
import { dirname as dirname7 } from "node:path";
|
|
3149
3258
|
import { nanoid as nanoid2 } from "nanoid";
|
|
3150
3259
|
var CurrentConversationStore = class {
|
|
@@ -3204,8 +3313,8 @@ var CurrentConversationStore = class {
|
|
|
3204
3313
|
this.writeState(state);
|
|
3205
3314
|
}
|
|
3206
3315
|
readState() {
|
|
3207
|
-
if (!
|
|
3208
|
-
const raw = JSON.parse(
|
|
3316
|
+
if (!existsSync6(this.configPath)) return {};
|
|
3317
|
+
const raw = JSON.parse(readFileSync4(this.configPath, "utf8"));
|
|
3209
3318
|
return raw && typeof raw === "object" ? raw : {};
|
|
3210
3319
|
}
|
|
3211
3320
|
writeCurrent(record) {
|
|
@@ -3227,8 +3336,8 @@ var CurrentConversationStore = class {
|
|
|
3227
3336
|
this.writeState(state);
|
|
3228
3337
|
}
|
|
3229
3338
|
writeState(state) {
|
|
3230
|
-
|
|
3231
|
-
|
|
3339
|
+
mkdirSync4(dirname7(this.configPath), { recursive: true });
|
|
3340
|
+
writeFileSync4(this.configPath, `${JSON.stringify(state, null, 2)}
|
|
3232
3341
|
`, "utf8");
|
|
3233
3342
|
}
|
|
3234
3343
|
};
|
|
@@ -3518,10 +3627,11 @@ var MessageRouter = class _MessageRouter {
|
|
|
3518
3627
|
}
|
|
3519
3628
|
async handleCommand(chatId, user, command) {
|
|
3520
3629
|
if (command.kind === "help") {
|
|
3630
|
+
const helpAddress = await this.options.getHelpAddress?.();
|
|
3521
3631
|
await this.sendToChat({
|
|
3522
3632
|
chatId,
|
|
3523
3633
|
kind: "markdown",
|
|
3524
|
-
text: buildBridgeCommandHelpMarkdown()
|
|
3634
|
+
text: buildBridgeCommandHelpMarkdown({ publicUrl: helpAddress })
|
|
3525
3635
|
});
|
|
3526
3636
|
return;
|
|
3527
3637
|
}
|
|
@@ -3542,7 +3652,7 @@ var MessageRouter = class _MessageRouter {
|
|
|
3542
3652
|
}
|
|
3543
3653
|
if (command.kind === "new_session") {
|
|
3544
3654
|
await this.preemptActiveGeneration(chatId);
|
|
3545
|
-
const providerId = command.providerId ?? this.options.defaults?.defaultProvider ?? "claude-code";
|
|
3655
|
+
const providerId = command.providerId ?? this.conversation.getCurrent()?.providerId ?? this.options.defaults?.defaultProvider ?? "claude-code";
|
|
3546
3656
|
const cwd = command.cwd ?? this.conversation.getCurrent()?.cwd ?? this.options.defaults?.defaultWorkspace ?? defaultWorkspaceDir();
|
|
3547
3657
|
const session = this.conversation.create({
|
|
3548
3658
|
chatId,
|
|
@@ -3672,6 +3782,13 @@ var MessageRouter = class _MessageRouter {
|
|
|
3672
3782
|
providerSessionId: session.providerSessionId,
|
|
3673
3783
|
cwd: session.cwd
|
|
3674
3784
|
});
|
|
3785
|
+
if (session.providerId === "codex") {
|
|
3786
|
+
await upsertCodexSessionIndexEntry({
|
|
3787
|
+
sessionId: session.providerSessionId,
|
|
3788
|
+
threadName: session.resumeTitle
|
|
3789
|
+
});
|
|
3790
|
+
return;
|
|
3791
|
+
}
|
|
3675
3792
|
if (session.providerId === "claude-code") {
|
|
3676
3793
|
await ensureClaudeSessionBridgeMetadata({
|
|
3677
3794
|
sessionId: session.providerSessionId,
|
|
@@ -3733,7 +3850,7 @@ function composeInboundText(content) {
|
|
|
3733
3850
|
}
|
|
3734
3851
|
|
|
3735
3852
|
// src/storage/lastProviderSessionStore.ts
|
|
3736
|
-
import { existsSync as
|
|
3853
|
+
import { existsSync as existsSync7, mkdirSync as mkdirSync5, readFileSync as readFileSync5, writeFileSync as writeFileSync5 } from "node:fs";
|
|
3737
3854
|
import { dirname as dirname8 } from "node:path";
|
|
3738
3855
|
var LastProviderSessionStore = class {
|
|
3739
3856
|
constructor(configPath) {
|
|
@@ -3760,19 +3877,19 @@ var LastProviderSessionStore = class {
|
|
|
3760
3877
|
return this.readState().bridge?.lastProviderSessions ?? {};
|
|
3761
3878
|
}
|
|
3762
3879
|
readState() {
|
|
3763
|
-
if (!
|
|
3764
|
-
const raw = JSON.parse(
|
|
3880
|
+
if (!existsSync7(this.configPath)) return {};
|
|
3881
|
+
const raw = JSON.parse(readFileSync5(this.configPath, "utf8"));
|
|
3765
3882
|
return raw && typeof raw === "object" ? raw : {};
|
|
3766
3883
|
}
|
|
3767
3884
|
writeState(state) {
|
|
3768
|
-
|
|
3769
|
-
|
|
3885
|
+
mkdirSync5(dirname8(this.configPath), { recursive: true });
|
|
3886
|
+
writeFileSync5(this.configPath, `${JSON.stringify(state, null, 2)}
|
|
3770
3887
|
`, "utf8");
|
|
3771
3888
|
}
|
|
3772
3889
|
};
|
|
3773
3890
|
|
|
3774
3891
|
// src/storage/runtimeUserStore.ts
|
|
3775
|
-
import { mkdirSync as
|
|
3892
|
+
import { mkdirSync as mkdirSync6, readFileSync as readFileSync6, writeFileSync as writeFileSync6, existsSync as existsSync8 } from "node:fs";
|
|
3776
3893
|
import { dirname as dirname9 } from "node:path";
|
|
3777
3894
|
import { nanoid as nanoid3 } from "nanoid";
|
|
3778
3895
|
var RuntimeUserStore = class {
|
|
@@ -3833,13 +3950,13 @@ var RuntimeUserStore = class {
|
|
|
3833
3950
|
return { ok: true };
|
|
3834
3951
|
}
|
|
3835
3952
|
readState() {
|
|
3836
|
-
if (!
|
|
3837
|
-
const raw = JSON.parse(
|
|
3953
|
+
if (!existsSync8(this.configPath)) return {};
|
|
3954
|
+
const raw = JSON.parse(readFileSync6(this.configPath, "utf8"));
|
|
3838
3955
|
return raw && typeof raw === "object" ? raw : {};
|
|
3839
3956
|
}
|
|
3840
3957
|
writeState(state) {
|
|
3841
|
-
|
|
3842
|
-
|
|
3958
|
+
mkdirSync6(dirname9(this.configPath), { recursive: true });
|
|
3959
|
+
writeFileSync6(this.configPath, `${JSON.stringify(state, null, 2)}
|
|
3843
3960
|
`, "utf8");
|
|
3844
3961
|
}
|
|
3845
3962
|
};
|
|
@@ -3861,15 +3978,221 @@ var BridgeEventHub = class {
|
|
|
3861
3978
|
}
|
|
3862
3979
|
};
|
|
3863
3980
|
|
|
3981
|
+
// src/runtime/relayTunnelProvider.ts
|
|
3982
|
+
var RelayTunnelProvider = class {
|
|
3983
|
+
constructor(options) {
|
|
3984
|
+
this.options = options;
|
|
3985
|
+
}
|
|
3986
|
+
options;
|
|
3987
|
+
socket = null;
|
|
3988
|
+
status = {
|
|
3989
|
+
installed: true,
|
|
3990
|
+
running: false,
|
|
3991
|
+
status: "stopped"
|
|
3992
|
+
};
|
|
3993
|
+
static defaultCreateSocket(url) {
|
|
3994
|
+
const socket = new WebSocket(url);
|
|
3995
|
+
return {
|
|
3996
|
+
get readyState() {
|
|
3997
|
+
return socket.readyState;
|
|
3998
|
+
},
|
|
3999
|
+
send(payload) {
|
|
4000
|
+
socket.send(payload);
|
|
4001
|
+
},
|
|
4002
|
+
close() {
|
|
4003
|
+
socket.close();
|
|
4004
|
+
},
|
|
4005
|
+
on(event, listener) {
|
|
4006
|
+
socket.addEventListener(event, listener);
|
|
4007
|
+
}
|
|
4008
|
+
};
|
|
4009
|
+
}
|
|
4010
|
+
async getStatus() {
|
|
4011
|
+
return { ...this.status };
|
|
4012
|
+
}
|
|
4013
|
+
async start() {
|
|
4014
|
+
this.status = {
|
|
4015
|
+
...this.status,
|
|
4016
|
+
status: "starting"
|
|
4017
|
+
};
|
|
4018
|
+
const socket = this.options.createSocket(this.options.serverUrl);
|
|
4019
|
+
this.socket = socket;
|
|
4020
|
+
return await new Promise((resolve2, reject) => {
|
|
4021
|
+
let settled = false;
|
|
4022
|
+
const finishResolve = (value) => {
|
|
4023
|
+
if (settled) return;
|
|
4024
|
+
settled = true;
|
|
4025
|
+
resolve2(value);
|
|
4026
|
+
};
|
|
4027
|
+
const finishReject = (error) => {
|
|
4028
|
+
if (settled) return;
|
|
4029
|
+
settled = true;
|
|
4030
|
+
reject(error);
|
|
4031
|
+
};
|
|
4032
|
+
socket.on("open", () => {
|
|
4033
|
+
socket.send(JSON.stringify({
|
|
4034
|
+
type: "register",
|
|
4035
|
+
clientVersion: "0.1.0",
|
|
4036
|
+
targetBaseUrl: this.options.targetBaseUrl,
|
|
4037
|
+
authToken: this.options.authToken
|
|
4038
|
+
}));
|
|
4039
|
+
});
|
|
4040
|
+
socket.on("message", (raw) => {
|
|
4041
|
+
const payload = typeof raw === "string" ? JSON.parse(raw) : typeof raw?.data === "string" ? JSON.parse(raw.data) : raw;
|
|
4042
|
+
if (payload?.type === "error") {
|
|
4043
|
+
const message = typeof payload?.error === "string" && payload.error ? payload.error : "relay_error";
|
|
4044
|
+
this.status = {
|
|
4045
|
+
...this.status,
|
|
4046
|
+
running: false,
|
|
4047
|
+
status: "error",
|
|
4048
|
+
error: message
|
|
4049
|
+
};
|
|
4050
|
+
finishReject(new Error(message));
|
|
4051
|
+
return;
|
|
4052
|
+
}
|
|
4053
|
+
if (payload?.type === "registered") {
|
|
4054
|
+
const publicUrl = typeof payload?.publicUrl === "string" && payload.publicUrl ? payload.publicUrl : typeof payload?.token === "string" ? `${derivePublicBaseUrl(this.options.serverUrl)}/${payload.token}` : void 0;
|
|
4055
|
+
this.status = {
|
|
4056
|
+
installed: true,
|
|
4057
|
+
running: true,
|
|
4058
|
+
status: "running",
|
|
4059
|
+
...publicUrl ? { publicUrl } : {}
|
|
4060
|
+
};
|
|
4061
|
+
finishResolve({ ...this.status });
|
|
4062
|
+
return;
|
|
4063
|
+
}
|
|
4064
|
+
if (payload?.type === "request") {
|
|
4065
|
+
void this.handleRequest(payload);
|
|
4066
|
+
}
|
|
4067
|
+
});
|
|
4068
|
+
socket.on("close", () => {
|
|
4069
|
+
this.status = {
|
|
4070
|
+
...this.status,
|
|
4071
|
+
running: false,
|
|
4072
|
+
status: "error",
|
|
4073
|
+
error: "relay_disconnected"
|
|
4074
|
+
};
|
|
4075
|
+
finishReject(new Error("relay_disconnected"));
|
|
4076
|
+
});
|
|
4077
|
+
socket.on("error", (error) => {
|
|
4078
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
4079
|
+
this.status = {
|
|
4080
|
+
...this.status,
|
|
4081
|
+
running: false,
|
|
4082
|
+
status: "error",
|
|
4083
|
+
error: message
|
|
4084
|
+
};
|
|
4085
|
+
finishReject(error);
|
|
4086
|
+
});
|
|
4087
|
+
});
|
|
4088
|
+
}
|
|
4089
|
+
async stop() {
|
|
4090
|
+
this.socket?.close();
|
|
4091
|
+
this.socket = null;
|
|
4092
|
+
this.status = {
|
|
4093
|
+
installed: true,
|
|
4094
|
+
running: false,
|
|
4095
|
+
status: "stopped"
|
|
4096
|
+
};
|
|
4097
|
+
return { ...this.status };
|
|
4098
|
+
}
|
|
4099
|
+
async handleRequest(payload) {
|
|
4100
|
+
const fetchImpl = this.options.fetchImpl ?? fetch;
|
|
4101
|
+
const response = await fetchImpl(`${this.options.targetBaseUrl}${payload.path}`, {
|
|
4102
|
+
method: payload.method,
|
|
4103
|
+
headers: payload.headers,
|
|
4104
|
+
body: payload.bodyBase64 ? Buffer.from(payload.bodyBase64, "base64") : void 0
|
|
4105
|
+
});
|
|
4106
|
+
const bodyBuffer = Buffer.from(await response.arrayBuffer());
|
|
4107
|
+
this.socket?.send(JSON.stringify({
|
|
4108
|
+
type: "response",
|
|
4109
|
+
requestId: payload.requestId,
|
|
4110
|
+
status: response.status,
|
|
4111
|
+
headers: Object.fromEntries(response.headers.entries()),
|
|
4112
|
+
bodyBase64: bodyBuffer.toString("base64")
|
|
4113
|
+
}));
|
|
4114
|
+
}
|
|
4115
|
+
};
|
|
4116
|
+
function derivePublicBaseUrl(serverUrl) {
|
|
4117
|
+
return serverUrl.replace(/^ws:\/\//, "http://").replace(/^wss:\/\//, "https://").replace(/\/agent\/?$/, "").replace(/\/+$/, "");
|
|
4118
|
+
}
|
|
4119
|
+
|
|
4120
|
+
// src/runtime/relayTunnelRouter.ts
|
|
4121
|
+
var RelayTunnelRouter = class {
|
|
4122
|
+
constructor(options) {
|
|
4123
|
+
this.options = options;
|
|
4124
|
+
}
|
|
4125
|
+
options;
|
|
4126
|
+
relayProvider = null;
|
|
4127
|
+
relayKey = "";
|
|
4128
|
+
async getStatus() {
|
|
4129
|
+
const provider = this.getOrCreateRelayProvider();
|
|
4130
|
+
if (!provider) return this.notConfiguredStatus();
|
|
4131
|
+
return await provider.getStatus();
|
|
4132
|
+
}
|
|
4133
|
+
async start() {
|
|
4134
|
+
const provider = this.getOrCreateRelayProvider();
|
|
4135
|
+
if (!provider) return this.notConfiguredStatus();
|
|
4136
|
+
return await provider.start();
|
|
4137
|
+
}
|
|
4138
|
+
async stop() {
|
|
4139
|
+
const provider = this.getOrCreateRelayProvider();
|
|
4140
|
+
if (!provider) return this.notConfiguredStatus();
|
|
4141
|
+
return await provider.stop();
|
|
4142
|
+
}
|
|
4143
|
+
getOrCreateRelayProvider() {
|
|
4144
|
+
const relay = this.options.defaults.tunnel.relay;
|
|
4145
|
+
if (!relay?.serverUrl || !relay.authToken) return null;
|
|
4146
|
+
const key = `${relay.serverUrl}|${relay.authToken}|${this.options.bridgePort}`;
|
|
4147
|
+
if (this.relayProvider && this.relayKey === key) return this.relayProvider;
|
|
4148
|
+
this.relayKey = key;
|
|
4149
|
+
this.relayProvider = new RelayTunnelProvider({
|
|
4150
|
+
serverUrl: relay.serverUrl,
|
|
4151
|
+
authToken: relay.authToken,
|
|
4152
|
+
targetBaseUrl: `http://127.0.0.1:${this.options.bridgePort}`,
|
|
4153
|
+
createSocket: RelayTunnelProvider.defaultCreateSocket
|
|
4154
|
+
});
|
|
4155
|
+
return this.relayProvider;
|
|
4156
|
+
}
|
|
4157
|
+
notConfiguredStatus() {
|
|
4158
|
+
return {
|
|
4159
|
+
installed: false,
|
|
4160
|
+
running: false,
|
|
4161
|
+
status: "error",
|
|
4162
|
+
error: "relay_not_configured"
|
|
4163
|
+
};
|
|
4164
|
+
}
|
|
4165
|
+
};
|
|
4166
|
+
|
|
3864
4167
|
// src/daemon/server.ts
|
|
3865
4168
|
function createDaemonServer(options = {}) {
|
|
3866
4169
|
const app = Fastify({ logger: true });
|
|
3867
4170
|
const events = new BridgeEventHub();
|
|
4171
|
+
const configPath = options.configPath ?? process.env.BRIDGE_CONFIG ?? join9(mkdtempSync(join9(tmpdir2(), "claude-codex-wechat-")), "config.json");
|
|
4172
|
+
const persistedConfig = loadBridgeConfig(configPath);
|
|
3868
4173
|
const bridgeDefaults = {
|
|
3869
|
-
defaultProvider: options.bridgeDefaults?.defaultProvider ?? "claude-code",
|
|
3870
|
-
defaultWorkspace: options.bridgeDefaults?.defaultWorkspace ?? process.cwd()
|
|
4174
|
+
defaultProvider: options.bridgeDefaults?.defaultProvider ?? persistedConfig.bridge?.defaultProvider ?? "claude-code",
|
|
4175
|
+
defaultWorkspace: options.bridgeDefaults?.defaultWorkspace ?? persistedConfig.bridge?.defaultWorkspace ?? process.cwd(),
|
|
4176
|
+
tunnel: options.bridgeDefaults?.tunnel ?? {
|
|
4177
|
+
relay: {
|
|
4178
|
+
serverUrl: persistedConfig.tunnel?.relay?.serverUrl ?? "wss://wechat.style520.com/agent",
|
|
4179
|
+
...persistedConfig.tunnel?.relay?.authToken ? { authToken: persistedConfig.tunnel.relay.authToken } : {}
|
|
4180
|
+
}
|
|
4181
|
+
}
|
|
3871
4182
|
};
|
|
3872
|
-
const
|
|
4183
|
+
const existingRelayAuthToken = bridgeDefaults.tunnel.relay?.authToken?.trim();
|
|
4184
|
+
if (existingRelayAuthToken) {
|
|
4185
|
+
bridgeDefaults.tunnel.relay = {
|
|
4186
|
+
...bridgeDefaults.tunnel.relay ?? {},
|
|
4187
|
+
authToken: existingRelayAuthToken
|
|
4188
|
+
};
|
|
4189
|
+
} else {
|
|
4190
|
+
const authToken = ensureRelayAuthTokenSync({ configPath });
|
|
4191
|
+
bridgeDefaults.tunnel.relay = {
|
|
4192
|
+
...bridgeDefaults.tunnel.relay ?? {},
|
|
4193
|
+
authToken
|
|
4194
|
+
};
|
|
4195
|
+
}
|
|
3873
4196
|
const conversation = new CurrentConversationStore(configPath, {
|
|
3874
4197
|
defaultCwd: bridgeDefaults.defaultWorkspace,
|
|
3875
4198
|
defaultProviderId: bridgeDefaults.defaultProvider
|
|
@@ -3911,6 +4234,10 @@ function createDaemonServer(options = {}) {
|
|
|
3911
4234
|
await managedWechatChannel.sendMessage({ chatId, kind: msg.kind, text: msg.text });
|
|
3912
4235
|
}
|
|
3913
4236
|
}) : void 0;
|
|
4237
|
+
const tunnelProvider = options.tunnelProvider ?? new RelayTunnelRouter({
|
|
4238
|
+
bridgePort: Number(process.env.BRIDGE_PORT ?? 8787),
|
|
4239
|
+
defaults: bridgeDefaults
|
|
4240
|
+
});
|
|
3914
4241
|
const messageRouter = channel ? new MessageRouter({
|
|
3915
4242
|
channel,
|
|
3916
4243
|
providers: providerAdapters,
|
|
@@ -3963,7 +4290,14 @@ function createDaemonServer(options = {}) {
|
|
|
3963
4290
|
},
|
|
3964
4291
|
lastProviderSessions,
|
|
3965
4292
|
events,
|
|
3966
|
-
defaults: bridgeDefaults
|
|
4293
|
+
defaults: bridgeDefaults,
|
|
4294
|
+
getHelpAddress: async () => {
|
|
4295
|
+
const tunnel = await tunnelProvider?.getStatus().catch(() => null);
|
|
4296
|
+
if (tunnel?.running && tunnel.publicUrl) return tunnel.publicUrl;
|
|
4297
|
+
const lan = listLanIpv4Addresses()[0];
|
|
4298
|
+
if (lan) return `http://${lan}:${process.env.BRIDGE_PORT ?? 8787}`;
|
|
4299
|
+
return `http://127.0.0.1:${process.env.BRIDGE_PORT ?? 8787}`;
|
|
4300
|
+
}
|
|
3967
4301
|
}) : void 0;
|
|
3968
4302
|
if (channel && messageRouter) {
|
|
3969
4303
|
channel.onMessage(async (message) => {
|
|
@@ -3992,10 +4326,24 @@ function createDaemonServer(options = {}) {
|
|
|
3992
4326
|
defaults: bridgeDefaults,
|
|
3993
4327
|
configPath
|
|
3994
4328
|
});
|
|
4329
|
+
if (tunnelProvider) {
|
|
4330
|
+
registerTunnelRoutes({
|
|
4331
|
+
app,
|
|
4332
|
+
configPath,
|
|
4333
|
+
defaults: { tunnel: bridgeDefaults.tunnel },
|
|
4334
|
+
tunnelManager: tunnelProvider
|
|
4335
|
+
});
|
|
4336
|
+
}
|
|
3995
4337
|
registerFsBrowseRoutes({ app });
|
|
3996
4338
|
app.get("/api/status", async () => ({
|
|
3997
4339
|
ok: true,
|
|
3998
|
-
sessions: conversation.getCurrent() ? [conversation.getCurrent()] : []
|
|
4340
|
+
sessions: conversation.getCurrent() ? [conversation.getCurrent()] : [],
|
|
4341
|
+
preferredLocalUrl: (() => {
|
|
4342
|
+
const lan = listLanIpv4Addresses()[0];
|
|
4343
|
+
const port = process.env.BRIDGE_PORT ?? 8787;
|
|
4344
|
+
if (lan) return `http://${lan}:${port}`;
|
|
4345
|
+
return `http://127.0.0.1:${port}`;
|
|
4346
|
+
})()
|
|
3999
4347
|
}));
|
|
4000
4348
|
app.get("/api/providers/status", async () => providers.getStatus());
|
|
4001
4349
|
app.post("/api/bridge-events", async (request, reply) => {
|
|
@@ -4029,7 +4377,7 @@ function createDaemonServer(options = {}) {
|
|
|
4029
4377
|
app.addHook("onClose", async () => {
|
|
4030
4378
|
await channel?.stop();
|
|
4031
4379
|
});
|
|
4032
|
-
return { app, conversation, sessions: conversation, events, activeUserStore };
|
|
4380
|
+
return { app, conversation, sessions: conversation, events, activeUserStore, tunnelProvider };
|
|
4033
4381
|
}
|
|
4034
4382
|
|
|
4035
4383
|
// src/daemon/bootstrap.ts
|
|
@@ -4043,24 +4391,35 @@ async function startDaemon(options) {
|
|
|
4043
4391
|
configPath,
|
|
4044
4392
|
providers: providerCommands
|
|
4045
4393
|
});
|
|
4046
|
-
const
|
|
4394
|
+
const bridgeDefaults = {
|
|
4395
|
+
defaultProvider: config.bridge?.defaultProvider ?? "claude-code",
|
|
4396
|
+
defaultWorkspace: config.bridge?.defaultWorkspace ?? process.cwd(),
|
|
4397
|
+
tunnel: {
|
|
4398
|
+
enabled: true,
|
|
4399
|
+
...config.tunnel?.relay ? { relay: config.tunnel.relay } : {}
|
|
4400
|
+
}
|
|
4401
|
+
};
|
|
4402
|
+
const { app, tunnelProvider: resolvedTunnelProvider } = createDaemonServer({
|
|
4047
4403
|
wechat: config.wechat,
|
|
4048
4404
|
providerCommands,
|
|
4049
|
-
bridgeDefaults
|
|
4050
|
-
|
|
4051
|
-
|
|
4052
|
-
},
|
|
4053
|
-
configPath
|
|
4405
|
+
bridgeDefaults,
|
|
4406
|
+
configPath,
|
|
4407
|
+
...options.tunnelProvider ? { tunnelProvider: options.tunnelProvider } : {}
|
|
4054
4408
|
});
|
|
4055
4409
|
await options.attachFrontend(app);
|
|
4056
4410
|
await app.listen({ host, port });
|
|
4411
|
+
const address = app.server.address();
|
|
4412
|
+
const actualPort = typeof address === "object" && address ? address.port : port;
|
|
4413
|
+
if (config.tunnel?.relay?.serverUrl && config.tunnel?.relay?.authToken) {
|
|
4414
|
+
await resolvedTunnelProvider?.start().catch(() => void 0);
|
|
4415
|
+
}
|
|
4057
4416
|
console.log("claude-codex-wechat listening:");
|
|
4058
|
-
console.log(` Local: http://127.0.0.1:${
|
|
4417
|
+
console.log(` Local: http://127.0.0.1:${actualPort}`);
|
|
4059
4418
|
for (const ip of listLanIpv4Addresses()) {
|
|
4060
|
-
console.log(` Network: http://${ip}:${
|
|
4419
|
+
console.log(` Network: http://${ip}:${actualPort}`);
|
|
4061
4420
|
}
|
|
4062
4421
|
console.log(`config path: ${configPath}`);
|
|
4063
|
-
return { app, port, host, configPath };
|
|
4422
|
+
return { app, port: actualPort, host, configPath };
|
|
4064
4423
|
}
|
|
4065
4424
|
function listLanIpv4Addresses() {
|
|
4066
4425
|
return Object.values(networkInterfaces()).flat().filter((iface) => Boolean(iface)).filter((iface) => iface.family === "IPv4" && !iface.internal).map((iface) => iface.address);
|
|
@@ -4076,7 +4435,7 @@ async function resolveProviderCommands(providers) {
|
|
|
4076
4435
|
}
|
|
4077
4436
|
|
|
4078
4437
|
// src/daemon/staticFrontend.ts
|
|
4079
|
-
import { readFileSync as
|
|
4438
|
+
import { readFileSync as readFileSync7 } from "node:fs";
|
|
4080
4439
|
import { join as join10 } from "node:path";
|
|
4081
4440
|
import fastifyStatic from "@fastify/static";
|
|
4082
4441
|
function attachStaticFrontend(webRoot2) {
|
|
@@ -4085,7 +4444,7 @@ function attachStaticFrontend(webRoot2) {
|
|
|
4085
4444
|
root: webRoot2,
|
|
4086
4445
|
wildcard: false
|
|
4087
4446
|
});
|
|
4088
|
-
const indexHtml =
|
|
4447
|
+
const indexHtml = readFileSync7(join10(webRoot2, "index.html"), "utf8");
|
|
4089
4448
|
app.setNotFoundHandler((request, reply) => {
|
|
4090
4449
|
if (request.url.startsWith("/api")) {
|
|
4091
4450
|
reply.code(404).send({ error: "not_found" });
|
|
@@ -4097,7 +4456,7 @@ function attachStaticFrontend(webRoot2) {
|
|
|
4097
4456
|
}
|
|
4098
4457
|
|
|
4099
4458
|
// src/daemon/service.ts
|
|
4100
|
-
import { existsSync as
|
|
4459
|
+
import { existsSync as existsSync9, statSync } from "node:fs";
|
|
4101
4460
|
import { mkdir as mkdir4, readFile as readFile6, rm as rm2, writeFile as writeFile5 } from "node:fs/promises";
|
|
4102
4461
|
import { homedir as homedir8 } from "node:os";
|
|
4103
4462
|
import { dirname as dirname11, join as join11 } from "node:path";
|
|
@@ -4147,13 +4506,13 @@ async function startService(context) {
|
|
|
4147
4506
|
async function stopService(context) {
|
|
4148
4507
|
if (process.platform === "darwin") {
|
|
4149
4508
|
const spec = buildLaunchdSpec(context);
|
|
4150
|
-
if (!
|
|
4509
|
+
if (!existsSync9(spec.plistPath)) return await readServiceStatus(context);
|
|
4151
4510
|
await runLaunchctl(["bootout", `gui/${process.getuid?.() ?? 0}`, spec.plistPath]).catch(() => void 0);
|
|
4152
4511
|
return await readServiceStatus(context);
|
|
4153
4512
|
}
|
|
4154
4513
|
if (process.platform === "linux") {
|
|
4155
4514
|
const spec = buildSystemdUserSpec(context);
|
|
4156
|
-
if (!
|
|
4515
|
+
if (!existsSync9(spec.unitPath)) return await readServiceStatus(context);
|
|
4157
4516
|
await runSystemctl(["--user", "stop", spec.unitName]).catch(() => void 0);
|
|
4158
4517
|
return await readServiceStatus(context);
|
|
4159
4518
|
}
|
|
@@ -4162,7 +4521,7 @@ async function stopService(context) {
|
|
|
4162
4521
|
async function uninstallService(context) {
|
|
4163
4522
|
if (process.platform === "darwin") {
|
|
4164
4523
|
const spec = buildLaunchdSpec(context);
|
|
4165
|
-
if (
|
|
4524
|
+
if (existsSync9(spec.plistPath)) {
|
|
4166
4525
|
await runLaunchctl(["bootout", `gui/${process.getuid?.() ?? 0}`, spec.plistPath]).catch(() => void 0);
|
|
4167
4526
|
await rm2(spec.plistPath, { force: true });
|
|
4168
4527
|
}
|
|
@@ -4170,7 +4529,7 @@ async function uninstallService(context) {
|
|
|
4170
4529
|
}
|
|
4171
4530
|
if (process.platform === "linux") {
|
|
4172
4531
|
const spec = buildSystemdUserSpec(context);
|
|
4173
|
-
if (
|
|
4532
|
+
if (existsSync9(spec.unitPath)) {
|
|
4174
4533
|
await runSystemctl(["--user", "disable", "--now", spec.unitName]).catch(() => void 0);
|
|
4175
4534
|
await rm2(spec.unitPath, { force: true });
|
|
4176
4535
|
await runSystemctl(["--user", "daemon-reload"]).catch(() => void 0);
|
|
@@ -4213,7 +4572,7 @@ async function tailServiceLogs(context) {
|
|
|
4213
4572
|
async function readServiceStatus(context) {
|
|
4214
4573
|
if (process.platform === "darwin") {
|
|
4215
4574
|
const spec = buildLaunchdSpec(context);
|
|
4216
|
-
const installed =
|
|
4575
|
+
const installed = existsSync9(spec.plistPath);
|
|
4217
4576
|
const running = installed ? await runLaunchctl(["print", `gui/${process.getuid?.() ?? 0}/${spec.label}`]).then(() => true, () => false) : false;
|
|
4218
4577
|
return {
|
|
4219
4578
|
manager: "launchd",
|
|
@@ -4227,7 +4586,7 @@ async function readServiceStatus(context) {
|
|
|
4227
4586
|
}
|
|
4228
4587
|
if (process.platform === "linux") {
|
|
4229
4588
|
const spec = buildSystemdUserSpec(context);
|
|
4230
|
-
const installed =
|
|
4589
|
+
const installed = existsSync9(spec.unitPath);
|
|
4231
4590
|
const running = installed ? await runSystemctl(["--user", "is-active", "--quiet", spec.unitName]).then(() => true, () => false) : false;
|
|
4232
4591
|
return {
|
|
4233
4592
|
manager: "systemd-user",
|
|
@@ -4363,16 +4722,16 @@ async function runSystemctl(args) {
|
|
|
4363
4722
|
await execFileAsync("systemctl", args);
|
|
4364
4723
|
}
|
|
4365
4724
|
async function ensureFileExists(path, errorCode) {
|
|
4366
|
-
if (!
|
|
4725
|
+
if (!existsSync9(path)) throw new Error(errorCode);
|
|
4367
4726
|
await readFile6(path, "utf8");
|
|
4368
4727
|
}
|
|
4369
4728
|
async function tailFile(path, lines) {
|
|
4370
|
-
if (!
|
|
4729
|
+
if (!existsSync9(path)) return "";
|
|
4371
4730
|
const content = await readFile6(path, "utf8");
|
|
4372
4731
|
return content.split(/\r?\n/).slice(-lines).join("\n").trim();
|
|
4373
4732
|
}
|
|
4374
4733
|
async function tailFiles(paths) {
|
|
4375
|
-
const readers = paths.filter((path) =>
|
|
4734
|
+
const readers = paths.filter((path) => existsSync9(path)).map((path) => {
|
|
4376
4735
|
const rl = createInterface({
|
|
4377
4736
|
input: createReadStream(path, { encoding: "utf8", start: Math.max(0, fileSizeHint(path) - 8192) }),
|
|
4378
4737
|
crlfDelay: Infinity
|
|
@@ -4386,7 +4745,7 @@ async function tailFiles(paths) {
|
|
|
4386
4745
|
if (process.platform !== "linux" && process.platform !== "darwin") {
|
|
4387
4746
|
return;
|
|
4388
4747
|
}
|
|
4389
|
-
const child = spawn6("tail", ["-f", ...paths.filter((path) =>
|
|
4748
|
+
const child = spawn6("tail", ["-f", ...paths.filter((path) => existsSync9(path))], { stdio: ["ignore", "pipe", "inherit"] });
|
|
4390
4749
|
child.stdout.on("data", (chunk) => {
|
|
4391
4750
|
process.stdout.write(String(chunk));
|
|
4392
4751
|
});
|
|
@@ -4459,7 +4818,7 @@ async function main() {
|
|
|
4459
4818
|
}
|
|
4460
4819
|
}
|
|
4461
4820
|
async function cmdStart() {
|
|
4462
|
-
if (!
|
|
4821
|
+
if (!existsSync10(webRoot)) {
|
|
4463
4822
|
console.error(`\u627E\u4E0D\u5230\u524D\u7AEF\u6784\u5EFA\u4EA7\u7269: ${webRoot}`);
|
|
4464
4823
|
console.error("\u8BF7\u91CD\u65B0\u5B89\u88C5\u5B8C\u6574\u7684 npm \u5305\u3002");
|
|
4465
4824
|
process.exitCode = 1;
|
|
@@ -4489,7 +4848,7 @@ async function cmdTail() {
|
|
|
4489
4848
|
await tailServiceLogs(createServiceContext());
|
|
4490
4849
|
}
|
|
4491
4850
|
async function cmdDaemon() {
|
|
4492
|
-
if (!
|
|
4851
|
+
if (!existsSync10(webRoot)) {
|
|
4493
4852
|
console.error(`\u627E\u4E0D\u5230\u524D\u7AEF\u6784\u5EFA\u4EA7\u7269: ${webRoot}`);
|
|
4494
4853
|
process.exitCode = 1;
|
|
4495
4854
|
return;
|
|
@@ -4501,8 +4860,8 @@ async function cmdDaemon() {
|
|
|
4501
4860
|
function cmdInit() {
|
|
4502
4861
|
const configPath = process.env.BRIDGE_CONFIG ?? defaultConfigPath();
|
|
4503
4862
|
const dir = dirname12(configPath);
|
|
4504
|
-
|
|
4505
|
-
if (
|
|
4863
|
+
mkdirSync7(dir, { recursive: true });
|
|
4864
|
+
if (existsSync10(configPath)) {
|
|
4506
4865
|
console.log(`\u914D\u7F6E\u5DF2\u5B58\u5728\uFF0C\u672A\u8986\u76D6: ${configPath}`);
|
|
4507
4866
|
return;
|
|
4508
4867
|
}
|
|
@@ -4519,7 +4878,7 @@ function cmdInit() {
|
|
|
4519
4878
|
defaultWorkspace: process.cwd()
|
|
4520
4879
|
}
|
|
4521
4880
|
};
|
|
4522
|
-
|
|
4881
|
+
writeFileSync7(configPath, `${JSON.stringify(template, null, 2)}
|
|
4523
4882
|
`, "utf8");
|
|
4524
4883
|
console.log(`\u5DF2\u521B\u5EFA\u914D\u7F6E: ${configPath}`);
|
|
4525
4884
|
console.log("\u8BF7\u586B\u5165\u5FAE\u4FE1 token / accountId \u540E\u8FD0\u884C: claude-codex-wechat start");
|
|
@@ -4527,13 +4886,13 @@ function cmdInit() {
|
|
|
4527
4886
|
async function cmdDoctor() {
|
|
4528
4887
|
const configPath = process.env.BRIDGE_CONFIG ?? defaultConfigPath();
|
|
4529
4888
|
console.log("claude-codex-wechat doctor\n");
|
|
4530
|
-
report("\u914D\u7F6E\u6587\u4EF6",
|
|
4531
|
-
report("\u524D\u7AEF\u4EA7\u7269",
|
|
4889
|
+
report("\u914D\u7F6E\u6587\u4EF6", existsSync10(configPath) ? configPath : `\u7F3A\u5931 (${configPath})\uFF0C\u8FD0\u884C init \u521B\u5EFA`);
|
|
4890
|
+
report("\u524D\u7AEF\u4EA7\u7269", existsSync10(webRoot) ? webRoot : `\u7F3A\u5931 (${webRoot})`);
|
|
4532
4891
|
const claude = await findExecutable("claude");
|
|
4533
4892
|
report("claude \u53EF\u6267\u884C", claude ?? "\u672A\u627E\u5230\uFF0C\u9700\u5148\u5B89\u88C5\u5E76\u767B\u5F55 Claude Code");
|
|
4534
4893
|
const codex = await findExecutable("codex");
|
|
4535
4894
|
report("codex \u53EF\u6267\u884C", codex ?? "\u672A\u627E\u5230\uFF08\u4EC5\u4F7F\u7528 Claude \u65F6\u53EF\u5FFD\u7565\uFF09");
|
|
4536
|
-
if (
|
|
4895
|
+
if (existsSync10(configPath)) {
|
|
4537
4896
|
const config = loadBridgeConfig(configPath);
|
|
4538
4897
|
const wechat = config.wechat;
|
|
4539
4898
|
report("\u5FAE\u4FE1\u542F\u7528", wechat?.enabled ? "\u662F" : "\u5426");
|
|
@@ -4543,12 +4902,12 @@ async function cmdDoctor() {
|
|
|
4543
4902
|
}
|
|
4544
4903
|
function cmdPrintConfig() {
|
|
4545
4904
|
const configPath = process.env.BRIDGE_CONFIG ?? defaultConfigPath();
|
|
4546
|
-
if (!
|
|
4905
|
+
if (!existsSync10(configPath)) {
|
|
4547
4906
|
console.error(`\u914D\u7F6E\u4E0D\u5B58\u5728: ${configPath}\uFF08\u8FD0\u884C init \u521B\u5EFA\uFF09`);
|
|
4548
4907
|
process.exitCode = 1;
|
|
4549
4908
|
return;
|
|
4550
4909
|
}
|
|
4551
|
-
console.log(
|
|
4910
|
+
console.log(readFileSync8(configPath, "utf8"));
|
|
4552
4911
|
}
|
|
4553
4912
|
async function cmdUninstall() {
|
|
4554
4913
|
const status = await uninstallService(createServiceContext());
|