mrmd-server 0.1.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.
- package/README.md +230 -0
- package/bin/cli.js +161 -0
- package/package.json +35 -0
- package/src/api/asset.js +283 -0
- package/src/api/bash.js +293 -0
- package/src/api/file.js +407 -0
- package/src/api/index.js +11 -0
- package/src/api/julia.js +345 -0
- package/src/api/project.js +296 -0
- package/src/api/pty.js +401 -0
- package/src/api/runtime.js +140 -0
- package/src/api/session.js +358 -0
- package/src/api/system.js +256 -0
- package/src/auth.js +60 -0
- package/src/events.js +50 -0
- package/src/index.js +9 -0
- package/src/server-v2.js +118 -0
- package/src/server.js +297 -0
- package/src/websocket.js +85 -0
- package/static/http-shim.js +371 -0
- package/static/index.html +171 -0
|
@@ -0,0 +1,371 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* http-shim.js - Drop-in replacement for Electron's electronAPI
|
|
3
|
+
*
|
|
4
|
+
* This shim allows the Electron UI (index.html) to work in a browser
|
|
5
|
+
* by replacing IPC calls with HTTP/WebSocket calls to mrmd-server.
|
|
6
|
+
*
|
|
7
|
+
* Usage:
|
|
8
|
+
* <script src="/http-shim.js"></script>
|
|
9
|
+
* <!-- Now window.electronAPI is available -->
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
(function() {
|
|
13
|
+
'use strict';
|
|
14
|
+
|
|
15
|
+
// Get configuration from URL or defaults
|
|
16
|
+
const params = new URLSearchParams(window.location.search);
|
|
17
|
+
const TOKEN = params.get('token') || '';
|
|
18
|
+
const BASE_URL = window.MRMD_SERVER_URL || window.location.origin;
|
|
19
|
+
|
|
20
|
+
// ==========================================================================
|
|
21
|
+
// HTTP Client
|
|
22
|
+
// ==========================================================================
|
|
23
|
+
|
|
24
|
+
async function apiCall(method, path, body = null) {
|
|
25
|
+
const url = new URL(path, BASE_URL);
|
|
26
|
+
if (TOKEN) {
|
|
27
|
+
url.searchParams.set('token', TOKEN);
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
const options = {
|
|
31
|
+
method,
|
|
32
|
+
headers: {
|
|
33
|
+
'Content-Type': 'application/json',
|
|
34
|
+
},
|
|
35
|
+
};
|
|
36
|
+
|
|
37
|
+
if (body !== null) {
|
|
38
|
+
options.body = JSON.stringify(body);
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
const response = await fetch(url.toString(), options);
|
|
42
|
+
|
|
43
|
+
if (!response.ok) {
|
|
44
|
+
const error = await response.json().catch(() => ({ error: response.statusText }));
|
|
45
|
+
throw new Error(error.error || error.message || `HTTP ${response.status}`);
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
return response.json();
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
const GET = (path) => apiCall('GET', path);
|
|
52
|
+
const POST = (path, body) => apiCall('POST', path, body);
|
|
53
|
+
const DELETE = (path) => apiCall('DELETE', path);
|
|
54
|
+
|
|
55
|
+
// ==========================================================================
|
|
56
|
+
// WebSocket for Events
|
|
57
|
+
// ==========================================================================
|
|
58
|
+
|
|
59
|
+
const eventHandlers = {
|
|
60
|
+
'files-update': [],
|
|
61
|
+
'venv-found': [],
|
|
62
|
+
'venv-scan-done': [],
|
|
63
|
+
'project:changed': [],
|
|
64
|
+
'sync-server-died': [],
|
|
65
|
+
};
|
|
66
|
+
|
|
67
|
+
let ws = null;
|
|
68
|
+
let wsReconnectTimer = null;
|
|
69
|
+
|
|
70
|
+
function connectWebSocket() {
|
|
71
|
+
const wsUrl = new URL('/events', BASE_URL);
|
|
72
|
+
wsUrl.protocol = wsUrl.protocol === 'https:' ? 'wss:' : 'ws:';
|
|
73
|
+
if (TOKEN) {
|
|
74
|
+
wsUrl.searchParams.set('token', TOKEN);
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
ws = new WebSocket(wsUrl.toString());
|
|
78
|
+
|
|
79
|
+
ws.onopen = () => {
|
|
80
|
+
console.log('[http-shim] WebSocket connected');
|
|
81
|
+
if (wsReconnectTimer) {
|
|
82
|
+
clearTimeout(wsReconnectTimer);
|
|
83
|
+
wsReconnectTimer = null;
|
|
84
|
+
}
|
|
85
|
+
};
|
|
86
|
+
|
|
87
|
+
ws.onmessage = (e) => {
|
|
88
|
+
try {
|
|
89
|
+
const { event, data } = JSON.parse(e.data);
|
|
90
|
+
const handlers = eventHandlers[event];
|
|
91
|
+
if (handlers) {
|
|
92
|
+
handlers.forEach(cb => {
|
|
93
|
+
try {
|
|
94
|
+
cb(data);
|
|
95
|
+
} catch (err) {
|
|
96
|
+
console.error('[http-shim] Event handler error:', err);
|
|
97
|
+
}
|
|
98
|
+
});
|
|
99
|
+
}
|
|
100
|
+
} catch (err) {
|
|
101
|
+
console.error('[http-shim] WebSocket message error:', err);
|
|
102
|
+
}
|
|
103
|
+
};
|
|
104
|
+
|
|
105
|
+
ws.onclose = () => {
|
|
106
|
+
console.log('[http-shim] WebSocket disconnected, reconnecting in 2s...');
|
|
107
|
+
wsReconnectTimer = setTimeout(connectWebSocket, 2000);
|
|
108
|
+
};
|
|
109
|
+
|
|
110
|
+
ws.onerror = (err) => {
|
|
111
|
+
console.error('[http-shim] WebSocket error:', err);
|
|
112
|
+
};
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
// Connect WebSocket on load
|
|
116
|
+
connectWebSocket();
|
|
117
|
+
|
|
118
|
+
// ==========================================================================
|
|
119
|
+
// electronAPI Shim
|
|
120
|
+
// ==========================================================================
|
|
121
|
+
|
|
122
|
+
window.electronAPI = {
|
|
123
|
+
// ========================================================================
|
|
124
|
+
// System
|
|
125
|
+
// ========================================================================
|
|
126
|
+
|
|
127
|
+
getHomeDir: () => GET('/api/system/home').then(r => r.homeDir),
|
|
128
|
+
|
|
129
|
+
getRecent: () => GET('/api/system/recent'),
|
|
130
|
+
|
|
131
|
+
getAi: () => GET('/api/system/ai'),
|
|
132
|
+
|
|
133
|
+
// ========================================================================
|
|
134
|
+
// Shell (stubs for browser)
|
|
135
|
+
// ========================================================================
|
|
136
|
+
|
|
137
|
+
shell: {
|
|
138
|
+
showItemInFolder: async (fullPath) => {
|
|
139
|
+
console.log('[http-shim] showItemInFolder not available in browser:', fullPath);
|
|
140
|
+
// Could show a toast with the path
|
|
141
|
+
return { success: false, path: fullPath };
|
|
142
|
+
},
|
|
143
|
+
|
|
144
|
+
openExternal: async (url) => {
|
|
145
|
+
window.open(url, '_blank');
|
|
146
|
+
return { success: true };
|
|
147
|
+
},
|
|
148
|
+
|
|
149
|
+
openPath: async (fullPath) => {
|
|
150
|
+
console.log('[http-shim] openPath not available in browser:', fullPath);
|
|
151
|
+
return { success: false, path: fullPath };
|
|
152
|
+
},
|
|
153
|
+
},
|
|
154
|
+
|
|
155
|
+
// ========================================================================
|
|
156
|
+
// File scanning
|
|
157
|
+
// ========================================================================
|
|
158
|
+
|
|
159
|
+
scanFiles: (searchDir) => GET(`/api/file/scan?root=${encodeURIComponent(searchDir || '')}`),
|
|
160
|
+
|
|
161
|
+
onFilesUpdate: (callback) => {
|
|
162
|
+
eventHandlers['files-update'].push(callback);
|
|
163
|
+
},
|
|
164
|
+
|
|
165
|
+
// ========================================================================
|
|
166
|
+
// Venv discovery
|
|
167
|
+
// ========================================================================
|
|
168
|
+
|
|
169
|
+
discoverVenvs: (projectDir) => POST('/api/system/discover-venvs', { projectDir }),
|
|
170
|
+
|
|
171
|
+
onVenvFound: (callback) => {
|
|
172
|
+
eventHandlers['venv-found'].push(callback);
|
|
173
|
+
},
|
|
174
|
+
|
|
175
|
+
onVenvScanDone: (callback) => {
|
|
176
|
+
eventHandlers['venv-scan-done'].push(callback);
|
|
177
|
+
},
|
|
178
|
+
|
|
179
|
+
// ========================================================================
|
|
180
|
+
// File info
|
|
181
|
+
// ========================================================================
|
|
182
|
+
|
|
183
|
+
readPreview: (filePath, lines) =>
|
|
184
|
+
GET(`/api/file/preview?path=${encodeURIComponent(filePath)}&lines=${lines || 40}`)
|
|
185
|
+
.then(r => r.content),
|
|
186
|
+
|
|
187
|
+
getFileInfo: (filePath) =>
|
|
188
|
+
GET(`/api/file/info?path=${encodeURIComponent(filePath)}`),
|
|
189
|
+
|
|
190
|
+
// ========================================================================
|
|
191
|
+
// Python management
|
|
192
|
+
// ========================================================================
|
|
193
|
+
|
|
194
|
+
installMrmdPython: (venvPath) =>
|
|
195
|
+
POST('/api/system/install-mrmd-python', { venvPath }),
|
|
196
|
+
|
|
197
|
+
startPython: (venvPath, forceNew = false) =>
|
|
198
|
+
POST('/api/runtime/start-python', { venvPath, forceNew }),
|
|
199
|
+
|
|
200
|
+
// ========================================================================
|
|
201
|
+
// Runtime management
|
|
202
|
+
// ========================================================================
|
|
203
|
+
|
|
204
|
+
listRuntimes: () => GET('/api/runtime'),
|
|
205
|
+
|
|
206
|
+
killRuntime: (runtimeId) => DELETE(`/api/runtime/${encodeURIComponent(runtimeId)}`),
|
|
207
|
+
|
|
208
|
+
attachRuntime: (runtimeId) => POST(`/api/runtime/${encodeURIComponent(runtimeId)}/attach`, {}),
|
|
209
|
+
|
|
210
|
+
// ========================================================================
|
|
211
|
+
// Open file (legacy)
|
|
212
|
+
// ========================================================================
|
|
213
|
+
|
|
214
|
+
openFile: async (filePath) => {
|
|
215
|
+
// This was used to open a file and get session info
|
|
216
|
+
// We'll get project info and session info separately
|
|
217
|
+
const project = await window.electronAPI.project.get(filePath);
|
|
218
|
+
const session = await window.electronAPI.session.forDocument(filePath);
|
|
219
|
+
return { project, session };
|
|
220
|
+
},
|
|
221
|
+
|
|
222
|
+
// ========================================================================
|
|
223
|
+
// PROJECT SERVICE
|
|
224
|
+
// ========================================================================
|
|
225
|
+
|
|
226
|
+
project: {
|
|
227
|
+
get: (filePath) =>
|
|
228
|
+
GET(`/api/project?path=${encodeURIComponent(filePath)}`),
|
|
229
|
+
|
|
230
|
+
create: (targetPath) =>
|
|
231
|
+
POST('/api/project', { targetPath }),
|
|
232
|
+
|
|
233
|
+
nav: (projectRoot) =>
|
|
234
|
+
GET(`/api/project/nav?root=${encodeURIComponent(projectRoot)}`),
|
|
235
|
+
|
|
236
|
+
invalidate: (projectRoot) =>
|
|
237
|
+
POST('/api/project/invalidate', { projectRoot }),
|
|
238
|
+
|
|
239
|
+
watch: (projectRoot) =>
|
|
240
|
+
POST('/api/project/watch', { projectRoot }),
|
|
241
|
+
|
|
242
|
+
unwatch: () =>
|
|
243
|
+
POST('/api/project/unwatch', {}),
|
|
244
|
+
|
|
245
|
+
onChanged: (callback) => {
|
|
246
|
+
// Remove existing handlers to prevent duplicates (matches Electron behavior)
|
|
247
|
+
eventHandlers['project:changed'] = [callback];
|
|
248
|
+
},
|
|
249
|
+
},
|
|
250
|
+
|
|
251
|
+
// ========================================================================
|
|
252
|
+
// SESSION SERVICE
|
|
253
|
+
// ========================================================================
|
|
254
|
+
|
|
255
|
+
session: {
|
|
256
|
+
list: () => GET('/api/session'),
|
|
257
|
+
|
|
258
|
+
start: (config) => POST('/api/session', { config }),
|
|
259
|
+
|
|
260
|
+
stop: (sessionName) => DELETE(`/api/session/${encodeURIComponent(sessionName)}`),
|
|
261
|
+
|
|
262
|
+
restart: (sessionName) => POST(`/api/session/${encodeURIComponent(sessionName)}/restart`, {}),
|
|
263
|
+
|
|
264
|
+
forDocument: (documentPath) => POST('/api/session/for-document', { documentPath }),
|
|
265
|
+
},
|
|
266
|
+
|
|
267
|
+
// ========================================================================
|
|
268
|
+
// BASH SESSION SERVICE
|
|
269
|
+
// ========================================================================
|
|
270
|
+
|
|
271
|
+
bash: {
|
|
272
|
+
list: () => GET('/api/bash'),
|
|
273
|
+
|
|
274
|
+
start: (config) => POST('/api/bash', { config }),
|
|
275
|
+
|
|
276
|
+
stop: (sessionName) => DELETE(`/api/bash/${encodeURIComponent(sessionName)}`),
|
|
277
|
+
|
|
278
|
+
restart: (sessionName) => POST(`/api/bash/${encodeURIComponent(sessionName)}/restart`, {}),
|
|
279
|
+
|
|
280
|
+
forDocument: (documentPath) => POST('/api/bash/for-document', { documentPath }),
|
|
281
|
+
},
|
|
282
|
+
|
|
283
|
+
// ========================================================================
|
|
284
|
+
// FILE SERVICE
|
|
285
|
+
// ========================================================================
|
|
286
|
+
|
|
287
|
+
file: {
|
|
288
|
+
scan: (root, options = {}) => {
|
|
289
|
+
const params = new URLSearchParams();
|
|
290
|
+
if (root) params.set('root', root);
|
|
291
|
+
if (options.extensions) params.set('extensions', options.extensions.join(','));
|
|
292
|
+
if (options.maxDepth) params.set('maxDepth', options.maxDepth);
|
|
293
|
+
if (options.includeHidden) params.set('includeHidden', 'true');
|
|
294
|
+
return GET(`/api/file/scan?${params.toString()}`);
|
|
295
|
+
},
|
|
296
|
+
|
|
297
|
+
create: (filePath, content = '') =>
|
|
298
|
+
POST('/api/file/create', { filePath, content }),
|
|
299
|
+
|
|
300
|
+
createInProject: (projectRoot, relativePath, content = '') =>
|
|
301
|
+
POST('/api/file/create-in-project', { projectRoot, relativePath, content }),
|
|
302
|
+
|
|
303
|
+
move: (projectRoot, fromPath, toPath) =>
|
|
304
|
+
POST('/api/file/move', { projectRoot, fromPath, toPath }),
|
|
305
|
+
|
|
306
|
+
reorder: (projectRoot, sourcePath, targetPath, position) =>
|
|
307
|
+
POST('/api/file/reorder', { projectRoot, sourcePath, targetPath, position }),
|
|
308
|
+
|
|
309
|
+
delete: (filePath) =>
|
|
310
|
+
DELETE(`/api/file?path=${encodeURIComponent(filePath)}`),
|
|
311
|
+
|
|
312
|
+
read: (filePath) =>
|
|
313
|
+
GET(`/api/file/read?path=${encodeURIComponent(filePath)}`),
|
|
314
|
+
|
|
315
|
+
write: (filePath, content) =>
|
|
316
|
+
POST('/api/file/write', { filePath, content }),
|
|
317
|
+
},
|
|
318
|
+
|
|
319
|
+
// ========================================================================
|
|
320
|
+
// ASSET SERVICE
|
|
321
|
+
// ========================================================================
|
|
322
|
+
|
|
323
|
+
asset: {
|
|
324
|
+
list: (projectRoot) =>
|
|
325
|
+
GET(`/api/asset?projectRoot=${encodeURIComponent(projectRoot || '')}`),
|
|
326
|
+
|
|
327
|
+
save: async (projectRoot, file, filename) => {
|
|
328
|
+
// Convert Uint8Array to base64 for JSON transport
|
|
329
|
+
const base64 = btoa(String.fromCharCode.apply(null, file));
|
|
330
|
+
return POST('/api/asset/save', {
|
|
331
|
+
projectRoot,
|
|
332
|
+
file: base64,
|
|
333
|
+
filename,
|
|
334
|
+
});
|
|
335
|
+
},
|
|
336
|
+
|
|
337
|
+
relativePath: (assetPath, documentPath) =>
|
|
338
|
+
GET(`/api/asset/relative-path?assetPath=${encodeURIComponent(assetPath)}&documentPath=${encodeURIComponent(documentPath)}`)
|
|
339
|
+
.then(r => r.relativePath),
|
|
340
|
+
|
|
341
|
+
orphans: (projectRoot) =>
|
|
342
|
+
GET(`/api/asset/orphans?projectRoot=${encodeURIComponent(projectRoot || '')}`),
|
|
343
|
+
|
|
344
|
+
delete: (projectRoot, assetPath) =>
|
|
345
|
+
DELETE(`/api/asset?projectRoot=${encodeURIComponent(projectRoot || '')}&assetPath=${encodeURIComponent(assetPath)}`),
|
|
346
|
+
},
|
|
347
|
+
|
|
348
|
+
// ========================================================================
|
|
349
|
+
// DATA LOSS PREVENTION
|
|
350
|
+
// ========================================================================
|
|
351
|
+
|
|
352
|
+
onSyncServerDied: (callback) => {
|
|
353
|
+
// Remove existing handlers to prevent duplicates
|
|
354
|
+
eventHandlers['sync-server-died'] = [callback];
|
|
355
|
+
},
|
|
356
|
+
};
|
|
357
|
+
|
|
358
|
+
// ==========================================================================
|
|
359
|
+
// Expose utilities
|
|
360
|
+
// ==========================================================================
|
|
361
|
+
|
|
362
|
+
window.MRMD_HTTP_SHIM = {
|
|
363
|
+
BASE_URL,
|
|
364
|
+
TOKEN,
|
|
365
|
+
reconnectWebSocket: connectWebSocket,
|
|
366
|
+
getWebSocketState: () => ws ? ws.readyState : -1,
|
|
367
|
+
};
|
|
368
|
+
|
|
369
|
+
console.log('[http-shim] electronAPI shim loaded', { BASE_URL, hasToken: !!TOKEN });
|
|
370
|
+
|
|
371
|
+
})();
|
|
@@ -0,0 +1,171 @@
|
|
|
1
|
+
<!DOCTYPE html>
|
|
2
|
+
<html>
|
|
3
|
+
<head>
|
|
4
|
+
<meta charset="UTF-8">
|
|
5
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
6
|
+
<title>mrmd</title>
|
|
7
|
+
|
|
8
|
+
<!-- Load the HTTP shim BEFORE anything else -->
|
|
9
|
+
<script src="/http-shim.js"></script>
|
|
10
|
+
|
|
11
|
+
<style>
|
|
12
|
+
* { margin: 0; padding: 0; box-sizing: border-box; }
|
|
13
|
+
|
|
14
|
+
body {
|
|
15
|
+
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', system-ui, sans-serif;
|
|
16
|
+
background: #0d1117;
|
|
17
|
+
color: #c9d1d9;
|
|
18
|
+
height: 100vh;
|
|
19
|
+
display: flex;
|
|
20
|
+
align-items: center;
|
|
21
|
+
justify-content: center;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
.loading {
|
|
25
|
+
text-align: center;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
.loading h1 {
|
|
29
|
+
font-size: 2rem;
|
|
30
|
+
margin-bottom: 1rem;
|
|
31
|
+
color: #58a6ff;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
.loading p {
|
|
35
|
+
color: #8b949e;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
.spinner {
|
|
39
|
+
width: 40px;
|
|
40
|
+
height: 40px;
|
|
41
|
+
border: 3px solid #30363d;
|
|
42
|
+
border-top-color: #58a6ff;
|
|
43
|
+
border-radius: 50%;
|
|
44
|
+
animation: spin 1s linear infinite;
|
|
45
|
+
margin: 1rem auto;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
@keyframes spin {
|
|
49
|
+
to { transform: rotate(360deg); }
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
.error {
|
|
53
|
+
color: #f85149;
|
|
54
|
+
margin-top: 1rem;
|
|
55
|
+
}
|
|
56
|
+
</style>
|
|
57
|
+
</head>
|
|
58
|
+
<body>
|
|
59
|
+
<div class="loading" id="loading">
|
|
60
|
+
<h1>mrmd</h1>
|
|
61
|
+
<div class="spinner"></div>
|
|
62
|
+
<p>Connecting to server...</p>
|
|
63
|
+
<p class="error" id="error" style="display: none;"></p>
|
|
64
|
+
</div>
|
|
65
|
+
|
|
66
|
+
<script>
|
|
67
|
+
// Validate token and load the full UI
|
|
68
|
+
async function init() {
|
|
69
|
+
const params = new URLSearchParams(window.location.search);
|
|
70
|
+
const token = params.get('token');
|
|
71
|
+
|
|
72
|
+
if (!token) {
|
|
73
|
+
showError('No token provided. Add ?token=YOUR_TOKEN to the URL.');
|
|
74
|
+
return;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
try {
|
|
78
|
+
// Validate token
|
|
79
|
+
const response = await fetch(`/auth/validate?token=${encodeURIComponent(token)}`);
|
|
80
|
+
const result = await response.json();
|
|
81
|
+
|
|
82
|
+
if (!result.valid) {
|
|
83
|
+
showError('Invalid token. Check your access URL.');
|
|
84
|
+
return;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
// Token is valid - load the full UI
|
|
88
|
+
// We'll redirect to the main app or load it inline
|
|
89
|
+
loadMainApp();
|
|
90
|
+
|
|
91
|
+
} catch (err) {
|
|
92
|
+
showError(`Connection failed: ${err.message}`);
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
function showError(message) {
|
|
97
|
+
document.getElementById('error').textContent = message;
|
|
98
|
+
document.getElementById('error').style.display = 'block';
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
async function loadMainApp() {
|
|
102
|
+
// For now, show a simple UI
|
|
103
|
+
// In the full implementation, this would load the mrmd-electron index.html content
|
|
104
|
+
// or use an iframe
|
|
105
|
+
|
|
106
|
+
document.body.innerHTML = `
|
|
107
|
+
<div style="padding: 20px; max-width: 800px; margin: 0 auto;">
|
|
108
|
+
<h1 style="color: #58a6ff; margin-bottom: 20px;">mrmd-server</h1>
|
|
109
|
+
<p style="margin-bottom: 20px; color: #8b949e;">
|
|
110
|
+
Server is running. The full UI will be loaded from mrmd-electron.
|
|
111
|
+
</p>
|
|
112
|
+
|
|
113
|
+
<div style="background: #161b22; border: 1px solid #30363d; border-radius: 8px; padding: 20px;">
|
|
114
|
+
<h3 style="margin-bottom: 10px;">Quick Start</h3>
|
|
115
|
+
<p style="color: #8b949e; margin-bottom: 15px;">
|
|
116
|
+
To use the full mrmd editor, ensure mrmd-electron is available and the server
|
|
117
|
+
is configured to serve its index.html.
|
|
118
|
+
</p>
|
|
119
|
+
|
|
120
|
+
<h4 style="margin-top: 20px; margin-bottom: 10px;">API Status</h4>
|
|
121
|
+
<pre id="status" style="background: #0d1117; padding: 15px; border-radius: 4px; overflow: auto;">Loading...</pre>
|
|
122
|
+
</div>
|
|
123
|
+
|
|
124
|
+
<div style="margin-top: 20px; background: #161b22; border: 1px solid #30363d; border-radius: 8px; padding: 20px;">
|
|
125
|
+
<h3 style="margin-bottom: 10px;">Test electronAPI</h3>
|
|
126
|
+
<button onclick="testAPI()" style="background: #238636; color: white; border: none; padding: 10px 20px; border-radius: 6px; cursor: pointer;">
|
|
127
|
+
Test API
|
|
128
|
+
</button>
|
|
129
|
+
<pre id="test-result" style="background: #0d1117; padding: 15px; border-radius: 4px; overflow: auto; margin-top: 15px; display: none;"></pre>
|
|
130
|
+
</div>
|
|
131
|
+
</div>
|
|
132
|
+
`;
|
|
133
|
+
|
|
134
|
+
// Load status
|
|
135
|
+
try {
|
|
136
|
+
const homeDir = await window.electronAPI.getHomeDir();
|
|
137
|
+
const recent = await window.electronAPI.getRecent();
|
|
138
|
+
|
|
139
|
+
document.getElementById('status').textContent = JSON.stringify({
|
|
140
|
+
connected: true,
|
|
141
|
+
homeDir,
|
|
142
|
+
recentFiles: recent.files?.length || 0,
|
|
143
|
+
recentVenvs: recent.venvs?.length || 0,
|
|
144
|
+
}, null, 2);
|
|
145
|
+
} catch (err) {
|
|
146
|
+
document.getElementById('status').textContent = `Error: ${err.message}`;
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
window.testAPI = async function() {
|
|
151
|
+
const resultEl = document.getElementById('test-result');
|
|
152
|
+
resultEl.style.display = 'block';
|
|
153
|
+
resultEl.textContent = 'Testing...';
|
|
154
|
+
|
|
155
|
+
try {
|
|
156
|
+
const results = {
|
|
157
|
+
homeDir: await window.electronAPI.getHomeDir(),
|
|
158
|
+
recent: await window.electronAPI.getRecent(),
|
|
159
|
+
ai: await window.electronAPI.getAi(),
|
|
160
|
+
};
|
|
161
|
+
|
|
162
|
+
resultEl.textContent = JSON.stringify(results, null, 2);
|
|
163
|
+
} catch (err) {
|
|
164
|
+
resultEl.textContent = `Error: ${err.message}`;
|
|
165
|
+
}
|
|
166
|
+
};
|
|
167
|
+
|
|
168
|
+
init();
|
|
169
|
+
</script>
|
|
170
|
+
</body>
|
|
171
|
+
</html>
|