cli-wechat-bridge 1.0.5
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE.txt +21 -0
- package/README.md +637 -0
- package/bin/_run-entry.mjs +35 -0
- package/bin/wechat-bridge-claude.mjs +5 -0
- package/bin/wechat-bridge-codex.mjs +5 -0
- package/bin/wechat-bridge-opencode.mjs +5 -0
- package/bin/wechat-bridge-shell.mjs +5 -0
- package/bin/wechat-bridge.mjs +5 -0
- package/bin/wechat-check-update.mjs +5 -0
- package/bin/wechat-claude-start.mjs +5 -0
- package/bin/wechat-claude.mjs +5 -0
- package/bin/wechat-codex-start.mjs +5 -0
- package/bin/wechat-codex.mjs +5 -0
- package/bin/wechat-daemon.mjs +5 -0
- package/bin/wechat-opencode-start.mjs +5 -0
- package/bin/wechat-opencode.mjs +5 -0
- package/bin/wechat-setup.mjs +5 -0
- package/dist/bridge/bridge-adapter-common.js +95 -0
- package/dist/bridge/bridge-adapters.claude.js +829 -0
- package/dist/bridge/bridge-adapters.codex.js +2228 -0
- package/dist/bridge/bridge-adapters.core.js +717 -0
- package/dist/bridge/bridge-adapters.js +26 -0
- package/dist/bridge/bridge-adapters.opencode.js +2129 -0
- package/dist/bridge/bridge-adapters.shared.js +1005 -0
- package/dist/bridge/bridge-adapters.shell.js +363 -0
- package/dist/bridge/bridge-controller.js +48 -0
- package/dist/bridge/bridge-final-reply.js +46 -0
- package/dist/bridge/bridge-process-reaper.js +348 -0
- package/dist/bridge/bridge-state.js +362 -0
- package/dist/bridge/bridge-types.js +1 -0
- package/dist/bridge/bridge-utils.js +1240 -0
- package/dist/bridge/claude-hook.js +82 -0
- package/dist/bridge/claude-hooks.js +267 -0
- package/dist/bridge/wechat-bridge.js +1026 -0
- package/dist/commands/check-update.js +30 -0
- package/dist/companion/codex-panel-link.js +72 -0
- package/dist/companion/codex-panel.js +179 -0
- package/dist/companion/codex-remote-client.js +124 -0
- package/dist/companion/local-companion-link.js +240 -0
- package/dist/companion/local-companion-start.js +420 -0
- package/dist/companion/local-companion.js +424 -0
- package/dist/daemon/daemon-link.js +175 -0
- package/dist/daemon/wechat-daemon.js +1202 -0
- package/dist/media/media-types.js +1 -0
- package/dist/runtime/create-runtime-host.js +12 -0
- package/dist/runtime/legacy-adapter-runtime.js +46 -0
- package/dist/runtime/runtime-types.js +5 -0
- package/dist/utils/version-checker.js +161 -0
- package/dist/wechat/channel-config.js +196 -0
- package/dist/wechat/setup.js +283 -0
- package/dist/wechat/standalone-bot.js +355 -0
- package/dist/wechat/wechat-channel.js +492 -0
- package/dist/wechat/wechat-transport.js +1213 -0
- package/package.json +101 -0
|
@@ -0,0 +1,420 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import net from "node:net";
|
|
3
|
+
import path from "node:path";
|
|
4
|
+
import { spawn } from "node:child_process";
|
|
5
|
+
import { fileURLToPath } from "node:url";
|
|
6
|
+
import { BRIDGE_LOG_FILE } from "../wechat/channel-config.js";
|
|
7
|
+
import { ensureWechatCredentials } from "../wechat/setup.js";
|
|
8
|
+
import { readBridgeLockFile, shouldAutoReclaimBridgeLock, } from "../bridge/bridge-state.js";
|
|
9
|
+
import { clearLocalCompanionOccupancy, clearLocalCompanionEndpoint, readLocalCompanionEndpoint, } from "./local-companion-link.js";
|
|
10
|
+
import { runCodexRemoteClient } from "./codex-remote-client.js";
|
|
11
|
+
import { runLocalCompanion } from "./local-companion.js";
|
|
12
|
+
import { clearDaemonEndpoint, isDaemonEndpointAlive, readDaemonEndpoint, sendDaemonRequest, } from "../daemon/daemon-link.js";
|
|
13
|
+
const MODULE_FILE = fileURLToPath(import.meta.url);
|
|
14
|
+
const MODULE_DIR = path.dirname(MODULE_FILE);
|
|
15
|
+
const RUNTIME_ENTRY_EXTENSION = path.extname(MODULE_FILE) === ".ts" ? ".ts" : ".js";
|
|
16
|
+
const DEFAULT_WAIT_TIMEOUT_MS = 15_000;
|
|
17
|
+
const DEFAULT_ADAPTER = "codex";
|
|
18
|
+
function log(adapter, message) {
|
|
19
|
+
process.stderr.write(`[wechat-${adapter}-start] ${message}\n`);
|
|
20
|
+
}
|
|
21
|
+
export function normalizeComparablePath(cwd) {
|
|
22
|
+
const normalized = path.resolve(cwd);
|
|
23
|
+
return process.platform === "win32" ? normalized.toLowerCase() : normalized;
|
|
24
|
+
}
|
|
25
|
+
export function isSameWorkspaceCwd(left, right) {
|
|
26
|
+
return normalizeComparablePath(left) === normalizeComparablePath(right);
|
|
27
|
+
}
|
|
28
|
+
export function formatAlreadyActiveMessage(cwd) {
|
|
29
|
+
return `Current workspace is already active: ${cwd}. Visible companion is already running, so nothing else was opened.`;
|
|
30
|
+
}
|
|
31
|
+
export function formatSwitchMessage(fromCwd, toCwd) {
|
|
32
|
+
return `Detected active workspace ${fromCwd}. Switching to ${toCwd}...`;
|
|
33
|
+
}
|
|
34
|
+
export function formatSwitchFailureMessage(cwd) {
|
|
35
|
+
return `Failed to stop the previous workspace bridge. Switch canceled; current workspace remains ${cwd}.`;
|
|
36
|
+
}
|
|
37
|
+
export function formatRestartUnhealthyMessage(cwd) {
|
|
38
|
+
return `Detected unhealthy companion state for ${cwd}. Restarting bridge...`;
|
|
39
|
+
}
|
|
40
|
+
export function decideLaunchAction(input) {
|
|
41
|
+
if (input.lockShouldAutoReclaim) {
|
|
42
|
+
return {
|
|
43
|
+
kind: "start_bridge",
|
|
44
|
+
message: `Detected reclaimable bridge lock for ${input.runningLock.cwd}. Replacing it for ${input.requestedCwd}...`,
|
|
45
|
+
};
|
|
46
|
+
}
|
|
47
|
+
const sameWorkspace = input.runningLock.adapter === input.requestedAdapter &&
|
|
48
|
+
isSameWorkspaceCwd(input.runningLock.cwd, input.requestedCwd);
|
|
49
|
+
if (!sameWorkspace) {
|
|
50
|
+
return {
|
|
51
|
+
kind: "switch_workspace",
|
|
52
|
+
fromCwd: input.runningLock.cwd,
|
|
53
|
+
toCwd: input.requestedCwd,
|
|
54
|
+
message: formatSwitchMessage(input.runningLock.cwd, input.requestedCwd),
|
|
55
|
+
failureMessage: formatSwitchFailureMessage(input.runningLock.cwd),
|
|
56
|
+
};
|
|
57
|
+
}
|
|
58
|
+
if (!input.endpoint || !input.endpointIsReachable) {
|
|
59
|
+
return {
|
|
60
|
+
kind: "restart_unhealthy",
|
|
61
|
+
message: formatRestartUnhealthyMessage(input.requestedCwd),
|
|
62
|
+
};
|
|
63
|
+
}
|
|
64
|
+
if (input.companionIsAlive &&
|
|
65
|
+
(input.endpoint.companionStatus === "stopped" ||
|
|
66
|
+
input.endpoint.companionStatus === "error")) {
|
|
67
|
+
return {
|
|
68
|
+
kind: "restart_unhealthy",
|
|
69
|
+
message: formatRestartUnhealthyMessage(input.requestedCwd),
|
|
70
|
+
};
|
|
71
|
+
}
|
|
72
|
+
if (input.endpoint && input.endpointIsReachable && input.companionIsAlive) {
|
|
73
|
+
return {
|
|
74
|
+
kind: "already_active",
|
|
75
|
+
message: formatAlreadyActiveMessage(input.requestedCwd),
|
|
76
|
+
};
|
|
77
|
+
}
|
|
78
|
+
return {
|
|
79
|
+
kind: "open_companion",
|
|
80
|
+
message: `Found running bridge for ${input.requestedCwd}. Opening companion...`,
|
|
81
|
+
};
|
|
82
|
+
}
|
|
83
|
+
export function parseCliArgs(argv) {
|
|
84
|
+
let adapter = DEFAULT_ADAPTER;
|
|
85
|
+
let cwd = process.cwd();
|
|
86
|
+
let profile;
|
|
87
|
+
let timeoutMs = DEFAULT_WAIT_TIMEOUT_MS;
|
|
88
|
+
const cliArgs = [];
|
|
89
|
+
for (let i = 0; i < argv.length; i += 1) {
|
|
90
|
+
const arg = argv[i];
|
|
91
|
+
if (!arg) {
|
|
92
|
+
continue;
|
|
93
|
+
}
|
|
94
|
+
const next = argv[i + 1];
|
|
95
|
+
if (arg === "--help" || arg === "-h") {
|
|
96
|
+
process.stdout.write([
|
|
97
|
+
"Usage: wechat-codex-start [--cwd <path>] [--profile <name-or-path>] [--timeout-ms <ms>] [...codex args]",
|
|
98
|
+
" wechat-claude-start [--cwd <path>] [--profile <name-or-path>] [--timeout-ms <ms>] [...claude args]",
|
|
99
|
+
" wechat-opencode-start [--cwd <path>] [--profile <name-or-path>] [--timeout-ms <ms>] [...opencode args]",
|
|
100
|
+
" local-companion-start [--adapter <codex|claude|opencode>] [--cwd <path>] [--profile <name-or-path>] [--timeout-ms <ms>] [...cli args]",
|
|
101
|
+
"",
|
|
102
|
+
"Starts or reuses a Codex, Claude, or OpenCode bridge for the current directory, waits for the local endpoint, then opens the visible companion or panel.",
|
|
103
|
+
"All adapters are companion-bound: closing the companion/panel also stops the bridge.",
|
|
104
|
+
"Unknown arguments are forwarded to the visible CLI client.",
|
|
105
|
+
"",
|
|
106
|
+
].join("\n"));
|
|
107
|
+
process.exit(0);
|
|
108
|
+
}
|
|
109
|
+
if (arg === "--adapter") {
|
|
110
|
+
if (!next || !["codex", "claude", "opencode"].includes(next)) {
|
|
111
|
+
throw new Error(`Invalid adapter: ${next ?? "(missing)"}`);
|
|
112
|
+
}
|
|
113
|
+
adapter = next;
|
|
114
|
+
i += 1;
|
|
115
|
+
continue;
|
|
116
|
+
}
|
|
117
|
+
if (arg === "--cwd") {
|
|
118
|
+
if (!next) {
|
|
119
|
+
throw new Error("--cwd requires a value");
|
|
120
|
+
}
|
|
121
|
+
cwd = path.resolve(next);
|
|
122
|
+
i += 1;
|
|
123
|
+
continue;
|
|
124
|
+
}
|
|
125
|
+
if (arg === "--profile") {
|
|
126
|
+
if (!next) {
|
|
127
|
+
throw new Error("--profile requires a value");
|
|
128
|
+
}
|
|
129
|
+
profile = next;
|
|
130
|
+
i += 1;
|
|
131
|
+
continue;
|
|
132
|
+
}
|
|
133
|
+
if (arg === "--timeout-ms") {
|
|
134
|
+
if (!next) {
|
|
135
|
+
throw new Error("--timeout-ms requires a value");
|
|
136
|
+
}
|
|
137
|
+
const parsed = Number(next);
|
|
138
|
+
if (!Number.isFinite(parsed) || parsed < 1000) {
|
|
139
|
+
throw new Error("--timeout-ms must be a number >= 1000");
|
|
140
|
+
}
|
|
141
|
+
timeoutMs = Math.trunc(parsed);
|
|
142
|
+
i += 1;
|
|
143
|
+
continue;
|
|
144
|
+
}
|
|
145
|
+
cliArgs.push(arg);
|
|
146
|
+
}
|
|
147
|
+
return { adapter, cwd, profile, timeoutMs, cliArgs };
|
|
148
|
+
}
|
|
149
|
+
function isPidAlive(pid) {
|
|
150
|
+
if (!Number.isInteger(pid) || pid <= 0) {
|
|
151
|
+
return false;
|
|
152
|
+
}
|
|
153
|
+
try {
|
|
154
|
+
process.kill(pid, 0);
|
|
155
|
+
return true;
|
|
156
|
+
}
|
|
157
|
+
catch {
|
|
158
|
+
return false;
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
async function waitForProcessExit(pid, timeoutMs) {
|
|
162
|
+
const deadline = Date.now() + timeoutMs;
|
|
163
|
+
while (Date.now() < deadline) {
|
|
164
|
+
if (!isPidAlive(pid)) {
|
|
165
|
+
return true;
|
|
166
|
+
}
|
|
167
|
+
await new Promise((resolve) => setTimeout(resolve, 250));
|
|
168
|
+
}
|
|
169
|
+
return !isPidAlive(pid);
|
|
170
|
+
}
|
|
171
|
+
async function stopExistingBridge(lock, requestedAdapter) {
|
|
172
|
+
const { pid, cwd } = lock;
|
|
173
|
+
log(requestedAdapter, `Stopping existing bridge for ${cwd} (pid=${pid})...`);
|
|
174
|
+
try {
|
|
175
|
+
process.kill(pid);
|
|
176
|
+
}
|
|
177
|
+
catch (error) {
|
|
178
|
+
if (isPidAlive(pid)) {
|
|
179
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
180
|
+
throw new Error(`Failed to stop existing bridge pid=${pid}: ${message}`, {
|
|
181
|
+
cause: error,
|
|
182
|
+
});
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
if (!(await waitForProcessExit(pid, 10_000))) {
|
|
186
|
+
throw new Error(`Timed out waiting for existing bridge pid=${pid} to exit.`);
|
|
187
|
+
}
|
|
188
|
+
if (lock.adapter === "codex" || lock.adapter === "claude" || lock.adapter === "opencode") {
|
|
189
|
+
clearLocalCompanionEndpoint(cwd, undefined, { adapter: lock.adapter });
|
|
190
|
+
}
|
|
191
|
+
else {
|
|
192
|
+
clearLocalCompanionEndpoint(cwd);
|
|
193
|
+
}
|
|
194
|
+
log(requestedAdapter, `Cleared stale local companion endpoint for previous workspace ${cwd}.`);
|
|
195
|
+
}
|
|
196
|
+
async function isEndpointReachable(endpoint) {
|
|
197
|
+
await new Promise((resolve) => setTimeout(resolve, 0));
|
|
198
|
+
return await new Promise((resolve) => {
|
|
199
|
+
const port = endpoint.serverPort ?? endpoint.port;
|
|
200
|
+
const socket = net.connect({
|
|
201
|
+
host: "127.0.0.1",
|
|
202
|
+
port,
|
|
203
|
+
});
|
|
204
|
+
let done = false;
|
|
205
|
+
const finish = (result) => {
|
|
206
|
+
if (done) {
|
|
207
|
+
return;
|
|
208
|
+
}
|
|
209
|
+
done = true;
|
|
210
|
+
socket.destroy();
|
|
211
|
+
resolve(result);
|
|
212
|
+
};
|
|
213
|
+
socket.setTimeout(400);
|
|
214
|
+
socket.once("connect", () => finish(true));
|
|
215
|
+
socket.once("timeout", () => finish(false));
|
|
216
|
+
socket.once("error", () => finish(false));
|
|
217
|
+
});
|
|
218
|
+
}
|
|
219
|
+
async function readUsableEndpoint(cwd, adapter) {
|
|
220
|
+
const endpoint = readLocalCompanionEndpoint(cwd, { adapter });
|
|
221
|
+
if (!endpoint || endpoint.kind !== adapter) {
|
|
222
|
+
return { endpoint: null };
|
|
223
|
+
}
|
|
224
|
+
if (await isEndpointReachable(endpoint)) {
|
|
225
|
+
return { endpoint };
|
|
226
|
+
}
|
|
227
|
+
clearLocalCompanionEndpoint(cwd, endpoint.instanceId, { adapter });
|
|
228
|
+
log(adapter, `Removed stale local companion endpoint for ${cwd}.`);
|
|
229
|
+
return { endpoint: null };
|
|
230
|
+
}
|
|
231
|
+
function isCompanionAlive(endpoint) {
|
|
232
|
+
if (!endpoint?.companionPid) {
|
|
233
|
+
return false;
|
|
234
|
+
}
|
|
235
|
+
if (isPidAlive(endpoint.companionPid)) {
|
|
236
|
+
return true;
|
|
237
|
+
}
|
|
238
|
+
clearLocalCompanionOccupancy(endpoint.cwd, endpoint.instanceId, {
|
|
239
|
+
adapter: endpoint.kind,
|
|
240
|
+
});
|
|
241
|
+
return false;
|
|
242
|
+
}
|
|
243
|
+
export function buildBackgroundBridgeArgs(entryPath, options) {
|
|
244
|
+
const lifecycle = "companion_bound";
|
|
245
|
+
const args = ["--no-warnings"];
|
|
246
|
+
if (path.extname(entryPath) === ".ts") {
|
|
247
|
+
args.push("--experimental-strip-types");
|
|
248
|
+
}
|
|
249
|
+
args.push(entryPath, "--adapter", options.adapter, "--cwd", options.cwd, "--lifecycle", lifecycle);
|
|
250
|
+
if (options.profile) {
|
|
251
|
+
args.push("--profile", options.profile);
|
|
252
|
+
}
|
|
253
|
+
return args;
|
|
254
|
+
}
|
|
255
|
+
function startBridgeInBackground(options) {
|
|
256
|
+
const entryPath = path.resolve(MODULE_DIR, "..", "bridge", `wechat-bridge${RUNTIME_ENTRY_EXTENSION}`);
|
|
257
|
+
const args = buildBackgroundBridgeArgs(entryPath, options);
|
|
258
|
+
const child = spawn(process.execPath, args, {
|
|
259
|
+
cwd: options.cwd,
|
|
260
|
+
env: process.env,
|
|
261
|
+
detached: true,
|
|
262
|
+
stdio: "ignore",
|
|
263
|
+
windowsHide: true,
|
|
264
|
+
});
|
|
265
|
+
child.unref();
|
|
266
|
+
}
|
|
267
|
+
async function waitForEndpoint(cwd, adapter, timeoutMs) {
|
|
268
|
+
const deadline = Date.now() + timeoutMs;
|
|
269
|
+
while (Date.now() < deadline) {
|
|
270
|
+
const result = await readUsableEndpoint(cwd, adapter);
|
|
271
|
+
if (result.endpoint) {
|
|
272
|
+
return result.endpoint;
|
|
273
|
+
}
|
|
274
|
+
await new Promise((resolve) => setTimeout(resolve, 250));
|
|
275
|
+
}
|
|
276
|
+
throw new Error(`Timed out waiting for the ${adapter} bridge endpoint for ${cwd}. Check ${BRIDGE_LOG_FILE}.`);
|
|
277
|
+
}
|
|
278
|
+
async function ensureBridgeReady(options) {
|
|
279
|
+
// If the lock is absent or the lock-holding process is dead, do NOT trust a
|
|
280
|
+
// leftover endpoint. The bridge (WeChat transport) may have crashed while
|
|
281
|
+
// the opencode server kept running. Starting only the panel would leave no
|
|
282
|
+
// bridge to poll WeChat messages.
|
|
283
|
+
const lock = readBridgeLockFile();
|
|
284
|
+
const lockProcessAlive = lock ? isPidAlive(lock.pid) : false;
|
|
285
|
+
if (!lock || !lockProcessAlive) {
|
|
286
|
+
if (lock && !lockProcessAlive) {
|
|
287
|
+
log(options.adapter, `Found stale lock for ${options.cwd} (pid=${lock.pid} dead). Clearing.`);
|
|
288
|
+
clearLocalCompanionEndpoint(options.cwd, undefined, {
|
|
289
|
+
adapter: options.adapter,
|
|
290
|
+
});
|
|
291
|
+
}
|
|
292
|
+
log(options.adapter, `Starting bridge in background for ${options.cwd}...`);
|
|
293
|
+
startBridgeInBackground(options);
|
|
294
|
+
await waitForEndpoint(options.cwd, options.adapter, options.timeoutMs);
|
|
295
|
+
return { shouldOpenVisibleClient: true };
|
|
296
|
+
}
|
|
297
|
+
// Lock is held by a live process; decide whether to reuse, switch, or replace it.
|
|
298
|
+
const endpointResult = await readUsableEndpoint(options.cwd, options.adapter);
|
|
299
|
+
const decision = decideLaunchAction({
|
|
300
|
+
requestedAdapter: options.adapter,
|
|
301
|
+
requestedCwd: options.cwd,
|
|
302
|
+
runningLock: lock,
|
|
303
|
+
lockShouldAutoReclaim: shouldAutoReclaimBridgeLock(lock),
|
|
304
|
+
endpoint: endpointResult.endpoint,
|
|
305
|
+
endpointIsReachable: Boolean(endpointResult.endpoint),
|
|
306
|
+
companionIsAlive: isCompanionAlive(endpointResult.endpoint),
|
|
307
|
+
});
|
|
308
|
+
log(options.adapter, decision.message);
|
|
309
|
+
if (decision.kind === "already_active") {
|
|
310
|
+
return { shouldOpenVisibleClient: false };
|
|
311
|
+
}
|
|
312
|
+
if (decision.kind === "open_companion") {
|
|
313
|
+
if (!endpointResult.endpoint) {
|
|
314
|
+
await waitForEndpoint(options.cwd, options.adapter, options.timeoutMs);
|
|
315
|
+
}
|
|
316
|
+
return { shouldOpenVisibleClient: true };
|
|
317
|
+
}
|
|
318
|
+
if (decision.kind === "switch_workspace") {
|
|
319
|
+
try {
|
|
320
|
+
await stopExistingBridge(lock, options.adapter);
|
|
321
|
+
}
|
|
322
|
+
catch (error) {
|
|
323
|
+
log(options.adapter, decision.failureMessage);
|
|
324
|
+
throw error;
|
|
325
|
+
}
|
|
326
|
+
}
|
|
327
|
+
else {
|
|
328
|
+
await stopExistingBridge(lock, options.adapter);
|
|
329
|
+
}
|
|
330
|
+
log(options.adapter, `Starting replacement bridge in background for ${options.cwd}...`);
|
|
331
|
+
startBridgeInBackground(options);
|
|
332
|
+
await waitForEndpoint(options.cwd, options.adapter, options.timeoutMs);
|
|
333
|
+
return { shouldOpenVisibleClient: true };
|
|
334
|
+
}
|
|
335
|
+
export async function tryDelegateToDaemon(options, deps = {}) {
|
|
336
|
+
const readEndpoint = deps.readEndpoint ?? readDaemonEndpoint;
|
|
337
|
+
const isEndpointAlive = deps.isEndpointAlive ?? isDaemonEndpointAlive;
|
|
338
|
+
const sendRequest = deps.sendRequest ?? sendDaemonRequest;
|
|
339
|
+
const clearEndpoint = deps.clearEndpoint ?? clearDaemonEndpoint;
|
|
340
|
+
const endpoint = readEndpoint();
|
|
341
|
+
if (!endpoint) {
|
|
342
|
+
return false;
|
|
343
|
+
}
|
|
344
|
+
if (!(await isEndpointAlive(endpoint))) {
|
|
345
|
+
clearEndpoint(endpoint.pid);
|
|
346
|
+
return false;
|
|
347
|
+
}
|
|
348
|
+
if (!isSameWorkspaceCwd(endpoint.cwd, options.cwd)) {
|
|
349
|
+
throw new Error(`wechat-daemon is running for ${endpoint.cwd}. This launcher requested ${options.cwd}; v1 daemon switching is limited to its startup cwd.`);
|
|
350
|
+
}
|
|
351
|
+
const response = await sendRequest(endpoint, {
|
|
352
|
+
command: "ensure_slot",
|
|
353
|
+
adapter: options.adapter,
|
|
354
|
+
cwd: options.cwd,
|
|
355
|
+
profile: options.profile,
|
|
356
|
+
cliArgs: options.cliArgs,
|
|
357
|
+
openVisible: true,
|
|
358
|
+
});
|
|
359
|
+
if (!response.ok) {
|
|
360
|
+
throw new Error(response.error);
|
|
361
|
+
}
|
|
362
|
+
log(options.adapter, `Delegated to running wechat-daemon for ${options.cwd}.`);
|
|
363
|
+
return true;
|
|
364
|
+
}
|
|
365
|
+
export async function runVisibleClient(options, runners = {}) {
|
|
366
|
+
// Keep the foreground client in-process so Windows does not briefly flash an
|
|
367
|
+
// extra bootstrap console before the real companion UI appears.
|
|
368
|
+
if (options.adapter === "codex") {
|
|
369
|
+
return await (runners.codexRemoteClient ?? runCodexRemoteClient)({
|
|
370
|
+
cwd: options.cwd,
|
|
371
|
+
cliArgs: options.cliArgs,
|
|
372
|
+
});
|
|
373
|
+
}
|
|
374
|
+
return await (runners.localCompanion ?? runLocalCompanion)({
|
|
375
|
+
adapter: options.adapter,
|
|
376
|
+
cwd: options.cwd,
|
|
377
|
+
cliArgs: options.cliArgs,
|
|
378
|
+
});
|
|
379
|
+
}
|
|
380
|
+
export async function ensureCompanionStartWechatCredentials(adapter, ensureCredentials = ensureWechatCredentials) {
|
|
381
|
+
await ensureCredentials({
|
|
382
|
+
requireUserId: true,
|
|
383
|
+
validateExisting: true,
|
|
384
|
+
log: (message) => log(adapter, message),
|
|
385
|
+
});
|
|
386
|
+
}
|
|
387
|
+
export async function runLocalCompanionStart(argv = process.argv.slice(2)) {
|
|
388
|
+
const options = parseCliArgs(argv);
|
|
389
|
+
await ensureCompanionStartWechatCredentials(options.adapter);
|
|
390
|
+
if (await tryDelegateToDaemon(options)) {
|
|
391
|
+
return 0;
|
|
392
|
+
}
|
|
393
|
+
const ready = await ensureBridgeReady(options);
|
|
394
|
+
if (!ready.shouldOpenVisibleClient) {
|
|
395
|
+
return 0;
|
|
396
|
+
}
|
|
397
|
+
return await runVisibleClient(options);
|
|
398
|
+
}
|
|
399
|
+
export async function main(argv = process.argv.slice(2)) {
|
|
400
|
+
try {
|
|
401
|
+
const exitCode = await runLocalCompanionStart(argv);
|
|
402
|
+
process.exit(exitCode);
|
|
403
|
+
}
|
|
404
|
+
catch (error) {
|
|
405
|
+
const adapter = (() => {
|
|
406
|
+
try {
|
|
407
|
+
return parseCliArgs(argv).adapter;
|
|
408
|
+
}
|
|
409
|
+
catch {
|
|
410
|
+
return DEFAULT_ADAPTER;
|
|
411
|
+
}
|
|
412
|
+
})();
|
|
413
|
+
log(adapter, error instanceof Error ? error.message : String(error));
|
|
414
|
+
process.exit(1);
|
|
415
|
+
}
|
|
416
|
+
}
|
|
417
|
+
const isDirectRun = Boolean(import.meta.main);
|
|
418
|
+
if (isDirectRun) {
|
|
419
|
+
void main();
|
|
420
|
+
}
|