leafdocs 0.1.0__tar.gz → 0.3.0__tar.gz

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.
Files changed (52) hide show
  1. leafdocs-0.3.0/PKG-INFO +261 -0
  2. leafdocs-0.3.0/README.md +235 -0
  3. {leafdocs-0.1.0 → leafdocs-0.3.0}/pyproject.toml +3 -1
  4. leafdocs-0.3.0/src/leafdocs/app.py +347 -0
  5. leafdocs-0.3.0/src/leafdocs/static/reader-render.js +61 -0
  6. leafdocs-0.3.0/src/leafdocs/static/vendor/VERSIONS.md +71 -0
  7. leafdocs-0.3.0/src/leafdocs/static/vendor/katex/LICENSE +21 -0
  8. leafdocs-0.3.0/src/leafdocs/static/vendor/katex/contrib/auto-render.min.js +1 -0
  9. leafdocs-0.3.0/src/leafdocs/static/vendor/katex/fonts/KaTeX_AMS-Regular.woff2 +0 -0
  10. leafdocs-0.3.0/src/leafdocs/static/vendor/katex/fonts/KaTeX_Caligraphic-Bold.woff2 +0 -0
  11. leafdocs-0.3.0/src/leafdocs/static/vendor/katex/fonts/KaTeX_Caligraphic-Regular.woff2 +0 -0
  12. leafdocs-0.3.0/src/leafdocs/static/vendor/katex/fonts/KaTeX_Fraktur-Bold.woff2 +0 -0
  13. leafdocs-0.3.0/src/leafdocs/static/vendor/katex/fonts/KaTeX_Fraktur-Regular.woff2 +0 -0
  14. leafdocs-0.3.0/src/leafdocs/static/vendor/katex/fonts/KaTeX_Main-Bold.woff2 +0 -0
  15. leafdocs-0.3.0/src/leafdocs/static/vendor/katex/fonts/KaTeX_Main-BoldItalic.woff2 +0 -0
  16. leafdocs-0.3.0/src/leafdocs/static/vendor/katex/fonts/KaTeX_Main-Italic.woff2 +0 -0
  17. leafdocs-0.3.0/src/leafdocs/static/vendor/katex/fonts/KaTeX_Main-Regular.woff2 +0 -0
  18. leafdocs-0.3.0/src/leafdocs/static/vendor/katex/fonts/KaTeX_Math-BoldItalic.woff2 +0 -0
  19. leafdocs-0.3.0/src/leafdocs/static/vendor/katex/fonts/KaTeX_Math-Italic.woff2 +0 -0
  20. leafdocs-0.3.0/src/leafdocs/static/vendor/katex/fonts/KaTeX_SansSerif-Bold.woff2 +0 -0
  21. leafdocs-0.3.0/src/leafdocs/static/vendor/katex/fonts/KaTeX_SansSerif-Italic.woff2 +0 -0
  22. leafdocs-0.3.0/src/leafdocs/static/vendor/katex/fonts/KaTeX_SansSerif-Regular.woff2 +0 -0
  23. leafdocs-0.3.0/src/leafdocs/static/vendor/katex/fonts/KaTeX_Script-Regular.woff2 +0 -0
  24. leafdocs-0.3.0/src/leafdocs/static/vendor/katex/fonts/KaTeX_Size1-Regular.woff2 +0 -0
  25. leafdocs-0.3.0/src/leafdocs/static/vendor/katex/fonts/KaTeX_Size2-Regular.woff2 +0 -0
  26. leafdocs-0.3.0/src/leafdocs/static/vendor/katex/fonts/KaTeX_Size3-Regular.woff2 +0 -0
  27. leafdocs-0.3.0/src/leafdocs/static/vendor/katex/fonts/KaTeX_Size4-Regular.woff2 +0 -0
  28. leafdocs-0.3.0/src/leafdocs/static/vendor/katex/fonts/KaTeX_Typewriter-Regular.woff2 +0 -0
  29. leafdocs-0.3.0/src/leafdocs/static/vendor/katex/katex.min.css +1 -0
  30. leafdocs-0.3.0/src/leafdocs/static/vendor/katex/katex.min.js +1 -0
  31. leafdocs-0.3.0/src/leafdocs/static/vendor/mermaid/LICENSE +21 -0
  32. leafdocs-0.3.0/src/leafdocs/static/vendor/mermaid/mermaid.min.js +4328 -0
  33. {leafdocs-0.1.0 → leafdocs-0.3.0}/src/leafdocs/templates/index.html +83 -11
  34. {leafdocs-0.1.0 → leafdocs-0.3.0}/src/leafdocs/templates/reader.html +45 -5
  35. leafdocs-0.3.0/src/leafdocs.egg-info/PKG-INFO +261 -0
  36. leafdocs-0.3.0/src/leafdocs.egg-info/SOURCES.txt +44 -0
  37. {leafdocs-0.1.0 → leafdocs-0.3.0}/src/leafdocs.egg-info/requires.txt +2 -0
  38. leafdocs-0.3.0/tests/test_app.py +819 -0
  39. leafdocs-0.1.0/PKG-INFO +0 -310
  40. leafdocs-0.1.0/README.md +0 -286
  41. leafdocs-0.1.0/src/leafdocs/app.py +0 -178
  42. leafdocs-0.1.0/src/leafdocs.egg-info/PKG-INFO +0 -310
  43. leafdocs-0.1.0/src/leafdocs.egg-info/SOURCES.txt +0 -16
  44. leafdocs-0.1.0/tests/test_app.py +0 -211
  45. {leafdocs-0.1.0 → leafdocs-0.3.0}/LICENSE +0 -0
  46. {leafdocs-0.1.0 → leafdocs-0.3.0}/setup.cfg +0 -0
  47. {leafdocs-0.1.0 → leafdocs-0.3.0}/src/leafdocs/__init__.py +0 -0
  48. {leafdocs-0.1.0 → leafdocs-0.3.0}/src/leafdocs/static/favicon.ico +0 -0
  49. {leafdocs-0.1.0 → leafdocs-0.3.0}/src/leafdocs/templates/base.html +0 -0
  50. {leafdocs-0.1.0 → leafdocs-0.3.0}/src/leafdocs/templates/login.html +0 -0
  51. {leafdocs-0.1.0 → leafdocs-0.3.0}/src/leafdocs.egg-info/dependency_links.txt +0 -0
  52. {leafdocs-0.1.0 → leafdocs-0.3.0}/src/leafdocs.egg-info/top_level.txt +0 -0
