md-semantic-search 1.0.2 → 1.0.3

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/README.md CHANGED
@@ -109,6 +109,9 @@ measurements that shaped its defaults.
109
109
 
110
110
  ## Install
111
111
 
112
+ Requires **Node.js ≥ 18**. Native dependencies ship as prebuilt binaries — no
113
+ compiler toolchain needed on any platform.
114
+
112
115
  Run on demand with `npx` (no install):
113
116
 
114
117
  ```bash
@@ -131,6 +134,9 @@ npm install
131
134
  node bin/cli.mjs --help
132
135
  ```
133
136
 
137
+ For headless server deployments there is a Docker path — see
138
+ [docs/deploy.md](./docs/deploy.md).
139
+
134
140
  ---
135
141
 
136
142
  ## Library usage
@@ -177,7 +183,14 @@ indexes and existing library consumers; schema-v3 search uses persisted BM25.
177
183
  mdss index --db /path/to/your/markdown
178
184
  ```
179
185
 
180
- First run downloads the model (~280 MB). The index is written to `<db>/.mdss/`
186
+ First run downloads the model (~280 MB) — and the download itself is
187
+ **visible**: an aggregated bar on stderr shows percent across every file of the
188
+ model directory, live MB/s and ETA (the same bar reappears when `--rerank`
189
+ first pulls the cross-encoder). Under `--json` the bar is replaced by throttled
190
+ `{"type":"download-progress", …}` / `{"type":"download-complete", …}` events
191
+ **on stderr**, so stdout stays machine-readable. A warm cache produces no
192
+ download output at all, and `--offline` behaves exactly as before. The index
193
+ is written to `<db>/.mdss/`
181
194
  by default (override with `--index-dir`). Re-run after editing your notes — it's
182
195
  incremental at two levels: unchanged files are skipped via their md5, and inside
183
196
  a changed file only the sections whose text actually changed are re-embedded
@@ -420,11 +433,76 @@ const { results } = await r.json();
420
433
  ```
421
434
 
422
435
  The API is unauthenticated by default (or secured via `--api-key`) — by default it binds to **loopback only**
