opencode-copilot-account-switcher 0.14.14 → 0.14.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.
@@ -1,4 +1,4 @@
1
- import net from "node:net";
1
+ import { createBrokerSocket } from "./broker-endpoint.js";
2
2
  import { parseEnvelopeLine, serializeEnvelope, } from "./protocol.js";
3
3
  function isNonEmptyString(value) {
4
4
  return typeof value === "string" && value.trim().length > 0;
@@ -23,7 +23,7 @@ export async function connect(endpoint, options = {}) {
23
23
  if (options.bridge && options.onCollectStatus) {
24
24
  throw new Error("broker client options are ambiguous: provide either bridge or onCollectStatus");
25
25
  }
26
- const socket = net.createConnection(endpoint);
26
+ const socket = createBrokerSocket(endpoint);
27
27
  let sequence = 0;
28
28
  let pendingResolve = null;
29
29
  let pendingReject = null;
@@ -0,0 +1,21 @@
1
+ import net from "node:net";
2
+ type BrokerEndpointOptions = {
3
+ platform?: NodeJS.Platform;
4
+ stateRoot?: string;
5
+ now?: () => number;
6
+ random?: () => number;
7
+ };
8
+ type ParsedBrokerEndpoint = {
9
+ kind: "tcp";
10
+ host: string;
11
+ port: number;
12
+ } | {
13
+ kind: "path";
14
+ path: string;
15
+ };
16
+ export declare function isTcpBrokerEndpoint(endpoint: string): boolean;
17
+ export declare function createDefaultBrokerEndpoint(options?: BrokerEndpointOptions): string;
18
+ export declare function parseBrokerEndpoint(endpoint: string): ParsedBrokerEndpoint;
19
+ export declare function createBrokerSocket(endpoint: string): net.Socket;
20
+ export declare function listenOnBrokerEndpoint(server: net.Server, endpoint: string): Promise<string>;
21
+ export {};
@@ -0,0 +1,78 @@
1
+ import net from "node:net";
2
+ import path from "node:path";
3
+ function isNonEmptyString(value) {
4
+ return typeof value === "string" && value.trim().length > 0;
5
+ }
6
+ export function isTcpBrokerEndpoint(endpoint) {
7
+ return endpoint.startsWith("tcp://");
8
+ }
9
+ export function createDefaultBrokerEndpoint(options = {}) {
10
+ const platform = options.platform ?? process.platform;
11
+ const stateRoot = options.stateRoot ?? ".";
12
+ const now = options.now ?? Date.now;
13
+ const random = options.random ?? Math.random;
14
+ const suffix = `${now()}-${random().toString(16).slice(2)}`;
15
+ if (platform === "win32") {
16
+ return "tcp://127.0.0.1:0";
17
+ }
18
+ return path.join(stateRoot, `broker-${suffix}.sock`);
19
+ }
20
+ export function parseBrokerEndpoint(endpoint) {
21
+ if (!isTcpBrokerEndpoint(endpoint)) {
22
+ return {
23
+ kind: "path",
24
+ path: endpoint,
25
+ };
26
+ }
27
+ const parsed = new URL(endpoint);
28
+ if (parsed.protocol !== "tcp:") {
29
+ throw new Error(`unsupported broker endpoint protocol: ${parsed.protocol}`);
30
+ }
31
+ if (!isNonEmptyString(parsed.hostname)) {
32
+ throw new Error("tcp broker endpoint host is required");
33
+ }
34
+ const port = Number(parsed.port);
35
+ if (!Number.isInteger(port) || port < 0 || port > 65535) {
36
+ throw new Error("tcp broker endpoint port is invalid");
37
+ }
38
+ return {
39
+ kind: "tcp",
40
+ host: parsed.hostname,
41
+ port,
42
+ };
43
+ }
44
+ export function createBrokerSocket(endpoint) {
45
+ const parsed = parseBrokerEndpoint(endpoint);
46
+ if (parsed.kind === "tcp") {
47
+ return net.createConnection({
48
+ host: parsed.host,
49
+ port: parsed.port,
50
+ });
51
+ }
52
+ return net.createConnection(parsed.path);
53
+ }
54
+ export async function listenOnBrokerEndpoint(server, endpoint) {
55
+ const parsed = parseBrokerEndpoint(endpoint);
56
+ await new Promise((resolve, reject) => {
57
+ server.once("error", reject);
58
+ if (parsed.kind === "tcp") {
59
+ server.listen({ host: parsed.host, port: parsed.port }, () => {
60
+ server.off("error", reject);
61
+ resolve();
62
+ });
63
+ return;
64
+ }
65
+ server.listen(parsed.path, () => {
66
+ server.off("error", reject);
67
+ resolve();
68
+ });
69
+ });
70
+ if (parsed.kind === "path") {
71
+ return parsed.path;
72
+ }
73
+ const address = server.address();
74
+ if (!address || typeof address === "string") {
75
+ throw new Error("tcp broker endpoint failed to resolve bound address");
76
+ }
77
+ return `tcp://${address.address}:${address.port}`;
78
+ }
@@ -1,3 +1,4 @@
1
+ import { createDefaultBrokerEndpoint } from "./broker-endpoint.js";
1
2
  type BrokerMetadata = {
2
3
  pid: number;
3
4
  endpoint: string;
@@ -24,5 +25,5 @@ type LaunchOptions = {
24
25
  pingImpl?: (endpoint: string) => Promise<boolean>;
25
26
  onLockAcquired?: (lock: LaunchLockContent) => void;
26
27
  };
28
+ export { createDefaultBrokerEndpoint };
27
29
  export declare function connectOrSpawnBroker(options?: LaunchOptions): Promise<BrokerMetadata>;
28
- export {};
@@ -1,9 +1,9 @@
1
- import net from "node:net";
2
1
  import path from "node:path";
3
2
  import { randomUUID } from "node:crypto";
4
3
  import { mkdir, open, readFile, rm } from "node:fs/promises";
5
4
  import { spawn } from "node:child_process";
6
5
  import { fileURLToPath } from "node:url";
6
+ import { createBrokerSocket, createDefaultBrokerEndpoint } from "./broker-endpoint.js";
7
7
  import { wechatStateRoot } from "./state-paths.js";
8
8
  import { parseEnvelopeLine, serializeEnvelope } from "./protocol.js";
9
9
  const DEFAULT_BACKOFF_MS = 250;
@@ -17,6 +17,7 @@ function isFiniteNumber(value) {
17
17
  function delay(ms) {
18
18
  return new Promise((resolve) => setTimeout(resolve, ms));
19
19
  }
20
+ export { createDefaultBrokerEndpoint };
20
21
  async function readCurrentPackageVersion() {
21
22
  try {
22
23
  const packageJsonPath = new URL("../../package.json", import.meta.url);
@@ -57,7 +58,7 @@ async function readBrokerMetadata(filePath) {
57
58
  }
58
59
  async function defaultPingImpl(endpoint) {
59
60
  return new Promise((resolve) => {
60
- const socket = net.createConnection(endpoint);
61
+ const socket = createBrokerSocket(endpoint);
61
62
  let buffer = "";
62
63
  const timer = setTimeout(() => {
63
64
  socket.destroy();
@@ -162,13 +163,7 @@ export async function connectOrSpawnBroker(options = {}) {
162
163
  const expectedVersion = options.expectedVersion ?? await readCurrentPackageVersion();
163
164
  const pingImpl = options.pingImpl ?? defaultPingImpl;
164
165
  const spawnImpl = options.spawnImpl ?? defaultSpawnImpl;
165
- const endpointFactory = options.endpointFactory ?? (() => {
166
- const suffix = `${Date.now()}-${Math.random().toString(16).slice(2)}`;
167
- if (process.platform === "win32") {
168
- return `\\\\.\\pipe\\wechat-broker-${process.pid}-${suffix}`;
169
- }
170
- return path.join(stateRoot, `broker-${suffix}.sock`);
171
- });
166
+ const endpointFactory = options.endpointFactory ?? (() => createDefaultBrokerEndpoint({ stateRoot }));
172
167
  await mkdir(stateRoot, { recursive: true, mode: 0o700 });
173
168
  for (let attempt = 0; attempt < maxAttempts; attempt += 1) {
174
169
  const running = await isBrokerAlive(brokerJsonFile, pingImpl, expectedVersion);
@@ -1,6 +1,7 @@
1
1
  import net from "node:net";
2
2
  import path from "node:path";
3
3
  import { chmod, mkdir, rm, stat, writeFile } from "node:fs/promises";
4
+ import { createBrokerSocket, isTcpBrokerEndpoint, listenOnBrokerEndpoint } from "./broker-endpoint.js";
4
5
  import { registerConnection, revokeSessionToken, validateSessionToken } from "./ipc-auth.js";
5
6
  import { createErrorEnvelope, parseEnvelopeLine, serializeEnvelope, } from "./protocol.js";
6
7
  import { WECHAT_DIR_MODE, WECHAT_FILE_MODE, instanceStatePath, instancesDir } from "./state-paths.js";
@@ -378,7 +379,7 @@ async function handleMessage(envelope, socket) {
378
379
  writeError(socket, "notImplemented", `${envelope.type} is not implemented`, requestId);
379
380
  }
380
381
  async function tightenEndpointPermission(endpoint) {
381
- if (process.platform === "win32") {
382
+ if (process.platform === "win32" || isTcpBrokerEndpoint(endpoint)) {
382
383
  return;
383
384
  }
384
385
  await chmod(endpoint, WECHAT_FILE_MODE);
@@ -389,7 +390,7 @@ async function tightenEndpointPermission(endpoint) {
389
390
  }
390
391
  async function ensureCurrentUserCanAccess(endpoint) {
391
392
  await new Promise((resolve, reject) => {
392
- const probe = net.createConnection(endpoint);
393
+ const probe = createBrokerSocket(endpoint);
393
394
  probe.once("connect", () => {
394
395
  probe.end();
395
396
  resolve();
@@ -398,7 +399,7 @@ async function ensureCurrentUserCanAccess(endpoint) {
398
399
  });
399
400
  }
400
401
  async function prepareEndpoint(endpoint) {
401
- if (process.platform === "win32") {
402
+ if (process.platform === "win32" || isTcpBrokerEndpoint(endpoint)) {
402
403
  return;
403
404
  }
404
405
  await mkdir(path.dirname(endpoint), { recursive: true, mode: WECHAT_DIR_MODE });
@@ -438,16 +439,10 @@ export async function startBrokerServer(endpoint) {
438
439
  }
439
440
  });
440
441
  });
441
- await new Promise((resolve, reject) => {
442
- server.once("error", reject);
443
- server.listen(endpoint, () => {
444
- server.off("error", reject);
445
- resolve();
446
- });
447
- });
442
+ const boundEndpoint = await listenOnBrokerEndpoint(server, endpoint);
448
443
  try {
449
- await tightenEndpointPermission(endpoint);
450
- await ensureCurrentUserCanAccess(endpoint);
444
+ await tightenEndpointPermission(boundEndpoint);
445
+ await ensureCurrentUserCanAccess(boundEndpoint);
451
446
  }
452
447
  catch (error) {
453
448
  await new Promise((resolve) => {
@@ -528,13 +523,13 @@ export async function startBrokerServer(endpoint) {
528
523
  await new Promise((resolve) => {
529
524
  server.close(() => resolve());
530
525
  });
531
- if (process.platform !== "win32") {
526
+ if (process.platform !== "win32" && !isTcpBrokerEndpoint(endpoint)) {
532
527
  await rm(endpoint, { force: true });
533
528
  }
534
529
  clearRuntimeState();
535
530
  };
536
531
  return {
537
- endpoint,
532
+ endpoint: boundEndpoint,
538
533
  startedAt: Date.now(),
539
534
  collectStatus,
540
535
  handleWechatSlashCommand,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "opencode-copilot-account-switcher",
3
- "version": "0.14.14",
3
+ "version": "0.14.15",
4
4
  "description": "GitHub Copilot account switcher plugin for OpenCode",
5
5
  "main": "./dist/index.js",
6
6
  "types": "./dist/index.d.ts",