docdex 0.2.39 → 0.2.41

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,5 +1,10 @@
1
1
  # Changelog
2
2
 
3
+ ## 0.2.41
4
+ - Improve Windows npm postinstall UX with a plain setup hint, npm lifecycle non-interactive detection, and no empty cmd window from immediate daemon start.
5
+ - Fix setup TUI menu selection highlighting on Windows terminals.
6
+ - Propagate DAG session IDs in docs so `docdex_dag_export` is called with the `dag_session_id` from search results.
7
+
3
8
  ## 0.2.39
4
9
  - Bump release metadata to 0.2.39.
5
10
 
package/assets/agents.md CHANGED
@@ -1,4 +1,4 @@
1
- ---- START OF DOCDEX INFO V0.2.39 ----
1
+ ---- START OF DOCDEX INFO V0.2.41 ----
2
2
  Docdex URL: http://127.0.0.1:28491
3
3
  Use this base URL for Docdex HTTP endpoints.
4
4
  Health check endpoint: `GET /healthz` (not `/v1/health`).
@@ -177,7 +177,7 @@ This section is the authoritative source for how to call Docdex. Do not guess fi
177
177
 
178
178
  - Default HTTP base URL: http://127.0.0.1:28491 (override with DOCDEX_HTTP_BASE_URL).
179
179
  - Single-repo HTTP daemon: `docdexd serve --repo /abs/path`. /v1/initialize is NOT used. repo_id is optional, but must match the serving repo if provided.
180
- - Multi-repo HTTP daemon: `docdexd daemon`. You MUST call /v1/initialize before repo-scoped HTTP endpoints. When multiple repos are mounted, repo_id is required on every repo-scoped request.
180
+ - Multi-repo HTTP daemon: `docdexd daemon` (alias: `docdex start`). You MUST call /v1/initialize before repo-scoped HTTP endpoints. When multiple repos are mounted, repo_id is required on every repo-scoped request.
181
181
 
182
182
  ### 1) Initialize (HTTP) - exact request payload
183
183
 
@@ -246,6 +246,8 @@ Query params:
246
246
  - `format` (optional: json/text/dot; default json)
247
247
  - `max_nodes` (optional)
248
248
  - `repo_id` (required when multiple repos are mounted)
249
+ Notes:
250
+ - Use the `dag_session_id` from `/search` responses (or MCP `docdex_search`/`docdex_web_research`) as the `session_id`. `dag_session_id` is accepted as an alias.
249
251
 
250
252
  ### 7) MCP over HTTP/SSE
251
253
 
@@ -270,7 +272,7 @@ Do not guess fields; use these canonical shapes.
270
272
  - `docdex_ast`: `{ project_root, path, max_nodes? }`
271
273
  - `docdex_impact_diagnostics`: `{ project_root, file? }`
272
274
  - `docdex_impact_graph`: `{ project_root, file, max_edges?, max_depth?, edge_types? }`
273
- - `docdex_dag_export`: `{ project_root, session_id, format?, max_nodes? }`
275
+ - `docdex_dag_export`: `{ project_root, session_id|dag_session_id, format?, max_nodes? }`
274
276
  - `docdex_memory_save`: `{ project_root, text }`
275
277
  - `docdex_memory_recall`: `{ project_root, query, top_k? }`
276
278
  - `docdex_get_profile`: `{ agent_id }`
package/lib/install.js CHANGED
@@ -2294,6 +2294,16 @@ function printPostInstallBanner() {
2294
2294
  return false;
2295
2295
  }
2296
2296
  };
2297
+ if (process.platform === "win32") {
2298
+ const plain = [
2299
+ "[docdex] Docdex installed successfully.",
2300
+ "[docdex] Next step: run `docdex setup` to complete the installation.",
2301
+ "[docdex] Setup configures Ollama/models + browser."
2302
+ ].join("\r\n") + "\r\n";
2303
+ if (!writeDirect(plain)) {
2304
+ writeStderr(plain);
2305
+ }
2306
+ }
2297
2307
  let width = 0;
2298
2308
  const content = [
2299
2309
  "\x1b[31m _ _ \x1b[0m",
@@ -2283,9 +2283,11 @@ function writeWindowsSetupRunner({ binaryPath, args, logger, distBaseDir } = {})
2283
2283
  }
2284
2284
  }
2285
2285
 
