docdex 0.2.6 → 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 +111 -12
- 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
|
}
|
|
@@ -389,9 +395,11 @@ function canPromptWithTty(stdin, stdout) {
|
|
|
389
395
|
try {
|
|
390
396
|
const readFd = fs.openSync(inputPath, "r");
|
|
391
397
|
const writeFd = fs.openSync(outputPath, "w");
|
|
398
|
+
const readable = tty.isatty(readFd);
|
|
399
|
+
const writable = tty.isatty(writeFd);
|
|
392
400
|
fs.closeSync(readFd);
|
|
393
401
|
fs.closeSync(writeFd);
|
|
394
|
-
return
|
|
402
|
+
return readable && writable;
|
|
395
403
|
} catch {
|
|
396
404
|
return false;
|
|
397
405
|
}
|
|
@@ -592,8 +600,15 @@ function resolvePromptStreams(stdin, stdout) {
|
|
|
592
600
|
const inputPath = isWindows ? "CONIN$" : "/dev/tty";
|
|
593
601
|
const outputPath = isWindows ? "CONOUT$" : "/dev/tty";
|
|
594
602
|
try {
|
|
595
|
-
const
|
|
596
|
-
const
|
|
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 });
|
|
597
612
|
return {
|
|
598
613
|
input,
|
|
599
614
|
output,
|
|
@@ -610,8 +625,11 @@ function resolvePromptStreams(stdin, stdout) {
|
|
|
610
625
|
function promptYesNo(question, { defaultYes = true, stdin = process.stdin, stdout = process.stdout } = {}) {
|
|
611
626
|
return new Promise((resolve) => {
|
|
612
627
|
const { input, output, close } = resolvePromptStreams(stdin, stdout);
|
|
613
|
-
const rl = readline.createInterface({ input, output });
|
|
614
|
-
|
|
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) => {
|
|
615
633
|
rl.close();
|
|
616
634
|
if (typeof close === "function") close();
|
|
617
635
|
const normalized = String(answer || "").trim().toLowerCase();
|
|
@@ -624,8 +642,11 @@ function promptYesNo(question, { defaultYes = true, stdin = process.stdin, stdou
|
|
|
624
642
|
function promptInput(question, { stdin = process.stdin, stdout = process.stdout } = {}) {
|
|
625
643
|
return new Promise((resolve) => {
|
|
626
644
|
const { input, output, close } = resolvePromptStreams(stdin, stdout);
|
|
627
|
-
const rl = readline.createInterface({ input, output });
|
|
628
|
-
|
|
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) => {
|
|
629
650
|
rl.close();
|
|
630
651
|
if (typeof close === "function") close();
|
|
631
652
|
resolve(String(answer || "").trim());
|
|
@@ -901,6 +922,11 @@ function registerStartup({ binaryPath, port, repoRoot, logger }) {
|
|
|
901
922
|
`<dict>\n` +
|
|
902
923
|
` <key>Label</key>\n` +
|
|
903
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` +
|
|
904
930
|
` <key>ProgramArguments</key>\n` +
|
|
905
931
|
` <array>\n` +
|
|
906
932
|
programArgs.map((arg) => ` <string>${arg}</string>\n`).join("") +
|
|
@@ -939,6 +965,7 @@ function registerStartup({ binaryPath, port, repoRoot, logger }) {
|
|
|
939
965
|
"",
|
|
940
966
|
"[Service]",
|
|
941
967
|
`ExecStart=${binaryPath} ${args.join(" ")}`,
|
|
968
|
+
"Environment=DOCDEX_BROWSER_AUTO_INSTALL=0",
|
|
942
969
|
"Restart=always",
|
|
943
970
|
"RestartSec=2",
|
|
944
971
|
"",
|
|
@@ -956,7 +983,9 @@ function registerStartup({ binaryPath, port, repoRoot, logger }) {
|
|
|
956
983
|
|
|
957
984
|
if (process.platform === "win32") {
|
|
958
985
|
const taskName = "Docdex Daemon";
|
|
959
|
-
const
|
|
986
|
+
const joinedArgs = args.map((arg) => `"${arg}"`).join(" ");
|
|
987
|
+
const taskArgs =
|
|
988
|
+
`"cmd.exe" /c "set DOCDEX_BROWSER_AUTO_INSTALL=0 && \"${binaryPath}\" ${joinedArgs}"`;
|
|
960
989
|
const create = spawnSync("schtasks", [
|
|
961
990
|
"/Create",
|
|
962
991
|
"/F",
|
|
@@ -996,7 +1025,14 @@ function startDaemonNow({ binaryPath, port, repoRoot }) {
|
|
|
996
1025
|
"warn",
|
|
997
1026
|
"--secure-mode=false"
|
|
998
1027
|
],
|
|
999
|
-
{
|
|
1028
|
+
{
|
|
1029
|
+
stdio: "ignore",
|
|
1030
|
+
detached: true,
|
|
1031
|
+
env: {
|
|
1032
|
+
...process.env,
|
|
1033
|
+
DOCDEX_BROWSER_AUTO_INSTALL: "0"
|
|
1034
|
+
}
|
|
1035
|
+
}
|
|
1000
1036
|
);
|
|
1001
1037
|
child.unref();
|
|
1002
1038
|
return true;
|
|
@@ -1008,6 +1044,12 @@ function recordStartupFailure(details) {
|
|
|
1008
1044
|
fs.writeFileSync(markerPath, JSON.stringify(details, null, 2));
|
|
1009
1045
|
}
|
|
1010
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
|
+
|
|
1011
1053
|
function clearStartupFailure() {
|
|
1012
1054
|
const markerPath = path.join(stateDir(), STARTUP_FAILURE_MARKER);
|
|
1013
1055
|
if (fs.existsSync(markerPath)) fs.unlinkSync(markerPath);
|
|
@@ -1017,6 +1059,58 @@ function startupFailureReported() {
|
|
|
1017
1059
|
return fs.existsSync(path.join(stateDir(), STARTUP_FAILURE_MARKER));
|
|
1018
1060
|
}
|
|
1019
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
|
+
|
|
1020
1114
|
async function runPostInstallSetup({ binaryPath, logger } = {}) {
|
|
1021
1115
|
const log = logger || console;
|
|
1022
1116
|
const configPath = defaultConfigPath();
|
|
@@ -1063,8 +1157,11 @@ async function runPostInstallSetup({ binaryPath, logger } = {}) {
|
|
|
1063
1157
|
}
|
|
1064
1158
|
|
|
1065
1159
|
startDaemonNow({ binaryPath: resolvedBinary, port, repoRoot: daemonRoot });
|
|
1066
|
-
|
|
1067
|
-
|
|
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
|
+
}
|
|
1068
1165
|
return { port, url, configPath };
|
|
1069
1166
|
}
|
|
1070
1167
|
|
|
@@ -1086,5 +1183,7 @@ module.exports = {
|
|
|
1086
1183
|
pullOllamaModel,
|
|
1087
1184
|
listOllamaModels,
|
|
1088
1185
|
hasInteractiveTty,
|
|
1089
|
-
canPromptWithTty
|
|
1186
|
+
canPromptWithTty,
|
|
1187
|
+
shouldSkipSetup,
|
|
1188
|
+
launchSetupWizard
|
|
1090
1189
|
};
|