docdex 0.2.21 → 0.2.23

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.
@@ -3,6 +3,7 @@
3
3
 
4
4
  const fs = require("node:fs");
5
5
  const net = require("node:net");
6
+ const http = require("node:http");
6
7
  const os = require("node:os");
7
8
  const path = require("node:path");
8
9
  const readline = require("node:readline");
@@ -12,8 +13,11 @@ const { spawn, spawnSync } = require("node:child_process");
12
13
  const { detectPlatformKey, UnsupportedPlatformError } = require("./platform");
13
14
 
14
15
  const DEFAULT_HOST = "127.0.0.1";
15
- const DEFAULT_PORT_PRIMARY = 3000;
16
- const DEFAULT_PORT_FALLBACK = 3210;
16
+ const DEFAULT_DAEMON_PORT = 28491;
17
+ const DAEMON_HEALTH_TIMEOUT_MS = 8000;
18
+ const DAEMON_HEALTH_REQUEST_TIMEOUT_MS = 1000;
19
+ const DAEMON_HEALTH_POLL_INTERVAL_MS = 200;
20
+ const DAEMON_HEALTH_PATH = "/healthz";
17
21
  const STARTUP_FAILURE_MARKER = "startup_registration_failed.json";
18
22
  const DEFAULT_OLLAMA_MODEL = "nomic-embed-text";
19
23
  const DEFAULT_OLLAMA_CHAT_MODEL = "phi3.5:3.8b";
@@ -39,6 +43,17 @@ function setupPendingPath() {
39
43
  return path.join(stateDir(), SETUP_PENDING_MARKER);
40
44
  }
41
45
 
46
+ function daemonLockPaths() {
47
+ const root = path.join(os.homedir(), ".docdex");
48
+ const paths = [];
49
+ if (process.env.DOCDEX_DAEMON_LOCK_PATH) {
50
+ paths.push(process.env.DOCDEX_DAEMON_LOCK_PATH);
51
+ }
52
+ paths.push(path.join(root, "locks", "daemon.lock"));
53
+ paths.push(path.join(root, "daemon.lock"));
54
+ return Array.from(new Set(paths.filter(Boolean)));
55
+ }
56
+
42
57
  function configUrlForPort(port) {
43
58
  return `http://localhost:${port}/sse`;
44
59
  }
@@ -59,22 +74,51 @@ function isPortAvailable(port, host) {
59
74
  });
60
75
  }
61
76
 
