mrmd-server 0.1.19 → 0.1.20

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "mrmd-server",
3
- "version": "0.1.19",
3
+ "version": "0.1.20",
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",
package/src/ai-service.js CHANGED
@@ -3,12 +3,15 @@
3
3
  *
4
4
  * The AI server is shared across all sessions (stateless).
5
5
  * It's started once on first request and kept running.
6
+ *
7
+ * API keys are passed as environment variables to the AI server process
8
+ * because dspy/litellm reads them from env vars.
6
9
  */
7
10
 
8
11
  import { spawn, execSync } from 'child_process';
9
12
  import net from 'net';
10
13
  import path from 'path';
11
- import { existsSync } from 'fs';
14
+ import { existsSync, readFileSync } from 'fs';
12
15
  import os from 'os';
13
16
 
14
17
  // AI server singleton
@@ -75,11 +78,54 @@ function findUv() {
75
78
  return null;
76
79
  }
77
80
 
81
+ /**
82
+ * Load API keys from settings file
83
+ */
84
+ function loadApiKeysFromSettings() {
85
+ const settingsPath = path.join(os.homedir(), '.mrmd', 'settings.json');
86
+ try {
87
+ if (existsSync(settingsPath)) {
88
+ const settings = JSON.parse(readFileSync(settingsPath, 'utf-8'));
89
+ return settings.apiKeys || {};
90
+ }
91
+ } catch (e) {
92
+ console.warn('[ai] Failed to load API keys from settings:', e.message);
93
+ }
94
+ return {};
95
+ }
96
+
97
+ /**
98
+ * Build environment variables for AI server from API keys
99
+ */
100
+ function buildEnvFromApiKeys(apiKeys) {
101
+ const env = { ...process.env };
102
+
103
+ // Map settings keys to litellm environment variable names
104
+ const envMapping = {
105
+ anthropic: 'ANTHROPIC_API_KEY',
106
+ openai: 'OPENAI_API_KEY',
107
+ groq: 'GROQ_API_KEY',
108
+ gemini: 'GEMINI_API_KEY',
109
+ openrouter: 'OPENROUTER_API_KEY',
110
+ };
111
+
112
+ for (const [provider, envVar] of Object.entries(envMapping)) {
113
+ if (apiKeys[provider]) {
114
+ env[envVar] = apiKeys[provider];
115
+ console.log(`[ai] Setting ${envVar} from settings`);
116
+ }
117
+ }
118
+
119
+ return env;
120
+ }
121
+
78
122
  /**
79
123
  * Ensure AI server is running
124
+ * @param {Object} options
125
+ * @param {Object} [options.apiKeys] - API keys to pass as env vars (optional, will load from settings if not provided)
80
126
  * Returns { port, url, success } or { error, success: false }
81
127
  */
82
- export async function ensureAiServer() {
128
+ export async function ensureAiServer(options = {}) {
83
129
  // Already running
84
130
  if (aiServer) {
85
131
  return {
@@ -103,6 +149,10 @@ export async function ensureAiServer() {
103
149
  };
104
150
  }
105
151
 
152
+ // Load API keys - from options or settings file
153
+ const apiKeys = options.apiKeys || loadApiKeysFromSettings();
154
+ const env = buildEnvFromApiKeys(apiKeys);
155
+
106
156
  const port = await findFreePort();
107
157
  console.log(`[ai] Starting mrmd-ai on port ${port}...`);
108
158
 
@@ -113,6 +163,7 @@ export async function ensureAiServer() {
113
163
  '--port', port.toString(),
114
164
  ], {
115
165
  stdio: ['pipe', 'pipe', 'pipe'],
166
+ env, // Pass API keys as environment variables
116
167
  });
117
168
 
118
169
  proc.stdout.on('data', (d) => console.log('[ai]', d.toString().trim()));
@@ -182,3 +233,12 @@ export function stopAiServer() {
182
233
  startPromise = null;
183
234
  }
184
235
  }
236
+
237
+ /**
238
+ * Restart AI server with new API keys
239
+ * Call this when API keys change in settings
240
+ */
241
+ export async function restartAiServer(apiKeys) {
242
+ stopAiServer();
243
+ return ensureAiServer({ apiKeys });
244
+ }
package/src/api/system.js CHANGED
@@ -10,7 +10,7 @@ import path from 'path';
10
10
  import fs from 'fs/promises';
11
11
  import { existsSync } from 'fs';
12
12
  import { spawn, execSync } from 'child_process';
13
- import { ensureAiServer, getAiServer } from '../ai-service.js';
13
+ import { ensureAiServer, getAiServer, restartAiServer } from '../ai-service.js';
14
14
 
15
15
  /**
16
16
  * Create system routes
@@ -210,6 +210,23 @@ export function createSystemRoutes(ctx) {
210
210
  res.json(getAiServer());
211
211
  });
212
212
 
213
+ /**
214
+ * POST /api/system/ai/restart
215
+ * Restart AI server with new API keys
216
+ * Call this after changing API keys in settings
217
+ */
218
+ router.post('/ai/restart', async (req, res) => {
219
+ try {
220
+ const { apiKeys } = req.body;
221
+ console.log('[system:ai:restart] Restarting AI server with new API keys');
222
+ const result = await restartAiServer(apiKeys);
223
+ res.json(result);
224
+ } catch (err) {
225
+ console.error('[system:ai:restart]', err);
226
+ res.status(500).json({ success: false, error: err.message });
227
+ }
228
+ });
229
+
213
230
  /**
214
231
  * POST /api/system/discover-venvs
215
232
  * Discover virtual environments