2286
- function registerStartup({ binaryPath, port, repoRoot, logger, distBaseDir }) {
2286
+ function registerStartup({ binaryPath, port, repoRoot, logger, distBaseDir, startNow = true }) {
2287
2287
  if (!binaryPath) return { ok: false, reason: "missing_binary" };
2288
- stopDaemonService({ logger });
2288
+ if (startNow) {
2289
+ stopDaemonService({ logger });
2290
+ }
2289
2291
  const envPairs = buildDaemonEnvPairs();
2290
2292
  const workingDir = repoRoot ? path.resolve(repoRoot) : null;
2291
2293
  const args = [
@@ -2337,6 +2339,7 @@ function registerStartup({ binaryPath, port, repoRoot, logger, distBaseDir }) {
2337
2339
  `</plist>\n`;
2338
2340
  fs.mkdirSync(path.dirname(plistPath), { recursive: true });
2339
2341
  fs.writeFileSync(plistPath, plist);
2342
+ if (!startNow) return { ok: true };
2340
2343
  const uid = typeof process.getuid === "function" ? process.getuid() : null;
2341
2344
  const bootstrap = uid != null
2342
2345
  ? spawnSync("launchctl", ["bootstrap", `gui/${uid}`, plistPath])
@@ -2371,7 +2374,11 @@ function registerStartup({ binaryPath, port, repoRoot, logger, distBaseDir }) {
2371
2374
  ].filter(Boolean).join("\n");
2372
2375
  fs.writeFileSync(unitPath, unit);
2373
2376
  const reload = spawnSync("systemctl", ["--user", "daemon-reload"]);
2374
- const enable = spawnSync("systemctl", ["--user", "enable", "--now", "docdexd.service"]);
2377
+ const enableArgs = startNow
2378
+ ? ["--user", "enable", "--now", "docdexd.service"]
2379
+ : ["--user", "enable", "docdexd.service"];
2380
+ const enable = spawnSync("systemctl", enableArgs);
2381
+ ensureSystemdUserLinger({ logger });
2375
2382
  if (reload.status === 0 && enable.status === 0) return { ok: true };
2376
2383
  logger?.warn?.(`[docdex] systemd failed: ${enable.stderr || reload.stderr || "unknown error"}`);
2377
2384
  return { ok: false, reason: "systemd_failed" };
@@ -2402,7 +2409,9 @@ function registerStartup({ binaryPath, port, repoRoot, logger, distBaseDir }) {
2402
2409
  taskArgs
2403
2410
  ]);
2404
2411
  if (create.status === 0) {
2405
- spawnSync("schtasks", ["/Run", "/TN", taskName]);
2412
+ if (startNow) {
2413
+ spawnSync("schtasks", ["/Run", "/TN", taskName]);
2414
+ }
2406
2415
  return { ok: true };
2407
2416
  }
2408
2417
  logger?.warn?.(`[docdex] schtasks failed: ${create.stderr || "unknown error"}`);
@@ -2412,18 +2421,22 @@ function registerStartup({ binaryPath, port, repoRoot, logger, distBaseDir }) {
2412
2421
  return { ok: false, reason: "unsupported_platform" };
2413
2422
  }
2414
2423
 
2415
- async function startDaemonWithHealthCheck({ binaryPath, port, host, logger, distBaseDir }) {
2424
+ async function startDaemonWithHealthCheck({ binaryPath, port, host, logger, distBaseDir, startNow = true }) {
2416
2425
  const startup = registerStartup({
2417
2426
  binaryPath,
2418
2427
  port,
2419
2428
  repoRoot: daemonRootPath(),
2420
2429
  logger,
2421
- distBaseDir
2430
+ distBaseDir,
2431
+ startNow
2422
2432
  });
2423
2433
  if (!startup.ok) {
2424
2434
  logger?.warn?.(`[docdex] daemon service registration failed (${startup.reason || "unknown"}).`);
2425
2435
  return { ok: false, reason: "startup_failed" };
2426
2436
  }
2437
+ if (!startNow) {
2438
+ return { ok: true, reason: "registered" };
2439
+ }
2427
2440
  startDaemonService({ logger });
2428
2441
  const healthy = await waitForDaemonHealthy({ host, port });
2429
2442
  if (healthy) {
@@ -2459,7 +2472,11 @@ function startupFailureReported() {
2459
2472
  }
2460
2473
 
2461
2474
  function isNpmLifecycle(env = process.env) {
2462
- return Boolean(env?.npm_lifecycle_event);
2475
+ if (env?.npm_lifecycle_event) return true;
2476
+ const userAgent = String(env?.npm_config_user_agent || "");
2477
+ if (userAgent.includes("npm/")) return true;
2478
+ if (env?.npm_execpath) return true;
2479
+ return false;
2463
2480
  }
2464
2481
 
2465
2482
  function shouldSkipDaemonSideEffects({ env = process.env, skipDaemon } = {}) {
@@ -2478,6 +2495,25 @@ function commandExists(cmd, spawnSyncFn) {
2478
2495
  return true;
2479
2496
  }
2480
2497
 
2498
+ function currentUsername() {
2499
+ try {
2500
+ const info = os.userInfo();
2501
+ if (info && info.username) return info.username;
2502
+ } catch {}
2503
+ return process.env.USER || process.env.LOGNAME || process.env.USERNAME || null;
2504
+ }
2505
+
2506
+ function ensureSystemdUserLinger({ logger } = {}) {
2507
+ if (process.platform !== "linux") return { ok: false, reason: "unsupported_platform" };
2508
+ if (!commandExists("loginctl", spawnSync)) return { ok: false, reason: "loginctl_missing" };
2509
+ const username = currentUsername();
2510
+ if (!username) return { ok: false, reason: "username_missing" };
2511
+ const result = spawnSync("loginctl", ["enable-linger", username]);
2512
+ if (result.status === 0) return { ok: true };
2513
+ logger?.warn?.(`[docdex] loginctl enable-linger failed: ${result.stderr || "unknown error"}`);
2514
+ return { ok: false, reason: "loginctl_failed" };
2515
+ }
2516
+
2481
2517
  function launchMacTerminal({ binaryPath, args, spawnSyncFn, logger }) {
2482
2518
  const command = [
2483
2519
  "DOCDEX_SETUP_AUTO=1",
@@ -2558,6 +2594,9 @@ function launchSetupWizard({
2558
2594
  }
2559
2595
 
2560
2596
  if (platform === "win32") {
2597
+ if (!canPrompt(stdin, stdout)) {
2598
+ return { ok: false, reason: "non_interactive" };
2599
+ }
2561
2600
  const runnerPath = writeWindowsSetupRunner({
2562
2601
  binaryPath,
2563
2602
  args,
@@ -2584,6 +2623,7 @@ function launchSetupWizard({
2584
2623
  async function runPostInstallSetup({ binaryPath, logger, env, skipDaemon, distBaseDir } = {}) {
2585
2624
  const log = logger || console;
2586
2625
  const effectiveEnv = env || process.env;
2626
+ const isNpm = isNpmLifecycle(effectiveEnv);
2587
2627
  const distCandidates = resolveDistBaseCandidates({ env: effectiveEnv });
2588
2628
  const resolvedDistBaseDir = distBaseDir || resolveDistBaseDir({ env: effectiveEnv, fsModule: fs });
2589
2629
  let allowDaemon = !shouldSkipDaemonSideEffects({ env: effectiveEnv, skipDaemon });
@@ -2665,14 +2705,16 @@ async function runPostInstallSetup({ binaryPath, logger, env, skipDaemon, distBa
2665
2705
  }
2666
2706
  }
2667
2707
  }
2668
- let startupOk = reuseExisting;
2669
- if (allowDaemon && !reuseExisting) {
2708
+ let startupOk = false;
2709
+ if (allowDaemon) {
2710
+ const allowStartNow = !(process.platform === "win32" && isNpm);
2670
2711
  const result = await startDaemonWithHealthCheck({
2671
2712
  binaryPath: startupBinaries.binaryPath,
2672
2713
  port,
2673
2714
  host: DEFAULT_HOST,
2674
2715
  logger: log,
2675
- distBaseDir: resolvedDistBaseDir
2716
+ distBaseDir: resolvedDistBaseDir,
2717
+ startNow: !reuseExisting && allowStartNow
2676
2718
  });
2677
2719
  if (!result.ok) {
2678
2720
  log.warn?.(`[docdex] daemon failed to start on ${DEFAULT_HOST}:${port}.`);
@@ -2717,7 +2759,7 @@ async function runPostInstallSetup({ binaryPath, logger, env, skipDaemon, distBa
2717
2759
  clearStartupFailure();
2718
2760
  }
2719
2761
  const skipExplicit = shouldSkipSetup(effectiveEnv);
2720
- const skipWizard = isNpmLifecycle(effectiveEnv) || skipExplicit;
2762
+ const skipWizard = isNpm || skipExplicit;
2721
2763
  const setupLaunch = skipWizard
2722
2764
  ? { ok: false, reason: skipExplicit ? "skipped" : "npm_lifecycle" }
2723
2765
  : launchSetupWizard({
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "docdex",
3
- "version": "0.2.39",
3
+ "version": "0.2.41",
4
4
  "mcpName": "io.github.bekirdag/docdex",
5
5
  "description": "Local-first documentation and code indexer with HTTP/MCP search, AST, and agent memory.",
6
6
  "bin": {