@fentz26/envcp 1.0.1 → 1.0.2

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 (63) hide show
  1. package/README.md +79 -130
  2. package/__tests__/config.test.ts +65 -0
  3. package/__tests__/crypto.test.ts +76 -0
  4. package/__tests__/http.test.ts +49 -0
  5. package/__tests__/storage.test.ts +94 -0
  6. package/dist/adapters/base.d.ts +1 -2
  7. package/dist/adapters/base.d.ts.map +1 -1
  8. package/dist/adapters/base.js +139 -14
  9. package/dist/adapters/base.js.map +1 -1
  10. package/dist/adapters/gemini.d.ts +1 -0
  11. package/dist/adapters/gemini.d.ts.map +1 -1
  12. package/dist/adapters/gemini.js +13 -99
  13. package/dist/adapters/gemini.js.map +1 -1
  14. package/dist/adapters/openai.d.ts +1 -0
  15. package/dist/adapters/openai.d.ts.map +1 -1
  16. package/dist/adapters/openai.js +13 -99
  17. package/dist/adapters/openai.js.map +1 -1
  18. package/dist/adapters/rest.d.ts +1 -0
  19. package/dist/adapters/rest.d.ts.map +1 -1
  20. package/dist/adapters/rest.js +16 -13
  21. package/dist/adapters/rest.js.map +1 -1
  22. package/dist/cli/index.js +132 -196
  23. package/dist/cli/index.js.map +1 -1
  24. package/dist/config/manager.d.ts.map +1 -1
  25. package/dist/config/manager.js +4 -1
  26. package/dist/config/manager.js.map +1 -1
  27. package/dist/mcp/server.d.ts +1 -16
  28. package/dist/mcp/server.d.ts.map +1 -1
  29. package/dist/mcp/server.js +23 -511
  30. package/dist/mcp/server.js.map +1 -1
  31. package/dist/server/unified.d.ts +1 -0
  32. package/dist/server/unified.d.ts.map +1 -1
  33. package/dist/server/unified.js +31 -19
  34. package/dist/server/unified.js.map +1 -1
  35. package/dist/storage/index.d.ts +2 -0
  36. package/dist/storage/index.d.ts.map +1 -1
  37. package/dist/storage/index.js +18 -4
  38. package/dist/storage/index.js.map +1 -1
  39. package/dist/types.d.ts +10 -0
  40. package/dist/types.d.ts.map +1 -1
  41. package/dist/types.js +2 -0
  42. package/dist/types.js.map +1 -1
  43. package/dist/utils/http.d.ts +13 -1
  44. package/dist/utils/http.d.ts.map +1 -1
  45. package/dist/utils/http.js +65 -2
  46. package/dist/utils/http.js.map +1 -1
  47. package/dist/utils/session.d.ts.map +1 -1
  48. package/dist/utils/session.js +8 -3
  49. package/dist/utils/session.js.map +1 -1
  50. package/jest.config.js +11 -0
  51. package/package.json +4 -3
  52. package/src/adapters/base.ts +147 -16
  53. package/src/adapters/gemini.ts +19 -105
  54. package/src/adapters/openai.ts +19 -105
  55. package/src/adapters/rest.ts +19 -15
  56. package/src/cli/index.ts +135 -259
  57. package/src/config/manager.ts +4 -1
  58. package/src/mcp/server.ts +26 -582
  59. package/src/server/unified.ts +37 -23
  60. package/src/storage/index.ts +22 -6
  61. package/src/types.ts +2 -0
  62. package/src/utils/http.ts +76 -2
  63. package/src/utils/session.ts +13 -8
@@ -2,6 +2,7 @@ import * as fs from 'fs-extra';
2
2
  import * as path from 'path';
3
3
  import { SessionSchema } from '../types.js';
4
4
  import { generateId, encrypt, decrypt } from './crypto.js';
