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 +1 -1
- package/lib/postinstall_setup.js +130 -18
- package/package.json +2 -1
package/CHANGELOG.md
CHANGED
package/lib/postinstall_setup.js
CHANGED
|
@@ -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
|
|
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
|
|
389
|
-
fs.
|
|
390
|
-
|
|
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
|
|
600
|
+
const inputPath = isWindows ? "CONIN$" : "/dev/tty";
|
|
601
|
+
const outputPath = isWindows ? "CONOUT$" : "/dev/tty";
|
|
589
602
|
try {
|
|
590
|
-
const
|
|
591
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
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
|
-
{
|
|
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
|
-
|
|
1054
|
-
|
|
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
|
};
|