@fentz26/envcp 1.2.0-exp.8d5b91b → 1.2.0-exp.92feaad

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 (86) hide show
  1. package/VERSION +1 -1
  2. package/dist/adapters/base.d.ts +25 -2
  3. package/dist/adapters/base.d.ts.map +1 -1
  4. package/dist/adapters/base.js +68 -6
  5. package/dist/adapters/base.js.map +1 -1
  6. package/dist/adapters/gemini.d.ts +1 -4
  7. package/dist/adapters/gemini.d.ts.map +1 -1
  8. package/dist/adapters/gemini.js +21 -112
  9. package/dist/adapters/gemini.js.map +1 -1
  10. package/dist/adapters/openai.d.ts +1 -4
  11. package/dist/adapters/openai.d.ts.map +1 -1
  12. package/dist/adapters/openai.js +25 -126
  13. package/dist/adapters/openai.js.map +1 -1
  14. package/dist/adapters/rest.d.ts.map +1 -1
  15. package/dist/adapters/rest.js +7 -3
  16. package/dist/adapters/rest.js.map +1 -1
  17. package/dist/cli/index.js +109 -62
  18. package/dist/cli/index.js.map +1 -1
  19. package/dist/config/config-guard.d.ts.map +1 -1
  20. package/dist/config/config-guard.js +9 -4
  21. package/dist/config/config-guard.js.map +1 -1
  22. package/dist/config/config-hmac.js +2 -2
  23. package/dist/config/config-hmac.js.map +1 -1
  24. package/dist/config/manager.d.ts.map +1 -1
  25. package/dist/config/manager.js +22 -20
  26. package/dist/config/manager.js.map +1 -1
  27. package/dist/mcp/server.d.ts.map +1 -1
  28. package/dist/mcp/server.js +2 -1
  29. package/dist/mcp/server.js.map +1 -1
  30. package/dist/server/unified.js +1 -1
  31. package/dist/server/unified.js.map +1 -1
  32. package/dist/service/config.d.ts.map +1 -1
  33. package/dist/service/config.js +5 -4
  34. package/dist/service/config.js.map +1 -1
  35. package/dist/service/generators.d.ts.map +1 -1
  36. package/dist/service/generators.js +6 -8
  37. package/dist/service/generators.js.map +1 -1
  38. package/dist/service/index.js +4 -3
  39. package/dist/service/index.js.map +1 -1
  40. package/dist/service/platform.js +2 -2
  41. package/dist/service/platform.js.map +1 -1
  42. package/dist/storage/index.d.ts +1 -1
  43. package/dist/storage/index.d.ts.map +1 -1
  44. package/dist/storage/index.js +35 -27
  45. package/dist/storage/index.js.map +1 -1
  46. package/dist/types.js +1 -1
  47. package/dist/types.js.map +1 -1
  48. package/dist/utils/crypto.js +2 -2
  49. package/dist/utils/crypto.js.map +1 -1
  50. package/dist/utils/fs.js +6 -6
  51. package/dist/utils/fs.js.map +1 -1
  52. package/dist/utils/hsm.d.ts +8 -8
  53. package/dist/utils/hsm.d.ts.map +1 -1
  54. package/dist/utils/hsm.js +7 -6
  55. package/dist/utils/hsm.js.map +1 -1
  56. package/dist/utils/http.d.ts +1 -1
  57. package/dist/utils/http.d.ts.map +1 -1
  58. package/dist/utils/http.js +1 -1
  59. package/dist/utils/http.js.map +1 -1
  60. package/dist/utils/keychain.js +4 -4
  61. package/dist/utils/keychain.js.map +1 -1
  62. package/dist/utils/lock.d.ts.map +1 -1
  63. package/dist/utils/lock.js +2 -1
  64. package/dist/utils/lock.js.map +1 -1
  65. package/dist/utils/lockout.d.ts +2 -2
  66. package/dist/utils/lockout.d.ts.map +1 -1
  67. package/dist/utils/lockout.js +3 -3
  68. package/dist/utils/lockout.js.map +1 -1
  69. package/dist/utils/prompt.js +3 -3
  70. package/dist/utils/prompt.js.map +1 -1
  71. package/dist/utils/secure-memory.d.ts.map +1 -1
  72. package/dist/utils/secure-memory.js +6 -3
  73. package/dist/utils/secure-memory.js.map +1 -1
  74. package/dist/utils/session.js +3 -3
  75. package/dist/utils/session.js.map +1 -1
  76. package/dist/utils/update-checker.d.ts +3 -0
  77. package/dist/utils/update-checker.d.ts.map +1 -1
  78. package/dist/utils/update-checker.js +51 -5
  79. package/dist/utils/update-checker.js.map +1 -1
  80. package/dist/vault/index.d.ts.map +1 -1
  81. package/dist/vault/index.js +5 -3
  82. package/dist/vault/index.js.map +1 -1
  83. package/dist/version.js +3 -3
  84. package/dist/version.js.map +1 -1
  85. package/package.json +1 -1
  86. package/scripts/sync-version.js +65 -23
