@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
package/src/cli/index.ts CHANGED
@@ -3,11 +3,51 @@ import inquirer from 'inquirer';
3
3
  import chalk from 'chalk';
4
4
  import * as path from 'path';
5
5
  import * as fs from 'fs-extra';
6
- import { loadConfig, saveConfig, initConfig } from '../config/manager.js';
7
- import { StorageManager, LogManager } from '../storage/index.js';
6
+ import { loadConfig, initConfig } from '../config/manager.js';
7
+ import { StorageManager } from '../storage/index.js';
8
8
  import { SessionManager } from '../utils/session.js';
9
9
  import { maskValue, validatePassword } from '../utils/crypto.js';
10
- import { Variable } from '../types.js';
10
+ import { Variable, EnvCPConfig } from '../types.js';
11
+
12
+ async function withSession(fn: (storage: StorageManager, password: string, config: EnvCPConfig, projectPath: string) => Promise<void>): Promise<void> {
13
+ const projectPath = process.cwd();
14
+ const config = await loadConfig(projectPath);
15
+
16
+ const sessionManager = new SessionManager(
17
+ path.join(projectPath, config.session?.path || '.envcp/.session'),
18
+ config.session?.timeout_minutes || 30,
19
+ config.session?.max_extensions || 5
20
+ );
21
+ await sessionManager.init();
22
+
23
+ let session = await sessionManager.load();
24
+ let password = '';
25
+
26
+ if (!session) {
27
+ const answer = await inquirer.prompt([
28
+ { type: 'password', name: 'password', message: 'Enter password:', mask: '*' }
29
+ ]);
30
+ password = answer.password;
31
+
32
+ const validation = validatePassword(password, config.password || {});
33
+ if (!validation.valid) {
34
+ console.log(chalk.red(validation.error));
35
+ return;
36
+ }
37
+
38
+ session = await sessionManager.create(password);
39
+ }
40
+
41
+ password = sessionManager.getPassword() || password;
42
+
43
+ const storage = new StorageManager(
44
+ path.join(projectPath, config.storage.path),
45
+ config.storage.encrypted
46
+ );
47
+ if (password) storage.setPassword(password);
48
+
49
+ await fn(storage, password, config, projectPath);
50
+ }
11
51
 
12
52
  const program = new Command();
13
53
 
@@ -207,74 +247,39 @@ program
207
247
  .option('-t, --tags <tags>', 'Tags (comma-separated)')
208
248
  .option('-d, --description <desc>', 'Description')