@@ -0,0 +1,261 @@
1
+ Metadata-Version: 2.4
2
+ Name: leafdocs
3
+ Version: 0.3.0
4
+ Summary: Turn a directory of Markdown files into a self-hosted, searchable web reader.
5
+ Author-email: Anshul <you@example.com>
6
+ License: MIT
7
+ Keywords: markdown,docs,flask,self-hosted
8
+ Classifier: Development Status :: 3 - Alpha
9
+ Classifier: Framework :: Flask
10
+ Classifier: License :: OSI Approved :: MIT License
11
+ Classifier: Programming Language :: Python :: 3
12
+ Requires-Python: >=3.10
13
+ Description-Content-Type: text/markdown
14
+ License-File: LICENSE
15
+ Requires-Dist: flask>=3.0
16
+ Requires-Dist: markdown>=3.5
17
+ Requires-Dist: python-frontmatter>=1.1
18
+ Requires-Dist: bcrypt>=4.1
19
+ Requires-Dist: python-dotenv>=1.0
20
+ Requires-Dist: nh3>=0.2.22
21
+ Requires-Dist: pymdown-extensions>=10.0
22
+ Provides-Extra: dev
23
+ Requires-Dist: pytest; extra == "dev"
24
+ Requires-Dist: pytest-flask; extra == "dev"
25
+ Dynamic: license-file
26
+
27
+ # leafdocs
28
+
29
+ A lightweight Python library that turns a directory of Markdown files into a self-hosted, searchable web reader.
30
+
31
+ Install it, point it at a folder, get a running Flask app you can deploy anywhere.
32
+
33
+ ```python
34
+ from leafdocs import LeafDocs
35
+
36
+ ld = LeafDocs(docs_dir="./docs")
37
+ ld.run()
38
+ ```
39
+
40
+ ---
41
+
42
+ ## Installation
43
+
44
+ ```bash
45
+ pip install leafdocs
46
+ ```
47
+
48
+ ---
49
+
50
+ ## Usage
51
+
52
+ ### Minimal setup
53
+
54
+ ```python
55
+ from leafdocs import LeafDocs
56
+
57
+ ld = LeafDocs(docs_dir="./docs")
58
+ ld.run()
59
+ ```
60
+
61
+ Visit `http://127.0.0.1:5000` to see a searchable index of all `.md` files in your `docs/` directory, including subfolders.
62
+
63
+ ### Constructor arguments
64
+
65
+ | Argument | Type | Default | Description |
66
+ |--------------|-----------------|-----------------|--------------------------------------------------|
67
+ | `docs_dir` | `str` | `"./docs"` | Path to the directory containing `.md` files |
68
+ | `secret_key` | `str` or `None` | `None` | Flask session secret key (see Auth section) |
69
+
70
+ ### Accessing the Flask app
71
+
72
+ The underlying Flask app is exposed as `ld.flask_app`. Use it to add routes, middleware, or blueprints:
73
+
74
+ ```python
75
+ from leafdocs import LeafDocs
76
+
77
+ ld = LeafDocs(docs_dir="./docs")
78
+
79
+ @ld.flask_app.route("/health")
80
+ def health():
81
+ return {"status": "ok"}
82
+
83
+ ld.run()
84
+ ```
85
+
86
+ ---
87
+
88
+ ## Frontmatter
89
+
90
+ Each `.md` file can optionally include YAML frontmatter. Supported fields:
91
+
92
+ ```yaml
93
+ ---
94
+ title: My Document Title
95
+ tags: [python, tutorial]
96
+ ---
97
+ ```
98
+
99
+ | Field | Type | Fallback |
100
+ |---------|--------------------------------|----------------------|
101
+ | `title` | string | Filename, titlecased |
102
+ | `tags` | list or comma-separated string | None |
103
+
104
+ Frontmatter is optional — files without it are discovered and served normally.
105
+
106
+ ---
107
+
108
+ ## Folders and images
109
+
110
+ Subfolders under `docs_dir` are shown on the index as a collapsible tree, folders first, then docs. Folder names are titlecased the same way as filenames (`getting-started` becomes `Getting Started`). Folders with no `.md` files anywhere beneath them are left out. While the search box has text, matching docs are shown with their folders opened; clearing it restores the folders you had open.
111
+
112
+ A doc's URL is its path relative to `docs_dir` without `.md`, so `docs/guides/setup.md` is served at `/guides/setup`. Links ending in `.md`, such as `/guides/setup.md`, redirect to the same URL without it.
113
+
114
+ Skipped during discovery and returned as 404 by the reader:
115
+
116
+ - Any file or folder whose name starts with `.`, such as `.git/`
117
+ - Top-level `login.md`, `logout.md`, and anything under `static/`, since those paths belong to built-in routes. Nested files such as `guides/login.md` are fine.
118
+
119
+ Symlinks that point outside `docs_dir` are not listed and return 403.
120
+
121
+ Images (`.png`, `.jpg`, `.jpeg`, `.gif`, `.webp`) inside `docs_dir` are served at their path, behind the same login as the docs, so a doc can reference an image next to it with a relative path:
122
+
123
+ ```markdown
124
+ ![Diagram](diagram.png)
125
+ ```
126
+
127
+ No other file types are served. SVG is excluded because it can carry scripts.
128
+
129
+ ---
130
+
131
+ ## Math and diagrams
132
+
133
+ Docs can contain TeX math and [mermaid](https://mermaid.js.org) diagrams. Both are rendered in the browser by copies of [KaTeX](https://katex.org) and mermaid bundled with the package, so nothing is fetched from a CDN. Pages without math or diagrams load neither library.
134
+
135
+ Inline math uses `\(...\)`. Single dollar signs are left alone so prices in prose render normally. Block math uses `$$...$$` or `\[...\]`:
136
+
137
+ ```markdown
138
+ The area is \(\pi r^2\), and it costs $5.
139
+
140
+ $$
141
+ \int_0^1 x^2 \, dx = \frac{1}{3}
142
+ $$
143
+ ```
144
+
145
+ Diagrams are fenced code blocks tagged `mermaid`:
146
+
147
+ ````markdown
148
+ ```mermaid
149
+ flowchart LR
150
+ Write --> Commit --> Read
151
+ ```
152
+ ````
153
+
154
+ Mermaid runs with `securityLevel: "strict"`, so click handlers and raw HTML in diagram labels are disabled. Bundled versions and their licenses are listed in [`src/leafdocs/static/vendor/VERSIONS.md`](src/leafdocs/static/vendor/VERSIONS.md).
155
+
156
+ ---
157
+
158
+ ## Authentication
159
+
160
+ By default the server runs open with no login required.
161
+
162
+ To enable pin-based auth, add pins to a `.env` file in your working directory:
163
+
164
+ ```
165
+ LEAFDOCS_PINS=mypin123,anotherpin
166
+ ```
167
+
168
+ Restart the server. All routes will now require a valid pin.
169
+
170
+ **How it works:**
171
+ - Pins are hashed with bcrypt at startup — raw values are never stored in memory
172
+ - A successful login issues a session cookie with `HttpOnly`, `SameSite=Lax`, and `Secure` (see `LEAFDOCS_COOKIE_SECURE` below)
173
+ - Multiple pins are supported — useful for sharing access without a shared secret
174
+ - To invalidate all sessions, rotate or remove the pin and restart
175
+
176
+ ### Session secret key
177
+
178
+ By default a random secret key is generated at startup, which means sessions are invalidated on every restart. For persistent sessions across restarts, set a stable key:
179
+
180
+ ```
181
+ LEAFDOCS_SECRET_KEY=your-long-random-secret-here
182
+ ```
183
+
184
+ Or pass it directly in code:
185
+
186
+ ```python
187
+ ld = LeafDocs(docs_dir="./docs", secret_key="your-long-random-secret-here")
188
+ ```
189
+
190
+ Generate a good key with:
191
+
192
+ ```bash
193
+ python -c "import secrets; print(secrets.token_hex(32))"
194
+ ```
195
+
196
+ ---
197
+
198
+ ## Configuration reference
199
+
200
+ All pins and the secret key are configured via `.env` only. Copy `.env.example` to `.env` to get started:
201
+
202
+ ```
203
+ LEAFDOCS_PINS=pin1,pin2
204
+ LEAFDOCS_SECRET_KEY=your-secret-here
205
+ LEAFDOCS_COOKIE_SECURE=true
206
+ LEAFDOCS_ALLOW_HTML=false
207
+ ```
208
+
209
+ | Variable | Default | Description |
210
+ |--------------------------|---------|-------------|
211
+ | `LEAFDOCS_PINS` | unset | Comma-separated pins. Unset or empty means no auth. |
212
+ | `LEAFDOCS_SECRET_KEY` | random | Session signing key. Random means sessions reset on restart. |
213
+ | `LEAFDOCS_COOKIE_SECURE` | `true` | Sets the `Secure` flag on the session cookie. `false`, `0`, `no`, or `off` disables it. Disable only when serving over plain HTTP, such as local or raw-IP testing; browsers will not send a `Secure` cookie over HTTP, so login loops back to the login page. |
214
+ | `LEAFDOCS_ALLOW_HTML` | `false` | Skips HTML sanitizing of rendered Markdown. `true`, `1`, `yes`, or `on` enables it. Only safe when you are the sole author of all content. |
215
+
216
+ ---
217
+
218
+ ## Known limitations
219
+
220
+ - **No caching** — Markdown is rendered on every request. Fine for personal or low-traffic use; not suitable for high-traffic production serving.
221
+ - **No per-device session revocation** — rotating the pin invalidates all active sessions across all devices.
222
+ - **No plugin system** — the primary extension hook is `ld.flask_app`. Add routes and middleware directly on the Flask app.
223
+ - **Raw HTML in docs is sanitized** - rendered Markdown is passed through [nh3](https://github.com/messense/nh3) with an allowlist covering what the enabled extensions produce (headings with ids, lists, tables with alignment, code blocks with `language-*` classes, links, images, the `[TOC]` block, and `span` and `div` elements with class `arithmatex` for math). Scripts, event handler attributes, iframes, inline styles other than table alignment, and URL schemes other than `http`, `https`, and `mailto` are removed. Links get `rel="noopener noreferrer"`. Raw HTML outside the allowlist, such as `<details>`, is stripped, and `class` on `span` and `div` is removed unless it is exactly `arithmatex` (or `toc` on `div`). Set `LEAFDOCS_ALLOW_HTML=true` to turn sanitizing off; only do this when you are the sole author of all content.
224
+
225
+ ---
226
+
227
+ ## Deployment
228
+
229
+ > **Disclaimer:** leafdocs is a Flask application. Running it with `ld.run()` uses Flask's built-in development server, which is not suitable for production. For production use, you are responsible for:
230
+ > - Running behind a production WSGI server (Gunicorn recommended)
231
+ > - Terminating HTTPS at a reverse proxy (Nginx recommended)
232
+ > - Managing the process with a supervisor (systemd recommended)
233
+
234
+ For full production setup on **AWS EC2** or **GCP Compute Engine** — Gunicorn + Nginx + systemd, raw-IP access, and two options for a custom domain (direct DNS + Certbot, or Cloudflare Tunnel for boxes without a public IPv4) — see **[DEPLOY.md](DEPLOY.md)**.
235
+
236
+ ---
237
+
238
+ ## Claude Code skill
239
+
240
+ [`SKILL.md`](SKILL.md) is a [Claude Code](https://claude.com/claude-code) skill that generates Markdown files with the correct leafdocs frontmatter (`title`, `tags`) from raw notes or existing content, ready to drop into your `docs/` directory. Drop it into your Claude Code skills directory to use it.
241
+
242
+ ---
243
+
244
+ ## Development
245
+
246
+ ```bash
247
+ git clone https://github.com/anshulraj10/leafdocs
248
+ cd leafdocs
249
+ python -m venv .venv
250
+ source .venv/bin/activate
251
+ pip install -e ".[dev]"
252
+ pytest tests/ -v
253
+ ```
254
+
255
+ `python main.py` runs a dev server on http://127.0.0.1:5000 (cookie Secure flag is off for plain http).
256
+
257
+ ---
258
+
259
+ ## License
260
+
261
+ MIT
@@ -0,0 +1,235 @@
1
+ # leafdocs
2
+
3
+ A lightweight Python library that turns a directory of Markdown files into a self-hosted, searchable web reader.
4
+
5
+ Install it, point it at a folder, get a running Flask app you can deploy anywhere.
6
+
7
+ ```python
8
+ from leafdocs import LeafDocs
9
+
10
+ ld = LeafDocs(docs_dir="./docs")
11
+ ld.run()
12
+ ```
13
+
14
+ ---
15
+
16
+ ## Installation
17
+
18
+ ```bash
19
+ pip install leafdocs
20
+ ```
21
+
22
+ ---
23
+
24
+ ## Usage
25
+
26
+ ### Minimal setup
27
+
28
+ ```python
29
+ from leafdocs import LeafDocs
30
+
31
+ ld = LeafDocs(docs_dir="./docs")
32
+ ld.run()
33
+ ```
34
+
35
+ Visit `http://127.0.0.1:5000` to see a searchable index of all `.md` files in your `docs/` directory, including subfolders.
36
+
37
+ ### Constructor arguments
38
+
39
+ | Argument | Type | Default | Description |
40
+ |--------------|-----------------|-----------------|--------------------------------------------------|
41
+ | `docs_dir` | `str` | `"./docs"` | Path to the directory containing `.md` files |
42
+ | `secret_key` | `str` or `None` | `None` | Flask session secret key (see Auth section) |
43
+
44
+ ### Accessing the Flask app
45
+
46
+ The underlying Flask app is exposed as `ld.flask_app`. Use it to add routes, middleware, or blueprints:
47
+
48
+ ```python
49
+ from leafdocs import LeafDocs
50
+
51
+ ld = LeafDocs(docs_dir="./docs")
52
+
53
+ @ld.flask_app.route("/health")
54
+ def health():
55
+ return {"status": "ok"}
56
+
57
+ ld.run()
58
+ ```
59
+
60
+ ---
61
+
62
+ ## Frontmatter
63
+
64
+ Each `.md` file can optionally include YAML frontmatter. Supported fields:
65
+
66
+ ```yaml
67
+ ---
68
+ title: My Document Title
69
+ tags: [python, tutorial]
70
+ ---
71
+ ```
72
+
73
+ | Field | Type | Fallback |
74
+ |---------|--------------------------------|----------------------|
75
+ | `title` | string | Filename, titlecased |
76
+ | `tags` | list or comma-separated string | None |
77
+
78
+ Frontmatter is optional — files without it are discovered and served normally.
79
+
80
+ ---
81
+
82
+ ## Folders and images
83
+
84
+ Subfolders under `docs_dir` are shown on the index as a collapsible tree, folders first, then docs. Folder names are titlecased the same way as filenames (`getting-started` becomes `Getting Started`). Folders with no `.md` files anywhere beneath them are left out. While the search box has text, matching docs are shown with their folders opened; clearing it restores the folders you had open.
85
+
86
+ A doc's URL is its path relative to `docs_dir` without `.md`, so `docs/guides/setup.md` is served at `/guides/setup`. Links ending in `.md`, such as `/guides/setup.md`, redirect to the same URL without it.
87
+
88
+ Skipped during discovery and returned as 404 by the reader:
89
+
90
+ - Any file or folder whose name starts with `.`, such as `.git/`
91
+ - Top-level `login.md`, `logout.md`, and anything under `static/`, since those paths belong to built-in routes. Nested files such as `guides/login.md` are fine.
92
+
93
+ Symlinks that point outside `docs_dir` are not listed and return 403.
94
+
95
+ Images (`.png`, `.jpg`, `.jpeg`, `.gif`, `.webp`) inside `docs_dir` are served at their path, behind the same login as the docs, so a doc can reference an image next to it with a relative path:
96
+
97
+ ```markdown
98
+ ![Diagram](diagram.png)
99
+ ```
100
+
101
+ No other file types are served. SVG is excluded because it can carry scripts.
102
+
103
+ ---
104
+
105
+ ## Math and diagrams
106
+
107
+ Docs can contain TeX math and [mermaid](https://mermaid.js.org) diagrams. Both are rendered in the browser by copies of [KaTeX](https://katex.org) and mermaid bundled with the package, so nothing is fetched from a CDN. Pages without math or diagrams load neither library.
108
+
109
+ Inline math uses `\(...\)`. Single dollar signs are left alone so prices in prose render normally. Block math uses `$$...$$` or `\[...\]`:
110
+
111
+ ```markdown
112
+ The area is \(\pi r^2\), and it costs $5.
113
+
114
+ $$
115
+ \int_0^1 x^2 \, dx = \frac{1}{3}
116
+ $$
117
+ ```
118
+
119
+ Diagrams are fenced code blocks tagged `mermaid`:
120
+
121
+ ````markdown
122
+ ```mermaid
123
+ flowchart LR
124
+ Write --> Commit --> Read
125
+ ```
126
+ ````
127
+
128
+ Mermaid runs with `securityLevel: "strict"`, so click handlers and raw HTML in diagram labels are disabled. Bundled versions and their licenses are listed in [`src/leafdocs/static/vendor/VERSIONS.md`](src/leafdocs/static/vendor/VERSIONS.md).
129
+
130
+ ---
131
+
132
+ ## Authentication
133
+
134
+ By default the server runs open with no login required.
135
+
136
+ To enable pin-based auth, add pins to a `.env` file in your working directory:
137
+
138
+ ```
139
+ LEAFDOCS_PINS=mypin123,anotherpin
140
+ ```
141
+
142
+ Restart the server. All routes will now require a valid pin.
143
+
144
+ **How it works:**
145
+ - Pins are hashed with bcrypt at startup — raw values are never stored in memory
146
+ - A successful login issues a session cookie with `HttpOnly`, `SameSite=Lax`, and `Secure` (see `LEAFDOCS_COOKIE_SECURE` below)
147
+ - Multiple pins are supported — useful for sharing access without a shared secret
148
+ - To invalidate all sessions, rotate or remove the pin and restart
149
+
150
+ ### Session secret key
151
+
152
+ By default a random secret key is generated at startup, which means sessions are invalidated on every restart. For persistent sessions across restarts, set a stable key:
153
+
154
+ ```
155
+ LEAFDOCS_SECRET_KEY=your-long-random-secret-here
156
+ ```
157
+
158
+ Or pass it directly in code:
159
+
160
+ ```python
161
+ ld = LeafDocs(docs_dir="./docs", secret_key="your-long-random-secret-here")
162
+ ```
163
+
164
+ Generate a good key with:
165
+
166
+ ```bash
167
+ python -c "import secrets; print(secrets.token_hex(32))"
168
+ ```
169
+
170
+ ---
171
+
172
+ ## Configuration reference
173
+
174
+ All pins and the secret key are configured via `.env` only. Copy `.env.example` to `.env` to get started:
175
+
176
+ ```
177
+ LEAFDOCS_PINS=pin1,pin2
178
+ LEAFDOCS_SECRET_KEY=your-secret-here
179
+ LEAFDOCS_COOKIE_SECURE=true
180
+ LEAFDOCS_ALLOW_HTML=false
181
+ ```
182
+
183
+ | Variable | Default | Description |
184
+ |--------------------------|---------|-------------|
185
+ | `LEAFDOCS_PINS` | unset | Comma-separated pins. Unset or empty means no auth. |
186
+ | `LEAFDOCS_SECRET_KEY` | random | Session signing key. Random means sessions reset on restart. |
187
+ | `LEAFDOCS_COOKIE_SECURE` | `true` | Sets the `Secure` flag on the session cookie. `false`, `0`, `no`, or `off` disables it. Disable only when serving over plain HTTP, such as local or raw-IP testing; browsers will not send a `Secure` cookie over HTTP, so login loops back to the login page. |
188
+ | `LEAFDOCS_ALLOW_HTML` | `false` | Skips HTML sanitizing of rendered Markdown. `true`, `1`, `yes`, or `on` enables it. Only safe when you are the sole author of all content. |
189
+
190
+ ---
191
+
192
+ ## Known limitations
193
+
194
+ - **No caching** — Markdown is rendered on every request. Fine for personal or low-traffic use; not suitable for high-traffic production serving.
195
+ - **No per-device session revocation** — rotating the pin invalidates all active sessions across all devices.
196
+ - **No plugin system** — the primary extension hook is `ld.flask_app`. Add routes and middleware directly on the Flask app.
197
+ - **Raw HTML in docs is sanitized** - rendered Markdown is passed through [nh3](https://github.com/messense/nh3) with an allowlist covering what the enabled extensions produce (headings with ids, lists, tables with alignment, code blocks with `language-*` classes, links, images, the `[TOC]` block, and `span` and `div` elements with class `arithmatex` for math). Scripts, event handler attributes, iframes, inline styles other than table alignment, and URL schemes other than `http`, `https`, and `mailto` are removed. Links get `rel="noopener noreferrer"`. Raw HTML outside the allowlist, such as `<details>`, is stripped, and `class` on `span` and `div` is removed unless it is exactly `arithmatex` (or `toc` on `div`). Set `LEAFDOCS_ALLOW_HTML=true` to turn sanitizing off; only do this when you are the sole author of all content.
198
+
199
+ ---
200
+
201
+ ## Deployment
202
+
203
+ > **Disclaimer:** leafdocs is a Flask application. Running it with `ld.run()` uses Flask's built-in development server, which is not suitable for production. For production use, you are responsible for:
204
+ > - Running behind a production WSGI server (Gunicorn recommended)
205
+ > - Terminating HTTPS at a reverse proxy (Nginx recommended)
206
+ > - Managing the process with a supervisor (systemd recommended)
207
+
208
+ For full production setup on **AWS EC2** or **GCP Compute Engine** — Gunicorn + Nginx + systemd, raw-IP access, and two options for a custom domain (direct DNS + Certbot, or Cloudflare Tunnel for boxes without a public IPv4) — see **[DEPLOY.md](DEPLOY.md)**.
209
+
210
+ ---
211
+
212
+ ## Claude Code skill
213
+
214
+ [`SKILL.md`](SKILL.md) is a [Claude Code](https://claude.com/claude-code) skill that generates Markdown files with the correct leafdocs frontmatter (`title`, `tags`) from raw notes or existing content, ready to drop into your `docs/` directory. Drop it into your Claude Code skills directory to use it.
215
+
216
+ ---
217
+
218
+ ## Development
219
+
220
+ ```bash
221
+ git clone https://github.com/anshulraj10/leafdocs
222
+ cd leafdocs
223
+ python -m venv .venv
224
+ source .venv/bin/activate
225
+ pip install -e ".[dev]"
226
+ pytest tests/ -v
227
+ ```
228
+
229
+ `python main.py` runs a dev server on http://127.0.0.1:5000 (cookie Secure flag is off for plain http).
230
+
231
+ ---
232
+
233
+ ## License
234
+
235
+ MIT
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "leafdocs"
7
- version = "0.1.0"
7
+ version = "0.3.0"
8
8
  description = "Turn a directory of Markdown files into a self-hosted, searchable web reader."
9
9
  readme = "README.md"
10
10
  requires-python = ">=3.10"
@@ -23,6 +23,8 @@ dependencies = [
23
23
  "python-frontmatter>=1.1",
24
24
  "bcrypt>=4.1",
25
25
  "python-dotenv>=1.0",
26
+ "nh3>=0.2.22",
27
+ "pymdown-extensions>=10.0",
26
28
  ]
27
29
 
28
30
  [project.optional-dependencies]