@fentz26/envcp 1.0.1 → 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 (71) hide show
  1. package/README.md +82 -133
  2. package/dist/adapters/base.d.ts +1 -2
  3. package/dist/adapters/base.d.ts.map +1 -1
  4. package/dist/adapters/base.js +139 -14
  5. package/dist/adapters/base.js.map +1 -1
  6. package/dist/adapters/gemini.d.ts +1 -0
  7. package/dist/adapters/gemini.d.ts.map +1 -1
  8. package/dist/adapters/gemini.js +13 -99
  9. package/dist/adapters/gemini.js.map +1 -1
  10. package/dist/adapters/openai.d.ts +1 -0
  11. package/dist/adapters/openai.d.ts.map +1 -1
  12. package/dist/adapters/openai.js +13 -99
  13. package/dist/adapters/openai.js.map +1 -1
  14. package/dist/adapters/rest.d.ts +1 -0
  15. package/dist/adapters/rest.d.ts.map +1 -1
  16. package/dist/adapters/rest.js +16 -13
  17. package/dist/adapters/rest.js.map +1 -1
  18. package/dist/cli/index.js +510 -197
  19. package/dist/cli/index.js.map +1 -1
  20. package/dist/config/manager.d.ts +6 -0
  21. package/dist/config/manager.d.ts.map +1 -1
  22. package/dist/config/manager.js +81 -1
  23. package/dist/config/manager.js.map +1 -1
  24. package/dist/mcp/server.d.ts +1 -16
  25. package/dist/mcp/server.d.ts.map +1 -1
  26. package/dist/mcp/server.js +23 -511
  27. package/dist/mcp/server.js.map +1 -1
  28. package/dist/server/unified.d.ts +1 -0
  29. package/dist/server/unified.d.ts.map +1 -1
  30. package/dist/server/unified.js +31 -19
  31. package/dist/server/unified.js.map +1 -1
  32. package/dist/storage/index.d.ts +12 -1
  33. package/dist/storage/index.d.ts.map +1 -1
  34. package/dist/storage/index.js +107 -10
  35. package/dist/storage/index.js.map +1 -1
  36. package/dist/types.d.ts +28 -0
  37. package/dist/types.d.ts.map +1 -1
  38. package/dist/types.js +6 -0
  39. package/dist/types.js.map +1 -1
  40. package/dist/utils/crypto.d.ts +3 -0
  41. package/dist/utils/crypto.d.ts.map +1 -1
  42. package/dist/utils/crypto.js +12 -0
  43. package/dist/utils/crypto.js.map +1 -1
  44. package/dist/utils/http.d.ts +13 -1
  45. package/dist/utils/http.d.ts.map +1 -1
  46. package/dist/utils/http.js +65 -2
  47. package/dist/utils/http.js.map +1 -1
  48. package/dist/utils/session.d.ts.map +1 -1
  49. package/dist/utils/session.js +8 -3
  50. package/dist/utils/session.js.map +1 -1
  51. package/package.json +9 -3
  52. package/.github/workflows/publish.yml +0 -48
  53. package/src/adapters/base.ts +0 -411
  54. package/src/adapters/gemini.ts +0 -314
  55. package/src/adapters/index.ts +0 -4
  56. package/src/adapters/openai.ts +0 -324
  57. package/src/adapters/rest.ts +0 -294
  58. package/src/cli/index.ts +0 -640
  59. package/src/cli.ts +0 -2
  60. package/src/config/manager.ts +0 -134
  61. package/src/index.ts +0 -4
  62. package/src/mcp/index.ts +0 -1
  63. package/src/mcp/server.ts +0 -623
  64. package/src/server/index.ts +0 -1
  65. package/src/server/unified.ts +0 -460
  66. package/src/storage/index.ts +0 -112
  67. package/src/types.ts +0 -181
  68. package/src/utils/crypto.ts +0 -100
  69. package/src/utils/http.ts +0 -45
  70. package/src/utils/session.ts +0 -141
  71. package/tsconfig.json +0 -20
package/README.md CHANGED
@@ -2,104 +2,76 @@
2
2
 
3
3
  **Secure Environment Variable Management for AI-Assisted Coding**
4
4
 