5
+ import * as crypto from 'crypto';
5
6
  export class SessionManager {
6
7
  sessionPath;
7
8
  session = null;
@@ -27,9 +28,11 @@ export class SessionManager {
27
28
  last_access: now.toISOString(),
28
29
  };
29
30
  this.password = password;
31
+ // Store a verification hash instead of the raw password
32
+ const passwordHash = crypto.createHash('sha256').update(password).digest('hex');
30
33
  const sessionData = JSON.stringify({
31
34
  session: this.session,
32
- password: password,
35
+ passwordHash,
33
36
  });
34
37
  const encrypted = encrypt(sessionData, password);
35
38
  await fs.writeFile(this.sessionPath, encrypted, 'utf8');
@@ -48,7 +51,8 @@ export class SessionManager {
48
51
  const decrypted = decrypt(encrypted, pwd);
49
52
  const data = JSON.parse(decrypted);
50
53
  this.session = SessionSchema.parse(data.session);
51
- this.password = data.password;
54
+ // Password is verified by successful decryption — no longer stored in file
55
+ this.password = pwd;
52
56
  if (new Date() > new Date(this.session.expires)) {
53
57
  await this.destroy();
54
58
  return null;
@@ -80,9 +84,10 @@ export class SessionManager {
80
84
  this.session.expires = expires.toISOString();
81
85
  this.session.extensions += 1;
82
86
  this.session.last_access = now.toISOString();
87
+ const passwordHash = crypto.createHash('sha256').update(this.password).digest('hex');
83
88
  const sessionData = JSON.stringify({
84
89
  session: this.session,
85
- password: this.password,
90
+ passwordHash,
86
91
  });
87
92
  const encrypted = encrypt(sessionData, this.password);
88
93
  await fs.writeFile(this.sessionPath, encrypted, 'utf8');
@@ -1 +1 @@
1
- {"version":3,"file":"session.js","sourceRoot":"","sources":["../../src/utils/session.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"session.js","sourceRoot":"","sources":["../../src/utils/session.ts"],"names":[],"mappings":"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"}
package/jest.config.js ADDED
@@ -0,0 +1,11 @@
1
+ export default {
2
+ preset: 'ts-jest/presets/default-esm',
3
+ testEnvironment: 'node',
4
+ extensionsToTreatAsEsm: ['.ts'],
5
+ moduleNameMapper: {
6
+ '^(\\.{1,2}/.*)\\.js$': '$1',
7
+ },
8
+ transform: {
9
+ '^.+\\.ts$': ['ts-jest', { useESM: true }],
10
+ },
11
+ };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@fentz26/envcp",
3
- "version": "1.0.1",
3
+ "version": "1.0.2",
4
4
  "description": "MCP server for secure environment variable management - Keep your secrets safe from AI agents",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",
@@ -29,7 +29,6 @@
29
29
  "@modelcontextprotocol/sdk": "^1.0.0",
30
30
  "chalk": "^4.1.2",
31
31
  "commander": "^11.1.0",
32
-
33
32
  "dotenv": "^16.3.1",
34
33
  "fs-extra": "^11.2.0",
35
34
  "inquirer": "^8.2.6",
@@ -37,11 +36,13 @@
37
36
  "zod": "^3.22.4"
38
37
  },
39
38
  "devDependencies": {
40
-
41
39
  "@types/fs-extra": "^11.0.4",
42
40
  "@types/inquirer": "^9.0.7",
41
+ "@types/jest": "^30.0.0",
43
42
  "@types/js-yaml": "^4.0.9",
44
43
  "@types/node": "^20.10.0",
44
+ "jest": "^30.3.0",
45
+ "ts-jest": "^29.4.9",
45
46
  "typescript": "^5.3.0"
46
47
  },
47
48
  "engines": {
@@ -1,7 +1,7 @@
1
1
  import { StorageManager, LogManager } from '../storage/index.js';
2
2
  import { EnvCPConfig, Variable, ToolDefinition } from '../types.js';
3
3
  import { maskValue } from '../utils/crypto.js';
4
- import { canAccess, isBlacklisted, canAIActiveCheck, requiresUserReference } from '../config/manager.js';
4
+ import { canAccess, isBlacklisted, canAIActiveCheck, validateVariableName, matchesPattern } from '../config/manager.js';
5
5
  import { SessionManager } from '../utils/session.js';
6
6
  import * as fs from 'fs-extra';
7
7
  import * as path from 'path';
@@ -40,6 +40,108 @@ export abstract class BaseAdapter {
40
40
 
41
41
  protected abstract registerTools(): void;
42
42
 
43
+ protected registerDefaultTools(): void {
44
+ const tools: ToolDefinition[] = [
45
+ {
46
+ name: 'envcp_list',
47
+ description: 'List all available environment variable names. Values are never shown.',
48
+ parameters: {
49
+ type: 'object',
50
+ properties: {
51
+ tags: { type: 'array', items: { type: 'string' }, description: 'Filter by tags' },
52
+ },
53
+ },
54
+ handler: async (params) => this.listVariables(params as { tags?: string[] }),
55
+ },
56
+ {
57
+ name: 'envcp_get',
58
+ description: 'Get an environment variable. Returns masked value by default.',
59
+ parameters: {
60
+ type: 'object',
61
+ properties: {
62
+ name: { type: 'string', description: 'Variable name' },
63
+ show_value: { type: 'boolean', description: 'Show actual value (requires user confirmation)' },
64
+ },
65
+ required: ['name'],
66
+ },
67
+ handler: async (params) => this.getVariable(params as { name: string; show_value?: boolean }),
68
+ },
69
+ {
70
+ name: 'envcp_set',
71
+ description: 'Create or update an environment variable.',
72
+ parameters: {
73
+ type: 'object',
74
+ properties: {
75
+ name: { type: 'string', description: 'Variable name' },
76
+ value: { type: 'string', description: 'Variable value' },
77
+ tags: { type: 'array', items: { type: 'string' }, description: 'Tags' },
78
+ description: { type: 'string', description: 'Description' },
79
+ },
80
+ required: ['name', 'value'],
81
+ },
82
+ handler: async (params) => this.setVariable(params as { name: string; value: string; tags?: string[]; description?: string }),
83
+ },
84
+ {
85
+ name: 'envcp_delete',
86
+ description: 'Delete an environment variable.',
87
+ parameters: {
88
+ type: 'object',
89
+ properties: {
90
+ name: { type: 'string', description: 'Variable name' },
91
+ },
92
+ required: ['name'],
93
+ },
94
+ handler: async (params) => this.deleteVariable(params as { name: string }),
95
+ },
96
+ {
97
+ name: 'envcp_sync',
98
+ description: 'Sync variables to .env file.',
99
+ parameters: { type: 'object', properties: {} },
100
+ handler: async () => this.syncToEnv(),
101
+ },
102
+ {
103
+ name: 'envcp_run',
104
+ description: 'Execute a command with environment variables injected.',
105
+ parameters: {
106
+ type: 'object',
107
+ properties: {
108
+ command: { type: 'string', description: 'Command to execute' },
109
+ variables: { type: 'array', items: { type: 'string' }, description: 'Variables to inject' },
110
+ },
111
+ required: ['command', 'variables'],
112
+ },
113
+ handler: async (params) => this.runCommand(params as { command: string; variables: string[] }),
114
+ },
115
+ {
116
+ name: 'envcp_add_to_env',
117
+ description: 'Write a stored variable to a .env file.',
118
+ parameters: {
119
+ type: 'object',
120
+ properties: {
121
+ name: { type: 'string', description: 'Variable name to add' },
122
+ env_file: { type: 'string', description: 'Path to .env file (default: .env)' },
123
+ },
124
+ required: ['name'],
125
+ },
126
+ handler: async (params) => this.addToEnv(params as { name: string; env_file?: string }),
127
+ },
128
+ {
129
+ name: 'envcp_check_access',
130
+ description: 'Check if a variable exists and can be accessed.',
131
+ parameters: {
132
+ type: 'object',
133
+ properties: {
134
+ name: { type: 'string', description: 'Variable name to check' },
135
+ },
136
+ required: ['name'],
137
+ },
138
+ handler: async (params) => this.checkAccess(params as { name: string }),
139
+ },
140
+ ];
141
+
142
+ tools.forEach(tool => this.tools.set(tool.name, tool));
143
+ }
144
+
43
145
  async init(): Promise<void> {
44
146
  await this.logs.init();
45
147
  await this.sessionManager.init();
@@ -158,6 +260,10 @@ export abstract class BaseAdapter {
158
260
  throw new Error('AI write access is disabled');
159
261
  }
160
262
 
263
+ if (!validateVariableName(args.name)) {
264
+ throw new Error(`Invalid variable name '${args.name}'. Must match [A-Za-z_][A-Za-z0-9_]*`);
265
+ }
266
+
161
267
  if (isBlacklisted(args.name, this.config)) {
162
268
  throw new Error(`Variable '${args.name}' is blacklisted`);
163
269
  }
@@ -233,16 +339,15 @@ export abstract class BaseAdapter {
233
339
  continue;
234
340
  }
235
341
 
236
- const excluded = this.config.sync.exclude?.some(pattern => {
237
- const regex = new RegExp('^' + pattern.replace(/\*/g, '.*') + '$');
238
- return regex.test(name);
239
- });
342
+ const excluded = this.config.sync.exclude?.some(pattern => matchesPattern(name, pattern));
240
343
 
241
344
  if (excluded || !variable.sync_to_env) {
242
345
  continue;
243
346
  }
244
347
 
245
- lines.push(`${name}=${variable.value}`);
348
+ const needsQuoting = /[\s#"'\\]/.test(variable.value);
349
+ const val = needsQuoting ? `"${variable.value.replace(/\\/g, '\\\\').replace(/"/g, '\\"')}"` : variable.value;
350
+ lines.push(`${name}=${val}`);
246
351
  }
247
352
 
248
353
  const envPath = path.join(this.projectPath, this.config.sync.target);
@@ -270,7 +375,11 @@ export abstract class BaseAdapter {
270
375
  throw new Error(`Variable '${args.name}' is blacklisted`);
271
376
  }
272
377
 
273
- const envPath = path.join(this.projectPath, args.env_file || '.env');
378
+ const envPath = path.resolve(this.projectPath, args.env_file || '.env');
379
+ if (!envPath.startsWith(path.resolve(this.projectPath))) {
380
+ throw new Error('env_file must be within the project directory');
381
+ }
382
+
274
383
  let content = '';
275
384
 
276
385
  if (await fs.pathExists(envPath)) {
@@ -279,17 +388,20 @@ export abstract class BaseAdapter {
279
388
 
280
389
  const envVars = dotenv.parse(content);
281
390
 
391
+ const needsQuoting = /[\s#"'\\]/.test(variable.value);
392
+ const quotedValue = needsQuoting ? `"${variable.value.replace(/\\/g, '\\\\').replace(/"/g, '\\"')}"` : variable.value;
393
+
282
394
  if (envVars[args.name]) {
283
395
  const lines = content.split('\n');
284
396
  const newLines = lines.map(line => {
285
397
  if (line.startsWith(`${args.name}=`)) {
286
- return `${args.name}=${variable.value}`;
398
+ return `${args.name}=${quotedValue}`;
287
399
  }
288
400
  return line;
289
401
  });
290
402
  content = newLines.join('\n');
291
403
  } else {
292
- content += `\n${args.name}=${variable.value}`;
404
+ content += `\n${args.name}=${quotedValue}`;
293
405
  }
294
406
 
295
407
  await fs.writeFile(envPath, content, 'utf8');
@@ -308,9 +420,7 @@ export abstract class BaseAdapter {
308
420
 
309
421
  protected async checkAccess(args: { name: string }): Promise<{
310
422
  name: string;
311
- exists: boolean;
312
423
  accessible: boolean;
313
- blacklisted: boolean;
314
424
  message: string;
315
425
  }> {
316
426
  const variable = await this.storage.get(args.name);
@@ -329,10 +439,8 @@ export abstract class BaseAdapter {
329
439
 
330
440
  return {
331
441
  name: args.name,
332
- exists,
333
442
  accessible,
334
- blacklisted,
335
- message: accessible ? 'Variable exists and can be accessed' : 'Variable cannot be accessed or does not exist',
443
+ message: accessible ? 'Variable exists and can be accessed' : 'Variable cannot be accessed',
336
444
  };
337
445
  }
338
446
 
@@ -375,10 +483,20 @@ export abstract class BaseAdapter {
375
483
  stdout: string;
376
484
  stderr: string;
377
485
  }> {
486
+ if (!this.config.access.allow_ai_execute) {
487
+ throw new Error('AI command execution is disabled');
488
+ }
489
+
378
490
  this.validateCommand(args.command);
379
491
 
380
492
  const { spawn } = await import('child_process');
381
- const { program, args: cmdArgs } = this.parseCommand(args.command);
493
+ const { program: prog, args: cmdArgs } = this.parseCommand(args.command);
494
+
495
+ if (this.config.access.allowed_commands && this.config.access.allowed_commands.length > 0) {
496
+ if (!this.config.access.allowed_commands.includes(prog)) {
497
+ throw new Error(`Command '${prog}' is not in the allowed commands list`);
498
+ }
499
+ }
382
500
  const env: Record<string, string> = { ...process.env } as Record<string, string>;
383
501
 
384
502
  for (const name of args.variables) {
@@ -391,19 +509,32 @@ export abstract class BaseAdapter {
391
509
  }
392
510
  }
393
511
 
512
+ const TIMEOUT_MS = 30000;
513
+
394
514
  return new Promise((resolve) => {
395
- const proc = spawn(program, cmdArgs, {
515
+ const proc = spawn(prog, cmdArgs, {
396
516
  env,
397
517
  cwd: this.projectPath,
398
518
  });
399
519
 
400
520
  let stdout = '';
401
521
  let stderr = '';
522
+ let killed = false;
523
+
524
+ const timer = setTimeout(() => {
525
+ killed = true;
526
+ proc.kill('SIGTERM');
527
+ setTimeout(() => { if (!proc.killed) proc.kill('SIGKILL'); }, 5000);
528
+ }, TIMEOUT_MS);
402
529
 
403
530
  proc.stdout.on('data', (data) => { stdout += data; });
404
531
  proc.stderr.on('data', (data) => { stderr += data; });
405
532
 
406
533
  proc.on('close', (code) => {
534
+ clearTimeout(timer);
535
+ if (killed) {
536
+ stderr += '\n[Process killed: exceeded 30s timeout]';
537
+ }
407
538
  resolve({ exitCode: code, stdout, stderr });
408
539
  });
409
540
  });
@@ -1,110 +1,18 @@
1
1
  import { BaseAdapter } from './base.js';
2
- import { EnvCPConfig, GeminiFunctionDeclaration, GeminiFunctionCall, GeminiFunctionResponse, ToolDefinition } from '../types.js';
3
- import { setCorsHeaders, sendJson, parseBody, validateApiKey } from '../utils/http.js';
2
+ import { EnvCPConfig, GeminiFunctionDeclaration, GeminiFunctionCall, GeminiFunctionResponse } from '../types.js';
3
+ import { setCorsHeaders, sendJson, parseBody, validateApiKey, RateLimiter, rateLimitMiddleware } from '../utils/http.js';
4
4
  import * as http from 'http';
5
- import * as url from 'url';
6
5
 
7
6
  export class GeminiAdapter extends BaseAdapter {
8
7
  private server: http.Server | null = null;
8
+ private rateLimiter = new RateLimiter(60, 60000);
9
9
 
10
10
  constructor(config: EnvCPConfig, projectPath: string, password?: string) {
11
11
  super(config, projectPath, password);
12
12
  }
13
13
 
14
14
  protected registerTools(): void {
15
- const tools: ToolDefinition[] = [
16
- {
17
- name: 'envcp_list',
18
- description: 'List all available environment variable names. Values are never shown.',
19
- parameters: {
20
- type: 'object',
21
- properties: {
22
- tags: {
23
- type: 'array',
24
- items: { type: 'string' },
25
- description: 'Filter by tags',
26
- },
27
- },
28
- },
29
- handler: async (params) => this.listVariables(params as { tags?: string[] }),
30
- },
31
- {
32
- name: 'envcp_get',
33
- description: 'Get an environment variable. Returns masked value by default.',
34
- parameters: {
35
- type: 'object',
36
- properties: {
37
- name: { type: 'string', description: 'Variable name' },
38
- show_value: { type: 'boolean', description: 'Show actual value (requires user confirmation)' },
39
- },
40
- required: ['name'],
41
- },
42
- handler: async (params) => this.getVariable(params as { name: string; show_value?: boolean }),
43
- },
44
- {
45
- name: 'envcp_set',
46
- description: 'Create or update an environment variable.',
47
- parameters: {
48
- type: 'object',
49
- properties: {
50
- name: { type: 'string', description: 'Variable name' },
51
- value: { type: 'string', description: 'Variable value' },
52
- tags: { type: 'array', items: { type: 'string' }, description: 'Tags' },
53
- description: { type: 'string', description: 'Description' },
54
- },
55
- required: ['name', 'value'],
56
- },
57
- handler: async (params) => this.setVariable(params as any),
58
- },
59
- {
60
- name: 'envcp_delete',
61
- description: 'Delete an environment variable.',
62
- parameters: {
63
- type: 'object',
64
- properties: {
65
- name: { type: 'string', description: 'Variable name' },
66
- },
67
- required: ['name'],
68
- },
69
- handler: async (params) => this.deleteVariable(params as { name: string }),
70
- },
71
- {
72
- name: 'envcp_sync',
73
- description: 'Sync variables to .env file.',
74
- parameters: {
75
- type: 'object',
76
- properties: {},
77
- },
78
- handler: async () => this.syncToEnv(),
79
- },
80
- {
81
- name: 'envcp_run',
82
- description: 'Execute a command with environment variables injected.',
83
- parameters: {
84
- type: 'object',
85
- properties: {
86
- command: { type: 'string', description: 'Command to execute' },
87
- variables: { type: 'array', items: { type: 'string' }, description: 'Variables to inject' },
88
- },
89
- required: ['command', 'variables'],
90
- },
91
- handler: async (params) => this.runCommand(params as { command: string; variables: string[] }),
92
- },
93
- {
94
- name: 'envcp_check_access',
95
- description: 'Check if a variable exists and can be accessed.',
96
- parameters: {
97
- type: 'object',
98
- properties: {
99
- name: { type: 'string', description: 'Variable name' },
100
- },
101
- required: ['name'],
102
- },
103
- handler: async (params) => this.checkAccess(params as { name: string }),
104
- },
105
- ];
106
-
107
- tools.forEach(tool => this.tools.set(tool.name, tool));
15
+ this.registerDefaultTools();
108
16
  }
109
17
 
110
18
  // Convert tools to Gemini function declaration format
@@ -114,8 +22,8 @@ export class GeminiAdapter extends BaseAdapter {
114
22
  description: tool.description,
115
23
  parameters: {
116
24
  type: 'object' as const,
117
- properties: (tool.parameters as any).properties || {},
118
- required: (tool.parameters as any).required,
25
+ properties: (tool.parameters as Record<string, unknown>).properties as Record<string, unknown> || {},
26
+ required: (tool.parameters as Record<string, unknown>).required as string[] | undefined,
119
27
  },
120
28
  }));
121
29
  }
@@ -131,10 +39,11 @@ export class GeminiAdapter extends BaseAdapter {
131
39
  name: call.name,
132
40
  response: { result },
133
41
  });
134
- } catch (error: any) {
42
+ } catch (error: unknown) {
43
+ const message = error instanceof Error ? error.message : String(error);
135
44
  results.push({
136
45
  name: call.name,
137
- response: { error: error.message },
46
+ response: { error: message },
138
47
  });
139
48
  }
140
49
  }
@@ -147,7 +56,7 @@ export class GeminiAdapter extends BaseAdapter {
147
56
  await this.init();
148
57
 
149
58
  this.server = http.createServer(async (req, res) => {
150
- setCorsHeaders(res);
59
+ setCorsHeaders(res, undefined, req.headers.origin);
151
60
 
152
61
  if (req.method === 'OPTIONS') {
153
62
  res.writeHead(204);
@@ -155,6 +64,10 @@ export class GeminiAdapter extends BaseAdapter {
155
64
  return;
156
65
  }
157
66
 
67
+ if (!rateLimitMiddleware(this.rateLimiter, req, res)) {
68
+ return;
69
+ }
70
+
158
71
  // API key validation
159
72
  if (apiKey) {
160
73
  const providedKey = (req.headers['x-goog-api-key'] || req.headers['authorization']?.replace('Bearer ', '')) as string | undefined;
@@ -164,8 +77,8 @@ export class GeminiAdapter extends BaseAdapter {
164
77
  }
165
78
  }
166
79
 
167
- const parsedUrl = url.parse(req.url || '/', true);
168
- const pathname = parsedUrl.pathname || '/';
80
+ const parsedUrl = new URL(req.url || '/', `http://${req.headers.host || 'localhost'}`);
81
+ const pathname = parsedUrl.pathname;
169
82
 
170
83
  try {
171
84
  // Gemini-compatible endpoints
@@ -293,8 +206,9 @@ export class GeminiAdapter extends BaseAdapter {
293
206
  // 404
294
207
  sendJson(res, 404, { error: { code: 404, message: 'Not found', status: 'NOT_FOUND' } });
295
208
 
296
- } catch (error: any) {
297
- sendJson(res, 500, { error: { code: 500, message: error.message, status: 'INTERNAL' } });
209
+ } catch (error: unknown) {
210
+ const message = error instanceof Error ? error.message : String(error);
211
+ sendJson(res, 500, { error: { code: 500, message, status: 'INTERNAL' } });
298
212
  }
299
213
  });
300
214