lsh-framework 0.10.2 → 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 +97 -59
- package/dist/cli.js +27 -23
- package/dist/lib/job-manager.js +0 -1
- package/dist/lib/secrets-manager.js +6 -6
- package/dist/lib/zsh-compatibility.js +0 -1
- package/dist/pipeline/mcli-bridge.js +2 -1
- package/dist/services/lib/lib.js +11 -1
- package/dist/services/secrets/secrets.js +210 -38
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -32,7 +32,7 @@ npm install -g lsh-framework
|
|
|
32
32
|
|
|
33
33
|
# 3. ONE command does everything!
|
|
34
34
|
cd ~/repos/your-project
|
|
35
|
-
lsh
|
|
35
|
+
lsh sync
|
|
36
36
|
|
|
37
37
|
# That's it! Smart Sync:
|
|
38
38
|
# ✅ Auto-generates encryption key
|
|
@@ -46,7 +46,7 @@ lsh lib secrets sync
|
|
|
46
46
|
|
|
47
47
|
```bash
|
|
48
48
|
# Sync and load secrets into current shell
|
|
49
|
-
eval "$(lsh
|
|
49
|
+
eval "$(lsh sync --load)"
|
|
50
50
|
|
|
51
51
|
# Your secrets are now available!
|
|
52
52
|
echo $DATABASE_URL
|
|
@@ -59,7 +59,7 @@ echo $DATABASE_URL
|
|
|
59
59
|
npm install -g lsh-framework
|
|
60
60
|
|
|
61
61
|
# 2. Generate encryption key
|
|
62
|
-
lsh
|
|
62
|
+
lsh key
|
|
63
63
|
# Add the output to your .env:
|
|
64
64
|
# LSH_SECRETS_KEY=<your-key>
|
|
65
65
|
|
|
@@ -69,10 +69,10 @@ lsh lib secrets key
|
|
|
69
69
|
# SUPABASE_ANON_KEY=<your-anon-key>
|
|
70
70
|
|
|
71
71
|
# 4. Push your secrets
|
|
72
|
-
lsh
|
|
72
|
+
lsh push
|
|
73
73
|
|
|
74
74
|
# 5. Pull on any other machine
|
|
75
|
-
lsh
|
|
75
|
+
lsh pull
|
|
76
76
|
|
|
77
77
|
# Done! Your secrets are synced.
|
|
78
78
|
```
|
|
@@ -85,8 +85,8 @@ lsh lib secrets pull
|
|
|
85
85
|
|
|
86
86
|
```bash
|
|
87
87
|
cd ~/repos/my-app
|
|
88
|
-
lsh
|
|
89
|
-
eval "$(lsh
|
|
88
|
+
lsh sync # Auto-setup and sync
|
|
89
|
+
eval "$(lsh sync --load)" # Sync AND load into shell
|
|
90
90
|
```
|
|
91
91
|
|
|
92
92
|
What Smart Sync does automatically:
|
|
@@ -100,10 +100,10 @@ What Smart Sync does automatically:
|
|
|
100
100
|
**Repository Isolation:**
|
|
101
101
|
```bash
|
|
102
102
|
cd ~/repos/app1
|
|
103
|
-
lsh
|
|
103
|
+
lsh sync # Stored as: app1_dev
|
|
104
104
|
|
|
105
105
|
cd ~/repos/app2
|
|
106
|
-
lsh
|
|
106
|
+
lsh sync # Stored as: app2_dev (separate!)
|
|
107
107
|
```
|
|
108
108
|
|
|
109
109
|
No more conflicts between projects using the same environment names!
|
|
@@ -124,16 +124,16 @@ Use the built-in daemon to automatically rotate secrets on a schedule:
|
|
|
124
124
|
|
|
125
125
|
```bash
|
|
126
126
|
# Schedule API key rotation every 30 days
|
|
127
|
-
lsh
|
|
127
|
+
lsh cron add \
|
|
128
128
|
--name "rotate-api-keys" \
|
|
129
129
|
--schedule "0 0 1 * *" \
|
|
130
|
-
--command "./scripts/rotate-keys.sh && lsh
|
|
130
|
+
--command "./scripts/rotate-keys.sh && lsh push"
|
|
131
131
|
|
|
132
132
|
# Or use interval-based scheduling
|
|
133
|
-
lsh
|
|
133
|
+
lsh cron add \
|
|
134
134
|
--name "sync-secrets" \
|
|
135
135
|
--interval 3600 \
|
|
136
|
-
--command "lsh
|
|
136
|
+
--command "lsh pull && ./scripts/reload-app.sh"
|
|
137
137
|
```
|
|
138
138
|
|
|
139
139
|
**No other secrets manager has this built-in!** Most require complex integrations with cron or external tools.
|
|
@@ -143,8 +143,8 @@ lsh lib cron add \
|
|
|
143
143
|
**Setup (One Time):**
|
|
144
144
|
```bash
|
|
145
145
|
# Project lead:
|
|
146
|
-
lsh
|
|
147
|
-
lsh
|
|
146
|
+
lsh key # Generate shared key
|
|
147
|
+
lsh push --env prod # Push team secrets
|
|
148
148
|
# Share LSH_SECRETS_KEY via 1Password
|
|
149
149
|
```
|
|
150
150
|
|
|
@@ -155,7 +155,7 @@ lsh lib secrets push --env prod # Push team secrets
|
|
|
155
155
|
echo "LSH_SECRETS_KEY=<shared-key>" > .env
|
|
156
156
|
|
|
157
157
|
# 3. Pull secrets
|
|
158
|
-
lsh
|
|
158
|
+
lsh pull --env prod
|
|
159
159
|
|
|
160
160
|
# 4. Start coding!
|
|
161
161
|
npm start
|
|
@@ -165,31 +165,69 @@ npm start
|
|
|
165
165
|
|
|
166
166
|
```bash
|
|
167
167
|
# Development
|
|
168
|
-
lsh
|
|
168
|
+
lsh push --env dev
|
|
169
169
|
|
|
170
170
|
# Staging (different values)
|
|
171
|
-
lsh
|
|
171
|
+
lsh push --file .env.staging --env staging
|
|
172
172
|
|
|
173
173
|
# Production (super secret)
|
|
174
|
-
lsh
|
|
174
|
+
lsh push --file .env.production --env prod
|
|
175
175
|
|
|
176
176
|
# Pull whatever you need
|
|
177
|
-
lsh
|
|
178
|
-
lsh
|
|
179
|
-
lsh
|
|
177
|
+
lsh pull --env dev # for local dev
|
|
178
|
+
lsh pull --env staging # for testing
|
|
179
|
+
lsh pull --env prod # for production debugging
|
|
180
180
|
```
|
|
181
181
|
|
|
182
|
+
### 📝 Batch Upsert Secrets
|
|
183
|
+
|
|
184
|
+
**New in v1.1.0:** Pipe environment variables directly into your `.env` file!
|
|
185
|
+
|
|
186
|
+
```bash
|
|
187
|
+
# Copy all current environment variables
|
|
188
|
+
printenv | lsh set
|
|
189
|
+
|
|
190
|
+
# Import from another .env file
|
|
191
|
+
cat .env.backup | lsh set
|
|
192
|
+
|
|
193
|
+
# Import specific variables
|
|
194
|
+
printenv | grep "^AWS_" | lsh set
|
|
195
|
+
|
|
196
|
+
# Merge multiple sources
|
|
197
|
+
cat .env.base .env.local | lsh set
|
|
198
|
+
|
|
199
|
+
# From file with --stdin flag
|
|
200
|
+
lsh set --stdin < .env.production
|
|
201
|
+
|
|
202
|
+
# Single key-value still works
|
|
203
|
+
lsh set API_KEY sk_live_12345
|
|
204
|
+
lsh set DATABASE_URL postgres://localhost/db
|
|
205
|
+
```
|
|
206
|
+
|
|
207
|
+
**Features:**
|
|
208
|
+
- ✅ Automatic upsert (updates existing, adds new)
|
|
209
|
+
- ✅ Preserves comments and formatting
|
|
210
|
+
- ✅ Handles quoted values
|
|
211
|
+
- ✅ Validates key names
|
|
212
|
+
- ✅ Shows summary of changes
|
|
213
|
+
|
|
182
214
|
## Secrets Commands
|
|
183
215
|
|
|
184
216
|
| Command | Description |
|
|
185
217
|
|---------|-------------|
|
|
186
|
-
| `lsh
|
|
187
|
-
| `lsh
|
|
188
|
-
| `lsh
|
|
189
|
-
| `lsh
|
|
190
|
-
| `lsh
|
|
191
|
-
| `lsh
|
|
192
|
-
| `lsh
|
|
218
|
+
| `lsh push` | Upload .env to encrypted cloud storage |
|
|
219
|
+
| `lsh pull` | Download .env from cloud storage |
|
|
220
|
+
| `lsh list` | List secrets in current .env file |
|
|
221
|
+
| `lsh env` | List all stored environments |
|
|
222
|
+
| `lsh key` | Generate encryption key |
|
|
223
|
+
| `lsh create` | Create new .env file |
|
|
224
|
+
| `lsh delete` | Delete .env file (with confirmation) |
|
|
225
|
+
| `lsh sync` | Smart sync (auto-setup and sync) |
|
|
226
|
+
| `lsh status` | Get detailed secrets status |
|
|
227
|
+
| `lsh get <key>` | Get a specific secret value |
|
|
228
|
+
| `lsh set <key> <value>` | Set a single secret value |
|
|
229
|
+
| `printenv \| lsh set` | Batch upsert from stdin (pipe) |
|
|
230
|
+
| `lsh set --stdin < file` | Batch upsert from file |
|
|
193
231
|
|
|
194
232
|
See the complete guide: [SECRETS_GUIDE.md](docs/features/secrets/SECRETS_GUIDE.md)
|
|
195
233
|
|
|
@@ -217,7 +255,7 @@ lsh self version
|
|
|
217
255
|
|
|
218
256
|
```bash
|
|
219
257
|
# 1. Generate encryption key
|
|
220
|
-
lsh
|
|
258
|
+
lsh key
|
|
221
259
|
|
|
222
260
|
# 2. Create .env file
|
|
223
261
|
cat > .env <<EOF
|
|
@@ -234,7 +272,7 @@ API_KEY=your-api-key
|
|
|
234
272
|
EOF
|
|
235
273
|
|
|
236
274
|
# 3. Push to cloud
|
|
237
|
-
lsh
|
|
275
|
+
lsh push --env dev
|
|
238
276
|
```
|
|
239
277
|
|
|
240
278
|
## Advanced Features (Bonus!)
|
|
@@ -247,13 +285,13 @@ Run jobs reliably in the background:
|
|
|
247
285
|
|
|
248
286
|
```bash
|
|
249
287
|
# Start daemon
|
|
250
|
-
lsh
|
|
288
|
+
lsh daemon start
|
|
251
289
|
|
|
252
290
|
# Check status
|
|
253
|
-
lsh
|
|
291
|
+
lsh daemon status
|
|
254
292
|
|
|
255
293
|
# Stop daemon
|
|
256
|
-
lsh
|
|
294
|
+
lsh daemon stop
|
|
257
295
|
```
|
|
258
296
|
|
|
259
297
|
### Cron-Style Scheduling
|
|
@@ -262,20 +300,20 @@ Schedule any task with cron expressions:
|
|
|
262
300
|
|
|
263
301
|
```bash
|
|
264
302
|
# Daily backup at midnight
|
|
265
|
-
lsh
|
|
303
|
+
lsh cron add --name "backup" \
|
|
266
304
|
--schedule "0 0 * * *" \
|
|
267
305
|
--command "./backup.sh"
|
|
268
306
|
|
|
269
307
|
# Every 6 hours
|
|
270
|
-
lsh
|
|
308
|
+
lsh cron add --name "sync" \
|
|
271
309
|
--schedule "0 */6 * * *" \
|
|
272
|
-
--command "lsh
|
|
310
|
+
--command "lsh pull && ./reload.sh"
|
|
273
311
|
|
|
274
312
|
# List all jobs
|
|
275
|
-
lsh
|
|
313
|
+
lsh cron list
|
|
276
314
|
|
|
277
315
|
# Trigger manually
|
|
278
|
-
lsh
|
|
316
|
+
lsh cron trigger backup
|
|
279
317
|
```
|
|
280
318
|
|
|
281
319
|
### RESTful API
|
|
@@ -284,7 +322,7 @@ Control everything via HTTP API:
|
|
|
284
322
|
|
|
285
323
|
```bash
|
|
286
324
|
# Start API server
|
|
287
|
-
LSH_API_KEY=your-key lsh
|
|
325
|
+
LSH_API_KEY=your-key lsh api start --port 3030
|
|
288
326
|
|
|
289
327
|
# Use the API
|
|
290
328
|
curl -H "X-API-Key: your-key" http://localhost:3030/api/jobs
|
|
@@ -368,13 +406,13 @@ LSH validates all environment variables at startup and fails fast if:
|
|
|
368
406
|
**Solution:**
|
|
369
407
|
```bash
|
|
370
408
|
# Laptop
|
|
371
|
-
lsh
|
|
409
|
+
lsh push --env dev
|
|
372
410
|
|
|
373
411
|
# Desktop
|
|
374
|
-
lsh
|
|
412
|
+
lsh pull --env dev
|
|
375
413
|
|
|
376
414
|
# Cloud server
|
|
377
|
-
lsh
|
|
415
|
+
lsh pull --env dev
|
|
378
416
|
|
|
379
417
|
# All synced!
|
|
380
418
|
```
|
|
@@ -386,11 +424,11 @@ lsh lib secrets pull --env dev
|
|
|
386
424
|
**Solution:**
|
|
387
425
|
```bash
|
|
388
426
|
# New team member (after getting LSH_SECRETS_KEY from 1Password)
|
|
389
|
-
cd ~/projects/service-1 && lsh
|
|
390
|
-
cd ~/projects/service-2 && lsh
|
|
391
|
-
cd ~/projects/service-3 && lsh
|
|
392
|
-
cd ~/projects/service-4 && lsh
|
|
393
|
-
cd ~/projects/service-5 && lsh
|
|
427
|
+
cd ~/projects/service-1 && lsh pull --env dev
|
|
428
|
+
cd ~/projects/service-2 && lsh pull --env dev
|
|
429
|
+
cd ~/projects/service-3 && lsh pull --env dev
|
|
430
|
+
cd ~/projects/service-4 && lsh pull --env dev
|
|
431
|
+
cd ~/projects/service-5 && lsh pull --env dev
|
|
394
432
|
|
|
395
433
|
# Done in 30 seconds instead of 30 minutes
|
|
396
434
|
```
|
|
@@ -411,7 +449,7 @@ NEW_KEY=$(curl -X POST https://api.provider.com/keys/rotate)
|
|
|
411
449
|
sed -i "s/API_KEY=.*/API_KEY=$NEW_KEY/" .env
|
|
412
450
|
|
|
413
451
|
# Push to cloud
|
|
414
|
-
lsh
|
|
452
|
+
lsh push --env prod
|
|
415
453
|
|
|
416
454
|
# Notify team
|
|
417
455
|
echo "API keys rotated at $(date)" | mail -s "Key Rotation" team@company.com
|
|
@@ -430,18 +468,18 @@ lsh lib cron add --name "rotate-keys" \
|
|
|
430
468
|
**Solution:**
|
|
431
469
|
```bash
|
|
432
470
|
# Push from local dev
|
|
433
|
-
lsh
|
|
471
|
+
lsh push --file .env.development --env dev
|
|
434
472
|
|
|
435
473
|
# Push staging config
|
|
436
|
-
lsh
|
|
474
|
+
lsh push --file .env.staging --env staging
|
|
437
475
|
|
|
438
476
|
# Push production config (from secure machine only)
|
|
439
|
-
lsh
|
|
477
|
+
lsh push --file .env.production --env prod
|
|
440
478
|
|
|
441
479
|
# CI/CD pulls the right one
|
|
442
480
|
# In .github/workflows/deploy.yml:
|
|
443
481
|
- name: Get secrets
|
|
444
|
-
run: lsh
|
|
482
|
+
run: lsh pull --env ${{ github.ref == 'refs/heads/main' && 'prod' || 'staging' }}
|
|
445
483
|
```
|
|
446
484
|
|
|
447
485
|
## Comparison with Other Tools
|
|
@@ -504,7 +542,7 @@ LSH_ALLOW_DANGEROUS_COMMANDS=false # Keep false in production
|
|
|
504
542
|
|
|
505
543
|
```bash
|
|
506
544
|
# Encryption key for secrets
|
|
507
|
-
lsh
|
|
545
|
+
lsh key
|
|
508
546
|
|
|
509
547
|
# API key for HTTP API
|
|
510
548
|
openssl rand -hex 32
|
|
@@ -561,10 +599,10 @@ lsh --version
|
|
|
561
599
|
|
|
562
600
|
```bash
|
|
563
601
|
# Check stored environments
|
|
564
|
-
lsh
|
|
602
|
+
lsh list
|
|
565
603
|
|
|
566
604
|
# Push if missing
|
|
567
|
-
lsh
|
|
605
|
+
lsh push --env dev
|
|
568
606
|
```
|
|
569
607
|
|
|
570
608
|
### "Decryption failed"
|
|
@@ -574,8 +612,8 @@ lsh lib secrets push --env dev
|
|
|
574
612
|
# Make sure LSH_SECRETS_KEY matches the one used to encrypt
|
|
575
613
|
|
|
576
614
|
# Generate new key and re-push
|
|
577
|
-
lsh
|
|
578
|
-
lsh
|
|
615
|
+
lsh key
|
|
616
|
+
lsh push
|
|
579
617
|
```
|
|
580
618
|
|
|
581
619
|
### "Supabase not configured"
|
|
@@ -675,7 +713,7 @@ See API endpoints documentation in the Advanced Features section.
|
|
|
675
713
|
|
|
676
714
|
## Roadmap
|
|
677
715
|
|
|
678
|
-
- [ ] CLI command shortcuts (`lsh push` instead of `lsh
|
|
716
|
+
- [ ] CLI command shortcuts (`lsh push` instead of `lsh push`)
|
|
679
717
|
- [ ] Built-in secret rotation templates (AWS, GCP, Azure)
|
|
680
718
|
- [ ] Web dashboard for team secret management
|
|
681
719
|
- [ ] Audit logging for secret access
|
package/dist/cli.js
CHANGED
|
@@ -85,9 +85,9 @@ program
|
|
|
85
85
|
console.log(' status Get detailed secrets status');
|
|
86
86
|
console.log('');
|
|
87
87
|
console.log('🔄 Automation (Schedule secret rotation):');
|
|
88
|
-
console.log('
|
|
89
|
-
console.log('
|
|
90
|
-
console.log('
|
|
88
|
+
console.log(' cron add Schedule automatic tasks');
|
|
89
|
+
console.log(' cron list List scheduled jobs');
|
|
90
|
+
console.log(' daemon start Start persistent daemon');
|
|
91
91
|
console.log('');
|
|
92
92
|
console.log('🚀 Quick Start:');
|
|
93
93
|
console.log(' lsh key # Generate encryption key');
|
|
@@ -95,10 +95,10 @@ program
|
|
|
95
95
|
console.log(' lsh pull --env dev # Pull on another machine');
|
|
96
96
|
console.log('');
|
|
97
97
|
console.log('📚 More Commands:');
|
|
98
|
-
console.log('
|
|
99
|
-
console.log('
|
|
100
|
-
console.log('
|
|
101
|
-
console.log('
|
|
98
|
+
console.log(' api API server management');
|
|
99
|
+
console.log(' supabase Supabase database management');
|
|
100
|
+
console.log(' daemon Daemon management');
|
|
101
|
+
console.log(' cron Cron job management');
|
|
102
102
|
console.log(' self Self-management commands');
|
|
103
103
|
console.log(' self zsh ZSH compatibility commands');
|
|
104
104
|
console.log(' -i, --interactive Start interactive shell');
|
|
@@ -217,9 +217,13 @@ function findSimilarCommands(input, validCommands) {
|
|
|
217
217
|
(async () => {
|
|
218
218
|
// REPL interactive shell
|
|
219
219
|
await init_ishell(program);
|
|
220
|
-
//
|
|
220
|
+
// Flatten all service commands to top-level (no more 'lib' parent)
|
|
221
|
+
await init_supabase(program);
|
|
222
|
+
await init_daemon(program);
|
|
223
|
+
await init_cron(program);
|
|
224
|
+
registerApiCommands(program);
|
|
225
|
+
// Legacy 'lib' command group with deprecation warnings
|
|
221
226
|
const libCommand = await init_lib(program);
|
|
222
|
-
// Nest service commands under lib
|
|
223
227
|
await init_supabase(libCommand);
|
|
224
228
|
await init_daemon(libCommand);
|
|
225
229
|
await init_cron(libCommand);
|
|
@@ -596,13 +600,13 @@ function showDetailedHelp() {
|
|
|
596
600
|
console.log(' self zsh ZSH compatibility commands');
|
|
597
601
|
console.log(' self zsh-import Import ZSH configs (aliases, functions, exports)');
|
|
598
602
|
console.log('');
|
|
599
|
-
console.log('
|
|
600
|
-
console.log('
|
|
601
|
-
console.log('
|
|
602
|
-
console.log('
|
|
603
|
-
console.log('
|
|
604
|
-
console.log('
|
|
605
|
-
console.log('
|
|
603
|
+
console.log('Service Commands:');
|
|
604
|
+
console.log(' api API server management');
|
|
605
|
+
console.log(' supabase Supabase database management');
|
|
606
|
+
console.log(' daemon Daemon management');
|
|
607
|
+
console.log(' daemon job Job management');
|
|
608
|
+
console.log(' daemon db Database integration');
|
|
609
|
+
console.log(' cron Cron job management');
|
|
606
610
|
console.log('');
|
|
607
611
|
console.log('Examples:');
|
|
608
612
|
console.log('');
|
|
@@ -632,13 +636,13 @@ function showDetailedHelp() {
|
|
|
632
636
|
console.log(' lsh secrets pull # Pull secrets from cloud');
|
|
633
637
|
console.log(' lsh secrets list # List environments');
|
|
634
638
|
console.log('');
|
|
635
|
-
console.log('
|
|
636
|
-
console.log(' lsh
|
|
637
|
-
console.log(' lsh
|
|
638
|
-
console.log(' lsh
|
|
639
|
-
console.log(' lsh
|
|
640
|
-
console.log(' lsh
|
|
641
|
-
console.log(' lsh
|
|
639
|
+
console.log(' Service Operations:');
|
|
640
|
+
console.log(' lsh daemon start # Start daemon');
|
|
641
|
+
console.log(' lsh daemon status # Check daemon status');
|
|
642
|
+
console.log(' lsh daemon job list # List all jobs');
|
|
643
|
+
console.log(' lsh cron list # List cron jobs');
|
|
644
|
+
console.log(' lsh api start # Start API server');
|
|
645
|
+
console.log(' lsh api key # Generate API key');
|
|
642
646
|
console.log('');
|
|
643
647
|
console.log('Features:');
|
|
644
648
|
console.log(' ✅ POSIX Shell Compliance (85-95%)');
|
package/dist/lib/job-manager.js
CHANGED
|
@@ -462,8 +462,8 @@ export class SecretsManager {
|
|
|
462
462
|
return true;
|
|
463
463
|
}
|
|
464
464
|
catch (error) {
|
|
465
|
-
const
|
|
466
|
-
logger.error(`Failed to save encryption key: ${
|
|
465
|
+
const _err = error;
|
|
466
|
+
logger.error(`Failed to save encryption key: ${_err.message}`);
|
|
467
467
|
logger.info('Please set it manually:');
|
|
468
468
|
logger.info(`export LSH_SECRETS_KEY=${key}`);
|
|
469
469
|
return false;
|
|
@@ -499,8 +499,8 @@ LSH_SECRETS_KEY=${this.encryptionKey}
|
|
|
499
499
|
return true;
|
|
500
500
|
}
|
|
501
501
|
catch (error) {
|
|
502
|
-
const
|
|
503
|
-
logger.error(`Failed to create ${envFilePath}: ${
|
|
502
|
+
const _err = error;
|
|
503
|
+
logger.error(`Failed to create ${envFilePath}: ${_err.message}`);
|
|
504
504
|
return false;
|
|
505
505
|
}
|
|
506
506
|
}
|
|
@@ -517,8 +517,8 @@ LSH_SECRETS_KEY=${this.encryptionKey}
|
|
|
517
517
|
return true;
|
|
518
518
|
}
|
|
519
519
|
catch (error) {
|
|
520
|
-
const
|
|
521
|
-
logger.error(`Failed to create ${envFilePath}: ${
|
|
520
|
+
const _err = error;
|
|
521
|
+
logger.error(`Failed to create ${envFilePath}: ${_err.message}`);
|
|
522
522
|
return false;
|
|
523
523
|
}
|
|
524
524
|
}
|
|
@@ -330,7 +330,6 @@ export class ZshCompatibility {
|
|
|
330
330
|
/**
|
|
331
331
|
* Generate completions based on patterns
|
|
332
332
|
*/
|
|
333
|
-
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
334
333
|
async generateCompletions(context, patterns) {
|
|
335
334
|
const completions = [];
|
|
336
335
|
for (const pattern of patterns) {
|
|
@@ -170,10 +170,11 @@ export class MCLIBridge extends EventEmitter {
|
|
|
170
170
|
await this.jobTracker.completeExecution(execution.id, result, metrics, artifacts);
|
|
171
171
|
break;
|
|
172
172
|
case 'failed':
|
|
173
|
-
case 'error':
|
|
173
|
+
case 'error': {
|
|
174
174
|
const errorObj = error;
|
|
175
175
|
await this.jobTracker.failExecution(execution.id, errorObj?.message || 'Job failed in MCLI', error);
|
|
176
176
|
break;
|
|
177
|
+
}
|
|
177
178
|
case 'cancelled':
|
|
178
179
|
await this.jobTracker.updateJobStatus(pipelineJobId, JobStatus.CANCELLED);
|
|
179
180
|
break;
|
package/dist/services/lib/lib.js
CHANGED
|
@@ -68,7 +68,7 @@ async function _makeCommand(commander) {
|
|
|
68
68
|
export async function init_lib(program) {
|
|
69
69
|
const lib = program
|
|
70
70
|
.command("lib")
|
|
71
|
-
.description("
|
|
71
|
+
.description("⚠️ DEPRECATED: Use top-level commands instead (daemon, cron, api, supabase)");
|
|
72
72
|
// Load and register dynamic commands
|
|
73
73
|
const commands = await loadCommands();
|
|
74
74
|
for (const commandName of Object.keys(commands)) {
|
|
@@ -80,6 +80,16 @@ export async function init_lib(program) {
|
|
|
80
80
|
.description("commandName")
|
|
81
81
|
.usage(`${commandName} used as follows:`);
|
|
82
82
|
}
|
|
83
|
+
// Show deprecation warning when 'lib' is used
|
|
84
|
+
lib.hook('preAction', (_thisCommand, _actionCommand) => {
|
|
85
|
+
console.warn('\x1b[33m⚠️ WARNING: "lsh lib" commands are deprecated as of v1.0.0\x1b[0m');
|
|
86
|
+
console.warn('\x1b[33m Use top-level commands instead:\x1b[0m');
|
|
87
|
+
console.warn('\x1b[33m - lsh daemon (instead of lsh lib daemon)\x1b[0m');
|
|
88
|
+
console.warn('\x1b[33m - lsh cron (instead of lsh lib cron)\x1b[0m');
|
|
89
|
+
console.warn('\x1b[33m - lsh api (instead of lsh lib api)\x1b[0m');
|
|
90
|
+
console.warn('\x1b[33m - lsh supabase (instead of lsh lib supabase)\x1b[0m');
|
|
91
|
+
console.warn('');
|
|
92
|
+
});
|
|
83
93
|
// Optional: Enhance the 'lib' command group with additional descriptions and error handling
|
|
84
94
|
lib
|
|
85
95
|
.showHelpAfterError("Command not recognized, here's some help.")
|
|
@@ -351,52 +351,31 @@ API_KEY=
|
|
|
351
351
|
process.exit(1);
|
|
352
352
|
}
|
|
353
353
|
});
|
|
354
|
-
// Set a specific secret value
|
|
354
|
+
// Set a specific secret value or batch upsert from stdin
|
|
355
355
|
program
|
|
356
|
-
.command('set
|
|
357
|
-
.description('Set a specific secret value in .env file')
|
|
356
|
+
.command('set [key] [value]')
|
|
357
|
+
.description('Set a specific secret value in .env file, or batch upsert from stdin (KEY=VALUE format)')
|
|
358
358
|
.option('-f, --file <path>', 'Path to .env file', '.env')
|
|
359
|
+
.option('--stdin', 'Read KEY=VALUE pairs from stdin (one per line)')
|
|
359
360
|
.action(async (key, value, options) => {
|
|
360
361
|
try {
|
|
361
362
|
const envPath = path.resolve(options.file);
|
|
362
|
-
//
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
363
|
+
// Check if we should read from stdin
|
|
364
|
+
const isStdin = options.stdin || (!key && !value);
|
|
365
|
+
if (isStdin) {
|
|
366
|
+
// Batch mode: read from stdin
|
|
367
|
+
await batchSetSecrets(envPath);
|
|
366
368
|
}
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
if (line.trim().startsWith('#') || !line.trim()) {
|
|
375
|
-
newLines.push(line);
|
|
376
|
-
continue;
|
|
377
|
-
}
|
|
378
|
-
const match = line.match(/^([^=]+)=(.*)$/);
|
|
379
|
-
if (match && match[1].trim() === key) {
|
|
380
|
-
// Quote values with spaces or special characters
|
|
381
|
-
const needsQuotes = /[\s#]/.test(value);
|
|
382
|
-
const quotedValue = needsQuotes ? `"${value}"` : value;
|
|
383
|
-
newLines.push(`${key}=${quotedValue}`);
|
|
384
|
-
found = true;
|
|
385
|
-
}
|
|
386
|
-
else {
|
|
387
|
-
newLines.push(line);
|
|
388
|
-
}
|
|
369
|
+
else {
|
|
370
|
+
// Single mode: set one key-value pair
|
|
371
|
+
if (!key || value === undefined) {
|
|
372
|
+
console.error('❌ Usage: lsh set <key> <value>');
|
|
373
|
+
console.error(' Or pipe input: printenv | lsh set');
|
|
374
|
+
console.error(' Or use stdin: lsh set --stdin < file.env');
|
|
375
|
+
process.exit(1);
|
|
389
376
|
}
|
|
390
|
-
|
|
391
|
-
}
|
|
392
|
-
// If key wasn't found, append it
|
|
393
|
-
if (!found) {
|
|
394
|
-
const needsQuotes = /[\s#]/.test(value);
|
|
395
|
-
const quotedValue = needsQuotes ? `"${value}"` : value;
|
|
396
|
-
content = content.trimRight() + `\n${key}=${quotedValue}\n`;
|
|
377
|
+
await setSingleSecret(envPath, key, value);
|
|
397
378
|
}
|
|
398
|
-
fs.writeFileSync(envPath, content, 'utf8');
|
|
399
|
-
console.log(`✅ Set ${key} in ${options.file}`);
|
|
400
379
|
}
|
|
401
380
|
catch (error) {
|
|
402
381
|
const err = error;
|
|
@@ -404,6 +383,199 @@ API_KEY=
|
|
|
404
383
|
process.exit(1);
|
|
405
384
|
}
|
|
406
385
|
});
|
|
386
|
+
/**
|
|
387
|
+
* Set a single secret value
|
|
388
|
+
*/
|
|
389
|
+
async function setSingleSecret(envPath, key, value) {
|
|
390
|
+
// Validate key format
|
|
391
|
+
if (!/^[A-Za-z_][A-Za-z0-9_]*$/.test(key)) {
|
|
392
|
+
console.error(`❌ Invalid key format: ${key}. Must be a valid environment variable name.`);
|
|
393
|
+
process.exit(1);
|
|
394
|
+
}
|
|
395
|
+
let content = '';
|
|
396
|
+
let found = false;
|
|
397
|
+
if (fs.existsSync(envPath)) {
|
|
398
|
+
content = fs.readFileSync(envPath, 'utf8');
|
|
399
|
+
const lines = content.split('\n');
|
|
400
|
+
const newLines = [];
|
|
401
|
+
for (const line of lines) {
|
|
402
|
+
if (line.trim().startsWith('#') || !line.trim()) {
|
|
403
|
+
newLines.push(line);
|
|
404
|
+
continue;
|
|
405
|
+
}
|
|
406
|
+
const match = line.match(/^([^=]+)=(.*)$/);
|
|
407
|
+
if (match && match[1].trim() === key) {
|
|
408
|
+
// Quote values with spaces or special characters
|
|
409
|
+
const needsQuotes = /[\s#]/.test(value);
|
|
410
|
+
const quotedValue = needsQuotes ? `"${value}"` : value;
|
|
411
|
+
newLines.push(`${key}=${quotedValue}`);
|
|
412
|
+
found = true;
|
|
413
|
+
}
|
|
414
|
+
else {
|
|
415
|
+
newLines.push(line);
|
|
416
|
+
}
|
|
417
|
+
}
|
|
418
|
+
content = newLines.join('\n');
|
|
419
|
+
}
|
|
420
|
+
// If key wasn't found, append it
|
|
421
|
+
if (!found) {
|
|
422
|
+
const needsQuotes = /[\s#]/.test(value);
|
|
423
|
+
const quotedValue = needsQuotes ? `"${value}"` : value;
|
|
424
|
+
content = content.trimRight() + `\n${key}=${quotedValue}\n`;
|
|
425
|
+
}
|
|
426
|
+
fs.writeFileSync(envPath, content, 'utf8');
|
|
427
|
+
console.log(`✅ Set ${key}`);
|
|
428
|
+
}
|
|
429
|
+
/**
|
|
430
|
+
* Batch upsert secrets from stdin
|
|
431
|
+
*/
|
|
432
|
+
async function batchSetSecrets(envPath) {
|
|
433
|
+
return new Promise((resolve, reject) => {
|
|
434
|
+
let inputData = '';
|
|
435
|
+
const stdin = process.stdin;
|
|
436
|
+
// Check if stdin is a TTY (interactive terminal)
|
|
437
|
+
if (stdin.isTTY) {
|
|
438
|
+
console.error('❌ No input provided. Please pipe data or use --stdin flag.');
|
|
439
|
+
console.error('');
|
|
440
|
+
console.error('Examples:');
|
|
441
|
+
console.error(' printenv | lsh set');
|
|
442
|
+
console.error(' lsh set --stdin < .env.backup');
|
|
443
|
+
console.error(' echo "API_KEY=secret123" | lsh set');
|
|
444
|
+
process.exit(1);
|
|
445
|
+
}
|
|
446
|
+
stdin.setEncoding('utf8');
|
|
447
|
+
stdin.on('data', (chunk) => {
|
|
448
|
+
inputData += chunk;
|
|
449
|
+
});
|
|
450
|
+
stdin.on('end', () => {
|
|
451
|
+
try {
|
|
452
|
+
const lines = inputData.split('\n').filter(line => line.trim());
|
|
453
|
+
if (lines.length === 0) {
|
|
454
|
+
console.error('❌ No valid KEY=VALUE pairs found in input');
|
|
455
|
+
process.exit(1);
|
|
456
|
+
}
|
|
457
|
+
// Read existing .env file
|
|
458
|
+
let content = '';
|
|
459
|
+
const existingKeys = new Map();
|
|
460
|
+
if (fs.existsSync(envPath)) {
|
|
461
|
+
content = fs.readFileSync(envPath, 'utf8');
|
|
462
|
+
const existingLines = content.split('\n');
|
|
463
|
+
for (const line of existingLines) {
|
|
464
|
+
if (line.trim().startsWith('#') || !line.trim())
|
|
465
|
+
continue;
|
|
466
|
+
const match = line.match(/^([^=]+)=(.*)$/);
|
|
467
|
+
if (match) {
|
|
468
|
+
existingKeys.set(match[1].trim(), line);
|
|
469
|
+
}
|
|
470
|
+
}
|
|
471
|
+
}
|
|
472
|
+
const updates = [];
|
|
473
|
+
const errors = [];
|
|
474
|
+
const newKeys = new Map();
|
|
475
|
+
// Parse input lines
|
|
476
|
+
for (const line of lines) {
|
|
477
|
+
const trimmed = line.trim();
|
|
478
|
+
// Skip comments and empty lines
|
|
479
|
+
if (trimmed.startsWith('#') || !trimmed)
|
|
480
|
+
continue;
|
|
481
|
+
// Parse KEY=VALUE format
|
|
482
|
+
const match = trimmed.match(/^([^=]+)=(.*)$/);
|
|
483
|
+
if (!match) {
|
|
484
|
+
errors.push(`Invalid format: ${trimmed}`);
|
|
485
|
+
continue;
|
|
486
|
+
}
|
|
487
|
+
const key = match[1].trim();
|
|
488
|
+
let value = match[2].trim();
|
|
489
|
+
// Validate key format
|
|
490
|
+
if (!/^[A-Za-z_][A-Za-z0-9_]*$/.test(key)) {
|
|
491
|
+
errors.push(`Invalid key format: ${key}`);
|
|
492
|
+
continue;
|
|
493
|
+
}
|
|
494
|
+
// Remove surrounding quotes if present
|
|
495
|
+
if ((value.startsWith('"') && value.endsWith('"')) ||
|
|
496
|
+
(value.startsWith("'") && value.endsWith("'"))) {
|
|
497
|
+
value = value.slice(1, -1);
|
|
498
|
+
}
|
|
499
|
+
// Track if this is an update or addition
|
|
500
|
+
const action = existingKeys.has(key) ? 'updated' : 'added';
|
|
501
|
+
updates.push({ key, value, action });
|
|
502
|
+
newKeys.set(key, value);
|
|
503
|
+
}
|
|
504
|
+
// Build new content
|
|
505
|
+
const newLines = [];
|
|
506
|
+
let hasContent = false;
|
|
507
|
+
if (fs.existsSync(envPath)) {
|
|
508
|
+
const existingLines = content.split('\n');
|
|
509
|
+
for (const line of existingLines) {
|
|
510
|
+
if (line.trim().startsWith('#') || !line.trim()) {
|
|
511
|
+
newLines.push(line);
|
|
512
|
+
continue;
|
|
513
|
+
}
|
|
514
|
+
const match = line.match(/^([^=]+)=(.*)$/);
|
|
515
|
+
if (match) {
|
|
516
|
+
const key = match[1].trim();
|
|
517
|
+
if (newKeys.has(key)) {
|
|
518
|
+
// Update existing key
|
|
519
|
+
const value = newKeys.get(key);
|
|
520
|
+
const needsQuotes = /[\s#]/.test(value);
|
|
521
|
+
const quotedValue = needsQuotes ? `"${value}"` : value;
|
|
522
|
+
newLines.push(`${key}=${quotedValue}`);
|
|
523
|
+
newKeys.delete(key); // Mark as processed
|
|
524
|
+
hasContent = true;
|
|
525
|
+
}
|
|
526
|
+
else {
|
|
527
|
+
// Keep existing line
|
|
528
|
+
newLines.push(line);
|
|
529
|
+
hasContent = true;
|
|
530
|
+
}
|
|
531
|
+
}
|
|
532
|
+
else {
|
|
533
|
+
newLines.push(line);
|
|
534
|
+
}
|
|
535
|
+
}
|
|
536
|
+
}
|
|
537
|
+
// Add new keys that weren't in the existing file
|
|
538
|
+
for (const [key, value] of newKeys.entries()) {
|
|
539
|
+
const needsQuotes = /[\s#]/.test(value);
|
|
540
|
+
const quotedValue = needsQuotes ? `"${value}"` : value;
|
|
541
|
+
if (hasContent) {
|
|
542
|
+
newLines.push(`${key}=${quotedValue}`);
|
|
543
|
+
}
|
|
544
|
+
else {
|
|
545
|
+
newLines.push(`${key}=${quotedValue}`);
|
|
546
|
+
hasContent = true;
|
|
547
|
+
}
|
|
548
|
+
}
|
|
549
|
+
// Write updated content
|
|
550
|
+
let finalContent = newLines.join('\n');
|
|
551
|
+
if (hasContent && !finalContent.endsWith('\n')) {
|
|
552
|
+
finalContent += '\n';
|
|
553
|
+
}
|
|
554
|
+
fs.writeFileSync(envPath, finalContent, 'utf8');
|
|
555
|
+
// Report results
|
|
556
|
+
const added = updates.filter(u => u.action === 'added').length;
|
|
557
|
+
const updated = updates.filter(u => u.action === 'updated').length;
|
|
558
|
+
console.log(`✅ Batch upsert complete:`);
|
|
559
|
+
if (added > 0)
|
|
560
|
+
console.log(` Added: ${added} key(s)`);
|
|
561
|
+
if (updated > 0)
|
|
562
|
+
console.log(` Updated: ${updated} key(s)`);
|
|
563
|
+
if (errors.length > 0) {
|
|
564
|
+
console.log('');
|
|
565
|
+
console.log('⚠️ Skipped invalid entries:');
|
|
566
|
+
errors.forEach(err => console.log(` ${err}`));
|
|
567
|
+
}
|
|
568
|
+
resolve();
|
|
569
|
+
}
|
|
570
|
+
catch (error) {
|
|
571
|
+
reject(error);
|
|
572
|
+
}
|
|
573
|
+
});
|
|
574
|
+
stdin.on('error', (error) => {
|
|
575
|
+
reject(error);
|
|
576
|
+
});
|
|
577
|
+
});
|
|
578
|
+
}
|
|
407
579
|
// Delete .env file with confirmation
|
|
408
580
|
program
|
|
409
581
|
.command('delete')
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "lsh-framework",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "1.1.0",
|
|
4
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": {
|