lsh-framework 3.1.0 → 3.1.1

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.
@@ -8,6 +8,7 @@ import * as path from 'path';
8
8
  import { createClient } from '@supabase/supabase-js';
9
9
  import { getPlatformPaths, getPlatformInfo } from '../lib/platform-utils.js';
10
10
  import { IPFSClientManager } from '../lib/ipfs-client-manager.js';
11
+ import * as os from 'os';
11
12
  /**
12
13
  * Register doctor commands
13
14
  */
@@ -15,6 +16,7 @@ export function registerDoctorCommands(program) {
15
16
  program
16
17
  .command('doctor')
17
18
  .description('Health check and troubleshooting')
19
+ .option('-g, --global', 'Use global workspace ($HOME)')
18
20
  .option('-v, --verbose', 'Show detailed information')
19
21
  .option('--json', 'Output results as JSON')
20
22
  .action(async (options) => {
@@ -28,12 +30,25 @@ export function registerDoctorCommands(program) {
28
30
  }
29
31
  });
30
32
  }
33
+ /**
34
+ * Get the base directory for .env files
35
+ */
36
+ function getBaseDir(globalMode) {
37
+ return globalMode ? os.homedir() : process.cwd();
38
+ }
31
39
  /**
32
40
  * Run comprehensive health check
33
41
  */
