claude-codex-wechat 0.1.16 → 0.1.18
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 +12 -22
- package/dist/server/cli.js +217 -68
- package/package.json +7 -2
package/README.md
CHANGED
|
@@ -47,7 +47,7 @@
|
|
|
47
47
|
### 作为 npm 包安装
|
|
48
48
|
|
|
49
49
|
```bash
|
|
50
|
-
npm install -g claude-codex-wechat
|
|
50
|
+
npm install -g claude-codex-wechat --registry=https://registry.npmmirror.com/
|
|
51
51
|
# 或
|
|
52
52
|
pnpm add -g claude-codex-wechat
|
|
53
53
|
```
|
|
@@ -64,26 +64,24 @@ pnpm add -g claude-codex-wechat
|
|
|
64
64
|
安装后按以下顺序跑通:
|
|
65
65
|
|
|
66
66
|
```bash
|
|
67
|
-
# 1.
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
# 2. 编辑 ~/.claude-codex-wechat/config.json,填入微信 token / accountId
|
|
71
|
-
# (配置说明见下方「配置」一节)
|
|
72
|
-
|
|
73
|
-
# 3. 检查环境(配置、前端产物、claude/codex 是否就绪)
|
|
74
|
-
claude-codex-wechat doctor
|
|
75
|
-
|
|
76
|
-
# 4. 后台启动(首次会自动注册为系统服务并守护运行)
|
|
67
|
+
# 1. 后台启动(首次会自动注册为系统服务并守护运行;
|
|
68
|
+
# 首次启动自动生成 relay 接入凭据、自动连接默认中转,无需手动配置)
|
|
77
69
|
claude-codex-wechat start
|
|
78
70
|
|
|
79
|
-
#
|
|
71
|
+
# 2. 打开管理页,用微信扫码登录完成绑定
|
|
72
|
+
# http://127.0.0.1:8787
|
|
73
|
+
|
|
74
|
+
# 3. 查看状态 / 日志
|
|
80
75
|
claude-codex-wechat status
|
|
81
76
|
claude-codex-wechat logs
|
|
77
|
+
|
|
78
|
+
# 可选:检查环境(配置、前端产物、claude/codex 是否就绪)
|
|
79
|
+
claude-codex-wechat doctor
|
|
82
80
|
```
|
|
83
81
|
|
|
84
82
|
启动后:
|
|
85
83
|
|
|
86
|
-
- 管理页:`http://127.0.0.1:8787
|
|
84
|
+
- 管理页:`http://127.0.0.1:8787`(在此扫码登录微信)
|
|
87
85
|
- 微信侧即可直接给机器人发消息,驱动本机 `Claude Code` / `Codex CLI` 会话
|
|
88
86
|
|
|
89
87
|
停止 / 重启 / 卸载:
|
|
@@ -119,8 +117,6 @@ pnpm install
|
|
|
119
117
|
- 打印 / 实时跟随日志
|
|
120
118
|
- `claude-codex-wechat doctor`
|
|
121
119
|
- 检查配置、前端产物、`claude`/`codex` 可执行文件
|
|
122
|
-
- `claude-codex-wechat init`
|
|
123
|
-
- 写默认配置到 `~/.claude-codex-wechat/config.json`
|
|
124
120
|
- `claude-codex-wechat uninstall`
|
|
125
121
|
- 卸载后台服务
|
|
126
122
|
- `claude-codex-wechat print-config`
|
|
@@ -140,13 +136,7 @@ pnpm install
|
|
|
140
136
|
~/.claude-codex-wechat/config.json
|
|
141
137
|
```
|
|
142
138
|
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
```bash
|
|
146
|
-
claude-codex-wechat init
|
|
147
|
-
```
|
|
148
|
-
|
|
149
|
-
最小配置示例:
|
|
139
|
+
该文件在**首次 `start` 时自动创建**(并自动写入生成的 relay 接入凭据),正常使用无需手动编辑。以下为可选的进阶自定义示例:
|
|
150
140
|
|
|
151
141
|
```json
|
|
152
142
|
{
|
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 existsSync10,
|
|
5
|
+
import { existsSync as existsSync10, readFileSync as readFileSync8 } from "node:fs";
|
|
6
6
|
import { dirname as dirname12, join as join12 } from "node:path";
|
|
7
7
|
import { fileURLToPath } from "node:url";
|
|
8
8
|
|
|
@@ -2377,6 +2377,9 @@ function terminateChild(child, signal = "SIGTERM") {
|
|
|
2377
2377
|
function defaultWorkspaceDir() {
|
|
2378
2378
|
return join8(tmpdir(), "project");
|
|
2379
2379
|
}
|
|
2380
|
+
function useShellForCli() {
|
|
2381
|
+
return isWindows;
|
|
2382
|
+
}
|
|
2380
2383
|
function statePath(filename) {
|
|
2381
2384
|
return join8(tmpdir(), filename);
|
|
2382
2385
|
}
|
|
@@ -2551,7 +2554,7 @@ function parseJsonLine(line) {
|
|
|
2551
2554
|
}
|
|
2552
2555
|
}
|
|
2553
2556
|
function defaultClaudeStreamSpawner(call) {
|
|
2554
|
-
const child = spawn2(call.command, call.args, { cwd: expandTilde(call.cwd) ?? call.cwd, stdio: ["pipe", "pipe", "pipe"] });
|
|
2557
|
+
const child = spawn2(call.command, call.args, { cwd: expandTilde(call.cwd) ?? call.cwd, stdio: ["pipe", "pipe", "pipe"], shell: useShellForCli() });
|
|
2555
2558
|
let stderr = "";
|
|
2556
2559
|
let buffer = "";
|
|
2557
2560
|
const queue = [];
|
|
@@ -2663,7 +2666,8 @@ var CodexAppServerClient = class {
|
|
|
2663
2666
|
const command = input.command ?? "codex";
|
|
2664
2667
|
this.child = spawn3(command, ["app-server", "--listen", "stdio://"], {
|
|
2665
2668
|
cwd: input.cwd,
|
|
2666
|
-
stdio: ["pipe", "pipe", "pipe"]
|
|
2669
|
+
stdio: ["pipe", "pipe", "pipe"],
|
|
2670
|
+
shell: useShellForCli()
|
|
2667
2671
|
});
|
|
2668
2672
|
this.child.stdout.on("data", (chunk) => this.onStdout(String(chunk)));
|
|
2669
2673
|
this.child.stderr.on("data", () => {
|
|
@@ -3070,7 +3074,7 @@ function createDefaultProviders(input = {}) {
|
|
|
3070
3074
|
import { spawn as spawn4 } from "node:child_process";
|
|
3071
3075
|
async function defaultCommandRunner(command, args) {
|
|
3072
3076
|
return await new Promise((resolve2) => {
|
|
3073
|
-
const child = spawn4(command, args, { stdio: ["ignore", "pipe", "pipe"] });
|
|
3077
|
+
const child = spawn4(command, args, { stdio: ["ignore", "pipe", "pipe"], shell: useShellForCli() });
|
|
3074
3078
|
let stdout = "";
|
|
3075
3079
|
let stderr = "";
|
|
3076
3080
|
child.stdout.on("data", (chunk) => {
|
|
@@ -3106,7 +3110,7 @@ ${result.stderr}`) };
|
|
|
3106
3110
|
import { spawn as spawn5 } from "node:child_process";
|
|
3107
3111
|
async function defaultCodexCommandRunner(command, args) {
|
|
3108
3112
|
return await new Promise((resolve2) => {
|
|
3109
|
-
const child = spawn5(command, args, { stdio: ["ignore", "pipe", "pipe"] });
|
|
3113
|
+
const child = spawn5(command, args, { stdio: ["ignore", "pipe", "pipe"], shell: useShellForCli() });
|
|
3110
3114
|
let stdout = "";
|
|
3111
3115
|
let stderr = "";
|
|
3112
3116
|
child.stdout.on("data", (chunk) => {
|
|
@@ -3990,6 +3994,9 @@ var RelayTunnelProvider = class {
|
|
|
3990
3994
|
running: false,
|
|
3991
3995
|
status: "stopped"
|
|
3992
3996
|
};
|
|
3997
|
+
stopped = true;
|
|
3998
|
+
reconnectAttempts = 0;
|
|
3999
|
+
reconnectTimer = null;
|
|
3993
4000
|
static defaultCreateSocket(url) {
|
|
3994
4001
|
const socket = new WebSocket(url);
|
|
3995
4002
|
return {
|
|
@@ -4011,13 +4018,34 @@ var RelayTunnelProvider = class {
|
|
|
4011
4018
|
return { ...this.status };
|
|
4012
4019
|
}
|
|
4013
4020
|
async start() {
|
|
4021
|
+
this.stopped = false;
|
|
4022
|
+
return await this.connect();
|
|
4023
|
+
}
|
|
4024
|
+
async stop() {
|
|
4025
|
+
this.stopped = true;
|
|
4026
|
+
if (this.reconnectTimer) {
|
|
4027
|
+
clearTimeout(this.reconnectTimer);
|
|
4028
|
+
this.reconnectTimer = null;
|
|
4029
|
+
}
|
|
4030
|
+
this.socket?.close();
|
|
4031
|
+
this.socket = null;
|
|
4032
|
+
this.status = {
|
|
4033
|
+
installed: true,
|
|
4034
|
+
running: false,
|
|
4035
|
+
status: "stopped"
|
|
4036
|
+
};
|
|
4037
|
+
return { ...this.status };
|
|
4038
|
+
}
|
|
4039
|
+
// 单次连接尝试:成功注册即 resolve。底层连接断开(close/error)时,
|
|
4040
|
+
// 只要未被 stop(),就按指数退避自动重连,直到恢复。
|
|
4041
|
+
connect() {
|
|
4014
4042
|
this.status = {
|
|
4015
4043
|
...this.status,
|
|
4016
4044
|
status: "starting"
|
|
4017
4045
|
};
|
|
4018
4046
|
const socket = this.options.createSocket(this.options.serverUrl);
|
|
4019
4047
|
this.socket = socket;
|
|
4020
|
-
return
|
|
4048
|
+
return new Promise((resolve2, reject) => {
|
|
4021
4049
|
let settled = false;
|
|
4022
4050
|
const finishResolve = (value) => {
|
|
4023
4051
|
if (settled) return;
|
|
@@ -4048,9 +4076,11 @@ var RelayTunnelProvider = class {
|
|
|
4048
4076
|
error: message
|
|
4049
4077
|
};
|
|
4050
4078
|
finishReject(new Error(message));
|
|
4079
|
+
this.scheduleReconnect();
|
|
4051
4080
|
return;
|
|
4052
4081
|
}
|
|
4053
4082
|
if (payload?.type === "registered") {
|
|
4083
|
+
this.reconnectAttempts = 0;
|
|
4054
4084
|
const publicUrl = typeof payload?.publicUrl === "string" && payload.publicUrl ? payload.publicUrl : typeof payload?.token === "string" ? `${derivePublicBaseUrl(this.options.serverUrl)}/${payload.token}` : void 0;
|
|
4055
4085
|
this.status = {
|
|
4056
4086
|
installed: true,
|
|
@@ -4066,6 +4096,10 @@ var RelayTunnelProvider = class {
|
|
|
4066
4096
|
}
|
|
4067
4097
|
});
|
|
4068
4098
|
socket.on("close", () => {
|
|
4099
|
+
if (this.stopped) {
|
|
4100
|
+
finishReject(new Error("relay_stopped"));
|
|
4101
|
+
return;
|
|
4102
|
+
}
|
|
4069
4103
|
this.status = {
|
|
4070
4104
|
...this.status,
|
|
4071
4105
|
running: false,
|
|
@@ -4073,8 +4107,13 @@ var RelayTunnelProvider = class {
|
|
|
4073
4107
|
error: "relay_disconnected"
|
|
4074
4108
|
};
|
|
4075
4109
|
finishReject(new Error("relay_disconnected"));
|
|
4110
|
+
this.scheduleReconnect();
|
|
4076
4111
|
});
|
|
4077
4112
|
socket.on("error", (error) => {
|
|
4113
|
+
if (this.stopped) {
|
|
4114
|
+
finishReject(new Error("relay_stopped"));
|
|
4115
|
+
return;
|
|
4116
|
+
}
|
|
4078
4117
|
const message = error instanceof Error ? error.message : String(error);
|
|
4079
4118
|
this.status = {
|
|
4080
4119
|
...this.status,
|
|
@@ -4083,18 +4122,22 @@ var RelayTunnelProvider = class {
|
|
|
4083
4122
|
error: message
|
|
4084
4123
|
};
|
|
4085
4124
|
finishReject(error);
|
|
4125
|
+
this.scheduleReconnect();
|
|
4086
4126
|
});
|
|
4087
4127
|
});
|
|
4088
4128
|
}
|
|
4089
|
-
|
|
4090
|
-
this.
|
|
4091
|
-
this.
|
|
4092
|
-
this.
|
|
4093
|
-
|
|
4094
|
-
|
|
4095
|
-
|
|
4096
|
-
|
|
4097
|
-
|
|
4129
|
+
scheduleReconnect() {
|
|
4130
|
+
if (this.stopped) return;
|
|
4131
|
+
if (this.reconnectTimer) return;
|
|
4132
|
+
const maxDelay = this.options.maxReconnectDelayMs ?? 3e4;
|
|
4133
|
+
const delay3 = Math.min(maxDelay, 1e3 * 2 ** this.reconnectAttempts);
|
|
4134
|
+
this.reconnectAttempts += 1;
|
|
4135
|
+
this.reconnectTimer = setTimeout(() => {
|
|
4136
|
+
this.reconnectTimer = null;
|
|
4137
|
+
if (this.stopped) return;
|
|
4138
|
+
void this.connect().catch(() => void 0);
|
|
4139
|
+
}, delay3);
|
|
4140
|
+
this.reconnectTimer.unref?.();
|
|
4098
4141
|
}
|
|
4099
4142
|
async handleRequest(payload) {
|
|
4100
4143
|
const fetchImpl = this.options.fetchImpl ?? fetch;
|
|
@@ -4456,7 +4499,7 @@ function attachStaticFrontend(webRoot2) {
|
|
|
4456
4499
|
}
|
|
4457
4500
|
|
|
4458
4501
|
// src/daemon/service.ts
|
|
4459
|
-
import { existsSync as existsSync9, statSync } from "node:fs";
|
|
4502
|
+
import { existsSync as existsSync9, statSync, openSync, closeSync, watchFile } from "node:fs";
|
|
4460
4503
|
import { mkdir as mkdir4, readFile as readFile6, rm as rm2, writeFile as writeFile5 } from "node:fs/promises";
|
|
4461
4504
|
import { homedir as homedir8 } from "node:os";
|
|
4462
4505
|
import { dirname as dirname11, join as join11 } from "node:path";
|
|
@@ -4485,6 +4528,9 @@ async function installService(context) {
|
|
|
4485
4528
|
await runSystemctl(["--user", "enable", "--now", spec.unitName]);
|
|
4486
4529
|
return await readServiceStatus(context);
|
|
4487
4530
|
}
|
|
4531
|
+
if (process.platform === "win32") {
|
|
4532
|
+
return await startService(context);
|
|
4533
|
+
}
|
|
4488
4534
|
throw new Error("service_install_not_supported_on_this_platform");
|
|
4489
4535
|
}
|
|
4490
4536
|
async function startService(context) {
|
|
@@ -4501,6 +4547,15 @@ async function startService(context) {
|
|
|
4501
4547
|
await runSystemctl(["--user", "start", spec.unitName]);
|
|
4502
4548
|
return await readServiceStatus(context);
|
|
4503
4549
|
}
|
|
4550
|
+
if (process.platform === "win32") {
|
|
4551
|
+
const spec = buildProcessSpec(context);
|
|
4552
|
+
const existingPid = await readPidFile(spec.pidPath);
|
|
4553
|
+
if (existingPid !== null && isProcessAlive(existingPid)) {
|
|
4554
|
+
return await readServiceStatus(context);
|
|
4555
|
+
}
|
|
4556
|
+
await spawnDetachedDaemon(spec);
|
|
4557
|
+
return await readServiceStatus(context);
|
|
4558
|
+
}
|
|
4504
4559
|
throw new Error("service_start_not_supported_on_this_platform");
|
|
4505
4560
|
}
|
|
4506
4561
|
async function stopService(context) {
|
|
@@ -4516,6 +4571,15 @@ async function stopService(context) {
|
|
|
4516
4571
|
await runSystemctl(["--user", "stop", spec.unitName]).catch(() => void 0);
|
|
4517
4572
|
return await readServiceStatus(context);
|
|
4518
4573
|
}
|
|
4574
|
+
if (process.platform === "win32") {
|
|
4575
|
+
const spec = buildProcessSpec(context);
|
|
4576
|
+
const pid = await readPidFile(spec.pidPath);
|
|
4577
|
+
if (pid !== null && isProcessAlive(pid)) {
|
|
4578
|
+
await killPid(pid).catch(() => void 0);
|
|
4579
|
+
}
|
|
4580
|
+
await removePidFile(spec.pidPath);
|
|
4581
|
+
return await readServiceStatus(context);
|
|
4582
|
+
}
|
|
4519
4583
|
throw new Error("service_stop_not_supported_on_this_platform");
|
|
4520
4584
|
}
|
|
4521
4585
|
async function uninstallService(context) {
|
|
@@ -4536,6 +4600,9 @@ async function uninstallService(context) {
|
|
|
4536
4600
|
}
|
|
4537
4601
|
return await readServiceStatus(context);
|
|
4538
4602
|
}
|
|
4603
|
+
if (process.platform === "win32") {
|
|
4604
|
+
return await stopService(context);
|
|
4605
|
+
}
|
|
4539
4606
|
throw new Error("service_uninstall_not_supported_on_this_platform");
|
|
4540
4607
|
}
|
|
4541
4608
|
async function restartService(context) {
|
|
@@ -4551,6 +4618,10 @@ async function restartService(context) {
|
|
|
4551
4618
|
await runSystemctl(["--user", "restart", spec.unitName]);
|
|
4552
4619
|
return await readServiceStatus(context);
|
|
4553
4620
|
}
|
|
4621
|
+
if (process.platform === "win32") {
|
|
4622
|
+
await stopService(context);
|
|
4623
|
+
return await startService(context);
|
|
4624
|
+
}
|
|
4554
4625
|
throw new Error("service_restart_not_supported_on_this_platform");
|
|
4555
4626
|
}
|
|
4556
4627
|
async function readServiceLogs(context, lines = 120) {
|
|
@@ -4598,6 +4669,21 @@ async function readServiceStatus(context) {
|
|
|
4598
4669
|
stderrPath: spec.stderrPath
|
|
4599
4670
|
};
|
|
4600
4671
|
}
|
|
4672
|
+
if (process.platform === "win32") {
|
|
4673
|
+
const spec = buildProcessSpec(context);
|
|
4674
|
+
const pid = await readPidFile(spec.pidPath);
|
|
4675
|
+
const installed = pid !== null;
|
|
4676
|
+
const running = pid !== null && isProcessAlive(pid);
|
|
4677
|
+
return {
|
|
4678
|
+
manager: "process",
|
|
4679
|
+
installed,
|
|
4680
|
+
running,
|
|
4681
|
+
serviceFilePath: spec.pidPath,
|
|
4682
|
+
label: "claude-codex-wechat",
|
|
4683
|
+
stdoutPath: spec.stdoutPath,
|
|
4684
|
+
stderrPath: spec.stderrPath
|
|
4685
|
+
};
|
|
4686
|
+
}
|
|
4601
4687
|
throw new Error("service_status_not_supported_on_this_platform");
|
|
4602
4688
|
}
|
|
4603
4689
|
function buildLaunchdSpec(context) {
|
|
@@ -4624,6 +4710,17 @@ function buildSystemdUserSpec(context) {
|
|
|
4624
4710
|
environment: buildServiceEnvironment(context)
|
|
4625
4711
|
};
|
|
4626
4712
|
}
|
|
4713
|
+
function buildProcessSpec(context) {
|
|
4714
|
+
const stateDir = join11(homedir8(), ".claude-codex-wechat");
|
|
4715
|
+
return {
|
|
4716
|
+
pidPath: join11(stateDir, "service.pid"),
|
|
4717
|
+
command: [context.nodePath ?? process.execPath, context.cliEntrypointPath, "__daemon"],
|
|
4718
|
+
workingDirectory: homedir8(),
|
|
4719
|
+
stdoutPath: join11(stateDir, "logs", "service.stdout.log"),
|
|
4720
|
+
stderrPath: join11(stateDir, "logs", "service.stderr.log"),
|
|
4721
|
+
environment: buildServiceEnvironment(context)
|
|
4722
|
+
};
|
|
4723
|
+
}
|
|
4627
4724
|
function buildServiceEnvironment(context) {
|
|
4628
4725
|
const env = {
|
|
4629
4726
|
PATH: process.env.PATH ?? "",
|
|
@@ -4721,6 +4818,66 @@ async function runLaunchctl(args) {
|
|
|
4721
4818
|
async function runSystemctl(args) {
|
|
4722
4819
|
await execFileAsync("systemctl", args);
|
|
4723
4820
|
}
|
|
4821
|
+
function isProcessAlive(pid) {
|
|
4822
|
+
if (!Number.isInteger(pid) || pid <= 0) return false;
|
|
4823
|
+
try {
|
|
4824
|
+
process.kill(pid, 0);
|
|
4825
|
+
return true;
|
|
4826
|
+
} catch (error) {
|
|
4827
|
+
return error.code === "EPERM";
|
|
4828
|
+
}
|
|
4829
|
+
}
|
|
4830
|
+
async function readPidFile(pidPath) {
|
|
4831
|
+
if (!existsSync9(pidPath)) return null;
|
|
4832
|
+
try {
|
|
4833
|
+
const raw = (await readFile6(pidPath, "utf8")).trim();
|
|
4834
|
+
const pid = Number.parseInt(raw, 10);
|
|
4835
|
+
return Number.isInteger(pid) && pid > 0 ? pid : null;
|
|
4836
|
+
} catch {
|
|
4837
|
+
return null;
|
|
4838
|
+
}
|
|
4839
|
+
}
|
|
4840
|
+
async function writePidFile(pidPath, pid) {
|
|
4841
|
+
await mkdir4(dirname11(pidPath), { recursive: true });
|
|
4842
|
+
await writeFile5(pidPath, `${pid}
|
|
4843
|
+
`, "utf8");
|
|
4844
|
+
}
|
|
4845
|
+
async function removePidFile(pidPath) {
|
|
4846
|
+
await rm2(pidPath, { force: true });
|
|
4847
|
+
}
|
|
4848
|
+
async function killPid(pid) {
|
|
4849
|
+
if (process.platform === "win32") {
|
|
4850
|
+
await execFileAsync("taskkill", ["/PID", String(pid), "/T", "/F"]);
|
|
4851
|
+
return;
|
|
4852
|
+
}
|
|
4853
|
+
process.kill(pid, "SIGTERM");
|
|
4854
|
+
}
|
|
4855
|
+
async function spawnDetachedDaemon(spec) {
|
|
4856
|
+
await mkdir4(dirname11(spec.stdoutPath), { recursive: true });
|
|
4857
|
+
const outFd = openSync(spec.stdoutPath, "a");
|
|
4858
|
+
const errFd = openSync(spec.stderrPath, "a");
|
|
4859
|
+
try {
|
|
4860
|
+
const [command, ...args] = spec.command;
|
|
4861
|
+
const child = spawn6(command, args, {
|
|
4862
|
+
cwd: spec.workingDirectory,
|
|
4863
|
+
// Windows 分离进程需继承完整 process.env(SystemRoot/APPDATA/PATHEXT 等),
|
|
4864
|
+
// 否则子进程及其拉起的 claude/codex 会异常;spec.environment 仅作 BRIDGE_* 等覆盖层。
|
|
4865
|
+
env: { ...process.env, ...spec.environment },
|
|
4866
|
+
detached: true,
|
|
4867
|
+
windowsHide: true,
|
|
4868
|
+
stdio: ["ignore", outFd, errFd]
|
|
4869
|
+
});
|
|
4870
|
+
if (typeof child.pid !== "number") {
|
|
4871
|
+
throw new Error("service_spawn_failed");
|
|
4872
|
+
}
|
|
4873
|
+
await writePidFile(spec.pidPath, child.pid);
|
|
4874
|
+
child.unref();
|
|
4875
|
+
return child.pid;
|
|
4876
|
+
} finally {
|
|
4877
|
+
closeSync(outFd);
|
|
4878
|
+
closeSync(errFd);
|
|
4879
|
+
}
|
|
4880
|
+
}
|
|
4724
4881
|
async function ensureFileExists(path, errorCode) {
|
|
4725
4882
|
if (!existsSync9(path)) throw new Error(errorCode);
|
|
4726
4883
|
await readFile6(path, "utf8");
|
|
@@ -4731,29 +4888,46 @@ async function tailFile(path, lines) {
|
|
|
4731
4888
|
return content.split(/\r?\n/).slice(-lines).join("\n").trim();
|
|
4732
4889
|
}
|
|
4733
4890
|
async function tailFiles(paths) {
|
|
4734
|
-
const
|
|
4735
|
-
|
|
4736
|
-
|
|
4737
|
-
|
|
4738
|
-
|
|
4739
|
-
rl.on("line", (line) => {
|
|
4740
|
-
process.stdout.write(`[${path}] ${line}
|
|
4891
|
+
const existing = paths.filter((path) => existsSync9(path));
|
|
4892
|
+
for (const path of existing) {
|
|
4893
|
+
const tail = await tailFile(path, 200);
|
|
4894
|
+
if (tail) process.stdout.write(`[${path}]
|
|
4895
|
+
${tail}
|
|
4741
4896
|
`);
|
|
4897
|
+
}
|
|
4898
|
+
if (process.platform === "linux" || process.platform === "darwin") {
|
|
4899
|
+
const child = spawn6("tail", ["-f", ...existing], { stdio: ["ignore", "pipe", "inherit"] });
|
|
4900
|
+
child.stdout.on("data", (chunk) => {
|
|
4901
|
+
process.stdout.write(String(chunk));
|
|
4902
|
+
});
|
|
4903
|
+
await new Promise((resolve2, reject) => {
|
|
4904
|
+
child.on("exit", () => resolve2());
|
|
4905
|
+
child.on("error", reject);
|
|
4742
4906
|
});
|
|
4743
|
-
return rl;
|
|
4744
|
-
});
|
|
4745
|
-
if (process.platform !== "linux" && process.platform !== "darwin") {
|
|
4746
4907
|
return;
|
|
4747
4908
|
}
|
|
4748
|
-
const
|
|
4749
|
-
|
|
4750
|
-
|
|
4751
|
-
|
|
4752
|
-
|
|
4753
|
-
|
|
4754
|
-
|
|
4755
|
-
|
|
4756
|
-
|
|
4909
|
+
const offsets = new Map(existing.map((path) => [path, fileSizeHint(path)]));
|
|
4910
|
+
for (const path of existing) {
|
|
4911
|
+
watchFile(path, { interval: 1e3 }, (curr) => {
|
|
4912
|
+
const prevSize = offsets.get(path) ?? 0;
|
|
4913
|
+
if (curr.size < prevSize) {
|
|
4914
|
+
offsets.set(path, 0);
|
|
4915
|
+
return;
|
|
4916
|
+
}
|
|
4917
|
+
if (curr.size === prevSize) return;
|
|
4918
|
+
const start = prevSize;
|
|
4919
|
+
offsets.set(path, curr.size);
|
|
4920
|
+
const rl = createInterface({
|
|
4921
|
+
input: createReadStream(path, { encoding: "utf8", start, end: curr.size - 1 }),
|
|
4922
|
+
crlfDelay: Infinity
|
|
4923
|
+
});
|
|
4924
|
+
rl.on("line", (line) => {
|
|
4925
|
+
process.stdout.write(`[${path}] ${line}
|
|
4926
|
+
`);
|
|
4927
|
+
});
|
|
4928
|
+
});
|
|
4929
|
+
}
|
|
4930
|
+
await new Promise(() => {
|
|
4757
4931
|
});
|
|
4758
4932
|
}
|
|
4759
4933
|
function fileSizeHint(path) {
|
|
@@ -4789,9 +4963,6 @@ async function main() {
|
|
|
4789
4963
|
case "tail":
|
|
4790
4964
|
await cmdTail();
|
|
4791
4965
|
return;
|
|
4792
|
-
case "init":
|
|
4793
|
-
cmdInit();
|
|
4794
|
-
return;
|
|
4795
4966
|
case "doctor":
|
|
4796
4967
|
await cmdDoctor();
|
|
4797
4968
|
return;
|
|
@@ -4857,36 +5028,10 @@ async function cmdDaemon() {
|
|
|
4857
5028
|
attachFrontend: attachStaticFrontend(webRoot)
|
|
4858
5029
|
});
|
|
4859
5030
|
}
|
|
4860
|
-
function cmdInit() {
|
|
4861
|
-
const configPath = process.env.BRIDGE_CONFIG ?? defaultConfigPath();
|
|
4862
|
-
const dir = dirname12(configPath);
|
|
4863
|
-
mkdirSync7(dir, { recursive: true });
|
|
4864
|
-
if (existsSync10(configPath)) {
|
|
4865
|
-
console.log(`\u914D\u7F6E\u5DF2\u5B58\u5728\uFF0C\u672A\u8986\u76D6: ${configPath}`);
|
|
4866
|
-
return;
|
|
4867
|
-
}
|
|
4868
|
-
const template = {
|
|
4869
|
-
wechat: {
|
|
4870
|
-
enabled: true,
|
|
4871
|
-
baseUrl: "https://ilinkai.weixin.qq.com",
|
|
4872
|
-
token: "replace-with-weixin-bot-token",
|
|
4873
|
-
accountId: "replace-with-weixin-account-id"
|
|
4874
|
-
},
|
|
4875
|
-
providers: {},
|
|
4876
|
-
bridge: {
|
|
4877
|
-
defaultProvider: "claude-code",
|
|
4878
|
-
defaultWorkspace: process.cwd()
|
|
4879
|
-
}
|
|
4880
|
-
};
|
|
4881
|
-
writeFileSync7(configPath, `${JSON.stringify(template, null, 2)}
|
|
4882
|
-
`, "utf8");
|
|
4883
|
-
console.log(`\u5DF2\u521B\u5EFA\u914D\u7F6E: ${configPath}`);
|
|
4884
|
-
console.log("\u8BF7\u586B\u5165\u5FAE\u4FE1 token / accountId \u540E\u8FD0\u884C: claude-codex-wechat start");
|
|
4885
|
-
}
|
|
4886
5031
|
async function cmdDoctor() {
|
|
4887
5032
|
const configPath = process.env.BRIDGE_CONFIG ?? defaultConfigPath();
|
|
4888
5033
|
console.log("claude-codex-wechat doctor\n");
|
|
4889
|
-
report("\u914D\u7F6E\u6587\u4EF6", existsSync10(configPath) ? configPath : `\u7F3A\u5931 (${configPath})\uFF0C\
|
|
5034
|
+
report("\u914D\u7F6E\u6587\u4EF6", existsSync10(configPath) ? configPath : `\u7F3A\u5931 (${configPath})\uFF0C\u9996\u6B21 start \u65F6\u81EA\u52A8\u521B\u5EFA`);
|
|
4890
5035
|
report("\u524D\u7AEF\u4EA7\u7269", existsSync10(webRoot) ? webRoot : `\u7F3A\u5931 (${webRoot})`);
|
|
4891
5036
|
const claude = await findExecutable("claude");
|
|
4892
5037
|
report("claude \u53EF\u6267\u884C", claude ?? "\u672A\u627E\u5230\uFF0C\u9700\u5148\u5B89\u88C5\u5E76\u767B\u5F55 Claude Code");
|
|
@@ -4903,7 +5048,7 @@ async function cmdDoctor() {
|
|
|
4903
5048
|
function cmdPrintConfig() {
|
|
4904
5049
|
const configPath = process.env.BRIDGE_CONFIG ?? defaultConfigPath();
|
|
4905
5050
|
if (!existsSync10(configPath)) {
|
|
4906
|
-
console.error(`\u914D\u7F6E\u4E0D\u5B58\u5728: ${configPath}\uFF08\
|
|
5051
|
+
console.error(`\u914D\u7F6E\u4E0D\u5B58\u5728: ${configPath}\uFF08\u9996\u6B21 start \u540E\u81EA\u52A8\u751F\u6210\uFF09`);
|
|
4907
5052
|
process.exitCode = 1;
|
|
4908
5053
|
return;
|
|
4909
5054
|
}
|
|
@@ -4949,13 +5094,17 @@ function printUsage() {
|
|
|
4949
5094
|
logs \u6253\u5370\u6700\u8FD1\u65E5\u5FD7
|
|
4950
5095
|
tail \u5B9E\u65F6\u8DDF\u968F\u65E5\u5FD7
|
|
4951
5096
|
doctor \u68C0\u67E5\u914D\u7F6E\u3001\u524D\u7AEF\u4EA7\u7269\u4E0E claude/codex \u53EF\u6267\u884C\u6587\u4EF6
|
|
4952
|
-
init \u5728 ~/.claude-codex-wechat/ \u521B\u5EFA\u9ED8\u8BA4\u914D\u7F6E
|
|
4953
5097
|
uninstall \u5378\u8F7D\u540E\u53F0\u670D\u52A1
|
|
4954
5098
|
print-config \u6253\u5370\u5F53\u524D\u914D\u7F6E\u6587\u4EF6\u5185\u5BB9
|
|
4955
5099
|
help \u663E\u793A\u672C\u5E2E\u52A9
|
|
4956
5100
|
|
|
4957
5101
|
\u73AF\u5883\u53D8\u91CF:
|
|
4958
5102
|
BRIDGE_PORT \u76D1\u542C\u7AEF\u53E3\uFF08\u9ED8\u8BA4 8787\uFF09
|
|
4959
|
-
BRIDGE_CONFIG \u914D\u7F6E\u6587\u4EF6\u8DEF\u5F84\uFF08\u9ED8\u8BA4 ~/.claude-codex-wechat/config.json\uFF09
|
|
5103
|
+
BRIDGE_CONFIG \u914D\u7F6E\u6587\u4EF6\u8DEF\u5F84\uFF08\u9ED8\u8BA4 ~/.claude-codex-wechat/config.json\uFF09
|
|
5104
|
+
|
|
5105
|
+
\u5E73\u53F0\u8BF4\u660E:
|
|
5106
|
+
macOS \u7528 launchd\u3001Linux \u7528 systemd --user \u6258\u7BA1\uFF0C\u767B\u5F55\u81EA\u542F\u4E14\u5D29\u6E83\u81EA\u52A8\u91CD\u542F\u3002
|
|
5107
|
+
Windows \u91C7\u7528\u540E\u53F0\u8FDB\u7A0B + PID \u6587\u4EF6\u6258\u7BA1\uFF08\u514D\u7BA1\u7406\u5458\uFF09\uFF0C\u4E0D\u968F\u5F00\u673A\u81EA\u542F\uFF0C
|
|
5108
|
+
\u91CD\u542F\u7535\u8111\u540E\u9700\u91CD\u65B0\u8FD0\u884C start\u3002`);
|
|
4960
5109
|
}
|
|
4961
5110
|
await main();
|
package/package.json
CHANGED
|
@@ -1,8 +1,13 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "claude-codex-wechat",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.18",
|
|
4
4
|
"private": false,
|
|
5
5
|
"type": "module",
|
|
6
|
+
"packageManager": "pnpm@11.5.0",
|
|
7
|
+
"repository": {
|
|
8
|
+
"type": "git",
|
|
9
|
+
"url": "git+https://github.com/izerui/claude-codex-wechat.git"
|
|
10
|
+
},
|
|
6
11
|
"bin": {
|
|
7
12
|
"claude-codex-wechat": "./dist/server/cli.js"
|
|
8
13
|
},
|
|
@@ -29,7 +34,7 @@
|
|
|
29
34
|
"build": "vite build && node scripts/build-server.mjs",
|
|
30
35
|
"relay:start": "pnpm --dir relay-server start",
|
|
31
36
|
"relay:test": "pnpm --dir relay-server test",
|
|
32
|
-
"prepublishOnly": "npm
|
|
37
|
+
"prepublishOnly": "npm run build"
|
|
33
38
|
},
|
|
34
39
|
"dependencies": {
|
|
35
40
|
"@fastify/static": "^9.1.3",
|