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.
- package/CHANGELOG.md +10 -4
- package/README.md +221 -272
- package/assets/agents.md +14 -0
- package/bin/docdex.js +6 -11
- package/lib/install.js +38 -136
- package/lib/postinstall_setup.js +289 -98
- package/lib/uninstall.js +263 -34
- package/package.json +2 -4
- package/bin/docdex-mcp-server.js +0 -66
- package/lib/playwright_fetch.js +0 -174
- package/lib/playwright_install.js +0 -302
package/lib/postinstall_setup.js
CHANGED
|
@@ -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
|
|
16
|
-
const
|
|
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
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
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
|
-
|
|
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
|
|
1468
|
-
|
|
1469
|
-
|
|
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(
|
|
1473
|
-
|
|
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(
|
|
1481
|
-
return Object.fromEntries(buildDaemonEnvPairs(
|
|
1676
|
+
function buildDaemonEnv() {
|
|
1677
|
+
return Object.fromEntries(buildDaemonEnvPairs());
|
|
1482
1678
|
}
|
|
1483
1679
|
|
|
1484
|
-
function registerStartup({ binaryPath,
|
|
1680
|
+
function registerStartup({ binaryPath, port, repoRoot, logger }) {
|
|
1485
1681
|
if (!binaryPath) return { ok: false, reason: "missing_binary" };
|
|
1486
|
-
|
|
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
|
|
1604
|
-
|
|
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
|
-
|
|
1610
|
-
|
|
1611
|
-
|
|
1612
|
-
|
|
1613
|
-
|
|
1614
|
-
|
|
1615
|
-
|
|
1616
|
-
|
|
1617
|
-
|
|
1618
|
-
|
|
1619
|
-
|
|
1620
|
-
|
|
1621
|
-
|
|
1622
|
-
|
|
1623
|
-
|
|
1624
|
-
|
|
1625
|
-
|
|
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
|
|
1762
|
-
|
|
1763
|
-
if (
|
|
1764
|
-
|
|
1765
|
-
|
|
1766
|
-
|
|
1767
|
-
port
|
|
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
|
-
|
|
1770
|
-
|
|
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
|
|
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,
|