@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.
- package/README.md +79 -130
- package/__tests__/config.test.ts +65 -0
- package/__tests__/crypto.test.ts +76 -0
- package/__tests__/http.test.ts +49 -0
- package/__tests__/storage.test.ts +94 -0
- package/dist/adapters/base.d.ts +1 -2
- package/dist/adapters/base.d.ts.map +1 -1
- package/dist/adapters/base.js +139 -14
- package/dist/adapters/base.js.map +1 -1
- package/dist/adapters/gemini.d.ts +1 -0
- package/dist/adapters/gemini.d.ts.map +1 -1
- package/dist/adapters/gemini.js +13 -99
- package/dist/adapters/gemini.js.map +1 -1
- package/dist/adapters/openai.d.ts +1 -0
- package/dist/adapters/openai.d.ts.map +1 -1
- package/dist/adapters/openai.js +13 -99
- package/dist/adapters/openai.js.map +1 -1
- package/dist/adapters/rest.d.ts +1 -0
- package/dist/adapters/rest.d.ts.map +1 -1
- package/dist/adapters/rest.js +16 -13
- package/dist/adapters/rest.js.map +1 -1
- package/dist/cli/index.js +132 -196
- package/dist/cli/index.js.map +1 -1
- package/dist/config/manager.d.ts.map +1 -1
- package/dist/config/manager.js +4 -1
- package/dist/config/manager.js.map +1 -1
- package/dist/mcp/server.d.ts +1 -16
- package/dist/mcp/server.d.ts.map +1 -1
- package/dist/mcp/server.js +23 -511
- package/dist/mcp/server.js.map +1 -1
- package/dist/server/unified.d.ts +1 -0
- package/dist/server/unified.d.ts.map +1 -1
- package/dist/server/unified.js +31 -19
- package/dist/server/unified.js.map +1 -1
- package/dist/storage/index.d.ts +2 -0
- package/dist/storage/index.d.ts.map +1 -1
- package/dist/storage/index.js +18 -4
- package/dist/storage/index.js.map +1 -1
- package/dist/types.d.ts +10 -0
- package/dist/types.d.ts.map +1 -1
- package/dist/types.js +2 -0
- package/dist/types.js.map +1 -1
- package/dist/utils/http.d.ts +13 -1
- package/dist/utils/http.d.ts.map +1 -1
- package/dist/utils/http.js +65 -2
- package/dist/utils/http.js.map +1 -1
- package/dist/utils/session.d.ts.map +1 -1
- package/dist/utils/session.js +8 -3
- package/dist/utils/session.js.map +1 -1
- package/jest.config.js +11 -0
- package/package.json +4 -3
- package/src/adapters/base.ts +147 -16
- package/src/adapters/gemini.ts +19 -105
- package/src/adapters/openai.ts +19 -105
- package/src/adapters/rest.ts +19 -15
- package/src/cli/index.ts +135 -259
- package/src/config/manager.ts +4 -1
- package/src/mcp/server.ts +26 -582
- package/src/server/unified.ts +37 -23
- package/src/storage/index.ts +22 -6
- package/src/types.ts +2 -0
- package/src/utils/http.ts +76 -2
- package/src/utils/session.ts +13 -8
package/src/adapters/openai.ts
CHANGED
|
@@ -1,110 +1,18 @@
|
|
|
1
1
|
import { BaseAdapter } from './base.js';
|
|
2
|
-
import { EnvCPConfig, OpenAIFunction, OpenAIToolCall, OpenAIMessage
|
|
3
|
-
import { setCorsHeaders, sendJson, parseBody, validateApiKey } from '../utils/http.js';
|
|
2
|
+
import { EnvCPConfig, OpenAIFunction, OpenAIToolCall, OpenAIMessage } 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 OpenAIAdapter 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
|
-
|
|
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 OpenAI function format
|
|
@@ -114,8 +22,8 @@ export class OpenAIAdapter extends BaseAdapter {
|
|
|
114
22
|
description: tool.description,
|
|
115
23
|
parameters: {
|
|
116
24
|
type: 'object' as const,
|
|
117
|
-
properties: (tool.parameters as
|
|
118
|
-
required: (tool.parameters as
|
|
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
|
}
|
|
@@ -133,11 +41,12 @@ export class OpenAIAdapter extends BaseAdapter {
|
|
|
133
41
|
tool_call_id: call.id,
|
|
134
42
|
content: JSON.stringify(result),
|
|
135
43
|
});
|
|
136
|
-
} catch (error:
|
|
44
|
+
} catch (error: unknown) {
|
|
45
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
137
46
|
results.push({
|
|
138
47
|
role: 'tool',
|
|
139
48
|
tool_call_id: call.id,
|
|
140
|
-
content: JSON.stringify({ error:
|
|
49
|
+
content: JSON.stringify({ error: message }),
|
|
141
50
|
});
|
|
142
51
|
}
|
|
143
52
|
}
|
|
@@ -150,7 +59,7 @@ export class OpenAIAdapter extends BaseAdapter {
|
|
|
150
59
|
await this.init();
|
|
151
60
|
|
|
152
61
|
this.server = http.createServer(async (req, res) => {
|
|
153
|
-
setCorsHeaders(res);
|
|
62
|
+
setCorsHeaders(res, undefined, req.headers.origin);
|
|
154
63
|
|
|
155
64
|
if (req.method === 'OPTIONS') {
|
|
156
65
|
res.writeHead(204);
|
|
@@ -158,6 +67,10 @@ export class OpenAIAdapter extends BaseAdapter {
|
|
|
158
67
|
return;
|
|
159
68
|
}
|
|
160
69
|
|
|
70
|
+
if (!rateLimitMiddleware(this.rateLimiter, req, res)) {
|
|
71
|
+
return;
|
|
72
|
+
}
|
|
73
|
+
|
|
161
74
|
// API key validation
|
|
162
75
|
if (apiKey) {
|
|
163
76
|
const providedKey = req.headers['authorization']?.replace('Bearer ', '');
|
|
@@ -167,8 +80,8 @@ export class OpenAIAdapter extends BaseAdapter {
|
|
|
167
80
|
}
|
|
168
81
|
}
|
|
169
82
|
|
|
170
|
-
const parsedUrl =
|
|
171
|
-
const pathname = parsedUrl.pathname
|
|
83
|
+
const parsedUrl = new URL(req.url || '/', `http://${req.headers.host || 'localhost'}`);
|
|
84
|
+
const pathname = parsedUrl.pathname;
|
|
172
85
|
|
|
173
86
|
try {
|
|
174
87
|
// OpenAI-compatible endpoints
|
|
@@ -303,8 +216,9 @@ export class OpenAIAdapter extends BaseAdapter {
|
|
|
303
216
|
// 404
|
|
304
217
|
sendJson(res, 404, { error: { message: 'Not found', type: 'not_found' } });
|
|
305
218
|
|
|
306
|
-
} catch (error:
|
|
307
|
-
|
|
219
|
+
} catch (error: unknown) {
|
|
220
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
221
|
+
sendJson(res, 500, { error: { message, type: 'internal_error' } });
|
|
308
222
|
}
|
|
309
223
|
});
|
|
310
224
|
|
package/src/adapters/rest.ts
CHANGED
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
import { BaseAdapter } from './base.js';
|
|
2
2
|
import { EnvCPConfig, RESTResponse, ToolDefinition } from '../types.js';
|
|
3
|
-
import { setCorsHeaders, sendJson, parseBody, validateApiKey } from '../utils/http.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 RESTAdapter 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);
|
|
@@ -39,7 +39,7 @@ export class RESTAdapter extends BaseAdapter {
|
|
|
39
39
|
tags: { type: 'array', items: { type: 'string' }, description: 'Tags' },
|
|
40
40
|
description: { type: 'string', description: 'Description' },
|
|
41
41
|
},
|
|
42
|
-
handler: async (params) => this.setVariable(params as
|
|
42
|
+
handler: async (params) => this.setVariable(params as { name: string; value: string; tags?: string[]; description?: string }),
|
|
43
43
|
},
|
|
44
44
|
{
|
|
45
45
|
name: 'envcp_delete',
|
|
@@ -100,7 +100,7 @@ export class RESTAdapter extends BaseAdapter {
|
|
|
100
100
|
await this.init();
|
|
101
101
|
|
|
102
102
|
this.server = http.createServer(async (req, res) => {
|
|
103
|
-
setCorsHeaders(res);
|
|
103
|
+
setCorsHeaders(res, undefined, req.headers.origin);
|
|
104
104
|
|
|
105
105
|
if (req.method === 'OPTIONS') {
|
|
106
106
|
res.writeHead(204);
|
|
@@ -108,6 +108,10 @@ export class RESTAdapter extends BaseAdapter {
|
|
|
108
108
|
return;
|
|
109
109
|
}
|
|
110
110
|
|
|
111
|
+
if (!rateLimitMiddleware(this.rateLimiter, req, res)) {
|
|
112
|
+
return;
|
|
113
|
+
}
|
|
114
|
+
|
|
111
115
|
// API key validation
|
|
112
116
|
if (apiKey) {
|
|
113
117
|
const providedKey = (req.headers['x-api-key'] || req.headers['authorization']?.replace('Bearer ', '')) as string | undefined;
|
|
@@ -117,8 +121,8 @@ export class RESTAdapter extends BaseAdapter {
|
|
|
117
121
|
}
|
|
118
122
|
}
|
|
119
123
|
|
|
120
|
-
const parsedUrl =
|
|
121
|
-
const pathname = parsedUrl.pathname
|
|
124
|
+
const parsedUrl = new URL(req.url || '/', `http://${req.headers.host || 'localhost'}`);
|
|
125
|
+
const pathname = parsedUrl.pathname;
|
|
122
126
|
const segments = pathname.split('/').filter(Boolean);
|
|
123
127
|
|
|
124
128
|
try {
|
|
@@ -171,9 +175,8 @@ export class RESTAdapter extends BaseAdapter {
|
|
|
171
175
|
const varName = segments[2];
|
|
172
176
|
|
|
173
177
|
if (!varName && req.method === 'GET') {
|
|
174
|
-
const
|
|
175
|
-
|
|
176
|
-
: undefined;
|
|
178
|
+
const tagsParam = parsedUrl.searchParams.getAll('tags');
|
|
179
|
+
const tags = tagsParam.length > 0 ? tagsParam : undefined;
|
|
177
180
|
const result = await this.callTool('envcp_list', { tags });
|
|
178
181
|
sendJson(res, 200, this.createResponse(true, result));
|
|
179
182
|
return;
|
|
@@ -187,7 +190,7 @@ export class RESTAdapter extends BaseAdapter {
|
|
|
187
190
|
}
|
|
188
191
|
|
|
189
192
|
if (varName && req.method === 'GET') {
|
|
190
|
-
const showValue = parsedUrl.
|
|
193
|
+
const showValue = parsedUrl.searchParams.get('show_value') === 'true';
|
|
191
194
|
const result = await this.callTool('envcp_get', { name: varName, show_value: showValue });
|
|
192
195
|
sendJson(res, 200, this.createResponse(true, result));
|
|
193
196
|
return;
|
|
@@ -233,11 +236,12 @@ export class RESTAdapter extends BaseAdapter {
|
|
|
233
236
|
// 404
|
|
234
237
|
sendJson(res, 404, this.createResponse(false, undefined, 'Not found'));
|
|
235
238
|
|
|
236
|
-
} catch (error:
|
|
237
|
-
const
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
239
|
+
} catch (error: unknown) {
|
|
240
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
241
|
+
const status = message.includes('locked') ? 401 :
|
|
242
|
+
message.includes('not found') ? 404 :
|
|
243
|
+
message.includes('disabled') ? 403 : 500;
|
|
244
|
+
sendJson(res, status, this.createResponse(false, undefined, message));
|
|
241
245
|
}
|
|
242
246
|
});
|
|
243
247
|
|