package/dist/cli/index.js CHANGED
@@ -13,7 +13,8 @@ import { SessionManager } from '../utils/session.js';
13
13
  import { maskValue, validatePassword, encrypt, decrypt, generateRecoveryKey, createRecoveryData, recoverPassword } from '../utils/crypto.js';
14
14
  import { KeychainManager } from '../utils/keychain.js';
15
15
  import { HsmManager } from '../utils/hsm.js';
16
- import { checkForUpdate, formatUpdateMessage, logUpdateCheck } from '../utils/update-checker.js';
16
+ import { checkForUpdate, formatUpdateMessage, logUpdateCheck, fetchReleases, filterByChannel } from '../utils/update-checker.js';
17
+ import { spawnSync } from 'child_process';
17
18
  import { LockoutManager } from '../utils/lockout.js';
18
19
  import { initMemoryProtection } from '../utils/secure-memory.js';
19
20
  import { getGlobalVaultPath, getProjectVaultPath, resolveVaultPath, resolveSessionPath, setActiveVault, listVaults, initNamedVault, } from '../vault/index.js';
@@ -33,6 +34,38 @@ async function resolveCliContext(vaultOverride) {
33
34
  }
34
35
  return { projectPath, config };
35
36
  }
37
+ function buildSecuritySettings(securityChoice) {
38
+ const bruteForceProtection = {
39
+ enabled: true,
40
+ max_attempts: 5,
41
+ lockout_duration: 300,
42
+ progressive_delay: true,
43
+ max_delay: 60,
44
+ permanent_lockout_threshold: 50,
45
+ permanent_lockout_action: 'require_recovery_key',
46
+ notifications: {},
47
+ };
48
+ if (securityChoice === 'none') {
49
+ return {
50
+ encryption: { enabled: false },
51
+ storageEncrypted: false,
52
+ security: {
53
+ mode: 'recoverable',
54
+ recovery_file: '.envcp/.recovery',
55
+ brute_force_protection: bruteForceProtection,
56
+ },
57
+ };
58
+ }
59
+ return {
60
+ encryption: { enabled: true },
61
+ storageEncrypted: true,
62
+ security: {
63
+ mode: securityChoice,
64
+ recovery_file: '.envcp/.recovery',
65
+ brute_force_protection: bruteForceProtection,
66
+ },
67
+ };
68
+ }
36
69
  async function withSession(fn, vaultOverride) {
37
70
  const { projectPath, config } = await resolveCliContext(vaultOverride);
38
71
  const vaultPath = vaultOverride
@@ -188,43 +221,10 @@ program
188
221
  { name: 'Encrypted hard-lock (max security, no recovery)', value: 'hard-lock' },
189
222
  ], 'recoverable');
190
223
  }
