llmd 0.1.0 → 0.2.0

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 (4) hide show
  1. package/README.md +39 -166
  2. package/dist/client.js +1 -1
  3. package/dist/llmd +619 -269
  4. package/package.json +4 -2
package/README.md CHANGED
@@ -2,203 +2,76 @@
2
2
 
3
3
  **Serve Markdown as beautiful HTML. Instantly.**
4
4
 
5
- A zero-config CLI tool for viewing Markdown files in your browser with syntax highlighting, live reload, and a clean interface. Built for developers reviewing LLM-generated documentation.
6
-
7
- ```
8
- ██╗ ██╗ ███╗ ███╗██████╗
9
- ██║ ██║ ████╗ ████║██╔══██╗
10
- ██║ ██║ ██╔████╔██║██║ ██║
11
- ██║ ██║ ██║╚██╔╝██║██║ ██║
12
- ███████╗███████╗██║ ╚═╝ ██║██████╔╝
13
- ╚══════╝╚══════╝╚═╝ ╚═╝╚═════╝
14
- ```
15
-
16
- ## Features
17
-
18
- - **Zero config** - Point at a directory and go
19
- - **Syntax highlighting** - Powered by Shiki
20
- - **Live reload** - Watch mode reloads on file changes
21
- - **Copy buttons** - One-click code copying
22
- - **Dark/light themes** - With 3 font options
23
- - **Fast** - Built with Bun, instant startup
24
- - **Sidebar navigation** - Browse files with directory structure
25
- - **Table of contents** - Auto-generated from headings
5
+ A minimal CLI tool for viewing Markdown files in your browser with syntax highlighting, live reload, and a clean interface. Built for developers reviewing LLM-generated documentation.
26
6
 
27
7
  ## Installation
28
8
 
29
- ### npm (Recommended)
30
-
31
9
  ```bash
32
10
  npm install -g llmd
33
11
  ```
34
12
 
35
13
  Or run directly without installing:
14
+
36
15
  ```bash
37
16
  npx llmd
38
17
  ```
39
18
 
40
19
  Requires Node.js 22 or later.
41
20
 
42
- ### From Source
21
+ ## Quick Start
43
22
 
44
- ```bash
45
- git clone https://github.com/pbzona/llmd.git
46
- cd llmd
47
- bun install
48
- bun run build:npm
49
- npm install -g .
50
- ```
51
-
52
- ## Usage
23
+ Want to see what llmd can do? **Try it on its own documentation:**
53
24
 
54
25
  ```bash
55
- # Serve current directory
56
- llmd
57
-
58
- # Serve specific directory
59
- llmd ./docs
60
-
61
- # Dark mode with live reload
62
- llmd ./docs --theme dark --watch
26
+ # Install llmd
27
+ npm install -g llmd
63
28
 
64
- # Custom port
65
- llmd ./docs --port 8080
29
+ # View the documentation
30
+ llmd docs
66
31
  ```
67
32
 
68
- ## Font Combinations
33
+ That's it! The docs will open in your browser. Click through the sidebar to explore.
69
34
 
70
- llmd includes 9 built-in font combinations:
71
-
72
- - **serif** - System serif fonts (Georgia, Times)
73
- - **sans** - System sans-serif fonts (default)
74
- - **mono** - System monospace fonts
75
- - **classic** - Baskerville headings + Geist body
76
- - **future** - Space Mono headings + Space Grotesk body
77
- - **modern** - Inter throughout + JetBrains Mono
78
- - **artsy** - Playfair Display + Fira Code
79
- - **literary** - Spectral headings + Newsreader body
80
- - **editorial** - Bitter headings + Lora body
81
-
82
- All custom fonts are loaded from Google Fonts CDN for fast, reliable delivery.
83
-
84
- ### Custom Fonts
85
-
86
- Create custom font combinations in your `themes.json` config file.
35
+ ## Features
87
36
 