209
249
  .action(async (name, options) => {
210
- const projectPath = process.cwd();
211
- const config = await loadConfig(projectPath);
212
-
213
- const sessionManager = new SessionManager(
214
- path.join(projectPath, config.session?.path || '.envcp/.session'),
215
- config.session?.timeout_minutes || 30,
216
- config.session?.max_extensions || 5
217
- );
218
- await sessionManager.init();
219
-
220
- let session = await sessionManager.load();
221
- let password = '';
222
-
223
- if (!session) {
224
- const answer = await inquirer.prompt([
225
- { type: 'password', name: 'password', message: 'Enter password:', mask: '*' }
226
- ]);
227
- password = answer.password;
228
-
229
- const validation = validatePassword(password, config.password || {});
230
- if (!validation.valid) {
231
- console.log(chalk.red(validation.error));
232
- return;
250
+ await withSession(async (storage, _password, config) => {
251
+ let value = options.value;
252
+ let tags: string[] = [];
253
+ let description = options.description;
254
+
255
+ if (!value) {
256
+ const answers = await inquirer.prompt([
257
+ { type: 'password', name: 'value', message: 'Enter value:', mask: '*' },
258
+ { type: 'input', name: 'tags', message: 'Tags (comma-separated):' },
259
+ { type: 'input', name: 'description', message: 'Description:' },
260
+ ]);
261
+ value = answers.value;
262
+ tags = answers.tags.split(',').map((t: string) => t.trim()).filter(Boolean);
263
+ description = answers.description;
264
+ } else if (options.tags) {
265
+ tags = options.tags.split(',').map((t: string) => t.trim()).filter(Boolean);
233
266
  }
234
-
235
- session = await sessionManager.create(password);
236
- }
237
-
238
- password = sessionManager.getPassword() || password;
239
-
240
- let value = options.value;
241
- let tags: string[] = [];
242
- let description = options.description;
243
-
244
- if (!value) {
245
- const answers = await inquirer.prompt([
246
- { type: 'password', name: 'value', message: 'Enter value:', mask: '*' },
247
- { type: 'input', name: 'tags', message: 'Tags (comma-separated):' },
248
- { type: 'input', name: 'description', message: 'Description:' },
249
- ]);
250
- value = answers.value;
251
- tags = answers.tags.split(',').map((t: string) => t.trim()).filter(Boolean);
252
- description = answers.description;
253
- } else if (options.tags) {
254
- tags = options.tags.split(',').map((t: string) => t.trim()).filter(Boolean);
255
- }
256
-
257
- const storage = new StorageManager(
258
- path.join(projectPath, config.storage.path),
259
- config.storage.encrypted
260
- );
261
- if (password) storage.setPassword(password);
262
-
263
- const now = new Date().toISOString();
264
- const variable: Variable = {
265
- name,
266
- value,
267
- encrypted: config.storage.encrypted,
268
- tags: tags.length > 0 ? tags : undefined,
269
- description,
270
- created: now,
271
- updated: now,
272
- sync_to_env: true,
273
- };
274
267
 
275
- await storage.set(name, variable);
276
-
277
- console.log(chalk.green(`Variable '${name}' added successfully`));
268
+ const now = new Date().toISOString();
269
+ const variable: Variable = {
270
+ name,
271
+ value,
272
+ encrypted: config.storage.encrypted,
273
+ tags: tags.length > 0 ? tags : undefined,
274
+ description,
275
+ created: now,
276
+ updated: now,
277
+ sync_to_env: true,
278
+ };
279
+
280
+ await storage.set(name, variable);
281
+ console.log(chalk.green(`Variable '${name}' added successfully`));
282
+ });
278
283
  });
279
284
 
280
285
  program
@@ -282,194 +287,88 @@ program
282
287
  .description('List all variables (names only, values hidden)')
283
288
  .option('-v, --show-values', 'Show actual values')
284
289
  .action(async (options) => {
285
- const projectPath = process.cwd();
286
- const config = await loadConfig(projectPath);
287
-
288
- const sessionManager = new SessionManager(
289
- path.join(projectPath, config.session?.path || '.envcp/.session'),
290
- config.session?.timeout_minutes || 30,
291
- config.session?.max_extensions || 5
292
- );
293
- await sessionManager.init();
294
-
295
- let session = await sessionManager.load();
296
- let password = '';
297
-
298
- if (!session) {
299
- const answer = await inquirer.prompt([
300
- { type: 'password', name: 'password', message: 'Enter password:', mask: '*' }
301
- ]);
302
- password = answer.password;
303
- session = await sessionManager.create(password);
304
- }
305
-
306
- password = sessionManager.getPassword() || password;
290
+ await withSession(async (storage) => {
291
+ const variables = await storage.load();
292
+ const names = Object.keys(variables);
307
293
 
308
- const storage = new StorageManager(
309
- path.join(projectPath, config.storage.path),
310
- config.storage.encrypted
311
- );
312
- if (password) storage.setPassword(password);
294
+ if (names.length === 0) {
295
+ console.log(chalk.yellow('No variables found'));
296
+ return;
297
+ }
313
298
 
314
- const variables = await storage.load();
315
- const names = Object.keys(variables);
299
+ console.log(chalk.blue(`\nVariables (${names.length}):\n`));
316
300
 
317
- if (names.length === 0) {
318
- console.log(chalk.yellow('No variables found'));
319
- return;
320
- }
301
+ for (const name of names) {
302
+ const v = variables[name];
303
+ const value = options.showValues ? v.value : maskValue(v.value);
304
+ const tags = v.tags ? chalk.gray(` [${v.tags.join(', ')}]`) : '';
305
+ console.log(` ${chalk.cyan(name)} = ${value}${tags}`);
306
+ }
321
307
 
322
- console.log(chalk.blue(`\nVariables (${names.length}):\n`));
323
-
324
- for (const name of names) {
325
- const v = variables[name];
326
- const value = options.showValues ? v.value : maskValue(v.value);
327
- const tags = v.tags ? chalk.gray(` [${v.tags.join(', ')}]`) : '';
328
- console.log(` ${chalk.cyan(name)} = ${value}${tags}`);
329
- }
330
-
331
- console.log('');
308
+ console.log('');
309
+ });
332
310
  });
333
311
 
334
312
  program
335
313
  .command('get <name>')
336
314
  .description('Get a variable value')
337
315
  .action(async (name) => {
338
- const projectPath = process.cwd();
339
- const config = await loadConfig(projectPath);
340
-
341
- const sessionManager = new SessionManager(
342
- path.join(projectPath, config.session?.path || '.envcp/.session'),
343
- config.session?.timeout_minutes || 30,
344
- config.session?.max_extensions || 5
345
- );
346
- await sessionManager.init();
347
-
348
- let session = await sessionManager.load();
349
- let password = '';
350
-
351
- if (!session) {
352
- const answer = await inquirer.prompt([
353
- { type: 'password', name: 'password', message: 'Enter password:', mask: '*' }
354
- ]);
355
- password = answer.password;
356
- session = await sessionManager.create(password);
357
- }
358
-
359
- password = sessionManager.getPassword() || password;
360
-
361
- const storage = new StorageManager(
362
- path.join(projectPath, config.storage.path),
363
- config.storage.encrypted
364
- );
365
- if (password) storage.setPassword(password);
316
+ await withSession(async (storage) => {
317
+ const variable = await storage.get(name);
366
318
 
367
- const variable = await storage.get(name);
368
-
369
- if (!variable) {
370
- console.log(chalk.red(`Variable '${name}' not found`));
371
- return;
372
- }
319
+ if (!variable) {
320
+ console.log(chalk.red(`Variable '${name}' not found`));
321
+ return;
322
+ }
373
323
 
374
- console.log(chalk.cyan(name));
375
- console.log(` Value: ${variable.value}`);
376
- if (variable.tags) console.log(` Tags: ${variable.tags.join(', ')}`);
377
- if (variable.description) console.log(` Description: ${variable.description}`);
324
+ console.log(chalk.cyan(name));
325
+ console.log(` Value: ${variable.value}`);
326
+ if (variable.tags) console.log(` Tags: ${variable.tags.join(', ')}`);
327
+ if (variable.description) console.log(` Description: ${variable.description}`);
328
+ });
378
329
  });
379
330
 
380
331
  program
381
332
  .command('remove <name>')
382
333
  .description('Remove a variable')
383
334
  .action(async (name) => {
384
- const projectPath = process.cwd();
385
- const config = await loadConfig(projectPath);
386
-
387
- const sessionManager = new SessionManager(
388
- path.join(projectPath, config.session?.path || '.envcp/.session'),
389
- config.session?.timeout_minutes || 30,
390
- config.session?.max_extensions || 5
391
- );
392
- await sessionManager.init();
393
-
394
- let session = await sessionManager.load();
395
- let password = '';
396
-
397
- if (!session) {
398
- const answer = await inquirer.prompt([
399
- { type: 'password', name: 'password', message: 'Enter password:', mask: '*' }
400
- ]);
401
- password = answer.password;
402
- session = await sessionManager.create(password);
403
- }
404
-
405
- password = sessionManager.getPassword() || password;
406
-
407
- const storage = new StorageManager(
408
- path.join(projectPath, config.storage.path),
409
- config.storage.encrypted
410
- );
411
- if (password) storage.setPassword(password);
335
+ await withSession(async (storage) => {
336
+ const deleted = await storage.delete(name);
412
337
 
413
- const deleted = await storage.delete(name);
414
-
415
- if (deleted) {
416
- console.log(chalk.green(`Variable '${name}' removed`));
417
- } else {
418
- console.log(chalk.red(`Variable '${name}' not found`));
419
- }
338
+ if (deleted) {
339
+ console.log(chalk.green(`Variable '${name}' removed`));
340
+ } else {
341
+ console.log(chalk.red(`Variable '${name}' not found`));
342
+ }
343
+ });
420
344
  });
421
345
 
422
346
  program
423
347
  .command('sync')
424
348
  .description('Sync variables to .env file')
425
349
  .action(async () => {
426
- const projectPath = process.cwd();
427
- const config = await loadConfig(projectPath);
428
-
429
- if (!config.sync.enabled) {
430
- console.log(chalk.yellow('Sync is disabled in configuration'));
431
- return;
432
- }
433
-
434
- const sessionManager = new SessionManager(
435
- path.join(projectPath, config.session?.path || '.envcp/.session'),
436
- config.session?.timeout_minutes || 30,
437
- config.session?.max_extensions || 5
438
- );
439
- await sessionManager.init();
440
-
441
- let session = await sessionManager.load();
442
- let password = '';
443
-
444
- if (!session) {
445
- const answer = await inquirer.prompt([
446
- { type: 'password', name: 'password', message: 'Enter password:', mask: '*' }
447
- ]);
448
- password = answer.password;
449
- session = await sessionManager.create(password);
450
- }
451
-
452
- password = sessionManager.getPassword() || password;
350
+ await withSession(async (storage, _password, config, projectPath) => {
351
+ if (!config.sync.enabled) {
352
+ console.log(chalk.yellow('Sync is disabled in configuration'));
353
+ return;
354
+ }
453
355
 
454
- const storage = new StorageManager(
455
- path.join(projectPath, config.storage.path),
456
- config.storage.encrypted
457
- );
458
- if (password) storage.setPassword(password);
356
+ const variables = await storage.load();
357
+ const lines: string[] = [];
459
358
 
460
- const variables = await storage.load();
461
- const lines: string[] = [];
462
-
463
- if (config.sync.header) {
464
- lines.push(config.sync.header);
465
- }
359
+ if (config.sync.header) {
360
+ lines.push(config.sync.header);
361
+ }
466
362
 
467
- for (const [name, variable] of Object.entries(variables)) {
468
- lines.push(`${name}=${variable.value}`);
469
- }
363
+ for (const [name, variable] of Object.entries(variables)) {
364
+ const needsQuoting = /[\s#"'\\]/.test(variable.value);
365
+ const val = needsQuoting ? `"${variable.value.replace(/\\/g, '\\\\').replace(/"/g, '\\"')}"` : variable.value;
366
+ lines.push(`${name}=${val}`);
367
+ }
470
368
 
471
- await fs.writeFile(path.join(projectPath, config.sync.target), lines.join('\n'), 'utf8');
472
- console.log(chalk.green(`Synced ${lines.length} variables to ${config.sync.target}`));
369
+ await fs.writeFile(path.join(projectPath, config.sync.target), lines.join('\n'), 'utf8');
370
+ console.log(chalk.green(`Synced ${lines.length} variables to ${config.sync.target}`));
371
+ });
473
372
  });
474
373
 
475
374
  program
@@ -528,7 +427,7 @@ program
528
427
  const { UnifiedServer } = await import('../server/unified.js');
529
428
 
530
429
  const serverConfig = {
531
- mode: mode as any,
430
+ mode: mode as 'mcp' | 'rest' | 'openai' | 'gemini' | 'all' | 'auto',
532
431
  port,
533
432
  host,
534
433
  api_key: apiKey,
@@ -588,35 +487,7 @@ program
588
487
  .description('Export variables')
589
488
  .option('-f, --format <format>', 'Output format: env, json, yaml', 'env')
590
489
  .action(async (options) => {
591
- const projectPath = process.cwd();
592
- const config = await loadConfig(projectPath);
593
-
594
- const sessionManager = new SessionManager(
595
- path.join(projectPath, config.session?.path || '.envcp/.session'),
596
- config.session?.timeout_minutes || 30,
597
- config.session?.max_extensions || 5
598
- );
599
- await sessionManager.init();
600
-
601
- let session = await sessionManager.load();
602
- let password = '';
603
-
604
- if (!session) {
605
- const answer = await inquirer.prompt([
606
- { type: 'password', name: 'password', message: 'Enter password:', mask: '*' }
607
- ]);
608
- password = answer.password;
609
- session = await sessionManager.create(password);
610
- }
611
-
612
- password = sessionManager.getPassword() || password;
613
-
614
- const storage = new StorageManager(
615
- path.join(projectPath, config.storage.path),
616
- config.storage.encrypted
617
- );
618
- if (password) storage.setPassword(password);
619
-
490
+ await withSession(async (storage) => {
620
491
  const variables = await storage.load();
621
492
 
622
493
  let output: string;
@@ -630,11 +501,16 @@ program
630
501
  output = yaml.dump(variables);
631
502
  break;
632
503
  default:
633
- const lines = Object.entries(variables).map(([k, v]) => `${k}=${v.value}`);
504
+ const lines = Object.entries(variables).map(([k, v]) => {
505
+ const needsQuoting = /[\s#"'\\]/.test(v.value);
506
+ const val = needsQuoting ? `"${v.value.replace(/\\/g, '\\\\').replace(/"/g, '\\"')}"` : v.value;
507
+ return `${k}=${val}`;
508
+ });
634
509
  output = lines.join('\n');
635
510
  }
636
511
 
637
512
  console.log(output);
513
+ });
638
514
  });
639
515
 
640
516
  program.parse();
@@ -16,8 +16,10 @@ const DEFAULT_CONFIG: Partial<EnvCPConfig> = {
16
16
  allow_ai_write: false,
17
17
  allow_ai_delete: false,
18
18
  allow_ai_export: false,
19
+ allow_ai_execute: false,
19
20
  allow_ai_active_check: false,
20
21
  require_user_reference: true,
22
+ allowed_commands: undefined,
21
23
  require_confirmation: true,
22
24
  mask_values: true,
23
25
  audit_log: true,
@@ -92,7 +94,8 @@ export function validateVariableName(name: string): boolean {
92
94
  }
93
95
 
94
96
  export function matchesPattern(name: string, pattern: string): boolean {
95
- const regex = new RegExp('^' + pattern.replace(/\*/g, '.*') + '$');
97
+ const escaped = pattern.replace(/[.+?^${}()|[\]\\]/g, '\\$&');
98
+ const regex = new RegExp('^' + escaped.replace(/\*/g, '.*') + '$');
96
99
  return regex.test(name);
97
100
  }
98
101