191
- // Apply security choice to config
192
- if (securityChoice === 'none') {
193
- config.encryption = { enabled: false };
194
- config.storage.encrypted = false;
195
- config.security = {
196
- mode: 'recoverable',
197
- recovery_file: '.envcp/.recovery',
198
- brute_force_protection: {
199
- enabled: true,
200
- max_attempts: 5,
201
- lockout_duration: 300,
202
- progressive_delay: true,
203
- max_delay: 60,
204
- permanent_lockout_threshold: 50,
205
- permanent_lockout_action: 'require_recovery_key',
206
- notifications: {}
207
- }
208
- };
209
- }
210
- else {
211
- config.encryption = { enabled: true };
212
- config.storage.encrypted = true;
213
- config.security = {
214
- mode: securityChoice,
215
- recovery_file: '.envcp/.recovery',
216
- brute_force_protection: {
217
- enabled: true,
218
- max_attempts: 5,
219
- lockout_duration: 300,
220
- progressive_delay: true,
221
- max_delay: 60,
222
- permanent_lockout_threshold: 50,
223
- permanent_lockout_action: 'require_recovery_key',
224
- notifications: {},
225
- }
226
- };
227
- }
224
+ const securitySettings = buildSecuritySettings(securityChoice);
225
+ config.encryption = securitySettings.encryption;
226
+ config.storage.encrypted = securitySettings.storageEncrypted;
227
+ config.security = securitySettings.security;
228
228
  // For encrypted modes: get password now
229
229
  let pwd = '';
