claude-codex-wechat 0.1.4 → 0.1.6
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 +33 -0
- package/dist/server/cli.js +428 -11
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -189,6 +189,38 @@ claude-codex-wechat start
|
|
|
189
189
|
- 这是前台进程
|
|
190
190
|
- npm 本身不负责守护
|
|
191
191
|
- 长期常驻建议交给进程管理器
|
|
192
|
+
- 如果默认端口已被本程序的后台服务占用,`start` 会先停掉后台服务,再以前台模式启动
|
|
193
|
+
- 如果端口被别的进程占用,`start` 会直接报出占用 PID 和命令,不会强杀陌生进程
|
|
194
|
+
|
|
195
|
+
### 安装后后台运行
|
|
196
|
+
|
|
197
|
+
参考 `happier` 的方式,这个仓库现在提供 `service` 命令,把当前 CLI 注册成操作系统服务,而不是在 Node 进程里自己 daemonize。
|
|
198
|
+
|
|
199
|
+
常用命令:
|
|
200
|
+
|
|
201
|
+
```bash
|
|
202
|
+
claude-codex-wechat service install
|
|
203
|
+
claude-codex-wechat service start
|
|
204
|
+
claude-codex-wechat service restart
|
|
205
|
+
claude-codex-wechat service status
|
|
206
|
+
claude-codex-wechat service logs
|
|
207
|
+
claude-codex-wechat service tail
|
|
208
|
+
claude-codex-wechat service stop
|
|
209
|
+
claude-codex-wechat service uninstall
|
|
210
|
+
```
|
|
211
|
+
|
|
212
|
+
当前支持:
|
|
213
|
+
|
|
214
|
+
- macOS:`launchd`(`~/Library/LaunchAgents/`)
|
|
215
|
+
- Linux:`systemd --user`(`~/.config/systemd/user/`)
|
|
216
|
+
|
|
217
|
+
推荐安装后直接这样:
|
|
218
|
+
|
|
219
|
+
```bash
|
|
220
|
+
claude-codex-wechat init
|
|
221
|
+
claude-codex-wechat service install
|
|
222
|
+
claude-codex-wechat service status
|
|
223
|
+
```
|
|
192
224
|
|
|
193
225
|
### pm2
|
|
194
226
|
|
|
@@ -315,6 +347,7 @@ HTTPS_PROXY=http://127.0.0.1:7890 HTTP_PROXY=http://127.0.0.1:7890 ./release.sh
|
|
|
315
347
|
|
|
316
348
|
这个脚本会:
|
|
317
349
|
|
|
350
|
+
- 先检查 `npm whoami`
|
|
318
351
|
- 先执行 `pnpm typecheck`
|
|
319
352
|
- 再执行 `pnpm build`
|
|
320
353
|
- 提交当前改动
|
package/dist/server/cli.js
CHANGED
|
@@ -2,8 +2,8 @@
|
|
|
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
|
|
6
|
-
import { dirname as
|
|
5
|
+
import { existsSync as existsSync9, mkdirSync as mkdirSync6, readFileSync as readFileSync7, writeFileSync as writeFileSync6 } from "node:fs";
|
|
6
|
+
import { dirname as dirname11, join as join11 } from "node:path";
|
|
7
7
|
import { fileURLToPath } from "node:url";
|
|
8
8
|
|
|
9
9
|
// src/daemon/bootstrap.ts
|
|
@@ -3989,6 +3989,27 @@ async function resolveProviderCommands(providers) {
|
|
|
3989
3989
|
};
|
|
3990
3990
|
}
|
|
3991
3991
|
|
|
3992
|
+
// src/daemon/portGuard.ts
|
|
3993
|
+
import { execFile } from "node:child_process";
|
|
3994
|
+
import { promisify } from "node:util";
|
|
3995
|
+
var execFileAsync = promisify(execFile);
|
|
3996
|
+
async function findListeningProcess(port) {
|
|
3997
|
+
if (process.platform === "win32") return null;
|
|
3998
|
+
try {
|
|
3999
|
+
const { stdout } = await execFileAsync("lsof", ["-n", "-P", `-iTCP:${port}`, "-sTCP:LISTEN"]);
|
|
4000
|
+
const lines = stdout.trim().split(/\r?\n/).filter(Boolean);
|
|
4001
|
+
const record = lines.slice(1)[0];
|
|
4002
|
+
if (!record) return null;
|
|
4003
|
+
const parts = record.trim().split(/\s+/);
|
|
4004
|
+
const command = parts[0] ?? "";
|
|
4005
|
+
const pid = Number(parts[1] ?? "");
|
|
4006
|
+
if (!command || !Number.isFinite(pid)) return null;
|
|
4007
|
+
return { pid, command };
|
|
4008
|
+
} catch {
|
|
4009
|
+
return null;
|
|
4010
|
+
}
|
|
4011
|
+
}
|
|
4012
|
+
|
|
3992
4013
|
// src/daemon/staticFrontend.ts
|
|
3993
4014
|
import { readFileSync as readFileSync6 } from "node:fs";
|
|
3994
4015
|
import { join as join9 } from "node:path";
|
|
@@ -4010,9 +4031,318 @@ function attachStaticFrontend(webRoot2) {
|
|
|
4010
4031
|
};
|
|
4011
4032
|
}
|
|
4012
4033
|
|
|
4034
|
+
// src/daemon/service.ts
|
|
4035
|
+
import { existsSync as existsSync8, statSync } from "node:fs";
|
|
4036
|
+
import { mkdir as mkdir4, readFile as readFile6, rm as rm2, writeFile as writeFile5 } from "node:fs/promises";
|
|
4037
|
+
import { homedir as homedir7 } from "node:os";
|
|
4038
|
+
import { dirname as dirname10, join as join10 } from "node:path";
|
|
4039
|
+
import { createReadStream } from "node:fs";
|
|
4040
|
+
import { execFile as execFile2, spawn as spawn6 } from "node:child_process";
|
|
4041
|
+
import { createInterface } from "node:readline";
|
|
4042
|
+
import { promisify as promisify2 } from "node:util";
|
|
4043
|
+
var execFileAsync2 = promisify2(execFile2);
|
|
4044
|
+
async function installService(context) {
|
|
4045
|
+
if (process.platform === "darwin") {
|
|
4046
|
+
const spec = buildLaunchdSpec(context);
|
|
4047
|
+
await mkdir4(dirname10(spec.plistPath), { recursive: true });
|
|
4048
|
+
await mkdir4(dirname10(spec.stdoutPath), { recursive: true });
|
|
4049
|
+
await writeFile5(spec.plistPath, renderLaunchdPlist(spec), "utf8");
|
|
4050
|
+
await runLaunchctl(["unload", spec.plistPath]).catch(() => void 0);
|
|
4051
|
+
await runLaunchctl(["load", spec.plistPath]);
|
|
4052
|
+
await runLaunchctl(["kickstart", "-k", `gui/${process.getuid?.() ?? 0}/${spec.label}`]);
|
|
4053
|
+
return await readServiceStatus(context);
|
|
4054
|
+
}
|
|
4055
|
+
if (process.platform === "linux") {
|
|
4056
|
+
const spec = buildSystemdUserSpec(context);
|
|
4057
|
+
await mkdir4(dirname10(spec.unitPath), { recursive: true });
|
|
4058
|
+
await mkdir4(dirname10(spec.stdoutPath), { recursive: true });
|
|
4059
|
+
await writeFile5(spec.unitPath, renderSystemdUnit(spec), "utf8");
|
|
4060
|
+
await runSystemctl(["--user", "daemon-reload"]);
|
|
4061
|
+
await runSystemctl(["--user", "enable", "--now", spec.unitName]);
|
|
4062
|
+
return await readServiceStatus(context);
|
|
4063
|
+
}
|
|
4064
|
+
throw new Error("service_install_not_supported_on_this_platform");
|
|
4065
|
+
}
|
|
4066
|
+
async function startService(context) {
|
|
4067
|
+
if (process.platform === "darwin") {
|
|
4068
|
+
const spec = buildLaunchdSpec(context);
|
|
4069
|
+
await ensureFileExists(spec.plistPath, "service_not_installed");
|
|
4070
|
+
await runLaunchctl(["load", spec.plistPath]).catch(() => void 0);
|
|
4071
|
+
await runLaunchctl(["kickstart", "-k", `gui/${process.getuid?.() ?? 0}/${spec.label}`]);
|
|
4072
|
+
return await readServiceStatus(context);
|
|
4073
|
+
}
|
|
4074
|
+
if (process.platform === "linux") {
|
|
4075
|
+
const spec = buildSystemdUserSpec(context);
|
|
4076
|
+
await ensureFileExists(spec.unitPath, "service_not_installed");
|
|
4077
|
+
await runSystemctl(["--user", "start", spec.unitName]);
|
|
4078
|
+
return await readServiceStatus(context);
|
|
4079
|
+
}
|
|
4080
|
+
throw new Error("service_start_not_supported_on_this_platform");
|
|
4081
|
+
}
|
|
4082
|
+
async function stopService(context) {
|
|
4083
|
+
if (process.platform === "darwin") {
|
|
4084
|
+
const spec = buildLaunchdSpec(context);
|
|
4085
|
+
if (!existsSync8(spec.plistPath)) return await readServiceStatus(context);
|
|
4086
|
+
await runLaunchctl(["bootout", `gui/${process.getuid?.() ?? 0}`, spec.plistPath]).catch(() => void 0);
|
|
4087
|
+
return await readServiceStatus(context);
|
|
4088
|
+
}
|
|
4089
|
+
if (process.platform === "linux") {
|
|
4090
|
+
const spec = buildSystemdUserSpec(context);
|
|
4091
|
+
if (!existsSync8(spec.unitPath)) return await readServiceStatus(context);
|
|
4092
|
+
await runSystemctl(["--user", "stop", spec.unitName]).catch(() => void 0);
|
|
4093
|
+
return await readServiceStatus(context);
|
|
4094
|
+
}
|
|
4095
|
+
throw new Error("service_stop_not_supported_on_this_platform");
|
|
4096
|
+
}
|
|
4097
|
+
async function uninstallService(context) {
|
|
4098
|
+
if (process.platform === "darwin") {
|
|
4099
|
+
const spec = buildLaunchdSpec(context);
|
|
4100
|
+
if (existsSync8(spec.plistPath)) {
|
|
4101
|
+
await runLaunchctl(["bootout", `gui/${process.getuid?.() ?? 0}`, spec.plistPath]).catch(() => void 0);
|
|
4102
|
+
await rm2(spec.plistPath, { force: true });
|
|
4103
|
+
}
|
|
4104
|
+
return await readServiceStatus(context);
|
|
4105
|
+
}
|
|
4106
|
+
if (process.platform === "linux") {
|
|
4107
|
+
const spec = buildSystemdUserSpec(context);
|
|
4108
|
+
if (existsSync8(spec.unitPath)) {
|
|
4109
|
+
await runSystemctl(["--user", "disable", "--now", spec.unitName]).catch(() => void 0);
|
|
4110
|
+
await rm2(spec.unitPath, { force: true });
|
|
4111
|
+
await runSystemctl(["--user", "daemon-reload"]).catch(() => void 0);
|
|
4112
|
+
}
|
|
4113
|
+
return await readServiceStatus(context);
|
|
4114
|
+
}
|
|
4115
|
+
throw new Error("service_uninstall_not_supported_on_this_platform");
|
|
4116
|
+
}
|
|
4117
|
+
async function restartService(context) {
|
|
4118
|
+
if (process.platform === "darwin") {
|
|
4119
|
+
const spec = buildLaunchdSpec(context);
|
|
4120
|
+
await ensureFileExists(spec.plistPath, "service_not_installed");
|
|
4121
|
+
await runLaunchctl(["kickstart", "-k", `gui/${process.getuid?.() ?? 0}/${spec.label}`]);
|
|
4122
|
+
return await readServiceStatus(context);
|
|
4123
|
+
}
|
|
4124
|
+
if (process.platform === "linux") {
|
|
4125
|
+
const spec = buildSystemdUserSpec(context);
|
|
4126
|
+
await ensureFileExists(spec.unitPath, "service_not_installed");
|
|
4127
|
+
await runSystemctl(["--user", "restart", spec.unitName]);
|
|
4128
|
+
return await readServiceStatus(context);
|
|
4129
|
+
}
|
|
4130
|
+
throw new Error("service_restart_not_supported_on_this_platform");
|
|
4131
|
+
}
|
|
4132
|
+
async function readServiceLogs(context, lines = 120) {
|
|
4133
|
+
const status = await readServiceStatus(context);
|
|
4134
|
+
const stdoutTail = await tailFile(status.stdoutPath, lines);
|
|
4135
|
+
const stderrTail = await tailFile(status.stderrPath, lines);
|
|
4136
|
+
return [
|
|
4137
|
+
`[stdout] ${status.stdoutPath}`,
|
|
4138
|
+
stdoutTail || "(empty)",
|
|
4139
|
+
"",
|
|
4140
|
+
`[stderr] ${status.stderrPath}`,
|
|
4141
|
+
stderrTail || "(empty)"
|
|
4142
|
+
].join("\n");
|
|
4143
|
+
}
|
|
4144
|
+
async function tailServiceLogs(context) {
|
|
4145
|
+
const status = await readServiceStatus(context);
|
|
4146
|
+
await tailFiles([status.stdoutPath, status.stderrPath]);
|
|
4147
|
+
}
|
|
4148
|
+
async function readServiceStatus(context) {
|
|
4149
|
+
if (process.platform === "darwin") {
|
|
4150
|
+
const spec = buildLaunchdSpec(context);
|
|
4151
|
+
const installed = existsSync8(spec.plistPath);
|
|
4152
|
+
const running = installed ? await runLaunchctl(["print", `gui/${process.getuid?.() ?? 0}/${spec.label}`]).then(() => true, () => false) : false;
|
|
4153
|
+
return {
|
|
4154
|
+
manager: "launchd",
|
|
4155
|
+
installed,
|
|
4156
|
+
running,
|
|
4157
|
+
serviceFilePath: spec.plistPath,
|
|
4158
|
+
label: spec.label,
|
|
4159
|
+
stdoutPath: spec.stdoutPath,
|
|
4160
|
+
stderrPath: spec.stderrPath
|
|
4161
|
+
};
|
|
4162
|
+
}
|
|
4163
|
+
if (process.platform === "linux") {
|
|
4164
|
+
const spec = buildSystemdUserSpec(context);
|
|
4165
|
+
const installed = existsSync8(spec.unitPath);
|
|
4166
|
+
const running = installed ? await runSystemctl(["--user", "is-active", "--quiet", spec.unitName]).then(() => true, () => false) : false;
|
|
4167
|
+
return {
|
|
4168
|
+
manager: "systemd-user",
|
|
4169
|
+
installed,
|
|
4170
|
+
running,
|
|
4171
|
+
serviceFilePath: spec.unitPath,
|
|
4172
|
+
label: spec.unitName,
|
|
4173
|
+
stdoutPath: spec.stdoutPath,
|
|
4174
|
+
stderrPath: spec.stderrPath
|
|
4175
|
+
};
|
|
4176
|
+
}
|
|
4177
|
+
throw new Error("service_status_not_supported_on_this_platform");
|
|
4178
|
+
}
|
|
4179
|
+
function buildLaunchdSpec(context) {
|
|
4180
|
+
const stateDir = join10(homedir7(), ".claude-codex-wechat");
|
|
4181
|
+
return {
|
|
4182
|
+
label: "com.claude-codex-wechat",
|
|
4183
|
+
plistPath: join10(homedir7(), "Library", "LaunchAgents", "com.claude-codex-wechat.plist"),
|
|
4184
|
+
programArgs: [context.nodePath ?? process.execPath, context.cliEntrypointPath, "start"],
|
|
4185
|
+
workingDirectory: homedir7(),
|
|
4186
|
+
stdoutPath: join10(stateDir, "logs", "service.stdout.log"),
|
|
4187
|
+
stderrPath: join10(stateDir, "logs", "service.stderr.log"),
|
|
4188
|
+
environment: buildServiceEnvironment(context)
|
|
4189
|
+
};
|
|
4190
|
+
}
|
|
4191
|
+
function buildSystemdUserSpec(context) {
|
|
4192
|
+
const stateDir = join10(homedir7(), ".claude-codex-wechat");
|
|
4193
|
+
return {
|
|
4194
|
+
unitName: "claude-codex-wechat.service",
|
|
4195
|
+
unitPath: join10(homedir7(), ".config", "systemd", "user", "claude-codex-wechat.service"),
|
|
4196
|
+
execStart: [context.nodePath ?? process.execPath, context.cliEntrypointPath, "start"],
|
|
4197
|
+
workingDirectory: homedir7(),
|
|
4198
|
+
stdoutPath: join10(stateDir, "logs", "service.stdout.log"),
|
|
4199
|
+
stderrPath: join10(stateDir, "logs", "service.stderr.log"),
|
|
4200
|
+
environment: buildServiceEnvironment(context)
|
|
4201
|
+
};
|
|
4202
|
+
}
|
|
4203
|
+
function buildServiceEnvironment(context) {
|
|
4204
|
+
const env = {
|
|
4205
|
+
PATH: process.env.PATH ?? "",
|
|
4206
|
+
HOME: process.env.HOME ?? homedir7()
|
|
4207
|
+
};
|
|
4208
|
+
const configPath = context.configPath ?? process.env.BRIDGE_CONFIG;
|
|
4209
|
+
const port = context.port ?? Number(process.env.BRIDGE_PORT ?? 8787);
|
|
4210
|
+
if (configPath) env.BRIDGE_CONFIG = configPath;
|
|
4211
|
+
if (Number.isFinite(port)) env.BRIDGE_PORT = String(port);
|
|
4212
|
+
for (const key of [
|
|
4213
|
+
"BRIDGE_WECHAT_ENABLED",
|
|
4214
|
+
"BRIDGE_WECHAT_BASE_URL",
|
|
4215
|
+
"BRIDGE_WECHAT_TOKEN",
|
|
4216
|
+
"BRIDGE_WECHAT_ACCOUNT_ID",
|
|
4217
|
+
"BRIDGE_CLAUDE_COMMAND",
|
|
4218
|
+
"BRIDGE_CODEX_COMMAND",
|
|
4219
|
+
"HTTP_PROXY",
|
|
4220
|
+
"HTTPS_PROXY",
|
|
4221
|
+
"http_proxy",
|
|
4222
|
+
"https_proxy"
|
|
4223
|
+
]) {
|
|
4224
|
+
const value = process.env[key];
|
|
4225
|
+
if (value) env[key] = value;
|
|
4226
|
+
}
|
|
4227
|
+
return env;
|
|
4228
|
+
}
|
|
4229
|
+
function renderLaunchdPlist(spec) {
|
|
4230
|
+
const programArgs = spec.programArgs.map((arg) => ` <string>${escapeXml(arg)}</string>`).join("\n");
|
|
4231
|
+
const envEntries = Object.entries(spec.environment).map(([key, value]) => ` <key>${escapeXml(key)}</key>
|
|
4232
|
+
<string>${escapeXml(value)}</string>`).join("\n");
|
|
4233
|
+
return [
|
|
4234
|
+
'<?xml version="1.0" encoding="UTF-8"?>',
|
|
4235
|
+
'<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">',
|
|
4236
|
+
'<plist version="1.0">',
|
|
4237
|
+
"<dict>",
|
|
4238
|
+
" <key>Label</key>",
|
|
4239
|
+
` <string>${escapeXml(spec.label)}</string>`,
|
|
4240
|
+
" <key>ProgramArguments</key>",
|
|
4241
|
+
" <array>",
|
|
4242
|
+
programArgs,
|
|
4243
|
+
" </array>",
|
|
4244
|
+
" <key>WorkingDirectory</key>",
|
|
4245
|
+
` <string>${escapeXml(spec.workingDirectory)}</string>`,
|
|
4246
|
+
" <key>EnvironmentVariables</key>",
|
|
4247
|
+
" <dict>",
|
|
4248
|
+
envEntries,
|
|
4249
|
+
" </dict>",
|
|
4250
|
+
" <key>RunAtLoad</key>",
|
|
4251
|
+
" <true/>",
|
|
4252
|
+
" <key>KeepAlive</key>",
|
|
4253
|
+
" <true/>",
|
|
4254
|
+
" <key>StandardOutPath</key>",
|
|
4255
|
+
` <string>${escapeXml(spec.stdoutPath)}</string>`,
|
|
4256
|
+
" <key>StandardErrorPath</key>",
|
|
4257
|
+
` <string>${escapeXml(spec.stderrPath)}</string>`,
|
|
4258
|
+
"</dict>",
|
|
4259
|
+
"</plist>",
|
|
4260
|
+
""
|
|
4261
|
+
].join("\n");
|
|
4262
|
+
}
|
|
4263
|
+
function renderSystemdUnit(spec) {
|
|
4264
|
+
const envEntries = Object.entries(spec.environment).map(([key, value]) => `Environment=${quoteSystemdEnv(`${key}=${value}`)}`).join("\n");
|
|
4265
|
+
return [
|
|
4266
|
+
"[Unit]",
|
|
4267
|
+
"Description=claude-codex-wechat bridge daemon",
|
|
4268
|
+
"After=network.target",
|
|
4269
|
+
"",
|
|
4270
|
+
"[Service]",
|
|
4271
|
+
"Type=simple",
|
|
4272
|
+
`WorkingDirectory=${spec.workingDirectory}`,
|
|
4273
|
+
`ExecStart=${spec.execStart.map(quoteSystemdArg).join(" ")}`,
|
|
4274
|
+
envEntries,
|
|
4275
|
+
`StandardOutput=append:${spec.stdoutPath}`,
|
|
4276
|
+
`StandardError=append:${spec.stderrPath}`,
|
|
4277
|
+
"Restart=on-failure",
|
|
4278
|
+
"RestartSec=5",
|
|
4279
|
+
"",
|
|
4280
|
+
"[Install]",
|
|
4281
|
+
"WantedBy=default.target",
|
|
4282
|
+
""
|
|
4283
|
+
].join("\n");
|
|
4284
|
+
}
|
|
4285
|
+
function escapeXml(value) {
|
|
4286
|
+
return value.replaceAll("&", "&").replaceAll("<", "<").replaceAll(">", ">").replaceAll('"', """).replaceAll("'", "'");
|
|
4287
|
+
}
|
|
4288
|
+
function quoteSystemdArg(value) {
|
|
4289
|
+
return `"${value.replaceAll("\\", "\\\\").replaceAll('"', '\\"')}"`;
|
|
4290
|
+
}
|
|
4291
|
+
function quoteSystemdEnv(value) {
|
|
4292
|
+
return `"${value.replaceAll("\\", "\\\\").replaceAll('"', '\\"')}"`;
|
|
4293
|
+
}
|
|
4294
|
+
async function runLaunchctl(args) {
|
|
4295
|
+
await execFileAsync2("launchctl", args);
|
|
4296
|
+
}
|
|
4297
|
+
async function runSystemctl(args) {
|
|
4298
|
+
await execFileAsync2("systemctl", args);
|
|
4299
|
+
}
|
|
4300
|
+
async function ensureFileExists(path, errorCode) {
|
|
4301
|
+
if (!existsSync8(path)) throw new Error(errorCode);
|
|
4302
|
+
await readFile6(path, "utf8");
|
|
4303
|
+
}
|
|
4304
|
+
async function tailFile(path, lines) {
|
|
4305
|
+
if (!existsSync8(path)) return "";
|
|
4306
|
+
const content = await readFile6(path, "utf8");
|
|
4307
|
+
return content.split(/\r?\n/).slice(-lines).join("\n").trim();
|
|
4308
|
+
}
|
|
4309
|
+
async function tailFiles(paths) {
|
|
4310
|
+
const readers = paths.filter((path) => existsSync8(path)).map((path) => {
|
|
4311
|
+
const rl = createInterface({
|
|
4312
|
+
input: createReadStream(path, { encoding: "utf8", start: Math.max(0, fileSizeHint(path) - 8192) }),
|
|
4313
|
+
crlfDelay: Infinity
|
|
4314
|
+
});
|
|
4315
|
+
rl.on("line", (line) => {
|
|
4316
|
+
process.stdout.write(`[${path}] ${line}
|
|
4317
|
+
`);
|
|
4318
|
+
});
|
|
4319
|
+
return rl;
|
|
4320
|
+
});
|
|
4321
|
+
if (process.platform !== "linux" && process.platform !== "darwin") {
|
|
4322
|
+
return;
|
|
4323
|
+
}
|
|
4324
|
+
const child = spawn6("tail", ["-f", ...paths.filter((path) => existsSync8(path))], { stdio: ["ignore", "pipe", "inherit"] });
|
|
4325
|
+
child.stdout.on("data", (chunk) => {
|
|
4326
|
+
process.stdout.write(String(chunk));
|
|
4327
|
+
});
|
|
4328
|
+
await new Promise((resolve, reject) => {
|
|
4329
|
+
child.on("exit", () => resolve());
|
|
4330
|
+
child.on("error", reject);
|
|
4331
|
+
}).finally(() => {
|
|
4332
|
+
for (const reader of readers) reader.close();
|
|
4333
|
+
});
|
|
4334
|
+
}
|
|
4335
|
+
function fileSizeHint(path) {
|
|
4336
|
+
try {
|
|
4337
|
+
return statSync(path).size;
|
|
4338
|
+
} catch {
|
|
4339
|
+
return 0;
|
|
4340
|
+
}
|
|
4341
|
+
}
|
|
4342
|
+
|
|
4013
4343
|
// src/cli.ts
|
|
4014
|
-
var here =
|
|
4015
|
-
var webRoot =
|
|
4344
|
+
var here = dirname11(fileURLToPath(import.meta.url));
|
|
4345
|
+
var webRoot = join11(here, "..", "web");
|
|
4016
4346
|
async function main() {
|
|
4017
4347
|
const command = process.argv[2];
|
|
4018
4348
|
switch (command) {
|
|
@@ -4026,6 +4356,9 @@ async function main() {
|
|
|
4026
4356
|
case "doctor":
|
|
4027
4357
|
await cmdDoctor();
|
|
4028
4358
|
return;
|
|
4359
|
+
case "service":
|
|
4360
|
+
await cmdService(process.argv.slice(3));
|
|
4361
|
+
return;
|
|
4029
4362
|
case "print-config":
|
|
4030
4363
|
cmdPrintConfig();
|
|
4031
4364
|
return;
|
|
@@ -4042,21 +4375,36 @@ async function main() {
|
|
|
4042
4375
|
}
|
|
4043
4376
|
}
|
|
4044
4377
|
async function cmdStart() {
|
|
4045
|
-
if (!
|
|
4378
|
+
if (!existsSync9(webRoot)) {
|
|
4046
4379
|
console.error(`\u627E\u4E0D\u5230\u524D\u7AEF\u6784\u5EFA\u4EA7\u7269: ${webRoot}`);
|
|
4047
4380
|
console.error("\u8BF7\u5148\u8FD0\u884C\u6784\u5EFA (pnpm build) \u540E\u518D\u542F\u52A8\uFF0C\u6216\u91CD\u65B0\u5B89\u88C5\u5B8C\u6574\u7684 npm \u5305\u3002");
|
|
4048
4381
|
process.exitCode = 1;
|
|
4049
4382
|
return;
|
|
4050
4383
|
}
|
|
4384
|
+
const context = createServiceContext();
|
|
4385
|
+
const port = context.port ?? 8787;
|
|
4386
|
+
const occupiedBy = await findListeningProcess(port);
|
|
4387
|
+
if (occupiedBy) {
|
|
4388
|
+
const serviceStatus = await readServiceStatus(context).catch(() => null);
|
|
4389
|
+
if (serviceStatus?.installed && serviceStatus.running) {
|
|
4390
|
+
console.log(`\u7AEF\u53E3 ${port} \u5DF2\u88AB\u540E\u53F0\u670D\u52A1\u5360\u7528\uFF0C\u5148\u505C\u6B62\u670D\u52A1\u518D\u4EE5\u524D\u53F0\u6A21\u5F0F\u542F\u52A8\u3002`);
|
|
4391
|
+
await stopService(context);
|
|
4392
|
+
} else {
|
|
4393
|
+
console.error(`\u7AEF\u53E3 ${port} \u5DF2\u88AB\u5360\u7528: PID=${occupiedBy.pid} COMMAND=${occupiedBy.command}`);
|
|
4394
|
+
console.error("\u8BF7\u5148\u505C\u6B62\u5360\u7528\u8FDB\u7A0B\uFF0C\u6216\u6539\u7528 `claude-codex-wechat service stop` \u505C\u6389\u540E\u53F0\u670D\u52A1\u3002");
|
|
4395
|
+
process.exitCode = 1;
|
|
4396
|
+
return;
|
|
4397
|
+
}
|
|
4398
|
+
}
|
|
4051
4399
|
await startDaemon({
|
|
4052
4400
|
attachFrontend: attachStaticFrontend(webRoot)
|
|
4053
4401
|
});
|
|
4054
4402
|
}
|
|
4055
4403
|
function cmdInit() {
|
|
4056
4404
|
const configPath = process.env.BRIDGE_CONFIG ?? defaultConfigPath();
|
|
4057
|
-
const dir =
|
|
4405
|
+
const dir = dirname11(configPath);
|
|
4058
4406
|
mkdirSync6(dir, { recursive: true });
|
|
4059
|
-
if (
|
|
4407
|
+
if (existsSync9(configPath)) {
|
|
4060
4408
|
console.log(`\u914D\u7F6E\u5DF2\u5B58\u5728\uFF0C\u672A\u8986\u76D6: ${configPath}`);
|
|
4061
4409
|
return;
|
|
4062
4410
|
}
|
|
@@ -4081,13 +4429,13 @@ function cmdInit() {
|
|
|
4081
4429
|
async function cmdDoctor() {
|
|
4082
4430
|
const configPath = process.env.BRIDGE_CONFIG ?? defaultConfigPath();
|
|
4083
4431
|
console.log("claude-codex-wechat doctor\n");
|
|
4084
|
-
report("\u914D\u7F6E\u6587\u4EF6",
|
|
4085
|
-
report("\u524D\u7AEF\u4EA7\u7269",
|
|
4432
|
+
report("\u914D\u7F6E\u6587\u4EF6", existsSync9(configPath) ? configPath : `\u7F3A\u5931 (${configPath})\uFF0C\u8FD0\u884C init \u521B\u5EFA`);
|
|
4433
|
+
report("\u524D\u7AEF\u4EA7\u7269", existsSync9(webRoot) ? webRoot : `\u7F3A\u5931 (${webRoot})`);
|
|
4086
4434
|
const claude = await findExecutable("claude");
|
|
4087
4435
|
report("claude \u53EF\u6267\u884C", claude ?? "\u672A\u627E\u5230\uFF0C\u9700\u5148\u5B89\u88C5\u5E76\u767B\u5F55 Claude Code");
|
|
4088
4436
|
const codex = await findExecutable("codex");
|
|
4089
4437
|
report("codex \u53EF\u6267\u884C", codex ?? "\u672A\u627E\u5230\uFF08\u4EC5\u4F7F\u7528 Claude \u65F6\u53EF\u5FFD\u7565\uFF09");
|
|
4090
|
-
if (
|
|
4438
|
+
if (existsSync9(configPath)) {
|
|
4091
4439
|
const config = loadBridgeConfig(configPath);
|
|
4092
4440
|
const wechat = config.wechat;
|
|
4093
4441
|
report("\u5FAE\u4FE1\u542F\u7528", wechat?.enabled ? "\u662F" : "\u5426");
|
|
@@ -4097,16 +4445,84 @@ async function cmdDoctor() {
|
|
|
4097
4445
|
}
|
|
4098
4446
|
function cmdPrintConfig() {
|
|
4099
4447
|
const configPath = process.env.BRIDGE_CONFIG ?? defaultConfigPath();
|
|
4100
|
-
if (!
|
|
4448
|
+
if (!existsSync9(configPath)) {
|
|
4101
4449
|
console.error(`\u914D\u7F6E\u4E0D\u5B58\u5728: ${configPath}\uFF08\u8FD0\u884C init \u521B\u5EFA\uFF09`);
|
|
4102
4450
|
process.exitCode = 1;
|
|
4103
4451
|
return;
|
|
4104
4452
|
}
|
|
4105
4453
|
console.log(readFileSync7(configPath, "utf8"));
|
|
4106
4454
|
}
|
|
4455
|
+
async function cmdService(args) {
|
|
4456
|
+
const action = args[0] ?? "status";
|
|
4457
|
+
const context = createServiceContext();
|
|
4458
|
+
switch (action) {
|
|
4459
|
+
case "install": {
|
|
4460
|
+
const status = await installService(context);
|
|
4461
|
+
printServiceStatus("service installed", status);
|
|
4462
|
+
return;
|
|
4463
|
+
}
|
|
4464
|
+
case "start": {
|
|
4465
|
+
const status = await startService(context);
|
|
4466
|
+
printServiceStatus("service started", status);
|
|
4467
|
+
return;
|
|
4468
|
+
}
|
|
4469
|
+
case "stop": {
|
|
4470
|
+
const status = await stopService(context);
|
|
4471
|
+
printServiceStatus("service stopped", status);
|
|
4472
|
+
return;
|
|
4473
|
+
}
|
|
4474
|
+
case "restart": {
|
|
4475
|
+
const status = await restartService(context);
|
|
4476
|
+
printServiceStatus("service restarted", status);
|
|
4477
|
+
return;
|
|
4478
|
+
}
|
|
4479
|
+
case "logs": {
|
|
4480
|
+
console.log(await readServiceLogs(context));
|
|
4481
|
+
return;
|
|
4482
|
+
}
|
|
4483
|
+
case "tail": {
|
|
4484
|
+
await tailServiceLogs(context);
|
|
4485
|
+
return;
|
|
4486
|
+
}
|
|
4487
|
+
case "uninstall": {
|
|
4488
|
+
const status = await uninstallService(context);
|
|
4489
|
+
printServiceStatus("service uninstalled", status);
|
|
4490
|
+
return;
|
|
4491
|
+
}
|
|
4492
|
+
case "status": {
|
|
4493
|
+
const status = await readServiceStatus(context);
|
|
4494
|
+
printServiceStatus("service status", status);
|
|
4495
|
+
return;
|
|
4496
|
+
}
|
|
4497
|
+
default:
|
|
4498
|
+
console.error(`\u672A\u77E5 service \u5B50\u547D\u4EE4: ${action}
|
|
4499
|
+
`);
|
|
4500
|
+
printUsage();
|
|
4501
|
+
process.exitCode = 1;
|
|
4502
|
+
}
|
|
4503
|
+
}
|
|
4504
|
+
function createServiceContext() {
|
|
4505
|
+
return {
|
|
4506
|
+
cliEntrypointPath: fileURLToPath(import.meta.url),
|
|
4507
|
+
nodePath: process.execPath,
|
|
4508
|
+
configPath: process.env.BRIDGE_CONFIG ?? defaultConfigPath(),
|
|
4509
|
+
port: Number(process.env.BRIDGE_PORT ?? 8787)
|
|
4510
|
+
};
|
|
4511
|
+
}
|
|
4107
4512
|
function report(label, value) {
|
|
4108
4513
|
console.log(` ${label.padEnd(14)}: ${value}`);
|
|
4109
4514
|
}
|
|
4515
|
+
function printServiceStatus(title, status) {
|
|
4516
|
+
console.log(`claude-codex-wechat ${title}
|
|
4517
|
+
`);
|
|
4518
|
+
report("service manager", status.manager);
|
|
4519
|
+
report("installed", status.installed ? "yes" : "no");
|
|
4520
|
+
report("running", status.running ? "yes" : "no");
|
|
4521
|
+
report("label", status.label);
|
|
4522
|
+
report("service file", status.serviceFilePath);
|
|
4523
|
+
report("stdout log", status.stdoutPath);
|
|
4524
|
+
report("stderr log", status.stderrPath);
|
|
4525
|
+
}
|
|
4110
4526
|
function printUsage() {
|
|
4111
4527
|
console.log(`claude-codex-wechat \u2014 \u672C\u5730 WeChat \u2194 Claude/Codex bridge daemon
|
|
4112
4528
|
|
|
@@ -4117,6 +4533,7 @@ function printUsage() {
|
|
|
4117
4533
|
start \u542F\u52A8 daemon\uFF08\u9ED8\u8BA4\u547D\u4EE4\uFF0C\u524D\u53F0\u8FD0\u884C\uFF09
|
|
4118
4534
|
init \u5728 ~/.claude-codex-wechat/ \u521B\u5EFA\u9ED8\u8BA4\u914D\u7F6E
|
|
4119
4535
|
doctor \u68C0\u67E5\u914D\u7F6E\u3001\u524D\u7AEF\u4EA7\u7269\u4E0E claude/codex \u53EF\u6267\u884C\u6587\u4EF6
|
|
4536
|
+
service \u7BA1\u7406\u540E\u53F0\u670D\u52A1\uFF08install/start/stop/restart/status/logs/tail/uninstall\uFF09
|
|
4120
4537
|
print-config \u6253\u5370\u5F53\u524D\u914D\u7F6E\u6587\u4EF6\u5185\u5BB9
|
|
4121
4538
|
help \u663E\u793A\u672C\u5E2E\u52A9
|
|
4122
4539
|
|