@fentz26/envcp 1.2.0-exp.1 → 1.2.0-exp.8d5b91b

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 (48) hide show
  1. package/README.md +8 -1
  2. package/VERSION +1 -1
  3. package/dist/adapters/base.d.ts +18 -3
  4. package/dist/adapters/base.d.ts.map +1 -1
  5. package/dist/adapters/base.js +86 -21
  6. package/dist/adapters/base.js.map +1 -1
  7. package/dist/adapters/gemini.d.ts +1 -1
  8. package/dist/adapters/gemini.d.ts.map +1 -1
  9. package/dist/adapters/gemini.js +5 -3
  10. package/dist/adapters/gemini.js.map +1 -1
  11. package/dist/adapters/openai.d.ts +1 -1
  12. package/dist/adapters/openai.d.ts.map +1 -1
  13. package/dist/adapters/openai.js +5 -3
  14. package/dist/adapters/openai.js.map +1 -1
  15. package/dist/adapters/rest.d.ts +1 -1
  16. package/dist/adapters/rest.d.ts.map +1 -1
  17. package/dist/adapters/rest.js +29 -13
  18. package/dist/adapters/rest.js.map +1 -1
  19. package/dist/cli/index.js +170 -46
  20. package/dist/cli/index.js.map +1 -1
  21. package/dist/config/manager.d.ts +6 -2
  22. package/dist/config/manager.d.ts.map +1 -1
  23. package/dist/config/manager.js +31 -13
  24. package/dist/config/manager.js.map +1 -1
  25. package/dist/mcp/server.d.ts +1 -1
  26. package/dist/mcp/server.d.ts.map +1 -1
  27. package/dist/mcp/server.js +6 -3
  28. package/dist/mcp/server.js.map +1 -1
  29. package/dist/server/unified.d.ts.map +1 -1
  30. package/dist/server/unified.js +9 -9
  31. package/dist/server/unified.js.map +1 -1
  32. package/dist/storage/index.d.ts +13 -0
  33. package/dist/storage/index.d.ts.map +1 -1
  34. package/dist/storage/index.js +32 -0
  35. package/dist/storage/index.js.map +1 -1
  36. package/dist/types.d.ts +29 -0
  37. package/dist/types.d.ts.map +1 -1
  38. package/dist/types.js +5 -0
  39. package/dist/types.js.map +1 -1
  40. package/dist/utils/fs.d.ts +1 -0
  41. package/dist/utils/fs.d.ts.map +1 -1
  42. package/dist/utils/fs.js +12 -0
  43. package/dist/utils/fs.js.map +1 -1
  44. package/dist/vault/index.d.ts +2 -0
  45. package/dist/vault/index.d.ts.map +1 -1
  46. package/dist/vault/index.js +12 -2
  47. package/dist/vault/index.js.map +1 -1
  48. package/package.json +1 -1
package/dist/cli/index.js CHANGED
@@ -7,7 +7,7 @@ import { promptPassword, promptInput, promptConfirm, promptList } from '../utils
7
7
  import { ensureDir, pathExists } from '../utils/fs.js';
8
8
  import { loadConfig, initConfig, saveConfig, parseEnvFile, registerMcpConfig, isBlacklisted, canAccess } from '../config/manager.js';
9
9
  import { ConfigGuard } from '../config/config-guard.js';
10
- import { StorageManager, LogManager } from '../storage/index.js';
10
+ import { StorageManager, LogManager, resolveLogPath } from '../storage/index.js';
11
11
  import { VERSION } from '../version.js';
12
12
  import { SessionManager } from '../utils/session.js';
13
13
  import { maskValue, validatePassword, encrypt, decrypt, generateRecoveryKey, createRecoveryData, recoverPassword } from '../utils/crypto.js';
@@ -16,11 +16,25 @@ import { HsmManager } from '../utils/hsm.js';
16
16
  import { checkForUpdate, formatUpdateMessage, logUpdateCheck } from '../utils/update-checker.js';
17
17
  import { LockoutManager } from '../utils/lockout.js';
18
18
  import { initMemoryProtection } from '../utils/secure-memory.js';