34
42
  async function runHealthCheck(options) {
43
+ const baseDir = getBaseDir(options.global);
35
44
  if (!options.json) {
36
- console.log(chalk.bold.cyan('\nšŸ„ LSH Health Check'));
45
+ if (options.global) {
46
+ console.log(chalk.bold.cyan('\nšŸ„ LSH Health Check (Global Workspace)'));
47
+ console.log(chalk.yellow(` Location: ${baseDir}`));
48
+ }
49
+ else {
50
+ console.log(chalk.bold.cyan('\nšŸ„ LSH Health Check'));
51
+ }
37
52
  console.log(chalk.gray('━'.repeat(50)));
38
53
  console.log('');
39
54
  }
@@ -41,18 +56,20 @@ async function runHealthCheck(options) {
41
56
  // Platform check
42
57
  checks.push(await checkPlatform(options.verbose));
43
58
  // .env file check
44
- checks.push(await checkEnvFile(options.verbose));
59
+ checks.push(await checkEnvFile(options.verbose, baseDir));
45
60
  // Encryption key check
46
- checks.push(await checkEncryptionKey(options.verbose));
61
+ checks.push(await checkEncryptionKey(options.verbose, baseDir));
47
62
  // Storage backend check
48
- const storageChecks = await checkStorageBackend(options.verbose);
63
+ const storageChecks = await checkStorageBackend(options.verbose, baseDir);
49
64
  checks.push(...storageChecks);
50
- // Git repository check
51
- checks.push(await checkGitRepository(options.verbose));
65
+ // Git repository check (skip for global mode)
66
+ if (!options.global) {
67
+ checks.push(await checkGitRepository(options.verbose));
68
+ }
52
69
  // IPFS client check
53
70
  checks.push(await checkIPFSClient(options.verbose));
54
71
  // Permissions check
55
- checks.push(await checkPermissions(options.verbose));
72
+ checks.push(await checkPermissions(options.verbose, baseDir));
56
73
  // Display results
57
74
  if (options.json) {
58
75
  console.log(JSON.stringify({ checks, summary: getSummary(checks) }, null, 2));
@@ -86,9 +103,9 @@ async function checkPlatform(verbose) {
86
103
  /**
87
104
  * Check .env file
88
105
  */
89
- async function checkEnvFile(verbose) {
106
+ async function checkEnvFile(verbose, baseDir) {
90
107
  try {
91
- const envPath = path.join(process.cwd(), '.env');
108
+ const envPath = path.join(baseDir || process.cwd(), '.env');
92
109
  // Read file directly without access check to avoid TOCTOU race condition
93
110
  const content = await fs.readFile(envPath, 'utf-8');
94
111
  const lines = content.split('\n').filter(l => l.trim() && !l.startsWith('#'));
@@ -111,9 +128,9 @@ async function checkEnvFile(verbose) {
111
128
  /**
112
129
  * Check encryption key
113
130
  */
114
- async function checkEncryptionKey(verbose) {
131
+ async function checkEncryptionKey(verbose, baseDir) {
115
132
  try {
116
- const envPath = path.join(process.cwd(), '.env');
133
+ const envPath = path.join(baseDir || process.cwd(), '.env');
117
134
  const content = await fs.readFile(envPath, 'utf-8');
118
135
  const match = content.match(/^LSH_SECRETS_KEY=(.+)$/m);
119
136
  if (!match) {
@@ -163,10 +180,10 @@ async function checkEncryptionKey(verbose) {
163
180
  /**
164
181
  * Check storage backend configuration
165
182
  */
166
- async function checkStorageBackend(verbose) {
183
+ async function checkStorageBackend(verbose, baseDir) {
167
184
  const checks = [];
168
185
  try {
169
- const envPath = path.join(process.cwd(), '.env');
186
+ const envPath = path.join(baseDir || process.cwd(), '.env');
170
187
  const content = await fs.readFile(envPath, 'utf-8');
171
188
  const supabaseUrl = content.match(/^SUPABASE_URL=(.+)$/m)?.[1]?.trim();
172
189
  const supabaseKey = content.match(/^SUPABASE_ANON_KEY=(.+)$/m)?.[1]?.trim();
@@ -332,7 +349,7 @@ async function checkIPFSClient(verbose) {
332
349
  /**
333
350
  * Check file permissions
334
351
  */
335
- async function checkPermissions(verbose) {
352
+ async function checkPermissions(verbose, _baseDir) {
336
353
  try {
337
354
  const paths = getPlatformPaths();
338
355
  // Check if we can write to temp directory with secure permissions
@@ -11,6 +11,7 @@ import { createClient } from '@supabase/supabase-js';
11
11
  import ora from 'ora';
12
12
  import { getPlatformPaths } from '../lib/platform-utils.js';
13
13
  import { getGitRepoInfo } from '../lib/git-utils.js';
14
+ import * as os from 'os';
14
15
  /**
15
16
  * Register init commands
16
17
  */
@@ -18,6 +19,7 @@ export function registerInitCommands(program) {
18
19
  program
19
20
  .command('init')
20
21
  .description('Interactive setup wizard (first-time configuration)')
22
+ .option('-g, --global', 'Use global workspace ($HOME)')
21
23
  .option('--local', 'Use local-only encryption (no cloud sync)')
22
24
  .option('--storacha', 'Use Storacha IPFS network sync (recommended)')
23
25
  .option('--supabase', 'Use Supabase cloud storage')
@@ -34,15 +36,30 @@ export function registerInitCommands(program) {
34
36
  }
35
37
  });
36
38
  }
39
+ /**
40
+ * Get the base directory for .env files
41
+ */
42
+ function getBaseDir(globalMode) {
43
+ return globalMode ? os.homedir() : process.cwd();
44
+ }
37
45
  /**
38
46
  * Run the interactive setup wizard
39
47
  */
40
48
  async function runSetupWizard(options) {
41
- console.log(chalk.bold.cyan('\nšŸ” LSH Secrets Manager - Setup Wizard'));
42
- console.log(chalk.gray('━'.repeat(50)));
49
+ const globalMode = options.global ?? false;
50
+ const baseDir = getBaseDir(globalMode);
51
+ if (globalMode) {
52
+ console.log(chalk.bold.cyan('\nšŸ” LSH Secrets Manager - Global Setup Wizard'));
53
+ console.log(chalk.gray('━'.repeat(50)));
54
+ console.log(chalk.yellow(`\n🌐 Global Mode: Using $HOME (${baseDir})`));
55
+ }
56
+ else {
57
+ console.log(chalk.bold.cyan('\nšŸ” LSH Secrets Manager - Setup Wizard'));
58
+ console.log(chalk.gray('━'.repeat(50)));
59
+ }
43
60
  console.log('');
44
61
  // Check if already configured
45
- const existingConfig = await checkExistingConfig();
62
+ const existingConfig = await checkExistingConfig(baseDir);
46
63
  if (existingConfig) {
47
64
  const { overwrite } = await inquirer.prompt([
48
65
  {
@@ -181,16 +198,16 @@ async function runSetupWizard(options) {
181
198
  }
182
199
  }
183
200
  // Save configuration
184
- await saveConfiguration(config);
201
+ await saveConfiguration(config, baseDir, globalMode);
185
202
  // Show success message
186
203
  showSuccessMessage(config);
187
204
  }
188
205
  /**
189
206
  * Check if LSH is already configured
190
207
  */
191
- async function checkExistingConfig() {
208
+ async function checkExistingConfig(baseDir) {
192
209
  try {
193
- const envPath = path.join(process.cwd(), '.env');
210
+ const envPath = path.join(baseDir, '.env');
194
211
  // Read file directly without access check to avoid TOCTOU race condition
195
212
  const content = await fs.readFile(envPath, 'utf-8');
196
213
  return content.includes('LSH_SECRETS_KEY') ||
@@ -417,10 +434,10 @@ function generateEncryptionKey() {
417
434
  /**
418
435
  * Save configuration to .env file
419
436
  */
420
- async function saveConfiguration(config) {
437
+ async function saveConfiguration(config, baseDir, globalMode) {
421
438
  const spinner = ora('Saving configuration...').start();
422
439
  try {
423
- const envPath = path.join(process.cwd(), '.env');
440
+ const envPath = path.join(baseDir, '.env');
424
441
  let envContent = '';
425
442
  // Try to read existing .env
426
443
  try {
@@ -461,8 +478,10 @@ async function saveConfiguration(config) {
461
478
  }
462
479
  // Write .env file
463
480
  await fs.writeFile(envPath, envContent, 'utf-8');
464
- // Update .gitignore
465
- await updateGitignore();
481
+ // Update .gitignore (skip for global mode since it's in $HOME)
482
+ if (!globalMode) {
483
+ await updateGitignore();
484
+ }
466
485
  spinner.succeed(chalk.green('āœ… Configuration saved'));
467
486
  }
468
487
  catch (error) {
@@ -222,10 +222,12 @@ export async function init_secrets(program) {
222
222
  .command('create')
223
223
  .description('Create a new .env file')
224
224
  .option('-f, --file <path>', 'Path to .env file', '.env')
225
+ .option('-g, --global', 'Use global workspace ($HOME)')
225
226
  .option('-t, --template', 'Create with common template variables')
226
227
  .action(async (options) => {
227
228
  try {
228
- const envPath = path.resolve(options.file);
229
+ const manager = new SecretsManager({ globalMode: options.global });
230
+ const envPath = path.resolve(manager.resolveFilePath(options.file));
229
231
  // Check if file already exists
230
232
  if (fs.existsSync(envPath)) {
231
233
  console.log(`āŒ File already exists: ${envPath}`);
@@ -882,6 +884,7 @@ API_KEY=
882
884
  program
883
885
  .command('clear')
884
886
  .description('Clear local metadata and cache to resolve stuck registries')
887
+ .option('-g, --global', 'Use global workspace ($HOME) - default behavior')
885
888
  .option('--repo <name>', 'Clear metadata for specific repo only')
886
889
  .option('--cache', 'Also clear local encrypted secrets cache')
887
890
  .option('--storacha', 'Also delete old Storacha uploads (registries and secrets)')
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "lsh-framework",
3
- "version": "3.1.0",
3
+ "version": "3.1.1",
4
4
  "description": "Simple, cross-platform encrypted secrets manager with automatic sync, IPFS audit logs, and multi-environment support. Just run lsh sync and start managing your secrets.",
5
5
  "main": "dist/app.js",
6
6
  "bin": {
@@ -17,7 +17,7 @@
17
17
  "scripts": {
18
18
  "build": "tsc",
19
19
  "watch": "tsc --watch",
20
- "test": "node --experimental-vm-modules ./node_modules/.bin/jest",
20
+ "test": "node --experimental-vm-modules ./node_modules/.bin/jest --runInBand",
21
21
  "test:ci": "node --experimental-vm-modules ./node_modules/.bin/jest --runInBand",
22
22
  "test:coverage": "node --experimental-vm-modules ./node_modules/.bin/jest --coverage --runInBand",
23
23
  "clean": "rm -rf ./build; rm -rf ./bin; rm -rf ./dist",