5
- EnvCP is a universal tool server that allows developers to safely use AI assistants for coding without exposing sensitive environment variables, API keys, or secrets. Works with Claude, ChatGPT, Gemini, Cursor, and any AI tool.
6
-
7
- ## Why EnvCP?
8
-
9
- When using AI coding assistants, you often need to reference environment variables, API keys, or other secrets. But you don't want to share these with the AI. EnvCP solves this by:
10
-
11
- - **Local-only storage** - Your secrets never leave your machine
12
- - **Encrypted at rest** - AES-256-GCM encryption with PBKDF2 key derivation (100,000 iterations)
13
- - **Reference-based access** - AI references variables by name, never sees the actual values
14
- - **Automatic .env injection** - Values can be automatically injected into your .env files
15
- - **AI Access Control** - Block AI from proactively checking or listing your secrets
16
- - **Universal Compatibility** - Works with any AI tool via multiple protocols
17
-
18
- ## Platform Compatibility
19
-
20
- | Platform | Support | Protocol |
21
- |----------|---------|----------|
22
- | Claude Desktop | Native | MCP |
23
- | Claude Code | Native | MCP |
24
- | Cursor | Native | MCP |
25
- | Cline (VS Code) | Native | MCP |
26
- | Continue.dev | Native | MCP |
27
- | Zed Editor | Native | MCP |
28
- | ChatGPT | Via API | OpenAI Function Calling |
29
- | GPT-4 API | Via API | OpenAI Function Calling |
30
- | Gemini | Via API | Google Function Calling |
31
- | Gemini API | Via API | Google Function Calling |
32
- | Local LLMs (Ollama) | Via API | REST / OpenAI-compatible |
33
- | LM Studio | Via API | REST / OpenAI-compatible |
34
- | Open WebUI | Via API | REST |
35
- | Any HTTP Client | Via API | REST |
36
-
37
- ## Features
38
-
39
- - **Multi-Protocol Support** - MCP, REST API, OpenAI, and Gemini protocols
40
- - **Auto-Detection** - Automatically detects client type from request headers
41
- - **AES-256-GCM Encryption** - Military-grade encryption with PBKDF2-SHA512 key derivation
42
- - **Flexible Passwords** - No complexity requirements - use any password you want
43
- - **Session Management** - Quick password mode with configurable session duration
44
- - **AI Access Control** - Prevent AI from actively checking variables without permission
45
- - **Blacklist Patterns** - Block AI access to sensitive variables matching patterns
46
- - **Project-based Organization** - Separate environments per project
47
- - **Auto .env Sync** - Automatically sync to .env files
48
- - **Audit Logging** - All operations logged for security review
5
+ EnvCP lets you safely use AI assistants (Claude, ChatGPT, Gemini, Cursor, etc.) without exposing your secrets. Your API keys and environment variables stay encrypted on your machine AI only references them by name.
49
6
 
50
7
  ## Installation
51
8
 
52
9
  ```bash
53
- npm install -g envcp
10
+ npm install -g @fentz26/envcp
54
11
  ```
55
12
 
56
- Or use with npx:
13
+ Or use without installing:
57
14
 
58
15
  ```bash
59
- npx envcp init
16
+ npx @fentz26/envcp init
60
17
  ```
61
18
 
62
19
  ## Quick Start
63
20
 
64
- ### 1. Initialize EnvCP in your project
65
-
66
21
  ```bash
22
+ # 1. Initialize in your project
67
23
  envcp init
68
- ```
69
-
70
- ### 2. Add your secrets
71
24
 
72
- ```bash
25
+ # 2. Add your secrets
73
26
  envcp add API_KEY --value "your-secret-key"
74
27
  envcp add DATABASE_URL --value "postgres://..."
28
+
29
+ # 3. Start the server (auto-detects client type)
30
+ envcp serve --mode auto --port 3456
75
31
  ```
76
32
 
77
- ### 3. Start the server
33
+ ## Basic CLI Commands
78
34
 
79
35
  ```bash
80
- # Auto mode - detects client type automatically (recommended)
81
- envcp serve --mode auto --port 3456
36
+ # Variable Management
37
+ envcp add <name> [options] # Add a variable
38
+ envcp list [--show-values] # List variables
39
+ envcp get <name> # Get a variable
40
+ envcp remove <name> # Remove a variable
41
+
42
+ # Session Management
43
+ envcp unlock # Unlock with password
44
+ envcp lock # Lock immediately
45
+ envcp status # Check session status
82
46
 
83
- # Or specific modes:
84
- envcp serve --mode mcp # For Claude Desktop, Cursor, etc.
85
- envcp serve --mode rest # For REST API clients
86
- envcp serve --mode openai # For ChatGPT/OpenAI-compatible
87
- envcp serve --mode gemini # For Google Gemini
88
- envcp serve --mode all # All protocols on same port
47
+ # Sync and Export
48
+ envcp sync # Sync to .env file
49
+ envcp export [--format env|json|yaml]
89
50
  ```