19
- import { getGlobalVaultPath, getProjectVaultPath, resolveVaultPath, setActiveVault, listVaults, initNamedVault, } from '../vault/index.js';
19
+ import { getGlobalVaultPath, getProjectVaultPath, resolveVaultPath, resolveSessionPath, setActiveVault, listVaults, initNamedVault, } from '../vault/index.js';
20
+ import { findProjectRoot } from '../utils/fs.js';
20
21
  initMemoryProtection();
21
- async function withSession(fn, vaultOverride) {
22
+ async function resolveCliContext(vaultOverride) {
23
+ if (vaultOverride === 'global') {
24
+ const home = process.env.HOME || process.env.USERPROFILE || os.homedir();
25
+ const config = await loadConfig(home);
26
+ config.vault = { ...config.vault, mode: 'global' };
27
+ return { projectPath: home, config };
28
+ }
22
29
  const projectPath = process.cwd();
23
30
  const config = await loadConfig(projectPath);
31
+ if (vaultOverride === 'project') {
32
+ config.vault = { ...config.vault, mode: 'project' };
33
+ }
34
+ return { projectPath, config };
35
+ }
36
+ async function withSession(fn, vaultOverride) {
37
+ const { projectPath, config } = await resolveCliContext(vaultOverride);
24
38
  const vaultPath = vaultOverride
25
39
  ? vaultOverride === 'global'
26
40
  ? getGlobalVaultPath(config)
@@ -28,15 +42,15 @@ async function withSession(fn, vaultOverride) {
28
42
  : await resolveVaultPath(projectPath, config);
29
43
  if (config.encryption?.enabled === false) {
30
44
  const storage = new StorageManager(vaultPath, false);
31
- const logManager = new LogManager(path.join(projectPath, '.envcp', 'logs'), config.audit);
45
+ const logManager = new LogManager(resolveLogPath(config.audit, projectPath), config.audit);
32
46
  await logManager.init();
33
47
  await fn(storage, '', config, projectPath, logManager);
34
48
  return;
35
49
  }
36
- const sessionManager = new SessionManager(path.join(projectPath, config.session?.path || '.envcp/.session'), config.session?.timeout_minutes || 30, config.session?.max_extensions || 5);
50
+ const sessionManager = new SessionManager(resolveSessionPath(projectPath, config), config.session?.timeout_minutes || 30, config.session?.max_extensions || 5);
37
51
  await sessionManager.init();
38
52
  // Initialize audit logging
39
- const logManager = new LogManager(path.join(projectPath, '.envcp', 'logs'), config.audit);
53
+ const logManager = new LogManager(resolveLogPath(config.audit, projectPath), config.audit);
40
54
  await logManager.init();
41
55
  let session = await sessionManager.load();
42
56
  let password = '';
@@ -136,12 +150,32 @@ program
136
150
  .option('--hsm-type <type>', 'HSM type for --auth-method hsm|multi: yubikey | gpg | pkcs11')
137
151
  .option('--key-id <id>', 'GPG key ID or PKCS#11 key label for HSM auth')
138
152
  .option('--pkcs11-lib <path>', 'Path to PKCS#11 shared library for --hsm-type pkcs11')
153
+ .option('--global', 'Initialize a global vault at ~/.envcp/ (config + store live in $HOME)')
154
+ .option('--force', 'Overwrite existing envcp.yaml (DESTRUCTIVE: existing vault may become inaccessible)')
139
155
  .action(async (options) => {
140
- const projectPath = process.cwd();
141
- const projectName = options.project || path.basename(projectPath);
142
- console.log(chalk.blue('Initializing EnvCP...'));
156
+ const useGlobal = !!options.global;
157
+ const home = process.env.HOME || process.env.USERPROFILE || os.homedir();
158
+ const projectPath = useGlobal ? home : process.cwd();
159
+ const projectName = options.project || (useGlobal ? 'global' : path.basename(projectPath));
160
+ const configPath = path.join(projectPath, 'envcp.yaml');
161
+ if (await pathExists(configPath) && !options.force) {
162
+ const vaultRelPath = path.join('.envcp', 'store.enc');
163
+ console.log(chalk.yellow('EnvCP is already initialized here.'));
164
+ console.log(chalk.gray(` Config: ${configPath}`));
165
+ if (await pathExists(path.join(projectPath, vaultRelPath))) {
166
+ console.log(chalk.gray(` Vault: ${path.join(projectPath, vaultRelPath)}`));
167
+ }
168
+ console.log('');
169
+ console.log(chalk.gray('Running init again would overwrite your config and may make'));
170
+ console.log(chalk.gray('existing encrypted variables permanently inaccessible.'));
171
+ console.log('');
172
+ console.log(chalk.gray('To reconfigure: edit envcp.yaml directly'));
173
+ console.log(chalk.gray('To force re-init (DESTRUCTIVE): envcp init --force'));
174
+ process.exit(1);
175
+ }
176
+ console.log(chalk.blue(useGlobal ? 'Initializing EnvCP (global vault)...' : 'Initializing EnvCP...'));
143
177
  console.log('');
144
- const config = await initConfig(projectPath, projectName);
178
+ const config = await initConfig(projectPath, projectName, { global: useGlobal });
145
179
  // Single security question (or skip if --no-encrypt)
146
180
  let securityChoice;
147
181
  if (options.encrypt === false) {
@@ -203,7 +237,7 @@ program
203
237
  }
204
238
  pwd = password;
205
239
  }
206
- await saveConfig(config, projectPath);
240
+ await saveConfig(config, projectPath, { global: useGlobal });
207
241
  const modeLabel = securityChoice === 'none' ? 'no encryption' : securityChoice;
208
242
  console.log(chalk.green('EnvCP initialized!'));
209
243
  console.log(chalk.gray(` Project: ${config.project}`));
@@ -236,7 +270,7 @@ program
236
270
  await storage.save(existing);
237
271
  // Create session for encrypted mode
238
272
  if (pwd) {
239
- const sessionManager = new SessionManager(path.join(projectPath, config.session?.path || '.envcp/.session'), config.session?.timeout_minutes || 30, config.session?.max_extensions || 5);
273
+ const sessionManager = new SessionManager(resolveSessionPath(projectPath, config), config.session?.timeout_minutes || 30, config.session?.max_extensions || 5);
240
274
  await sessionManager.init();
241
275
  await sessionManager.create(pwd);
242
276
  }
@@ -303,7 +337,7 @@ program
303
337
  if (await hsm.isAvailable()) {
304
338
  try {
305
339
  await hsm.protectVaultPassword(pwd);
306
- await saveConfig(config, projectPath);
340
+ await saveConfig(config, projectPath, { global: useGlobal });
307
341
  console.log('');
308
342
  console.log(chalk.green(` Vault password protected by ${hsm.backendName}`));
309
343
  console.log(chalk.gray(` Future sessions will authenticate via hardware`));
@@ -317,7 +351,7 @@ program
317
351
  console.log(chalk.yellow(` HSM device (${hsm.backendName}) not available at init time.`));
318
352
  console.log(chalk.gray(' Run "envcp unlock --setup-hsm" later to enable hardware authentication.'));
319
353
  config.auth = { method: authMethod, multi_factors: ['password', 'hsm'], fallback: 'password' };
320
- await saveConfig(config, projectPath);
354
+ await saveConfig(config, projectPath, { global: useGlobal });
321
355
  }
322
356
  }
323
357
  console.log('');
@@ -333,9 +367,9 @@ program
333
367
  .option('--hsm-type <type>', 'HSM type: yubikey | gpg | pkcs11 (default: yubikey)')
334
368
  .option('--key-id <id>', 'GPG key ID or PKCS#11 key label')
335
369
  .option('--pkcs11-lib <path>', 'Path to PKCS#11 shared library (.so / .dll)')
370
+ .option('--global', 'Unlock the global vault at ~/.envcp')
336
371
  .action(async (options) => {
337
- const projectPath = process.cwd();
338
- const config = await loadConfig(projectPath);
372
+ const { projectPath, config } = await resolveCliContext(options.global ? 'global' : undefined);
339
373
  let password = options.password;
340
374
  if (!password) {
341
375
  password = await promptPassword('Enter password:');
@@ -349,7 +383,7 @@ program
349
383
  if (passwordValid && passwordWarning) {
350
384
  console.log(chalk.yellow('⚠ Weak password detected'));
351
385
  }
352
- const sessionDir = path.join(projectPath, path.dirname(config.session?.path || '.envcp/.session'));
386
+ const sessionDir = path.dirname(resolveSessionPath(projectPath, config));
353
387
  let lockoutManager = new LockoutManager(path.join(sessionDir, '.lockout'));
354
388
  // Handle recovery key if provided
355
389
  if (options.recoveryKey) {
@@ -365,7 +399,7 @@ program
365
399
  await lockoutManager.clearPermanentLockout();
366
400
  console.log(chalk.green('Permanent lockout cleared.'));
367
401
  // Log recovery event
368
- const logManager = new LogManager(path.join(projectPath, '.envcp', 'logs'), config.audit);
402
+ const logManager = new LogManager(resolveLogPath(config.audit, projectPath), config.audit);
369
403
  await logManager.init();
370
404
  await logManager.log({
371
405
  timestamp: new Date().toISOString(),
@@ -394,12 +428,13 @@ program
394
428
  const progressiveDelay = bfpConfig?.progressive_delay ?? true;
395
429
  const maxDelay = bfpConfig?.max_delay ?? 60;
396
430
  const permanentThreshold = bfpConfig?.permanent_lockout_threshold ?? 0;
397
- const sessionManager = new SessionManager(path.join(projectPath, config.session?.path || '.envcp/.session'), config.session?.timeout_minutes || 30, config.session?.max_extensions || 5);
431
+ const sessionManager = new SessionManager(resolveSessionPath(projectPath, config), config.session?.timeout_minutes || 30, config.session?.max_extensions || 5);
398
432
  await sessionManager.init();
399
433
  // Initialize audit logging
400
- const logManager = new LogManager(path.join(projectPath, '.envcp', 'logs'), config.audit);
434
+ const logManager = new LogManager(resolveLogPath(config.audit, projectPath), config.audit);
401
435
  await logManager.init();
402
- const storage = new StorageManager(path.join(projectPath, config.storage.path), config.storage.encrypted);
436
+ const vaultPathForUnlock = await resolveVaultPath(projectPath, config);
437
+ const storage = new StorageManager(vaultPathForUnlock, config.storage.encrypted);
403
438
  storage.setPassword(password);
404
439
  const storeExists = await storage.exists();
405
440
  // Check lockout before attempting password verification on existing stores
@@ -618,10 +653,10 @@ program
618
653
  program
619
654
  .command('lock')
620
655
  .description('Lock EnvCP session')
621
- .action(async () => {
622
- const projectPath = process.cwd();
623
- const config = await loadConfig(projectPath);
624
- const sessionManager = new SessionManager(path.join(projectPath, config.session?.path || '.envcp/.session'), config.session?.timeout_minutes || 30, config.session?.max_extensions || 5);
656
+ .option('--global', 'Lock the global vault session at ~/.envcp/.session')
657
+ .action(async (options) => {
658
+ const { projectPath, config } = await resolveCliContext(options.global ? 'global' : undefined);
659
+ const sessionManager = new SessionManager(resolveSessionPath(projectPath, config), config.session?.timeout_minutes || 30, config.session?.max_extensions || 5);
625
660
  await sessionManager.init();
626
661
  await sessionManager.destroy();
627
662
  console.log(chalk.green('Session locked'));
@@ -629,10 +664,10 @@ program
629
664
  program
630
665
  .command('status')
631
666
  .description('Check session status')
632
- .action(async () => {
633
- const projectPath = process.cwd();
634
- const config = await loadConfig(projectPath);
635
- const sessionManager = new SessionManager(path.join(projectPath, config.session?.path || '.envcp/.session'), config.session?.timeout_minutes || 30, config.session?.max_extensions || 5);
667
+ .option('--global', 'Check status of the global vault session at ~/.envcp/.session')
668
+ .action(async (options) => {
669
+ const { projectPath, config } = await resolveCliContext(options.global ? 'global' : undefined);
670
+ const sessionManager = new SessionManager(resolveSessionPath(projectPath, config), config.session?.timeout_minutes || 30, config.session?.max_extensions || 5);
636
671
  await sessionManager.init();
637
672
  const password = await promptPassword('Enter password:');
638
673
  const session = await sessionManager.load(password);
@@ -669,10 +704,10 @@ program
669
704
  program
670
705
  .command('extend')
671
706
  .description('Extend session timeout')
672
- .action(async () => {
673
- const projectPath = process.cwd();
674
- const config = await loadConfig(projectPath);
675
- const sessionManager = new SessionManager(path.join(projectPath, config.session?.path || '.envcp/.session'), config.session?.timeout_minutes || 30, config.session?.max_extensions || 5);
707
+ .option('--global', 'Extend the global vault session at ~/.envcp/.session')
708
+ .action(async (options) => {
709
+ const { projectPath, config } = await resolveCliContext(options.global ? 'global' : undefined);
710
+ const sessionManager = new SessionManager(resolveSessionPath(projectPath, config), config.session?.timeout_minutes || 30, config.session?.max_extensions || 5);
676
711
  await sessionManager.init();
677
712
  const password = await promptPassword('Enter password:');
678
713
  const loaded = await sessionManager.load(password);
@@ -749,7 +784,7 @@ program
749
784
  console.log(chalk.yellow.bold(` ${newRecoveryKey}`));
750
785
  console.log(chalk.gray('Your old recovery key no longer works.'));
751
786
  // Create a session with new password
752
- const sessionManager = new SessionManager(path.join(projectPath, config.session?.path || '.envcp/.session'), config.session?.timeout_minutes || 30, config.session?.max_extensions || 5);
787
+ const sessionManager = new SessionManager(resolveSessionPath(projectPath, config), config.session?.timeout_minutes || 30, config.session?.max_extensions || 5);
753
788
  await sessionManager.init();
754
789
  await sessionManager.create(newPassword);
755
790
  console.log(chalk.green('Session unlocked with new password.'));
@@ -786,15 +821,70 @@ program
786
821
  program
787
822
  .command('add <name>')
788
823
  .description('Add a new environment variable')
789
- .option('-v, --value <value>', 'Variable value')
824
+ .option('-v, --value <value>', 'Variable value (WARNING: leaks in shell history; prefer --from-env/--from-file/--stdin)')
825
+ .option('--from-env <envVar>', 'Read value from the named environment variable')
826
+ .option('--from-file <path>', 'Read value from a file (trailing newline trimmed)')
827
+ .option('--stdin', 'Read value from piped stdin')
790
828
  .option('-t, --tags <tags>', 'Tags (comma-separated)')
791
829
  .option('-d, --description <desc>', 'Description')
792
830
  .action(async (name, options) => {
831
+ const sourceFlags = [
832
+ options.value !== undefined ? '--value' : null,
833
+ options.fromEnv !== undefined ? '--from-env' : null,
834
+ options.fromFile !== undefined ? '--from-file' : null,
835
+ options.stdin ? '--stdin' : null,
836
+ ].filter(Boolean);
837
+ if (sourceFlags.length > 1) {
838
+ console.error(chalk.red(`Error: ${sourceFlags.join(', ')} are mutually exclusive — pick one`));
839
+ process.exit(1);
840
+ }
841
+ let value;
842
+ let sourced = false;
843
+ if (options.value !== undefined) {
844
+ value = options.value;
845
+ sourced = true;
846
+ if (process.stdout.isTTY) {
847
+ console.warn(chalk.yellow('⚠ --value is visible in shell history and process list. Use --from-env, --from-file, or --stdin for secrets.'));
848
+ }
849
+ }
850
+ else if (options.fromEnv) {
851
+ const envValue = process.env[options.fromEnv];
852
+ if (envValue === undefined) {
853
+ console.error(chalk.red(`Error: environment variable '${options.fromEnv}' is not set`));
854
+ process.exit(1);
855
+ }
856
+ value = envValue;
857
+ sourced = true;
858
+ }
859
+ else if (options.fromFile) {
860
+ try {
861
+ const raw = await fs.readFile(options.fromFile, 'utf-8');
862
+ value = raw.replace(/\r?\n$/, '');
863
+ sourced = true;
864
+ }
865
+ catch (err) {
866
+ const msg = err instanceof Error ? err.message : String(err);
867
+ console.error(chalk.red(`Error: cannot read '${options.fromFile}': ${msg}`));
868
+ process.exit(1);
869
+ }
870
+ }
871
+ else if (options.stdin) {
872
+ if (process.stdin.isTTY) {
873
+ console.error(chalk.red('Error: --stdin requires piped input (e.g., `echo "$SECRET" | envcp add NAME --stdin`)'));
874
+ process.exit(1);
875
+ }
876
+ value = await new Promise((resolve, reject) => {
877
+ const chunks = [];
878
+ process.stdin.on('data', (c) => chunks.push(typeof c === 'string' ? Buffer.from(c) : c));
879
+ process.stdin.on('end', () => resolve(Buffer.concat(chunks).toString('utf-8').replace(/\r?\n$/, '')));
880
+ process.stdin.on('error', reject);
881
+ });
882
+ sourced = true;
883
+ }
793
884
  await withSession(async (storage, _password, config) => {
794
- let value = options.value;
795
885
  let tags = [];
796
886
  let description = options.description;
797
- if (!value) {
887
+ if (!sourced) {
798
888
  value = await promptPassword('Enter value:');
799
889
  const tagsInput = await promptInput('Tags (comma-separated):');
800
890
  tags = tagsInput.split(',').map((t) => t.trim()).filter(Boolean);
@@ -803,6 +893,10 @@ program
803
893
  else if (options.tags) {
804
894
  tags = options.tags.split(',').map((t) => t.trim()).filter(Boolean);
805
895
  }
896
+ if (value === undefined) {
897
+ console.error(chalk.red('Error: no value provided'));
898
+ process.exit(1);
899
+ }
806
900
  const now = new Date().toISOString();
807
901
  const variable = {
808
902
  name,
@@ -984,10 +1078,40 @@ program
984
1078
  .option('--port <port>', 'HTTP port (for non-MCP modes)', '3456')
985
1079
  .option('--host <host>', 'HTTP host', '127.0.0.1')
986
1080
  .option('-k, --api-key <key>', 'API key for HTTP authentication')
1081
+ .option('--global', 'Force the global vault at ~/.envcp (skip project lookup)')
987
1082
  .action(async (options) => {
988
- const projectPath = process.cwd();
1083
+ const home = process.env.HOME || process.env.USERPROFILE || os.homedir();
1084
+ const globalConfigPath = path.join(home, '.envcp', 'config.yaml');
1085
+ // Resolve where the vault lives: --global forces home; otherwise walk up
1086
+ // from cwd looking for envcp.yaml; otherwise fall back to a global install.
1087
+ let projectPath;
1088
+ let forceGlobalMode = false;
1089
+ if (options.global) {
1090
+ projectPath = home;
1091
+ forceGlobalMode = true;
1092
+ }
1093
+ else {
1094
+ const found = await findProjectRoot(process.cwd());
1095
+ if (found) {
1096
+ projectPath = found;
1097
+ }
1098
+ else if (await pathExists(globalConfigPath)) {
1099
+ projectPath = home;
1100
+ forceGlobalMode = true;
1101
+ }
1102
+ else {
1103
+ process.stderr.write('Error: No envcp.yaml found in cwd or any ancestor, and no global config at ~/.envcp/config.yaml.\n' +
1104
+ 'Run `envcp init` (project) or `envcp init --global` first.\n');
1105
+ process.exit(1);
1106
+ }
1107
+ }
989
1108
  const configGuard = new ConfigGuard(projectPath);
990
1109
  const config = await configGuard.loadAndLock();
1110
+ if (forceGlobalMode) {
1111
+ config.vault = { ...config.vault, mode: 'global' };
1112
+ }
1113
+ const vaultPath = await resolveVaultPath(projectPath, config);
1114
+ const sessionPath = resolveSessionPath(projectPath, config);
991
1115
  const mode = options.mode;
992
1116
  const port = parseInt(options.port, 10);
993
1117
  const host = options.host;
@@ -997,20 +1121,20 @@ program
997
1121
  if (config.encryption?.enabled === false) {
998
1122
  if (mode === 'mcp') {
999
1123
  const { EnvCPServer } = await import('../mcp/server.js');
1000
- const server = new EnvCPServer(config, projectPath);
1124
+ const server = new EnvCPServer(config, projectPath, undefined, vaultPath, sessionPath);
1001
1125
  await server.start();
1002
1126
  return;
1003
1127
  }
1004
1128
  }
1005
1129
  else {
1006
1130
  // Encrypted mode: need password
1007
- const sessionManager = new SessionManager(path.join(projectPath, config.session?.path || '.envcp/.session'), config.session?.timeout_minutes || 30, config.session?.max_extensions || 5);
1131
+ const sessionManager = new SessionManager(sessionPath, config.session?.timeout_minutes || 30, config.session?.max_extensions || 5);
1008
1132
  await sessionManager.init();
1009
1133
  let session = await sessionManager.load();
1010
1134
  if (!session && !password) {
1011
1135
  // MCP mode uses stdio — can't prompt interactively
1012
1136
  if (mode === 'mcp') {
1013
- process.stderr.write('Error: No active session. Run `envcp unlock` first, or use --password flag.\n');
1137
+ process.stderr.write(`Error: No active session at ${sessionPath}. Run \`envcp unlock${forceGlobalMode ? ' --global' : ''}\` first, or use --password flag.\n`);
1014
1138
  process.exit(1);
1015
1139
  }
1016
1140
  password = await promptPassword('Enter password:');
@@ -1029,7 +1153,7 @@ program
1029
1153
  // MCP mode uses stdio
1030
1154
  if (mode === 'mcp') {
1031
1155
  const { EnvCPServer } = await import('../mcp/server.js');
1032
- const server = new EnvCPServer(config, projectPath, password);
1156
+ const server = new EnvCPServer(config, projectPath, password, vaultPath, sessionPath);
1033
1157
  await server.start();
1034
1158
  return;
1035
1159
  }
@@ -1340,7 +1464,7 @@ program
1340
1464
  }
1341
1465
  // 5. Session status
1342
1466
  if (encrypted) {
1343
- const sessionManager = new SessionManager(path.join(projectPath, config.session?.path || '.envcp/.session'), config.session?.timeout_minutes || 30, config.session?.max_extensions || 5);
1467
+ const sessionManager = new SessionManager(resolveSessionPath(projectPath, config), config.session?.timeout_minutes || 30, config.session?.max_extensions || 5);
1344
1468
  await sessionManager.init();
1345
1469
  const session = await sessionManager.load();
1346
1470
  if (session) {
@@ -1727,7 +1851,7 @@ program
1727
1851
  .action(async (options) => {
1728
1852
  const projectPath = process.cwd();
1729
1853
  const config = await loadConfig(projectPath);
1730
- const logDir = path.join(projectPath, '.envcp', 'logs');
1854
+ const logDir = resolveLogPath(config.audit, projectPath);
1731
1855
  const logs = new LogManager(logDir, config.audit);
1732
1856
  await logs.init();
1733
1857
  if (options.dates) {
@@ -1794,7 +1918,7 @@ program
1794
1918
  .action(async (options) => {
1795
1919
  const projectPath = process.cwd();
1796
1920
  const config = await loadConfig(projectPath);
1797
- const logDir = path.join(projectPath, '.envcp', 'logs');
1921
+ const logDir = resolveLogPath(config.audit, projectPath);
1798
1922
  const logs = new LogManager(logDir, config.audit);
1799
1923
  await logs.init();
1800
1924
  if (!config.audit.hmac_chain) {
@@ -1817,7 +1941,7 @@ program
1817
1941
  .action(async () => {
1818
1942
  const projectPath = process.cwd();
1819
1943
  const config = await loadConfig(projectPath);
1820
- const logDir = path.join(projectPath, '.envcp', 'logs');
1944
+ const logDir = resolveLogPath(config.audit, projectPath);
1821
1945
  const logs = new LogManager(logDir, config.audit);
1822
1946
  await logs.init();
1823
1947
  if (config.audit.protection === 'none') {