@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.
- package/VERSION +1 -1
- package/dist/adapters/base.d.ts +25 -2
- package/dist/adapters/base.d.ts.map +1 -1
- package/dist/adapters/base.js +68 -6
- package/dist/adapters/base.js.map +1 -1
- package/dist/adapters/gemini.d.ts +1 -4
- package/dist/adapters/gemini.d.ts.map +1 -1
- package/dist/adapters/gemini.js +21 -112
- package/dist/adapters/gemini.js.map +1 -1
- package/dist/adapters/openai.d.ts +1 -4
- package/dist/adapters/openai.d.ts.map +1 -1
- package/dist/adapters/openai.js +25 -126
- package/dist/adapters/openai.js.map +1 -1
- package/dist/adapters/rest.d.ts.map +1 -1
- package/dist/adapters/rest.js +7 -3
- package/dist/adapters/rest.js.map +1 -1
- package/dist/cli/index.js +109 -62
- package/dist/cli/index.js.map +1 -1
- package/dist/config/config-guard.d.ts.map +1 -1
- package/dist/config/config-guard.js +9 -4
- package/dist/config/config-guard.js.map +1 -1
- package/dist/config/config-hmac.js +2 -2
- package/dist/config/config-hmac.js.map +1 -1
- package/dist/config/manager.d.ts.map +1 -1
- package/dist/config/manager.js +22 -20
- package/dist/config/manager.js.map +1 -1
- package/dist/mcp/server.d.ts.map +1 -1
- package/dist/mcp/server.js +2 -1
- package/dist/mcp/server.js.map +1 -1
- package/dist/server/unified.js +1 -1
- package/dist/server/unified.js.map +1 -1
- package/dist/service/config.d.ts.map +1 -1
- package/dist/service/config.js +5 -4
- package/dist/service/config.js.map +1 -1
- package/dist/service/generators.d.ts.map +1 -1
- package/dist/service/generators.js +6 -8
- package/dist/service/generators.js.map +1 -1
- package/dist/service/index.js +4 -3
- package/dist/service/index.js.map +1 -1
- package/dist/service/platform.js +2 -2
- package/dist/service/platform.js.map +1 -1
- package/dist/storage/index.d.ts +1 -1
- package/dist/storage/index.d.ts.map +1 -1
- package/dist/storage/index.js +35 -27
- package/dist/storage/index.js.map +1 -1
- package/dist/types.js +1 -1
- package/dist/types.js.map +1 -1
- package/dist/utils/crypto.js +2 -2
- package/dist/utils/crypto.js.map +1 -1
- package/dist/utils/fs.js +6 -6
- package/dist/utils/fs.js.map +1 -1
- package/dist/utils/hsm.d.ts +8 -8
- package/dist/utils/hsm.d.ts.map +1 -1
- package/dist/utils/hsm.js +7 -6
- package/dist/utils/hsm.js.map +1 -1
- package/dist/utils/http.d.ts +1 -1
- package/dist/utils/http.d.ts.map +1 -1
- package/dist/utils/http.js +1 -1
- package/dist/utils/http.js.map +1 -1
- package/dist/utils/keychain.js +4 -4
- package/dist/utils/keychain.js.map +1 -1
- package/dist/utils/lock.d.ts.map +1 -1
- package/dist/utils/lock.js +2 -1
- package/dist/utils/lock.js.map +1 -1
- package/dist/utils/lockout.d.ts +2 -2
- package/dist/utils/lockout.d.ts.map +1 -1
- package/dist/utils/lockout.js +3 -3
- package/dist/utils/lockout.js.map +1 -1
- package/dist/utils/prompt.js +3 -3
- package/dist/utils/prompt.js.map +1 -1
- package/dist/utils/secure-memory.d.ts.map +1 -1
- package/dist/utils/secure-memory.js +6 -3
- package/dist/utils/secure-memory.js.map +1 -1
- package/dist/utils/session.js +3 -3
- package/dist/utils/session.js.map +1 -1
- package/dist/utils/update-checker.d.ts +3 -0
- package/dist/utils/update-checker.d.ts.map +1 -1
- package/dist/utils/update-checker.js +51 -5
- package/dist/utils/update-checker.js.map +1 -1
- package/dist/vault/index.d.ts.map +1 -1
- package/dist/vault/index.js +5 -3
- package/dist/vault/index.js.map +1 -1
- package/dist/version.js +3 -3
- package/dist/version.js.map +1 -1
- package/package.json +1 -1
- 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
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
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
|
-
|
|
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.
|
|
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.
|
|
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.
|
|
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 =
|
|
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
|
|
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.
|
|
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().
|
|
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('--
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
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
|