mrmd-server 0.1.21 → 0.1.23

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
@@ -1,17 +1,19 @@
1
- # mrmd-server
1
+ # MRMD Server
2
2
 
3
- Run mrmd in any browser. Access your notebooks from anywhere.
3
+ **Run MRMD in any browser.** Access your markdown notebooks from anywhere — your phone, tablet, or any machine with a browser.
4
+
5
+ [![npm version](https://img.shields.io/npm/v/mrmd-server)](https://www.npmjs.com/package/mrmd-server) [![License: MIT](https://img.shields.io/badge/License-MIT-blue.svg)](LICENSE)
4
6
 
5
7
  ```
6
8
  ┌─────────────────────────────────────────────────────────────┐
7
- │ Your VPS / Cloud Server
9
+ │ Your Server / VPS / Cloud
8
10
  │ │
9
11
  │ ┌─────────────────────────────────────────────────────┐ │
10
12
  │ │ mrmd-server │ │
11
- │ │ • HTTP API (full electronAPI equivalent) │ │
12
- │ │ • Static file serving │ │
13
- │ │ • WebSocket for real-time events │ │
14
- │ │ • Token authentication │ │
13
+ │ │ • Full MRMD UI served over HTTP │ │
14
+ │ │ • Code execution (Python, JS, Bash, R, Julia) │ │
15
+ │ │ • Real-time collaboration via WebSocket │ │
16
+ │ │ • Token-based authentication │ │
15
17
  │ └─────────────────────────────────────────────────────┘ │
16
18
  │ │
17
19
  └─────────────────────────────────────────────────────────────┘
@@ -25,23 +27,65 @@ Run mrmd in any browser. Access your notebooks from anywhere.
25
27
  └───────┘ └─────────┘ └─────────┘
26
28
  ```
27
29
 
30
+ ---
31
+
32
+ ## What is MRMD Server?
33
+
34
+ MRMD Server is the headless/server version of [MRMD Electron](https://github.com/MaximeRivest/mrmd-electron). It provides the same markdown notebook experience without requiring a desktop app — just start the server and open it in any browser.
35
+
36
+ **Use cases:**
37
+ - Run notebooks on a remote GPU server, access from your laptop
38
+ - Host shared notebooks for a team
39
+ - Access your notebooks from your phone or tablet
40
+ - Deploy on a cloud VM for always-on compute
41
+
42
+ ---
43
+
28
44
  ## Features
29
45
 
30
- - **Same UI as Electron** - Uses the exact same index.html from mrmd-electron
31
- - **Access from anywhere** - Phone, tablet, any browser
32
- - **Real-time collaboration** - Yjs sync works over WebSocket
33
- - **Token authentication** - Secure access with shareable links
34
- - **Portable compute** - Move your disk to a GPU server when needed
46
+ ### Same UI as Desktop
47
+ The exact same editor, code execution, and collaboration features as MRMD Electron.
35
48
 
36
- ## Quick Start
49
+ ### Access from Anywhere
50
+ Open your notebooks in any browser — phone, tablet, another computer.
51
+
52
+ ### Real-Time Collaboration
53
+ Share the URL with teammates. Changes sync instantly via Yjs CRDT.
54
+
55
+ ### Token Authentication
56
+ Secure access with auto-generated or custom tokens. Share links safely.
57
+
58
+ ### Portable Compute
59
+ Start the server wherever your data lives. GPU machine? Local workstation? Cloud VM? Just run `mrmd-server`.
60
+
61
+ ---
62
+
63
+ ## Installation
64
+
65
+ ### npm (recommended)
37
66
 
38
67
  ```bash
39
- # Install
40
- cd mrmd-packages/mrmd-server
41
- npm install
68
+ npm install -g mrmd-server
69
+ ```
42
70
 
43
- # Start server in your project directory
71
+ ### npx (no install)
72
+
73
+ ```bash
44
74
  npx mrmd-server ./my-notebooks
75
+ ```
76
+
77
+ ### Requirements
78
+
79
+ - **Node.js 18+**
80
+ - **Python 3.11+** with [uv](https://github.com/astral-sh/uv) (for Python execution)
81
+
82
+ ---
83
+
84
+ ## Quick Start
85
+
86
+ ```bash
87
+ # Start server in your project directory
88
+ mrmd-server ./my-notebooks
45
89
 
46
90
  # Output:
47
91
  # mrmd-server
@@ -54,9 +98,26 @@ npx mrmd-server ./my-notebooks
54
98
  # http://localhost:8080?token=abc123xyz...
55
99
  ```
56
100
 
101
+ Open the Access URL in your browser. That's it.
102
+
103
+ ---
104
+
57
105
  ## Usage
58
106
 
59
- ### Basic Usage
107
+ ### Command Line Options
108
+
109
+ ```bash
110
+ mrmd-server [options] [project-dir]
111
+
112
+ Options:
113
+ -p, --port <port> HTTP port (default: 8080)
114
+ -h, --host <host> Bind address (default: 0.0.0.0)
115
+ -t, --token <token> Auth token (auto-generated if not provided)
116
+ --no-auth Disable authentication (local dev only!)
117
+ --help Show help
118
+ ```
119
+
120
+ ### Examples
60
121
 
61
122
  ```bash
62
123
  # Start in current directory
@@ -68,163 +129,185 @@ mrmd-server ./my-project
68
129
  # Custom port
69
130
  mrmd-server -p 3000 ./my-project
70
131
 
71
- # With specific token
132
+ # With specific token (for automation)
72
133
  mrmd-server -t my-secret-token ./my-project
73
134
 
74
135
  # No auth (local development only!)
75
- mrmd-server --no-auth ./my-project
136
+ mrmd-server --no-auth
76
137
  ```
77
138
 
78
- ### Remote Access
139
+ ---
79
140
 
80
- 1. Start mrmd-server on your VPS:
81
- ```bash
82
- mrmd-server -p 8080 /home/you/notebooks
83
- ```
141
+ ## Remote Access Setup
84
142
 
85
- 2. Set up HTTPS (recommended) with nginx or caddy:
86
- ```nginx
87
- server {
88
- listen 443 ssl;
89
- server_name notebooks.example.com;
143
+ ### 1. Start the server on your remote machine
90
144
 
91
- location / {
92
- proxy_pass http://localhost:8080;
93
- proxy_http_version 1.1;
94
- proxy_set_header Upgrade $http_upgrade;
95
- proxy_set_header Connection "upgrade";
96
- }
97
- }
98
- ```
145
+ ```bash
146
+ ssh your-server
147
+ cd /path/to/notebooks
148
+ mrmd-server -p 8080
149
+ ```
150
+
151
+ ### 2. Set up HTTPS with nginx (recommended)
152
+
153
+ ```nginx
154
+ server {
155
+ listen 443 ssl;
156
+ server_name notebooks.example.com;
99
157
 
100
- 3. Access from anywhere:
101
- ```
102
- https://notebooks.example.com?token=YOUR_TOKEN
103
- ```
158
+ ssl_certificate /path/to/cert.pem;
159
+ ssl_certificate_key /path/to/key.pem;
160
+
161
+ location / {
162
+ proxy_pass http://localhost:8080;
163
+ proxy_http_version 1.1;
164
+ proxy_set_header Upgrade $http_upgrade;
165
+ proxy_set_header Connection "upgrade";
166
+ proxy_set_header Host $host;
167
+ }
168
+ }
169
+ ```
104
170
 
105
- ### Share with Collaborators
171
+ ### 3. Access from anywhere
172
+
173
+ ```
174
+ https://notebooks.example.com?token=YOUR_TOKEN
175
+ ```
176
+
177
+ ---
178
+
179
+ ## Sharing & Collaboration
180
+
181
+ Share the URL (including the token) with collaborators:
106
182
 
107
- Just share the URL with the token:
108
183
  ```
109
184
  https://your-server.com?token=abc123xyz
110
185
  ```
111
186
 
112
- Collaborators get:
113
- - Real-time collaborative editing (Yjs)
114
- - Code execution (via the server)
115
- - Same UI as local Electron app
187
+ Everyone with the URL gets:
188
+ - Real-time collaborative editing
189
+ - Code execution on your server
190
+ - Full MRMD features
191
+
192
+ ---
116
193
 
117
194
  ## Architecture
118
195
 
119
- mrmd-server provides an HTTP API that mirrors Electron's IPC interface:
196
+ MRMD Server mirrors the Electron app's IPC interface as an HTTP API:
120
197
 
121
- | Electron (IPC) | mrmd-server (HTTP) |
198
+ | Electron (IPC) | MRMD Server (HTTP) |
122
199
  |----------------|-------------------|
123
200
  | `electronAPI.project.get(path)` | `GET /api/project?path=...` |
124
201
  | `electronAPI.file.write(path, content)` | `POST /api/file/write` |
125
202
  | `electronAPI.session.forDocument(path)` | `POST /api/session/for-document` |
126
203
  | `ipcRenderer.on('project:changed', cb)` | WebSocket `/events` |
127
204
 
128
- The browser loads `http-shim.js` which creates a `window.electronAPI` object that makes HTTP calls instead of IPC calls. The existing UI code works unchanged.
205
+ The browser loads an HTTP shim that creates `window.electronAPI` making HTTP calls instead of IPC. The UI code works unchanged.
206
+
207
+ ```
208
+ ┌─────────────────────────────────────────────────────────────┐
209
+ │ mrmd-server │
210
+ │ ┌─────────────┐ ┌─────────────┐ ┌─────────────┐ │
211
+ │ │ Express │ │ mrmd-sync │ │ mrmd- │ │
212
+ │ │ HTTP API │ │ (Yjs) │ │ python/bash │ │
213
+ │ └─────────────┘ └─────────────┘ └─────────────┘ │
214
+ └─────────────────────────────────────────────────────────────┘
215
+ │ │ │
216
+ HTTP/REST WebSocket Execution
217
+ ```
218
+
219
+ ---
129
220
 
130
221
  ## API Reference
131
222
 
132
223
  ### Authentication
133
224
 
134
- All API endpoints (except `/health` and `/auth/validate`) require authentication.
135
-
136
- Provide token via:
225
+ All `/api/*` endpoints require authentication. Provide token via:
137
226
  - Query parameter: `?token=xxx`
138
227
  - Header: `Authorization: Bearer xxx`
139
228
  - Header: `X-Token: xxx`
140
229
 
141
- ### Endpoints
142
-
143
- #### System
144
- - `GET /api/system/home` - Get home directory
145
- - `GET /api/system/recent` - Get recent files/venvs
146
- - `GET /api/system/ai` - Get AI server info
147
- - `POST /api/system/discover-venvs` - Start venv discovery
148
-
149
- #### Project
150
- - `GET /api/project?path=...` - Get project info
151
- - `POST /api/project` - Create project
152
- - `GET /api/project/nav?root=...` - Get navigation tree
153
- - `POST /api/project/watch` - Watch for changes
154
- - `POST /api/project/unwatch` - Stop watching
155
-
156
- #### Session
157
- - `GET /api/session` - List sessions
158
- - `POST /api/session` - Start session
159
- - `DELETE /api/session/:name` - Stop session
160
- - `POST /api/session/for-document` - Get/create session for document
161
-
162
- #### Bash
163
- - Same as Session, at `/api/bash/*`
164
-
165
- #### File
166
- - `GET /api/file/scan` - Scan for files
167
- - `POST /api/file/create` - Create file
168
- - `POST /api/file/create-in-project` - Create with FSML ordering
169
- - `POST /api/file/move` - Move/rename
170
- - `POST /api/file/reorder` - Drag-drop reorder
171
- - `DELETE /api/file?path=...` - Delete file
172
- - `GET /api/file/read?path=...` - Read file
173
- - `POST /api/file/write` - Write file
174
-
175
- #### Asset
176
- - `GET /api/asset` - List assets
177
- - `POST /api/asset/save` - Upload asset
178
- - `GET /api/asset/relative-path` - Calculate relative path
179
- - `GET /api/asset/orphans` - Find orphaned assets
180
- - `DELETE /api/asset` - Delete asset
181
-
182
- #### Runtime
183
- - `GET /api/runtime` - List runtimes
184
- - `DELETE /api/runtime/:id` - Kill runtime
185
- - `POST /api/runtime/:id/attach` - Attach to runtime
230
+ ### Core Endpoints
231
+
232
+ | Endpoint | Description |
233
+ |----------|-------------|
234
+ | `GET /health` | Health check (no auth) |
235
+ | `GET /auth/validate?token=xxx` | Validate token (no auth) |
236
+ | `GET /api/project?path=...` | Get project info |
237
+ | `GET /api/file/read?path=...` | Read file |
238
+ | `POST /api/file/write` | Write file |
239
+ | `POST /api/session/for-document` | Get/create session for document |
240
+ | `GET /api/runtime` | List active runtimes |
241
+ | `DELETE /api/runtime/:id` | Kill a runtime |
186
242
 
187
243
  ### WebSocket Events
188
244
 
189
- Connect to `/events?token=xxx` to receive push events:
245
+ Connect to `/events?token=xxx` for real-time updates:
190
246
 
191
247
  ```javascript
192
248
  const ws = new WebSocket('wss://server.com/events?token=xxx');
193
249
  ws.onmessage = (e) => {
194
250
  const { event, data } = JSON.parse(e.data);
195
- // event: 'project:changed', 'venv-found', 'sync-server-died', etc.
251
+ // Events: 'project:changed', 'venv-found', 'sync-server-died', etc.
196
252
  };
197
253
  ```
198
254
 
199
- ## Security Considerations
255
+ ---
256
+
257
+ ## Security
258
+
259
+ 1. **Always use HTTPS** in production — use nginx, caddy, or a cloud load balancer
260
+ 2. **Keep tokens secret** — treat them like passwords
261
+ 3. **Never use `--no-auth` on public networks**
262
+ 4. **Rotate tokens** if you suspect they're compromised
200
263
 
201
- 1. **Always use HTTPS** in production (use nginx/caddy as reverse proxy)
202
- 2. **Keep tokens secret** - treat them like passwords
203
- 3. **Use `--no-auth` only for local development**
204
- 4. **Rotate tokens** if compromised
264
+ ---
205
265
 
206
266
  ## Limitations
207
267
 
208
- Some Electron features can't work in browser:
268
+ Some desktop features don't work in browser mode:
209
269
 
210
270
  | Feature | Browser Behavior |
211
271
  |---------|------------------|
212
- | `shell.showItemInFolder` | Returns path (can't open Finder) |
213
- | `shell.openPath` | Returns path (can't open local apps) |
214
- | Native titlebar | Standard browser chrome |
215
- | Offline | Requires server connection |
272
+ | "Show in Finder" | Returns path (can't open native file browser) |
273
+ | Native window controls | Standard browser chrome |
274
+ | Offline mode | Requires server connection |
275
+
276
+ ---
216
277
 
217
278
  ## Development
218
279
 
219
280
  ```bash
281
+ # Clone the monorepo
282
+ git clone https://github.com/MaximeRivest/mrmd-packages.git
283
+ cd mrmd-packages/mrmd-server
284
+
285
+ # Install dependencies
286
+ npm install
287
+
220
288
  # Run in dev mode
221
289
  npm run dev
222
-
223
- # The server will:
224
- # - Watch for file changes
225
- # - Auto-restart on changes
226
290
  ```
227
291
 
292
+ ---
293
+
294
+ ## Related Projects
295
+
296
+ | Project | Description |
297
+ |---------|-------------|
298
+ | [MRMD Electron](https://github.com/MaximeRivest/mrmd-electron) | Desktop app (macOS, Windows, Linux) |
299
+ | [mrmd-python](https://github.com/MaximeRivest/mrmd-python) | Python execution runtime |
300
+ | [mrmd-editor](https://github.com/MaximeRivest/mrmd-editor) | CodeMirror-based editor component |
301
+ | [mrmd-sync](https://github.com/MaximeRivest/mrmd-sync) | Yjs collaboration server |
302
+
303
+ ---
304
+
228
305
  ## License
229
306
 
230
- MIT
307
+ MIT License. See [LICENSE](LICENSE) for details.
308
+
309
+ ---
310
+
311
+ <p align="center">
312
+ <b>MRMD Server</b> — Your notebooks, anywhere.
313
+ </p>
package/bin/cli.js CHANGED
@@ -23,7 +23,7 @@ function parseArgs(args) {
23
23
  const options = {
24
24
  port: 8080,
25
25
  host: '0.0.0.0',
26
- token: null,
26
+ token: undefined,
27
27
  noAuth: false,
28
28
  projectDir: '.',
29
29
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "mrmd-server",
3
- "version": "0.1.21",
3
+ "version": "0.1.23",
4
4
  "description": "HTTP server for mrmd - run mrmd in any browser, access from anywhere",
5
5
  "type": "module",
6
6
  "main": "src/index.js",
@@ -45,8 +45,8 @@
45
45
  "multer": "^1.4.5-lts.1",
46
46
  "chokidar": "^3.6.0",
47
47
  "fzf": "^0.5.2",
48
- "mrmd-project": "^0.1.1",
48
+ "mrmd-project": "^0.1.2",
49
49
  "mrmd-electron": "^0.3.4",
50
- "mrmd-sync": "^0.3.2"
50
+ "mrmd-sync": "^0.3.3"
51
51
  }
52
52
  }
package/src/api/file.js CHANGED
@@ -27,8 +27,8 @@ export function createFileRoutes(ctx) {
27
27
  const os = await import('os');
28
28
  const root = req.query.root || ctx.projectDir || process.cwd() || os.default.homedir();
29
29
  const options = {
30
- // Default to both .md and .ipynb (like Electron)
31
- extensions: req.query.extensions?.split(',') || ['.md', '.ipynb'],
30
+ // Default to markdown-like docs and .ipynb (like Electron)
31
+ extensions: req.query.extensions?.split(',') || ['.md', '.qmd', '.ipynb'],
32
32
  maxDepth: parseInt(req.query.maxDepth) || 10,
33
33
  includeHidden: req.query.includeHidden === 'true',
34
34
  };
@@ -58,10 +58,10 @@ export function createFileRoutes(ctx) {
58
58
  }
59
59
 
60
60
  const fullPath = resolvePath(ctx.projectDir, filePath);
61
- const result = await fileService.createFile(fullPath, content);
61
+ await fileService.createFile(fullPath, content);
62
62
 
63
63
  ctx.eventBus.projectChanged(ctx.projectDir);
64
- res.json({ success: true, path: result });
64
+ res.json({ success: true, path: fullPath });
65
65
  } catch (err) {
66
66
  if (err.message?.includes('already exists')) {
67
67
  return res.status(409).json({ error: 'File already exists' });
@@ -89,7 +89,7 @@ export function createFileRoutes(ctx) {
89
89
  ctx.eventBus.projectChanged(root);
90
90
  res.json({
91
91
  success: true,
92
- path: result.relativePath,
92
+ path: result,
93
93
  });
94
94
  } catch (err) {
95
95
  console.error('[file:createInProject]', err);
@@ -239,7 +239,7 @@ export function createFileRoutes(ctx) {
239
239
  const content = await fileService.read(fullPath);
240
240
  const previewLines = content.split('\n').slice(0, lines).join('\n');
241
241
 
242
- res.json({ success: true, content: previewLines });
242
+ res.json({ success: true, content: previewLines, preview: previewLines });
243
243
  } catch (err) {
244
244
  if (err.code === 'ENOENT') {
245
245
  return res.status(404).json({ success: false, error: 'File not found' });