@fentz26/envcp 1.0.7 → 1.0.9
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/LICENSE +28 -17
- package/README.md +58 -12
- package/dist/adapters/base.d.ts.map +1 -1
- package/dist/adapters/base.js +2 -0
- package/dist/adapters/base.js.map +1 -1
- package/dist/cli/index.js +241 -24
- package/dist/cli/index.js.map +1 -1
- package/dist/config/manager.d.ts +15 -0
- package/dist/config/manager.d.ts.map +1 -1
- package/dist/config/manager.js +25 -9
- package/dist/config/manager.js.map +1 -1
- package/dist/storage/index.d.ts +7 -0
- package/dist/storage/index.d.ts.map +1 -1
- package/dist/storage/index.js +68 -22
- package/dist/storage/index.js.map +1 -1
- package/dist/types.d.ts +18 -10
- package/dist/types.d.ts.map +1 -1
- package/dist/types.js +7 -5
- package/dist/types.js.map +1 -1
- package/dist/utils/crypto.d.ts +21 -4
- package/dist/utils/crypto.d.ts.map +1 -1
- package/dist/utils/crypto.js +91 -15
- package/dist/utils/crypto.js.map +1 -1
- package/dist/utils/keychain.d.ts +27 -0
- package/dist/utils/keychain.d.ts.map +1 -0
- package/dist/utils/keychain.js +234 -0
- package/dist/utils/keychain.js.map +1 -0
- package/dist/utils/session.d.ts.map +1 -1
- package/dist/utils/session.js +37 -21
- package/dist/utils/session.js.map +1 -1
- package/package.json +16 -4
- package/scripts/convert-wiki.sh +91 -0
- package/scripts/install.sh +54 -0
- package/scripts/postinstall.js +13 -6
package/dist/cli/index.js
CHANGED
|
@@ -2,11 +2,13 @@ import { Command } from 'commander';
|
|
|
2
2
|
import inquirer from 'inquirer';
|
|
3
3
|
import chalk from 'chalk';
|
|
4
4
|
import * as path from 'path';
|
|
5
|
+
import * as os from 'os';
|
|
5
6
|
import fs from 'fs-extra';
|
|
6
|
-
import { loadConfig, initConfig, saveConfig, parseEnvFile, registerMcpConfig } from '../config/manager.js';
|
|
7
|
+
import { loadConfig, initConfig, saveConfig, parseEnvFile, registerMcpConfig, isBlacklisted, canAccess } from '../config/manager.js';
|
|
7
8
|
import { StorageManager } from '../storage/index.js';
|
|
8
9
|
import { SessionManager } from '../utils/session.js';
|
|
9
10
|
import { maskValue, validatePassword, encrypt, decrypt, generateRecoveryKey, createRecoveryData, recoverPassword } from '../utils/crypto.js';
|
|
11
|
+
import { KeychainManager } from '../utils/keychain.js';
|
|
10
12
|
async function withSession(fn) {
|
|
11
13
|
const projectPath = process.cwd();
|
|
12
14
|
const config = await loadConfig(projectPath);
|
|
@@ -21,14 +23,28 @@ async function withSession(fn) {
|
|
|
21
23
|
let session = await sessionManager.load();
|
|
22
24
|
let password = '';
|
|
23
25
|
if (!session) {
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
26
|
+
// Try OS keychain first if enabled
|
|
27
|
+
if (config.keychain?.enabled) {
|
|
28
|
+
const keychain = new KeychainManager(config.keychain.service || 'envcp');
|
|
29
|
+
const stored = await keychain.retrievePassword(projectPath);
|
|
30
|
+
if (stored) {
|
|
31
|
+
password = stored;
|
|
32
|
+
console.log(chalk.gray('Password retrieved from OS keychain'));
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
if (!password) {
|
|
36
|
+
const answer = await inquirer.prompt([
|
|
37
|
+
{ type: 'password', name: 'password', message: 'Enter password:', mask: '*' }
|
|
38
|
+
]);
|
|
39
|
+
password = answer.password;
|
|
40
|
+
const validation = validatePassword(password, config.password || {});
|
|
41
|
+
if (!validation.valid) {
|
|
42
|
+
console.log(chalk.red(validation.error));
|
|
43
|
+
return;
|
|
44
|
+
}
|
|
45
|
+
if (validation.warning) {
|
|
46
|
+
console.log(chalk.yellow(`⚠ ${validation.warning}`));
|
|
47
|
+
}
|
|
32
48
|
}
|
|
33
49
|
session = await sessionManager.create(password);
|
|
34
50
|
}
|
|
@@ -145,7 +161,7 @@ program
|
|
|
145
161
|
// Generate recovery key for encrypted recoverable mode
|
|
146
162
|
if (securityChoice === 'recoverable' && pwd) {
|
|
147
163
|
const recoveryKey = generateRecoveryKey();
|
|
148
|
-
const recoveryData = createRecoveryData(pwd, recoveryKey);
|
|
164
|
+
const recoveryData = await createRecoveryData(pwd, recoveryKey);
|
|
149
165
|
const recoveryPath = path.join(projectPath, config.security.recovery_file);
|
|
150
166
|
await fs.writeFile(recoveryPath, recoveryData, 'utf8');
|
|
151
167
|
console.log('');
|
|
@@ -185,6 +201,7 @@ program
|
|
|
185
201
|
.command('unlock')
|
|
186
202
|
.description('Unlock EnvCP session with password')
|
|
187
203
|
.option('-p, --password <password>', 'Password (will prompt if not provided)')
|
|
204
|
+
.option('--save-to-keychain', 'Save password to OS keychain for auto-unlock')
|
|
188
205
|
.action(async (options) => {
|
|
189
206
|
const projectPath = process.cwd();
|
|
190
207
|
const config = await loadConfig(projectPath);
|
|
@@ -205,6 +222,9 @@ program
|
|
|
205
222
|
console.log(chalk.red(validation.error));
|
|
206
223
|
return;
|
|
207
224
|
}
|
|
225
|
+
if (validation.warning) {
|
|
226
|
+
console.log(chalk.yellow(`⚠ ${validation.warning}`));
|
|
227
|
+
}
|
|
208
228
|
const sessionManager = new SessionManager(path.join(projectPath, config.session?.path || '.envcp/.session'), config.session?.timeout_minutes || 30, config.session?.max_extensions || 5);
|
|
209
229
|
await sessionManager.init();
|
|
210
230
|
const storage = new StorageManager(path.join(projectPath, config.storage.path), config.storage.encrypted);
|
|
@@ -223,7 +243,7 @@ program
|
|
|
223
243
|
const recoveryPath = path.join(projectPath, config.security.recovery_file || '.envcp/.recovery');
|
|
224
244
|
if (!await fs.pathExists(recoveryPath)) {
|
|
225
245
|
const recoveryKey = generateRecoveryKey();
|
|
226
|
-
const recoveryData = createRecoveryData(password, recoveryKey);
|
|
246
|
+
const recoveryData = await createRecoveryData(password, recoveryKey);
|
|
227
247
|
await fs.ensureDir(path.dirname(recoveryPath));
|
|
228
248
|
await fs.writeFile(recoveryPath, recoveryData, 'utf8');
|
|
229
249
|
console.log('');
|
|
@@ -247,6 +267,26 @@ program
|
|
|
247
267
|
console.log(chalk.gray(` Expires in: ${config.session?.timeout_minutes || 30} minutes`));
|
|
248
268
|
const maxExt = config.session?.max_extensions || 5;
|
|
249
269
|
console.log(chalk.gray(` Extensions remaining: ${maxExt - session.extensions}/${maxExt}`));
|
|
270
|
+
// Save to keychain if requested
|
|
271
|
+
if (options.saveToKeychain) {
|
|
272
|
+
const keychain = new KeychainManager(config.keychain?.service || 'envcp');
|
|
273
|
+
if (await keychain.isAvailable()) {
|
|
274
|
+
const result = await keychain.storePassword(password, projectPath);
|
|
275
|
+
if (result.success) {
|
|
276
|
+
// Enable keychain in config
|
|
277
|
+
config.keychain = { ...config.keychain, enabled: true };
|
|
278
|
+
await saveConfig(config, projectPath);
|
|
279
|
+
console.log(chalk.green(`Password saved to ${keychain.backendName}`));
|
|
280
|
+
console.log(chalk.gray(' Future sessions will auto-unlock from keychain'));
|
|
281
|
+
}
|
|
282
|
+
else {
|
|
283
|
+
console.log(chalk.red(`Failed to save to keychain: ${result.error}`));
|
|
284
|
+
}
|
|
285
|
+
}
|
|
286
|
+
else {
|
|
287
|
+
console.log(chalk.red(`OS keychain not available (${keychain.backendName})`));
|
|
288
|
+
}
|
|
289
|
+
}
|
|
250
290
|
});
|
|
251
291
|
program
|
|
252
292
|
.command('lock')
|
|
@@ -331,7 +371,7 @@ program
|
|
|
331
371
|
const recoveryData = await fs.readFile(recoveryPath, 'utf8');
|
|
332
372
|
let oldPassword;
|
|
333
373
|
try {
|
|
334
|
-
oldPassword = recoverPassword(recoveryData, recoveryKey);
|
|
374
|
+
oldPassword = await recoverPassword(recoveryData, recoveryKey);
|
|
335
375
|
}
|
|
336
376
|
catch {
|
|
337
377
|
console.log(chalk.red('Invalid recovery key.'));
|
|
@@ -366,7 +406,7 @@ program
|
|
|
366
406
|
await storage.save(variables);
|
|
367
407
|
// Update recovery file with new password
|
|
368
408
|
const newRecoveryKey = generateRecoveryKey();
|
|
369
|
-
const newRecoveryData = createRecoveryData(newPassword, newRecoveryKey);
|
|
409
|
+
const newRecoveryData = await createRecoveryData(newPassword, newRecoveryKey);
|
|
370
410
|
await fs.writeFile(recoveryPath, newRecoveryData, 'utf8');
|
|
371
411
|
console.log(chalk.green('Password reset successfully!'));
|
|
372
412
|
console.log('');
|
|
@@ -452,7 +492,7 @@ program
|
|
|
452
492
|
.description('List all variables (names only, values hidden)')
|
|
453
493
|
.option('-v, --show-values', 'Show actual values')
|
|
454
494
|
.action(async (options) => {
|
|
455
|
-
await withSession(async (storage) => {
|
|
495
|
+
await withSession(async (storage, _password, config) => {
|
|
456
496
|
const variables = await storage.load();
|
|
457
497
|
const names = Object.keys(variables);
|
|
458
498
|
if (names.length === 0) {
|
|
@@ -462,7 +502,9 @@ program
|
|
|
462
502
|
console.log(chalk.blue(`\nVariables (${names.length}):\n`));
|
|
463
503
|
for (const name of names) {
|
|
464
504
|
const v = variables[name];
|
|
465
|
-
const value =
|
|
505
|
+
const value = config.access?.mask_values && !options.showValues
|
|
506
|
+
? maskValue(v.value)
|
|
507
|
+
: v.value;
|
|
466
508
|
const tags = v.tags ? chalk.gray(` [${v.tags.join(', ')}]`) : '';
|
|
467
509
|
console.log(` ${chalk.cyan(name)} = ${value}${tags}`);
|
|
468
510
|
}
|
|
@@ -472,15 +514,27 @@ program
|
|
|
472
514
|
program
|
|
473
515
|
.command('get <name>')
|
|
474
516
|
.description('Get a variable value')
|
|
475
|
-
.
|
|
476
|
-
|
|
517
|
+
.option('--show-value', 'Reveal the unmasked value')
|
|
518
|
+
.action(async (name, options) => {
|
|
519
|
+
await withSession(async (storage, _password, config) => {
|
|
520
|
+
if (isBlacklisted(name, config)) {
|
|
521
|
+
console.log(chalk.red(`Variable '${name}' is blacklisted and cannot be accessed`));
|
|
522
|
+
return;
|
|
523
|
+
}
|
|
524
|
+
if (!canAccess(name, config)) {
|
|
525
|
+
console.log(chalk.red(`Access denied to variable '${name}'`));
|
|
526
|
+
return;
|
|
527
|
+
}
|
|
477
528
|
const variable = await storage.get(name);
|
|
478
529
|
if (!variable) {
|
|
479
530
|
console.log(chalk.red(`Variable '${name}' not found`));
|
|
480
531
|
return;
|
|
481
532
|
}
|
|
533
|
+
const value = config.access?.mask_values && !options.showValue
|
|
534
|
+
? maskValue(variable.value)
|
|
535
|
+
: variable.value;
|
|
482
536
|
console.log(chalk.cyan(name));
|
|
483
|
-
console.log(` Value: ${
|
|
537
|
+
console.log(` Value: ${value}`);
|
|
484
538
|
if (variable.tags)
|
|
485
539
|
console.log(` Tags: ${variable.tags.join(', ')}`);
|
|
486
540
|
if (variable.description)
|
|
@@ -517,6 +571,16 @@ program
|
|
|
517
571
|
lines.push(config.sync.header);
|
|
518
572
|
}
|
|
519
573
|
for (const [name, variable] of Object.entries(variables)) {
|
|
574
|
+
if (isBlacklisted(name, config) || !canAccess(name, config))
|
|
575
|
+
continue;
|
|
576
|
+
if (!variable.sync_to_env)
|
|
577
|
+
continue;
|
|
578
|
+
const excluded = config.sync.exclude?.some((pattern) => {
|
|
579
|
+
const regex = new RegExp('^' + pattern.replace(/\*/g, '.*') + '$');
|
|
580
|
+
return regex.test(name);
|
|
581
|
+
});
|
|
582
|
+
if (excluded)
|
|
583
|
+
continue;
|
|
520
584
|
const needsQuoting = /[\s#"'\\]/.test(variable.value);
|
|
521
585
|
const val = needsQuoting ? `"${variable.value.replace(/\\/g, '\\\\').replace(/"/g, '\\"')}"` : variable.value;
|
|
522
586
|
lines.push(`${name}=${val}`);
|
|
@@ -532,6 +596,16 @@ program
|
|
|
532
596
|
const updated = [];
|
|
533
597
|
const removed = [];
|
|
534
598
|
for (const [name, variable] of Object.entries(variables)) {
|
|
599
|
+
if (isBlacklisted(name, config) || !canAccess(name, config))
|
|
600
|
+
continue;
|
|
601
|
+
if (!variable.sync_to_env)
|
|
602
|
+
continue;
|
|
603
|
+
const excluded = config.sync.exclude?.some((pattern) => {
|
|
604
|
+
const regex = new RegExp('^' + pattern.replace(/\*/g, '.*') + '$');
|
|
605
|
+
return regex.test(name);
|
|
606
|
+
});
|
|
607
|
+
if (excluded)
|
|
608
|
+
continue;
|
|
535
609
|
if (name in existing) {
|
|
536
610
|
if (existing[name] !== variable.value)
|
|
537
611
|
updated.push(name);
|
|
@@ -613,6 +687,9 @@ program
|
|
|
613
687
|
console.log(chalk.red(validation.error));
|
|
614
688
|
return;
|
|
615
689
|
}
|
|
690
|
+
if (validation.warning) {
|
|
691
|
+
console.log(chalk.yellow(`⚠ ${validation.warning}`));
|
|
692
|
+
}
|
|
616
693
|
session = await sessionManager.create(password);
|
|
617
694
|
}
|
|
618
695
|
password = sessionManager.getPassword() || password;
|
|
@@ -640,7 +717,7 @@ program
|
|
|
640
717
|
console.log(chalk.gray(` Host: ${host}`));
|
|
641
718
|
console.log(chalk.gray(` Port: ${port}`));
|
|
642
719
|
if (apiKey)
|
|
643
|
-
console.log(chalk.gray(` API Key: ${apiKey.
|
|
720
|
+
console.log(chalk.gray(` API Key: ${'*'.repeat(apiKey.length)}`));
|
|
644
721
|
console.log('');
|
|
645
722
|
await server.start();
|
|
646
723
|
console.log(chalk.green(`EnvCP server running at http://${host}:${port}`));
|
|
@@ -708,7 +785,7 @@ program
|
|
|
708
785
|
meta: { project: config.project, timestamp: new Date().toISOString(), count: Object.keys(variables).length, version: '1.0' },
|
|
709
786
|
variables,
|
|
710
787
|
}, null, 2);
|
|
711
|
-
const encrypted = encrypt(exportData, exportPassword);
|
|
788
|
+
const encrypted = await encrypt(exportData, exportPassword);
|
|
712
789
|
await fs.writeFile(outputPath, encrypted, 'utf8');
|
|
713
790
|
console.log(chalk.green(`Encrypted export saved to: ${outputPath}`));
|
|
714
791
|
console.log(chalk.gray(` Variables: ${Object.keys(variables).length}`));
|
|
@@ -753,7 +830,7 @@ program
|
|
|
753
830
|
const fileContent = await fs.readFile(file, 'utf8');
|
|
754
831
|
let importData;
|
|
755
832
|
try {
|
|
756
|
-
const decrypted = decrypt(fileContent, importPassword);
|
|
833
|
+
const decrypted = await decrypt(fileContent, importPassword);
|
|
757
834
|
importData = JSON.parse(decrypted);
|
|
758
835
|
}
|
|
759
836
|
catch {
|
|
@@ -852,7 +929,7 @@ program
|
|
|
852
929
|
},
|
|
853
930
|
variables,
|
|
854
931
|
}, null, 2);
|
|
855
|
-
const encrypted = encrypt(backupData, password);
|
|
932
|
+
const encrypted = await encrypt(backupData, password);
|
|
856
933
|
await fs.ensureDir(path.dirname(outputPath));
|
|
857
934
|
await fs.writeFile(outputPath, encrypted, 'utf8');
|
|
858
935
|
console.log(chalk.green(`Backup created: ${outputPath}`));
|
|
@@ -873,7 +950,7 @@ program
|
|
|
873
950
|
const encrypted = await fs.readFile(file, 'utf8');
|
|
874
951
|
let backupData;
|
|
875
952
|
try {
|
|
876
|
-
const decrypted = decrypt(encrypted, password);
|
|
953
|
+
const decrypted = await decrypt(encrypted, password);
|
|
877
954
|
backupData = JSON.parse(decrypted);
|
|
878
955
|
}
|
|
879
956
|
catch {
|
|
@@ -925,7 +1002,7 @@ program
|
|
|
925
1002
|
checks.push({ name: 'Config', status: 'pass', detail: `Loaded (project: ${config.project || 'unnamed'})` });
|
|
926
1003
|
// 2. Encryption mode
|
|
927
1004
|
const encrypted = config.encryption?.enabled !== false;
|
|
928
|
-
checks.push({ name: 'Encryption', status: 'pass', detail: encrypted ?
|
|
1005
|
+
checks.push({ name: 'Encryption', status: 'pass', detail: encrypted ? 'Enabled (AES-256-GCM)' : 'Disabled (passwordless)' });
|
|
929
1006
|
// 3. Security mode
|
|
930
1007
|
checks.push({ name: 'Security mode', status: 'pass', detail: config.security?.mode || 'recoverable' });
|
|
931
1008
|
// 4. Store file
|
|
@@ -1020,5 +1097,145 @@ program
|
|
|
1020
1097
|
console.log(chalk.green('All checks passed.'));
|
|
1021
1098
|
}
|
|
1022
1099
|
});
|
|
1100
|
+
program
|
|
1101
|
+
.command('vault')
|
|
1102
|
+
.description('Manage vault settings')
|
|
1103
|
+
.addCommand(new Command('rename')
|
|
1104
|
+
.description('Rename the current vault (updates project name in config)')
|
|
1105
|
+
.argument('<name>', 'New vault name')
|
|
1106
|
+
.action(async (name) => {
|
|
1107
|
+
const projectPath = process.cwd();
|
|
1108
|
+
try {
|
|
1109
|
+
const config = await loadConfig(projectPath);
|
|
1110
|
+
const old = config.project || path.basename(projectPath);
|
|
1111
|
+
config.project = name;
|
|
1112
|
+
await saveConfig(config, projectPath);
|
|
1113
|
+
console.log(`Vault renamed: ${old} -> ${name}`);
|
|
1114
|
+
}
|
|
1115
|
+
catch (error) {
|
|
1116
|
+
console.error(`Failed to rename vault: ${error.message}`);
|
|
1117
|
+
process.exit(1);
|
|
1118
|
+
}
|
|
1119
|
+
}));
|
|
1120
|
+
program
|
|
1121
|
+
.command('keychain')
|
|
1122
|
+
.description('Manage OS keychain integration')
|
|
1123
|
+
.addCommand(new Command('status')
|
|
1124
|
+
.description('Check keychain availability and stored credentials')
|
|
1125
|
+
.action(async () => {
|
|
1126
|
+
const projectPath = process.cwd();
|
|
1127
|
+
const config = await loadConfig(projectPath);
|
|
1128
|
+
const keychain = new KeychainManager(config.keychain?.service || 'envcp');
|
|
1129
|
+
const status = await keychain.getStatus(projectPath);
|
|
1130
|
+
console.log(chalk.bold('Keychain Status'));
|
|
1131
|
+
console.log(chalk.gray(` Backend: ${status.backend}`));
|
|
1132
|
+
console.log(chalk.gray(` Available: ${status.available ? chalk.green('yes') : chalk.red('no')}`));
|
|
1133
|
+
console.log(chalk.gray(` Stored: ${status.hasPassword ? chalk.green('yes') : chalk.yellow('no')}`));
|
|
1134
|
+
console.log(chalk.gray(` Enabled: ${config.keychain?.enabled ? chalk.green('yes') : chalk.yellow('no')}`));
|
|
1135
|
+
if (!status.available) {
|
|
1136
|
+
console.log('');
|
|
1137
|
+
if (process.platform === 'linux') {
|
|
1138
|
+
console.log(chalk.yellow('Install libsecret: sudo apt install libsecret-tools'));
|
|
1139
|
+
}
|
|
1140
|
+
else if (process.platform === 'darwin') {
|
|
1141
|
+
console.log(chalk.yellow('macOS Keychain should be available by default'));
|
|
1142
|
+
}
|
|
1143
|
+
}
|
|
1144
|
+
else if (!status.hasPassword) {
|
|
1145
|
+
console.log('');
|
|
1146
|
+
console.log(chalk.gray('Run: envcp unlock --save-to-keychain'));
|
|
1147
|
+
}
|
|
1148
|
+
}))
|
|
1149
|
+
.addCommand(new Command('save')
|
|
1150
|
+
.description('Save current password to OS keychain')
|
|
1151
|
+
.action(async () => {
|
|
1152
|
+
await withSession(async (storage, password, config, projectPath) => {
|
|
1153
|
+
if (!password) {
|
|
1154
|
+
console.log(chalk.red('No password available (encryption disabled?)'));
|
|
1155
|
+
return;
|
|
1156
|
+
}
|
|
1157
|
+
const keychain = new KeychainManager(config.keychain?.service || 'envcp');
|
|
1158
|
+
if (!await keychain.isAvailable()) {
|
|
1159
|
+
console.log(chalk.red(`OS keychain not available (${keychain.backendName})`));
|
|
1160
|
+
return;
|
|
1161
|
+
}
|
|
1162
|
+
const result = await keychain.storePassword(password, projectPath);
|
|
1163
|
+
if (result.success) {
|
|
1164
|
+
config.keychain = { ...config.keychain, enabled: true };
|
|
1165
|
+
await saveConfig(config, projectPath);
|
|
1166
|
+
console.log(chalk.green(`Password saved to ${keychain.backendName}`));
|
|
1167
|
+
console.log(chalk.gray(' Future sessions will auto-unlock from keychain'));
|
|
1168
|
+
}
|
|
1169
|
+
else {
|
|
1170
|
+
console.log(chalk.red(`Failed: ${result.error}`));
|
|
1171
|
+
}
|
|
1172
|
+
});
|
|
1173
|
+
}))
|
|
1174
|
+
.addCommand(new Command('remove')
|
|
1175
|
+
.description('Remove stored password from OS keychain')
|
|
1176
|
+
.action(async () => {
|
|
1177
|
+
const projectPath = process.cwd();
|
|
1178
|
+
const config = await loadConfig(projectPath);
|
|
1179
|
+
const keychain = new KeychainManager(config.keychain?.service || 'envcp');
|
|
1180
|
+
const result = await keychain.removePassword(projectPath);
|
|
1181
|
+
if (result.success) {
|
|
1182
|
+
config.keychain = { ...config.keychain, enabled: false };
|
|
1183
|
+
await saveConfig(config, projectPath);
|
|
1184
|
+
console.log(chalk.green('Password removed from keychain'));
|
|
1185
|
+
}
|
|
1186
|
+
else {
|
|
1187
|
+
console.log(chalk.yellow(`Nothing to remove or error: ${result.error}`));
|
|
1188
|
+
}
|
|
1189
|
+
}))
|
|
1190
|
+
.addCommand(new Command('disable')
|
|
1191
|
+
.description('Disable keychain auto-unlock (keeps stored credential)')
|
|
1192
|
+
.action(async () => {
|
|
1193
|
+
const projectPath = process.cwd();
|
|
1194
|
+
const config = await loadConfig(projectPath);
|
|
1195
|
+
config.keychain = { ...config.keychain, enabled: false };
|
|
1196
|
+
await saveConfig(config, projectPath);
|
|
1197
|
+
console.log(chalk.green('Keychain auto-unlock disabled'));
|
|
1198
|
+
}));
|
|
1199
|
+
// Show welcome screen on first ever run
|
|
1200
|
+
const firstRunMarker = path.join(os.homedir(), '.envcp', '.welcomed');
|
|
1201
|
+
if (!await fs.pathExists(firstRunMarker)) {
|
|
1202
|
+
await fs.ensureDir(path.dirname(firstRunMarker));
|
|
1203
|
+
await fs.writeFile(firstRunMarker, new Date().toISOString());
|
|
1204
|
+
console.log(`
|
|
1205
|
+
███████╗███╗ ██╗██╗ ██╗ ██████╗██████╗
|
|
1206
|
+
██╔════╝████╗ ██║██║ ██║██╔════╝██╔══██╗
|
|
1207
|
+
█████╗ ██╔██╗ ██║██║ ██║██║ ██████╔╝
|
|
1208
|
+
██╔══╝ ██║╚██╗██║╚██╗ ██╔╝██║ ██╔═══╝
|
|
1209
|
+
███████╗██║ ╚████║ ╚████╔╝ ╚██████╗██║
|
|
1210
|
+
╚══════╝╚═╝ ╚═══╝ ╚═══╝ ╚═════╝╚═╝
|
|
1211
|
+
|
|
1212
|
+
Thanks for installing EnvCP!
|
|
1213
|
+
Keep your secrets safe from AI agents.
|
|
1214
|
+
|
|
1215
|
+
─────────────────────────────────────────────
|
|
1216
|
+
|
|
1217
|
+
Vault location:
|
|
1218
|
+
|
|
1219
|
+
~/ or / -> Global vault (shared across all projects)
|
|
1220
|
+
any folder -> Project vault (named after the folder)
|
|
1221
|
+
Rename anytime: envcp vault rename <name>
|
|
1222
|
+
|
|
1223
|
+
─────────────────────────────────────────────
|
|
1224
|
+
|
|
1225
|
+
Get started:
|
|
1226
|
+
|
|
1227
|
+
Simple (one-time setup):
|
|
1228
|
+
$ envcp init # Interactive guided setup
|
|
1229
|
+
|
|
1230
|
+
Advanced (manual config):
|
|
1231
|
+
$ envcp init --advanced # Full config options
|
|
1232
|
+
$ envcp add [NAME] [VALUE] # Add a secret manually
|
|
1233
|
+
|
|
1234
|
+
Explore:
|
|
1235
|
+
$ envcp --help # See all commands
|
|
1236
|
+
|
|
1237
|
+
Docs: https://github.com/fentz26/EnvCP
|
|
1238
|
+
`);
|
|
1239
|
+
}
|
|
1023
1240
|
program.parse();
|
|
1024
1241
|
//# sourceMappingURL=index.js.map
|