90
51
 
52
+ ## Why EnvCP?
53
+
54
+ - **Local-only storage** — Your secrets never leave your machine
55
+ - **Encrypted at rest** — AES-256-GCM with PBKDF2 key derivation (100,000 iterations)
56
+ - **Reference-based access** — AI references variables by name, never sees the actual values
57
+ - **Automatic .env injection** — Values can be automatically injected into your .env files
58
+ - **AI Access Control** — Block AI from proactively listing or checking your secrets
59
+ - **Universal Compatibility** — Works with any AI tool via MCP, OpenAI, Gemini, or REST protocols
60
+
61
+ ---
62
+
91
63
  ## Integration Guides
92
64
 
93
65
  ### Claude Desktop / Cursor / Cline (MCP)
94
66
 
95
- Add to your config file:
67
+ Add to your MCP config file:
96
68
 
97
69
  ```json
98
70
  {
99
71
  "mcpServers": {
100
72
  "envcp": {
101
73
  "command": "npx",
102
- "args": ["envcp", "serve", "--mode", "mcp"]
74
+ "args": ["@fentz26/envcp", "serve", "--mode", "mcp"]
103
75
  }
104
76
  }
105
77
  }
@@ -107,26 +79,18 @@ Add to your config file:
107
79
 
108
80
  ### ChatGPT / OpenAI API
109
81
 
110
- Start the server in OpenAI mode:
111
-
112
82
  ```bash
113
83
  envcp serve --mode openai --port 3456 --api-key your-secret-key
114
84
  ```
115
85
 
116
- Use with OpenAI client:
117
-
118
86
  ```python
119
87
  import openai
120
88
 
121
- # Point to EnvCP server
122
89
  client = openai.OpenAI(
123
90
  base_url="http://localhost:3456/v1",
124
91
  api_key="your-secret-key"
125
92
  )
126
93
 
127
- # Get available functions
128
- functions = client.get("/functions")
129
-
130
94
  # Call a function
