@fentz26/envcp 1.0.2 → 1.0.3

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 (45) hide show
  1. package/README.md +3 -3
  2. package/dist/cli/index.js +382 -5
  3. package/dist/cli/index.js.map +1 -1
  4. package/dist/config/manager.d.ts +6 -0
  5. package/dist/config/manager.d.ts.map +1 -1
  6. package/dist/config/manager.js +77 -0
  7. package/dist/config/manager.js.map +1 -1
  8. package/dist/storage/index.d.ts +10 -1
  9. package/dist/storage/index.d.ts.map +1 -1
  10. package/dist/storage/index.js +89 -6
  11. package/dist/storage/index.js.map +1 -1
  12. package/dist/types.d.ts +18 -0
  13. package/dist/types.d.ts.map +1 -1
  14. package/dist/types.js +4 -0
  15. package/dist/types.js.map +1 -1
  16. package/dist/utils/crypto.d.ts +3 -0
  17. package/dist/utils/crypto.d.ts.map +1 -1
  18. package/dist/utils/crypto.js +12 -0
  19. package/dist/utils/crypto.js.map +1 -1
  20. package/package.json +6 -1
  21. package/.github/workflows/publish.yml +0 -48
  22. package/__tests__/config.test.ts +0 -65
  23. package/__tests__/crypto.test.ts +0 -76
  24. package/__tests__/http.test.ts +0 -49
  25. package/__tests__/storage.test.ts +0 -94
  26. package/jest.config.js +0 -11
  27. package/src/adapters/base.ts +0 -542
  28. package/src/adapters/gemini.ts +0 -228
  29. package/src/adapters/index.ts +0 -4
  30. package/src/adapters/openai.ts +0 -238
  31. package/src/adapters/rest.ts +0 -298
  32. package/src/cli/index.ts +0 -516
  33. package/src/cli.ts +0 -2
  34. package/src/config/manager.ts +0 -137
  35. package/src/index.ts +0 -4
  36. package/src/mcp/index.ts +0 -1
  37. package/src/mcp/server.ts +0 -67
  38. package/src/server/index.ts +0 -1
  39. package/src/server/unified.ts +0 -474
  40. package/src/storage/index.ts +0 -128
  41. package/src/types.ts +0 -183
  42. package/src/utils/crypto.ts +0 -100
  43. package/src/utils/http.ts +0 -119
  44. package/src/utils/session.ts +0 -146
  45. package/tsconfig.json +0 -20