230
230
  if (securityChoice !== 'none') {
@@ -360,7 +360,6 @@ program
360
360
  program
361
361
  .command('unlock')
362
362
  .description('Unlock EnvCP session with password')
363
- .option('-p, --password <password>', 'Password (will prompt if not provided)')
364
363
  .option('--recovery-key <key>', 'Recovery key to clear permanent lockout')
365
364
  .option('--save-to-keychain', 'Save password to OS keychain for auto-unlock')
366
365
  .option('--setup-hsm', 'Protect vault password with hardware security module')
@@ -370,10 +369,7 @@ program
370
369
  .option('--global', 'Unlock the global vault at ~/.envcp')
371
370
  .action(async (options) => {
372
371
  const { projectPath, config } = await resolveCliContext(options.global ? 'global' : undefined);
373
- let password = options.password;
374
- if (!password) {
375
- password = await promptPassword('Enter password:');
376
- }
372
+ const password = await promptPassword('Enter password:');
377
373
  const { valid: passwordValid, warning: passwordWarning } = validatePassword(password, config.password || {});
378
374
  if (!passwordValid) {
379
375
  // nosem: no tainted data flows to log
@@ -1003,13 +999,13 @@ program
1003
999
  continue;
1004
1000
  const excluded = config.sync.exclude?.some((pattern) => {
1005
1001
  // eslint-disable-next-line security/detect-non-literal-regexp -- glob pattern from config; metacharacters escaped above
1006
- const regex = new RegExp('^' + pattern.replace(/[.+?^${}()|[\]\\]/g, '\\$&').replace(/\*/g, '.*') + '$');
1002
+ const regex = new RegExp('^' + pattern.replaceAll(/[.+?^${}()|[\]\\]/g, '\\$&').replaceAll('*', '.*') + '$');
1007
1003
  return regex.test(name);
1008
1004
  });
1009
1005
  if (excluded)
1010
1006
  continue;
1011
1007
  const needsQuoting = /[\s#"'\\]/.test(variable.value);
1012
- const val = needsQuoting ? `"${variable.value.replace(/\\/g, '\\\\').replace(/"/g, '\\"')}"` : variable.value;
1008
+ const val = needsQuoting ? `"${variable.value.replaceAll('\\', '\\\\').replaceAll('"', '\\"')}"` : variable.value;
1013
1009
  lines.push(`${name}=${val}`);
1014
1010
  }
1015
1011
  if (options.dryRun) {
@@ -1029,7 +1025,7 @@ program
1029
1025
  continue;
1030
1026
  const excluded = config.sync.exclude?.some((pattern) => {
1031
1027
  // eslint-disable-next-line security/detect-non-literal-regexp -- glob pattern from config; metacharacters escaped above
1032
- const regex = new RegExp('^' + pattern.replace(/[.+?^${}()|[\]\\]/g, '\\$&').replace(/\*/g, '.*') + '$');
1028
+ const regex = new RegExp('^' + pattern.replaceAll(/[.+?^${}()|[\]\\]/g, '\\$&').replaceAll('*', '.*') + '$');
1033
1029
  return regex.test(name);
1034
1030
  });
1035
1031
  if (excluded)
@@ -1073,7 +1069,6 @@ program
1073
1069
  program
1074
1070
  .command('serve')
1075
1071
  .description('Start EnvCP server')
1076
- .option('-p, --password <password>', 'Encryption password')
1077
1072
  .option('-m, --mode <mode>', 'Server mode: mcp, rest, openai, gemini, all, auto', 'auto')
1078
1073
  .option('--port <port>', 'HTTP port (for non-MCP modes)', '3456')
1079
1074
  .option('--host <host>', 'HTTP host', '127.0.0.1')
@@ -1116,7 +1111,7 @@ program
1116
1111
  const port = parseInt(options.port, 10);
1117
1112
  const host = options.host;
1118
1113
  const apiKey = options.apiKey;
1119
- let password = options.password || '';
1114
+ let password = '';
1120
1115
  // Passwordless mode: skip all session/password logic
1121
1116
  if (config.encryption?.enabled === false) {
1122
1117
  if (mode === 'mcp') {
@@ -1134,7 +1129,7 @@ program
1134
1129
  if (!session && !password) {
1135
1130
  // MCP mode uses stdio — can't prompt interactively
1136
1131
  if (mode === 'mcp') {
1137
- process.stderr.write(`Error: No active session at ${sessionPath}. Run \`envcp unlock${forceGlobalMode ? ' --global' : ''}\` first, or use --password flag.\n`);
1132
+ process.stderr.write(`Error: No active session at ${sessionPath}. Run \`envcp unlock${forceGlobalMode ? ' --global' : ''}\` first.\n`);
1138
1133
  process.exit(1);
1139
1134
  }
1140
1135
  password = await promptPassword('Enter password:');
@@ -1258,7 +1253,7 @@ program
1258
1253
  default: {
1259
1254
  const lines = Object.entries(variables).map(([k, v]) => {
1260
1255
  const needsQuoting = /[\s#"'\\]/.test(v.value);
1261
- const val = needsQuoting ? `"${v.value.replace(/\\/g, '\\\\').replace(/"/g, '\\"')}"` : v.value;
1256
+ const val = needsQuoting ? `"${v.value.replaceAll('\\', '\\\\').replaceAll('"', '\\"')}"` : v.value;
1262
1257
  return `${k}=${val}`;
1263
1258
  });
1264
1259
  output = lines.join('\n');
@@ -1367,7 +1362,7 @@ program
1367
1362
  console.log(chalk.yellow('No variables to backup'));
1368
1363
  return;
1369
1364
  }
1370
- const timestamp = new Date().toISOString().replace(/[:.]/g, '-');
1365
+ const timestamp = new Date().toISOString().replaceAll(/[:.]/g, '-');
1371
1366
  const defaultPath = path.join(projectPath, `.envcp/backup-${timestamp}.enc`);
1372
1367
  const outputPath = options.output || defaultPath;
1373
1368
  const backupData = JSON.stringify({
@@ -1547,32 +1542,84 @@ program
1547
1542
  });
1548
1543
  program
1549
1544
  .command('update')
1550
- .description('Check for EnvCP updates')
1551
- .option('--check', 'Check for available updates')
1545
+ .description('Check for or install EnvCP updates')
1546
+ .option('-l, --latest', 'List and install latest stable versions')
1547
+ .option('-e, --experimental', 'List and install experimental versions')
1548
+ .option('-c, --canary', 'List and install canary versions')
1549
+ .option('--backup', 'Backup vault before installing')
1552
1550
  .action(async (options) => {
1553
1551
  const projectPath = process.cwd();
1554
- if (options.check || Object.keys(options).length === 0) {
1552
+ const home = os.homedir();
1553
+ const channel = options.latest ? 'latest' :
1554
+ options.experimental ? 'experimental' :
1555
+ options.canary ? 'canary' : null;
1556
+ if (!channel) {
1557
+ // Original behaviour: just check and notify
1555
1558
  console.log(chalk.blue('Checking for updates...'));
1556
1559
  try {
1557
1560
  const info = await checkForUpdate(projectPath);
1558
1561
  const message = formatUpdateMessage(info);
1559
1562
  await logUpdateCheck(projectPath, info);
1560
1563
  if (info.updateAvailable) {
1561
- if (info.critical) {
1562
- console.log(chalk.red.bold(message));
1563
- }
1564
- else {
1565
- console.log(chalk.yellow(message));
1566
- }
1564
+ console.log(info.critical ? chalk.red.bold(message) : chalk.yellow(message));
1567
1565
  }
1568
1566
  else {
1569
1567
  console.log(chalk.green(message));
1570
1568
  }
1571
1569
  }
1572
- catch (error) {
1570
+ catch {
1573
1571
  console.log(chalk.yellow('Could not check for updates (offline or rate-limited)'));
1574
- console.log(chalk.gray(` Current version: v${require('../../package.json').version}`));
1575
1572
  }
1573
+ return;
1574
+ }
1575
+ console.log(chalk.blue(`Fetching ${channel} releases...`));
1576
+ let releases;
1577
+ try {
1578
+ const all = await fetchReleases();
1579
+ releases = filterByChannel(all, channel).slice(0, 3);
1580
+ }
1581
+ catch {
1582
+ console.log(chalk.red('Could not fetch releases (offline or rate-limited)'));
1583
+ return;
1584
+ }
1585
+ if (releases.length === 0) {
1586
+ console.log(chalk.yellow(`No ${channel} releases found.`));
1587
+ return;
1588
+ }
1589
+ console.log(chalk.bold(`\n Available ${channel} versions:\n`));
1590
+ releases.forEach((r, i) => {
1591
+ console.log(chalk.cyan(` [${i + 1}] v${r.tag}`));
1592
+ });
1593
+ console.log(chalk.gray('\n [0] Cancel\n'));
1594
+ const answer = await promptInput('Pick a version (0 to cancel):');
1595
+ const picked = parseInt(answer.trim(), 10);
1596
+ if (!picked || picked < 1 || picked > releases.length) {
1597
+ console.log(chalk.gray('Cancelled.'));
1598
+ return;
1599
+ }
1600
+ const chosen = releases[picked - 1];
1601
+ if (options.backup) {
1602
+ const backupPath = path.join(home, `.envcp.bak.${Date.now()}`);
1603
+ const vaultDir = path.join(home, '.envcp');
1604
+ const confirmed = await promptConfirm(`Backup ~/.envcp → ${backupPath}?`);
1605
+ if (confirmed) {
1606
+ try {
1607
+ await fs.cp(vaultDir, backupPath, { recursive: true });
1608
+ console.log(chalk.green(` ✓ Backed up to ${backupPath}`));
1609
+ }
1610
+ catch (e) {
1611
+ console.log(chalk.red(` Backup failed: ${e.message}`));
1612
+ return;
1613
+ }
1614
+ }
1615
+ }
1616
+ console.log(chalk.blue(`\nInstalling @fentz26/envcp@${chosen.tag}...`));
1617
+ const result = spawnSync('npm', ['install', '-g', `@fentz26/envcp@${chosen.tag}`], { stdio: 'inherit' });
1618
+ if (result.status === 0) {
1619
+ console.log(chalk.green(`\n ✓ Installed v${chosen.tag}`));
1620
+ }
1621
+ else {
1622
+ console.log(chalk.red('\n Installation failed.'));
1576
1623
  }
1577
1624
  });
1578
1625
  program