@fentz26/envcp 1.0.91 → 1.1.0
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/README.md +104 -20
- package/dist/adapters/base.d.ts +10 -2
- package/dist/adapters/base.d.ts.map +1 -1
- package/dist/adapters/base.js +160 -11
- package/dist/adapters/base.js.map +1 -1
- package/dist/adapters/gemini.d.ts +3 -3
- package/dist/adapters/gemini.d.ts.map +1 -1
- package/dist/adapters/gemini.js +10 -4
- package/dist/adapters/gemini.js.map +1 -1
- package/dist/adapters/openai.d.ts +3 -3
- package/dist/adapters/openai.d.ts.map +1 -1
- package/dist/adapters/openai.js +10 -4
- package/dist/adapters/openai.js.map +1 -1
- package/dist/adapters/rest.d.ts +3 -3
- package/dist/adapters/rest.d.ts.map +1 -1
- package/dist/adapters/rest.js +10 -4
- package/dist/adapters/rest.js.map +1 -1
- package/dist/cli/index.js +238 -53
- package/dist/cli/index.js.map +1 -1
- package/dist/config/config-guard.d.ts +37 -0
- package/dist/config/config-guard.d.ts.map +1 -0
- package/dist/config/config-guard.js +212 -0
- package/dist/config/config-guard.js.map +1 -0
- package/dist/config/manager.d.ts.map +1 -1
- package/dist/config/manager.js +17 -11
- package/dist/config/manager.js.map +1 -1
- package/dist/mcp/server.d.ts +1 -1
- package/dist/mcp/server.d.ts.map +1 -1
- package/dist/mcp/server.js +2 -2
- package/dist/mcp/server.js.map +1 -1
- package/dist/server/unified.d.ts.map +1 -1
- package/dist/server/unified.js +18 -8
- package/dist/server/unified.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 -31
- package/dist/storage/index.js.map +1 -1
- package/dist/types.d.ts +150 -8
- package/dist/types.d.ts.map +1 -1
- package/dist/types.js +26 -2
- package/dist/types.js.map +1 -1
- package/dist/utils/crypto.d.ts +18 -0
- package/dist/utils/crypto.d.ts.map +1 -1
- package/dist/utils/crypto.js +31 -0
- package/dist/utils/crypto.js.map +1 -1
- package/dist/utils/fs.d.ts +4 -0
- package/dist/utils/fs.d.ts.map +1 -0
- package/dist/utils/fs.js +40 -0
- package/dist/utils/fs.js.map +1 -0
- package/dist/utils/http.d.ts +1 -1
- package/dist/utils/http.d.ts.map +1 -1
- package/dist/utils/http.js +8 -2
- package/dist/utils/http.js.map +1 -1
- package/dist/utils/session.d.ts.map +1 -1
- package/dist/utils/session.js +24 -25
- package/dist/utils/session.js.map +1 -1
- package/dist/utils/update-checker.d.ts +45 -0
- package/dist/utils/update-checker.d.ts.map +1 -0
- package/dist/utils/update-checker.js +200 -0
- package/dist/utils/update-checker.js.map +1 -0
- package/dist/vault/index.d.ts +13 -0
- package/dist/vault/index.d.ts.map +1 -0
- package/dist/vault/index.js +90 -0
- package/dist/vault/index.js.map +1 -0
- package/package.json +1 -6
package/dist/cli/index.js
CHANGED
|
@@ -3,18 +3,26 @@ import inquirer from 'inquirer';
|
|
|
3
3
|
import chalk from 'chalk';
|
|
4
4
|
import * as path from 'path';
|
|
5
5
|
import * as os from 'os';
|
|
6
|
-
import fs from 'fs
|
|
6
|
+
import * as fs from 'fs/promises';
|
|
7
|
+
import { ensureDir, pathExists } from '../utils/fs.js';
|
|
7
8
|
import { loadConfig, initConfig, saveConfig, parseEnvFile, registerMcpConfig, isBlacklisted, canAccess } from '../config/manager.js';
|
|
9
|
+
import { ConfigGuard } from '../config/config-guard.js';
|
|
8
10
|
import { StorageManager } from '../storage/index.js';
|
|
9
11
|
import { SessionManager } from '../utils/session.js';
|
|
10
12
|
import { maskValue, validatePassword, encrypt, decrypt, generateRecoveryKey, createRecoveryData, recoverPassword } from '../utils/crypto.js';
|
|
11
13
|
import { KeychainManager } from '../utils/keychain.js';
|
|
12
|
-
|
|
14
|
+
import { checkForUpdate, formatUpdateMessage, logUpdateCheck } from '../utils/update-checker.js';
|
|
15
|
+
import { getGlobalVaultPath, getProjectVaultPath, resolveVaultPath, setActiveVault, listVaults, initNamedVault, } from '../vault/index.js';
|
|
16
|
+
async function withSession(fn, vaultOverride) {
|
|
13
17
|
const projectPath = process.cwd();
|
|
14
18
|
const config = await loadConfig(projectPath);
|
|
15
|
-
|
|
19
|
+
const vaultPath = vaultOverride
|
|
20
|
+
? vaultOverride === 'global'
|
|
21
|
+
? getGlobalVaultPath(config)
|
|
22
|
+
: getProjectVaultPath(projectPath, config)
|
|
23
|
+
: await resolveVaultPath(projectPath, config);
|
|
16
24
|
if (config.encryption?.enabled === false) {
|
|
17
|
-
const storage = new StorageManager(
|
|
25
|
+
const storage = new StorageManager(vaultPath, false);
|
|
18
26
|
await fn(storage, '', config, projectPath);
|
|
19
27
|
return;
|
|
20
28
|
}
|
|
@@ -23,7 +31,6 @@ async function withSession(fn) {
|
|
|
23
31
|
let session = await sessionManager.load();
|
|
24
32
|
let password = '';
|
|
25
33
|
if (!session) {
|
|
26
|
-
// Try OS keychain first if enabled
|
|
27
34
|
if (config.keychain?.enabled) {
|
|
28
35
|
const keychain = new KeychainManager(config.keychain.service || 'envcp');
|
|
29
36
|
const stored = await keychain.retrievePassword(projectPath);
|
|
@@ -37,19 +44,19 @@ async function withSession(fn) {
|
|
|
37
44
|
{ type: 'password', name: 'password', message: 'Enter password:', mask: '*' }
|
|
38
45
|
]);
|
|
39
46
|
password = answer.password;
|
|
40
|
-
const
|
|
41
|
-
if (!
|
|
42
|
-
console.log(chalk.red(
|
|
47
|
+
const { valid: passwordValid, warning: passwordWarning } = validatePassword(password, config.password || {});
|
|
48
|
+
if (!passwordValid) {
|
|
49
|
+
console.log(chalk.red("Invalid password"));
|
|
43
50
|
return;
|
|
44
51
|
}
|
|
45
|
-
if (
|
|
46
|
-
console.log(chalk.yellow(
|
|
52
|
+
if (passwordValid && passwordWarning) {
|
|
53
|
+
console.log(chalk.yellow('⚠ Weak password detected'));
|
|
47
54
|
}
|
|
48
55
|
}
|
|
49
56
|
session = await sessionManager.create(password);
|
|
50
57
|
}
|
|
51
58
|
password = sessionManager.getPassword() || password;
|
|
52
|
-
const storage = new StorageManager(
|
|
59
|
+
const storage = new StorageManager(vaultPath, config.storage.encrypted);
|
|
53
60
|
if (password)
|
|
54
61
|
storage.setPassword(password);
|
|
55
62
|
await fn(storage, password, config, projectPath);
|
|
@@ -130,7 +137,7 @@ program
|
|
|
130
137
|
// Auto-import .env
|
|
131
138
|
if (!options.skipEnv) {
|
|
132
139
|
const envPath = path.join(projectPath, '.env');
|
|
133
|
-
if (await
|
|
140
|
+
if (await pathExists(envPath)) {
|
|
134
141
|
const envContent = await fs.readFile(envPath, 'utf8');
|
|
135
142
|
const vars = parseEnvFile(envContent);
|
|
136
143
|
const count = Object.keys(vars).length;
|
|
@@ -145,6 +152,7 @@ program
|
|
|
145
152
|
encrypted: config.storage.encrypted,
|
|
146
153
|
created: now, updated: now,
|
|
147
154
|
sync_to_env: true,
|
|
155
|
+
protected: false,
|
|
148
156
|
});
|
|
149
157
|
}
|
|
150
158
|
// Create session for encrypted mode
|
|
@@ -217,13 +225,14 @@ program
|
|
|
217
225
|
]);
|
|
218
226
|
password = answer.password;
|
|
219
227
|
}
|
|
220
|
-
const
|
|
221
|
-
if (!
|
|
222
|
-
|
|
228
|
+
const { valid: passwordValid, warning: passwordWarning } = validatePassword(password, config.password || {});
|
|
229
|
+
if (!passwordValid) {
|
|
230
|
+
// nosem: no tainted data flows to log
|
|
231
|
+
console.log(chalk.red("Invalid password"));
|
|
223
232
|
return;
|
|
224
233
|
}
|
|
225
|
-
if (
|
|
226
|
-
console.log(chalk.yellow(
|
|
234
|
+
if (passwordValid && passwordWarning) {
|
|
235
|
+
console.log(chalk.yellow('⚠ Weak password detected'));
|
|
227
236
|
}
|
|
228
237
|
const sessionManager = new SessionManager(path.join(projectPath, config.session?.path || '.envcp/.session'), config.session?.timeout_minutes || 30, config.session?.max_extensions || 5);
|
|
229
238
|
await sessionManager.init();
|
|
@@ -241,10 +250,10 @@ program
|
|
|
241
250
|
// Generate recovery key for new stores in recoverable mode
|
|
242
251
|
if (config.security?.mode === 'recoverable') {
|
|
243
252
|
const recoveryPath = path.join(projectPath, config.security.recovery_file || '.envcp/.recovery');
|
|
244
|
-
if (!await
|
|
253
|
+
if (!await pathExists(recoveryPath)) {
|
|
245
254
|
const recoveryKey = generateRecoveryKey();
|
|
246
255
|
const recoveryData = await createRecoveryData(password, recoveryKey);
|
|
247
|
-
await
|
|
256
|
+
await ensureDir(path.dirname(recoveryPath));
|
|
248
257
|
await fs.writeFile(recoveryPath, recoveryData, 'utf8');
|
|
249
258
|
console.log('');
|
|
250
259
|
console.log(chalk.yellow.bold('RECOVERY KEY (save this somewhere safe!):'));
|
|
@@ -320,8 +329,28 @@ program
|
|
|
320
329
|
const maxExt = config.session?.max_extensions || 5;
|
|
321
330
|
console.log(chalk.green('Session active'));
|
|
322
331
|
console.log(chalk.gray(` Session ID: ${session.id}`));
|
|
323
|
-
console.log(chalk.gray(`
|
|
324
|
-
console.log(chalk.gray(`
|
|
332
|
+
console.log(chalk.gray(` Remaining: ${remaining} minutes`));
|
|
333
|
+
console.log(chalk.gray(` Extensions remaining: ${maxExt - session.extensions}/${maxExt}`));
|
|
334
|
+
});
|
|
335
|
+
program
|
|
336
|
+
.command('config')
|
|
337
|
+
.description('Config management commands')
|
|
338
|
+
.command('reload')
|
|
339
|
+
.description('Reload config from envcp.yaml (requires password)')
|
|
340
|
+
.action(async () => {
|
|
341
|
+
const projectPath = process.cwd();
|
|
342
|
+
const configGuard = new ConfigGuard(projectPath);
|
|
343
|
+
const { password } = await inquirer.prompt([
|
|
344
|
+
{ type: 'password', name: 'password', message: 'Enter password to reload config:', mask: '*' }
|
|
345
|
+
]);
|
|
346
|
+
const result = await configGuard.reload(password);
|
|
347
|
+
if (result.success) {
|
|
348
|
+
console.log(chalk.green('Config reloaded successfully'));
|
|
349
|
+
console.log(chalk.gray(' New config hash: ' + configGuard.getHash()?.substring(0, 16) + '...'));
|
|
350
|
+
}
|
|
351
|
+
else {
|
|
352
|
+
console.log(chalk.red(result.error || 'Failed to reload config'));
|
|
353
|
+
}
|
|
325
354
|
});
|
|
326
355
|
program
|
|
327
356
|
.command('extend')
|
|
@@ -361,7 +390,7 @@ program
|
|
|
361
390
|
return;
|
|
362
391
|
}
|
|
363
392
|
const recoveryPath = path.join(projectPath, config.security?.recovery_file || '.envcp/.recovery');
|
|
364
|
-
if (!await
|
|
393
|
+
if (!await pathExists(recoveryPath)) {
|
|
365
394
|
console.log(chalk.red('No recovery file found. Recovery is not available.'));
|
|
366
395
|
return;
|
|
367
396
|
}
|
|
@@ -432,7 +461,7 @@ program
|
|
|
432
461
|
// Check recovery file
|
|
433
462
|
if (config.security?.mode === 'recoverable') {
|
|
434
463
|
const recoveryPath = path.join(projectPath, config.security.recovery_file || '.envcp/.recovery');
|
|
435
|
-
const hasRecovery = await
|
|
464
|
+
const hasRecovery = await pathExists(recoveryPath);
|
|
436
465
|
console.log(chalk.gray(` Recovery: ${hasRecovery ? 'available' : 'not found'}`));
|
|
437
466
|
}
|
|
438
467
|
else {
|
|
@@ -482,6 +511,7 @@ program
|
|
|
482
511
|
created: now,
|
|
483
512
|
updated: now,
|
|
484
513
|
sync_to_env: true,
|
|
514
|
+
protected: false,
|
|
485
515
|
};
|
|
486
516
|
await storage.set(name, variable);
|
|
487
517
|
console.log(chalk.green(`Variable '${name}' added successfully`));
|
|
@@ -588,7 +618,7 @@ program
|
|
|
588
618
|
if (options.dryRun) {
|
|
589
619
|
const envPath = path.join(projectPath, config.sync.target);
|
|
590
620
|
const existing = {};
|
|
591
|
-
if (await
|
|
621
|
+
if (await pathExists(envPath)) {
|
|
592
622
|
const content = await fs.readFile(envPath, 'utf8');
|
|
593
623
|
Object.assign(existing, parseEnvFile(content));
|
|
594
624
|
}
|
|
@@ -652,7 +682,8 @@ program
|
|
|
652
682
|
.option('-k, --api-key <key>', 'API key for HTTP authentication')
|
|
653
683
|
.action(async (options) => {
|
|
654
684
|
const projectPath = process.cwd();
|
|
655
|
-
const
|
|
685
|
+
const configGuard = new ConfigGuard(projectPath);
|
|
686
|
+
const config = await configGuard.loadAndLock();
|
|
656
687
|
const mode = options.mode;
|
|
657
688
|
const port = parseInt(options.port, 10);
|
|
658
689
|
const host = options.host;
|
|
@@ -682,13 +713,14 @@ program
|
|
|
682
713
|
{ type: 'password', name: 'password', message: 'Enter password:', mask: '*' }
|
|
683
714
|
]);
|
|
684
715
|
password = answer.password;
|
|
685
|
-
const
|
|
686
|
-
if (!
|
|
687
|
-
|
|
716
|
+
const { valid: passwordValid, warning: passwordWarning } = validatePassword(password, config.password || {});
|
|
717
|
+
if (!passwordValid) {
|
|
718
|
+
// nosem: no tainted data flows to log
|
|
719
|
+
console.log(chalk.red("Invalid password"));
|
|
688
720
|
return;
|
|
689
721
|
}
|
|
690
|
-
if (
|
|
691
|
-
console.log(chalk.yellow(
|
|
722
|
+
if (passwordValid && passwordWarning) {
|
|
723
|
+
console.log(chalk.yellow('⚠ Weak password detected'));
|
|
692
724
|
}
|
|
693
725
|
session = await sessionManager.create(password);
|
|
694
726
|
}
|
|
@@ -710,6 +742,7 @@ program
|
|
|
710
742
|
api_key: apiKey,
|
|
711
743
|
cors: true,
|
|
712
744
|
auto_detect: mode === 'auto',
|
|
745
|
+
rate_limit: config.server?.rate_limit,
|
|
713
746
|
};
|
|
714
747
|
const server = new UnifiedServer(config, serverConfig, projectPath, password);
|
|
715
748
|
console.log(chalk.blue('Starting EnvCP server...'));
|
|
@@ -820,7 +853,7 @@ program
|
|
|
820
853
|
.option('--dry-run', 'Preview what would be imported without writing')
|
|
821
854
|
.action(async (file, options) => {
|
|
822
855
|
await withSession(async (storage) => {
|
|
823
|
-
if (!await
|
|
856
|
+
if (!await pathExists(file)) {
|
|
824
857
|
console.log(chalk.red(`File not found: ${file}`));
|
|
825
858
|
return;
|
|
826
859
|
}
|
|
@@ -930,7 +963,7 @@ program
|
|
|
930
963
|
variables,
|
|
931
964
|
}, null, 2);
|
|
932
965
|
const encrypted = await encrypt(backupData, password);
|
|
933
|
-
await
|
|
966
|
+
await ensureDir(path.dirname(outputPath));
|
|
934
967
|
await fs.writeFile(outputPath, encrypted, 'utf8');
|
|
935
968
|
console.log(chalk.green(`Backup created: ${outputPath}`));
|
|
936
969
|
console.log(chalk.gray(` Variables: ${count}`));
|
|
@@ -943,7 +976,7 @@ program
|
|
|
943
976
|
.option('--merge', 'Merge with existing variables (default: replace)')
|
|
944
977
|
.action(async (file, options) => {
|
|
945
978
|
await withSession(async (storage, password) => {
|
|
946
|
-
if (!await
|
|
979
|
+
if (!await pathExists(file)) {
|
|
947
980
|
console.log(chalk.red(`Backup file not found: ${file}`));
|
|
948
981
|
return;
|
|
949
982
|
}
|
|
@@ -1007,7 +1040,7 @@ program
|
|
|
1007
1040
|
checks.push({ name: 'Security mode', status: 'pass', detail: config.security?.mode || 'recoverable' });
|
|
1008
1041
|
// 4. Store file
|
|
1009
1042
|
const storePath = path.join(projectPath, config.storage.path);
|
|
1010
|
-
if (await
|
|
1043
|
+
if (await pathExists(storePath)) {
|
|
1011
1044
|
const stat = await fs.stat(storePath);
|
|
1012
1045
|
checks.push({ name: 'Store file', status: 'pass', detail: `Exists (${stat.size} bytes)` });
|
|
1013
1046
|
}
|
|
@@ -1033,7 +1066,7 @@ program
|
|
|
1033
1066
|
// 6. Recovery file
|
|
1034
1067
|
if (config.security?.mode === 'recoverable') {
|
|
1035
1068
|
const recoveryPath = path.join(projectPath, config.security.recovery_file || '.envcp/.recovery');
|
|
1036
|
-
if (await
|
|
1069
|
+
if (await pathExists(recoveryPath)) {
|
|
1037
1070
|
checks.push({ name: 'Recovery file', status: 'pass', detail: 'Present' });
|
|
1038
1071
|
}
|
|
1039
1072
|
else {
|
|
@@ -1045,7 +1078,7 @@ program
|
|
|
1045
1078
|
}
|
|
1046
1079
|
// 7. .envcp directory
|
|
1047
1080
|
const envcpDir = path.join(projectPath, '.envcp');
|
|
1048
|
-
if (await
|
|
1081
|
+
if (await pathExists(envcpDir)) {
|
|
1049
1082
|
checks.push({ name: '.envcp directory', status: 'pass', detail: 'Exists' });
|
|
1050
1083
|
}
|
|
1051
1084
|
else {
|
|
@@ -1053,7 +1086,7 @@ program
|
|
|
1053
1086
|
}
|
|
1054
1087
|
// 8. .gitignore check
|
|
1055
1088
|
const gitignorePath = path.join(projectPath, '.gitignore');
|
|
1056
|
-
if (await
|
|
1089
|
+
if (await pathExists(gitignorePath)) {
|
|
1057
1090
|
const gitignore = await fs.readFile(gitignorePath, 'utf8');
|
|
1058
1091
|
if (gitignore.includes('.envcp/')) {
|
|
1059
1092
|
checks.push({ name: '.gitignore', status: 'pass', detail: '.envcp/ is ignored' });
|
|
@@ -1097,26 +1130,178 @@ program
|
|
|
1097
1130
|
console.log(chalk.green('All checks passed.'));
|
|
1098
1131
|
}
|
|
1099
1132
|
});
|
|
1133
|
+
program
|
|
1134
|
+
.command('update')
|
|
1135
|
+
.description('Check for EnvCP updates')
|
|
1136
|
+
.option('--check', 'Check for available updates')
|
|
1137
|
+
.action(async (options) => {
|
|
1138
|
+
const projectPath = process.cwd();
|
|
1139
|
+
if (options.check || Object.keys(options).length === 0) {
|
|
1140
|
+
console.log(chalk.blue('Checking for updates...'));
|
|
1141
|
+
try {
|
|
1142
|
+
const info = await checkForUpdate(projectPath);
|
|
1143
|
+
const message = formatUpdateMessage(info);
|
|
1144
|
+
await logUpdateCheck(projectPath, info);
|
|
1145
|
+
if (info.updateAvailable) {
|
|
1146
|
+
if (info.critical) {
|
|
1147
|
+
console.log(chalk.red.bold(message));
|
|
1148
|
+
}
|
|
1149
|
+
else {
|
|
1150
|
+
console.log(chalk.yellow(message));
|
|
1151
|
+
}
|
|
1152
|
+
}
|
|
1153
|
+
else {
|
|
1154
|
+
console.log(chalk.green(message));
|
|
1155
|
+
}
|
|
1156
|
+
}
|
|
1157
|
+
catch (error) {
|
|
1158
|
+
console.log(chalk.yellow('Could not check for updates (offline or rate-limited)'));
|
|
1159
|
+
console.log(chalk.gray(` Current version: v${require('../../package.json').version}`));
|
|
1160
|
+
}
|
|
1161
|
+
}
|
|
1162
|
+
});
|
|
1100
1163
|
program
|
|
1101
1164
|
.command('vault')
|
|
1102
|
-
.description('Manage
|
|
1103
|
-
.
|
|
1104
|
-
.
|
|
1105
|
-
.
|
|
1106
|
-
.
|
|
1165
|
+
.description('Manage vaults (global, project, or named)')
|
|
1166
|
+
.option('--global', 'Operate on the global vault')
|
|
1167
|
+
.option('--project', 'Operate on the project vault')
|
|
1168
|
+
.option('--name <name>', 'Operate on a named vault')
|
|
1169
|
+
.addCommand(new Command('init')
|
|
1170
|
+
.description('Initialize a vault')
|
|
1171
|
+
.action(async (options, cmd) => {
|
|
1172
|
+
const parentOpts = cmd.parent.opts();
|
|
1107
1173
|
const projectPath = process.cwd();
|
|
1108
|
-
|
|
1109
|
-
|
|
1110
|
-
|
|
1111
|
-
|
|
1112
|
-
|
|
1113
|
-
|
|
1174
|
+
const config = await loadConfig(projectPath);
|
|
1175
|
+
let vaultPath;
|
|
1176
|
+
let vaultName;
|
|
1177
|
+
if (parentOpts.global) {
|
|
1178
|
+
vaultPath = getGlobalVaultPath(config);
|
|
1179
|
+
vaultName = 'global';
|
|
1114
1180
|
}
|
|
1115
|
-
|
|
1116
|
-
|
|
1117
|
-
|
|
1181
|
+
else if (parentOpts.name) {
|
|
1182
|
+
vaultPath = await initNamedVault(projectPath, parentOpts.name);
|
|
1183
|
+
vaultName = parentOpts.name;
|
|
1184
|
+
}
|
|
1185
|
+
else {
|
|
1186
|
+
vaultPath = getProjectVaultPath(projectPath, config);
|
|
1187
|
+
vaultName = 'project';
|
|
1118
1188
|
}
|
|
1189
|
+
const vaultDir = path.dirname(vaultPath);
|
|
1190
|
+
await ensureDir(vaultDir);
|
|
1191
|
+
console.log(chalk.green(`Vault "${vaultName}" initialized at ${vaultPath}`));
|
|
1192
|
+
}))
|
|
1193
|
+
.addCommand(new Command('add')
|
|
1194
|
+
.description('Add a variable to vault')
|
|
1195
|
+
.argument('<name>', 'Variable name')
|
|
1196
|
+
.option('-v, --value <value>', 'Variable value')
|
|
1197
|
+
.option('-t, --tags <tags>', 'Tags (comma-separated)')
|
|
1198
|
+
.action(async (name, options, cmd) => {
|
|
1199
|
+
const parentOpts = cmd.parent.opts();
|
|
1200
|
+
const vaultOverride = parentOpts.global ? 'global' : parentOpts.project ? 'project' : undefined;
|
|
1201
|
+
await withSession(async (storage) => {
|
|
1202
|
+
const value = options.value || (await inquirer.prompt([{ type: 'input', name: 'value', message: 'Value:' }])).value;
|
|
1203
|
+
const tags = options.tags ? options.tags.split(',').map((t) => t.trim()) : [];
|
|
1204
|
+
const now = new Date().toISOString();
|
|
1205
|
+
await storage.set(name, {
|
|
1206
|
+
name,
|
|
1207
|
+
value,
|
|
1208
|
+
encrypted: storage.encrypted,
|
|
1209
|
+
created: now,
|
|
1210
|
+
updated: now,
|
|
1211
|
+
sync_to_env: true,
|
|
1212
|
+
tags,
|
|
1213
|
+
protected: false,
|
|
1214
|
+
});
|
|
1215
|
+
console.log(chalk.green(`Variable '${name}' added to vault`));
|
|
1216
|
+
}, vaultOverride);
|
|
1217
|
+
}))
|
|
1218
|
+
.addCommand(new Command('list')
|
|
1219
|
+
.description('List vault contents')
|
|
1220
|
+
.option('-v, --show-values', 'Show actual values')
|
|
1221
|
+
.action(async (options, cmd) => {
|
|
1222
|
+
const parentOpts = cmd.parent.opts();
|
|
1223
|
+
const vaultOverride = parentOpts.global ? 'global' : parentOpts.project ? 'project' : undefined;
|
|
1224
|
+
await withSession(async (storage) => {
|
|
1225
|
+
const variables = await storage.load();
|
|
1226
|
+
const names = Object.keys(variables);
|
|
1227
|
+
if (names.length === 0) {
|
|
1228
|
+
console.log(chalk.gray('Vault is empty'));
|
|
1229
|
+
return;
|
|
1230
|
+
}
|
|
1231
|
+
for (const name of names) {
|
|
1232
|
+
const v = variables[name];
|
|
1233
|
+
const display = options.showValues ? v.value : maskValue(v.value);
|
|
1234
|
+
const tags = v.tags?.length ? ` [${v.tags.join(', ')}]` : '';
|
|
1235
|
+
console.log(` ${name} = ${display}${tags}`);
|
|
1236
|
+
}
|
|
1237
|
+
}, vaultOverride);
|
|
1238
|
+
}))
|
|
1239
|
+
.addCommand(new Command('get')
|
|
1240
|
+
.description('Get a variable from vault')
|
|
1241
|
+
.argument('<name>', 'Variable name')
|
|
1242
|
+
.option('-v, --show-value', 'Show actual value')
|
|
1243
|
+
.action(async (name, options, cmd) => {
|
|
1244
|
+
const parentOpts = cmd.parent.opts();
|
|
1245
|
+
const vaultOverride = parentOpts.global ? 'global' : parentOpts.project ? 'project' : undefined;
|
|
1246
|
+
await withSession(async (storage) => {
|
|
1247
|
+
const v = await storage.get(name);
|
|
1248
|
+
if (!v) {
|
|
1249
|
+
console.log(chalk.red(`Variable '${name}' not found`));
|
|
1250
|
+
return;
|
|
1251
|
+
}
|
|
1252
|
+
const display = options.showValue ? v.value : maskValue(v.value);
|
|
1253
|
+
console.log(`${name} = ${display}`);
|
|
1254
|
+
}, vaultOverride);
|
|
1255
|
+
}))
|
|
1256
|
+
.addCommand(new Command('delete')
|
|
1257
|
+
.description('Delete a variable from vault')
|
|
1258
|
+
.argument('<name>', 'Variable name')
|
|
1259
|
+
.action(async (name, cmd) => {
|
|
1260
|
+
const parentOpts = cmd.parent.parent.opts();
|
|
1261
|
+
const vaultOverride = parentOpts.global ? 'global' : parentOpts.project ? 'project' : undefined;
|
|
1262
|
+
await withSession(async (storage) => {
|
|
1263
|
+
const deleted = await storage.delete(name);
|
|
1264
|
+
if (deleted) {
|
|
1265
|
+
console.log(chalk.green(`Variable '${name}' deleted`));
|
|
1266
|
+
}
|
|
1267
|
+
else {
|
|
1268
|
+
console.log(chalk.red(`Variable '${name}' not found`));
|
|
1269
|
+
}
|
|
1270
|
+
}, vaultOverride);
|
|
1119
1271
|
}));
|
|
1272
|
+
program
|
|
1273
|
+
.command('vault-switch')
|
|
1274
|
+
.description('Switch active vault context')
|
|
1275
|
+
.argument('<name>', 'Vault name (global, project, or named vault)')
|
|
1276
|
+
.action(async (name) => {
|
|
1277
|
+
const projectPath = process.cwd();
|
|
1278
|
+
const config = await loadConfig(projectPath);
|
|
1279
|
+
if (name !== 'global' && name !== 'project') {
|
|
1280
|
+
const vaultDir = path.join(projectPath, '.envcp/vaults', name);
|
|
1281
|
+
if (!await pathExists(vaultDir)) {
|
|
1282
|
+
console.log(chalk.red(`Named vault "${name}" does not exist. Create it with: envcp vault --name ${name} init`));
|
|
1283
|
+
return;
|
|
1284
|
+
}
|
|
1285
|
+
}
|
|
1286
|
+
await setActiveVault(projectPath, name);
|
|
1287
|
+
console.log(chalk.green(`Switched to vault: ${name}`));
|
|
1288
|
+
});
|
|
1289
|
+
program
|
|
1290
|
+
.command('vault-list')
|
|
1291
|
+
.description('List all available vaults')
|
|
1292
|
+
.action(async () => {
|
|
1293
|
+
const projectPath = process.cwd();
|
|
1294
|
+
const config = await loadConfig(projectPath);
|
|
1295
|
+
const vaults = await listVaults(projectPath, config);
|
|
1296
|
+
console.log('Available vaults:');
|
|
1297
|
+
for (const v of vaults) {
|
|
1298
|
+
const active = v.active ? chalk.green(' (active)') : '';
|
|
1299
|
+
const exists = await pathExists(v.path);
|
|
1300
|
+
const status = exists ? '' : chalk.gray(' [not initialized]');
|
|
1301
|
+
console.log(` ${v.name}${active}${status}`);
|
|
1302
|
+
console.log(chalk.gray(` ${v.path}`));
|
|
1303
|
+
}
|
|
1304
|
+
});
|
|
1120
1305
|
program
|
|
1121
1306
|
.command('keychain')
|
|
1122
1307
|
.description('Manage OS keychain integration')
|
|
@@ -1198,8 +1383,8 @@ program
|
|
|
1198
1383
|
}));
|
|
1199
1384
|
// Show welcome screen on first ever run
|
|
1200
1385
|
const firstRunMarker = path.join(os.homedir(), '.envcp', '.welcomed');
|
|
1201
|
-
if (!await
|
|
1202
|
-
await
|
|
1386
|
+
if (!await pathExists(firstRunMarker)) {
|
|
1387
|
+
await ensureDir(path.dirname(firstRunMarker));
|
|
1203
1388
|
await fs.writeFile(firstRunMarker, new Date().toISOString());
|
|
1204
1389
|
console.log(`
|
|
1205
1390
|
███████╗███╗ ██╗██╗ ██╗ ██████╗██████╗
|