docdex 0.2.22 → 0.2.24

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,17 +1,18 @@
1
1
  # Changelog
2
2
 
3
- ## 0.2.22
3
+ ## Unreleased
4
+ - Remove legacy stdio MCP (`docdexd mcp` / `docdex-mcp-server`); MCP is served only over HTTP/SSE.
5
+
6
+ ## 0.2.23
4
7
  - Add Smithery session config schema metadata (titles/descriptions, defaults, example config) for local MCP sessions.
5
8
  - Enrich MCP tools with titles, descriptions, parameter descriptions, and annotations to improve Smithery scoring.
6
9
  - Expose MCP prompts and resources (with titles/mime types/annotations) for onboarding, incident triage, and refactor planning.
10
+ - Switch web scraping to Chromium-only installs under `~/.docdex/state/bin/chromium/` and remove legacy browser tooling.
7
11
 
8
12
  ## 0.2.21
9
13
  - Prompt for npm updates at CLI start (TTY-only, opt-out via `DOCDEX_UPDATE_CHECK=0`).
10
- - Export bundled Playwright fetcher for daemon startup (launchd/systemd/schtasks + immediate spawn).
11
- - Pass `DOCDEX_PLAYWRIGHT_FETCHER` in the npm wrapper when launching the daemon.
12
14
 
13
15
  ## 0.2.19
14
- - Playwright issue fix
15
16
  - Agents md adding command manually
16
17
  - Agents md append repeat fix
17
18
 
@@ -38,7 +39,7 @@
38
39
  - Publish npm wrapper with the latest MCP-compliant binary.
39
40
 
40
41
  ## 0.1.5
41
- - Publish the MCP-enabled CLI wrapper (use `docdex mcp` for MCP clients) and align docs with the new stdio mode.
42
+ - Publish the MCP-enabled CLI wrapper and align docs with MCP mode.
42
43
  - Keep npm version in sync with the MCP release for binary downloads.
43
44
 
44
45
  ## 0.1.4
