mdshelf 1.1.1 → 1.1.2

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
@@ -27,6 +27,7 @@ Ships as a single static Rust binary with zero runtime dependencies.
27
27
  - Auto-generated index pages for folders that have no `index.md`
28
28
  - Home page listing all mounted sites with page counts
29
29
  - Copy or download any page's Markdown source straight from its header
30
+ - **Search** — keyword and semantic search across every mounted site, with [qmd](https://github.com/tobi/qmd) ranking and results limited to pages the reader may open
30
31
 
31
32
  **Theme**
32
33
  - Bundled `mdshelf-theme` — mobile-first, responsive, light/dark aware
@@ -439,12 +440,69 @@ Keep `mdshelf share list --json` if you want an inventory of what a deletion wou
439
440
  before creating a folder's `index.md`. Serving, checking, exporting, and explaining are
440
441
  strictly read-only.
441
442
 
443
+ ## Search
444
+
445
+ With a `[search]` section, every page gets a search field in its header (`/` or
446
+ <kbd>Cmd</kbd>/<kbd>Ctrl</kbd>+<kbd>K</kbd> opens it). Results update as you type, with the
447
+ matching words highlighted; <kbd>Enter</kbd> opens the selected page, and **All results**
448
+ opens the full results page at `/__search?q=…`. It finds pages both by the words they
449
+ contain and by meaning — "how do we handle incidents" finds an incident response page
450
+ that never uses those words.
451
+
452
+ mdshelf does not rank pages itself. It works with a [qmd](https://github.com/tobi/qmd)
453
+ server (`npm install -g @tobilu/qmd`), and splits the work so that access rules stay
454
+ mdshelf's:
455
+
456
+ 1. **mdshelf writes a corpus.** At startup and after every content change, mdshelf writes
457
+ each published page's body — frontmatter removed, drafts left out — to
458
+ `<corpus>/<collection>/<page path>`. The collection is the site's mount without its
459
+ leading `/` (`/docs/v2` → `docs-v2`). A page whose file does not end in `.md` gets `.md`
460
+ appended, because qmd indexes `*.md`.
461
+ 2. **qmd indexes the corpus** and answers queries over HTTP. It never sees the vault, so
462
+ `allow` and `deny` lists are not searchable text.
463
+ 3. **mdshelf filters what qmd ranks.** Each ranked page is looked up exactly as a page
464
+ request would be, for the reader asking. Pages they may not open and drafts are left
465
+ out, so a query that matches only restricted pages answers exactly like one that
466
+ matches nothing. Titles and snippets come from mdshelf's own copy of the page.
467
+
468
+ ```toml
469
+ [search]
470
+ qmd_url = "http://127.0.0.1:4600" # the qmd server holding this site's index
471
+ corpus = "/var/lib/mdshelf/search" # a directory of its own: mdshelf deletes stale files here
472
+ ```
473
+
474
+ Set up qmd once, with one collection per site, pointing at the corpus:
475
+
476
+ ```bash
477
+ qmd --index mysite collection add /var/lib/mdshelf/search/docs --name docs
478
+ qmd --index mysite embed # downloads the ~300 MB embedding model once
479
+ qmd --index mysite mcp --http --host 127.0.0.1 --port 4600
480
+ ```
481
+
482
+ Keep the index current by running `qmd --index mysite update && qmd --index mysite embed`
483
+ on a timer; both only process what changed. Restart the qmd server after adding a
484
+ collection, since it reads the collection list at startup.
485
+
486
+ mdshelf asks qmd for keyword and vector matches fused by rank, without qmd's language-model
487
+ reranker, so only the embedding model is loaded: about 800 MB of memory per qmd server,
488
+ with queries in tens of milliseconds once warm. The first query after qmd starts loads
489
+ the model and takes several seconds. If qmd is unreachable, the results page says search
490
+ is unavailable rather than showing no matches.
491
+
492
+ Notes:
493
+
494
+ - Run qmd with `CI` unset: qmd refuses to load models when `CI` is set.
495
+ - qmd always skips folders named `node_modules`, `.git`, `.cache`, `vendor`, `dist` and
496
+ `build`, so pages under such folders are not searchable.
497
+ - Share-link readers get no search; anonymous visitors get the sign-in page, as for any
498
+ other path.
499
+
442
500
  ## Theme customisation
443
501
 
444
502
  Override or extend templates by pointing `[theme].directory` at a local directory:
445
503
 
446
- - `layouts/` — `base.html`, `doc.html`, `home.html`, `error.html`, `link.html`
447
- - `partials/` — `header.html`, `sidebar.html`, `toc.html`, `breadcrumbs.html`, `footer.html`
504
+ - `layouts/` — `base.html`, `doc.html`, `home.html`, `error.html`, `link.html`, `search.html`
505
+ - `partials/` — `header.html`, `sidebar.html`, `toc.html`, `breadcrumbs.html`, `footer.html`, `search.html`
448
506
  - `assets/` — static files served from `/__assets/...`
449
507
 
450
508
  ### Theme capabilities
@@ -499,8 +557,12 @@ start. It prints the redirect URI to register with Google.
499
557
  (`~/.config/mdshelf/secret.key`) and the ACME cache resolve to the paths install checked.
500
558
  - **Relative paths are pinned** to the directory you ran install in: `--tls-cert`,
501
559
  `--tls-key` and `--acme-cache` are stored as absolute paths.
502
- - **Windows:** a Windows service cannot be given `HOME`, so `install` refuses `--auth`, and
503
- `--domain` without `--acme-cache`, there.
560
+ - **Paths with spaces** work with launchd, systemd and Windows: install quotes each
561
+ argument for systemd, so `%` and `$` stay literal too.
562
+ - **OpenRC, rc.d and Windows** services are not given environment variables, so `install`
563
+ refuses `--auth`, and `--domain` without `--acme-cache`, there. OpenRC and rc.d also
564
+ split their command on spaces with no way to quote, so install refuses paths or values
565
+ containing spaces or shell characters on those systems.
504
566
 
505
567
  ## Serving on port 80
506
568
 
@@ -23,7 +23,7 @@
23
23
  "hasInstallScript": true,
24
24
  "license": "MIT OR Apache-2.0",
25
25
  "name": "mdshelf",
26
- "version": "1.1.1"
26
+ "version": "1.1.2"
27
27
  },
28
28
  "node_modules/@isaacs/cliui": {
29
29
  "engines": {
@@ -542,5 +542,5 @@
542
542
  }
543
543
  },
544
544
  "requires": true,
545
- "version": "1.1.1"
545
+ "version": "1.1.2"
546
546
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "artifactDownloadUrls": [
3
- "https://github.com/prbski/mdshelf/releases/download/v1.1.1"
3
+ "https://github.com/prbski/mdshelf/releases/download/v1.1.2"
4
4
  ],
5
5
  "author": "Patryk Porebski",
6
6
  "bin": {
@@ -100,7 +100,7 @@
100
100
  "zipExt": ".tar.xz"
101
101
  }
102
102
  },
103
- "version": "1.1.1",
103
+ "version": "1.1.2",
104
104
  "volta": {
105
105
  "node": "18.14.1",
106
106
  "npm": "9.5.0"