claude-codex-wechat 0.1.13 → 0.1.15
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 +467 -76
- package/dist/web/assets/index-DBTko96w.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,20 @@ 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 enabled = record.enabled === true;
|
|
110
|
+
const relayRecord = record.relay && typeof record.relay === "object" ? record.relay : void 0;
|
|
111
|
+
const relay = relayRecord ? {
|
|
112
|
+
...typeof relayRecord.serverUrl === "string" && relayRecord.serverUrl.trim() ? { serverUrl: relayRecord.serverUrl } : {},
|
|
113
|
+
...typeof relayRecord.authToken === "string" && relayRecord.authToken.trim() ? { authToken: relayRecord.authToken } : {}
|
|
114
|
+
} : void 0;
|
|
115
|
+
if (!enabled && !relay) return void 0;
|
|
116
|
+
return {
|
|
117
|
+
enabled,
|
|
118
|
+
...relay ? { relay } : {}
|
|
119
|
+
};
|
|
120
|
+
}
|
|
106
121
|
function normalizeProviderCommand(raw, envFallback) {
|
|
107
122
|
const record = raw && typeof raw === "object" ? raw : {};
|
|
108
123
|
const command = typeof record.command === "string" && record.command ? record.command : typeof envFallback === "string" && envFallback ? envFallback : void 0;
|
|
@@ -125,6 +140,8 @@ function normalizeWechatConfig(raw, env) {
|
|
|
125
140
|
|
|
126
141
|
// src/daemon/configPersistence.ts
|
|
127
142
|
import { mkdir, readFile, rm, writeFile } from "node:fs/promises";
|
|
143
|
+
import { randomBytes } from "node:crypto";
|
|
144
|
+
import { existsSync as existsSync2, mkdirSync, readFileSync as readFileSync2, writeFileSync } from "node:fs";
|
|
128
145
|
import { dirname } from "node:path";
|
|
129
146
|
async function persistWechatCredentialsToConfigFile(input) {
|
|
130
147
|
const currentConfig = await readConfigFile(input.configPath);
|
|
@@ -152,18 +169,52 @@ async function deleteConfigFile(configPath) {
|
|
|
152
169
|
}
|
|
153
170
|
async function persistBridgeDefaultsToConfigFile(input) {
|
|
154
171
|
const currentConfig = await readConfigFile(input.configPath);
|
|
172
|
+
const currentBridge = isRecord(currentConfig.bridge) ? currentConfig.bridge : void 0;
|
|
155
173
|
const nextConfig = {
|
|
156
174
|
...currentConfig,
|
|
157
175
|
bridge: {
|
|
158
|
-
...
|
|
159
|
-
defaultProvider: input.defaultProvider,
|
|
160
|
-
defaultWorkspace: input.defaultWorkspace
|
|
161
|
-
}
|
|
176
|
+
...currentBridge ?? {},
|
|
177
|
+
...input.defaultProvider ? { defaultProvider: input.defaultProvider } : {},
|
|
178
|
+
...input.defaultWorkspace ? { defaultWorkspace: input.defaultWorkspace } : {}
|
|
179
|
+
},
|
|
180
|
+
...input.tunnel ? {
|
|
181
|
+
tunnel: {
|
|
182
|
+
enabled: input.tunnel.enabled === true,
|
|
183
|
+
...input.tunnel.relay ? {
|
|
184
|
+
relay: {
|
|
185
|
+
...input.tunnel.relay.serverUrl ? { serverUrl: input.tunnel.relay.serverUrl } : {},
|
|
186
|
+
...input.tunnel.relay.authToken ? { authToken: input.tunnel.relay.authToken } : {}
|
|
187
|
+
}
|
|
188
|
+
} : {}
|
|
189
|
+
}
|
|
190
|
+
} : {}
|
|
162
191
|
};
|
|
163
192
|
await mkdir(dirname(input.configPath), { recursive: true });
|
|
164
193
|
await writeFile(input.configPath, `${JSON.stringify(nextConfig, null, 2)}
|
|
165
194
|
`, "utf8");
|
|
166
195
|
}
|
|
196
|
+
function ensureRelayAuthTokenSync(input) {
|
|
197
|
+
const currentConfig = readConfigFileSync(input.configPath);
|
|
198
|
+
const currentTunnel = isRecord(currentConfig.tunnel) ? currentConfig.tunnel : void 0;
|
|
199
|
+
const currentRelay = isRecord(currentTunnel?.relay) ? currentTunnel.relay : void 0;
|
|
200
|
+
const existing = typeof currentRelay?.authToken === "string" && currentRelay.authToken.trim() ? currentRelay.authToken.trim() : "";
|
|
201
|
+
if (existing) return existing;
|
|
202
|
+
const authToken = `clrt_${randomBytes(12).toString("hex")}`;
|
|
203
|
+
const nextConfig = {
|
|
204
|
+
...currentConfig,
|
|
205
|
+
tunnel: {
|
|
206
|
+
...currentTunnel ?? {},
|
|
207
|
+
relay: {
|
|
208
|
+
...currentRelay ?? {},
|
|
209
|
+
authToken
|
|
210
|
+
}
|
|
211
|
+
}
|
|
212
|
+
};
|
|
213
|
+
mkdirSync(dirname(input.configPath), { recursive: true });
|
|
214
|
+
writeFileSync(input.configPath, `${JSON.stringify(nextConfig, null, 2)}
|
|
215
|
+
`, "utf8");
|
|
216
|
+
return authToken;
|
|
217
|
+
}
|
|
167
218
|
async function persistProviderCommandsToConfigFile(input) {
|
|
168
219
|
const currentConfig = await readConfigFile(input.configPath);
|
|
169
220
|
const normalizedProviders = normalizeProvidersForPersistence(input.providers);
|
|
@@ -185,6 +236,17 @@ async function readConfigFile(path) {
|
|
|
185
236
|
throw error;
|
|
186
237
|
}
|
|
187
238
|
}
|
|
239
|
+
function readConfigFileSync(path) {
|
|
240
|
+
try {
|
|
241
|
+
if (!existsSync2(path)) return {};
|
|
242
|
+
const raw = readFileSync2(path, "utf8");
|
|
243
|
+
const parsed = JSON.parse(raw);
|
|
244
|
+
return isRecord(parsed) ? parsed : {};
|
|
245
|
+
} catch (error) {
|
|
246
|
+
if (isMissingFileError(error)) return {};
|
|
247
|
+
throw error;
|
|
248
|
+
}
|
|
249
|
+
}
|
|
188
250
|
function isRecord(value) {
|
|
189
251
|
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
190
252
|
}
|
|
@@ -202,7 +264,7 @@ function normalizeProvidersForPersistence(providers) {
|
|
|
202
264
|
}
|
|
203
265
|
|
|
204
266
|
// src/channels/weixin-direct/weixinStateStore.ts
|
|
205
|
-
import { existsSync as
|
|
267
|
+
import { existsSync as existsSync3, mkdirSync as mkdirSync2, readFileSync as readFileSync3, writeFileSync as writeFileSync2 } from "node:fs";
|
|
206
268
|
import { dirname as dirname2 } from "node:path";
|
|
207
269
|
var PUSH_QUOTA_LIMIT = 10;
|
|
208
270
|
var PUSH_WINDOW_MS = 24 * 60 * 60 * 1e3;
|
|
@@ -296,9 +358,9 @@ var FileWeixinStateStore = class {
|
|
|
296
358
|
return this.readState().bridge?.weixinChannel ?? {};
|
|
297
359
|
}
|
|
298
360
|
readState() {
|
|
299
|
-
if (!
|
|
361
|
+
if (!existsSync3(this.configPath)) return {};
|
|
300
362
|
try {
|
|
301
|
-
const raw = JSON.parse(
|
|
363
|
+
const raw = JSON.parse(readFileSync3(this.configPath, "utf8"));
|
|
302
364
|
return raw && typeof raw === "object" ? raw : {};
|
|
303
365
|
} catch {
|
|
304
366
|
return {};
|
|
@@ -309,8 +371,8 @@ var FileWeixinStateStore = class {
|
|
|
309
371
|
...state,
|
|
310
372
|
bridge: { ...state.bridge ?? {}, weixinChannel: channel }
|
|
311
373
|
};
|
|
312
|
-
|
|
313
|
-
|
|
374
|
+
mkdirSync2(dirname2(this.configPath), { recursive: true });
|
|
375
|
+
writeFileSync2(this.configPath, `${JSON.stringify(next, null, 2)}
|
|
314
376
|
`, "utf8");
|
|
315
377
|
}
|
|
316
378
|
};
|
|
@@ -608,7 +670,7 @@ async function normalizeClaudeSessionFileForResume(sessionPath) {
|
|
|
608
670
|
// src/providers/codex/nativeSessions.ts
|
|
609
671
|
import Database from "better-sqlite3";
|
|
610
672
|
import { readdir as readdir2, readFile as readFile3, stat as stat2 } from "node:fs/promises";
|
|
611
|
-
import { existsSync as
|
|
673
|
+
import { existsSync as existsSync4 } from "node:fs";
|
|
612
674
|
import { homedir as homedir3 } from "node:os";
|
|
613
675
|
import { basename as basename2, join as join3 } from "node:path";
|
|
614
676
|
function resolveCodexSessionsRoot(env = process.env) {
|
|
@@ -710,7 +772,7 @@ async function readCodexSessionIndex(env) {
|
|
|
710
772
|
function readCodexThreadMetadata(env) {
|
|
711
773
|
const dbPath = join3(env.CODEX_HOME || join3(env.HOME || homedir3(), ".codex"), "state_5.sqlite");
|
|
712
774
|
const metadata = /* @__PURE__ */ new Map();
|
|
713
|
-
if (!
|
|
775
|
+
if (!existsSync4(dbPath)) return metadata;
|
|
714
776
|
let db = null;
|
|
715
777
|
try {
|
|
716
778
|
db = new Database(dbPath, { readonly: true });
|
|
@@ -814,7 +876,7 @@ async function upsertCodexSessionIndexEntry(input, env = process.env) {
|
|
|
814
876
|
|
|
815
877
|
// src/providers/codex/nativeThreads.ts
|
|
816
878
|
import Database2 from "better-sqlite3";
|
|
817
|
-
import { existsSync as
|
|
879
|
+
import { existsSync as existsSync5 } from "node:fs";
|
|
818
880
|
import { readFile as readFile5, writeFile as writeFile4 } from "node:fs/promises";
|
|
819
881
|
import { homedir as homedir5 } from "node:os";
|
|
820
882
|
import { join as join5 } from "node:path";
|
|
@@ -824,7 +886,7 @@ function resolveCodexStateDbPath(env = process.env) {
|
|
|
824
886
|
async function syncCodexThreadForResume(input) {
|
|
825
887
|
const env = input.env ?? process.env;
|
|
826
888
|
const dbPath = resolveCodexStateDbPath(env);
|
|
827
|
-
if (!
|
|
889
|
+
if (!existsSync5(dbPath)) return false;
|
|
828
890
|
for (let attempt = 0; attempt < 50; attempt += 1) {
|
|
829
891
|
const db = new Database2(dbPath);
|
|
830
892
|
try {
|
|
@@ -863,7 +925,7 @@ async function syncCodexThreadForResume(input) {
|
|
|
863
925
|
return false;
|
|
864
926
|
}
|
|
865
927
|
async function syncCodexRolloutSessionMeta(rolloutPath) {
|
|
866
|
-
if (!
|
|
928
|
+
if (!existsSync5(rolloutPath)) return;
|
|
867
929
|
const content = await readFile5(rolloutPath, "utf8");
|
|
868
930
|
const lines = content.split(/\r?\n/);
|
|
869
931
|
if (!lines[0]?.trim()) return;
|
|
@@ -1405,6 +1467,40 @@ async function resolveRecoverableProviderResumeHistorySynced(session) {
|
|
|
1405
1467
|
});
|
|
1406
1468
|
}
|
|
1407
1469
|
|
|
1470
|
+
// src/admin/tunnelRoutes.ts
|
|
1471
|
+
function registerTunnelRoutes(input) {
|
|
1472
|
+
const getStatus = async () => await input.tunnelManager.getStatus();
|
|
1473
|
+
input.app.get("/api/tunnel/status", getStatus);
|
|
1474
|
+
const start = async () => {
|
|
1475
|
+
input.defaults.tunnel.enabled = true;
|
|
1476
|
+
await persistBridgeDefaultsToConfigFile({
|
|
1477
|
+
configPath: input.configPath,
|
|
1478
|
+
tunnel: input.defaults.tunnel
|
|
1479
|
+
});
|
|
1480
|
+
return await input.tunnelManager.start();
|
|
1481
|
+
};
|
|
1482
|
+
input.app.post("/api/tunnel/start", start);
|
|
1483
|
+
const stop = async () => {
|
|
1484
|
+
input.defaults.tunnel.enabled = false;
|
|
1485
|
+
await persistBridgeDefaultsToConfigFile({
|
|
1486
|
+
configPath: input.configPath,
|
|
1487
|
+
tunnel: input.defaults.tunnel
|
|
1488
|
+
});
|
|
1489
|
+
return await input.tunnelManager.stop();
|
|
1490
|
+
};
|
|
1491
|
+
input.app.post("/api/tunnel/stop", stop);
|
|
1492
|
+
const setEnabled = async (request) => {
|
|
1493
|
+
const enabled = request.body?.enabled === true;
|
|
1494
|
+
input.defaults.tunnel.enabled = enabled;
|
|
1495
|
+
await persistBridgeDefaultsToConfigFile({
|
|
1496
|
+
configPath: input.configPath,
|
|
1497
|
+
tunnel: input.defaults.tunnel
|
|
1498
|
+
});
|
|
1499
|
+
return await input.tunnelManager.setEnabled(enabled);
|
|
1500
|
+
};
|
|
1501
|
+
input.app.post("/api/tunnel/settings", setEnabled);
|
|
1502
|
+
}
|
|
1503
|
+
|
|
1408
1504
|
// src/admin/settingsRoutes.ts
|
|
1409
1505
|
function registerSettingsRoutes(input) {
|
|
1410
1506
|
input.app.get("/api/settings", async () => input.defaults);
|
|
@@ -1412,22 +1508,53 @@ function registerSettingsRoutes(input) {
|
|
|
1412
1508
|
const current = { ...input.defaults };
|
|
1413
1509
|
const next = normalizeSettings({
|
|
1414
1510
|
...current,
|
|
1415
|
-
...request.body
|
|
1511
|
+
...request.body,
|
|
1512
|
+
tunnel: {
|
|
1513
|
+
...current.tunnel,
|
|
1514
|
+
...request.body.tunnel && typeof request.body.tunnel === "object" ? request.body.tunnel : {},
|
|
1515
|
+
relay: {
|
|
1516
|
+
...current.tunnel?.relay ?? {},
|
|
1517
|
+
...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 : {}
|
|
1518
|
+
}
|
|
1519
|
+
}
|
|
1416
1520
|
}, current.defaultWorkspace);
|
|
1521
|
+
if (!next.tunnel.relay?.authToken) {
|
|
1522
|
+
const authToken = ensureRelayAuthTokenSync({ configPath: input.configPath });
|
|
1523
|
+
next.tunnel = {
|
|
1524
|
+
...next.tunnel,
|
|
1525
|
+
relay: {
|
|
1526
|
+
...next.tunnel.relay ?? {},
|
|
1527
|
+
authToken
|
|
1528
|
+
}
|
|
1529
|
+
};
|
|
1530
|
+
}
|
|
1417
1531
|
input.defaults.defaultProvider = next.defaultProvider;
|
|
1418
1532
|
input.defaults.defaultWorkspace = next.defaultWorkspace;
|
|
1533
|
+
input.defaults.tunnel = next.tunnel;
|
|
1419
1534
|
await persistBridgeDefaultsToConfigFile({
|
|
1420
1535
|
configPath: input.configPath,
|
|
1421
1536
|
defaultProvider: next.defaultProvider,
|
|
1422
|
-
defaultWorkspace: next.defaultWorkspace
|
|
1537
|
+
defaultWorkspace: next.defaultWorkspace,
|
|
1538
|
+
tunnel: next.tunnel
|
|
1423
1539
|
});
|
|
1424
1540
|
return { ok: true };
|
|
1425
1541
|
});
|
|
1426
1542
|
}
|
|
1427
1543
|
function normalizeSettings(input, defaultWorkspace) {
|
|
1544
|
+
const tunnel = input.tunnel && typeof input.tunnel === "object" ? input.tunnel : {};
|
|
1545
|
+
const relay = tunnel.relay && typeof tunnel.relay === "object" ? tunnel.relay : {};
|
|
1428
1546
|
return {
|
|
1429
1547
|
defaultProvider: input.defaultProvider === "codex" ? "codex" : "claude-code",
|
|
1430
|
-
defaultWorkspace: typeof input.defaultWorkspace === "string" && input.defaultWorkspace.trim() ? input.defaultWorkspace : defaultWorkspace
|
|
1548
|
+
defaultWorkspace: typeof input.defaultWorkspace === "string" && input.defaultWorkspace.trim() ? input.defaultWorkspace : defaultWorkspace,
|
|
1549
|
+
tunnel: {
|
|
1550
|
+
enabled: tunnel.enabled === true,
|
|
1551
|
+
...typeof relay.serverUrl === "string" && relay.serverUrl.trim() || typeof relay.authToken === "string" && relay.authToken.trim() ? {
|
|
1552
|
+
relay: {
|
|
1553
|
+
...typeof relay.serverUrl === "string" && relay.serverUrl.trim() ? { serverUrl: relay.serverUrl } : {},
|
|
1554
|
+
...typeof relay.authToken === "string" && relay.authToken.trim() ? { authToken: relay.authToken } : {}
|
|
1555
|
+
}
|
|
1556
|
+
} : {}
|
|
1557
|
+
}
|
|
1431
1558
|
};
|
|
1432
1559
|
}
|
|
1433
1560
|
|
|
@@ -1967,7 +2094,7 @@ function chunkText(text, limit) {
|
|
|
1967
2094
|
}
|
|
1968
2095
|
|
|
1969
2096
|
// src/channels/weixin-direct/mediaDownloader.ts
|
|
1970
|
-
import { mkdirSync as
|
|
2097
|
+
import { mkdirSync as mkdirSync3, writeFileSync as writeFileSync3 } from "node:fs";
|
|
1971
2098
|
import { dirname as dirname6 } from "node:path";
|
|
1972
2099
|
|
|
1973
2100
|
// src/channels/weixin-direct/mediaCrypto.ts
|
|
@@ -2031,8 +2158,8 @@ var WeixinMediaDownloader = class {
|
|
|
2031
2158
|
} catch {
|
|
2032
2159
|
return { ok: false, reason: "decrypt_failed" };
|
|
2033
2160
|
}
|
|
2034
|
-
|
|
2035
|
-
|
|
2161
|
+
mkdirSync3(dirname6(input.destPath), { recursive: true });
|
|
2162
|
+
writeFileSync3(input.destPath, plaintext);
|
|
2036
2163
|
return { ok: true, localPath: input.destPath, bytes: plaintext.length };
|
|
2037
2164
|
}
|
|
2038
2165
|
};
|
|
@@ -3132,8 +3259,11 @@ var BRIDGE_COMMAND_HELP_GROUPS = [
|
|
|
3132
3259
|
]
|
|
3133
3260
|
}
|
|
3134
3261
|
];
|
|
3135
|
-
function buildBridgeCommandHelpMarkdown() {
|
|
3262
|
+
function buildBridgeCommandHelpMarkdown(input) {
|
|
3136
3263
|
const lines = ["**\u53EF\u7528\u547D\u4EE4**", "", BRIDGE_COMMAND_HELP_INTRO];
|
|
3264
|
+
if (input?.publicUrl) {
|
|
3265
|
+
lines.push("", `**\u8BBF\u95EE\u5730\u5740**`, "", `[${input.publicUrl}](${input.publicUrl})`);
|
|
3266
|
+
}
|
|
3137
3267
|
for (const group of BRIDGE_COMMAND_HELP_GROUPS) {
|
|
3138
3268
|
lines.push("", `**${group.title}**${group.note ? `\uFF08${group.note}\uFF09` : ""}`);
|
|
3139
3269
|
for (const entry of group.entries) {
|
|
@@ -3144,7 +3274,7 @@ function buildBridgeCommandHelpMarkdown() {
|
|
|
3144
3274
|
}
|
|
3145
3275
|
|
|
3146
3276
|
// src/session/currentConversationStore.ts
|
|
3147
|
-
import { existsSync as
|
|
3277
|
+
import { existsSync as existsSync6, mkdirSync as mkdirSync4, readFileSync as readFileSync4, writeFileSync as writeFileSync4 } from "node:fs";
|
|
3148
3278
|
import { dirname as dirname7 } from "node:path";
|
|
3149
3279
|
import { nanoid as nanoid2 } from "nanoid";
|
|
3150
3280
|
var CurrentConversationStore = class {
|
|
@@ -3204,8 +3334,8 @@ var CurrentConversationStore = class {
|
|
|
3204
3334
|
this.writeState(state);
|
|
3205
3335
|
}
|
|
3206
3336
|
readState() {
|
|
3207
|
-
if (!
|
|
3208
|
-
const raw = JSON.parse(
|
|
3337
|
+
if (!existsSync6(this.configPath)) return {};
|
|
3338
|
+
const raw = JSON.parse(readFileSync4(this.configPath, "utf8"));
|
|
3209
3339
|
return raw && typeof raw === "object" ? raw : {};
|
|
3210
3340
|
}
|
|
3211
3341
|
writeCurrent(record) {
|
|
@@ -3227,8 +3357,8 @@ var CurrentConversationStore = class {
|
|
|
3227
3357
|
this.writeState(state);
|
|
3228
3358
|
}
|
|
3229
3359
|
writeState(state) {
|
|
3230
|
-
|
|
3231
|
-
|
|
3360
|
+
mkdirSync4(dirname7(this.configPath), { recursive: true });
|
|
3361
|
+
writeFileSync4(this.configPath, `${JSON.stringify(state, null, 2)}
|
|
3232
3362
|
`, "utf8");
|
|
3233
3363
|
}
|
|
3234
3364
|
};
|
|
@@ -3518,10 +3648,11 @@ var MessageRouter = class _MessageRouter {
|
|
|
3518
3648
|
}
|
|
3519
3649
|
async handleCommand(chatId, user, command) {
|
|
3520
3650
|
if (command.kind === "help") {
|
|
3651
|
+
const helpAddress = await this.options.getHelpAddress?.();
|
|
3521
3652
|
await this.sendToChat({
|
|
3522
3653
|
chatId,
|
|
3523
3654
|
kind: "markdown",
|
|
3524
|
-
text: buildBridgeCommandHelpMarkdown()
|
|
3655
|
+
text: buildBridgeCommandHelpMarkdown({ publicUrl: helpAddress })
|
|
3525
3656
|
});
|
|
3526
3657
|
return;
|
|
3527
3658
|
}
|
|
@@ -3672,6 +3803,13 @@ var MessageRouter = class _MessageRouter {
|
|
|
3672
3803
|
providerSessionId: session.providerSessionId,
|
|
3673
3804
|
cwd: session.cwd
|
|
3674
3805
|
});
|
|
3806
|
+
if (session.providerId === "codex") {
|
|
3807
|
+
await upsertCodexSessionIndexEntry({
|
|
3808
|
+
sessionId: session.providerSessionId,
|
|
3809
|
+
threadName: session.resumeTitle
|
|
3810
|
+
});
|
|
3811
|
+
return;
|
|
3812
|
+
}
|
|
3675
3813
|
if (session.providerId === "claude-code") {
|
|
3676
3814
|
await ensureClaudeSessionBridgeMetadata({
|
|
3677
3815
|
sessionId: session.providerSessionId,
|
|
@@ -3733,7 +3871,7 @@ function composeInboundText(content) {
|
|
|
3733
3871
|
}
|
|
3734
3872
|
|
|
3735
3873
|
// src/storage/lastProviderSessionStore.ts
|
|
3736
|
-
import { existsSync as
|
|
3874
|
+
import { existsSync as existsSync7, mkdirSync as mkdirSync5, readFileSync as readFileSync5, writeFileSync as writeFileSync5 } from "node:fs";
|
|
3737
3875
|
import { dirname as dirname8 } from "node:path";
|
|
3738
3876
|
var LastProviderSessionStore = class {
|
|
3739
3877
|
constructor(configPath) {
|
|
@@ -3760,19 +3898,19 @@ var LastProviderSessionStore = class {
|
|
|
3760
3898
|
return this.readState().bridge?.lastProviderSessions ?? {};
|
|
3761
3899
|
}
|
|
3762
3900
|
readState() {
|
|
3763
|
-
if (!
|
|
3764
|
-
const raw = JSON.parse(
|
|
3901
|
+
if (!existsSync7(this.configPath)) return {};
|
|
3902
|
+
const raw = JSON.parse(readFileSync5(this.configPath, "utf8"));
|
|
3765
3903
|
return raw && typeof raw === "object" ? raw : {};
|
|
3766
3904
|
}
|
|
3767
3905
|
writeState(state) {
|
|
3768
|
-
|
|
3769
|
-
|
|
3906
|
+
mkdirSync5(dirname8(this.configPath), { recursive: true });
|
|
3907
|
+
writeFileSync5(this.configPath, `${JSON.stringify(state, null, 2)}
|
|
3770
3908
|
`, "utf8");
|
|
3771
3909
|
}
|
|
3772
3910
|
};
|
|
3773
3911
|
|
|
3774
3912
|
// src/storage/runtimeUserStore.ts
|
|
3775
|
-
import { mkdirSync as
|
|
3913
|
+
import { mkdirSync as mkdirSync6, readFileSync as readFileSync6, writeFileSync as writeFileSync6, existsSync as existsSync8 } from "node:fs";
|
|
3776
3914
|
import { dirname as dirname9 } from "node:path";
|
|
3777
3915
|
import { nanoid as nanoid3 } from "nanoid";
|
|
3778
3916
|
var RuntimeUserStore = class {
|
|
@@ -3833,13 +3971,13 @@ var RuntimeUserStore = class {
|
|
|
3833
3971
|
return { ok: true };
|
|
3834
3972
|
}
|
|
3835
3973
|
readState() {
|
|
3836
|
-
if (!
|
|
3837
|
-
const raw = JSON.parse(
|
|
3974
|
+
if (!existsSync8(this.configPath)) return {};
|
|
3975
|
+
const raw = JSON.parse(readFileSync6(this.configPath, "utf8"));
|
|
3838
3976
|
return raw && typeof raw === "object" ? raw : {};
|
|
3839
3977
|
}
|
|
3840
3978
|
writeState(state) {
|
|
3841
|
-
|
|
3842
|
-
|
|
3979
|
+
mkdirSync6(dirname9(this.configPath), { recursive: true });
|
|
3980
|
+
writeFileSync6(this.configPath, `${JSON.stringify(state, null, 2)}
|
|
3843
3981
|
`, "utf8");
|
|
3844
3982
|
}
|
|
3845
3983
|
};
|
|
@@ -3861,15 +3999,232 @@ var BridgeEventHub = class {
|
|
|
3861
3999
|
}
|
|
3862
4000
|
};
|
|
3863
4001
|
|
|
4002
|
+
// src/runtime/relayTunnelProvider.ts
|
|
4003
|
+
var RelayTunnelProvider = class {
|
|
4004
|
+
constructor(options) {
|
|
4005
|
+
this.options = options;
|
|
4006
|
+
}
|
|
4007
|
+
options;
|
|
4008
|
+
socket = null;
|
|
4009
|
+
status = {
|
|
4010
|
+
installed: true,
|
|
4011
|
+
enabled: false,
|
|
4012
|
+
running: false,
|
|
4013
|
+
status: "stopped"
|
|
4014
|
+
};
|
|
4015
|
+
static defaultCreateSocket(url) {
|
|
4016
|
+
const socket = new WebSocket(url);
|
|
4017
|
+
return {
|
|
4018
|
+
get readyState() {
|
|
4019
|
+
return socket.readyState;
|
|
4020
|
+
},
|
|
4021
|
+
send(payload) {
|
|
4022
|
+
socket.send(payload);
|
|
4023
|
+
},
|
|
4024
|
+
close() {
|
|
4025
|
+
socket.close();
|
|
4026
|
+
},
|
|
4027
|
+
on(event, listener) {
|
|
4028
|
+
socket.addEventListener(event, listener);
|
|
4029
|
+
}
|
|
4030
|
+
};
|
|
4031
|
+
}
|
|
4032
|
+
async getStatus() {
|
|
4033
|
+
return { ...this.status };
|
|
4034
|
+
}
|
|
4035
|
+
async start() {
|
|
4036
|
+
this.status = {
|
|
4037
|
+
...this.status,
|
|
4038
|
+
enabled: true,
|
|
4039
|
+
status: "starting"
|
|
4040
|
+
};
|
|
4041
|
+
const socket = this.options.createSocket(this.options.serverUrl);
|
|
4042
|
+
this.socket = socket;
|
|
4043
|
+
return await new Promise((resolve2, reject) => {
|
|
4044
|
+
let settled = false;
|
|
4045
|
+
const finishResolve = (value) => {
|
|
4046
|
+
if (settled) return;
|
|
4047
|
+
settled = true;
|
|
4048
|
+
resolve2(value);
|
|
4049
|
+
};
|
|
4050
|
+
const finishReject = (error) => {
|
|
4051
|
+
if (settled) return;
|
|
4052
|
+
settled = true;
|
|
4053
|
+
reject(error);
|
|
4054
|
+
};
|
|
4055
|
+
socket.on("open", () => {
|
|
4056
|
+
socket.send(JSON.stringify({
|
|
4057
|
+
type: "register",
|
|
4058
|
+
clientVersion: "0.1.0",
|
|
4059
|
+
targetBaseUrl: this.options.targetBaseUrl,
|
|
4060
|
+
authToken: this.options.authToken
|
|
4061
|
+
}));
|
|
4062
|
+
});
|
|
4063
|
+
socket.on("message", (raw) => {
|
|
4064
|
+
const payload = typeof raw === "string" ? JSON.parse(raw) : typeof raw?.data === "string" ? JSON.parse(raw.data) : raw;
|
|
4065
|
+
if (payload?.type === "error") {
|
|
4066
|
+
const message = typeof payload?.error === "string" && payload.error ? payload.error : "relay_error";
|
|
4067
|
+
this.status = {
|
|
4068
|
+
...this.status,
|
|
4069
|
+
running: false,
|
|
4070
|
+
status: "error",
|
|
4071
|
+
error: message
|
|
4072
|
+
};
|
|
4073
|
+
finishReject(new Error(message));
|
|
4074
|
+
return;
|
|
4075
|
+
}
|
|
4076
|
+
if (payload?.type === "registered") {
|
|
4077
|
+
const publicUrl = typeof payload?.token === "string" ? `${derivePublicBaseUrl(this.options.serverUrl)}/${payload.token}` : void 0;
|
|
4078
|
+
this.status = {
|
|
4079
|
+
installed: true,
|
|
4080
|
+
enabled: true,
|
|
4081
|
+
running: true,
|
|
4082
|
+
status: "running",
|
|
4083
|
+
...publicUrl ? { publicUrl } : {}
|
|
4084
|
+
};
|
|
4085
|
+
finishResolve({ ...this.status });
|
|
4086
|
+
return;
|
|
4087
|
+
}
|
|
4088
|
+
if (payload?.type === "request") {
|
|
4089
|
+
void this.handleRequest(payload);
|
|
4090
|
+
}
|
|
4091
|
+
});
|
|
4092
|
+
socket.on("close", () => {
|
|
4093
|
+
this.status = {
|
|
4094
|
+
...this.status,
|
|
4095
|
+
running: false,
|
|
4096
|
+
status: "error",
|
|
4097
|
+
error: "relay_disconnected"
|
|
4098
|
+
};
|
|
4099
|
+
finishReject(new Error("relay_disconnected"));
|
|
4100
|
+
});
|
|
4101
|
+
socket.on("error", (error) => {
|
|
4102
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
4103
|
+
this.status = {
|
|
4104
|
+
...this.status,
|
|
4105
|
+
running: false,
|
|
4106
|
+
status: "error",
|
|
4107
|
+
error: message
|
|
4108
|
+
};
|
|
4109
|
+
finishReject(error);
|
|
4110
|
+
});
|
|
4111
|
+
});
|
|
4112
|
+
}
|
|
4113
|
+
async stop() {
|
|
4114
|
+
this.socket?.close();
|
|
4115
|
+
this.socket = null;
|
|
4116
|
+
this.status = {
|
|
4117
|
+
installed: true,
|
|
4118
|
+
enabled: false,
|
|
4119
|
+
running: false,
|
|
4120
|
+
status: "stopped"
|
|
4121
|
+
};
|
|
4122
|
+
return { ...this.status };
|
|
4123
|
+
}
|
|
4124
|
+
async setEnabled(enabled) {
|
|
4125
|
+
return enabled ? await this.start() : await this.stop();
|
|
4126
|
+
}
|
|
4127
|
+
async handleRequest(payload) {
|
|
4128
|
+
const fetchImpl = this.options.fetchImpl ?? fetch;
|
|
4129
|
+
const response = await fetchImpl(`${this.options.targetBaseUrl}${payload.path}`, {
|
|
4130
|
+
method: payload.method,
|
|
4131
|
+
headers: payload.headers,
|
|
4132
|
+
body: payload.bodyBase64 ? Buffer.from(payload.bodyBase64, "base64") : void 0
|
|
4133
|
+
});
|
|
4134
|
+
const bodyBuffer = Buffer.from(await response.arrayBuffer());
|
|
4135
|
+
this.socket?.send(JSON.stringify({
|
|
4136
|
+
type: "response",
|
|
4137
|
+
requestId: payload.requestId,
|
|
4138
|
+
status: response.status,
|
|
4139
|
+
headers: Object.fromEntries(response.headers.entries()),
|
|
4140
|
+
bodyBase64: bodyBuffer.toString("base64")
|
|
4141
|
+
}));
|
|
4142
|
+
}
|
|
4143
|
+
};
|
|
4144
|
+
function derivePublicBaseUrl(serverUrl) {
|
|
4145
|
+
return serverUrl.replace(/^ws:\/\//, "http://").replace(/^wss:\/\//, "https://").replace(/\/agent\/?$/, "").replace(/\/+$/, "");
|
|
4146
|
+
}
|
|
4147
|
+
|
|
4148
|
+
// src/runtime/relayTunnelRouter.ts
|
|
4149
|
+
var RelayTunnelRouter = class {
|
|
4150
|
+
constructor(options) {
|
|
4151
|
+
this.options = options;
|
|
4152
|
+
}
|
|
4153
|
+
options;
|
|
4154
|
+
relayProvider = null;
|
|
4155
|
+
relayKey = "";
|
|
4156
|
+
async getStatus() {
|
|
4157
|
+
const provider = this.getOrCreateRelayProvider();
|
|
4158
|
+
if (!provider) return this.notConfiguredStatus();
|
|
4159
|
+
return await provider.getStatus();
|
|
4160
|
+
}
|
|
4161
|
+
async start() {
|
|
4162
|
+
const provider = this.getOrCreateRelayProvider();
|
|
4163
|
+
if (!provider) return this.notConfiguredStatus();
|
|
4164
|
+
return await provider.start();
|
|
4165
|
+
}
|
|
4166
|
+
async stop() {
|
|
4167
|
+
const provider = this.getOrCreateRelayProvider();
|
|
4168
|
+
if (!provider) return this.notConfiguredStatus();
|
|
4169
|
+
return await provider.stop();
|
|
4170
|
+
}
|
|
4171
|
+
async setEnabled(enabled) {
|
|
4172
|
+
const provider = this.getOrCreateRelayProvider();
|
|
4173
|
+
if (!provider) return this.notConfiguredStatus();
|
|
4174
|
+
return await provider.setEnabled(enabled);
|
|
4175
|
+
}
|
|
4176
|
+
getOrCreateRelayProvider() {
|
|
4177
|
+
const relay = this.options.defaults.tunnel.relay;
|
|
4178
|
+
if (!relay?.serverUrl || !relay.authToken) return null;
|
|
4179
|
+
const key = `${relay.serverUrl}|${relay.authToken}|${this.options.bridgePort}`;
|
|
4180
|
+
if (this.relayProvider && this.relayKey === key) return this.relayProvider;
|
|
4181
|
+
this.relayKey = key;
|
|
4182
|
+
this.relayProvider = new RelayTunnelProvider({
|
|
4183
|
+
serverUrl: relay.serverUrl,
|
|
4184
|
+
authToken: relay.authToken,
|
|
4185
|
+
targetBaseUrl: `http://127.0.0.1:${this.options.bridgePort}`,
|
|
4186
|
+
createSocket: RelayTunnelProvider.defaultCreateSocket
|
|
4187
|
+
});
|
|
4188
|
+
return this.relayProvider;
|
|
4189
|
+
}
|
|
4190
|
+
notConfiguredStatus() {
|
|
4191
|
+
return {
|
|
4192
|
+
installed: false,
|
|
4193
|
+
enabled: false,
|
|
4194
|
+
running: false,
|
|
4195
|
+
status: "error",
|
|
4196
|
+
error: "relay_not_configured"
|
|
4197
|
+
};
|
|
4198
|
+
}
|
|
4199
|
+
};
|
|
4200
|
+
|
|
3864
4201
|
// src/daemon/server.ts
|
|
3865
4202
|
function createDaemonServer(options = {}) {
|
|
3866
4203
|
const app = Fastify({ logger: true });
|
|
3867
4204
|
const events = new BridgeEventHub();
|
|
4205
|
+
const configPath = options.configPath ?? process.env.BRIDGE_CONFIG ?? join9(mkdtempSync(join9(tmpdir2(), "claude-codex-wechat-")), "config.json");
|
|
4206
|
+
const persistedConfig = loadBridgeConfig(configPath);
|
|
3868
4207
|
const bridgeDefaults = {
|
|
3869
|
-
defaultProvider: options.bridgeDefaults?.defaultProvider ?? "claude-code",
|
|
3870
|
-
defaultWorkspace: options.bridgeDefaults?.defaultWorkspace ?? process.cwd()
|
|
4208
|
+
defaultProvider: options.bridgeDefaults?.defaultProvider ?? persistedConfig.bridge?.defaultProvider ?? "claude-code",
|
|
4209
|
+
defaultWorkspace: options.bridgeDefaults?.defaultWorkspace ?? persistedConfig.bridge?.defaultWorkspace ?? process.cwd(),
|
|
4210
|
+
tunnel: options.bridgeDefaults?.tunnel ?? {
|
|
4211
|
+
enabled: persistedConfig.tunnel?.enabled === true,
|
|
4212
|
+
...persistedConfig.tunnel?.relay ? { relay: persistedConfig.tunnel.relay } : {}
|
|
4213
|
+
}
|
|
3871
4214
|
};
|
|
3872
|
-
const
|
|
4215
|
+
const existingRelayAuthToken = bridgeDefaults.tunnel.relay?.authToken?.trim();
|
|
4216
|
+
if (existingRelayAuthToken) {
|
|
4217
|
+
bridgeDefaults.tunnel.relay = {
|
|
4218
|
+
...bridgeDefaults.tunnel.relay ?? {},
|
|
4219
|
+
authToken: existingRelayAuthToken
|
|
4220
|
+
};
|
|
4221
|
+
} else {
|
|
4222
|
+
const authToken = ensureRelayAuthTokenSync({ configPath });
|
|
4223
|
+
bridgeDefaults.tunnel.relay = {
|
|
4224
|
+
...bridgeDefaults.tunnel.relay ?? {},
|
|
4225
|
+
authToken
|
|
4226
|
+
};
|
|
4227
|
+
}
|
|
3873
4228
|
const conversation = new CurrentConversationStore(configPath, {
|
|
3874
4229
|
defaultCwd: bridgeDefaults.defaultWorkspace,
|
|
3875
4230
|
defaultProviderId: bridgeDefaults.defaultProvider
|
|
@@ -3911,6 +4266,10 @@ function createDaemonServer(options = {}) {
|
|
|
3911
4266
|
await managedWechatChannel.sendMessage({ chatId, kind: msg.kind, text: msg.text });
|
|
3912
4267
|
}
|
|
3913
4268
|
}) : void 0;
|
|
4269
|
+
const tunnelProvider = options.tunnelProvider ?? new RelayTunnelRouter({
|
|
4270
|
+
bridgePort: Number(process.env.BRIDGE_PORT ?? 8787),
|
|
4271
|
+
defaults: bridgeDefaults
|
|
4272
|
+
});
|
|
3914
4273
|
const messageRouter = channel ? new MessageRouter({
|
|
3915
4274
|
channel,
|
|
3916
4275
|
providers: providerAdapters,
|
|
@@ -3963,7 +4322,14 @@ function createDaemonServer(options = {}) {
|
|
|
3963
4322
|
},
|
|
3964
4323
|
lastProviderSessions,
|
|
3965
4324
|
events,
|
|
3966
|
-
defaults: bridgeDefaults
|
|
4325
|
+
defaults: bridgeDefaults,
|
|
4326
|
+
getHelpAddress: async () => {
|
|
4327
|
+
const tunnel = await tunnelProvider?.getStatus().catch(() => null);
|
|
4328
|
+
if (tunnel?.running && tunnel.publicUrl) return tunnel.publicUrl;
|
|
4329
|
+
const lan = listLanIpv4Addresses()[0];
|
|
4330
|
+
if (lan) return `http://${lan}:${process.env.BRIDGE_PORT ?? 8787}`;
|
|
4331
|
+
return `http://127.0.0.1:${process.env.BRIDGE_PORT ?? 8787}`;
|
|
4332
|
+
}
|
|
3967
4333
|
}) : void 0;
|
|
3968
4334
|
if (channel && messageRouter) {
|
|
3969
4335
|
channel.onMessage(async (message) => {
|
|
@@ -3992,10 +4358,24 @@ function createDaemonServer(options = {}) {
|
|
|
3992
4358
|
defaults: bridgeDefaults,
|
|
3993
4359
|
configPath
|
|
3994
4360
|
});
|
|
4361
|
+
if (tunnelProvider) {
|
|
4362
|
+
registerTunnelRoutes({
|
|
4363
|
+
app,
|
|
4364
|
+
configPath,
|
|
4365
|
+
defaults: { tunnel: bridgeDefaults.tunnel },
|
|
4366
|
+
tunnelManager: tunnelProvider
|
|
4367
|
+
});
|
|
4368
|
+
}
|
|
3995
4369
|
registerFsBrowseRoutes({ app });
|
|
3996
4370
|
app.get("/api/status", async () => ({
|
|
3997
4371
|
ok: true,
|
|
3998
|
-
sessions: conversation.getCurrent() ? [conversation.getCurrent()] : []
|
|
4372
|
+
sessions: conversation.getCurrent() ? [conversation.getCurrent()] : [],
|
|
4373
|
+
preferredLocalUrl: (() => {
|
|
4374
|
+
const lan = listLanIpv4Addresses()[0];
|
|
4375
|
+
const port = process.env.BRIDGE_PORT ?? 8787;
|
|
4376
|
+
if (lan) return `http://${lan}:${port}`;
|
|
4377
|
+
return `http://127.0.0.1:${port}`;
|
|
4378
|
+
})()
|
|
3999
4379
|
}));
|
|
4000
4380
|
app.get("/api/providers/status", async () => providers.getStatus());
|
|
4001
4381
|
app.post("/api/bridge-events", async (request, reply) => {
|
|
@@ -4029,7 +4409,7 @@ function createDaemonServer(options = {}) {
|
|
|
4029
4409
|
app.addHook("onClose", async () => {
|
|
4030
4410
|
await channel?.stop();
|
|
4031
4411
|
});
|
|
4032
|
-
return { app, conversation, sessions: conversation, events, activeUserStore };
|
|
4412
|
+
return { app, conversation, sessions: conversation, events, activeUserStore, tunnelProvider };
|
|
4033
4413
|
}
|
|
4034
4414
|
|
|
4035
4415
|
// src/daemon/bootstrap.ts
|
|
@@ -4043,24 +4423,35 @@ async function startDaemon(options) {
|
|
|
4043
4423
|
configPath,
|
|
4044
4424
|
providers: providerCommands
|
|
4045
4425
|
});
|
|
4046
|
-
const
|
|
4426
|
+
const bridgeDefaults = {
|
|
4427
|
+
defaultProvider: config.bridge?.defaultProvider ?? "claude-code",
|
|
4428
|
+
defaultWorkspace: config.bridge?.defaultWorkspace ?? process.cwd(),
|
|
4429
|
+
tunnel: {
|
|
4430
|
+
enabled: config.tunnel?.enabled === true,
|
|
4431
|
+
...config.tunnel?.relay ? { relay: config.tunnel.relay } : {}
|
|
4432
|
+
}
|
|
4433
|
+
};
|
|
4434
|
+
const { app, tunnelProvider: resolvedTunnelProvider } = createDaemonServer({
|
|
4047
4435
|
wechat: config.wechat,
|
|
4048
4436
|
providerCommands,
|
|
4049
|
-
bridgeDefaults
|
|
4050
|
-
|
|
4051
|
-
|
|
4052
|
-
},
|
|
4053
|
-
configPath
|
|
4437
|
+
bridgeDefaults,
|
|
4438
|
+
configPath,
|
|
4439
|
+
...options.tunnelProvider ? { tunnelProvider: options.tunnelProvider } : {}
|
|
4054
4440
|
});
|
|
4055
4441
|
await options.attachFrontend(app);
|
|
4056
4442
|
await app.listen({ host, port });
|
|
4443
|
+
const address = app.server.address();
|
|
4444
|
+
const actualPort = typeof address === "object" && address ? address.port : port;
|
|
4445
|
+
if (config.tunnel?.enabled === true) {
|
|
4446
|
+
await resolvedTunnelProvider?.start().catch(() => void 0);
|
|
4447
|
+
}
|
|
4057
4448
|
console.log("claude-codex-wechat listening:");
|
|
4058
|
-
console.log(` Local: http://127.0.0.1:${
|
|
4449
|
+
console.log(` Local: http://127.0.0.1:${actualPort}`);
|
|
4059
4450
|
for (const ip of listLanIpv4Addresses()) {
|
|
4060
|
-
console.log(` Network: http://${ip}:${
|
|
4451
|
+
console.log(` Network: http://${ip}:${actualPort}`);
|
|
4061
4452
|
}
|
|
4062
4453
|
console.log(`config path: ${configPath}`);
|
|
4063
|
-
return { app, port, host, configPath };
|
|
4454
|
+
return { app, port: actualPort, host, configPath };
|
|
4064
4455
|
}
|
|
4065
4456
|
function listLanIpv4Addresses() {
|
|
4066
4457
|
return Object.values(networkInterfaces()).flat().filter((iface) => Boolean(iface)).filter((iface) => iface.family === "IPv4" && !iface.internal).map((iface) => iface.address);
|
|
@@ -4076,7 +4467,7 @@ async function resolveProviderCommands(providers) {
|
|
|
4076
4467
|
}
|
|
4077
4468
|
|
|
4078
4469
|
// src/daemon/staticFrontend.ts
|
|
4079
|
-
import { readFileSync as
|
|
4470
|
+
import { readFileSync as readFileSync7 } from "node:fs";
|
|
4080
4471
|
import { join as join10 } from "node:path";
|
|
4081
4472
|
import fastifyStatic from "@fastify/static";
|
|
4082
4473
|
function attachStaticFrontend(webRoot2) {
|
|
@@ -4085,7 +4476,7 @@ function attachStaticFrontend(webRoot2) {
|
|
|
4085
4476
|
root: webRoot2,
|
|
4086
4477
|
wildcard: false
|
|
4087
4478
|
});
|
|
4088
|
-
const indexHtml =
|
|
4479
|
+
const indexHtml = readFileSync7(join10(webRoot2, "index.html"), "utf8");
|
|
4089
4480
|
app.setNotFoundHandler((request, reply) => {
|
|
4090
4481
|
if (request.url.startsWith("/api")) {
|
|
4091
4482
|
reply.code(404).send({ error: "not_found" });
|
|
@@ -4097,7 +4488,7 @@ function attachStaticFrontend(webRoot2) {
|
|
|
4097
4488
|
}
|
|
4098
4489
|
|
|
4099
4490
|
// src/daemon/service.ts
|
|
4100
|
-
import { existsSync as
|
|
4491
|
+
import { existsSync as existsSync9, statSync } from "node:fs";
|
|
4101
4492
|
import { mkdir as mkdir4, readFile as readFile6, rm as rm2, writeFile as writeFile5 } from "node:fs/promises";
|
|
4102
4493
|
import { homedir as homedir8 } from "node:os";
|
|
4103
4494
|
import { dirname as dirname11, join as join11 } from "node:path";
|
|
@@ -4147,13 +4538,13 @@ async function startService(context) {
|
|
|
4147
4538
|
async function stopService(context) {
|
|
4148
4539
|
if (process.platform === "darwin") {
|
|
4149
4540
|
const spec = buildLaunchdSpec(context);
|
|
4150
|
-
if (!
|
|
4541
|
+
if (!existsSync9(spec.plistPath)) return await readServiceStatus(context);
|
|
4151
4542
|
await runLaunchctl(["bootout", `gui/${process.getuid?.() ?? 0}`, spec.plistPath]).catch(() => void 0);
|
|
4152
4543
|
return await readServiceStatus(context);
|
|
4153
4544
|
}
|
|
4154
4545
|
if (process.platform === "linux") {
|
|
4155
4546
|
const spec = buildSystemdUserSpec(context);
|
|
4156
|
-
if (!
|
|
4547
|
+
if (!existsSync9(spec.unitPath)) return await readServiceStatus(context);
|
|
4157
4548
|
await runSystemctl(["--user", "stop", spec.unitName]).catch(() => void 0);
|
|
4158
4549
|
return await readServiceStatus(context);
|
|
4159
4550
|
}
|
|
@@ -4162,7 +4553,7 @@ async function stopService(context) {
|
|
|
4162
4553
|
async function uninstallService(context) {
|
|
4163
4554
|
if (process.platform === "darwin") {
|
|
4164
4555
|
const spec = buildLaunchdSpec(context);
|
|
4165
|
-
if (
|
|
4556
|
+
if (existsSync9(spec.plistPath)) {
|
|
4166
4557
|
await runLaunchctl(["bootout", `gui/${process.getuid?.() ?? 0}`, spec.plistPath]).catch(() => void 0);
|
|
4167
4558
|
await rm2(spec.plistPath, { force: true });
|
|
4168
4559
|
}
|
|
@@ -4170,7 +4561,7 @@ async function uninstallService(context) {
|
|
|
4170
4561
|
}
|
|
4171
4562
|
if (process.platform === "linux") {
|
|
4172
4563
|
const spec = buildSystemdUserSpec(context);
|
|
4173
|
-
if (
|
|
4564
|
+
if (existsSync9(spec.unitPath)) {
|
|
4174
4565
|
await runSystemctl(["--user", "disable", "--now", spec.unitName]).catch(() => void 0);
|
|
4175
4566
|
await rm2(spec.unitPath, { force: true });
|
|
4176
4567
|
await runSystemctl(["--user", "daemon-reload"]).catch(() => void 0);
|
|
@@ -4213,7 +4604,7 @@ async function tailServiceLogs(context) {
|
|
|
4213
4604
|
async function readServiceStatus(context) {
|
|
4214
4605
|
if (process.platform === "darwin") {
|
|
4215
4606
|
const spec = buildLaunchdSpec(context);
|
|
4216
|
-
const installed =
|
|
4607
|
+
const installed = existsSync9(spec.plistPath);
|
|
4217
4608
|
const running = installed ? await runLaunchctl(["print", `gui/${process.getuid?.() ?? 0}/${spec.label}`]).then(() => true, () => false) : false;
|
|
4218
4609
|
return {
|
|
4219
4610
|
manager: "launchd",
|
|
@@ -4227,7 +4618,7 @@ async function readServiceStatus(context) {
|
|
|
4227
4618
|
}
|
|
4228
4619
|
if (process.platform === "linux") {
|
|
4229
4620
|
const spec = buildSystemdUserSpec(context);
|
|
4230
|
-
const installed =
|
|
4621
|
+
const installed = existsSync9(spec.unitPath);
|
|
4231
4622
|
const running = installed ? await runSystemctl(["--user", "is-active", "--quiet", spec.unitName]).then(() => true, () => false) : false;
|
|
4232
4623
|
return {
|
|
4233
4624
|
manager: "systemd-user",
|
|
@@ -4363,16 +4754,16 @@ async function runSystemctl(args) {
|
|
|
4363
4754
|
await execFileAsync("systemctl", args);
|
|
4364
4755
|
}
|
|
4365
4756
|
async function ensureFileExists(path, errorCode) {
|
|
4366
|
-
if (!
|
|
4757
|
+
if (!existsSync9(path)) throw new Error(errorCode);
|
|
4367
4758
|
await readFile6(path, "utf8");
|
|
4368
4759
|
}
|
|
4369
4760
|
async function tailFile(path, lines) {
|
|
4370
|
-
if (!
|
|
4761
|
+
if (!existsSync9(path)) return "";
|
|
4371
4762
|
const content = await readFile6(path, "utf8");
|
|
4372
4763
|
return content.split(/\r?\n/).slice(-lines).join("\n").trim();
|
|
4373
4764
|
}
|
|
4374
4765
|
async function tailFiles(paths) {
|
|
4375
|
-
const readers = paths.filter((path) =>
|
|
4766
|
+
const readers = paths.filter((path) => existsSync9(path)).map((path) => {
|
|
4376
4767
|
const rl = createInterface({
|
|
4377
4768
|
input: createReadStream(path, { encoding: "utf8", start: Math.max(0, fileSizeHint(path) - 8192) }),
|
|
4378
4769
|
crlfDelay: Infinity
|
|
@@ -4386,7 +4777,7 @@ async function tailFiles(paths) {
|
|
|
4386
4777
|
if (process.platform !== "linux" && process.platform !== "darwin") {
|
|
4387
4778
|
return;
|
|
4388
4779
|
}
|
|
4389
|
-
const child = spawn6("tail", ["-f", ...paths.filter((path) =>
|
|
4780
|
+
const child = spawn6("tail", ["-f", ...paths.filter((path) => existsSync9(path))], { stdio: ["ignore", "pipe", "inherit"] });
|
|
4390
4781
|
child.stdout.on("data", (chunk) => {
|
|
4391
4782
|
process.stdout.write(String(chunk));
|
|
4392
4783
|
});
|
|
@@ -4459,7 +4850,7 @@ async function main() {
|
|
|
4459
4850
|
}
|
|
4460
4851
|
}
|
|
4461
4852
|
async function cmdStart() {
|
|
4462
|
-
if (!
|
|
4853
|
+
if (!existsSync10(webRoot)) {
|
|
4463
4854
|
console.error(`\u627E\u4E0D\u5230\u524D\u7AEF\u6784\u5EFA\u4EA7\u7269: ${webRoot}`);
|
|
4464
4855
|
console.error("\u8BF7\u91CD\u65B0\u5B89\u88C5\u5B8C\u6574\u7684 npm \u5305\u3002");
|
|
4465
4856
|
process.exitCode = 1;
|
|
@@ -4489,7 +4880,7 @@ async function cmdTail() {
|
|
|
4489
4880
|
await tailServiceLogs(createServiceContext());
|
|
4490
4881
|
}
|
|
4491
4882
|
async function cmdDaemon() {
|
|
4492
|
-
if (!
|
|
4883
|
+
if (!existsSync10(webRoot)) {
|
|
4493
4884
|
console.error(`\u627E\u4E0D\u5230\u524D\u7AEF\u6784\u5EFA\u4EA7\u7269: ${webRoot}`);
|
|
4494
4885
|
process.exitCode = 1;
|
|
4495
4886
|
return;
|
|
@@ -4501,8 +4892,8 @@ async function cmdDaemon() {
|
|
|
4501
4892
|
function cmdInit() {
|
|
4502
4893
|
const configPath = process.env.BRIDGE_CONFIG ?? defaultConfigPath();
|
|
4503
4894
|
const dir = dirname12(configPath);
|
|
4504
|
-
|
|
4505
|
-
if (
|
|
4895
|
+
mkdirSync7(dir, { recursive: true });
|
|
4896
|
+
if (existsSync10(configPath)) {
|
|
4506
4897
|
console.log(`\u914D\u7F6E\u5DF2\u5B58\u5728\uFF0C\u672A\u8986\u76D6: ${configPath}`);
|
|
4507
4898
|
return;
|
|
4508
4899
|
}
|
|
@@ -4519,7 +4910,7 @@ function cmdInit() {
|
|
|
4519
4910
|
defaultWorkspace: process.cwd()
|
|
4520
4911
|
}
|
|
4521
4912
|
};
|
|
4522
|
-
|
|
4913
|
+
writeFileSync7(configPath, `${JSON.stringify(template, null, 2)}
|
|
4523
4914
|
`, "utf8");
|
|
4524
4915
|
console.log(`\u5DF2\u521B\u5EFA\u914D\u7F6E: ${configPath}`);
|
|
4525
4916
|
console.log("\u8BF7\u586B\u5165\u5FAE\u4FE1 token / accountId \u540E\u8FD0\u884C: claude-codex-wechat start");
|
|
@@ -4527,13 +4918,13 @@ function cmdInit() {
|
|
|
4527
4918
|
async function cmdDoctor() {
|
|
4528
4919
|
const configPath = process.env.BRIDGE_CONFIG ?? defaultConfigPath();
|
|
4529
4920
|
console.log("claude-codex-wechat doctor\n");
|
|
4530
|
-
report("\u914D\u7F6E\u6587\u4EF6",
|
|
4531
|
-
report("\u524D\u7AEF\u4EA7\u7269",
|
|
4921
|
+
report("\u914D\u7F6E\u6587\u4EF6", existsSync10(configPath) ? configPath : `\u7F3A\u5931 (${configPath})\uFF0C\u8FD0\u884C init \u521B\u5EFA`);
|
|
4922
|
+
report("\u524D\u7AEF\u4EA7\u7269", existsSync10(webRoot) ? webRoot : `\u7F3A\u5931 (${webRoot})`);
|
|
4532
4923
|
const claude = await findExecutable("claude");
|
|
4533
4924
|
report("claude \u53EF\u6267\u884C", claude ?? "\u672A\u627E\u5230\uFF0C\u9700\u5148\u5B89\u88C5\u5E76\u767B\u5F55 Claude Code");
|
|
4534
4925
|
const codex = await findExecutable("codex");
|
|
4535
4926
|
report("codex \u53EF\u6267\u884C", codex ?? "\u672A\u627E\u5230\uFF08\u4EC5\u4F7F\u7528 Claude \u65F6\u53EF\u5FFD\u7565\uFF09");
|
|
4536
|
-
if (
|
|
4927
|
+
if (existsSync10(configPath)) {
|
|
4537
4928
|
const config = loadBridgeConfig(configPath);
|
|
4538
4929
|
const wechat = config.wechat;
|
|
4539
4930
|
report("\u5FAE\u4FE1\u542F\u7528", wechat?.enabled ? "\u662F" : "\u5426");
|
|
@@ -4543,12 +4934,12 @@ async function cmdDoctor() {
|
|
|
4543
4934
|
}
|
|
4544
4935
|
function cmdPrintConfig() {
|
|
4545
4936
|
const configPath = process.env.BRIDGE_CONFIG ?? defaultConfigPath();
|
|
4546
|
-
if (!
|
|
4937
|
+
if (!existsSync10(configPath)) {
|
|
4547
4938
|
console.error(`\u914D\u7F6E\u4E0D\u5B58\u5728: ${configPath}\uFF08\u8FD0\u884C init \u521B\u5EFA\uFF09`);
|
|
4548
4939
|
process.exitCode = 1;
|
|
4549
4940
|
return;
|
|
4550
4941
|
}
|
|
4551
|
-
console.log(
|
|
4942
|
+
console.log(readFileSync8(configPath, "utf8"));
|
|
4552
4943
|
}
|
|
4553
4944
|
async function cmdUninstall() {
|
|
4554
4945
|
const status = await uninstallService(createServiceContext());
|