package/README.md CHANGED
@@ -1,5 +1,4 @@
1
1
  [![Website](https://img.shields.io/badge/website-docdex.org-blue)](https://docdex.org)
2
- [![smithery badge](https://smithery.ai/badge/bekirdag/docdex)](https://smithery.ai/server/bekirdag/docdex)
3
2
  ![GitHub Actions Workflow Status](https://img.shields.io/github/actions/workflow/status/bekirdag/docdex/release.yml?branch=main)
4
3
  ![GitHub License](https://img.shields.io/github/license/bekirdag/docdex)
5
4
  ![GitHub Release](https://img.shields.io/github/v/release/bekirdag/docdex)
@@ -55,7 +54,7 @@ npm i -g docdex
55
54
 
56
55
  ### 2. Auto-Configuration
57
56
 
58
- If you have any of the following clients installed, Docdex automatically configures them to use the local MCP server:
57
+ If you have any of the following clients installed, Docdex automatically configures them to use the local MCP endpoint (daemon HTTP/SSE):
59
58
 
60
59
  > **Claude Desktop, Cursor, Windsurf, Cline, Roo Code, Continue, VS Code, PearAI, Void, Zed, Codex.**
61
60
 
@@ -79,7 +78,7 @@ docdexd index --repo /path/to/my-project
79
78
  Start the shared server. This handles HTTP requests and MCP connections.
80
79
 
81
80
  ```bash
82
- docdexd daemon --repo /path/to/my-project --host 127.0.0.1 --port 3210
81
+ docdexd daemon --host 127.0.0.1 --port 28491
83
82
 
84
83
  ```
85
84
 
@@ -109,6 +108,8 @@ flowchart LR
109
108
 
110
109
  ```
111
110
 
111
+ Use the daemon HTTP/SSE endpoint.
112
+
112
113
  ### Manual Configuration
113
114
 
114
115
  If you need to configure your client manually:
@@ -119,7 +120,7 @@ If you need to configure your client manually:
119
120
  {
120
121
  "mcpServers": {
121
122
  "docdex": {
122
- "url": "http://localhost:3210/sse"
123
+ "url": "http://localhost:28491/v1/mcp/sse"
123
124
  }
124
125
  }
125
126
  }
@@ -130,7 +131,7 @@ If you need to configure your client manually:
130
131
 
131
132
  ```toml
132
133
  [mcp_servers]
133
- docdex = { url = "http://localhost:3210/v1/mcp" }
134
+ docdex = { url = "http://localhost:28491/v1/mcp" }
134
135
 
135
136
  ```
136
137
 
@@ -144,10 +145,10 @@ Don't just find the string "addressGenerator"; find the **definition** and what
144
145
 
145
146
  ```bash
146
147
  # Find definition
147
- curl "http://127.0.0.1:3210/v1/ast?name=addressGenerator&pathPrefix=src"
148
+ curl "http://127.0.0.1:28491/v1/ast?name=addressGenerator&pathPrefix=src"
148
149
 
149
150
  # Track downstream impact (what breaks if I change this?)
150
- curl "http://127.0.0.1:3210/v1/graph/impact?file=src/app.ts&maxDepth=3"
151
+ curl "http://127.0.0.1:28491/v1/graph/impact?file=src/app.ts&maxDepth=3"
151
152
 
152
153
  ```
153
154
 
@@ -182,7 +183,7 @@ Docdex uses Ollama for embeddings and optional local chat.
182
183
  * **Manual:** Ensure `nomic-embed-text` is pulled in Ollama (`ollama pull nomic-embed-text`).
183
184
  * **Custom URL:**
184
185
  ```bash
185
- DOCDEX_OLLAMA_BASE_URL=http://127.0.0.1:11434 docdexd daemon ...
186
+ DOCDEX_OLLAMA_BASE_URL=http://127.0.0.1:11434 docdexd daemon --host 127.0.0.1 --port 28491
186
187
 
187
188
  ```
188
189
 
@@ -196,23 +197,27 @@ Docdex runs as a local daemon serving:
196
197
 
197
198
  * **CLI Commands:** `docdexd chat`
198
199
  * **HTTP API:** `/search`, `/v1/ast`, `/v1/graph/impact`
199
- * **MCP Endpoints:** `/v1/mcp` and `/sse`
200
+ * **MCP Endpoints:** `/v1/mcp` and `/v1/mcp/sse`
200
201
 
201
202
  ### Multi-Repo Setup
202
203
 
203
204
  Run a single daemon and mount additional repos on demand.
204
205
 
205
206
  ```bash
206
- docdexd daemon --repo /path/to/repo-a --port 3210
207
+ docdexd daemon --port 28491
208
+
209
+ # Mount repos and capture repo_id values
210
+ curl -X POST "http://127.0.0.1:28491/v1/initialize" \
211
+ -H "Content-Type: application/json" \
212
+ -d '{"rootUri":"file:///path/to/repo-a"}'
207
213
 
208
- # Mount another repo and capture its repo_id
209
- curl -X POST "http://127.0.0.1:3210/v1/initialize" \
214
+ curl -X POST "http://127.0.0.1:28491/v1/initialize" \
210
215
  -H "Content-Type: application/json" \
211
216
  -d '{"rootUri":"file:///path/to/repo-b"}'
212
217
  ```
213
218
 
214
219
  Notes:
215
- - When more than one repo is mounted, include `x-docdex-repo-id: <sha256>` on HTTP requests.
220
+ - When more than one repo is mounted (or the daemon starts without a default repo), include `x-docdex-repo-id: <sha256>` on HTTP requests.
216
221
  - MCP sessions bind to the repo provided in `initialize.rootUri` and reuse that repo automatically.
217
222
 
218
223
  ### Security
@@ -225,7 +230,6 @@ Notes:
225
230
 
226
231
  ## 📚 Learn More
227
232
 
228
- * **Smithery:** [View on Smithery.ai](https://smithery.ai/server/@bekirdag/docdex)
229
233
  * **Detailed Usage:** `docs/usage.md`
230
234
  * **API Reference:** `docs/http_api.md`
231
235
  * **MCP Specs:** `docs/mcp/errors.md`
package/assets/agents.md CHANGED
@@ -1,3 +1,12 @@
1
+ ## Priority Directives (Mandatory)
2
+
3
+ - Always use both memory lobes early: docdex_get_profile + docdex_memory_recall; save new facts via docdex_memory_save and new preferences via docdex_save_preference.
4
+ - Maintain agent-specific profile memory: use your agent_id with docdex_get_profile/docdex_save_preference; store generic self-knowledge (role, tooling, preferences).
5
+ - Use impact analysis for every code change: call HTTP /v1/graph/impact?file=... and account for inbound/outbound deps; if unavailable, state it and proceed cautiously.
6
+ - Apply DAG reasoning for planning: prefer dependency graph facts (impact results and /v1/dag/export) to choose the right change order and scope.
7
+ - Use Docdex tools intentionally: docdex_search/symbols/ast for repo truth; docdex_stats/files/repo_inspect/index for index health.
8
+ - When you do not know something, run docdex_web_research (force_web=true). Web research is encouraged by default for non-repo facts and external APIs.
9
+
1
10
  # Docdex Agent Usage Instructions
2
11
 
3
12
  > Context for AI Agents: Docdex is your local-first Dual-Lobe Memory and Code Intelligence daemon. Unlike simple vector stores, it provides structural understanding of code (AST/Graph), persistent behavioral profiles (Agent Memory), and gated web enrichment, all strictly scoped to the local machine.
@@ -33,6 +42,7 @@ Docdex v2.1 introduces a strict separation between "facts" and "preferences." Us
33
42
  - Content: Your persona, user preferences, coding style, tooling constraints.
34
43
  - Example: "Always use Zod for validation," or "User prefers strict TypeScript types."
35
44
  - Tools: docdex_save_preference, docdex_get_profile
45
+ - Agent-specific: Each agent should use its own agent_id and store generic self-knowledge (role, tooling, preferences).
36
46
  - Usage: Use this to "learn" from corrections. If a user corrects your style, save it here so you do not repeat the mistake in a different repo.
37
47
 
38
48
  ## Tool Capabilities (MCP & HTTP)
@@ -66,6 +76,17 @@ Precision tools for structural analysis. Do not rely on text search for definiti
66
76
  | docdex_save_preference | Store a global user preference (Style, Tooling, Constraint). |
67
77
  | docdex_get_profile | Retrieve global preferences. |
68
78
 
79
+ ## Quick Tool Map (Often Missed)
80
+
81
+ - docdex_files: List indexed docs with rel_path/doc_id/token_estimate; use to verify indexing coverage.
82
+ - docdex_stats: Show index size, state dir, and last update time.
83
+ - docdex_repo_inspect: Confirm normalized repo root and repo identity mapping.
84
+ - docdex_index: Reindex the full repo or ingest specific files when stale.
85
+ - docdex_search diff: Limit search to working tree, staged, or ref ranges; filter by paths.
86
+ - docdex_web_research knobs: force_web, skip_local_search, repo_only, no_cache, web_limit, llm_filter_local_results, llm_model.
87
+ - HTTP /v1/initialize: Bind a default repo root for MCP when clients omit project_root.
88
+ - HTTP /v1/dag/export: Export the dependency graph for external analysis.
89
+
69
90
  ## Interaction Patterns
70
91
 
71
92
  ### 1. Reasoning Workflow
@@ -93,6 +114,21 @@ If the user asks: "Safe to delete getUser?"
93
114
  - Action: Call GET /v1/graph/impact?file=src/user.ts
94
115
  - Output: Analyze the inbound edges. If the list is not empty, it is unsafe.
95
116
 
117
+ ### 4. Non-Repo Real-World Queries (Web First)
118
+
119
+ If the user asks a non-repo, real-world question (weather, news, general facts), immediately call docdex_web_research with force_web=true.
120
+ - Resolve relative dates ("yesterday", "last week") using system time by default.
121
+ - Do not run docdex_search unless the user explicitly wants repo-local context.
122
+ - Assume web access is allowed unless the user forbids it; if the web call fails, report the failure and ask for a source or permission.
123
+
124
+ ### 5. Failure Handling (Missing Results or Errors)
125
+
126
+ - Ensure project_root or repo_path is set, or call /v1/initialize to bind a default root.
127
+ - Use docdex_repo_inspect to confirm repo identity and normalized root.
128
+ - Use docdex_stats and docdex_files to check whether the index exists and contains files.
129
+ - Reindex with docdex_index (or docdexd index) if the index is stale or empty.
130
+ - Add a repo-local .docdexignore for large generated artifacts or local caches when indexing is slow.
131
+
96
132
  ## Operational Context
97
133
 
98
134
  ### Repository Identification
package/bin/docdex.js CHANGED
@@ -29,6 +29,12 @@ function printLines(lines, { stderr } = {}) {
29
29
  }
30
30
  }
31
31
 
32
+ function envBool(value) {
33
+ if (!value) return false;
34
+ const normalized = String(value).trim().toLowerCase();
35
+ return ["1", "true", "t", "yes", "y", "on"].includes(normalized);
36
+ }
37
+
32
38
  function readInstallMetadata({ fsModule, pathModule, basePath }) {
33
39
  if (!fsModule || typeof fsModule.readFileSync !== "function") return null;
34
40
  const metadataPath = pathModule.join(basePath, "docdexd-install.json");
@@ -178,10 +184,6 @@ async function run() {
178
184
  basePath,
179
185
  process.platform === "win32" ? "docdexd.exe" : "docdexd"
180
186
  );
181
- const mcpBinaryPath = path.join(
182
- basePath,
183
- process.platform === "win32" ? "docdex-mcp-server.exe" : "docdex-mcp-server"
184
- );
185
187
 
186
188
  if (!fs.existsSync(binaryPath)) {
187
189
  console.error(`[docdex] Missing binary for ${platformKey}. Try reinstalling or set DOCDEX_DOWNLOAD_REPO to a repo with release assets.`);
@@ -202,13 +204,6 @@ async function run() {
202
204
  });
203
205
 
204
206
  const env = { ...process.env };
205
- if (!env.DOCDEX_MCP_SERVER_BIN && fs.existsSync(mcpBinaryPath)) {
206
- env.DOCDEX_MCP_SERVER_BIN = mcpBinaryPath;
207
- }
208
- const fetcherPath = path.join(__dirname, "..", "lib", "playwright_fetch.js");
209
- if (!env.DOCDEX_PLAYWRIGHT_FETCHER && fs.existsSync(fetcherPath)) {
210
- env.DOCDEX_PLAYWRIGHT_FETCHER = fetcherPath;
211
- }
212
207
  const child = spawn(binaryPath, process.argv.slice(2), { stdio: "inherit", env });
213
208
  child.on("exit", (code) => process.exit(code ?? 1));
214
209
  child.on("error", (err) => {
package/lib/install.js CHANGED
@@ -8,7 +8,7 @@ const os = require("node:os");
8
8
  const path = require("node:path");
9
9
  const { pipeline } = require("node:stream/promises");
10
10
  const crypto = require("node:crypto");
11
- const { spawn, spawnSync } = require("node:child_process");
11
+ const { spawn } = require("node:child_process");
12
12
 
13
13
  const pkg = require("../package.json");
14
14
  const {
@@ -41,8 +41,6 @@ const DEFAULT_INTEGRITY_CONFIG = Object.freeze({
41
41
  const LOCAL_FALLBACK_ENV = "DOCDEX_LOCAL_FALLBACK";
42
42
  const LOCAL_BINARY_ENV = "DOCDEX_LOCAL_BINARY";
43
43
  const AGENTS_DOC_FILENAME = "agents.md";
44
- const PLAYWRIGHT_INSTALL_GUARD = "DOCDEX_INTERNAL_PLAYWRIGHT_INSTALL";
45
- const PLAYWRIGHT_SKIP_ENV = "DOCDEX_SKIP_PLAYWRIGHT_DEP_INSTALL";
46
44
 
47
45
  const EXIT_CODE_BY_ERROR_CODE = Object.freeze({
48
46
  DOCDEX_INSTALLER_CONFIG: 2,
@@ -232,60 +230,6 @@ function writeAgentInstructions() {
232
230
  }
233
231
  }
234
232
 
235
- function resolvePlaywrightPackage() {
236
- const baseDir = path.join(__dirname, "..");
237
- try {
238
- return require.resolve("playwright/package.json", { paths: [baseDir] });
239
- } catch {}
240
- try {
241
- return require.resolve("playwright/package.json");
242
- } catch {}
243
- return null;
244
- }
245
-
246
- function resolveNpmCommand() {
247
- const npmExec = process.env.npm_execpath;
248
- if (npmExec) {
249
- return { cmd: process.execPath, args: [npmExec] };
250
- }
251
- const npmCmd = process.platform === "win32" ? "npm.cmd" : "npm";
252
- return { cmd: npmCmd, args: [] };
253
- }
254
-
255
- function ensurePlaywrightDependency({ logger = console } = {}) {
256
- if (process.env[PLAYWRIGHT_SKIP_ENV]) return;
257
- if (process.env[PLAYWRIGHT_INSTALL_GUARD]) return;
258
- if (resolvePlaywrightPackage()) return;
259
-
260
- const rootDir = path.join(__dirname, "..");
261
- const { cmd, args } = resolveNpmCommand();
262
- const installArgs = args.concat([
263
- "install",
264
- "--no-save",
265
- "--ignore-scripts",
266
- "--no-package-lock",
267
- "--no-audit",
268
- "--no-fund",
269
- "playwright"
270
- ]);
271
- const result = spawnSync(cmd, installArgs, {
272
- cwd: rootDir,
273
- stdio: "inherit",
274
- env: {
275
- ...process.env,
276
- [PLAYWRIGHT_INSTALL_GUARD]: "1"
277
- }
278
- });
279
- if (result.error || (typeof result.status === "number" && result.status !== 0)) {
280
- const message = result.error?.message || `npm exit status ${result.status}`;
281
- logger.warn?.(`[docdex] Playwright dependency install failed: ${message}`);
282
- return;
283
- }
284
- if (!resolvePlaywrightPackage()) {
285
- logger.warn?.("[docdex] Playwright dependency still missing after install attempt");
286
- }
287
- }
288
-
289
233
  function selectHttpClient(url) {
290
234
  try {
291
235
  const protocol = new URL(url).protocol;
@@ -577,13 +521,16 @@ function isLocalInstallRequest({ env, pathModule }) {
577
521
  return argv.some((arg) => isLikelyLocalInstallArg(arg, pathModule));
578
522
  }
579
523
 
580
- function shouldPreferLocalInstall({ env, localBinaryPath, pathModule }) {
524
+ function shouldPreferLocalInstall({ env, localBinaryPath, pathModule, localRepoRoot }) {
581
525
  if (!localBinaryPath) return false;
582
526
  if (parseEnvBool(env?.[LOCAL_FALLBACK_ENV]) === false) return false;
583
527
  if (env?.[LOCAL_BINARY_ENV]) return true;
584
- if (!env?.INIT_CWD) return false;
585
528
  if (env?.npm_lifecycle_event !== "postinstall") return false;
586
- return isLocalInstallRequest({ env, pathModule });
529
+ if (isLocalInstallRequest({ env, pathModule })) return true;
530
+ if (!env?.INIT_CWD || !localRepoRoot) return false;
531
+ const initCwd = pathModule.resolve(env.INIT_CWD);
532
+ const repoRoot = pathModule.resolve(localRepoRoot);
533
+ return initCwd === repoRoot || initCwd.startsWith(`${repoRoot}${pathModule.sep}`);
587
534
  }
588
535
 
589
536
  function resolveLocalBinaryCandidate({
@@ -599,35 +546,16 @@ function resolveLocalBinaryCandidate({
599
546
  if (fsModule.existsSync(resolved)) return resolved;
600
547
  }
601
548
  const isWin32 = platform === "win32";
602
- const mcpName = isWin32 ? "docdex-mcp-server.exe" : "docdex-mcp-server";
603
549
  const root = repoRoot || detectLocalRepoRoot({ pathModule, fsModule });
604
550
  if (!root) return null;
605
551
  const binaryName = platform === "win32" ? "docdexd.exe" : "docdexd";
606
552
  const releasePath = pathModule.join(root, "target", "release", binaryName);
607
- if (fsModule.existsSync(releasePath)) {
608
- if (localMcpPresent({ fsModule, pathModule, binaryPath: releasePath, mcpName })) {
609
- return releasePath;
610
- }
611
- }
553
+ if (fsModule.existsSync(releasePath)) return releasePath;
612
554
  const debugPath = pathModule.join(root, "target", "debug", binaryName);
613
- if (fsModule.existsSync(debugPath)) {
614
- if (localMcpPresent({ fsModule, pathModule, binaryPath: debugPath, mcpName })) {
615
- return debugPath;
616
- }
617
- }
555
+ if (fsModule.existsSync(debugPath)) return debugPath;
618
556
  return null;
619
557
  }
620
558
 
621
- function localMcpPresent({ fsModule, pathModule, binaryPath, mcpName }) {
622
- const dir = pathModule.dirname(binaryPath);
623
- const candidates = [
624
- pathModule.join(dir, mcpName),
625
- pathModule.join(pathModule.dirname(dir), "release", mcpName),
626
- pathModule.join(pathModule.dirname(dir), "debug", mcpName)
627
- ];
628
- return candidates.some((candidate) => fsModule.existsSync(candidate));
629
- }
630
-
631
559
  async function installFromLocalBinary({
632
560
  fsModule,
633
561
  pathModule,
@@ -650,22 +578,6 @@ async function installFromLocalBinary({
650
578
  if (!isWin32) {
651
579
  await fsModule.promises.chmod(destPath, 0o755).catch(() => {});
652
580
  }
653
- const mcpName = isWin32 ? "docdex-mcp-server.exe" : "docdex-mcp-server";
654
- const mcpCandidates = [
655
- pathModule.join(pathModule.dirname(binaryPath), mcpName),
656
- pathModule.join(pathModule.dirname(pathModule.dirname(binaryPath)), "release", mcpName),
657
- pathModule.join(pathModule.dirname(pathModule.dirname(binaryPath)), "debug", mcpName)
658
- ];
659
- const mcpSource = mcpCandidates.find((candidate) => fsModule.existsSync(candidate));
660
- if (mcpSource) {
661
- const mcpDest = pathModule.join(distDir, mcpName);
662
- await fsModule.promises.copyFile(mcpSource, mcpDest);
663
- if (!isWin32) {
664
- await fsModule.promises.chmod(mcpDest, 0o755).catch(() => {});
665
- }
666
- } else {
667
- logger?.warn?.(`[docdex] local MCP binary not found; expected near ${binaryPath}`);
668
- }
669
581
  const binarySha256 = await sha256FileFn(destPath);
670
582
  const metadata = {
671
583
  schemaVersion: INSTALL_METADATA_SCHEMA_VERSION,
@@ -718,7 +630,13 @@ async function maybeInstallLocalFallback({
718
630
 
719
631
  const candidate =
720
632
  localBinaryPath ||
721
- resolveLocalBinaryCandidate({ env, platform: process.platform, pathModule, fsModule, repoRoot: localRepoRoot });
633
+ resolveLocalBinaryCandidate({
634
+ env,
635
+ platform: process.platform,
636
+ pathModule,
637
+ fsModule,
638
+ repoRoot: localRepoRoot
639
+ });
722
640
  if (!candidate) return null;
723
641
 
724
642
  return installFromLocalBinary({
@@ -1074,9 +992,6 @@ function decideInstallAction({
1074
992
  integrityResult
1075
993
  }) {
1076
994
  if (!discoveredInstalledState?.binaryPresent) return { outcome: "update", reason: "binary_missing" };
1077
- if (discoveredInstalledState.mcpBinaryPresent === false) {
1078
- return { outcome: "update", reason: "mcp_binary_missing" };
1079
- }
1080
995
 
1081
996
  if (discoveredInstalledState.metadataStatus !== "valid") {
1082
997
  return {
@@ -1111,10 +1026,6 @@ function decideInstallAction({
1111
1026
 
1112
1027
  async function discoverInstalledState({ fsModule, pathModule, distDir, platformKey, isWin32 }) {
1113
1028
  const binaryPath = pathModule.join(distDir, isWin32 ? "docdexd.exe" : "docdexd");
1114
- const mcpBinaryPath = pathModule.join(
1115
- distDir,
1116
- isWin32 ? "docdex-mcp-server.exe" : "docdex-mcp-server"
1117
- );
1118
1029
  const metadataPath = installMetadataPath(distDir, pathModule);
1119
1030
 
1120
1031
  const existsSync = typeof fsModule?.existsSync === "function" ? fsModule.existsSync.bind(fsModule) : null;
@@ -1123,7 +1034,6 @@ async function discoverInstalledState({ fsModule, pathModule, distDir, platformK
1123
1034
  binaryPath,
1124
1035
  metadataPath,
1125
1036
  binaryPresent: false,
1126
- mcpBinaryPresent: false,
1127
1037
  installedVersion: null,
1128
1038
  metadata: null,
1129
1039
  metadataStatus: "unavailable",
@@ -1137,7 +1047,6 @@ async function discoverInstalledState({ fsModule, pathModule, distDir, platformK
1137
1047
  binaryPath,
1138
1048
  metadataPath,
1139
1049
  binaryPresent: false,
1140
- mcpBinaryPresent: false,
1141
1050
  installedVersion: null,
1142
1051
  metadata: null,
1143
1052
  metadataStatus: "missing",
@@ -1146,13 +1055,11 @@ async function discoverInstalledState({ fsModule, pathModule, distDir, platformK
1146
1055
  };
1147
1056
  }
1148
1057
 
1149
- const mcpBinaryPresent = existsSync(mcpBinaryPath);
1150
1058
  const metaResult = await readJsonFileIfPossible({ fsModule, filePath: metadataPath });
1151
1059
  const meta = metaResult.value;
1152
1060
  if (!isValidInstallMetadata(meta)) {
1153
1061
  return {
1154
1062
  binaryPath,
1155
- mcpBinaryPresent,
1156
1063
  metadataPath,
1157
1064
  binaryPresent: true,
1158
1065
  installedVersion: typeof meta?.version === "string" ? meta.version : null,
@@ -1175,7 +1082,6 @@ async function discoverInstalledState({ fsModule, pathModule, distDir, platformK
1175
1082
 
1176
1083
  return {
1177
1084
  binaryPath,
1178
- mcpBinaryPresent,
1179
1085
  metadataPath,
1180
1086
  binaryPresent: true,
1181
1087
  installedVersion: meta.version,
@@ -1249,7 +1155,6 @@ async function determineLocalInstallerOutcome({
1249
1155
 
1250
1156
  const shouldVerifyIntegrity =
1251
1157
  discoveredInstalledState.binaryPresent &&
1252
- discoveredInstalledState.mcpBinaryPresent !== false &&
1253
1158
  !discoveredInstalledState.platformMismatch &&
1254
1159
  discoveredInstalledState.installedVersion === expectedVersion &&
1255
1160
  (normalizeSha256Hex(expectedBinarySha256) || discoveredInstalledState.metadataStatus === "valid");
@@ -1805,6 +1710,7 @@ async function runInstaller(options) {
1805
1710
  });
1806
1711
 
1807
1712
  const priorRunnable = existsSync ? existsSync(local.binaryPath) : false;
1713
+ const preferLocal = shouldPreferLocalInstall({ env, localBinaryPath, pathModule, localRepoRoot });
1808
1714
 
1809
1715
  const forceLocalBinary = Boolean(env?.[LOCAL_BINARY_ENV]);
1810
1716
  if (forceLocalBinary && localBinaryPath) {
@@ -1825,6 +1731,24 @@ async function runInstaller(options) {
1825
1731
  return localInstall;
1826
1732
  }
1827
1733
 
1734
+ if (preferLocal) {
1735
+ const localInstall = await installFromLocalBinary({
1736
+ fsModule,
1737
+ pathModule,
1738
+ distDir,
1739
+ binaryPath: localBinaryPath,
1740
+ isWin32,
1741
+ version,
1742
+ platformKey,
1743
+ targetTriple,
1744
+ repoSlug: null,
1745
+ sha256FileFn,
1746
+ writeJsonFileAtomicFn,
1747
+ logger
1748
+ });
1749
+ return localInstall;
1750
+ }
1751
+
1828
1752
  if (local.outcome === "no-op") {
1829
1753
  logger.log("[docdex] Install outcome: no-op");
1830
1754
  await cleanupInstallArtifacts({
@@ -1853,24 +1777,6 @@ async function runInstaller(options) {
1853
1777
  };
1854
1778
  }
1855
1779
 
1856
- if (shouldPreferLocalInstall({ env, localBinaryPath, pathModule })) {
1857
- const localInstall = await installFromLocalBinary({
1858
- fsModule,
1859
- pathModule,
1860
- distDir,
1861
- binaryPath: localBinaryPath,
1862
- isWin32,
1863
- version,
1864
- platformKey,
1865
- targetTriple,
1866
- repoSlug: null,
1867
- sha256FileFn,
1868
- writeJsonFileAtomicFn,
1869
- logger
1870
- });
1871
- return localInstall;
1872
- }
1873
-
1874
1780
  let repoSlug = null;
1875
1781
  let archive;
1876
1782
  let expectedSha256;
@@ -2189,18 +2095,14 @@ async function main() {
2189
2095
  try {
2190
2096
  await runPostInstallSetup({ binaryPath: result?.binaryPath });
2191
2097
  } catch (err) {
2192
- console.warn(`[docdex] postinstall setup skipped: ${err?.message || err}`);
2098
+ console.warn(`[docdex] postinstall setup failed: ${err?.message || err}`);
2099
+ throw err;
2193
2100
  }
2194
2101
  try {
2195
2102
  writeAgentInstructions();
2196
2103
  } catch (err) {
2197
2104
  console.warn(`[docdex] agent instructions skipped: ${err?.message || err}`);
2198
2105
  }
2199
- try {
2200
- ensurePlaywrightDependency();
2201
- } catch (err) {
2202
- console.warn(`[docdex] playwright dependency check skipped: ${err?.message || err}`);
2203
- }
2204
2106
  printPostInstallBanner();
2205
2107
  }
2206
2108
 
@@ -2230,7 +2132,7 @@ function printPostInstallBanner() {
2230
2132
  "\x1b[32mDocdex installed successfully!\x1b[0m",
2231
2133
  "\x1b[41m\x1b[97m IMPORTANT \x1b[0m \x1b[33mNext step:\x1b[0m run \x1b[32m`docdex setup`\x1b[0m to complete the installation.",
2232
2134
  "\x1b[33mSetup:\x1b[0m configures Ollama/models + browser.",
2233
- "\x1b[34mTip:\x1b[0m after setup, start the daemon with \x1b[36m`docdexd serve --repo <path>`\x1b[0m"
2135
+ "\x1b[34mTip:\x1b[0m after setup, the daemon should auto-start; if not, run \x1b[36m`docdexd daemon`\x1b[0m"
2234
2136
  ];
2235
2137
  width = Math.max(72, content.reduce((max, line) => Math.max(max, stripAnsi(line).length), 0));
2236
2138
  const padLine = (text) => {