131
95
  result = client.post("/functions/call", json={
132
96
  "name": "envcp_get",
@@ -136,14 +100,10 @@ result = client.post("/functions/call", json={
136
100
 
137
101
  ### Gemini / Google AI
138
102
 
139
- Start the server in Gemini mode:
140
-
141
103
  ```bash
142
104
  envcp serve --mode gemini --port 3456 --api-key your-secret-key
143
105
  ```
144
106
 
145
- Use with Gemini:
146
-
147
107
  ```python
148
108
  import requests
149
109
 
@@ -163,22 +123,18 @@ result = requests.post(
163
123
 
164
124
  ### Local LLMs (Ollama, LM Studio)
165
125
 
166
- For local LLMs, use REST API mode or OpenAI-compatible mode:
167
-
168
126
  ```bash
169
- # REST API (universal)
170
- envcp serve --mode rest --port 3456
171
-
172
127
  # OpenAI-compatible (works with most local LLM tools)
173
128
  envcp serve --mode openai --port 3456
129
+
130
+ # Or universal REST
131
+ envcp serve --mode rest --port 3456
174
132
  ```
175
133
 
176
- Then configure your LLM tool to use `http://localhost:3456` as the tool server.
134
+ Configure your LLM tool to use `http://localhost:3456` as the tool server.
177
135
 
178
136
  ### REST API (Universal)
179
137
 
180
- Start the server:
181
-
182
138
  ```bash
183
139
  envcp serve --mode rest --port 3456 --api-key your-secret-key
184
140
  ```
@@ -198,8 +154,6 @@ GET /api/tools - List available tools
198
154
  POST /api/tools/:name - Call tool by name
199
155
  ```
200
156
 
201
- Example:
202
-
203
157
  ```bash
204
158
  # List variables
205
159
  curl -H "X-API-Key: your-secret-key" http://localhost:3456/api/variables
@@ -218,36 +172,14 @@ curl -X POST -H "X-API-Key: your-secret-key" \
218
172
 
219
173
  | Mode | Description | Use Case |
220
174
  |------|-------------|----------|
175
+ | `auto` | Auto-detect client from headers | Universal (recommended for HTTP) |
221
176
  | `mcp` | Model Context Protocol (stdio) | Claude Desktop, Cursor, Cline |
222
177
  | `rest` | REST API (HTTP) | Any HTTP client, custom integrations |
223
178
  | `openai` | OpenAI function calling format | ChatGPT, GPT-4 API, OpenAI-compatible tools |
224
179
  | `gemini` | Google function calling format | Gemini, Google AI |
225
180
  | `all` | All HTTP protocols on same port | Multiple clients |
226
- | `auto` | Auto-detect client from headers | Universal (recommended for HTTP) |
227
-
228
- ## CLI Commands
229
181
 
230
182
  ```bash
231
- # Initialize
232
- envcp init [options]
233
-
234
- # Session Management
235
- envcp unlock # Unlock session with password
236
- envcp lock # Lock session immediately
237
- envcp status # Check session status
238
- envcp extend [minutes] # Extend session duration
239
-
240
- # Variable Management
241
- envcp add <name> [options]
242
- envcp list [--show-values]
243
- envcp get <name>
244
- envcp remove <name>
245
-
246
- # Sync and Export
247
- envcp sync # Sync to .env file
248
- envcp export [--format env|json|yaml]
249
-
250
- # Server
251
183
  envcp serve [options]
252
184
  --mode, -m Server mode: mcp, rest, openai, gemini, all, auto
253
185
  --port HTTP port (default: 3456)
@@ -256,9 +188,40 @@ envcp serve [options]
256
188
  --password, -p Encryption password
257
189
  ```
258
190
 
259
- ## Configuration
191
+ ## Platform Compatibility
192
+
193
+ | Platform | Support | Protocol |
194
+ |----------|---------|----------|
195
+ | Claude Desktop | Native | MCP |
196
+ | Claude Code | Native | MCP |
197
+ | Cursor | Native | MCP |
198
+ | Cline (VS Code) | Native | MCP |
199
+ | Continue.dev | Native | MCP |
200
+ | Zed Editor | Native | MCP |
201
+ | ChatGPT | Via API | OpenAI Function Calling |
202
+ | GPT-4 API | Via API | OpenAI Function Calling |
203
+ | Gemini | Via API | Google Function Calling |
204
+ | Gemini API | Via API | Google Function Calling |
205
+ | Local LLMs (Ollama) | Via API | REST / OpenAI-compatible |
206
+ | LM Studio | Via API | REST / OpenAI-compatible |
207
+ | Open WebUI | Via API | REST |
208
+ | Any HTTP Client | Via API | REST |
260
209
 
261
- ### envcp.yaml
210
+ ## Available Tools
211
+
212
+ All protocols expose the same tools:
213
+
214
+ | Tool | Description |
215
+ |------|-------------|
216
+ | `envcp_list` | List variable names (not values) |
217
+ | `envcp_get` | Get a variable (masked by default) |
218
+ | `envcp_set` | Create/update a variable |
219
+ | `envcp_delete` | Delete a variable |
220
+ | `envcp_sync` | Sync to .env file |
221
+ | `envcp_run` | Run command with env vars injected |
222
+ | `envcp_check_access` | Check if variable is accessible |
223
+
224
+ ## Configuration (envcp.yaml)
262
225
 
263
226
  ```yaml
264
227
  version: "1.0"
@@ -298,20 +261,6 @@ sync:
298
261
  - "*_SECRET"
299
262
  ```
300
263
 
301
- ## Available Tools
302
-
303
- All protocols expose the same tools:
304
-
305
- | Tool | Description |
306
- |------|-------------|
307
- | `envcp_list` | List variable names (not values) |
308
- | `envcp_get` | Get a variable (masked by default) |
309
- | `envcp_set` | Create/update a variable |
310
- | `envcp_delete` | Delete a variable |
311
- | `envcp_sync` | Sync to .env file |
312
- | `envcp_run` | Run command with env vars injected |
313
- | `envcp_check_access` | Check if variable is accessible |
314
-
315
264
  ## AI Access Control
316
265
 
317
266
  ### Disable Active Checking
@@ -338,7 +287,7 @@ access:
338
287
 
339
288
  ## Security
340
289
 
341
- ### Encryption
290
+ ### Encryption Details
342
291
 
343
292
  - **Cipher**: AES-256-GCM
344
293
  - **Key Derivation**: PBKDF2-SHA512 (100,000 iterations)
@@ -364,16 +313,16 @@ Authorization: Bearer your-secret-key
364
313
 
365
314
  ## Best Practices
366
315
 
367
- 1. **Never commit `.envcp/`** - Add to `.gitignore`
368
- 2. **Use API keys for HTTP modes** - Protect your server endpoints
369
- 3. **Disable `allow_ai_active_check`** - Prevent AI from probing for variables
370
- 4. **Use blacklist patterns** - Block sensitive variable patterns
371
- 5. **Use `auto` mode for HTTP** - Let EnvCP detect the client type
372
- 6. **Review access logs** - Check `.envcp/logs/` regularly
316
+ 1. **Never commit `.envcp/`** Add to `.gitignore`
317
+ 2. **Use API keys for HTTP modes** Protect your server endpoints
318
+ 3. **Disable `allow_ai_active_check`** Prevent AI from probing for variables
319
+ 4. **Use blacklist patterns** Block sensitive variable patterns
320
+ 5. **Use `auto` mode for HTTP** Let EnvCP detect the client type
321
+ 6. **Review access logs** Check `.envcp/logs/` regularly
373
322
 
374
323
  ## License
375
324
 
376
- MIT License - See LICENSE file for details.
325
+ MIT License See LICENSE file for details.
377
326
 
378
327
  ## Support
379
328
 
@@ -10,6 +10,7 @@ export declare abstract class BaseAdapter {
10
10
  protected tools: Map<string, ToolDefinition>;
11
11
  constructor(config: EnvCPConfig, projectPath: string, password?: string);
12
12
  protected abstract registerTools(): void;
13
+ protected registerDefaultTools(): void;
13
14
  init(): Promise<void>;
14
15
  protected ensurePassword(): Promise<void>;
15
16
  getToolDefinitions(): ToolDefinition[];
@@ -60,9 +61,7 @@ export declare abstract class BaseAdapter {
60
61
  name: string;
61
62
  }): Promise<{
62
63
  name: string;
63
- exists: boolean;
64
64
  accessible: boolean;
65
- blacklisted: boolean;
66
65
  message: string;
67
66
  }>;
68
67
  private parseCommand;
@@ -1 +1 @@
1
- {"version":3,"file":"base.d.ts","sourceRoot":"","sources":["../../src/adapters/base.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"base.d.ts","sourceRoot":"","sources":["../../src/adapters/base.ts"],"names":[],"mappings":"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"}
@@ -1,6 +1,6 @@
1
1
  import { StorageManager, LogManager } from '../storage/index.js';
2
2
  import { maskValue } from '../utils/crypto.js';
3
- import { canAccess, isBlacklisted, canAIActiveCheck } from '../config/manager.js';
3
+ import { canAccess, isBlacklisted, canAIActiveCheck, validateVariableName, matchesPattern } from '../config/manager.js';
4
4
  import { SessionManager } from '../utils/session.js';
5
5
  import * as fs from 'fs-extra';
6
6
  import * as path from 'path';
@@ -24,6 +24,106 @@ export class BaseAdapter {
24
24
  this.tools = new Map();
25
25
  this.registerTools();
26
26
  }
27
+ registerDefaultTools() {
28
+ const tools = [
29
+ {
30
+ name: 'envcp_list',
31
+ description: 'List all available environment variable names. Values are never shown.',
32
+ parameters: {
33
+ type: 'object',
34
+ properties: {
35
+ tags: { type: 'array', items: { type: 'string' }, description: 'Filter by tags' },
36
+ },
37
+ },
38
+ handler: async (params) => this.listVariables(params),
39
+ },
40
+ {
41
+ name: 'envcp_get',
42
+ description: 'Get an environment variable. Returns masked value by default.',
43
+ parameters: {
44
+ type: 'object',
45
+ properties: {
46
+ name: { type: 'string', description: 'Variable name' },
47
+ show_value: { type: 'boolean', description: 'Show actual value (requires user confirmation)' },
48
+ },
49
+ required: ['name'],
50
+ },
51
+ handler: async (params) => this.getVariable(params),
52
+ },
53
+ {
54
+ name: 'envcp_set',
55
+ description: 'Create or update an environment variable.',
56
+ parameters: {
57
+ type: 'object',
58
+ properties: {
59
+ name: { type: 'string', description: 'Variable name' },
60
+ value: { type: 'string', description: 'Variable value' },
61
+ tags: { type: 'array', items: { type: 'string' }, description: 'Tags' },
62
+ description: { type: 'string', description: 'Description' },
63
+ },
64
+ required: ['name', 'value'],
65
+ },
66
+ handler: async (params) => this.setVariable(params),
67
+ },
68
+ {
69
+ name: 'envcp_delete',
70
+ description: 'Delete an environment variable.',
71
+ parameters: {
72
+ type: 'object',
73
+ properties: {
74
+ name: { type: 'string', description: 'Variable name' },
75
+ },
76
+ required: ['name'],
77
+ },
78
+ handler: async (params) => this.deleteVariable(params),
79
+ },
80
+ {
81
+ name: 'envcp_sync',
82
+ description: 'Sync variables to .env file.',
83
+ parameters: { type: 'object', properties: {} },
84
+ handler: async () => this.syncToEnv(),
85
+ },
86
+ {
87
+ name: 'envcp_run',
88
+ description: 'Execute a command with environment variables injected.',
89
+ parameters: {
90
+ type: 'object',
91
+ properties: {
92
+ command: { type: 'string', description: 'Command to execute' },
93
+ variables: { type: 'array', items: { type: 'string' }, description: 'Variables to inject' },
94
+ },
95
+ required: ['command', 'variables'],
96
+ },
97
+ handler: async (params) => this.runCommand(params),
98
+ },
99
+ {
100
+ name: 'envcp_add_to_env',
101
+ description: 'Write a stored variable to a .env file.',
102
+ parameters: {
103
+ type: 'object',
104
+ properties: {
105
+ name: { type: 'string', description: 'Variable name to add' },
106
+ env_file: { type: 'string', description: 'Path to .env file (default: .env)' },
107
+ },
108
+ required: ['name'],
109
+ },
110
+ handler: async (params) => this.addToEnv(params),
111
+ },
112
+ {
113
+ name: 'envcp_check_access',
114
+ description: 'Check if a variable exists and can be accessed.',
115
+ parameters: {
116
+ type: 'object',
117
+ properties: {
118
+ name: { type: 'string', description: 'Variable name to check' },
119
+ },
120
+ required: ['name'],
121
+ },
122
+ handler: async (params) => this.checkAccess(params),
123
+ },
124
+ ];
125
+ tools.forEach(tool => this.tools.set(tool.name, tool));
126
+ }
27
127
  async init() {
28
128
  await this.logs.init();
29
129
  await this.sessionManager.init();
@@ -111,6 +211,9 @@ export class BaseAdapter {
111
211
  if (!this.config.access.allow_ai_write) {
112
212
  throw new Error('AI write access is disabled');
113
213
  }
214
+ if (!validateVariableName(args.name)) {
215
+ throw new Error(`Invalid variable name '${args.name}'. Must match [A-Za-z_][A-Za-z0-9_]*`);
216
+ }
114
217
  if (isBlacklisted(args.name, this.config)) {
115
218
  throw new Error(`Variable '${args.name}' is blacklisted`);
116
219
  }
@@ -171,14 +274,13 @@ export class BaseAdapter {
171
274
  if (isBlacklisted(name, this.config)) {
172
275
  continue;
173
276
  }
174
- const excluded = this.config.sync.exclude?.some(pattern => {
175
- const regex = new RegExp('^' + pattern.replace(/\*/g, '.*') + '$');
176
- return regex.test(name);
177
- });
277
+ const excluded = this.config.sync.exclude?.some(pattern => matchesPattern(name, pattern));
178
278
  if (excluded || !variable.sync_to_env) {
179
279
  continue;
180
280
  }
181
- lines.push(`${name}=${variable.value}`);
281
+ const needsQuoting = /[\s#"'\\]/.test(variable.value);
282
+ const val = needsQuoting ? `"${variable.value.replace(/\\/g, '\\\\').replace(/"/g, '\\"')}"` : variable.value;
283
+ lines.push(`${name}=${val}`);
182
284
  }
183
285
  const envPath = path.join(this.projectPath, this.config.sync.target);
184
286
  await fs.writeFile(envPath, lines.join('\n'), 'utf8');
@@ -199,24 +301,29 @@ export class BaseAdapter {
199
301
  if (isBlacklisted(args.name, this.config)) {
200
302
  throw new Error(`Variable '${args.name}' is blacklisted`);
201
303
  }
202
- const envPath = path.join(this.projectPath, args.env_file || '.env');
304
+ const envPath = path.resolve(this.projectPath, args.env_file || '.env');
305
+ if (!envPath.startsWith(path.resolve(this.projectPath))) {
306
+ throw new Error('env_file must be within the project directory');
307
+ }
203
308
  let content = '';
204
309
  if (await fs.pathExists(envPath)) {
205
310
  content = await fs.readFile(envPath, 'utf8');
206
311
  }
207
312
  const envVars = dotenv.parse(content);
313
+ const needsQuoting = /[\s#"'\\]/.test(variable.value);
314
+ const quotedValue = needsQuoting ? `"${variable.value.replace(/\\/g, '\\\\').replace(/"/g, '\\"')}"` : variable.value;
208
315
  if (envVars[args.name]) {
209
316
  const lines = content.split('\n');
210
317
  const newLines = lines.map(line => {
211
318
  if (line.startsWith(`${args.name}=`)) {
212
- return `${args.name}=${variable.value}`;
319
+ return `${args.name}=${quotedValue}`;
213
320
  }
214
321
  return line;
215
322
  });
216
323
  content = newLines.join('\n');
217
324
  }
218
325
  else {
219
- content += `\n${args.name}=${variable.value}`;
326
+ content += `\n${args.name}=${quotedValue}`;
220
327
  }
221
328
  await fs.writeFile(envPath, content, 'utf8');
222
329
  await this.logs.log({
@@ -244,10 +351,8 @@ export class BaseAdapter {
244
351
  });
245
352
  return {
246
353
  name: args.name,
247
- exists,
248
354
  accessible,
249
- blacklisted,
250
- message: accessible ? 'Variable exists and can be accessed' : 'Variable cannot be accessed or does not exist',
355
+ message: accessible ? 'Variable exists and can be accessed' : 'Variable cannot be accessed',
251
356
  };
252
357
  }
253
358
  parseCommand(command) {
@@ -286,9 +391,17 @@ export class BaseAdapter {
286
391
  }
287
392
  }
288
393
  async runCommand(args) {
394
+ if (!this.config.access.allow_ai_execute) {
395
+ throw new Error('AI command execution is disabled');
396
+ }
289
397
  this.validateCommand(args.command);
290
398
  const { spawn } = await import('child_process');
291
- const { program, args: cmdArgs } = this.parseCommand(args.command);
399
+ const { program: prog, args: cmdArgs } = this.parseCommand(args.command);
400
+ if (this.config.access.allowed_commands && this.config.access.allowed_commands.length > 0) {
401
+ if (!this.config.access.allowed_commands.includes(prog)) {
402
+ throw new Error(`Command '${prog}' is not in the allowed commands list`);
403
+ }
404
+ }
292
405
  const env = { ...process.env };
293
406
  for (const name of args.variables) {
294
407
  if (isBlacklisted(name, this.config)) {
@@ -299,16 +412,28 @@ export class BaseAdapter {
299
412
  env[name] = variable.value;
300
413
  }
301
414
  }
415
+ const TIMEOUT_MS = 30000;
302
416
  return new Promise((resolve) => {
303
- const proc = spawn(program, cmdArgs, {
417
+ const proc = spawn(prog, cmdArgs, {
304
418
  env,
305
419
  cwd: this.projectPath,
306
420
  });
307
421
  let stdout = '';
308
422
  let stderr = '';
423
+ let killed = false;
424
+ const timer = setTimeout(() => {
425
+ killed = true;
426
+ proc.kill('SIGTERM');
427
+ setTimeout(() => { if (!proc.killed)
428
+ proc.kill('SIGKILL'); }, 5000);
429
+ }, TIMEOUT_MS);
309
430
  proc.stdout.on('data', (data) => { stdout += data; });
310
431
  proc.stderr.on('data', (data) => { stderr += data; });
311
432
  proc.on('close', (code) => {
433
+ clearTimeout(timer);
434
+ if (killed) {
435
+ stderr += '\n[Process killed: exceeded 30s timeout]';
436
+ }
312
437
  resolve({ exitCode: code, stdout, stderr });
313
438
  });
314
439
  });