88
- **Location:** `~/.config/llmd/themes.json` (or `$XDG_CONFIG_HOME/llmd/themes.json`)
37
+ - **Simple setup** - Point at a directory and go
38
+ - **Syntax highlighting** - Powered by Shiki
39
+ - **Live reload** - Watch mode reloads on file changes
40
+ - **Copy buttons** - One-click code copying
41
+ - **Dark/light themes** - With 9 font combinations
42
+ - **Fast** - Built with Bun, instant startup
43
+ - **Sidebar navigation** - Browse files with directory structure
44
+ - **Table of contents** - Auto-generated from headings
45
+ - **Usage Analytics** - Track which docs you view most (local-only, opt-in)
89
46
 
90
- **Simple Example (Auto-loaded from Google Fonts):**
91
- ```json
92
- {
93
- "fontThemes": {
94
- "myfont": {
95
- "heading": "Montserrat, sans-serif",
96
- "body": "Open Sans, sans-serif",
97
- "code": "Source Code Pro, monospace"
98
- }
99
- }
100
- }
101
- ```
47
+ ## Documentation
102
48
 
103
- Google Fonts are **loaded automatically** with weights 400 and 700. Just specify the font family names - no need to construct Google Fonts URLs manually!
104
-
105
- **Advanced Example (Custom Weights/Styles):**
106
- ```json
107
- {
108
- "fontThemes": {
109
- "custom": {
110
- "heading": "Poppins, sans-serif",
111
- "body": "Inter, sans-serif",
112
- "code": "Fira Code, monospace",
113
- "googleFontsUrl": "https://fonts.googleapis.com/css2?family=Poppins:wght@400;600;700&family=Inter:wght@300;400;500&family=Fira+Code:wght@400;500&display=swap"
114
- }
115
- }
116
- }
117
- ```
49
+ - [Installation](./docs/installation.md) - Installation methods
50
+ - [Usage](./docs/usage.md) - Command-line options and examples
51
+ - [Themes](./docs/themes.md) - Built-in and custom color themes
52
+ - [Fonts](./docs/fonts.md) - Built-in and custom font combinations
53
+ - [Analytics](./docs/analytics.md) - Local usage tracking
118
54
 
