docdex 0.1.10 → 0.2.1
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 +3 -0
- package/README.md +103 -65
- package/bin/docdex.js +145 -5
- package/lib/daemon_version.js +80 -0
- package/lib/install.js +1940 -28
- package/lib/installer_logging.js +134 -0
- package/lib/platform.js +275 -20
- package/lib/platform_matrix.js +127 -0
- package/lib/postinstall_setup.js +885 -0
- package/lib/release_manifest.js +226 -0
- package/lib/release_signing.js +93 -0
- package/package.json +4 -2
package/CHANGELOG.md
CHANGED
package/README.md
CHANGED
|
@@ -1,34 +1,40 @@
|
|
|
1
1
|
# Docdex
|
|
2
2
|
|
|
3
|
-
Docdex is a
|
|
3
|
+
Docdex is a local-first docs + code indexer/search daemon. It runs per repo, keeps an on-disk index, and serves search, chat, and code intelligence over HTTP, CLI, or MCP—no external services or uploads required.
|
|
4
4
|
|
|
5
5
|
## Install via npm
|
|
6
6
|
- Requires Node.js >= 18.
|
|
7
7
|
- Install: `npm i -g docdex` (or run `npx docdex --version` to verify).
|
|
8
8
|
- Commands: `docdex` (alias `docdexd`) downloads the right binary for your platform from the matching GitHub release.
|
|
9
|
-
- Supported
|
|
9
|
+
- Supported published binaries: macOS (arm64, x64), Linux glibc (arm64, x64), Linux musl (x64), Windows (x64); installer fetches the matching platform release asset.
|
|
10
|
+
- Supported platforms + manual source build + troubleshooting: `docs/ops/installer_supported_platforms.md` (in the repo).
|
|
11
|
+
- Release manifest schema (assets + checksums + fallback rules): `docs/contracts/release_manifest_schema_v1.md`.
|
|
10
12
|
- If you publish from a fork, set `DOCDEX_DOWNLOAD_REPO=<owner/repo>` before installing so the downloader fetches your release assets.
|
|
13
|
+
- If you mirror release assets locally, set `DOCDEX_DOWNLOAD_BASE=http://host/path` to point the installer at the mirror.
|
|
11
14
|
- Distribution: binaries stay in GitHub Releases (small npm package); postinstall fetches `docdexd-<platform>.tar.gz` matching the npm version.
|
|
15
|
+
- Platform diagnostics (no download): `docdex doctor` (or `docdex diagnostics`) prints detected OS/arch(/libc), whether supported, and the expected Rust target triple + release asset naming pattern.
|
|
12
16
|
- Publishing uses npm Trusted Publishing (OIDC) — no NPM token needed; see `.github/workflows/release.yml`.
|
|
17
|
+
- Postinstall prompts: if Ollama is missing, the installer asks to install Ollama and `nomic-embed-text`. If Ollama is available, it prompts to pick a default chat model and can install `phi3.5:3.8b` (~2.2 GB) while showing free disk space. Skip with `DOCDEX_OLLAMA_INSTALL=0` or `DOCDEX_OLLAMA_MODEL_PROMPT=0`; force with `DOCDEX_OLLAMA_INSTALL=1` or `DOCDEX_OLLAMA_MODEL=<model>`; preselect with `DOCDEX_OLLAMA_DEFAULT_MODEL`.
|
|
13
18
|
|
|
14
19
|
## Features at a glance
|
|
15
|
-
- Per-repo
|
|
16
|
-
- HTTP API (`/search`, `/snippet`, `/
|
|
17
|
-
-
|
|
18
|
-
-
|
|
19
|
-
-
|
|
20
|
-
-
|
|
20
|
+
- Per-repo indexing of docs + common source code extensions (tantivy-backed; no external services).
|
|
21
|
+
- HTTP API (`/search`, `/snippet`, `/v1/chat/completions`, `/v1/symbols`, `/v1/ast`, `/v1/graph/impact`) and CLI (`chat`, `ingest`, `symbols-status`, `impact-diagnostics`) share the same state.
|
|
22
|
+
- Memory + DAG endpoints (`/v1/memory/*`, `/v1/dag/export`) enabled by default (Ollama embeddings).
|
|
23
|
+
- Optional web fallback with strict gating (disabled by default; enable with `DOCDEX_WEB_ENABLED=1`).
|
|
24
|
+
- MCP tooling for agent integrations: search/open/files/index/stats plus web research, symbols/AST/impact, memory, and repo inspect.
|
|
25
|
+
- Secure defaults: loopback bind, TLS enforcement on non-loopback, auth token required in secure mode, default rate limiting, request-size limits, strict state-dir perms, audit log, chroot/privilege drop/unshare net (Unix).
|
|
26
|
+
- AI-friendly: `GET /ai-help` returns a JSON playbook (endpoints, CLI commands, limits, best practices).
|
|
21
27
|
|
|
22
28
|
## What it does
|
|
23
|
-
- Indexes
|
|
24
|
-
- Serves the same
|
|
29
|
+
- Indexes docs and source files inside a repo and stores them locally under `~/.docdex/state/repos/<fingerprint>/index`.
|
|
30
|
+
- Serves the same state over HTTP (`/search`, `/snippet`, `/v1/chat/completions`, `/v1/symbols`, `/v1/ast`, `/v1/graph/impact`) and via CLI (`chat`, `ingest`, `symbols-status`, `impact-diagnostics`).
|
|
25
31
|
- Watches files while serving to incrementally ingest changes.
|
|
26
|
-
- Hardened defaults: loopback binding, TLS enforcement on non-loopback, auth token required by default (disable with `--secure-mode=false`), loopback-only allowlist and default rate limit (60 req/min) in secure mode, audit log enabled,
|
|
32
|
+
- Hardened defaults: loopback binding, TLS enforcement on non-loopback, auth token required by default (disable with `--secure-mode=false`), loopback-only allowlist and default rate limit (60 req/min) in secure mode, audit log enabled, strict state-dir perms.
|
|
27
33
|
|
|
28
34
|
## How it works
|
|
29
|
-
1) `docdexd index` builds the on-disk index for your repo
|
|
35
|
+
1) `docdexd index` builds the on-disk index for your repo.
|
|
30
36
|
2) `docdexd serve` loads that index, starts a file watcher for incremental updates, and exposes the HTTP API.
|
|
31
|
-
3) HTTP clients or the CLI (`docdexd
|
|
37
|
+
3) HTTP clients or the CLI (`docdexd chat`) read from the same state; `ingest` can update a single file without full reindexing.
|
|
32
38
|
4) Optional TLS/auth/rate-limit settings secure remote access; audit logging can record access actions.
|
|
33
39
|
|
|
34
40
|
## Quick start
|
|
@@ -42,37 +48,41 @@ npx docdex --version
|
|
|
42
48
|
docdexd index --repo /path/to/repo
|
|
43
49
|
|
|
44
50
|
# serve HTTP API with live file watching (secure mode requires an auth token)
|
|
45
|
-
docdexd serve --repo /path/to/repo --host 127.0.0.1 --port
|
|
51
|
+
docdexd serve --repo /path/to/repo --host 127.0.0.1 --port 3210 --log info --auth-token <token>
|
|
46
52
|
# for local, token-free use, add --secure-mode=false
|
|
47
|
-
# docdexd serve --repo /path/to/repo --host 127.0.0.1 --port
|
|
53
|
+
# docdexd serve --repo /path/to/repo --host 127.0.0.1 --port 3210 --log info --secure-mode=false
|
|
48
54
|
|
|
49
|
-
# ad-hoc
|
|
50
|
-
docdexd
|
|
55
|
+
# ad-hoc chat via CLI (JSON)
|
|
56
|
+
docdexd chat --repo /path/to/repo --query "otp flow" --limit 5
|
|
51
57
|
```
|
|
52
58
|
|
|
53
59
|
## TL;DR for agents
|
|
54
|
-
- Use Docdex for repo docs: run `docdexd index --repo .` once, then
|
|
60
|
+
- Use Docdex for repo docs + code: run `docdexd index --repo .` once, then start the singleton daemon with `docdexd daemon --repo . --host 127.0.0.1 --port 3210` (shared MCP over `/sse`), or run legacy stdio MCP with `docdexd mcp --repo . --log warn`.
|
|
55
61
|
- Add `.docdex/` to `.gitignore` so indexes aren’t committed.
|
|
56
|
-
-
|
|
62
|
+
- npm install: the installer auto-selects a port (prefers 3000, fallback 3210), updates `~/.docdex/config.toml`, and injects the MCP URL into supported client configs.
|
|
63
|
+
- When MCP-aware, register a server named `docdex` that points to `http://localhost:<port>/sse` (shared MCP) or, for stdio-only clients, runs `docdexd mcp --repo . --log warn --max-results 8`.
|
|
57
64
|
- Prefer summary-first (snippets=false), fetch specific snippets only when needed, keep queries short, and respect token estimates.
|
|
58
65
|
|
|
59
66
|
## Usage cheat sheet
|
|
60
67
|
- Build index: `docdexd index --repo <path>` (add `--exclude-*` to skip paths).
|
|
61
|
-
- Serve with watcher: `docdexd serve --repo <path> --host 127.0.0.1 --port
|
|
68
|
+
- Serve with watcher: `docdexd serve --repo <path> --host 127.0.0.1 --port 3210 --log warn --auth-token <token>` (secure mode also allowlists loopback and rate-limits by default; add `--allow-ip`/`--secure-mode=false`/`--rate-limit-per-min` as needed for remote use).
|
|
62
69
|
- Secure serving: add `--auth-token <token>` (required by default); use TLS with `--tls-cert/--tls-key` or `--certbot-domain <domain>`.
|
|
63
70
|
- Single-file ingest: `docdexd ingest --repo <path> --file docs/new.md` (honors excludes).
|
|
64
|
-
- Query via CLI: `docdexd
|
|
65
|
-
-
|
|
66
|
-
-
|
|
67
|
-
-
|
|
68
|
-
-
|
|
71
|
+
- Query via CLI: `docdexd chat --repo <path> --query "term" --limit 4` (add `--repo-only` to ignore libs index hits).
|
|
72
|
+
- Memory: `docdexd memory-store --repo <path> --text "..."` and `docdexd memory-recall --repo <path> --query "..."`.
|
|
73
|
+
- Web fallback (disabled by default): `DOCDEX_WEB_ENABLED=1 docdexd web-search --query "..."`.
|
|
74
|
+
- Git hygiene: add `.docdex/` to `.gitignore` only if you opt into an in-repo `--state-dir`.
|
|
75
|
+
- Health check: `curl http://127.0.0.1:3210/healthz`.
|
|
76
|
+
- Summary-only search responses: `curl "http://127.0.0.1:3210/search?q=foo&snippets=false"`; fetch snippets only for top hits.
|
|
77
|
+
- Repo-only HTTP search (ignore libs index hits): `curl "http://127.0.0.1:3210/search?q=foo&include_libs=false"`.
|
|
78
|
+
- Token budgets: `curl "http://127.0.0.1:3210/search?q=foo&max_tokens=800"` to drop hits that would exceed your prompt budget; pair with `snippets=false` then fetch 1–2 snippets you keep.
|
|
69
79
|
- Text-only snippets: append `text_only=true` to `/snippet/:doc_id` or start `serve` with `--strip-snippet-html` (or `--disable-snippet-text` to return metadata only).
|
|
70
80
|
- Keep requests compact: defaults enforce `max_query_bytes=4096` and `max_request_bytes=16384`; keep queries short and leave `--max-limit` low (default 8) to avoid oversized responses.
|
|
71
81
|
- Prompt hygiene: in agent prompts, normalize whitespace and include only `rel_path`, `summary`, and trimmed `snippet` (omit `score`/`token_estimate`/`doc_id`).
|
|
72
82
|
- Trim noise early: use `--exclude-dir` and `--exclude-prefix` to keep vendor/build/cache/secrets out of the index so snippets stay relevant and short.
|
|
73
83
|
- Quiet logging for agents: run `docdexd serve --log warn --access-log=false` if you marshal responses elsewhere to cut log overhead.
|
|
74
84
|
- Cache hits client-side: store `doc_id` ↔ `rel_path` ↔ `summary` to avoid repeat snippet calls; fetch snippets only for new doc_ids.
|
|
75
|
-
- Agent help: `curl http://127.0.0.1:
|
|
85
|
+
- Agent help: `curl http://127.0.0.1:3210/ai-help` (requires auth if configured; include `Authorization: Bearer <token>` when you’ve set `--auth-token`). The response includes a short MCP registration recipe.
|
|
76
86
|
|
|
77
87
|
## Versioning
|
|
78
88
|
- Semantic versioning with tagged releases (`vX.Y.Z`). The Rust crate and npm package share the same version.
|
|
@@ -81,13 +91,13 @@ docdexd query --repo /path/to/repo --query "otp flow" --limit 5
|
|
|
81
91
|
- If you build from source, the version comes from `Cargo.toml` in this repo; the npm wrapper uses the matching version to fetch binaries.
|
|
82
92
|
|
|
83
93
|
## Paths and defaults
|
|
84
|
-
- State/index directory:
|
|
85
|
-
- HTTP API: defaults to `127.0.0.1:
|
|
86
|
-
-
|
|
94
|
+
- State/index directory: `~/.docdex/state/repos/<fingerprint>/index` (legacy `.gpt-creator/docdex/index` is reused with a warning). The directory is created with `0700` permissions by default.
|
|
95
|
+
- HTTP API: defaults to `127.0.0.1:3210` when serving.
|
|
96
|
+
- State and logs stay local; no external services are required.
|
|
87
97
|
|
|
88
98
|
## Configuration knobs
|
|
89
99
|
- `--repo <path>`: workspace root to index (defaults to `.`).
|
|
90
|
-
- `--state-dir <path>` / `DOCDEX_STATE_DIR`: override
|
|
100
|
+
- `--state-dir <path>` / `DOCDEX_STATE_DIR`: override state storage path (relative paths resolve under the repo root; absolute paths outside the repo are treated as shared base dirs and scoped to `<state-dir>/repos/<repo_id>/index`).
|
|
91
101
|
- `--exclude-prefix a,b,c` / `DOCDEX_EXCLUDE_PREFIXES`: extra relative prefixes to skip.
|
|
92
102
|
- `--exclude-dir a,b,c` / `DOCDEX_EXCLUDE_DIRS`: extra directory names to skip anywhere in the tree.
|
|
93
103
|
- `--auth-token <token>` / `DOCDEX_AUTH_TOKEN`: bearer token required in secure mode (default); omit only when starting with `--secure-mode=false`.
|
|
@@ -109,6 +119,8 @@ docdexd query --repo /path/to/repo --query "otp flow" --limit 5
|
|
|
109
119
|
- `--audit-disable` / `DOCDEX_AUDIT_DISABLE=true`: disable audit logging entirely.
|
|
110
120
|
- `--strip-snippet-html` / `DOCDEX_STRIP_SNIPPET_HTML=true`: omit `snippet.html` in responses to force text-only snippets (HTML is sanitized by default when present).
|
|
111
121
|
- `--disable-snippet-text` / `DOCDEX_DISABLE_SNIPPET_TEXT=true`: omit snippet text/html in responses entirely (only doc metadata is returned).
|
|
122
|
+
- `--enable-memory <true|false>` / `DOCDEX_ENABLE_MEMORY`: control memory endpoints (enabled by default via config; set `[memory].enabled=false` or `DOCDEX_ENABLE_MEMORY=0` to disable).
|
|
123
|
+
- `DOCDEX_WEB_ENABLED=1` / `DOCDEX_OFFLINE=1`: enable web fallback or force offline mode.
|
|
112
124
|
- `--access-log <true|false>` / `DOCDEX_ACCESS_LOG`: emit minimal structured access logs with query values redacted (default: true).
|
|
113
125
|
- `--run-as-uid` / `DOCDEX_RUN_AS_UID`, `--run-as-gid` / `DOCDEX_RUN_AS_GID`: (Unix) drop privileges to the provided UID/GID after startup prep.
|
|
114
126
|
- `--chroot <path>` / `DOCDEX_CHROOT`: (Unix) chroot into `path` before serving; repo/state paths must exist inside that jail.
|
|
@@ -117,47 +129,69 @@ docdexd query --repo /path/to/repo --query "otp flow" --limit 5
|
|
|
117
129
|
- Secure mode defaults: when `--secure-mode=true` (default), docdex requires an auth token, allows only loopback IPs unless overridden, and applies a 60 req/min rate limit. Set `--secure-mode=false` to opt out for local dev and adjust `--allow-ip`/rate limits as needed.
|
|
118
130
|
|
|
119
131
|
## Indexing rules (see `index/mod.rs`)
|
|
120
|
-
- File types: `.md`, `.markdown`, `.mdx`, `.txt` (extend `DEFAULT_EXTENSIONS` to add more).
|
|
132
|
+
- File types: `.md`, `.markdown`, `.mdx`, `.txt`, `.rs`, `.py`, `.js`, `.jsx`, `.ts`, `.tsx`, `.go` (extend `DEFAULT_EXTENSIONS` to add more).
|
|
121
133
|
- Skipped directories: broad VCS/build/cache/vendor folders across ecosystems (e.g., `.git`, `.hg`, `.svn`, `node_modules`, `.pnpm-store`, `.yarn*`, `.nx`, `.rollup-cache`, `.webpack-cache`, `.tsbuildinfo`, `.next`, `.nuxt`, `.svelte-kit`, `.mypy_cache`, `.ruff_cache`, `.venv`, `target`, `go-build`, `.gradle`, `.mvn`, `pods`, `.dart_tool`, `.android`, `.serverless`, `.vercel`, `.netlify`, `_build`, `_opam`, `.stack-work`, `elm-stuff`, `library`, `intermediate`, `.godot`, etc.; see `DEFAULT_EXCLUDED_DIR_NAMES` for the full list).
|
|
122
134
|
- Skipped relative prefixes: `logs/`, `.docdex/`, `.docdex/logs/`, `.docdex/tmp/`, `.gpt-creator/logs/`, `.gpt-creator/tmp/`, `.mastercoda/logs/`, `.mastercoda/tmp/`, `docker/.data/`, `docker-data/`, `.docker/`.
|
|
123
135
|
- Snippet sizing: summaries ~360 chars (up to 4 segments); snippets ~420 chars.
|
|
124
136
|
|
|
125
137
|
## HTTP API
|
|
126
138
|
- `GET /healthz` — returns `ok`; this endpoint is unauthenticated and not rate-limited (IP allowlist still applies).
|
|
127
|
-
- `GET /search?q=<text>&limit=<n>&snippets=<bool>&max_tokens=<u64>` — returns `{ hits: [...] }` with doc id,
|
|
128
|
-
- `GET /snippet/:doc_id?window=<lines>&q=<query>&text_only=<bool>&max_tokens=<u64>` — returns `{ doc, snippet }` with optional highlighted snippet; falls back to preview when query highlighting is empty (default window: 40 lines).
|
|
129
|
-
- `
|
|
130
|
-
- `
|
|
139
|
+
- `GET /search?q=<text>&limit=<n>&snippets=<bool>&max_tokens=<u64>&include_libs=<bool>` — returns `{ hits: [...] }` with doc id, `rel_path`/`path`, `kind` (`doc`|`code`), summary, snippet, score, token estimate. Optional: `force_web`, `skip_local_search`, `no_cache`, `max_web_results`, `llm_filter_local_results`, `diff_mode`, `diff_base`, `diff_head`, `diff_path`, `repo_id`.
|
|
140
|
+
- `GET /snippet/:doc_id?window=<lines>&q=<query>&text_only=<bool>&max_tokens=<u64>` — returns `{ doc, snippet }` with optional highlighted snippet; falls back to preview when query highlighting is empty (default window: 40 lines).
|
|
141
|
+
- `POST /v1/index/rebuild` — rebuild the repo index.
|
|
142
|
+
- `POST /v1/index/ingest` — ingest a single file.
|
|
143
|
+
- `POST /v1/chat/completions` — OpenAI-compatible chat completion with docdex context.
|
|
144
|
+
- `GET /v1/graph/impact` / `GET /v1/graph/impact/diagnostics` — impact graph edges + unresolved imports.
|
|
145
|
+
- `GET /v1/symbols`, `GET /v1/symbols/status` — symbols per file + parser drift status.
|
|
146
|
+
- `GET /v1/ast`, `GET /v1/ast/search`, `POST /v1/ast/query` — AST queries.
|
|
147
|
+
- `POST /v1/memory/store`, `POST /v1/memory/recall` — memory endpoints (enabled by default).
|
|
148
|
+
- `POST /v1/web/search`, `POST /v1/web/fetch`, `POST /v1/web/cache/flush` — web discovery/fetch (requires `DOCDEX_WEB_ENABLED=1`).
|
|
149
|
+
- `GET /ai-help` — JSON quickstart for agents.
|
|
150
|
+
- `GET /metrics` — Prometheus-style counters/gauges (see `docs/ops/browser_guard.md` in the repo).
|
|
151
|
+
- Repo scoping: include `repo_id` in query/body or the `x-docdex-repo-id` header; mismatches are rejected.
|
|
131
152
|
- If `--auth-token` is set, include `Authorization: Bearer <token>` on HTTP calls (including `/ai-help`).
|
|
132
153
|
|
|
133
154
|
## CLI commands
|
|
134
|
-
- `serve --repo <path> [--host 127.0.0.1] [--port
|
|
155
|
+
- `serve --repo <path> [--host 127.0.0.1] [--port 3210] [--log info]` — start HTTP API with file watching for incremental updates.
|
|
135
156
|
- `index --repo <path>` — rebuild the entire index.
|
|
136
157
|
- `ingest --repo <path> --file <file>` — reindex a single file.
|
|
137
|
-
- `
|
|
138
|
-
- `
|
|
158
|
+
- `chat --repo <path> --query "<text>" [--limit 8] [--repo-only|--web-only] [--max-web-results N]` — run a chat/search query (omit `--query` to enter REPL mode).
|
|
159
|
+
- `web-search --query "<text>"`, `web-fetch --url <url>`, `web-rag --query "<text>"` — web discovery/fetch and web-assisted queries.
|
|
160
|
+
- `memory-store --text "<text>"` / `memory-recall --query "<text>" --top-k 5` — memory store/recall (enabled by default).
|
|
161
|
+
- `symbols-status --repo <path>` — report Tree-sitter parser drift.
|
|
162
|
+
- `impact-diagnostics --repo <path>` — list unresolved dynamic imports.
|
|
163
|
+
- `self-check --repo <path> --terms "foo,bar" [--limit 5]` — scan the index for sensitive terms before enabling access.
|
|
164
|
+
|
|
165
|
+
## Perf checks
|
|
166
|
+
- Repo-only search latency (p95 < 50ms; see `docs/sds/sds.md`): `cargo test --release repo_only_search_p95_under_50ms_with_libs_index_present -- --ignored --nocapture`.
|
|
139
167
|
|
|
140
168
|
## Help and command discovery
|
|
141
169
|
- List all commands/flags: `docdexd --help`.
|
|
142
170
|
- Dump help for every subcommand: `docdexd help-all`.
|
|
143
171
|
- See `serve` options (TLS, auth, rate limits, watcher): `docdexd serve --help`.
|
|
144
172
|
- Indexing options: `docdexd index --help` (exclude paths, custom state dir).
|
|
145
|
-
- Ad-hoc queries: `docdexd
|
|
173
|
+
- Ad-hoc queries: `docdexd chat --help`.
|
|
146
174
|
- Self-check scanner options: `docdexd self-check --help`.
|
|
147
|
-
- Agent help endpoint: `curl http://127.0.0.1:
|
|
175
|
+
- Agent help endpoint: `curl http://127.0.0.1:3210/ai-help` (include `Authorization: Bearer <token>` if `--auth-token` is set) for a JSON listing of endpoints, limits, and best practices.
|
|
148
176
|
- MCP help/registration: `docdexd mcp --help` lists MCP flags; register with your client using `docdexd mcp --repo <repo> --log warn --max-results 8` (Codex CLI shortcut: `codex mcp add docdex -- docdexd mcp --repo <repo> --log warn --max-results 8`).
|
|
149
177
|
- Environment variables mirror the flags (e.g., `DOCDEX_AUTH_TOKEN`, `DOCDEX_TLS_CERT`, `DOCDEX_MAX_LIMIT`).
|
|
150
178
|
- Command overview (same as `docdexd --help`):
|
|
151
179
|
- `serve` — run HTTP API with watcher and security knobs.
|
|
152
180
|
- `index` — build or rebuild the whole index.
|
|
153
181
|
- `ingest` — reindex a single file.
|
|
154
|
-
- `
|
|
182
|
+
- `chat` — run an ad-hoc search, JSON to stdout (omit `--query` for REPL).
|
|
183
|
+
- `web-search` / `web-fetch` / `web-rag` — web discovery and web-assisted queries (requires `DOCDEX_WEB_ENABLED=1`).
|
|
184
|
+
- `memory-store` / `memory-recall` — memory store/recall.
|
|
185
|
+
- `symbols-status` / `impact-diagnostics` — code intelligence status and unresolved imports.
|
|
186
|
+
- `repo` — inspect or reassociate repo identity for shared state dirs.
|
|
187
|
+
- `mcp` / `mcp-add` — MCP server + helper for agent CLIs.
|
|
155
188
|
- `self-check` — scan index for sensitive terms with report.
|
|
156
189
|
- `help-all` — print help for every command/flag in one output.
|
|
157
190
|
|
|
158
191
|
## Troubleshooting
|
|
159
192
|
- Stale index: re-run `docdexd index --repo <path>`.
|
|
160
193
|
- Port conflicts: change `--host/--port`.
|
|
194
|
+
- Installer failures (`npm i -g docdex`): use the printed `DOCDEX_*` error code; see `docs/ops/installer_error_codes.md`.
|
|
161
195
|
|
|
162
196
|
## Security considerations
|
|
163
197
|
- Default bind is `127.0.0.1`; keep it unless you are behind a trusted reverse proxy/firewall. Avoid `--host 0.0.0.0` on untrusted networks.
|
|
@@ -175,7 +209,7 @@ docdexd query --repo /path/to/repo --query "otp flow" --limit 5
|
|
|
175
209
|
allow 192.168.0.0/16;
|
|
176
210
|
deny all;
|
|
177
211
|
location / {
|
|
178
|
-
proxy_pass http://127.0.0.1:
|
|
212
|
+
proxy_pass http://127.0.0.1:3210;
|
|
179
213
|
proxy_set_header Host $host;
|
|
180
214
|
}
|
|
181
215
|
}
|
|
@@ -183,55 +217,59 @@ docdexd query --repo /path/to/repo --query "otp flow" --limit 5
|
|
|
183
217
|
- Trim the corpus: prefer a curated staging directory, or use `--exclude-dir` / `--exclude-prefix` to keep secrets/private paths out before indexing; the watcher will ingest any in-scope file change under `repo`.
|
|
184
218
|
- Mind logs: avoid verbose logging in production if snippets/paths are sensitive; reverse-proxy access logs can also capture query terms and paths.
|
|
185
219
|
- Least privilege: run docdex under a low-privilege user/container and keep the state dir on a path with restricted permissions.
|
|
186
|
-
- Validate before publish: run `docdexd
|
|
220
|
+
- Validate before publish: run `docdexd chat` for sensitive keywords to confirm no hits; store indexes on encrypted disks if required.
|
|
187
221
|
- Optional hardening: require an auth token on the HTTP API (or proxy); enforce TLS when not on localhost (default) or explicitly opt out with `--require-tls=false`/`--insecure` only behind a trusted proxy; enable rate limiting (`--rate-limit-per-min`) and clamp `limit`/request sizes (`--max-limit`, `--max-query-bytes`, `--max-request-bytes`); escape/sanitize snippet HTML if embedding or disable snippets entirely with `--disable-snippet-text`; state dir is created `0700` by default—keep it under an unprivileged user, optionally `--run-as-uid/--run-as-gid`, `--chroot`, or containerize; keep access logging minimal/redacted (`--access-log`), and run `self-check` for sensitive terms before exposing the service; for at-rest confidentiality, place the state dir on encrypted storage or use host-level disk encryption.
|
|
188
222
|
|
|
189
223
|
## Integrating with LLM tools
|
|
190
224
|
Docdex is tool-agnostic. Drop-in recipe for agents/codegen tools:
|
|
191
|
-
- Start once per repo: `docdexd index --repo <repo>` then `docdexd serve --repo <repo> --host 127.0.0.1 --port
|
|
192
|
-
- Configure via env: `DOCDEX_STATE_DIR` (
|
|
193
|
-
- Query over HTTP: `GET /search?q=<text>&limit=<n>` returns `{
|
|
194
|
-
- Or
|
|
225
|
+
- Start once per repo: `docdexd index --repo <repo>` then `docdexd serve --repo <repo> --host 127.0.0.1 --port 3210 --log warn` (or use the CLI directly without serving).
|
|
226
|
+
- Configure via env: `DOCDEX_STATE_DIR` (state location), `DOCDEX_EXCLUDE_PREFIXES`, `DOCDEX_EXCLUDE_DIRS`, `RUST_LOG=docdexd=debug` (optional verbose logs).
|
|
227
|
+
- Query over HTTP: `GET /search?q=<text>&limit=<n>` returns `{hits:[...], top_score, meta}`; `GET /snippet/:doc_id` fetches a focused snippet plus doc metadata.
|
|
228
|
+
- Or chat over HTTP: `POST /v1/chat/completions` (OpenAI-compatible) with a `docdex` object to control gating and repo context.
|
|
229
|
+
- Or query via CLI: `docdexd chat --repo <repo> --query "<text>" --limit 8` (JSON to stdout).
|
|
195
230
|
- Health check: `GET /healthz` should return `ok` before issuing search requests.
|
|
196
231
|
- Inject snippets into prompts:
|
|
197
232
|
```
|
|
198
233
|
"You are building features for this repo. Use the following documentation snippets for context. If a snippet cites a path, keep that path in your response. Snippets:\n<insert docdex snippets here>\nQuestion: <your question>"
|
|
199
234
|
```
|
|
200
235
|
|
|
201
|
-
### MCP (
|
|
202
|
-
Docdex
|
|
203
|
-
-
|
|
236
|
+
### MCP (shared HTTP/SSE + legacy stdio)
|
|
237
|
+
Docdex exposes MCP over the singleton daemon (`/sse`, `/v1/mcp`, `/v1/mcp/message`) so multiple clients can share one service. Legacy stdio MCP (`docdexd mcp`) remains available for local-only tooling. If your MCP client supports resource templates, Docdex advertises a `docdex_file` template (`docdex://{path}`) which delegates to `docdex_open`.
|
|
238
|
+
- Shared MCP: `docdexd daemon --repo /path/to/repo --host 127.0.0.1 --port 3210` then point clients at `http://localhost:3210/sse`.
|
|
239
|
+
- Legacy stdio: `docdexd mcp --repo /path/to/repo --log warn --max-results 8` (alias: `--mcp-max-results 8`).
|
|
204
240
|
- Env override: `DOCDEX_MCP_MAX_RESULTS` clamps `docdex_search` results (min 1).
|
|
205
|
-
- Packaging:
|
|
206
|
-
- Registering with MCP clients: add a server named `docdex` that
|
|
241
|
+
- Packaging: `docdexd mcp` launches the companion `docdex-mcp-server` binary; build it with `cargo build -p docdex-mcp-server` or set `DOCDEX_MCP_SERVER_BIN` to the binary path.
|
|
242
|
+
- Registering with MCP clients (shared HTTP/SSE): add a server named `docdex` that points to `http://localhost:<port>/sse`. Example JSON config snippet:
|
|
207
243
|
```json
|
|
208
244
|
{
|
|
209
245
|
"mcpServers": {
|
|
210
246
|
"docdex": {
|
|
211
|
-
"
|
|
212
|
-
"args": ["mcp", "--repo", ".", "--log", "warn", "--max-results", "8"],
|
|
213
|
-
"env": {}
|
|
247
|
+
"url": "http://localhost:3210/sse"
|
|
214
248
|
}
|
|
215
249
|
}
|
|
216
250
|
}
|
|
217
251
|
```
|
|
218
252
|
- MCP quick add commands (popular agents):
|
|
219
|
-
- Docdex helper: `docdex mcp-add --repo /path/to/repo --log warn --max-results 8` auto-detects supported agents; add `--all` to attempt every known client and print manual steps for UI-only ones, or `--remove` to uninstall.
|
|
220
|
-
- Codex CLI: `codex mcp add docdex -- docdexd mcp --repo /path/to/repo --log warn --max-results 8`.
|
|
221
|
-
- Generic JSON config (Cursor, Continue, Windsurf, Cline, Claude Desktop devtools): add the `mcpServers.docdex` block above to your MCP config file (paths vary by client; most accept the `
|
|
253
|
+
- Docdex helper (stdio): `docdex mcp-add --repo /path/to/repo --log warn --max-results 8` auto-detects supported agents; add `--all` to attempt every known client and print manual steps for UI-only ones, or `--remove` to uninstall.
|
|
254
|
+
- Codex CLI (stdio): `codex mcp add docdex -- docdexd mcp --repo /path/to/repo --log warn --max-results 8`.
|
|
255
|
+
- Generic JSON config (Cursor, Continue, Windsurf, Cline, Claude Desktop devtools): add the `mcpServers.docdex` block above to your MCP config file (paths vary by client; most accept the `url` schema shown).
|
|
222
256
|
- Manual/stdio-only clients: start `docdexd mcp --repo /path/to/repo --log warn --max-results 8` yourself and point the client at that command/binary.
|
|
223
257
|
- Tools exposed (CallToolResult content: result.content[0].text contains JSON):
|
|
224
|
-
- `docdex_search` — args: `{ "query": "<text>", "limit": <int
|
|
225
|
-
- `
|
|
226
|
-
- `
|
|
227
|
-
- `
|
|
228
|
-
- `
|
|
258
|
+
- `docdex_search` — args: `{ "query": "<text>", "limit": <int>, "force_web": <bool>, "diff": {...}, "project_root": "<path>", "repo_path": "<path alias>" }`.
|
|
259
|
+
- `docdex_web_research` — args: `{ "query": "<text>", "limit": <int>, "web_limit": <int>, "force_web": <bool>, "skip_local_search": <bool>, "no_cache": <bool>, "llm_filter_local_results": <bool>, "repo_only": <bool>, "llm_model": "<id>", "llm_agent": "<slug>", "project_root": "<path>", "repo_path": "<path alias>" }`.
|
|
260
|
+
- `docdex_index` — args: `{ "paths": ["relative/or/absolute"], "project_root": "<path>", "repo_path": "<path alias>" }`.
|
|
261
|
+
- `docdex_files` — args: `{ "limit": <int>, "offset": <int>, "project_root": "<path>", "repo_path": "<path alias>" }`.
|
|
262
|
+
- `docdex_open` — args: `{ "path": "<relative file>", "start_line": <int>, "end_line": <int>, "project_root": "<path>", "repo_path": "<path alias>" }`.
|
|
263
|
+
- `docdex_stats` — args: `{ "project_root": "<path>", "repo_path": "<path alias>" }`.
|
|
264
|
+
- `docdex_repo_inspect` — args: `{ "project_root": "<path>", "repo_path": "<path alias>" }`.
|
|
265
|
+
- `docdex_symbols` / `docdex_ast` / `docdex_impact_diagnostics` — code intelligence tools.
|
|
266
|
+
- `docdex_memory_store` / `docdex_memory_recall` — memory tools (enabled by default).
|
|
229
267
|
- Example calls:
|
|
230
268
|
- Initialize: `{"jsonrpc":"2.0","id":1,"method":"initialize","params":{}}`
|
|
231
269
|
- Initialize with workspace root: `{"jsonrpc":"2.0","id":1,"method":"initialize","params":{"workspace_root":"/path/to/repo"}}` (must match the server repo; sets default project_root for later calls)
|
|
232
270
|
- List tools: `{"jsonrpc":"2.0","id":2,"method":"tools/list"}`
|
|
233
271
|
- Reindex: `{"jsonrpc":"2.0","id":3,"method":"tools/call","params":{"name":"docdex_index","arguments":{"paths":[]}}}`
|
|
234
|
-
|
|
272
|
+
- Search: `{"jsonrpc":"2.0","id":4,"method":"tools/call","params":{"name":"docdex_search","arguments":{"query":"payment auth flow","limit":3,"project_root":"/repo"}}}`
|
|
235
273
|
- List files: `{"jsonrpc":"2.0","id":5,"method":"tools/call","params":{"name":"docdex_files","arguments":{"limit":10,"offset":0}}}`
|
|
236
274
|
- Open file: `{"jsonrpc":"2.0","id":6,"method":"tools/call","params":{"name":"docdex_open","arguments":{"path":"docs/readme.md","start_line":1,"end_line":20}}}`
|
|
237
275
|
- Stats: `{"jsonrpc":"2.0","id":7,"method":"tools/call","params":{"name":"docdex_stats","arguments":{}}}`
|
|
@@ -242,6 +280,6 @@ Docdex can run as an MCP tool provider over stdio; it does not replace the HTTP
|
|
|
242
280
|
## HTTPS and Certbot
|
|
243
281
|
- TLS accepts PKCS8, PKCS1/RSA, and SEC1/EC private keys (compatible with Certbot output).
|
|
244
282
|
- Manual cert/key: `docdexd serve --repo <repo> --tls-cert /path/fullchain.pem --tls-key /path/privkey.pem`.
|
|
245
|
-
- Certbot helper: `docdexd serve --repo <repo> --host 0.0.0.0 --port
|
|
283
|
+
- Certbot helper: `docdexd serve --repo <repo> --host 0.0.0.0 --port 3210 --certbot-domain docs.example.com` (uses `/etc/letsencrypt/live/docs.example.com/{fullchain.pem,privkey.pem}`), or pass `--certbot-live-dir /custom/live/dir`.
|
|
246
284
|
- When using Certbot, set a deploy hook to restart/reload docdex after renewals (e.g., `certbot renew --deploy-hook "systemctl restart docdexd.service"` or kill -HUP your process supervisor).
|
|
247
285
|
- If binding to 443 directly, you need privileges; otherwise, keep docdex on 127.0.0.1 and let a reverse proxy terminate TLS.
|
package/bin/docdex.js
CHANGED
|
@@ -5,10 +5,148 @@ const fs = require("node:fs");
|
|
|
5
5
|
const path = require("node:path");
|
|
6
6
|
const { spawn } = require("node:child_process");
|
|
7
7
|
|
|
8
|
-
const {
|
|
8
|
+
const {
|
|
9
|
+
artifactName,
|
|
10
|
+
detectLibcFromRuntime,
|
|
11
|
+
detectPlatformKey,
|
|
12
|
+
targetTripleForPlatformKey,
|
|
13
|
+
assetPatternForPlatformKey,
|
|
14
|
+
UnsupportedPlatformError
|
|
15
|
+
} = require("../lib/platform");
|
|
16
|
+
|
|
17
|
+
function isDoctorCommand(argv) {
|
|
18
|
+
const sub = argv[0];
|
|
19
|
+
return sub === "doctor" || sub === "diagnostics";
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
function printLines(lines, { stderr } = {}) {
|
|
23
|
+
for (const line of lines) {
|
|
24
|
+
if (!line) continue;
|
|
25
|
+
if (stderr) console.error(line);
|
|
26
|
+
else console.log(line);
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
function runDoctor() {
|
|
31
|
+
const platform = process.platform;
|
|
32
|
+
const arch = process.arch;
|
|
33
|
+
|
|
34
|
+
let libc = null;
|
|
35
|
+
if (platform === "linux") {
|
|
36
|
+
try {
|
|
37
|
+
libc = detectLibcFromRuntime();
|
|
38
|
+
} catch (err) {
|
|
39
|
+
printLines(
|
|
40
|
+
[
|
|
41
|
+
"[docdex] doctor failed: could not detect libc",
|
|
42
|
+
`[docdex] Detected platform: ${platform}/${arch}`,
|
|
43
|
+
`[docdex] Error: ${err?.message || String(err)}`
|
|
44
|
+
],
|
|
45
|
+
{ stderr: true }
|
|
46
|
+
);
|
|
47
|
+
process.exit(1);
|
|
48
|
+
return;
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
let report;
|
|
53
|
+
try {
|
|
54
|
+
const platformKey = detectPlatformKey();
|
|
55
|
+
const targetTriple = targetTripleForPlatformKey(platformKey);
|
|
56
|
+
const expectedAssetName = artifactName(platformKey);
|
|
57
|
+
const expectedAssetPattern = assetPatternForPlatformKey(platformKey, { exampleAssetName: expectedAssetName });
|
|
58
|
+
|
|
59
|
+
report = {
|
|
60
|
+
exitCode: 0,
|
|
61
|
+
stderr: false,
|
|
62
|
+
lines: [
|
|
63
|
+
"[docdex] doctor",
|
|
64
|
+
`[docdex] Detected platform: ${platform}/${arch}${libc ? `/${libc}` : ""}`,
|
|
65
|
+
"[docdex] Supported: yes",
|
|
66
|
+
`[docdex] Platform key: ${platformKey}`,
|
|
67
|
+
`[docdex] Expected target triple: ${targetTriple}`,
|
|
68
|
+
`[docdex] Expected release asset: ${expectedAssetName}`,
|
|
69
|
+
`[docdex] Asset naming pattern: ${expectedAssetPattern}`
|
|
70
|
+
]
|
|
71
|
+
};
|
|
72
|
+
} catch (err) {
|
|
73
|
+
if (err instanceof UnsupportedPlatformError) {
|
|
74
|
+
const detected = `${err.details?.platform ?? platform}/${err.details?.arch ?? arch}`;
|
|
75
|
+
const libcSuffix = err.details?.libc ? `/${err.details.libc}` : "";
|
|
76
|
+
const candidatePlatformKey =
|
|
77
|
+
typeof err.details?.candidatePlatformKey === "string" ? err.details.candidatePlatformKey : null;
|
|
78
|
+
const candidateTargetTriple =
|
|
79
|
+
typeof err.details?.candidateTargetTriple === "string" ? err.details.candidateTargetTriple : null;
|
|
80
|
+
const supportedKeys = Array.isArray(err.details?.supportedPlatformKeys) ? err.details.supportedPlatformKeys : [];
|
|
81
|
+
|
|
82
|
+
const candidateAssetName = candidatePlatformKey ? artifactName(candidatePlatformKey) : null;
|
|
83
|
+
const candidateAssetPattern = candidatePlatformKey
|
|
84
|
+
? assetPatternForPlatformKey(candidatePlatformKey, { exampleAssetName: candidateAssetName })
|
|
85
|
+
: null;
|
|
86
|
+
|
|
87
|
+
report = {
|
|
88
|
+
exitCode: err.exitCode || 3,
|
|
89
|
+
stderr: true,
|
|
90
|
+
lines: [
|
|
91
|
+
"[docdex] doctor",
|
|
92
|
+
`[docdex] Detected platform: ${detected}${libcSuffix}`,
|
|
93
|
+
"[docdex] Supported: no",
|
|
94
|
+
`[docdex] error code: ${err.code}`,
|
|
95
|
+
"[docdex] No download/install is attempted for this platform.",
|
|
96
|
+
candidatePlatformKey ? `[docdex] Platform key: ${candidatePlatformKey}` : null,
|
|
97
|
+
candidateTargetTriple ? `[docdex] Target triple: ${candidateTargetTriple}` : null,
|
|
98
|
+
candidateAssetPattern ? `[docdex] Asset naming pattern: ${candidateAssetPattern}` : null,
|
|
99
|
+
supportedKeys.length ? `[docdex] Supported platforms: ${supportedKeys.join(", ")}` : null,
|
|
100
|
+
"[docdex] Next steps: use a supported platform or build from source (Rust)."
|
|
101
|
+
]
|
|
102
|
+
};
|
|
103
|
+
} else {
|
|
104
|
+
report = {
|
|
105
|
+
exitCode: 1,
|
|
106
|
+
stderr: true,
|
|
107
|
+
lines: [
|
|
108
|
+
"[docdex] doctor failed: unexpected error",
|
|
109
|
+
`[docdex] Detected platform: ${platform}/${arch}${libc ? `/${libc}` : ""}`,
|
|
110
|
+
`[docdex] Error: ${err?.message || String(err)}`
|
|
111
|
+
]
|
|
112
|
+
};
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
printLines(report.lines, { stderr: report.stderr });
|
|
117
|
+
process.exit(report.exitCode);
|
|
118
|
+
}
|
|
9
119
|
|
|
10
120
|
function run() {
|
|
11
|
-
const
|
|
121
|
+
const argv = process.argv.slice(2);
|
|
122
|
+
if (isDoctorCommand(argv)) {
|
|
123
|
+
runDoctor();
|
|
124
|
+
return;
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
let platformKey;
|
|
128
|
+
try {
|
|
129
|
+
platformKey = detectPlatformKey();
|
|
130
|
+
} catch (err) {
|
|
131
|
+
if (err instanceof UnsupportedPlatformError) {
|
|
132
|
+
const detected = `${err.details?.platform ?? process.platform}/${err.details?.arch ?? process.arch}`;
|
|
133
|
+
const libc = err.details?.libc ? `/${err.details.libc}` : "";
|
|
134
|
+
console.error(`[docdex] unsupported platform (${detected}${libc})`);
|
|
135
|
+
console.error(`[docdex] error code: ${err.code}`);
|
|
136
|
+
console.error("[docdex] No download/run was attempted for this platform.");
|
|
137
|
+
if (Array.isArray(err.details?.supportedPlatformKeys) && err.details.supportedPlatformKeys.length) {
|
|
138
|
+
console.error(`[docdex] Supported platforms: ${err.details.supportedPlatformKeys.join(", ")}`);
|
|
139
|
+
}
|
|
140
|
+
if (typeof err.details?.candidatePlatformKey === "string") {
|
|
141
|
+
console.error(`[docdex] Asset naming pattern: ${assetPatternForPlatformKey(err.details.candidatePlatformKey)}`);
|
|
142
|
+
}
|
|
143
|
+
console.error("[docdex] Next steps: use a supported platform or build from source (Rust).");
|
|
144
|
+
process.exit(err.exitCode || 3);
|
|
145
|
+
}
|
|
146
|
+
console.error(`[docdex] failed to detect platform: ${err?.message || String(err)}`);
|
|
147
|
+
process.exit(1);
|
|
148
|
+
}
|
|
149
|
+
|
|
12
150
|
const basePath = path.join(__dirname, "..", "dist", platformKey);
|
|
13
151
|
const binaryPath = path.join(
|
|
14
152
|
basePath,
|
|
@@ -16,9 +154,11 @@ function run() {
|
|
|
16
154
|
);
|
|
17
155
|
|
|
18
156
|
if (!fs.existsSync(binaryPath)) {
|
|
19
|
-
console.error(
|
|
20
|
-
|
|
21
|
-
|
|
157
|
+
console.error(`[docdex] Missing binary for ${platformKey}. Try reinstalling or set DOCDEX_DOWNLOAD_REPO to a repo with release assets.`);
|
|
158
|
+
try {
|
|
159
|
+
console.error(`[docdex] Expected target triple: ${targetTripleForPlatformKey(platformKey)}`);
|
|
160
|
+
console.error(`[docdex] Asset naming pattern: ${assetPatternForPlatformKey(platformKey)}`);
|
|
161
|
+
} catch {}
|
|
22
162
|
process.exit(1);
|
|
23
163
|
}
|
|
24
164
|
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
const fs = require("node:fs");
|
|
4
|
+
const { execFile } = require("node:child_process");
|
|
5
|
+
|
|
6
|
+
const DEFAULT_VERSION_PROBE_TIMEOUT_MS = 1500;
|
|
7
|
+
|
|
8
|
+
function parseDocdexdVersion(output) {
|
|
9
|
+
if (output == null) return null;
|
|
10
|
+
const text = String(output);
|
|
11
|
+
const match = text.match(
|
|
12
|
+
/(?:^|[^0-9A-Za-z])v?(\d+\.\d+\.\d+(?:-[0-9A-Za-z.-]+)?(?:\+[0-9A-Za-z.-]+)?)/u
|
|
13
|
+
);
|
|
14
|
+
return match ? match[1] : null;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
function normalizeExecErrorCode(err) {
|
|
18
|
+
if (!err) return null;
|
|
19
|
+
if (typeof err.code === "string" && err.code) return err.code;
|
|
20
|
+
if (typeof err.errno === "string" && err.errno) return err.errno;
|
|
21
|
+
return null;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
async function probeDocdexdVersion({
|
|
25
|
+
binaryPath,
|
|
26
|
+
timeoutMs = DEFAULT_VERSION_PROBE_TIMEOUT_MS,
|
|
27
|
+
execFileFn = execFile,
|
|
28
|
+
fsModule = fs
|
|
29
|
+
} = {}) {
|
|
30
|
+
if (!binaryPath) {
|
|
31
|
+
return { status: "not_installed", version: null, error: "missing_path" };
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
const existsSync = typeof fsModule?.existsSync === "function" ? fsModule.existsSync.bind(fsModule) : null;
|
|
35
|
+
if (!existsSync) {
|
|
36
|
+
return { status: "unknown", version: null, error: "existsSync_unavailable" };
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
if (!existsSync(binaryPath)) {
|
|
40
|
+
return { status: "not_installed", version: null, error: "missing_binary" };
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
return new Promise((resolve) => {
|
|
44
|
+
execFileFn(
|
|
45
|
+
binaryPath,
|
|
46
|
+
["--version"],
|
|
47
|
+
{ timeout: timeoutMs, windowsHide: true },
|
|
48
|
+
(err, stdout, stderr) => {
|
|
49
|
+
if (err) {
|
|
50
|
+
const code = normalizeExecErrorCode(err);
|
|
51
|
+
if (code === "ENOENT" || code === "EACCES") {
|
|
52
|
+
resolve({ status: "not_installed", version: null, error: code.toLowerCase() });
|
|
53
|
+
return;
|
|
54
|
+
}
|
|
55
|
+
if (code === "ETIMEDOUT" || err.killed) {
|
|
56
|
+
resolve({ status: "error", version: null, error: "timeout" });
|
|
57
|
+
return;
|
|
58
|
+
}
|
|
59
|
+
resolve({ status: "error", version: null, error: err.message || String(err) });
|
|
60
|
+
return;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
const combined = [stdout, stderr].filter(Boolean).join("\n");
|
|
64
|
+
const parsed = parseDocdexdVersion(combined);
|
|
65
|
+
if (!parsed) {
|
|
66
|
+
resolve({ status: "unknown", version: null, error: "unparseable_version", output: combined.trim() });
|
|
67
|
+
return;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
resolve({ status: "installed", version: parsed, error: null });
|
|
71
|
+
}
|
|
72
|
+
);
|
|
73
|
+
});
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
module.exports = {
|
|
77
|
+
DEFAULT_VERSION_PROBE_TIMEOUT_MS,
|
|
78
|
+
parseDocdexdVersion,
|
|
79
|
+
probeDocdexdVersion
|
|
80
|
+
};
|