docdex 0.2.5 → 0.2.7

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 CHANGED
@@ -1,6 +1,6 @@
1
1
  # Changelog
2
2
 
3
- ## 0.2.5
3
+ ## 0.2.7
4
4
  - Added glama support
5
5
 
6
6
  ## 0.1.10
@@ -6,6 +6,7 @@ const net = require("node:net");
6
6
  const os = require("node:os");
7
7
  const path = require("node:path");
8
8
  const readline = require("node:readline");
9
+ const tty = require("node:tty");
9
10
  const { spawn, spawnSync } = require("node:child_process");
10
11
 
11
12
  const { detectPlatformKey, UnsupportedPlatformError } = require("./platform");
@@ -17,6 +18,7 @@ const STARTUP_FAILURE_MARKER = "startup_registration_failed.json";
17
18
  const DEFAULT_OLLAMA_MODEL = "nomic-embed-text";
18
19
  const DEFAULT_OLLAMA_CHAT_MODEL = "phi3.5:3.8b";
19
20
  const DEFAULT_OLLAMA_CHAT_MODEL_SIZE_GIB = 2.2;
21
+ const SETUP_PENDING_MARKER = "setup_pending.json";
20
22
 
21
23
  function defaultConfigPath() {
22
24
  return path.join(os.homedir(), ".docdex", "config.toml");
@@ -30,6 +32,10 @@ function stateDir() {
30
32
  return path.join(os.homedir(), ".docdex", "state");
31
33
  }
32
34
 
35
+ function setupPendingPath() {
36
+ return path.join(stateDir(), SETUP_PENDING_MARKER);
37
+ }
38
+
33
39
  function configUrlForPort(port) {
34
40
  return `http://localhost:${port}/sse`;
35
41
  }
@@ -383,11 +389,17 @@ function hasInteractiveTty(stdin, stdout) {
383
389
 
384
390
  function canPromptWithTty(stdin, stdout) {
385
391
  if (hasInteractiveTty(stdin, stdout)) return true;
386
- const ttyPath = process.platform === "win32" ? "CONIN$" : "/dev/tty";
392
+ const isWindows = process.platform === "win32";
393
+ const inputPath = isWindows ? "CONIN$" : "/dev/tty";
394
+ const outputPath = isWindows ? "CONOUT$" : "/dev/tty";
387
395
  try {
388
- const fd = fs.openSync(ttyPath, "r");
389
- fs.closeSync(fd);
390
- return true;
396
+ const readFd = fs.openSync(inputPath, "r");
397
+ const writeFd = fs.openSync(outputPath, "w");
398
+ const readable = tty.isatty(readFd);
399
+ const writable = tty.isatty(writeFd);
400
+ fs.closeSync(readFd);
401
+ fs.closeSync(writeFd);
402
+ return readable && writable;
391
403
  } catch {
392
404
  return false;
393
405
  }
@@ -403,9 +415,9 @@ function resolveOllamaInstallMode({
403
415
  if (override === true) return { mode: "install", reason: "env", interactive: false };
404
416
  if (override === false) return { mode: "skip", reason: "env", interactive: false };
405
417
  if (!canPrompt(stdin, stdout)) {
418
+ if (env.CI) return { mode: "skip", reason: "ci", interactive: false };
406
419
  return { mode: "skip", reason: "non_interactive", interactive: false };
407
420
  }
408
- if (env.CI) return { mode: "skip", reason: "ci", interactive: false };
409
421
  return { mode: "prompt", reason: "interactive", interactive: true };
410
422
  }
411
423
 
@@ -421,9 +433,9 @@ function resolveOllamaModelPromptMode({
421
433
  const assumeYes = parseEnvBool(env.DOCDEX_OLLAMA_MODEL_ASSUME_Y);
422
434
  if (assumeYes === true) return { mode: "auto", reason: "env", interactive: false };
423
435
  if (!canPrompt(stdin, stdout)) {
436
+ if (env.CI) return { mode: "skip", reason: "ci", interactive: false };
424
437
  return { mode: "skip", reason: "non_interactive", interactive: false };
425
438
  }
426
- if (env.CI) return { mode: "skip", reason: "ci", interactive: false };
427
439
  return { mode: "prompt", reason: "interactive", interactive: true };
428
440
  }
429
441
 
@@ -585,10 +597,26 @@ function resolvePromptStreams(stdin, stdout) {
585
597
  return { input: stdin, output: stdout, close: null };
586
598
  }
587
599
  const isWindows = process.platform === "win32";
588
- const ttyPath = isWindows ? "CONIN$" : "/dev/tty";
600
+ const inputPath = isWindows ? "CONIN$" : "/dev/tty";
601
+ const outputPath = isWindows ? "CONOUT$" : "/dev/tty";
589
602
  try {
590
- const input = fs.createReadStream(ttyPath, { autoClose: true });
591
- return { input, output: stdout, close: () => input.close() };
603
+ const readFd = fs.openSync(inputPath, "r");
604
+ const writeFd = fs.openSync(outputPath, "w");
605
+ if (!tty.isatty(readFd) || !tty.isatty(writeFd)) {
606
+ fs.closeSync(readFd);
607
+ fs.closeSync(writeFd);
608
+ return { input: stdin, output: stdout, close: null };
609
+ }
610
+ const input = fs.createReadStream(inputPath, { fd: readFd, autoClose: true });
611
+ const output = fs.createWriteStream(outputPath, { fd: writeFd, autoClose: true });
612
+ return {
613
+ input,
614
+ output,
615
+ close: () => {
616
+ input.close();
617
+ output.end();
618
+ }
619
+ };
592
620
  } catch {
593
621
  return { input: stdin, output: stdout, close: null };
594
622
  }
@@ -597,8 +625,11 @@ function resolvePromptStreams(stdin, stdout) {
597
625
  function promptYesNo(question, { defaultYes = true, stdin = process.stdin, stdout = process.stdout } = {}) {
598
626
  return new Promise((resolve) => {
599
627
  const { input, output, close } = resolvePromptStreams(stdin, stdout);
600
- const rl = readline.createInterface({ input, output });
601
- rl.question(question, (answer) => {
628
+ const rl = readline.createInterface({ input, output, terminal: Boolean(output?.isTTY) });
629
+ if (output && typeof output.write === "function") {
630
+ output.write(`\n${question}`);
631
+ }
632
+ rl.question("", (answer) => {
602
633
  rl.close();
603
634
  if (typeof close === "function") close();
604
635
  const normalized = String(answer || "").trim().toLowerCase();
@@ -611,8 +642,11 @@ function promptYesNo(question, { defaultYes = true, stdin = process.stdin, stdou
611
642
  function promptInput(question, { stdin = process.stdin, stdout = process.stdout } = {}) {
612
643
  return new Promise((resolve) => {
613
644
  const { input, output, close } = resolvePromptStreams(stdin, stdout);
614
- const rl = readline.createInterface({ input, output });
615
- rl.question(question, (answer) => {
645
+ const rl = readline.createInterface({ input, output, terminal: Boolean(output?.isTTY) });
646
+ if (output && typeof output.write === "function") {
647
+ output.write(`\n${question}`);
648
+ }
649
+ rl.question("", (answer) => {
616
650
  rl.close();
617
651
  if (typeof close === "function") close();
618
652
  resolve(String(answer || "").trim());
@@ -888,6 +922,11 @@ function registerStartup({ binaryPath, port, repoRoot, logger }) {
888
922
  `<dict>\n` +
889
923
  ` <key>Label</key>\n` +
890
924
  ` <string>com.docdex.daemon</string>\n` +
925
+ ` <key>EnvironmentVariables</key>\n` +
926
+ ` <dict>\n` +
927
+ ` <key>DOCDEX_BROWSER_AUTO_INSTALL</key>\n` +
928
+ ` <string>0</string>\n` +
929
+ ` </dict>\n` +
891
930
  ` <key>ProgramArguments</key>\n` +
892
931
  ` <array>\n` +
893
932
  programArgs.map((arg) => ` <string>${arg}</string>\n`).join("") +
@@ -926,6 +965,7 @@ function registerStartup({ binaryPath, port, repoRoot, logger }) {
926
965
  "",
927
966
  "[Service]",
928
967
  `ExecStart=${binaryPath} ${args.join(" ")}`,
968
+ "Environment=DOCDEX_BROWSER_AUTO_INSTALL=0",
929
969
  "Restart=always",
930
970
  "RestartSec=2",
931
971
  "",
@@ -943,7 +983,9 @@ function registerStartup({ binaryPath, port, repoRoot, logger }) {
943
983
 
944
984
  if (process.platform === "win32") {
945
985
  const taskName = "Docdex Daemon";
946
- const taskArgs = `"${binaryPath}" ${args.map((arg) => `"${arg}"`).join(" ")}`;
986
+ const joinedArgs = args.map((arg) => `"${arg}"`).join(" ");
987
+ const taskArgs =
988
+ `"cmd.exe" /c "set DOCDEX_BROWSER_AUTO_INSTALL=0 && \"${binaryPath}\" ${joinedArgs}"`;
947
989
  const create = spawnSync("schtasks", [
948
990
  "/Create",
949
991
  "/F",
@@ -983,7 +1025,14 @@ function startDaemonNow({ binaryPath, port, repoRoot }) {
983
1025
  "warn",
984
1026
  "--secure-mode=false"
985
1027
  ],
986
- { stdio: "ignore", detached: true }
1028
+ {
1029
+ stdio: "ignore",
1030
+ detached: true,
1031
+ env: {
1032
+ ...process.env,
1033
+ DOCDEX_BROWSER_AUTO_INSTALL: "0"
1034
+ }
1035
+ }
987
1036
  );
988
1037
  child.unref();
989
1038
  return true;
@@ -995,6 +1044,12 @@ function recordStartupFailure(details) {
995
1044
  fs.writeFileSync(markerPath, JSON.stringify(details, null, 2));
996
1045
  }
997
1046
 
1047
+ function recordSetupPending(details) {
1048
+ const markerPath = setupPendingPath();
1049
+ fs.mkdirSync(path.dirname(markerPath), { recursive: true });
1050
+ fs.writeFileSync(markerPath, JSON.stringify(details, null, 2));
1051
+ }
1052
+
998
1053
  function clearStartupFailure() {
999
1054
  const markerPath = path.join(stateDir(), STARTUP_FAILURE_MARKER);
1000
1055
  if (fs.existsSync(markerPath)) fs.unlinkSync(markerPath);
@@ -1004,6 +1059,58 @@ function startupFailureReported() {
1004
1059
  return fs.existsSync(path.join(stateDir(), STARTUP_FAILURE_MARKER));
1005
1060
  }
1006
1061
 
1062
+ function shouldSkipSetup(env = process.env) {
1063
+ return parseEnvBool(env.DOCDEX_SETUP_SKIP) === true;
1064
+ }
1065
+
1066
+ function launchSetupWizard({
1067
+ binaryPath,
1068
+ logger,
1069
+ env = process.env,
1070
+ stdin = process.stdin,
1071
+ stdout = process.stdout,
1072
+ spawnFn = spawn,
1073
+ spawnSyncFn = spawnSync,
1074
+ platform = process.platform,
1075
+ canPrompt = canPromptWithTty
1076
+ }) {
1077
+ if (!binaryPath) return { ok: false, reason: "missing_binary" };
1078
+ if (shouldSkipSetup(env)) return { ok: false, reason: "skipped" };
1079
+
1080
+ const args = ["setup"];
1081
+ if (platform === "linux") {
1082
+ if (!canPrompt(stdin, stdout)) {
1083
+ return { ok: false, reason: "non_interactive" };
1084
+ }
1085
+ const child = spawnFn(binaryPath, args, { stdio: "inherit" });
1086
+ if (child.pid) return { ok: true };
1087
+ return { ok: false, reason: "spawn_failed" };
1088
+ }
1089
+
1090
+ if (platform === "darwin") {
1091
+ const command = `${binaryPath} ${args.join(" ")}`;
1092
+ const osa = [
1093
+ "osascript",
1094
+ "-e",
1095
+ `tell application \"Terminal\" to do script \"${command.replace(/"/g, '\\"')}\"`
1096
+ ];
1097
+ const result = spawnSyncFn(osa[0], osa.slice(1));
1098
+ if (result.status === 0) return { ok: true };
1099
+ logger?.warn?.(`[docdex] osascript failed: ${result.stderr || "unknown error"}`);
1100
+ return { ok: false, reason: "terminal_launch_failed" };
1101
+ }
1102
+
1103
+ if (platform === "win32") {
1104
+ const quoted = `"${binaryPath}" ${args.map((arg) => `"${arg}"`).join(" ")}`;
1105
+ const result = spawnSyncFn("cmd", ["/c", "start", "", quoted]);
1106
+ if (result.status === 0) return { ok: true };
1107
+ logger?.warn?.(`[docdex] cmd start failed: ${result.stderr || "unknown error"}`);
1108
+ return { ok: false, reason: "terminal_launch_failed" };
1109
+ }
1110
+
1111
+ return { ok: false, reason: "unsupported_platform" };
1112
+ }
1113
+
1007
1114
  async function runPostInstallSetup({ binaryPath, logger } = {}) {
1008
1115
  const log = logger || console;
1009
1116
  const configPath = defaultConfigPath();
@@ -1050,8 +1157,11 @@ async function runPostInstallSetup({ binaryPath, logger } = {}) {
1050
1157
  }
1051
1158
 
1052
1159
  startDaemonNow({ binaryPath: resolvedBinary, port, repoRoot: daemonRoot });
1053
- await maybeInstallOllama({ logger: log });
1054
- await maybePromptOllamaModel({ logger: log, configPath });
1160
+ const setupLaunch = launchSetupWizard({ binaryPath: resolvedBinary, logger: log });
1161
+ if (!setupLaunch.ok && setupLaunch.reason !== "skipped") {
1162
+ log.warn?.("[docdex] setup wizard did not launch. Run `docdex setup`.");
1163
+ recordSetupPending({ reason: setupLaunch.reason, port, repoRoot: daemonRoot });
1164
+ }
1055
1165
  return { port, url, configPath };
1056
1166
  }
1057
1167
 
@@ -1073,5 +1183,7 @@ module.exports = {
1073
1183
  pullOllamaModel,
1074
1184
  listOllamaModels,
1075
1185
  hasInteractiveTty,
1076
- canPromptWithTty
1186
+ canPromptWithTty,
1187
+ shouldSkipSetup,
1188
+ launchSetupWizard
1077
1189
  };
package/package.json CHANGED
@@ -1,6 +1,7 @@
1
1
  {
2
2
  "name": "docdex",
3
- "version": "0.2.5",
3
+ "version": "0.2.7",
4
+ "mcpName": "io.github.bekirdag/docdex",
4
5
  "description": "Docdex CLI as an npm-installable binary wrapper.",
5
6
  "bin": {
6
7
  "docdex": "bin/docdex.js",