119
- Use `googleFontsUrl` only if you need specific weights (like 300, 500, 600) or styles (italic, etc.). Get custom URLs from [Google Fonts](https://fonts.google.com).
55
+ ## Basic Usage
120
56
 
121
- **Usage:**
122
57
  ```bash
123
- llmd --fonts myfont
124
- ```
58
+ # View llmd documentation
59
+ llmd docs
125
60
 
126
- **Requirements:**
127
- - `heading`, `body`, and `code` properties are required
128
- - Font names should include CSS fallbacks (e.g., `"Roboto, sans-serif"`)
129
- - System fonts (Arial, Georgia, etc.) don't load from Google Fonts
130
- - Invalid font names will show helpful errors listing all available fonts
131
-
132
- ## Color Themes
133
-
134
- llmd includes 6 built-in color themes:
135
-
136
- - **dark** - Default dark theme (default)
137
- - **light** - Warm light theme
138
- - **nord** - Nord-inspired cool theme
139
- - **dracula** - Dracula-inspired purple theme
140
- - **solarized** - Solarized Light theme
141
- - **monokai** - Monokai-inspired theme
142
-
143
- ### Custom Themes
144
-
145
- Create custom color themes in your `themes.json` config file.
146
-
147
- **Location:** `~/.config/llmd/themes.json` (or `$XDG_CONFIG_HOME/llmd/themes.json`)
148
-
149
- **Format:**
150
- ```json
151
- {
152
- "colorThemes": {
153
- "mytheme": {
154
- "bg": "#1a1a1a",
155
- "fg": "#e0e0e0",
156
- "border": "#333",
157
- "hover": "#2a2a2a",
158
- "accent": "#4a9eff",
159
- "codeBg": "#2d2d2d",
160
- "sidebarBg": "#151515",
161
- "folderIcon": "#a78bfa",
162
- "fileIcon": "#fbbf24"
163
- }
164
- }
165
- }
166
- ```
167
-
168
- **Unified Config:**
169
- You can combine both color themes and font themes in a single file:
170
- ```json
171
- {
172
- "colorThemes": {
173
- "mytheme": { ... }
174
- },
175
- "fontThemes": {
176
- "myfont": { ... }
177
- }
178
- }
179
- ```
180
-
181
- **Usage:**
182
- ```bash
183
- llmd --theme mytheme --fonts myfont
184
- ```
61
+ # Serve current directory
62
+ llmd
185
63
 
186
- If a theme or font is not found, llmd will list all available options.
64
+ # Serve specific directory
65
+ llmd ./docs
187
66
 
188
- **Note:** The old flat format for color themes (without `colorThemes` key) is still supported for backward compatibility.
67
+ # Dark mode with live reload
68
+ llmd ./docs --theme dark --watch
189
69
 
190
- ## Options
70
+ # Open directly to analytics
71
+ llmd analytics
72
+ ```
191
73
 
192
- | Flag | Description | Default |
193
- |------|-------------|---------|
194
- | `--port <number>` | Port (0 = random) | `0` (random) |
195
- | `--host <string>` | Host interface | `localhost` |
196
- | `--theme <name>` | Color theme: `dark`, `light`, `nord`, `dracula`, `solarized`, `monokai`, or custom | `dark` |
197
- | `--fonts <name>` | Font combination: `serif`, `sans`, `mono`, `classic`, `future`, `modern`, `artsy`, `literary`, `editorial` | `sans` |
198
- | `--open / --no-open` | Auto-open browser | `--open` |
199
- | `--watch / --no-watch` | Live reload on changes | `--no-watch` |
200
- | `-h, --help` | Show help | |
201
- | `--version` | Show version | |
74
+ See [Usage](./docs/usage.md) for all options.
202
75
 
203
76
  ## Development
204
77
 
@@ -221,7 +94,7 @@ bun run build
221
94
  - **Runtime**: Bun
222
95
  - **Markdown**: marked (GFM support)
223
96
  - **Highlighting**: Shiki (VS Code themes)
224
- - **Server**: Bun.serve() with WebSocket
97
+ - **Server**: Node.js http + ws
225
98
  - **Bundler**: Bun's built-in bundler
226
99
 
227
100
  ## License
package/dist/client.js CHANGED
@@ -1 +1 @@
1
- var s=()=>{document.querySelectorAll("pre code").forEach((o)=>{let d=o.parentElement;if(!d)return;if(d.querySelector(".copy-button"))return;let t=document.createElement("button");t.className="copy-button",t.textContent="Copy",t.setAttribute("aria-label","Copy code to clipboard"),t.addEventListener("click",async()=>{try{await navigator.clipboard.writeText(o.textContent||""),t.textContent="Copied!",t.classList.add("copied"),setTimeout(()=>{t.textContent="Copy",t.classList.remove("copied")},2000)}catch(e){console.error("Failed to copy:",e),t.textContent="Failed",setTimeout(()=>{t.textContent="Copy"},2000)}}),d.appendChild(t)})};if(document.readyState==="loading")document.addEventListener("DOMContentLoaded",s);else s();var p=(o)=>{let t=`${window.location.protocol==="https:"?"wss:":"ws:"}//${window.location.host}/_ws`,e=null,n=null,l=()=>{e=new WebSocket(t),e.addEventListener("open",()=>{console.log("[llmd] Connected to file watcher"),e?.send(JSON.stringify({type:"watch",file:o}))}),e.addEventListener("message",(i)=>{try{let a=JSON.parse(i.data);if(a.type==="reload"&&a.file===o)console.log(`[llmd] File changed: ${o}, reloading...`),window.location.reload()}catch(a){console.error("[llmd] Failed to parse message:",a)}}),e.addEventListener("close",()=>{console.log("[llmd] Disconnected from file watcher"),n=window.setTimeout(()=>{console.log("[llmd] Reconnecting..."),l()},2000)}),e.addEventListener("error",(i)=>{console.error("[llmd] WebSocket error:",i),e?.close()})};l(),window.addEventListener("beforeunload",()=>{if(n)clearTimeout(n);e?.close()})};window.connectFileWatcher=p;console.log("[llmd] Client initialized");
1
+ var Q=()=>{let y=document.querySelectorAll(".dir-label");for(let m of Array.from(y)){let C=m,L=document.createElement("span");L.className="dir-chevron",L.innerHTML='<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><polyline points="6 9 12 15 18 9"></polyline></svg>',C.insertBefore(L,C.firstChild),C.style.cursor="pointer",C.addEventListener("click",(q)=>{q.preventDefault();let B=C.closest(".dir-item");if(B)B.classList.toggle("collapsed")})}},Y=()=>{let y=document.querySelector(".toc");if(!y)return;let m=y.querySelector("h3");if(!m)return;let C=document.createElement("span");C.className="toc-chevron",C.innerHTML='<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><polyline points="6 9 12 15 18 9"></polyline></svg>',m.style.cursor="pointer",m.insertBefore(C,m.firstChild),y.classList.add("collapsed"),m.addEventListener("click",()=>{y.classList.toggle("collapsed")})};if(typeof window<"u")window.addEventListener("DOMContentLoaded",()=>{Q(),Y()});var u=()=>{for(let y of Array.from(document.querySelectorAll("pre code"))){let m=y.parentElement;if(!m)continue;if(m.querySelector(".copy-button"))continue;let C=document.createElement("button");C.className="copy-button",C.textContent="Copy",C.setAttribute("aria-label","Copy code to clipboard"),C.addEventListener("click",async()=>{try{await navigator.clipboard.writeText(y.textContent||""),C.textContent="Copied!",C.classList.add("copied"),setTimeout(()=>{C.textContent="Copy",C.classList.remove("copied")},2000)}catch(L){console.error("Failed to copy:",L),C.textContent="Failed",setTimeout(()=>{C.textContent="Copy"},2000)}}),m.appendChild(C)}};if(document.readyState==="loading")document.addEventListener("DOMContentLoaded",u);else u();var Z=(y)=>{let C=`${window.location.protocol==="https:"?"wss:":"ws:"}//${window.location.host}/_ws`,L=null,q=null,B=()=>{L=new WebSocket(C),L.addEventListener("open",()=>{console.log("[llmd] Connected to file watcher"),L?.send(JSON.stringify({type:"watch",file:y}))}),L.addEventListener("message",(M)=>{try{let x=JSON.parse(M.data);if(x.type==="reload"&&x.file===y)console.log(`[llmd] File changed: ${y}, reloading...`),window.location.reload()}catch(x){console.error("[llmd] Failed to parse message:",x)}}),L.addEventListener("close",()=>{console.log("[llmd] Disconnected from file watcher"),q=window.setTimeout(()=>{console.log("[llmd] Reconnecting..."),B()},2000)}),L.addEventListener("error",(M)=>{console.error("[llmd] WebSocket error:",M),L?.close()})};B(),window.addEventListener("beforeunload",()=>{if(q)clearTimeout(q);L?.close()})};window.connectFileWatcher=Z;var _=()=>{let y=document.querySelector(".sidebar");if(!y)return;let m=document.createElement("div");m.className="sidebar-resize-handle",y.appendChild(m);let C=!1,L=0,q=0,B=(A)=>{C=!0,L=A.clientX,q=y.offsetWidth,document.body.style.cursor="ew-resize",document.body.style.userSelect="none",A.preventDefault()},M=(A)=>{if(!C)return;let K=A.clientX-L,O=q+K,k=Math.max(200,Math.min(600,O));y.style.width=`${k}px`,localStorage.setItem("llmd-sidebar-width",k.toString())},x=()=>{if(!C)return;C=!1,document.body.style.cursor="",document.body.style.userSelect=""};m.addEventListener("mousedown",B),document.addEventListener("mousemove",M),document.addEventListener("mouseup",x);let j=localStorage.getItem("llmd-sidebar-width");if(j){let A=Number.parseInt(j,10);if(A>=200&&A<=600)y.style.width=`${A}px`}};if(typeof window<"u")window.addEventListener("DOMContentLoaded",()=>{_()});var E=(y,m,C)=>{fetch("/api/events",{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({type:y,path:m,resourceType:C})}).catch(()=>{})},G=(y)=>{E("open",y,"dir")},J=(y)=>{E("view",y,"file")};window.trackDirectoryOpen=G;window.trackFileView=J;console.log("[llmd] Client initialized");