@@ -1,228 +0,0 @@
1
- import { BaseAdapter } from './base.js';
2
- import { EnvCPConfig, GeminiFunctionDeclaration, GeminiFunctionCall, GeminiFunctionResponse } from '../types.js';
3
- import { setCorsHeaders, sendJson, parseBody, validateApiKey, RateLimiter, rateLimitMiddleware } from '../utils/http.js';
4
- import * as http from 'http';
5
-
6
- export class GeminiAdapter extends BaseAdapter {
7
- private server: http.Server | null = null;
8
- private rateLimiter = new RateLimiter(60, 60000);
9
-
10
- constructor(config: EnvCPConfig, projectPath: string, password?: string) {
11
- super(config, projectPath, password);
12
- }
13
-
14
- protected registerTools(): void {
15
- this.registerDefaultTools();
16
- }
17
-
18
- // Convert tools to Gemini function declaration format
19
- getGeminiFunctionDeclarations(): GeminiFunctionDeclaration[] {
20
- return this.getToolDefinitions().map(tool => ({
21
- name: tool.name,
22
- description: tool.description,
23
- parameters: {
24
- type: 'object' as const,
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,
27
- },
28
- }));
29
- }
30
-
31
- // Process Gemini function calls
32
- async processFunctionCalls(calls: GeminiFunctionCall[]): Promise<GeminiFunctionResponse[]> {
33
- const results: GeminiFunctionResponse[] = [];
34
-
35
- for (const call of calls) {
36
- try {
37
- const result = await this.callTool(call.name, call.args);
38
- results.push({
39
- name: call.name,
40
- response: { result },
41
- });
42
- } catch (error: unknown) {
43
- const message = error instanceof Error ? error.message : String(error);
44
- results.push({
45
- name: call.name,
46
- response: { error: message },
47
- });
48
- }
49
- }
50
-
51
- return results;
52
- }
53
-
54
-
55
- async startServer(port: number, host: string, apiKey?: string): Promise<void> {
56
- await this.init();
57
-
58
- this.server = http.createServer(async (req, res) => {
59
- setCorsHeaders(res, undefined, req.headers.origin);
60
-
61
- if (req.method === 'OPTIONS') {
62
- res.writeHead(204);
63
- res.end();
64
- return;
65
- }
66
-
67
- if (!rateLimitMiddleware(this.rateLimiter, req, res)) {
68
- return;
69
- }
70
-
71
- // API key validation
72
- if (apiKey) {
73
- const providedKey = (req.headers['x-goog-api-key'] || req.headers['authorization']?.replace('Bearer ', '')) as string | undefined;
74
- if (!validateApiKey(providedKey, apiKey)) {
75
- sendJson(res, 401, { error: { code: 401, message: 'Invalid API key', status: 'UNAUTHENTICATED' } });
76
- return;
77
- }
78
- }
79
-
80
- const parsedUrl = new URL(req.url || '/', `http://${req.headers.host || 'localhost'}`);
81
- const pathname = parsedUrl.pathname;
82
-
83
- try {
84
- // Gemini-compatible endpoints
85
-
86
- // GET /v1/models - List models
87
- if (pathname === '/v1/models' && req.method === 'GET') {
88
- sendJson(res, 200, {
89
- models: [{
90
- name: 'models/envcp-1.0',
91
- displayName: 'EnvCP Tool Server',
92
- description: 'Environment variable management tools',
93
- supportedGenerationMethods: ['generateContent'],
94
- }],
95
- });
96
- return;
97
- }
98
-
99
- // GET /v1/tools - List available tools/functions
100
- if (pathname === '/v1/tools' && req.method === 'GET') {
101
- sendJson(res, 200, {
102
- tools: [{
103
- functionDeclarations: this.getGeminiFunctionDeclarations(),
104
- }],
105
- });
106
- return;
107
- }
108
-
109
- // POST /v1/functions/call - Call a function directly
110
- if (pathname === '/v1/functions/call' && req.method === 'POST') {
111
- const body = await parseBody(req);
112
- const { name, args } = body as { name: string; args: Record<string, unknown> };
113
-
114
- if (!name) {
115
- sendJson(res, 400, { error: { code: 400, message: 'Function name required', status: 'INVALID_ARGUMENT' } });
116
- return;
117
- }
118
-
119
- const result = await this.callTool(name, args || {});
120
- sendJson(res, 200, {
121
- name,
122
- response: { result },
123
- });
124
- return;
125
- }
126
-
127
- // POST /v1/function_calls - Process function calls (batch)
128
- if (pathname === '/v1/function_calls' && req.method === 'POST') {
129
- const body = await parseBody(req);
130
- const { functionCalls } = body as { functionCalls: GeminiFunctionCall[] };
131
-
132
- if (!functionCalls || !Array.isArray(functionCalls)) {
133
- sendJson(res, 400, { error: { code: 400, message: 'functionCalls array required', status: 'INVALID_ARGUMENT' } });
134
- return;
135
- }
136
-
137
- const results = await this.processFunctionCalls(functionCalls);
138
- sendJson(res, 200, {
139
- functionResponses: results,
140
- });
141
- return;
142
- }
143
-
144
- // POST /v1/models/envcp:generateContent - Gemini-style content generation
145
- if ((pathname === '/v1/models/envcp:generateContent' || pathname === '/v1beta/models/envcp:generateContent') && req.method === 'POST') {
146
- const body = await parseBody(req);
147
- const contents = body.contents as Array<{ parts: Array<{ functionCall?: GeminiFunctionCall }> }> | undefined;
148
-
149
- // Look for function calls in the content
150
- const functionCalls: GeminiFunctionCall[] = [];
151
- if (contents) {
152
- for (const content of contents) {
153
- for (const part of content.parts || []) {
154
- if (part.functionCall) {
155
- functionCalls.push(part.functionCall);
156
- }
157
- }
158
- }
159
- }
160
-
161
- if (functionCalls.length > 0) {
162
- const results = await this.processFunctionCalls(functionCalls);
163
- sendJson(res, 200, {
164
- candidates: [{
165
- content: {
166
- parts: results.map(r => ({
167
- functionResponse: r,
168
- })),
169
- role: 'model',
170
- },
171
- finishReason: 'STOP',
172
- }],
173
- });
174
- return;
175
- }
176
-
177
- // Return available tools if no function calls
178
- sendJson(res, 200, {
179
- candidates: [{
180
- content: {
181
- parts: [{
182
- text: 'EnvCP tools available. Use function calling to interact with environment variables.',
183
- }],
184
- role: 'model',
185
- },
186
- finishReason: 'STOP',
187
- }],
188
- availableTools: [{
189
- functionDeclarations: this.getGeminiFunctionDeclarations(),
190
- }],
191
- });
192
- return;
193
- }
194
-
195
- // Health check
196
- if (pathname === '/v1/health' || pathname === '/') {
197
- sendJson(res, 200, {
198
- status: 'ok',
199
- version: '1.0.0',
200
- mode: 'gemini',
201
- endpoints: ['/v1/models', '/v1/tools', '/v1/functions/call', '/v1/function_calls', '/v1/models/envcp:generateContent'],
202
- });
203
- return;
204
- }
205
-
206
- // 404
207
- sendJson(res, 404, { error: { code: 404, message: 'Not found', status: 'NOT_FOUND' } });
208
-
209
- } catch (error: unknown) {
210
- const message = error instanceof Error ? error.message : String(error);
211
- sendJson(res, 500, { error: { code: 500, message, status: 'INTERNAL' } });
212
- }
213
- });
214
-
215
- return new Promise((resolve) => {
216
- this.server!.listen(port, host, () => {
217
- resolve();
218
- });
219
- });
220
- }
221
-
222
- stopServer(): void {
223
- if (this.server) {
224
- this.server.close();
225
- this.server = null;
226
- }
227
- }
228
- }
@@ -1,4 +0,0 @@
1
- export { BaseAdapter } from './base.js';
2
- export { RESTAdapter } from './rest.js';
3
- export { OpenAIAdapter } from './openai.js';
4
- export { GeminiAdapter } from './gemini.js';
@@ -1,238 +0,0 @@
1
- import { BaseAdapter } from './base.js';
2
- import { EnvCPConfig, OpenAIFunction, OpenAIToolCall, OpenAIMessage } from '../types.js';
3
- import { setCorsHeaders, sendJson, parseBody, validateApiKey, RateLimiter, rateLimitMiddleware } from '../utils/http.js';
4
- import * as http from 'http';
5
-
6
- export class OpenAIAdapter extends BaseAdapter {
7
- private server: http.Server | null = null;
8
- private rateLimiter = new RateLimiter(60, 60000);
9
-
10
- constructor(config: EnvCPConfig, projectPath: string, password?: string) {
11
- super(config, projectPath, password);
12
- }
13
-
14
- protected registerTools(): void {
15
- this.registerDefaultTools();
16
- }
17
-
18
- // Convert tools to OpenAI function format
19
- getOpenAIFunctions(): OpenAIFunction[] {
20
- return this.getToolDefinitions().map(tool => ({
21
- name: tool.name,
22
- description: tool.description,
23
- parameters: {
24
- type: 'object' as const,
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,
27
- },
28
- }));
29
- }
30
-
31
- // Process OpenAI tool calls
32
- async processToolCalls(toolCalls: OpenAIToolCall[]): Promise<OpenAIMessage[]> {
33
- const results: OpenAIMessage[] = [];
34
-
35
- for (const call of toolCalls) {
36
- try {
37
- const args = JSON.parse(call.function.arguments);
38
- const result = await this.callTool(call.function.name, args);
39
- results.push({
40
- role: 'tool',
41
- tool_call_id: call.id,
42
- content: JSON.stringify(result),
43
- });
44
- } catch (error: unknown) {
45
- const message = error instanceof Error ? error.message : String(error);
46
- results.push({
47
- role: 'tool',
48
- tool_call_id: call.id,
49
- content: JSON.stringify({ error: message }),
50
- });
51
- }
52
- }
53
-
54
- return results;
55
- }
56
-
57
-
58
- async startServer(port: number, host: string, apiKey?: string): Promise<void> {
59
- await this.init();
60
-
61
- this.server = http.createServer(async (req, res) => {
62
- setCorsHeaders(res, undefined, req.headers.origin);
63
-
64
- if (req.method === 'OPTIONS') {
65
- res.writeHead(204);
66
- res.end();
67
- return;
68
- }
69
-
70
- if (!rateLimitMiddleware(this.rateLimiter, req, res)) {
71
- return;
72
- }
73
-
74
- // API key validation
75
- if (apiKey) {
76
- const providedKey = req.headers['authorization']?.replace('Bearer ', '');
77
- if (!validateApiKey(providedKey, apiKey)) {
78
- sendJson(res, 401, { error: { message: 'Invalid API key', type: 'invalid_api_key' } });
79
- return;
80
- }
81
- }
82
-
83
- const parsedUrl = new URL(req.url || '/', `http://${req.headers.host || 'localhost'}`);
84
- const pathname = parsedUrl.pathname;
85
-
86
- try {
87
- // OpenAI-compatible endpoints
88
-
89
- // GET /v1/models - List models (for compatibility)
90
- if (pathname === '/v1/models' && req.method === 'GET') {
91
- sendJson(res, 200, {
92
- object: 'list',
93
- data: [{
94
- id: 'envcp-1.0',
95
- object: 'model',
96
- created: Date.now(),
97
- owned_by: 'envcp',
98
- }],
99
- });
100
- return;
101
- }
102
-
103
- // GET /v1/functions - List available functions
104
- if (pathname === '/v1/functions' && req.method === 'GET') {
105
- sendJson(res, 200, {
106
- object: 'list',
107
- data: this.getOpenAIFunctions(),
108
- });
109
- return;
110
- }
111
-
112
- // POST /v1/functions/call - Call a function directly
113
- if (pathname === '/v1/functions/call' && req.method === 'POST') {
114
- const body = await parseBody(req);
115
- const { name, arguments: args } = body as { name: string; arguments: Record<string, unknown> };
116
-
117
- if (!name) {
118
- sendJson(res, 400, { error: { message: 'Function name required', type: 'invalid_request_error' } });
119
- return;
120
- }
121
-
122
- const result = await this.callTool(name, args || {});
123
- sendJson(res, 200, {
124
- object: 'function_result',
125
- name,
126
- result,
127
- });
128
- return;
129
- }
130
-
131
- // POST /v1/tool_calls - Process tool calls (batch)
132
- if (pathname === '/v1/tool_calls' && req.method === 'POST') {
133
- const body = await parseBody(req);
134
- const { tool_calls } = body as { tool_calls: OpenAIToolCall[] };
135
-
136
- if (!tool_calls || !Array.isArray(tool_calls)) {
137
- sendJson(res, 400, { error: { message: 'tool_calls array required', type: 'invalid_request_error' } });
138
- return;
139
- }
140
-
141
- const results = await this.processToolCalls(tool_calls);
142
- sendJson(res, 200, {
143
- object: 'list',
144
- data: results,
145
- });
146
- return;
147
- }
148
-
149
- // POST /v1/chat/completions - For integration with proxies
150
- // This allows tools to be called through a chat completion-like interface
151
- if (pathname === '/v1/chat/completions' && req.method === 'POST') {
152
- const body = await parseBody(req);
153
- const messages = body.messages as OpenAIMessage[] | undefined;
154
-
155
- if (!messages || !Array.isArray(messages)) {
156
- sendJson(res, 400, { error: { message: 'messages array required', type: 'invalid_request_error' } });
157
- return;
158
- }
159
-
160
- // Check if last message has tool_calls to process
161
- const lastMessage = messages[messages.length - 1];
162
- if (lastMessage?.tool_calls) {
163
- const results = await this.processToolCalls(lastMessage.tool_calls);
164
- sendJson(res, 200, {
165
- id: `chatcmpl-${Date.now()}`,
166
- object: 'chat.completion',
167
- created: Math.floor(Date.now() / 1000),
168
- model: 'envcp-1.0',
169
- choices: [{
170
- index: 0,
171
- message: {
172
- role: 'assistant',
173
- content: null,
174
- tool_calls: null,
175
- },
176
- finish_reason: 'tool_calls',
177
- }],
178
- tool_results: results,
179
- });
180
- return;
181
- }
182
-
183
- // Return available tools if no tool_calls
184
- sendJson(res, 200, {
185
- id: `chatcmpl-${Date.now()}`,
186
- object: 'chat.completion',
187
- created: Math.floor(Date.now() / 1000),
188
- model: 'envcp-1.0',
189
- choices: [{
190
- index: 0,
191
- message: {
192
- role: 'assistant',
193
- content: 'EnvCP tools available. Use function calling to interact with environment variables.',
194
- },
195
- finish_reason: 'stop',
196
- }],
197
- available_tools: this.getOpenAIFunctions().map(f => ({
198
- type: 'function',
199
- function: f,
200
- })),
201
- });
202
- return;
203
- }
204
-
205
- // Health check
206
- if (pathname === '/v1/health' || pathname === '/') {
207
- sendJson(res, 200, {
208
- status: 'ok',
209
- version: '1.0.0',
210
- mode: 'openai',
211
- endpoints: ['/v1/models', '/v1/functions', '/v1/functions/call', '/v1/tool_calls', '/v1/chat/completions'],
212
- });
213
- return;
214
- }
215
-
216
- // 404
217
- sendJson(res, 404, { error: { message: 'Not found', type: 'not_found' } });
218
-
219
- } catch (error: unknown) {
220
- const message = error instanceof Error ? error.message : String(error);
221
- sendJson(res, 500, { error: { message, type: 'internal_error' } });
222
- }
223
- });
224
-
225
- return new Promise((resolve) => {
226
- this.server!.listen(port, host, () => {
227
- resolve();
228
- });
229
- });
230
- }
231
-
232
- stopServer(): void {
233
- if (this.server) {
234
- this.server.close();
235
- this.server = null;
236
- }
237
- }
238
- }