62
- async function pickAvailablePort(host, preferred) {
63
- for (const port of preferred) {
64
- if (await isPortAvailable(port, host)) return port;
65
- }
66
- return new Promise((resolve, reject) => {
67
- const server = net.createServer();
68
- server.unref();
69
- server.once("error", reject);
70
- server.once("listening", () => {
71
- const addr = server.address();
72
- server.close(() => resolve(addr.port));
77
+ function sleep(ms) {
78
+ return new Promise((resolve) => setTimeout(resolve, ms));
79
+ }
80
+
81
+ function checkDaemonHealth({ host, port, timeoutMs = DAEMON_HEALTH_REQUEST_TIMEOUT_MS }) {
82
+ return new Promise((resolve) => {
83
+ const req = http.request(
84
+ {
85
+ host,
86
+ port,
87
+ path: DAEMON_HEALTH_PATH,
88
+ method: "GET",
89
+ timeout: timeoutMs
90
+ },
91
+ (res) => {
92
+ let body = "";
93
+ res.setEncoding("utf8");
94
+ res.on("data", (chunk) => {
95
+ body += chunk;
96
+ });
97
+ res.on("end", () => {
98
+ resolve(res.statusCode === 200 && body.trim() === "ok");
99
+ });
100
+ }
101
+ );
102
+ req.on("timeout", () => {
103
+ req.destroy();
104
+ resolve(false);
73
105
  });
74
- server.listen(0, host);
106
+ req.on("error", () => resolve(false));
107
+ req.end();
75
108
  });
76
109
  }
77
110
 
111
+ async function waitForDaemonHealthy({ host, port, timeoutMs = DAEMON_HEALTH_TIMEOUT_MS }) {
112
+ const deadline = Date.now() + timeoutMs;
113
+ while (Date.now() < deadline) {
114
+ if (await checkDaemonHealth({ host, port })) {
115
+ return true;
116
+ }
117
+ await sleep(DAEMON_HEALTH_POLL_INTERVAL_MS);
118
+ }
119
+ return false;
120
+ }
121
+
78
122
  function parseServerBind(contents) {
79
123
  let inServer = false;
80
124
  const lines = contents.split(/\r?\n/);
@@ -91,6 +135,125 @@ function parseServerBind(contents) {
91
135
  return null;
92
136
  }
93
137
 
138
+ function stopDaemonService({ logger } = {}) {
139
+ if (process.platform === "darwin") {
140
+ const uid = typeof process.getuid === "function" ? process.getuid() : null;
141
+ const domain = uid != null ? `gui/${uid}` : null;
142
+ const plistPath = path.join(os.homedir(), "Library", "LaunchAgents", "com.docdex.daemon.plist");
143
+ const bootoutByLabel = domain
144
+ ? spawnSync("launchctl", ["bootout", domain, "com.docdex.daemon"])
145
+ : spawnSync("launchctl", ["bootout", "com.docdex.daemon"]);
146
+ const bootoutByPath = domain
147
+ ? spawnSync("launchctl", ["bootout", domain, plistPath])
148
+ : spawnSync("launchctl", ["bootout", plistPath]);
149
+ const fallback = spawnSync("launchctl", ["unload", "-w", plistPath]);
150
+ spawnSync("launchctl", ["remove", "com.docdex.daemon"]);
151
+ if (bootoutByLabel.status === 0 || bootoutByPath.status === 0 || fallback.status === 0) {
152
+ return true;
153
+ }
154
+ logger?.warn?.(
155
+ `[docdex] launchctl stop failed: ${bootoutByLabel.stderr || bootoutByPath.stderr || fallback.stderr || "unknown error"}`
156
+ );
157
+ return false;
158
+ }
159
+ if (process.platform === "linux") {
160
+ const stop = spawnSync("systemctl", ["--user", "stop", "docdexd.service"]);
161
+ if (stop.status === 0) return true;
162
+ logger?.warn?.(`[docdex] systemd stop failed: ${stop.stderr || "unknown error"}`);
163
+ return false;
164
+ }
165
+ if (process.platform === "win32") {
166
+ const stop = spawnSync("schtasks", ["/End", "/TN", "Docdex Daemon"]);
167
+ if (stop.status === 0) return true;
168
+ logger?.warn?.(`[docdex] schtasks stop failed: ${stop.stderr || "unknown error"}`);
169
+ return false;
170
+ }
171
+ return false;
172
+ }
173
+
174
+ function startDaemonService({ logger } = {}) {
175
+ if (process.platform === "darwin") {
176
+ const uid = typeof process.getuid === "function" ? process.getuid() : null;
177
+ const domain = uid != null ? `gui/${uid}` : null;
178
+ const kickstart = domain
179
+ ? spawnSync("launchctl", ["kickstart", "-k", `${domain}/com.docdex.daemon`])
180
+ : spawnSync("launchctl", ["kickstart", "-k", "com.docdex.daemon"]);
181
+ if (kickstart.status === 0) return true;
182
+ const start = spawnSync("launchctl", ["start", "com.docdex.daemon"]);
183
+ if (start.status === 0) return true;
184
+ logger?.warn?.(`[docdex] launchctl start failed: ${kickstart.stderr || start.stderr || "unknown error"}`);
185
+ return false;
186
+ }
187
+ if (process.platform === "linux") {
188
+ const start = spawnSync("systemctl", ["--user", "restart", "docdexd.service"]);
189
+ if (start.status === 0) return true;
190
+ logger?.warn?.(`[docdex] systemd start failed: ${start.stderr || "unknown error"}`);
191
+ return false;
192
+ }
193
+ if (process.platform === "win32") {
194
+ const run = spawnSync("schtasks", ["/Run", "/TN", "Docdex Daemon"]);
195
+ if (run.status === 0) return true;
196
+ logger?.warn?.(`[docdex] schtasks run failed: ${run.stderr || "unknown error"}`);
197
+ return false;
198
+ }
199
+ return false;
200
+ }
201
+
202
+ function stopDaemonByName({ logger } = {}) {
203
+ if (process.platform === "win32") {
204
+ const result = spawnSync("taskkill", ["/IM", "docdexd.exe", "/T", "/F"]);
205
+ if (result?.error?.code === "ENOENT") return false;
206
+ return true;
207
+ }
208
+ const result = spawnSync("pkill", ["-TERM", "-x", "docdexd"]);
209
+ if (result?.error?.code === "ENOENT") {
210
+ spawnSync("killall", ["-TERM", "docdexd"]);
211
+ return false;
212
+ }
213
+ spawnSync("pkill", ["-TERM", "-f", "docdexd"]);
214
+ return true;
215
+ }
216
+
217
+ function clearDaemonLocks() {
218
+ let removed = false;
219
+ for (const lockPath of daemonLockPaths()) {
220
+ if (!lockPath || !fs.existsSync(lockPath)) continue;
221
+ try {
222
+ const resolved = path.resolve(lockPath);
223
+ const home = path.resolve(os.homedir());
224
+ if (!resolved.startsWith(home + path.sep)) continue;
225
+ fs.unlinkSync(lockPath);
226
+ removed = true;
227
+ } catch {
228
+ continue;
229
+ }
230
+ }
231
+ return removed;
232
+ }
233
+
234
+ function stopDaemonFromLock({ logger } = {}) {
235
+ let stopped = false;
236
+ for (const lockPath of daemonLockPaths()) {
237
+ if (!lockPath || !fs.existsSync(lockPath)) continue;
238
+ try {
239
+ const raw = fs.readFileSync(lockPath, "utf8");
240
+ if (!raw.trim()) continue;
241
+ const payload = JSON.parse(raw);
242
+ const pid = Number(payload?.pid);
243
+ if (!Number.isFinite(pid) || pid <= 0) continue;
244
+ try {
245
+ process.kill(pid, "SIGTERM");
246
+ stopped = true;
247
+ } catch (err) {
248
+ logger?.warn?.(`[docdex] failed to stop daemon pid ${pid}: ${err?.message || err}`);
249
+ }
250
+ } catch {
251
+ continue;
252
+ }
253
+ }
254
+ return stopped;
255
+ }
256
+
94
257
  function upsertServerConfig(contents, httpBindAddr) {
95
258
  const lines = contents.split(/\r?\n/);
96
259
  const output = [];
@@ -826,6 +989,41 @@ function resolveBinaryPath({ binaryPath } = {}) {
826
989
  return null;
827
990
  }
828
991
 
992
+ function isPathWithin(parent, candidate) {
993
+ const base = path.resolve(parent);
994
+ const target = path.resolve(candidate);
995
+ if (base === target) return true;
996
+ return target.startsWith(base + path.sep);
997
+ }
998
+
999
+ function isMacProtectedPath(candidate) {
1000
+ if (process.platform !== "darwin") return false;
1001
+ const home = os.homedir();
1002
+ return ["Desktop", "Documents", "Downloads"].some((dir) => isPathWithin(path.join(home, dir), candidate));
1003
+ }
1004
+
1005
+ function ensureStartupBinary(binaryPath, { logger } = {}) {
1006
+ if (!binaryPath) return null;
1007
+ if (!isMacProtectedPath(binaryPath) && !isTempPath(binaryPath)) return binaryPath;
1008
+ const binDir = path.join(os.homedir(), ".docdex", "bin");
1009
+ const target = path.join(binDir, path.basename(binaryPath));
1010
+ if (fs.existsSync(target)) return target;
1011
+ try {
1012
+ fs.mkdirSync(binDir, { recursive: true });
1013
+ fs.copyFileSync(binaryPath, target);
1014
+ fs.chmodSync(target, 0o755);
1015
+ return target;
1016
+ } catch (err) {
1017
+ logger?.warn?.(`[docdex] failed to stage daemon binary for startup: ${err?.message || err}`);
1018
+ return binaryPath;
1019
+ }
1020
+ }
1021
+
1022
+ function resolveStartupBinaryPaths({ binaryPath, logger } = {}) {
1023
+ const resolvedBinary = ensureStartupBinary(binaryPath, { logger });
1024
+ return { binaryPath: resolvedBinary };
1025
+ }
1026
+
829
1027
  function applyAgentInstructions({ logger } = {}) {
830
1028
  const instructions = buildDocdexInstructionBlock(loadAgentInstructions());
831
1029
  if (!normalizeInstructionText(instructions)) return { ok: false, reason: "missing_instructions" };
@@ -886,14 +1084,6 @@ function applyAgentInstructions({ logger } = {}) {
886
1084
  return { ok: true, updated };
887
1085
  }
888
1086
 
889
- function resolveMcpBinaryPath(binaryPath) {
890
- if (!binaryPath) return null;
891
- const dir = path.dirname(binaryPath);
892
- const name = process.platform === "win32" ? "docdex-mcp-server.exe" : "docdex-mcp-server";
893
- const candidate = path.join(dir, name);
894
- return fs.existsSync(candidate) ? candidate : null;
895
- }
896
-
897
1087
  function ensureDaemonRoot() {
898
1088
  const root = daemonRootPath();
899
1089
  fs.mkdirSync(root, { recursive: true });
@@ -1464,30 +1654,36 @@ async function maybePromptOllamaModel({
1464
1654
  return { status: "skipped", reason: "invalid_selection" };
1465
1655
  }
1466
1656
 
1467
- function resolvePlaywrightFetcherPath() {
1468
- const candidate = path.join(__dirname, "playwright_fetch.js");
1469
- return fs.existsSync(candidate) ? candidate : null;
1657
+ function envBool(value) {
1658
+ if (!value) return false;
1659
+ const normalized = String(value).trim().toLowerCase();
1660
+ return ["1", "true", "t", "yes", "y", "on"].includes(normalized);
1661
+ }
1662
+
1663
+ function isTempPath(value, osModule = os) {
1664
+ if (!value) return false;
1665
+ const tmpdir = osModule.tmpdir();
1666
+ if (!tmpdir) return false;
1667
+ const resolvedValue = path.resolve(value);
1668
+ const resolvedTmp = path.resolve(tmpdir);
1669
+ return resolvedValue === resolvedTmp || resolvedValue.startsWith(resolvedTmp + path.sep);
1470
1670
  }
1471
1671
 
1472
- function buildDaemonEnvPairs({ mcpBinaryPath } = {}) {
1473
- const pairs = [["DOCDEX_BROWSER_AUTO_INSTALL", "0"]];
1474
- if (mcpBinaryPath) pairs.push(["DOCDEX_MCP_SERVER_BIN", mcpBinaryPath]);
1475
- const fetcher = resolvePlaywrightFetcherPath();
1476
- if (fetcher) pairs.push(["DOCDEX_PLAYWRIGHT_FETCHER", fetcher]);
1477
- return pairs;
1672
+ function buildDaemonEnvPairs() {
1673
+ return [["DOCDEX_BROWSER_AUTO_INSTALL", "0"]];
1478
1674
  }
1479
1675
 
1480
- function buildDaemonEnv({ mcpBinaryPath } = {}) {
1481
- return Object.fromEntries(buildDaemonEnvPairs({ mcpBinaryPath }));
1676
+ function buildDaemonEnv() {
1677
+ return Object.fromEntries(buildDaemonEnvPairs());
1482
1678
  }
1483
1679
 
1484
- function registerStartup({ binaryPath, mcpBinaryPath, port, repoRoot, logger }) {
1680
+ function registerStartup({ binaryPath, port, repoRoot, logger }) {
1485
1681
  if (!binaryPath) return { ok: false, reason: "missing_binary" };
1486
- const envPairs = buildDaemonEnvPairs({ mcpBinaryPath });
1682
+ stopDaemonService({ logger });
1683
+ const envPairs = buildDaemonEnvPairs();
1684
+ const workingDir = repoRoot ? path.resolve(repoRoot) : null;
1487
1685
  const args = [
1488
1686
  "daemon",
1489
- "--repo",
1490
- repoRoot,
1491
1687
  "--host",
1492
1688
  DEFAULT_HOST,
1493
1689
  "--port",
@@ -1520,6 +1716,9 @@ function registerStartup({ binaryPath, mcpBinaryPath, port, repoRoot, logger })
1520
1716
  ` <array>\n` +
1521
1717
  programArgs.map((arg) => ` <string>${arg}</string>\n`).join("") +
1522
1718
  ` </array>\n` +
1719
+ (workingDir
1720
+ ? ` <key>WorkingDirectory</key>\n` + ` <string>${workingDir}</string>\n`
1721
+ : "") +
1523
1722
  ` <key>RunAtLoad</key>\n` +
1524
1723
  ` <true/>\n` +
1525
1724
  ` <key>KeepAlive</key>\n` +
@@ -1555,6 +1754,7 @@ function registerStartup({ binaryPath, mcpBinaryPath, port, repoRoot, logger })
1555
1754
  "",
1556
1755
  "[Service]",
1557
1756
  `ExecStart=${binaryPath} ${args.join(" ")}`,
1757
+ workingDir ? `WorkingDirectory=${workingDir}` : "",
1558
1758
  ...envLines,
1559
1759
  "Restart=always",
1560
1760
  "RestartSec=2",
@@ -1575,8 +1775,9 @@ function registerStartup({ binaryPath, mcpBinaryPath, port, repoRoot, logger })
1575
1775
  const taskName = "Docdex Daemon";
1576
1776
  const joinedArgs = args.map((arg) => `"${arg}"`).join(" ");
1577
1777
  const envParts = envPairs.map(([key, value]) => `set "${key}=${value}"`);
1778
+ const cdCommand = workingDir ? `cd /d "${workingDir}"` : null;
1578
1779
  const taskArgs =
1579
- `"cmd.exe" /c "${envParts.join(" && ")} && \"${binaryPath}\" ${joinedArgs}"`;
1780
+ `"cmd.exe" /c "${envParts.join(" && ")}${cdCommand ? ` && ${cdCommand}` : ""} && \"${binaryPath}\" ${joinedArgs}"`;
1580
1781
  const create = spawnSync("schtasks", [
1581
1782
  "/Create",
1582
1783
  "/F",
@@ -1600,34 +1801,28 @@ function registerStartup({ binaryPath, mcpBinaryPath, port, repoRoot, logger })
1600
1801
  return { ok: false, reason: "unsupported_platform" };
1601
1802
  }
1602
1803
 
1603
- function startDaemonNow({ binaryPath, mcpBinaryPath, port, repoRoot }) {
1604
- if (!binaryPath) return false;
1605
- const extraEnv = buildDaemonEnv({ mcpBinaryPath });
1606
- const child = spawn(
1804
+ async function startDaemonWithHealthCheck({ binaryPath, port, host, logger }) {
1805
+ const startup = registerStartup({
1607
1806
  binaryPath,
1608
- [
1609
- "daemon",
1610
- "--repo",
1611
- repoRoot,
1612
- "--host",
1613
- DEFAULT_HOST,
1614
- "--port",
1615
- String(port),
1616
- "--log",
1617
- "warn",
1618
- "--secure-mode=false"
1619
- ],
1620
- {
1621
- stdio: "ignore",
1622
- detached: true,
1623
- env: {
1624
- ...process.env,
1625
- ...extraEnv
1626
- }
1627
- }
1628
- );
1629
- child.unref();
1630
- return true;
1807
+ port,
1808
+ repoRoot: daemonRootPath(),
1809
+ logger
1810
+ });
1811
+ if (!startup.ok) {
1812
+ logger?.warn?.(`[docdex] daemon service registration failed (${startup.reason || "unknown"}).`);
1813
+ return { ok: false, reason: "startup_failed" };
1814
+ }
1815
+ startDaemonService({ logger });
1816
+ const healthy = await waitForDaemonHealthy({ host, port });
1817
+ if (healthy) {
1818
+ return { ok: true, reason: "healthy" };
1819
+ }
1820
+ logger?.warn?.(`[docdex] daemon failed health check on ${host}:${port}`);
1821
+ stopDaemonService({ logger });
1822
+ stopDaemonFromLock({ logger });
1823
+ stopDaemonByName({ logger });
1824
+ clearDaemonLocks();
1825
+ return { ok: false, reason: "health_failed" };
1631
1826
  }
1632
1827
 
1633
1828
  function recordStartupFailure(details) {
@@ -1758,16 +1953,34 @@ async function runPostInstallSetup({ binaryPath, logger } = {}) {
1758
1953
  if (fs.existsSync(configPath)) {
1759
1954
  existingConfig = fs.readFileSync(configPath, "utf8");
1760
1955
  }
1761
- const configuredBind = existingConfig ? parseServerBind(existingConfig) : null;
1762
- let port;
1763
- if (process.env.DOCDEX_DAEMON_PORT) {
1764
- port = Number(process.env.DOCDEX_DAEMON_PORT);
1765
- } else if (configuredBind) {
1766
- const match = configuredBind.match(/:(\d+)$/);
1767
- port = match ? Number(match[1]) : null;
1956
+ const port = DEFAULT_DAEMON_PORT;
1957
+ const available = await isPortAvailable(port, DEFAULT_HOST);
1958
+ if (!available) {
1959
+ log.error?.(
1960
+ `[docdex] ${DEFAULT_HOST}:${port} is already in use; docdex requires a fixed port. Stop the process using this port and re-run the install.`
1961
+ );
1962
+ throw new Error(`docdex requires ${DEFAULT_HOST}:${port}, but the port is already in use`);
1768
1963
  }
1769
- if (!port || Number.isNaN(port)) {
1770
- port = await pickAvailablePort(DEFAULT_HOST, [DEFAULT_PORT_PRIMARY, DEFAULT_PORT_FALLBACK]);
1964
+
1965
+ const daemonRoot = ensureDaemonRoot();
1966
+ const resolvedBinary = resolveBinaryPath({ binaryPath });
1967
+ const startupBinaries = resolveStartupBinaryPaths({
1968
+ binaryPath: resolvedBinary,
1969
+ logger: log
1970
+ });
1971
+ stopDaemonService({ logger: log });
1972
+ stopDaemonFromLock({ logger: log });
1973
+ stopDaemonByName({ logger: log });
1974
+ clearDaemonLocks();
1975
+ const result = await startDaemonWithHealthCheck({
1976
+ binaryPath: startupBinaries.binaryPath,
1977
+ port,
1978
+ host: DEFAULT_HOST,
1979
+ logger: log
1980
+ });
1981
+ if (!result.ok) {
1982
+ log.warn?.(`[docdex] daemon failed to start on ${DEFAULT_HOST}:${port}.`);
1983
+ throw new Error("docdex daemon failed to start");
1771
1984
  }
1772
1985
 
1773
1986
  const httpBindAddr = `${DEFAULT_HOST}:${port}`;
@@ -1800,29 +2013,8 @@ async function runPostInstallSetup({ binaryPath, logger } = {}) {
1800
2013
  }
1801
2014
  upsertCodexConfig(paths.codex, codexUrl);
1802
2015
  applyAgentInstructions({ logger: log });
1803
-
1804
- const daemonRoot = ensureDaemonRoot();
1805
- const resolvedBinary = resolveBinaryPath({ binaryPath });
1806
- const resolvedMcpBinary = resolveMcpBinaryPath(resolvedBinary);
1807
- const startup = registerStartup({
1808
- binaryPath: resolvedBinary,
1809
- mcpBinaryPath: resolvedMcpBinary,
1810
- port,
1811
- repoRoot: daemonRoot,
1812
- logger: log
1813
- });
1814
- if (!startup.ok) {
1815
- if (!startupFailureReported()) {
1816
- log.warn?.("[docdex] startup registration failed; run the daemon manually:");
1817
- log.warn?.(`[docdex] ${resolvedBinary || "docdexd"} daemon --repo ${daemonRoot} --host ${DEFAULT_HOST} --port ${port}`);
1818
- recordStartupFailure({ reason: startup.reason, port, repoRoot: daemonRoot });
1819
- }
1820
- } else {
1821
- clearStartupFailure();
1822
- }
1823
-
1824
- startDaemonNow({ binaryPath: resolvedBinary, mcpBinaryPath: resolvedMcpBinary, port, repoRoot: daemonRoot });
1825
- const setupLaunch = launchSetupWizard({ binaryPath: resolvedBinary, logger: log });
2016
+ clearStartupFailure();
2017
+ const setupLaunch = launchSetupWizard({ binaryPath: startupBinaries.binaryPath, logger: log });
1826
2018
  if (!setupLaunch.ok && setupLaunch.reason !== "skipped") {
1827
2019
  log.warn?.("[docdex] setup wizard did not launch. Run `docdex setup`.");
1828
2020
  recordSetupPending({ reason: setupLaunch.reason, port, repoRoot: daemonRoot });
@@ -1837,7 +2029,6 @@ module.exports = {
1837
2029
  upsertMcpServerJson,
1838
2030
  upsertZedConfig,
1839
2031
  upsertCodexConfig,
1840
- pickAvailablePort,
1841
2032
  configUrlForPort,
1842
2033
  configStreamableUrlForPort,
1843
2034
  parseEnvBool,