mdshelf 1.1.0 → 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 +114 -9
- package/npm-shrinkwrap.json +2 -2
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -2,7 +2,9 @@
|
|
|
2
2
|
|
|
3
3
|
Turn any folder of Markdown files into a fast, beautiful, browsable site. Point mdshelf at your OpenClaw, Hermes, GBrain, wikis, notes, docs, etc. — mount each one under its own URL (e.g. `/agent`, `/hermes`, `gbrain`, `/docs`, `/notes`, `/blog`) — and read them anywhere with hot reload, clean typography, and a mobile-first theme out of the box.
|
|
4
4
|
|
|
5
|
-
Pairs beautifully with [Tailscale](https://tailscale.com): one command serves your shelf to every device on your tailnet — phone, iPad, laptop.
|
|
5
|
+
Pairs beautifully with [Tailscale](https://tailscale.com): one command serves your shelf to every device on your tailnet — phone, iPad, laptop. Serving over Tailscale is fully private by default: no port forwarding, no public DNS, nothing exposed to the open internet.
|
|
6
|
+
|
|
7
|
+
Putting a shelf on the open internet? `mdshelf serve --auth google` signs readers in with Google and shows each one only the pages their address is allowed to read — the rules live in each file's frontmatter. Signed-in readers can hand anyone a link to a single page that expires on its own and can be revoked at any time.
|
|
6
8
|
|
|
7
9
|
One command installs it as a native system service (launchd, systemd, or Windows SCM) that runs quietly in the background and survives reboots.
|
|
8
10
|
|
|
@@ -25,6 +27,7 @@ Ships as a single static Rust binary with zero runtime dependencies.
|
|
|
25
27
|
- Auto-generated index pages for folders that have no `index.md`
|
|
26
28
|
- Home page listing all mounted sites with page counts
|
|
27
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
|
|
28
31
|
|
|
29
32
|
**Theme**
|
|
30
33
|
- Bundled `mdshelf-theme` — mobile-first, responsive, light/dark aware
|
|
@@ -33,6 +36,13 @@ Ships as a single static Rust binary with zero runtime dependencies.
|
|
|
33
36
|
- Per-site accent colors — auto-assigned from a palette or configured explicitly
|
|
34
37
|
- MiniJinja templates for layouts and partials — override any file without forking the whole theme
|
|
35
38
|
|
|
39
|
+
**Access**
|
|
40
|
+
- **Google sign-in** — `--auth google` puts every page behind sign-in with your own OAuth client
|
|
41
|
+
- Per-page access rules in frontmatter (`allow` / `deny`), inherited through folder `index.md` files; anything not granted is denied
|
|
42
|
+
- **Share links** — one page, no sign-in, expiring and revocable, from a Share button or `mdshelf share`
|
|
43
|
+
- Access log with `mdshelf audit`; rule debugging with `mdshelf acl explain` and `mdshelf acl doctor`
|
|
44
|
+
- Built-in HTTPS: Let's Encrypt, your own certificate, or TLS terminated by a proxy in front
|
|
45
|
+
|
|
36
46
|
**Server**
|
|
37
47
|
- **Hot reload** — WebSocket-based live reload; the browser refreshes automatically the moment you save a file
|
|
38
48
|
- Response compression (gzip / brotli / zstd via tower-http)
|
|
@@ -113,7 +123,7 @@ Open `http://127.0.0.1:4444/` for the site index.
|
|
|
113
123
|
| Command | Description |
|
|
114
124
|
|---|---|
|
|
115
125
|
| `mdshelf init [dir]` | Scaffold config and sample content |
|
|
116
|
-
| `mdshelf serve` | Start the web server |
|
|
126
|
+
| `mdshelf serve` | Start the web server (`--auth google` for a private site) |
|
|
117
127
|
| `mdshelf check` | Validate config and scan content |
|
|
118
128
|
| `mdshelf export` | Export a static bundle of HTML and CSS files |
|
|
119
129
|
| `mdshelf auth setup` | Walk through creating a Google OAuth client, then verify it |
|
|
@@ -124,7 +134,7 @@ Open `http://127.0.0.1:4444/` for the site index.
|
|
|
124
134
|
| `mdshelf share <path>` | Create a link that shares one page with anyone holding the URL |
|
|
125
135
|
| `mdshelf share list` | List share links (`--all` for dead ones, `--json` for an inventory) |
|
|
126
136
|
| `mdshelf share revoke <id>` | Revoke one link, or `--all` to revoke every live link |
|
|
127
|
-
| `mdshelf install` | Register as a native system service |
|
|
137
|
+
| `mdshelf install` | Register `serve`, with any of its options, as a native system service |
|
|
128
138
|
| `mdshelf start` | Start the installed service |
|
|
129
139
|
| `mdshelf stop` | Stop the installed service |
|
|
130
140
|
| `mdshelf restart` | Restart the installed service |
|
|
@@ -300,7 +310,7 @@ mid-page. Sessions expire absolutely after `session_max_age` (default 30 days).
|
|
|
300
310
|
> qualification. Each such event is logged at WARN with the cause, so an outage is
|
|
301
311
|
> visible in the logs rather than presenting as mysterious sign-outs.
|
|
302
312
|
|
|
303
|
-
###
|
|
313
|
+
### Exporting a private vault
|
|
304
314
|
|
|
305
315
|
A static bundle has no authentication, so exporting a vault that declares rules
|
|
306
316
|
requires saying whose view it is:
|
|
@@ -430,12 +440,69 @@ Keep `mdshelf share list --json` if you want an inventory of what a deletion wou
|
|
|
430
440
|
before creating a folder's `index.md`. Serving, checking, exporting, and explaining are
|
|
431
441
|
strictly read-only.
|
|
432
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
|
+
|
|
433
500
|
## Theme customisation
|
|
434
501
|
|
|
435
502
|
Override or extend templates by pointing `[theme].directory` at a local directory:
|
|
436
503
|
|
|
437
|
-
- `layouts/` — `base.html`, `doc.html`, `home.html`, `error.html`, `link.html`
|
|
438
|
-
- `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`
|
|
439
506
|
- `assets/` — static files served from `/__assets/...`
|
|
440
507
|
|
|
441
508
|
### Theme capabilities
|
|
@@ -468,6 +535,35 @@ sudo mdshelf uninstall
|
|
|
468
535
|
|
|
469
536
|
Use `--user` for a per-user service where the platform supports it.
|
|
470
537
|
|
|
538
|
+
`mdshelf install` takes every `serve` option and the service runs with exactly those, so a
|
|
539
|
+
private site installs the same way it serves:
|
|
540
|
+
|
|
541
|
+
```bash
|
|
542
|
+
sudo mdshelf auth setup # store Google credentials for the user installing the service
|
|
543
|
+
sudo mdshelf install --auth google --behind-proxy --public-url https://docs.acme.com
|
|
544
|
+
sudo mdshelf start
|
|
545
|
+
```
|
|
546
|
+
|
|
547
|
+
Install runs the checks `serve` runs before it binds — TLS flags, the `[auth]` and `[links]`
|
|
548
|
+
settings, and the stored credentials — and refuses to register a service that could not
|
|
549
|
+
start. It prints the redirect URI to register with Google.
|
|
550
|
+
|
|
551
|
+
- **Credentials come from the file, not your shell.** A service does not inherit the
|
|
552
|
+
installing shell's `MDSHELF_GOOGLE_*` variables, so install requires both in
|
|
553
|
+
`~/.config/mdshelf/credentials.env` (what `mdshelf auth setup` writes). Run `auth setup`
|
|
554
|
+
as the same user that runs `install` — with `sudo`, that is root.
|
|
555
|
+
- **The service gets the installer's `HOME`.** With `--auth` or `--domain`, the service is
|
|
556
|
+
given the installing user's `HOME`, so the credentials, the at-rest key
|
|
557
|
+
(`~/.config/mdshelf/secret.key`) and the ACME cache resolve to the paths install checked.
|
|
558
|
+
- **Relative paths are pinned** to the directory you ran install in: `--tls-cert`,
|
|
559
|
+
`--tls-key` and `--acme-cache` are stored as absolute paths.
|
|
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.
|
|
566
|
+
|
|
471
567
|
## Serving on port 80
|
|
472
568
|
|
|
473
569
|
Set `host` and `port` in your config:
|
|
@@ -504,7 +600,7 @@ Tailscale provisions a certificate automatically and makes the site available **
|
|
|
504
600
|
tailscale serve status # confirm the URL
|
|
505
601
|
```
|
|
506
602
|
|
|
507
|
-
Pair it with `sudo mdshelf install && sudo mdshelf start` (see [System service](#system-service)) so both services survive reboots
|
|
603
|
+
Pair it with `sudo mdshelf install && sudo mdshelf start` (see [System service](#system-service)) so both services survive reboots; pass the same `--auth` and TLS flags to `install` as to `serve`.
|
|
508
604
|
|
|
509
605
|
To stop serving:
|
|
510
606
|
|
|
@@ -514,7 +610,16 @@ tailscale serve off
|
|
|
514
610
|
|
|
515
611
|
### Public access via Tailscale Funnel
|
|
516
612
|
|
|
517
|
-
To make the site reachable from the **public internet** (not just your tailnet), use Funnel instead
|
|
613
|
+
To make the site reachable from the **public internet** (not just your tailnet), use Funnel instead.
|
|
614
|
+
Funnel publishes everything mdshelf serves to anyone, so serve a private site with sign-in on
|
|
615
|
+
and Tailscale's hostname as the public URL:
|
|
616
|
+
|
|
617
|
+
```bash
|
|
618
|
+
mdshelf serve --auth google --behind-proxy --public-url https://<machine>.<tailnet>.ts.net
|
|
619
|
+
```
|
|
620
|
+
|
|
621
|
+
Register `https://<machine>.<tailnet>.ts.net/auth/callback` as a redirect URI on your Google
|
|
622
|
+
OAuth client, then:
|
|
518
623
|
|
|
519
624
|
```bash
|
|
520
625
|
tailscale funnel --bg 4444
|
|
@@ -556,7 +661,7 @@ iPadOS Safari mishandle that, so there they go through `GET /__mdshelf/md/<page>
|
|
|
556
661
|
enforces exactly the same access rules as viewing the page — a page you may not read
|
|
557
662
|
returns the same response as one that does not exist.
|
|
558
663
|
|
|
559
|
-
###
|
|
664
|
+
### Exporting a static site
|
|
560
665
|
|
|
561
666
|
```bash
|
|
562
667
|
mdshelf export # all sites → ./dist
|
package/npm-shrinkwrap.json
CHANGED
|
@@ -23,7 +23,7 @@
|
|
|
23
23
|
"hasInstallScript": true,
|
|
24
24
|
"license": "MIT OR Apache-2.0",
|
|
25
25
|
"name": "mdshelf",
|
|
26
|
-
"version": "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.
|
|
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.
|
|
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.
|
|
103
|
+
"version": "1.1.2",
|
|
104
104
|
"volta": {
|
|
105
105
|
"node": "18.14.1",
|
|
106
106
|
"npm": "9.5.0"
|