423
- (`127.0.0.1`), so other machines on your LAN cannot reach it. Pass `--host
424
- 0.0.0.0` (or `MDSS_HOST`) only if you intend to expose it on a network, and put
425
- a reverse proxy (or at least a firewall rule) in front of it. Request bodies
426
- are capped at 64 KB (oversized → `413`), and malformed JSON gets a clear `400`
427
- instead of being silently swallowed.
436
+ (`127.0.0.1`), so other machines on your LAN cannot reach it. Passing `--host 0.0.0.0` (or `MDSS_HOST`)
437
+ without authentication **refuses to start** — set an API key, or opt in explicitly with `--allow-unsecured`
438
+ (see [Securing remote deployments](#securing-remote-deployments)). Request bodies are capped at 64 KB
439
+ (oversized → `413`), malformed JSON gets a clear `400`, per-client request rate is capped at 60/min with a
440
+ burst of 10 (`429 + Retry-After` above that; tune via `--rate-limit`), and at most `CPU count` searches run
441
+ concurrently with a bounded wait queue (`503` when saturated; tune via `--max-concurrency`). Every response
442
+ carries `X-Content-Type-Options: nosniff` and `Cache-Control: no-store`; the `Host` header is validated
443
+ against a loopback allowlist (`403` otherwise) so DNS-rebinding pages cannot read your notes cross-origin,
444
+ and CORS stays fully off unless you pass `--cors-origin`. `/health` exposes the live counters
445
+ (`in_flight`, `queued`, `rejected_total`).
446
+
447
+ ### Web UI (built-in, `--ui`)
448
+
449
+ By default `mdss serve` also serves a **zero-dependency web UI at `/`** — a keyboard-first search page
450
+ with live debounced results, term highlighting, score bars, tag/type/status filters, dark/light theme
451
+ (follows the system until you toggle it) and an API-key prompt when Bearer auth is enabled (the key is kept
452
+ in `sessionStorage`, same-origin only). It ships as plain HTML/CSS/JS split into three assets so a strict
453
+ CSP applies (`default-src 'none'; script-src 'self'; style-src 'self'; connect-src 'self';
454
+ frame-ancestors 'none'`) — no inline code, no external calls. Use `--no-ui` to restore pure JSON at `/`;
455
+ the JSON contract (`/search`, `/health`, `/help`) never changes. A Playwright smoke test runs weekly via
456
+ the opt-in `ui-smoke` workflow.
457
+
458
+ ### Securing remote deployments
459
+
460
+ `mdss serve` is designed to be private-first: loopback bind by default, refuse-to-start for unauthenticated
461
+ network binds, DNS-rebinding protection, and rate limiting built in. To serve a team over the network safely:
462
+
463
+ 1. **Keep TLS + auth at a reverse proxy** — mdss speaks plain HTTP on loopback; nginx/caddy terminate TLS.
464
+
465
+ ```nginx
466
+ # /etc/nginx/sites-available/mdss — TLS termination + Bearer-auth passthrough
467
+ server {
468
+ listen 443 ssl;
469
+ server_name search.example.com;
470
+ ssl_certificate /etc/letsencrypt/live/search.example.com/fullchain.pem;
471
+ ssl_certificate_key /etc/letsencrypt/live/search.example.com/privkey.pem;
472
+
473
+ location / {
474
+ proxy_pass http://127.0.0.1:8747;
475
+ proxy_set_header Host $host; # must match --allowed-host if not loopback-named
476
+ proxy_set_header X-Forwarded-For $remote_addr;
477
+ }
478
+ }
479
+ ```
480
+
481
+ ```caddyfile
482
+ # Caddyfile — automatic HTTPS
483
+ search.example.com {
484
+ reverse_proxy 127.0.0.1:8747
485
+ }
486
+ ```
487
+
488
+ 2. **Set an API key without leaking it into shell history or CI logs:**
489
+
490
+ ```bash
491
+ echo "$RANDOM_KEY" > /etc/mdss/api-key && chmod 600 /etc/mdss/api-key
492
+ mdss serve --db /srv/wiki --host 127.0.0.1 --api-key-file /etc/mdss/api-key
493
+ # or MDSS_API_KEY_FILE=/etc/mdss/api-key
494
+ ```
495
+
496
+ 3. **If you must expose mdss directly** (no proxy), require the key *and* keep the firewall tight:
497
+
498
+ ```bash
499
+ MDSS_API_KEY=$(openssl rand -hex 32) mdss serve --db /srv/wiki --host 0.0.0.0
500
+ ```
501
+
502
+ The startup banner warns loudly whenever an unsecured non-loopback bind was explicitly allowed.
503
+
504
+ 4. **Browser clients on another origin?** Opt into CORS explicitly, exact origins only:
505
+ `--cors-origin https://intranet.example.com`.
428
506
 
429
507
  ### 4. MCP Server (Claude Desktop, Cursor, Copilot, Antigravity)
430
508
 
@@ -434,6 +512,13 @@ instead of being silently swallowed.
434
512
  mdss mcp --db /path/to/your/markdown
435
513
  ```
436
514
 
515
+ **Streamable HTTP transport (issue #123):** a long-running daemon can expose
516
+ the same tools over HTTP — `mdss serve --db ./notes --mcp` mounts `/mcp`
517
+ (POST JSON-RPC, SSE channel on GET, `Mcp-Session-Id` sessions, protocol
518
+ `2025-03-26`). It sits behind the exact same auth, Host-allowlist and
519
+ rate-limit gates as `/search`, so remote agents inherit the hardening. stdio
520
+ via `mdss mcp` remains the default transport.
521
+
437
522
  #### Available MCP Tools:
438
523
  - **`search_markdown`**: Hybrid semantic vector + BM25 search with optional `path` glob and `tag` filters.
439
524
  - **`get_chunk`**: Retrieve a specific section by file and heading.
@@ -485,6 +570,9 @@ Add to your `claude_desktop_config.json`:
485
570
  | `--k <n>` | Number of results, positive integer (default 6). |
486
571
  | `--json` | Machine-readable output: `index` → build result JSON, `stats` → index stats JSON, `search` → hit list JSON (each hit includes `matches` — the query terms found in the chunk). |
487
572
  | `--semantic` | `search` only: pure vector ranking, skip lexical/RRF fusion (zero lexical tokenization). |
573
+ | `--open [N]` | `search`: open the top (or Nth) hit in your editor at its `startLine` — `$MDSS_EDITOR`/`$VISUAL`/`$EDITOR`, VS Code `--goto`, or the GUI opener. With `--json` on a non-TTY prints the intent instead of launching (issue #110). |
574
+ | `--fix` | `check`: apply safe auto-repairs — stale `.mdss.lock`, SHA-mismatched `vectors.bin` sidecar, corrupt `ivf.json`; rebuilds a corrupt index / recomputes `.hashes.json` when `--db` is available. Never touches source notes (issue #117). |
575
+ | `--dry-run` | `check --fix`: print the planned repairs without mutating anything. |
488
576
  | `--rerank` | Re-rank the candidate pool with a cross-encoder (`Xenova/bge-reranker-base`, ~280 MB model, downloaded on first use). Slower, sharper results — see *Reranking* below. |
489
577
  | `--graph-boost <n>` | Weight for Obsidian wikilink graph ranking and PageRank prior (search; default 0). Incorporates document centrality and 2-hop contextual propagation. |
490
578
  | `--filter <expr>` | Rich boolean filter across frontmatter tags and properties (e.g. `tag:engineering AND status != archived`). |
@@ -492,12 +580,50 @@ Add to your `claude_desktop_config.json`:
492
580
  | `--nprobe <n>` | Number of nearest centroid clusters to probe during ANN search (default 8). |
493
581
  | `--port <n>` | HTTP port for `serve` (default 8747, or `MDSS_PORT`). |
494
582
  | `--host <ip>` | Bind address for `serve` (default `127.0.0.1` — loopback only; use `0.0.0.0` to expose on the LAN, or `MDSS_HOST`). |
583
+ | `--api-key <key>` | `serve`: require Bearer auth on every request (`MDSS_API_KEY`). |
584
+ | `--api-key-file <path>` | `serve`: read the API key from a file instead of a literal value — avoids shell-history / CI-log leaks (`MDSS_API_KEY_FILE`; trailing newline trimmed, POSIX permissions checked). |
585
+ | `--allow-unsecured` | `serve`: explicit opt-in to bind non-loopback **without** auth; without it (and without an API key) such a bind refuses to start (issue #121). |
586
+ | `--allowed-host <h>` | `serve`: extra `Host` header value to accept (repeatable); anything else gets `403` — DNS-rebinding guard (issue #120). |
587
+ | `--cors-origin <o>` | `serve`: allow cross-origin browser access from this exact origin (repeatable); CORS is off unless set (issue #120). |
588
+ | `--rate-limit <n>` | `serve`: max `/search` requests per minute per client, burst 10 → `429 + Retry-After` above it; `0` disables (default 60, `MDSS_RATE_LIMIT`; issue #119). |
589
+ | `--max-concurrency <n>` | `serve`: max concurrent in-flight searches; queue cap 2× → `503` when full; `0` disables (default = CPU count, `MDSS_MAX_CONCURRENCY`; issue #119). |
495
590
  | `--watch` | `serve`: re-index incrementally when files change. |
591
+ | `--watch-debug` | `serve --watch`: verbose trace of polls, FS-error classifications and retries (issue #116). |
592
+ | `--recency <days>` | `search`: time-decay half-life — passages get a `0.5^(age/halfLife)` boost after fusion; age from frontmatter `created`/`updated`, then file mtime; surfaced in `explain` (issue #127). |
593
+ | `--embedder <name>` | Use an external embedding provider (`ollama` or `openai`) instead of local transformers.js, together with `--embedder-model` (+ `--embedder-base-url`, `--embedder-key-file`). The provider id/model/dim are recorded in the index — switching providers invalidates vectors with the usual full re-embed. Local remains the zero-config default and the only fully-offline path (issue #124). |
594
+ | `--no-query-cache` | `search`: skip the persistent query-embedding cache — repeated queries in fresh processes re-embed otherwise (issue #114). |
595
+ | `--no-ui` | `serve`: disable the built-in web UI at `/` — restores JSON API help there (issue #111). |
496
596
  | `--watch-interval <ms>` | `serve --watch`: poll every N ms (default 3000). |
497
597
  | `--watch-delay <ms>` | `serve --watch`: quiet-period debounce before a burst of saves triggers ONE re-index (default 1000; issue #42). |
498
598
  | `--offline` | Never download the model — require a cached one (or `MDSS_OFFLINE=1`). |
499
599
  | `--version` | Print the installed version. |
500
600
 
601
+ ### Shell completions
602
+
603
+ `mdss completions <bash|zsh|fish|powershell>` prints a self-contained completion
604
+ script covering every subcommand and flag, with directory completion for
605
+ `--db` / `--index-dir` / `--cache-dir` / `--vault` and file completion for
606
+ `--output` / `--config` / `--api-key-file`:
607
+
608
+ ```bash
609
+ # bash — current session only:
610
+ eval "$(mdss completions bash)"
611
+ # bash — persistent:
612
+ mdss completions bash > ~/.local/share/bash-completions/completions/mdss
613
+
614
+ # zsh:
615
+ mkdir -p ~/.zfunc && mdss completions zsh > ~/.zfunc/_mdss # fpath before compinit
616
+
617
+ # fish:
618
+ mdss completions fish > ~/.config/fish/completions/mdss.fish
619
+
620
+ # PowerShell — paste into your $PROFILE:
621
+ mdss completions powershell >> $PROFILE
622
+ ```
623
+
624
+ A unit test guards against flag drift: the generated tables must match the
625
+ flags `parseArgs` actually accepts.
626
+
501
627
  ### The base can live outside the project
502
628
 
503
629
  The index does not need write access to your notes if they're read-only — just
@@ -517,6 +643,13 @@ mdss index
517
643
  mdss search "incident runbook for db failover"
518
644
  ```
519
645
 
646
+ **Supported layouts** (hardened by the issue #118 suite): deep trees beyond
647
+ Windows MAX_PATH (>260 chars) work; unicode and spaces in names work;
648
+ `--index-dir` may live on a different drive than `--db`; NTFS junction loops
649
+ terminate the walker (bounded by path length); `\\?\` extended-length roots are
650
+ accepted for `--db`/`--index-dir`; remote UNC shares (`\\server\share`) are
651
+ **rejected** by design (local-only security boundary).
652
+
520
653
  ### Configuration file (`.mdssrc` / `mdss.config.json`)
521
654
 
522
655
  Instead of repeating CLI options on every command, create a `.mdssrc.json` or `mdss.config.json` file.
@@ -650,6 +783,21 @@ is an in-memory dot-product sweep.
650
783
 
651
784
  - **Obsidian**: An official plugin is available in [`integrations/obsidian`](./integrations/obsidian) providing a live semantic search panel directly in your vault connected to `mdss serve`.
652
785
  - **Model Context Protocol (MCP)**: Native stdio JSON-RPC 2.0 server (`mdss mcp`) integrating with Claude Desktop, Cursor, Copilot, Antigravity, and AI agents for local knowledge retrieval.
786
+ - **VS Code / Raycast / Alfred**: see [`integrations/README.md`](./integrations/README.md). Releases are automated — tagging `integrations/vscode/vX.Y.Z` builds and attaches the VSIX to a GitHub Release from source (no binaries are committed; issue #112).
787
+ - **Windows tray icon**: [`integrations/windows/`](./integrations/windows/) — a zero-dependency system-tray companion for `mdss serve` (pure PowerShell + .NET WinForms, built into Windows): state-colored icon, indexing stats on hover (chunks / model / index age), a balloon when your notes get re-indexed. One-command start:
788
+
789
+ ```cmd
790
+ integrations\windows\start-mdss.cmd D:\Notes
791
+ ```
792
+
793
+ ## Contributing
794
+
795
+ Contributions are welcome — bug reports with reproduction steps, docs fixes,
796
+ tests, and features all help. Start with
797
+ [CONTRIBUTING.md](./CONTRIBUTING.md) (dev setup, project tour, conventions)
798
+ and look for issues labeled **`good first issue`** — they are scoped for a
799
+ first-time contributor. By participating you agree to the
800
+ [Code of Conduct](./CODE_OF_CONDUCT.md).
653
801
 
654
802
  ## License
655
803