docdex 0.2.8 → 0.2.10
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 +1 -1
- package/lib/install.js +9 -0
- package/lib/postinstall_setup.js +101 -19
- package/lib/uninstall.js +70 -0
- package/package.json +2 -1
package/CHANGELOG.md
CHANGED
package/lib/install.js
CHANGED
|
@@ -427,6 +427,15 @@ async function installFromLocalBinary({
|
|
|
427
427
|
if (!isWin32) {
|
|
428
428
|
await fsModule.promises.chmod(destPath, 0o755).catch(() => {});
|
|
429
429
|
}
|
|
430
|
+
const mcpName = isWin32 ? "docdex-mcp-server.exe" : "docdex-mcp-server";
|
|
431
|
+
const mcpSource = pathModule.join(pathModule.dirname(binaryPath), mcpName);
|
|
432
|
+
if (fsModule.existsSync(mcpSource)) {
|
|
433
|
+
const mcpDest = pathModule.join(distDir, mcpName);
|
|
434
|
+
await fsModule.promises.copyFile(mcpSource, mcpDest);
|
|
435
|
+
if (!isWin32) {
|
|
436
|
+
await fsModule.promises.chmod(mcpDest, 0o755).catch(() => {});
|
|
437
|
+
}
|
|
438
|
+
}
|
|
430
439
|
const binarySha256 = await sha256FileFn(destPath);
|
|
431
440
|
const metadata = {
|
|
432
441
|
schemaVersion: INSTALL_METADATA_SCHEMA_VERSION,
|
package/lib/postinstall_setup.js
CHANGED
|
@@ -40,6 +40,10 @@ function configUrlForPort(port) {
|
|
|
40
40
|
return `http://localhost:${port}/sse`;
|
|
41
41
|
}
|
|
42
42
|
|
|
43
|
+
function configStreamableUrlForPort(port) {
|
|
44
|
+
return `http://localhost:${port}/v1/mcp`;
|
|
45
|
+
}
|
|
46
|
+
|
|
43
47
|
function isPortAvailable(port, host) {
|
|
44
48
|
return new Promise((resolve) => {
|
|
45
49
|
const server = net.createServer();
|
|
@@ -837,11 +841,21 @@ async function maybePromptOllamaModel({
|
|
|
837
841
|
}
|
|
838
842
|
|
|
839
843
|
const normalizedInstalled = installed.map(normalizeModelName);
|
|
844
|
+
const displayModels = normalizedInstalled.map((model) => {
|
|
845
|
+
const selectable = model.toLowerCase() !== DEFAULT_OLLAMA_MODEL.toLowerCase();
|
|
846
|
+
return {
|
|
847
|
+
model,
|
|
848
|
+
label: selectable ? model : `${model} (embedding only)`,
|
|
849
|
+
selectable
|
|
850
|
+
};
|
|
851
|
+
});
|
|
852
|
+
const selectableModels = displayModels.filter((item) => item.selectable).map((item) => item.model);
|
|
840
853
|
const installedLower = normalizedInstalled.map((model) => model.toLowerCase());
|
|
841
854
|
const hasPhi = installedLower.includes(phiModel.toLowerCase());
|
|
842
|
-
const
|
|
855
|
+
const defaultLower = defaultChoice ? defaultChoice.toLowerCase() : null;
|
|
856
|
+
const selectionDefault = defaultLower && selectableModels.some((model) => model.toLowerCase() === defaultLower)
|
|
843
857
|
? defaultChoice
|
|
844
|
-
:
|
|
858
|
+
: selectableModels[0];
|
|
845
859
|
|
|
846
860
|
if (decision.mode === "auto") {
|
|
847
861
|
if (selectionDefault) {
|
|
@@ -852,24 +866,28 @@ async function maybePromptOllamaModel({
|
|
|
852
866
|
}
|
|
853
867
|
|
|
854
868
|
stdout.write("[docdex] Ollama models detected:\n");
|
|
855
|
-
|
|
856
|
-
const marker = model === selectionDefault ? " (default)" : "";
|
|
857
|
-
stdout.write(` ${idx + 1}) ${
|
|
869
|
+
displayModels.forEach((item, idx) => {
|
|
870
|
+
const marker = item.model === selectionDefault ? " (default)" : "";
|
|
871
|
+
stdout.write(` ${idx + 1}) ${item.label}${marker}\n`);
|
|
858
872
|
});
|
|
859
873
|
if (!hasPhi) {
|
|
860
874
|
stdout.write(` I) Install ${phiModel} (~${sizeText}, free ${freeText})\n`);
|
|
861
875
|
}
|
|
862
876
|
stdout.write(" S) Skip\n");
|
|
863
877
|
|
|
878
|
+
const defaultHint = selectionDefault ? ` [${selectionDefault}]` : "";
|
|
864
879
|
const answer = await promptInput(
|
|
865
|
-
`[docdex] Select default model
|
|
880
|
+
`[docdex] Select default model${defaultHint}: `,
|
|
866
881
|
{ stdin, stdout }
|
|
867
882
|
);
|
|
868
883
|
const normalizedAnswer = normalizeModelName(answer);
|
|
869
884
|
const answerLower = normalizedAnswer.toLowerCase();
|
|
870
885
|
if (!answer) {
|
|
871
|
-
|
|
872
|
-
|
|
886
|
+
if (selectionDefault) {
|
|
887
|
+
updateDefaultModelConfig(configPath, selectionDefault, logger);
|
|
888
|
+
return { status: "selected", model: selectionDefault };
|
|
889
|
+
}
|
|
890
|
+
return { status: "skipped", reason: "no_models" };
|
|
873
891
|
}
|
|
874
892
|
if (answerLower === "s" || answerLower === "skip") {
|
|
875
893
|
return { status: "skipped", reason: "user_skip" };
|
|
@@ -881,16 +899,24 @@ async function maybePromptOllamaModel({
|
|
|
881
899
|
return { status: "installed", model: phiModel };
|
|
882
900
|
}
|
|
883
901
|
const numeric = Number.parseInt(answerLower, 10);
|
|
884
|
-
if (Number.isFinite(numeric) && numeric >= 1 && numeric <=
|
|
885
|
-
const selected =
|
|
886
|
-
|
|
887
|
-
|
|
902
|
+
if (Number.isFinite(numeric) && numeric >= 1 && numeric <= displayModels.length) {
|
|
903
|
+
const selected = displayModels[numeric - 1];
|
|
904
|
+
if (!selected.selectable) {
|
|
905
|
+
logger?.warn?.(`[docdex] ${selected.model} is an embedding-only model; choose a chat model.`);
|
|
906
|
+
return { status: "skipped", reason: "embedding_only" };
|
|
907
|
+
}
|
|
908
|
+
updateDefaultModelConfig(configPath, selected.model, logger);
|
|
909
|
+
return { status: "selected", model: selected.model };
|
|
888
910
|
}
|
|
889
911
|
const matchedIndex = installedLower.indexOf(answerLower);
|
|
890
912
|
if (matchedIndex !== -1) {
|
|
891
|
-
const selected =
|
|
892
|
-
|
|
893
|
-
|
|
913
|
+
const selected = displayModels[matchedIndex];
|
|
914
|
+
if (!selected.selectable) {
|
|
915
|
+
logger?.warn?.(`[docdex] ${selected.model} is an embedding-only model; choose a chat model.`);
|
|
916
|
+
return { status: "skipped", reason: "embedding_only" };
|
|
917
|
+
}
|
|
918
|
+
updateDefaultModelConfig(configPath, selected.model, logger);
|
|
919
|
+
return { status: "selected", model: selected.model };
|
|
894
920
|
}
|
|
895
921
|
logger?.warn?.("[docdex] Unrecognized selection; skipping model update.");
|
|
896
922
|
return { status: "skipped", reason: "invalid_selection" };
|
|
@@ -1063,6 +1089,55 @@ function shouldSkipSetup(env = process.env) {
|
|
|
1063
1089
|
return parseEnvBool(env.DOCDEX_SETUP_SKIP) === true;
|
|
1064
1090
|
}
|
|
1065
1091
|
|
|
1092
|
+
function commandExists(cmd, spawnSyncFn) {
|
|
1093
|
+
const result = spawnSyncFn(cmd, ["--version"], { stdio: "ignore" });
|
|
1094
|
+
if (result?.error?.code === "ENOENT") return false;
|
|
1095
|
+
return true;
|
|
1096
|
+
}
|
|
1097
|
+
|
|
1098
|
+
function launchMacTerminal({ binaryPath, args, spawnSyncFn, logger }) {
|
|
1099
|
+
const command = [binaryPath, ...args].join(" ");
|
|
1100
|
+
const script = [
|
|
1101
|
+
'tell application "Terminal"',
|
|
1102
|
+
'if not (exists window 1) then',
|
|
1103
|
+
`do script ${JSON.stringify(command)}`,
|
|
1104
|
+
"else",
|
|
1105
|
+
`do script ${JSON.stringify(command)} in window 1`,
|
|
1106
|
+
"end if",
|
|
1107
|
+
"activate",
|
|
1108
|
+
"end tell"
|
|
1109
|
+
].join("\n");
|
|
1110
|
+
const result = spawnSyncFn("osascript", ["-e", script]);
|
|
1111
|
+
if (result.status === 0) return true;
|
|
1112
|
+
logger?.warn?.(`[docdex] osascript launch failed: ${result.stderr || "unknown error"}`);
|
|
1113
|
+
return false;
|
|
1114
|
+
}
|
|
1115
|
+
|
|
1116
|
+
function launchLinuxTerminal({ binaryPath, args, spawnFn, spawnSyncFn }) {
|
|
1117
|
+
const candidates = [
|
|
1118
|
+
{ cmd: "x-terminal-emulator", args: ["-e", binaryPath, ...args] },
|
|
1119
|
+
{ cmd: "gnome-terminal", args: ["--", binaryPath, ...args] },
|
|
1120
|
+
{ cmd: "konsole", args: ["-e", binaryPath, ...args] },
|
|
1121
|
+
{ cmd: "xfce4-terminal", args: ["-e", binaryPath, ...args] },
|
|
1122
|
+
{ cmd: "xterm", args: ["-e", binaryPath, ...args] },
|
|
1123
|
+
{ cmd: "kitty", args: ["-e", binaryPath, ...args] },
|
|
1124
|
+
{ cmd: "alacritty", args: ["-e", binaryPath, ...args] },
|
|
1125
|
+
{ cmd: "wezterm", args: ["start", "--", binaryPath, ...args] }
|
|
1126
|
+
];
|
|
1127
|
+
for (const candidate of candidates) {
|
|
1128
|
+
if (!commandExists(candidate.cmd, spawnSyncFn)) continue;
|
|
1129
|
+
const child = spawnFn(candidate.cmd, candidate.args, {
|
|
1130
|
+
stdio: "ignore",
|
|
1131
|
+
detached: true
|
|
1132
|
+
});
|
|
1133
|
+
if (child?.pid) {
|
|
1134
|
+
child.unref?.();
|
|
1135
|
+
return true;
|
|
1136
|
+
}
|
|
1137
|
+
}
|
|
1138
|
+
return false;
|
|
1139
|
+
}
|
|
1140
|
+
|
|
1066
1141
|
function launchSetupWizard({
|
|
1067
1142
|
binaryPath,
|
|
1068
1143
|
logger,
|
|
@@ -1082,9 +1157,14 @@ function launchSetupWizard({
|
|
|
1082
1157
|
if (!canPrompt(stdin, stdout)) {
|
|
1083
1158
|
return { ok: false, reason: "non_interactive" };
|
|
1084
1159
|
}
|
|
1085
|
-
|
|
1086
|
-
|
|
1087
|
-
|
|
1160
|
+
if (platform === "darwin") {
|
|
1161
|
+
return launchMacTerminal({ binaryPath, args, spawnSyncFn, logger })
|
|
1162
|
+
? { ok: true }
|
|
1163
|
+
: { ok: false, reason: "terminal_launch_failed" };
|
|
1164
|
+
}
|
|
1165
|
+
return launchLinuxTerminal({ binaryPath, args, spawnFn, spawnSyncFn })
|
|
1166
|
+
? { ok: true }
|
|
1167
|
+
: { ok: false, reason: "terminal_launch_failed" };
|
|
1088
1168
|
}
|
|
1089
1169
|
|
|
1090
1170
|
if (platform === "win32") {
|
|
@@ -1125,10 +1205,11 @@ async function runPostInstallSetup({ binaryPath, logger } = {}) {
|
|
|
1125
1205
|
}
|
|
1126
1206
|
|
|
1127
1207
|
const url = configUrlForPort(port);
|
|
1208
|
+
const codexUrl = configStreamableUrlForPort(port);
|
|
1128
1209
|
const paths = clientConfigPaths();
|
|
1129
1210
|
upsertMcpServerJson(paths.claude, url);
|
|
1130
1211
|
upsertMcpServerJson(paths.cursor, url);
|
|
1131
|
-
upsertCodexConfig(paths.codex,
|
|
1212
|
+
upsertCodexConfig(paths.codex, codexUrl);
|
|
1132
1213
|
|
|
1133
1214
|
const daemonRoot = ensureDaemonRoot();
|
|
1134
1215
|
const resolvedBinary = resolveBinaryPath({ binaryPath });
|
|
@@ -1160,6 +1241,7 @@ module.exports = {
|
|
|
1160
1241
|
upsertCodexConfig,
|
|
1161
1242
|
pickAvailablePort,
|
|
1162
1243
|
configUrlForPort,
|
|
1244
|
+
configStreamableUrlForPort,
|
|
1163
1245
|
parseEnvBool,
|
|
1164
1246
|
resolveOllamaInstallMode,
|
|
1165
1247
|
resolveOllamaModelPromptMode,
|
package/lib/uninstall.js
CHANGED
|
@@ -206,9 +206,68 @@ function removeCodexConfig(pathname, name = "docdex") {
|
|
|
206
206
|
return output.join("\n");
|
|
207
207
|
};
|
|
208
208
|
|
|
209
|
+
const removeLooseEntry = (text) => {
|
|
210
|
+
const lines = text.split(/\r?\n/);
|
|
211
|
+
const output = [];
|
|
212
|
+
let changed = false;
|
|
213
|
+
for (const line of lines) {
|
|
214
|
+
if (/^\s*docdex\s*=/.test(line)) {
|
|
215
|
+
changed = true;
|
|
216
|
+
continue;
|
|
217
|
+
}
|
|
218
|
+
output.push(line);
|
|
219
|
+
}
|
|
220
|
+
return { text: output.join("\n"), changed };
|
|
221
|
+
};
|
|
222
|
+
|
|
223
|
+
const removeEmptyMcpServersTable = (text) => {
|
|
224
|
+
const lines = text.split(/\r?\n/);
|
|
225
|
+
const output = [];
|
|
226
|
+
let inTable = false;
|
|
227
|
+
let tableHasEntries = false;
|
|
228
|
+
let buffer = [];
|
|
229
|
+
|
|
230
|
+
const flushTable = () => {
|
|
231
|
+
if (!inTable) return;
|
|
232
|
+
if (tableHasEntries) output.push(...buffer);
|
|
233
|
+
inTable = false;
|
|
234
|
+
tableHasEntries = false;
|
|
235
|
+
buffer = [];
|
|
236
|
+
};
|
|
237
|
+
|
|
238
|
+
for (const line of lines) {
|
|
239
|
+
const section = line.match(/^\s*\[([^\]]+)\]\s*$/);
|
|
240
|
+
if (section) {
|
|
241
|
+
flushTable();
|
|
242
|
+
if (section[1].trim() === "mcp_servers") {
|
|
243
|
+
inTable = true;
|
|
244
|
+
buffer = [line];
|
|
245
|
+
continue;
|
|
246
|
+
}
|
|
247
|
+
output.push(line);
|
|
248
|
+
continue;
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
if (inTable) {
|
|
252
|
+
if (line.trim() && !line.trim().startsWith("#")) {
|
|
253
|
+
if (/=/.test(line)) tableHasEntries = true;
|
|
254
|
+
}
|
|
255
|
+
buffer.push(line);
|
|
256
|
+
continue;
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
output.push(line);
|
|
260
|
+
}
|
|
261
|
+
flushTable();
|
|
262
|
+
return output.join("\n");
|
|
263
|
+
};
|
|
264
|
+
|
|
209
265
|
contents = removeArrayBlocks(contents);
|
|
210
266
|
contents = removeNestedSection(contents);
|
|
211
267
|
contents = removeTableEntry(contents);
|
|
268
|
+
const loose = removeLooseEntry(contents);
|
|
269
|
+
contents = loose.text;
|
|
270
|
+
contents = removeEmptyMcpServersTable(contents);
|
|
212
271
|
|
|
213
272
|
if (contents !== original) {
|
|
214
273
|
fs.writeFileSync(pathname, contents.endsWith("\n") ? contents : `${contents}\n`);
|
|
@@ -321,6 +380,16 @@ function stopDaemonByName() {
|
|
|
321
380
|
return true;
|
|
322
381
|
}
|
|
323
382
|
|
|
383
|
+
function stopMcpByName() {
|
|
384
|
+
if (process.platform === "win32") {
|
|
385
|
+
spawnSync("taskkill", ["/IM", "docdex-mcp-server.exe", "/T", "/F"]);
|
|
386
|
+
return true;
|
|
387
|
+
}
|
|
388
|
+
spawnSync("pkill", ["-TERM", "-x", "docdex-mcp-server"]);
|
|
389
|
+
spawnSync("pkill", ["-TERM", "-f", "docdex-mcp-server"]);
|
|
390
|
+
return true;
|
|
391
|
+
}
|
|
392
|
+
|
|
324
393
|
function unregisterStartup() {
|
|
325
394
|
if (process.platform === "darwin") {
|
|
326
395
|
const plistPath = path.join(os.homedir(), "Library", "LaunchAgents", "com.docdex.daemon.plist");
|
|
@@ -399,6 +468,7 @@ async function main() {
|
|
|
399
468
|
if (!stopped) {
|
|
400
469
|
stopDaemonByName();
|
|
401
470
|
}
|
|
471
|
+
stopMcpByName();
|
|
402
472
|
unregisterStartup();
|
|
403
473
|
removeClientConfigs();
|
|
404
474
|
clearStartupFailure();
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "docdex",
|
|
3
|
-
"version": "0.2.
|
|
3
|
+
"version": "0.2.10",
|
|
4
4
|
"mcpName": "io.github.bekirdag/docdex",
|
|
5
5
|
"description": "Docdex CLI as an npm-installable binary wrapper.",
|
|
6
6
|
"bin": {
|
|
@@ -16,6 +16,7 @@
|
|
|
16
16
|
],
|
|
17
17
|
"scripts": {
|
|
18
18
|
"postinstall": "node ./lib/install.js",
|
|
19
|
+
"preuninstall": "node ./lib/uninstall.js",
|
|
19
20
|
"postuninstall": "node ./lib/uninstall.js",
|
|
20
21
|
"test": "node --test",
|
|
21
22
|
"pack:verify": "node --test test/packaging_guardrails.test.js"
|