lsh-framework 0.6.0 → 0.8.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 +493 -245
- package/dist/cli.js +191 -45
- package/dist/commands/self.js +59 -3
- package/dist/commands/theme.js +2 -1
- package/dist/lib/base-job-manager.js +2 -1
- package/dist/lib/job-manager.js +55 -7
- package/dist/lib/lshrc-init.js +3 -1
- package/dist/lib/secrets-manager.js +161 -11
- package/dist/services/lib/lib.js +3 -1
- package/dist/services/secrets/secrets.js +155 -4
- package/package.json +26 -22
|
@@ -3,6 +3,7 @@
|
|
|
3
3
|
* Sync .env files across machines using encrypted Supabase storage
|
|
4
4
|
*/
|
|
5
5
|
import * as fs from 'fs';
|
|
6
|
+
import * as path from 'path';
|
|
6
7
|
import * as crypto from 'crypto';
|
|
7
8
|
import DatabasePersistence from './database-persistence.js';
|
|
8
9
|
import { createLogger } from './logger.js';
|
|
@@ -45,14 +46,30 @@ export class SecretsManager {
|
|
|
45
46
|
* Decrypt a value
|
|
46
47
|
*/
|
|
47
48
|
decrypt(text) {
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
49
|
+
try {
|
|
50
|
+
const parts = text.split(':');
|
|
51
|
+
if (parts.length !== 2) {
|
|
52
|
+
throw new Error('Invalid encrypted format');
|
|
53
|
+
}
|
|
54
|
+
const iv = Buffer.from(parts[0], 'hex');
|
|
55
|
+
const encryptedText = parts[1];
|
|
56
|
+
const key = Buffer.from(this.encryptionKey, 'hex');
|
|
57
|
+
const decipher = crypto.createDecipheriv('aes-256-cbc', key.slice(0, 32), iv);
|
|
58
|
+
let decrypted = decipher.update(encryptedText, 'hex', 'utf8');
|
|
59
|
+
decrypted += decipher.final('utf8');
|
|
60
|
+
return decrypted;
|
|
61
|
+
}
|
|
62
|
+
catch (error) {
|
|
63
|
+
if (error.message.includes('bad decrypt') || error.message.includes('wrong final block length')) {
|
|
64
|
+
throw new Error('Decryption failed. This usually means:\n' +
|
|
65
|
+
' 1. You need to set LSH_SECRETS_KEY environment variable\n' +
|
|
66
|
+
' 2. The key must match the one used during encryption\n' +
|
|
67
|
+
' 3. Generate a shared key with: lsh secrets key\n' +
|
|
68
|
+
' 4. Add it to your .env: LSH_SECRETS_KEY=<key>\n' +
|
|
69
|
+
'\nOriginal error: ' + error.message);
|
|
70
|
+
}
|
|
71
|
+
throw error;
|
|
72
|
+
}
|
|
56
73
|
}
|
|
57
74
|
/**
|
|
58
75
|
* Parse .env file into key-value pairs
|
|
@@ -100,6 +117,13 @@ export class SecretsManager {
|
|
|
100
117
|
if (!fs.existsSync(envFilePath)) {
|
|
101
118
|
throw new Error(`File not found: ${envFilePath}`);
|
|
102
119
|
}
|
|
120
|
+
// Warn if using default key
|
|
121
|
+
if (!process.env.LSH_SECRETS_KEY) {
|
|
122
|
+
logger.warn('⚠️ Warning: No LSH_SECRETS_KEY set. Using machine-specific key.');
|
|
123
|
+
logger.warn(' To share secrets across machines, generate a key with: lsh secrets key');
|
|
124
|
+
logger.warn(' Then add LSH_SECRETS_KEY=<key> to your .env on all machines');
|
|
125
|
+
console.log();
|
|
126
|
+
}
|
|
103
127
|
logger.info(`Pushing ${envFilePath} to Supabase (${environment})...`);
|
|
104
128
|
const content = fs.readFileSync(envFilePath, 'utf8');
|
|
105
129
|
const env = this.parseEnvFile(content);
|
|
@@ -121,7 +145,7 @@ export class SecretsManager {
|
|
|
121
145
|
/**
|
|
122
146
|
* Pull .env from Supabase
|
|
123
147
|
*/
|
|
124
|
-
async pull(envFilePath = '.env', environment = 'dev') {
|
|
148
|
+
async pull(envFilePath = '.env', environment = 'dev', force = false) {
|
|
125
149
|
logger.info(`Pulling ${environment} secrets from Supabase...`);
|
|
126
150
|
// Get latest secrets
|
|
127
151
|
const jobs = await this.persistence.getActiveJobs();
|
|
@@ -136,8 +160,8 @@ export class SecretsManager {
|
|
|
136
160
|
throw new Error(`No encrypted data found for environment: ${environment}`);
|
|
137
161
|
}
|
|
138
162
|
const decrypted = this.decrypt(latestSecret.output);
|
|
139
|
-
// Backup existing .env if it exists
|
|
140
|
-
if (fs.existsSync(envFilePath)) {
|
|
163
|
+
// Backup existing .env if it exists (unless force is true)
|
|
164
|
+
if (fs.existsSync(envFilePath) && !force) {
|
|
141
165
|
const backup = `${envFilePath}.backup.${Date.now()}`;
|
|
142
166
|
fs.copyFileSync(envFilePath, backup);
|
|
143
167
|
logger.info(`Backed up existing .env to ${backup}`);
|
|
@@ -189,5 +213,131 @@ export class SecretsManager {
|
|
|
189
213
|
}
|
|
190
214
|
console.log();
|
|
191
215
|
}
|
|
216
|
+
/**
|
|
217
|
+
* Get status of secrets for an environment
|
|
218
|
+
*/
|
|
219
|
+
async status(envFilePath = '.env', environment = 'dev') {
|
|
220
|
+
const status = {
|
|
221
|
+
localExists: false,
|
|
222
|
+
localKeys: 0,
|
|
223
|
+
localModified: undefined,
|
|
224
|
+
cloudExists: false,
|
|
225
|
+
cloudKeys: 0,
|
|
226
|
+
cloudModified: undefined,
|
|
227
|
+
keySet: !!process.env.LSH_SECRETS_KEY,
|
|
228
|
+
keyMatches: undefined,
|
|
229
|
+
suggestions: [],
|
|
230
|
+
};
|
|
231
|
+
// Check local file
|
|
232
|
+
if (fs.existsSync(envFilePath)) {
|
|
233
|
+
status.localExists = true;
|
|
234
|
+
const stat = fs.statSync(envFilePath);
|
|
235
|
+
status.localModified = stat.mtime;
|
|
236
|
+
const content = fs.readFileSync(envFilePath, 'utf8');
|
|
237
|
+
const env = this.parseEnvFile(content);
|
|
238
|
+
status.localKeys = Object.keys(env).length;
|
|
239
|
+
}
|
|
240
|
+
// Check cloud storage
|
|
241
|
+
try {
|
|
242
|
+
const jobs = await this.persistence.getActiveJobs();
|
|
243
|
+
const secretsJobs = jobs
|
|
244
|
+
.filter(j => j.command === 'secrets_sync' && j.job_id.includes(environment))
|
|
245
|
+
.sort((a, b) => new Date(b.started_at).getTime() - new Date(a.started_at).getTime());
|
|
246
|
+
if (secretsJobs.length > 0) {
|
|
247
|
+
status.cloudExists = true;
|
|
248
|
+
const latestSecret = secretsJobs[0];
|
|
249
|
+
status.cloudModified = new Date(latestSecret.completed_at || latestSecret.started_at);
|
|
250
|
+
// Try to decrypt to check if key matches
|
|
251
|
+
if (latestSecret.output) {
|
|
252
|
+
try {
|
|
253
|
+
const decrypted = this.decrypt(latestSecret.output);
|
|
254
|
+
const env = this.parseEnvFile(decrypted);
|
|
255
|
+
status.cloudKeys = Object.keys(env).length;
|
|
256
|
+
status.keyMatches = true;
|
|
257
|
+
}
|
|
258
|
+
catch (error) {
|
|
259
|
+
status.keyMatches = false;
|
|
260
|
+
}
|
|
261
|
+
}
|
|
262
|
+
}
|
|
263
|
+
}
|
|
264
|
+
catch (error) {
|
|
265
|
+
// Cloud check failed, likely no connection
|
|
266
|
+
}
|
|
267
|
+
return status;
|
|
268
|
+
}
|
|
269
|
+
/**
|
|
270
|
+
* Sync command - check status and suggest actions
|
|
271
|
+
*/
|
|
272
|
+
async sync(envFilePath = '.env', environment = 'dev') {
|
|
273
|
+
console.log(`\n🔍 Checking secrets status for environment: ${environment}\n`);
|
|
274
|
+
const status = await this.status(envFilePath, environment);
|
|
275
|
+
// Display status
|
|
276
|
+
console.log('📊 Status:');
|
|
277
|
+
console.log(` Encryption key set: ${status.keySet ? '✅' : '❌'}`);
|
|
278
|
+
console.log(` Local .env file: ${status.localExists ? `✅ (${status.localKeys} keys)` : '❌'}`);
|
|
279
|
+
console.log(` Cloud storage: ${status.cloudExists ? `✅ (${status.cloudKeys} keys)` : '❌'}`);
|
|
280
|
+
if (status.cloudExists && status.keyMatches !== undefined) {
|
|
281
|
+
console.log(` Key matches cloud: ${status.keyMatches ? '✅' : '❌'}`);
|
|
282
|
+
}
|
|
283
|
+
console.log();
|
|
284
|
+
// Generate suggestions
|
|
285
|
+
const suggestions = [];
|
|
286
|
+
if (!status.keySet) {
|
|
287
|
+
suggestions.push('⚠️ No encryption key set!');
|
|
288
|
+
suggestions.push(' Generate a key: lsh lib secrets key');
|
|
289
|
+
suggestions.push(' Add it to .env: LSH_SECRETS_KEY=<your-key>');
|
|
290
|
+
suggestions.push(' Load it: export $(cat .env | xargs)');
|
|
291
|
+
}
|
|
292
|
+
if (status.cloudExists && status.keyMatches === false) {
|
|
293
|
+
suggestions.push('⚠️ Encryption key does not match cloud storage!');
|
|
294
|
+
suggestions.push(' Either use the original key, or push new secrets:');
|
|
295
|
+
suggestions.push(` lsh lib secrets push -f ${envFilePath} -e ${environment}`);
|
|
296
|
+
}
|
|
297
|
+
if (!status.localExists && status.cloudExists && status.keyMatches) {
|
|
298
|
+
suggestions.push('💡 Cloud secrets available but no local file');
|
|
299
|
+
suggestions.push(` Pull from cloud: lsh lib secrets pull -f ${envFilePath} -e ${environment}`);
|
|
300
|
+
}
|
|
301
|
+
if (status.localExists && !status.cloudExists) {
|
|
302
|
+
suggestions.push('💡 Local .env exists but not in cloud');
|
|
303
|
+
suggestions.push(` Push to cloud: lsh lib secrets push -f ${envFilePath} -e ${environment}`);
|
|
304
|
+
}
|
|
305
|
+
if (status.localExists && status.cloudExists && status.keyMatches) {
|
|
306
|
+
if (status.localModified && status.cloudModified) {
|
|
307
|
+
const localNewer = status.localModified > status.cloudModified;
|
|
308
|
+
const timeDiff = Math.abs(status.localModified.getTime() - status.cloudModified.getTime());
|
|
309
|
+
const daysDiff = Math.floor(timeDiff / (1000 * 60 * 60 * 24));
|
|
310
|
+
if (localNewer && daysDiff > 0) {
|
|
311
|
+
suggestions.push('💡 Local file is newer than cloud');
|
|
312
|
+
suggestions.push(` Push to cloud: lsh lib secrets push -f ${envFilePath} -e ${environment}`);
|
|
313
|
+
}
|
|
314
|
+
else if (!localNewer && daysDiff > 0) {
|
|
315
|
+
suggestions.push('💡 Cloud is newer than local file');
|
|
316
|
+
suggestions.push(` Pull from cloud: lsh lib secrets pull -f ${envFilePath} -e ${environment}`);
|
|
317
|
+
}
|
|
318
|
+
else {
|
|
319
|
+
suggestions.push('✅ Local and cloud are in sync!');
|
|
320
|
+
}
|
|
321
|
+
}
|
|
322
|
+
}
|
|
323
|
+
// Show how to load secrets in current shell
|
|
324
|
+
if (status.localExists && status.keySet) {
|
|
325
|
+
suggestions.push('');
|
|
326
|
+
suggestions.push('📝 To load secrets in your current shell:');
|
|
327
|
+
suggestions.push(` export $(cat ${envFilePath} | grep -v '^#' | xargs)`);
|
|
328
|
+
suggestions.push('');
|
|
329
|
+
suggestions.push(' Or for safer loading (with quotes):');
|
|
330
|
+
suggestions.push(` set -a; source ${envFilePath}; set +a`);
|
|
331
|
+
suggestions.push('');
|
|
332
|
+
suggestions.push('💡 Add to your shell profile for auto-loading:');
|
|
333
|
+
suggestions.push(` echo "set -a; source ${path.resolve(envFilePath)}; set +a" >> ~/.zshrc`);
|
|
334
|
+
}
|
|
335
|
+
// Display suggestions
|
|
336
|
+
if (suggestions.length > 0) {
|
|
337
|
+
console.log('📋 Recommendations:\n');
|
|
338
|
+
suggestions.forEach(s => console.log(s));
|
|
339
|
+
console.log();
|
|
340
|
+
}
|
|
341
|
+
}
|
|
192
342
|
}
|
|
193
343
|
export default SecretsManager;
|
package/dist/services/lib/lib.js
CHANGED
|
@@ -66,7 +66,9 @@ async function _makeCommand(commander) {
|
|
|
66
66
|
// // });
|
|
67
67
|
// }
|
|
68
68
|
export async function init_lib(program) {
|
|
69
|
-
const lib = program
|
|
69
|
+
const lib = program
|
|
70
|
+
.command("lib")
|
|
71
|
+
.description("LSH library and service commands");
|
|
70
72
|
// Load and register dynamic commands
|
|
71
73
|
const commands = await loadCommands();
|
|
72
74
|
for (const commandName of Object.keys(commands)) {
|
|
@@ -3,6 +3,9 @@
|
|
|
3
3
|
* Sync .env files across development environments
|
|
4
4
|
*/
|
|
5
5
|
import SecretsManager from '../../lib/secrets-manager.js';
|
|
6
|
+
import * as fs from 'fs';
|
|
7
|
+
import * as path from 'path';
|
|
8
|
+
import * as readline from 'readline';
|
|
6
9
|
export async function init_secrets(program) {
|
|
7
10
|
const secretsCmd = program
|
|
8
11
|
.command('secrets')
|
|
@@ -29,10 +32,11 @@ export async function init_secrets(program) {
|
|
|
29
32
|
.description('Pull .env from encrypted cloud storage')
|
|
30
33
|
.option('-f, --file <path>', 'Path to .env file', '.env')
|
|
31
34
|
.option('-e, --env <name>', 'Environment name (dev/staging/prod)', 'dev')
|
|
35
|
+
.option('--force', 'Overwrite without creating backup')
|
|
32
36
|
.action(async (options) => {
|
|
33
37
|
try {
|
|
34
38
|
const manager = new SecretsManager();
|
|
35
|
-
await manager.pull(options.file, options.env);
|
|
39
|
+
await manager.pull(options.file, options.env, options.force);
|
|
36
40
|
}
|
|
37
41
|
catch (error) {
|
|
38
42
|
console.error('❌ Failed to pull secrets:', error.message);
|
|
@@ -41,12 +45,18 @@ export async function init_secrets(program) {
|
|
|
41
45
|
});
|
|
42
46
|
// List environments
|
|
43
47
|
secretsCmd
|
|
44
|
-
.command('list')
|
|
48
|
+
.command('list [environment]')
|
|
45
49
|
.alias('ls')
|
|
46
|
-
.description('List all stored environments')
|
|
47
|
-
.action(async () => {
|
|
50
|
+
.description('List all stored environments or show secrets for specific environment')
|
|
51
|
+
.action(async (environment) => {
|
|
48
52
|
try {
|
|
49
53
|
const manager = new SecretsManager();
|
|
54
|
+
// If environment specified, show secrets for that environment
|
|
55
|
+
if (environment) {
|
|
56
|
+
await manager.show(environment);
|
|
57
|
+
return;
|
|
58
|
+
}
|
|
59
|
+
// Otherwise, list all environments
|
|
50
60
|
const envs = await manager.listEnvironments();
|
|
51
61
|
if (envs.length === 0) {
|
|
52
62
|
console.log('No environments found. Push your first .env with: lsh secrets push');
|
|
@@ -90,5 +100,146 @@ export async function init_secrets(program) {
|
|
|
90
100
|
console.log('💡 Tip: Share this key securely with your team to sync secrets.');
|
|
91
101
|
console.log(' Never commit it to git!\n');
|
|
92
102
|
});
|
|
103
|
+
// Create .env file
|
|
104
|
+
secretsCmd
|
|
105
|
+
.command('create')
|
|
106
|
+
.description('Create a new .env file')
|
|
107
|
+
.option('-f, --file <path>', 'Path to .env file', '.env')
|
|
108
|
+
.option('-t, --template', 'Create with common template variables')
|
|
109
|
+
.action(async (options) => {
|
|
110
|
+
try {
|
|
111
|
+
const envPath = path.resolve(options.file);
|
|
112
|
+
// Check if file already exists
|
|
113
|
+
if (fs.existsSync(envPath)) {
|
|
114
|
+
console.log(`❌ File already exists: ${envPath}`);
|
|
115
|
+
console.log('💡 Use a different path or delete the existing file first.');
|
|
116
|
+
process.exit(1);
|
|
117
|
+
}
|
|
118
|
+
// Create template content if requested
|
|
119
|
+
let content = '';
|
|
120
|
+
if (options.template) {
|
|
121
|
+
content = `# Environment Configuration
|
|
122
|
+
# Generated by LSH Secrets Manager
|
|
123
|
+
|
|
124
|
+
# Application
|
|
125
|
+
NODE_ENV=development
|
|
126
|
+
PORT=3000
|
|
127
|
+
|
|
128
|
+
# Database
|
|
129
|
+
DATABASE_URL=
|
|
130
|
+
|
|
131
|
+
# API Keys
|
|
132
|
+
API_KEY=
|
|
133
|
+
|
|
134
|
+
# LSH Secrets (for cross-machine sync)
|
|
135
|
+
# LSH_SECRETS_KEY=
|
|
136
|
+
|
|
137
|
+
# Add your environment variables below
|
|
138
|
+
`;
|
|
139
|
+
}
|
|
140
|
+
// Create the file
|
|
141
|
+
fs.writeFileSync(envPath, content, 'utf8');
|
|
142
|
+
console.log(`✅ Created .env file: ${envPath}`);
|
|
143
|
+
if (options.template) {
|
|
144
|
+
console.log('📝 Template variables added - update with your values');
|
|
145
|
+
}
|
|
146
|
+
console.log('');
|
|
147
|
+
console.log('Next steps:');
|
|
148
|
+
console.log(` 1. Edit the file: ${options.file}`);
|
|
149
|
+
console.log(` 2. Push to cloud: lsh lib secrets push -f ${options.file}`);
|
|
150
|
+
console.log('');
|
|
151
|
+
}
|
|
152
|
+
catch (error) {
|
|
153
|
+
console.error('❌ Failed to create .env file:', error.message);
|
|
154
|
+
process.exit(1);
|
|
155
|
+
}
|
|
156
|
+
});
|
|
157
|
+
// Sync command - check status and suggest actions
|
|
158
|
+
secretsCmd
|
|
159
|
+
.command('sync')
|
|
160
|
+
.description('Check secrets sync status and show recommended actions')
|
|
161
|
+
.option('-f, --file <path>', 'Path to .env file', '.env')
|
|
162
|
+
.option('-e, --env <name>', 'Environment name', 'dev')
|
|
163
|
+
.action(async (options) => {
|
|
164
|
+
try {
|
|
165
|
+
const manager = new SecretsManager();
|
|
166
|
+
await manager.sync(options.file, options.env);
|
|
167
|
+
}
|
|
168
|
+
catch (error) {
|
|
169
|
+
console.error('❌ Failed to check sync status:', error.message);
|
|
170
|
+
process.exit(1);
|
|
171
|
+
}
|
|
172
|
+
});
|
|
173
|
+
// Status command - get detailed status info
|
|
174
|
+
secretsCmd
|
|
175
|
+
.command('status')
|
|
176
|
+
.description('Get detailed secrets status (JSON output)')
|
|
177
|
+
.option('-f, --file <path>', 'Path to .env file', '.env')
|
|
178
|
+
.option('-e, --env <name>', 'Environment name', 'dev')
|
|
179
|
+
.action(async (options) => {
|
|
180
|
+
try {
|
|
181
|
+
const manager = new SecretsManager();
|
|
182
|
+
const status = await manager.status(options.file, options.env);
|
|
183
|
+
console.log(JSON.stringify(status, null, 2));
|
|
184
|
+
}
|
|
185
|
+
catch (error) {
|
|
186
|
+
console.error('❌ Failed to get status:', error.message);
|
|
187
|
+
process.exit(1);
|
|
188
|
+
}
|
|
189
|
+
});
|
|
190
|
+
// Delete .env file with confirmation
|
|
191
|
+
secretsCmd
|
|
192
|
+
.command('delete')
|
|
193
|
+
.description('Delete .env file (requires confirmation)')
|
|
194
|
+
.option('-f, --file <path>', 'Path to .env file', '.env')
|
|
195
|
+
.option('-y, --yes', 'Skip confirmation prompt')
|
|
196
|
+
.action(async (options) => {
|
|
197
|
+
try {
|
|
198
|
+
const envPath = path.resolve(options.file);
|
|
199
|
+
// Check if file exists
|
|
200
|
+
if (!fs.existsSync(envPath)) {
|
|
201
|
+
console.log(`❌ File not found: ${envPath}`);
|
|
202
|
+
process.exit(1);
|
|
203
|
+
}
|
|
204
|
+
console.log('⚠️ WARNING: You are about to delete a .env file!');
|
|
205
|
+
console.log('');
|
|
206
|
+
console.log(`File: ${envPath}`);
|
|
207
|
+
console.log('');
|
|
208
|
+
// Skip confirmation if --yes flag is provided
|
|
209
|
+
if (!options.yes) {
|
|
210
|
+
console.log('To confirm deletion, please type the full path of the file:');
|
|
211
|
+
console.log(`Expected: ${envPath}`);
|
|
212
|
+
console.log('');
|
|
213
|
+
const rl = readline.createInterface({
|
|
214
|
+
input: process.stdin,
|
|
215
|
+
output: process.stdout,
|
|
216
|
+
});
|
|
217
|
+
const answer = await new Promise((resolve) => {
|
|
218
|
+
rl.question('Enter path to confirm: ', (ans) => {
|
|
219
|
+
rl.close();
|
|
220
|
+
resolve(ans.trim());
|
|
221
|
+
});
|
|
222
|
+
});
|
|
223
|
+
if (answer !== envPath) {
|
|
224
|
+
console.log('');
|
|
225
|
+
console.log('❌ Confirmation failed - path does not match');
|
|
226
|
+
console.log('Deletion cancelled');
|
|
227
|
+
process.exit(1);
|
|
228
|
+
}
|
|
229
|
+
}
|
|
230
|
+
// Delete the file
|
|
231
|
+
fs.unlinkSync(envPath);
|
|
232
|
+
console.log('');
|
|
233
|
+
console.log(`✅ Deleted: ${envPath}`);
|
|
234
|
+
console.log('');
|
|
235
|
+
console.log('💡 Tip: You can still pull from cloud if you pushed previously:');
|
|
236
|
+
console.log(` lsh lib secrets pull -f ${options.file}`);
|
|
237
|
+
console.log('');
|
|
238
|
+
}
|
|
239
|
+
catch (error) {
|
|
240
|
+
console.error('❌ Failed to delete .env file:', error.message);
|
|
241
|
+
process.exit(1);
|
|
242
|
+
}
|
|
243
|
+
});
|
|
93
244
|
}
|
|
94
245
|
export default init_secrets;
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "lsh-framework",
|
|
3
|
-
"version": "0.
|
|
4
|
-
"description": "
|
|
3
|
+
"version": "0.8.0",
|
|
4
|
+
"description": "Encrypted secrets manager with automatic rotation, team sync, and multi-environment support. Built on a powerful shell with daemon scheduling and CI/CD integration.",
|
|
5
5
|
"main": "dist/app.js",
|
|
6
6
|
"bin": {
|
|
7
7
|
"lsh": "./dist/cli.js"
|
|
@@ -36,21 +36,25 @@
|
|
|
36
36
|
"audit:security": "npm audit --audit-level moderate"
|
|
37
37
|
},
|
|
38
38
|
"keywords": [
|
|
39
|
-
"
|
|
39
|
+
"secrets-manager",
|
|
40
|
+
"secrets",
|
|
41
|
+
"env-manager",
|
|
42
|
+
"dotenv",
|
|
43
|
+
"environment-variables",
|
|
44
|
+
"encryption",
|
|
45
|
+
"credential-management",
|
|
46
|
+
"team-sync",
|
|
47
|
+
"secrets-rotation",
|
|
48
|
+
"multi-environment",
|
|
49
|
+
"devops",
|
|
50
|
+
"security",
|
|
40
51
|
"shell",
|
|
41
|
-
"
|
|
42
|
-
"job-manager",
|
|
52
|
+
"automation",
|
|
43
53
|
"cron",
|
|
44
54
|
"daemon",
|
|
45
|
-
"
|
|
46
|
-
"
|
|
47
|
-
"
|
|
48
|
-
"database-persistence",
|
|
49
|
-
"task-scheduler",
|
|
50
|
-
"command-line",
|
|
51
|
-
"automation",
|
|
52
|
-
"devops",
|
|
53
|
-
"cicd"
|
|
55
|
+
"job-scheduler",
|
|
56
|
+
"cicd",
|
|
57
|
+
"cli"
|
|
54
58
|
],
|
|
55
59
|
"engines": {
|
|
56
60
|
"node": ">=20.18.0",
|
|
@@ -100,15 +104,10 @@
|
|
|
100
104
|
"ink-text-input": "^5.0.1",
|
|
101
105
|
"inquirer": "^9.2.12",
|
|
102
106
|
"ioredis": "^5.8.0",
|
|
103
|
-
"jest": "^29.7.0",
|
|
104
107
|
"jsonwebtoken": "^9.0.2",
|
|
105
108
|
"lodash": "^4.17.21",
|
|
106
|
-
"mocha": "^10.3.0",
|
|
107
|
-
"ncc": "^0.3.6",
|
|
108
|
-
"nexe": "^4.0.0-rc.2",
|
|
109
109
|
"node-cron": "^3.0.3",
|
|
110
110
|
"node-fetch": "^3.3.2",
|
|
111
|
-
"nodemon": "^3.0.1",
|
|
112
111
|
"ora": "^8.0.1",
|
|
113
112
|
"path": "^0.12.7",
|
|
114
113
|
"pg": "^8.16.3",
|
|
@@ -118,7 +117,6 @@
|
|
|
118
117
|
"socket.io": "^4.8.1",
|
|
119
118
|
"uuid": "^10.0.0",
|
|
120
119
|
"xstate": "^5.9.1",
|
|
121
|
-
"zapier-platform-core": "15.4.1",
|
|
122
120
|
"zx": "^7.2.3"
|
|
123
121
|
},
|
|
124
122
|
"devDependencies": {
|
|
@@ -137,8 +135,14 @@
|
|
|
137
135
|
"eslint": "^9.36.0",
|
|
138
136
|
"eslint-plugin-react": "^7.37.5",
|
|
139
137
|
"eslint-plugin-react-hooks": "^5.2.0",
|
|
138
|
+
"jest": "^29.7.0",
|
|
139
|
+
"mocha": "^10.3.0",
|
|
140
|
+
"ncc": "^0.3.6",
|
|
141
|
+
"nexe": "^4.0.0-rc.2",
|
|
142
|
+
"nodemon": "^3.0.1",
|
|
140
143
|
"supertest": "^7.1.4",
|
|
141
|
-
"ts-jest": "^29.
|
|
142
|
-
"typescript": "^5.4.5"
|
|
144
|
+
"ts-jest": "^29.2.5",
|
|
145
|
+
"typescript": "^5.4.5",
|
|
146
|
+
"zapier-platform-core": "15.4.1"
|
|
143
147
|
}
|
|
144
148
|
}
|