lsh-framework 3.1.8 → 3.1.14

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/dist/cli.js CHANGED
@@ -11,8 +11,9 @@ import { registerCompletionCommands } from './commands/completion.js';
11
11
  import { registerConfigCommands } from './commands/config.js';
12
12
  import { registerSyncHistoryCommands } from './commands/sync-history.js';
13
13
  import { registerIPFSCommands } from './commands/ipfs.js';
14
+ import { registerSyncCommands } from './commands/sync.js';
14
15
  import { registerMigrateCommand } from './commands/migrate.js';
15
- import { registerStorachaCommands } from './commands/storacha.js';
16
+ import { registerContextCommand } from './commands/context.js';
16
17
  import { init_daemon } from './services/daemon/daemon.js';
17
18
  import { init_supabase } from './services/supabase/supabase.js';
18
19
  import { init_cron } from './services/cron/cron.js';
@@ -148,8 +149,9 @@ function findSimilarCommands(input, validCommands) {
148
149
  registerConfigCommands(program);
149
150
  registerSyncHistoryCommands(program);
150
151
  registerIPFSCommands(program);
152
+ registerSyncCommands(program);
151
153
  registerMigrateCommand(program);
152
- registerStorachaCommands(program);
154
+ registerContextCommand(program);
153
155
  // Secrets management (primary feature)
154
156
  await init_secrets(program);
155
157
  // Supporting services
@@ -0,0 +1,578 @@
1
+ /**
2
+ * Context Command - Comprehensive ML/Agent-Friendly Documentation
3
+ *
4
+ * Provides thorough usage information in machine-readable format
5
+ * for ML models and automation agents to quickly build context.
6
+ */
7
+ import * as fs from 'fs';
8
+ import * as path from 'path';
9
+ import { fileURLToPath } from 'url';
10
+ const __filename = fileURLToPath(import.meta.url);
11
+ const __dirname = path.dirname(__filename);
12
+ /**
13
+ * Get current version from package.json
14
+ */
15
+ function getVersion() {
16
+ try {
17
+ const packageJsonPath = path.join(__dirname, '../../package.json');
18
+ const packageJson = JSON.parse(fs.readFileSync(packageJsonPath, 'utf-8'));
19
+ return packageJson.version || 'unknown';
20
+ }
21
+ catch {
22
+ return 'unknown';
23
+ }
24
+ }
25
+ /**
26
+ * Build comprehensive context data structure
27
+ */
28
+ function buildContextData() {
29
+ return {
30
+ tool: {
31
+ name: 'lsh',
32
+ package: 'lsh-framework',
33
+ version: getVersion(),
34
+ purpose: 'Encrypted secrets manager for .env files with multi-host sync via native IPFS',
35
+ repository: 'https://github.com/gwicho38/lsh',
36
+ },
37
+ quickStart: [
38
+ 'npm install -g lsh-framework',
39
+ 'lsh init # Interactive setup (first time)',
40
+ 'lsh doctor # Verify configuration',
41
+ 'lsh push # Push secrets to cloud',
42
+ 'lsh pull # Pull secrets from cloud',
43
+ ],
44
+ commands: {
45
+ setup: [
46
+ {
47
+ command: 'lsh init',
48
+ description: 'Interactive setup wizard for first-time configuration',
49
+ examples: ['lsh init'],
50
+ notes: ['Guides through encryption key generation and IPFS setup'],
51
+ },
52
+ {
53
+ command: 'lsh key',
54
+ description: 'Generate a new AES-256 encryption key',
55
+ examples: ['lsh key', 'lsh key >> .env'],
56
+ notes: ['Outputs LSH_SECRETS_KEY=<64-char-hex>', 'Add to .env or shell profile'],
57
+ },
58
+ {
59
+ command: 'lsh doctor',
60
+ description: 'Diagnose configuration and connectivity issues',
61
+ examples: ['lsh doctor'],
62
+ notes: ['Checks: encryption key, IPFS daemon, network, local files'],
63
+ },
64
+ ],
65
+ secrets: [
66
+ {
67
+ command: 'lsh push',
68
+ description: 'Upload encrypted .env to cloud storage',
69
+ options: [
70
+ { flag: '-f, --file <path>', description: 'Path to .env file', default: '.env' },
71
+ { flag: '-e, --env <name>', description: 'Environment name', default: 'dev' },
72
+ { flag: '-g, --global', description: 'Use global workspace ($HOME)' },
73
+ { flag: '--force', description: 'Force push even with destructive changes' },
74
+ ],
75
+ examples: [
76
+ 'lsh push',
77
+ 'lsh push --env prod',
78
+ 'lsh push --file .env.staging --env staging',
79
+ 'lsh push --global',
80
+ ],
81
+ },
82
+ {
83
+ command: 'lsh pull',
84
+ description: 'Download .env from cloud storage',
85
+ options: [
86
+ { flag: '-f, --file <path>', description: 'Path to .env file', default: '.env' },
87
+ { flag: '-e, --env <name>', description: 'Environment name', default: 'dev' },
88
+ { flag: '-g, --global', description: 'Use global workspace ($HOME)' },
89
+ { flag: '--force', description: 'Overwrite without backup' },
90
+ ],
91
+ examples: [
92
+ 'lsh pull',
93
+ 'lsh pull --env prod',
94
+ 'lsh pull --file .env.local --env local',
95
+ ],
96
+ },
97
+ {
98
+ command: 'lsh sync',
99
+ description: 'Smart sync - check status and auto-resolve differences',
100
+ examples: ['lsh sync'],
101
+ notes: ['Compares local and cloud, suggests or auto-performs sync'],
102
+ },
103
+ {
104
+ command: 'lsh list',
105
+ description: 'List secrets in local .env file',
106
+ options: [
107
+ { flag: '-f, --file <path>', description: 'Path to .env file', default: '.env' },
108
+ { flag: '--format <type>', description: 'Output format: env|json|yaml|toml|export', default: 'env' },
109
+ { flag: '--keys-only', description: 'Show only keys, not values' },
110
+ { flag: '--no-mask', description: 'Show full values (not masked)' },
111
+ ],
112
+ examples: [
113
+ 'lsh list',
114
+ 'lsh list --format json',
115
+ 'lsh list --format export',
116
+ 'eval "$(lsh list --format export)"',
117
+ ],
118
+ },
119
+ {
120
+ command: 'lsh env',
121
+ description: 'List cloud environments or view specific environment',
122
+ examples: ['lsh env', 'lsh env prod'],
123
+ },
124
+ {
125
+ command: 'lsh status',
126
+ description: 'Show detailed sync status report',
127
+ examples: ['lsh status'],
128
+ notes: ['Shows local hash, cloud hash, sync state, last sync time'],
129
+ },
130
+ ],
131
+ operations: [
132
+ {
133
+ command: 'lsh get <key>',
134
+ description: 'Get a specific secret value',
135
+ options: [
136
+ { flag: '--all', description: 'Get all secrets as key=value pairs' },
137
+ { flag: '--no-mask', description: 'Show unmasked value' },
138
+ ],
139
+ examples: [
140
+ 'lsh get API_KEY',
141
+ 'lsh get API_KEY --no-mask',
142
+ 'lsh get --all',
143
+ 'API_KEY=$(lsh get API_KEY --no-mask)',
144
+ ],
145
+ },
146
+ {
147
+ command: 'lsh set <key> <value>',
148
+ description: 'Set a specific secret value',
149
+ examples: [
150
+ 'lsh set API_KEY sk-abc123',
151
+ 'lsh set DATABASE_URL "postgres://user:pass@host/db"',
152
+ 'printenv | lsh set # Batch import from stdin',
153
+ ],
154
+ },
155
+ {
156
+ command: 'lsh load',
157
+ description: 'Auto-load secrets into current shell environment',
158
+ examples: [
159
+ 'lsh load',
160
+ 'source <(lsh load)',
161
+ ],
162
+ notes: ['Outputs export statements for sourcing'],
163
+ },
164
+ {
165
+ command: 'lsh create',
166
+ description: 'Create a new .env file',
167
+ examples: ['lsh create'],
168
+ },
169
+ {
170
+ command: 'lsh delete',
171
+ description: 'Delete .env file',
172
+ examples: ['lsh delete'],
173
+ notes: ['Prompts for confirmation'],
174
+ },
175
+ ],
176
+ automation: [
177
+ {
178
+ command: 'lsh daemon start',
179
+ description: 'Start persistent background daemon',
180
+ examples: ['lsh daemon start'],
181
+ notes: ['Required for cron jobs and scheduled tasks'],
182
+ },
183
+ {
184
+ command: 'lsh daemon stop',
185
+ description: 'Stop the background daemon',
186
+ examples: ['lsh daemon stop'],
187
+ },
188
+ {
189
+ command: 'lsh daemon status',
190
+ description: 'Check daemon status',
191
+ examples: ['lsh daemon status'],
192
+ },
193
+ {
194
+ command: 'lsh cron add',
195
+ description: 'Add a scheduled job',
196
+ options: [
197
+ { flag: '--name <name>', description: 'Job name (required)' },
198
+ { flag: '--schedule <cron>', description: 'Cron expression (required)' },
199
+ { flag: '--command <cmd>', description: 'Command to execute (required)' },
200
+ ],
201
+ examples: [
202
+ 'lsh cron add --name "daily-backup" --schedule "0 0 * * *" --command "lsh push"',
203
+ 'lsh cron add --name "monthly-rotate" --schedule "0 0 1 * *" --command "./rotate-keys.sh && lsh push --force"',
204
+ ],
205
+ },
206
+ {
207
+ command: 'lsh cron list',
208
+ description: 'List all scheduled jobs',
209
+ examples: ['lsh cron list'],
210
+ },
211
+ {
212
+ command: 'lsh cron remove <name>',
213
+ description: 'Remove a scheduled job',
214
+ examples: ['lsh cron remove daily-backup'],
215
+ },
216
+ ],
217
+ config: [
218
+ {
219
+ command: 'lsh config show',
220
+ description: 'Show current configuration',
221
+ examples: ['lsh config show'],
222
+ },
223
+ {
224
+ command: 'lsh config get <key>',
225
+ description: 'Get a configuration value',
226
+ examples: ['lsh config get default_env'],
227
+ },
228
+ {
229
+ command: 'lsh config set <key> <value>',
230
+ description: 'Set a configuration value',
231
+ examples: ['lsh config set default_env prod'],
232
+ },
233
+ ],
234
+ backend: [
235
+ {
236
+ command: 'lsh sync',
237
+ description: 'IPFS sync commands (push/pull secrets via native IPFS)',
238
+ examples: ['lsh sync'],
239
+ notes: ['Zero-config', 'No auth required', 'Share CIDs with teammates'],
240
+ },
241
+ {
242
+ command: 'lsh sync init',
243
+ description: 'Initialize IPFS daemon for sync',
244
+ examples: ['lsh sync init'],
245
+ notes: ['Guides through IPFS installation and startup'],
246
+ },
247
+ {
248
+ command: 'lsh sync push',
249
+ description: 'Push encrypted secrets to IPFS, returns CID',
250
+ examples: ['lsh sync push', 'lsh sync push -f .env.prod'],
251
+ notes: ['Share CID with teammates to pull secrets'],
252
+ },
253
+ {
254
+ command: 'lsh sync pull <cid>',
255
+ description: 'Pull secrets from IPFS by CID',
256
+ examples: ['lsh sync pull Qm...'],
257
+ notes: ['Requires same LSH_SECRETS_KEY used to push'],
258
+ },
259
+ {
260
+ command: 'lsh ipfs status',
261
+ description: 'Check IPFS daemon status',
262
+ examples: ['lsh ipfs status'],
263
+ },
264
+ ],
265
+ self: [
266
+ {
267
+ command: 'lsh self update',
268
+ description: 'Update to latest version from npm',
269
+ options: [
270
+ { flag: '--check', description: 'Only check, do not install' },
271
+ { flag: '-y, --yes', description: 'Skip confirmation prompt' },
272
+ ],
273
+ examples: ['lsh self update', 'lsh self update --check'],
274
+ },
275
+ {
276
+ command: 'lsh self version',
277
+ description: 'Show detailed version information',
278
+ examples: ['lsh self version'],
279
+ },
280
+ {
281
+ command: 'lsh self info',
282
+ description: 'Show installation and environment info',
283
+ examples: ['lsh self info'],
284
+ },
285
+ {
286
+ command: 'lsh --help',
287
+ description: 'Show help',
288
+ examples: ['lsh --help', 'lsh push --help'],
289
+ },
290
+ {
291
+ command: 'lsh --version',
292
+ description: 'Show version',
293
+ examples: ['lsh --version'],
294
+ },
295
+ {
296
+ command: 'lsh context',
297
+ description: 'Show this comprehensive ML/agent context',
298
+ options: [
299
+ { flag: '--json', description: 'Output as JSON for parsing' },
300
+ ],
301
+ examples: ['lsh context', 'lsh context --json'],
302
+ },
303
+ ],
304
+ },
305
+ environment: {
306
+ required: [
307
+ {
308
+ name: 'LSH_SECRETS_KEY',
309
+ description: 'AES-256 encryption key (64 hex characters)',
310
+ example: 'a1b2c3d4e5f6...64chars',
311
+ },
312
+ ],
313
+ optional: [
314
+ {
315
+ name: 'SUPABASE_URL',
316
+ description: 'Supabase project URL (alternative backend)',
317
+ },
318
+ {
319
+ name: 'SUPABASE_ANON_KEY',
320
+ description: 'Supabase anonymous key',
321
+ },
322
+ {
323
+ name: 'LSH_API_ENABLED',
324
+ description: 'Enable REST API server',
325
+ default: 'false',
326
+ },
327
+ {
328
+ name: 'LSH_API_PORT',
329
+ description: 'API server port',
330
+ default: '3030',
331
+ },
332
+ {
333
+ name: 'LSH_API_KEY',
334
+ description: 'API authentication key',
335
+ },
336
+ ],
337
+ },
338
+ files: [
339
+ { path: '.env', description: 'Local environment file (default target)' },
340
+ { path: '~/.config/lsh/lshrc', description: 'Global LSH configuration' },
341
+ { path: '~/.lsh/secrets-cache/', description: 'Encrypted secrets cache' },
342
+ { path: '~/.lsh/secrets-metadata.json', description: 'Sync metadata index' },
343
+ ],
344
+ patterns: [
345
+ {
346
+ name: 'First-Time Setup',
347
+ description: 'Initial configuration on a new machine',
348
+ commands: [
349
+ 'npm install -g lsh-framework',
350
+ 'lsh init',
351
+ 'lsh doctor',
352
+ ],
353
+ },
354
+ {
355
+ name: 'Daily Workflow',
356
+ description: 'Typical developer daily usage',
357
+ commands: [
358
+ 'cd ~/project',
359
+ 'lsh pull',
360
+ '# ... work ...',
361
+ 'lsh push',
362
+ ],
363
+ },
364
+ {
365
+ name: 'Team Onboarding',
366
+ description: 'Add new team member with shared secrets',
367
+ commands: [
368
+ '# Get LSH_SECRETS_KEY from teammate',
369
+ 'export LSH_SECRETS_KEY=<shared-key>',
370
+ 'lsh sync pull <cid-from-teammate>',
371
+ ],
372
+ },
373
+ {
374
+ name: 'CI/CD Integration',
375
+ description: 'Use secrets in CI pipeline',
376
+ commands: [
377
+ 'export LSH_SECRETS_KEY=${{ secrets.LSH_KEY }}',
378
+ 'lsh pull --env $ENV',
379
+ 'source <(lsh list --format export)',
380
+ ],
381
+ },
382
+ {
383
+ name: 'Multi-Environment Deploy',
384
+ description: 'Manage dev/staging/prod separately',
385
+ commands: [
386
+ 'lsh push --env dev',
387
+ 'lsh push --env staging',
388
+ 'lsh push --env prod',
389
+ 'lsh pull --env staging # Switch to staging',
390
+ ],
391
+ },
392
+ {
393
+ name: 'Automatic Key Rotation',
394
+ description: 'Schedule monthly key rotation',
395
+ commands: [
396
+ 'lsh daemon start',
397
+ 'lsh cron add --name "rotate" --schedule "0 0 1 * *" --command "lsh key >> .env && lsh push --force"',
398
+ ],
399
+ },
400
+ {
401
+ name: 'Load Secrets to Shell',
402
+ description: 'Export all secrets to current shell session',
403
+ commands: [
404
+ 'eval "$(lsh list --format export)"',
405
+ '# Or: source <(lsh list --format export)',
406
+ ],
407
+ },
408
+ {
409
+ name: 'Get Single Secret',
410
+ description: 'Retrieve one secret for use in script',
411
+ commands: [
412
+ 'API_KEY=$(lsh get API_KEY --no-mask)',
413
+ 'curl -H "Authorization: Bearer $API_KEY" https://api.example.com',
414
+ ],
415
+ },
416
+ ],
417
+ troubleshooting: [
418
+ {
419
+ error: 'No secrets found for environment',
420
+ cause: 'Environment does not exist in cloud storage',
421
+ solution: 'Run `lsh env` to list environments, then `lsh push --env <name>` to create',
422
+ },
423
+ {
424
+ error: 'Decryption failed',
425
+ cause: 'LSH_SECRETS_KEY does not match the key used to encrypt',
426
+ solution: 'Ensure LSH_SECRETS_KEY in .env matches the original. If lost, generate new key with `lsh key` and re-push',
427
+ },
428
+ {
429
+ error: 'IPFS daemon not running',
430
+ cause: 'IPFS daemon is not started',
431
+ solution: 'Run `lsh sync init` to set up IPFS or `lsh ipfs start` to start daemon',
432
+ },
433
+ {
434
+ error: '.env file not found',
435
+ cause: 'No local .env file exists',
436
+ solution: 'Run `lsh pull` to download from cloud, or `lsh create` to create new',
437
+ },
438
+ {
439
+ error: 'Network error / timeout',
440
+ cause: 'Cannot reach IPFS gateways',
441
+ solution: 'Check internet connection. Run `lsh doctor` to diagnose',
442
+ },
443
+ ],
444
+ };
445
+ }
446
+ /**
447
+ * Format context data as human-readable text
448
+ */
449
+ function formatAsText(data) {
450
+ const lines = [];
451
+ // Header
452
+ lines.push('================================================================================');
453
+ lines.push('LSH - Encrypted Secrets Manager - ML/Agent Context');
454
+ lines.push('================================================================================');
455
+ lines.push('');
456
+ lines.push(`Version: ${data.tool.version}`);
457
+ lines.push(`Package: ${data.tool.package}`);
458
+ lines.push(`Purpose: ${data.tool.purpose}`);
459
+ lines.push(`Repository: ${data.tool.repository}`);
460
+ lines.push('');
461
+ // Quick Start
462
+ lines.push('QUICK START');
463
+ lines.push('--------------------------------------------------------------------------------');
464
+ for (const cmd of data.quickStart) {
465
+ lines.push(` ${cmd}`);
466
+ }
467
+ lines.push('');
468
+ // Commands by category
469
+ const categories = [
470
+ { key: 'setup', title: 'SETUP COMMANDS' },
471
+ { key: 'secrets', title: 'SECRETS COMMANDS' },
472
+ { key: 'operations', title: 'SECRET OPERATIONS' },
473
+ { key: 'automation', title: 'AUTOMATION COMMANDS' },
474
+ { key: 'config', title: 'CONFIGURATION COMMANDS' },
475
+ { key: 'backend', title: 'BACKEND COMMANDS' },
476
+ { key: 'self', title: 'SELF-MANAGEMENT' },
477
+ ];
478
+ for (const cat of categories) {
479
+ const cmds = data.commands[cat.key];
480
+ lines.push(cat.title);
481
+ lines.push('--------------------------------------------------------------------------------');
482
+ for (const cmd of cmds) {
483
+ lines.push(` ${cmd.command}`);
484
+ lines.push(` ${cmd.description}`);
485
+ if (cmd.options && cmd.options.length > 0) {
486
+ lines.push(' Options:');
487
+ for (const opt of cmd.options) {
488
+ const def = opt.default ? ` (default: ${opt.default})` : '';
489
+ lines.push(` ${opt.flag} ${opt.description}${def}`);
490
+ }
491
+ }
492
+ if (cmd.examples && cmd.examples.length > 0) {
493
+ lines.push(' Examples:');
494
+ for (const ex of cmd.examples) {
495
+ lines.push(` $ ${ex}`);
496
+ }
497
+ }
498
+ if (cmd.notes && cmd.notes.length > 0) {
499
+ lines.push(' Notes:');
500
+ for (const note of cmd.notes) {
501
+ lines.push(` - ${note}`);
502
+ }
503
+ }
504
+ lines.push('');
505
+ }
506
+ }
507
+ // Environment Variables
508
+ lines.push('ENVIRONMENT VARIABLES');
509
+ lines.push('--------------------------------------------------------------------------------');
510
+ lines.push('Required:');
511
+ for (const env of data.environment.required) {
512
+ lines.push(` ${env.name}`);
513
+ lines.push(` ${env.description}`);
514
+ if (env.example) {
515
+ lines.push(` Example: ${env.example}`);
516
+ }
517
+ }
518
+ lines.push('');
519
+ lines.push('Optional:');
520
+ for (const env of data.environment.optional) {
521
+ const def = env.default ? ` (default: ${env.default})` : '';
522
+ lines.push(` ${env.name}${def}`);
523
+ lines.push(` ${env.description}`);
524
+ }
525
+ lines.push('');
526
+ // File Locations
527
+ lines.push('FILE LOCATIONS');
528
+ lines.push('--------------------------------------------------------------------------------');
529
+ for (const file of data.files) {
530
+ lines.push(` ${file.path}`);
531
+ lines.push(` ${file.description}`);
532
+ }
533
+ lines.push('');
534
+ // Common Patterns
535
+ lines.push('COMMON PATTERNS');
536
+ lines.push('--------------------------------------------------------------------------------');
537
+ for (const pattern of data.patterns) {
538
+ lines.push(` ${pattern.name}`);
539
+ lines.push(` ${pattern.description}`);
540
+ for (const cmd of pattern.commands) {
541
+ lines.push(` $ ${cmd}`);
542
+ }
543
+ lines.push('');
544
+ }
545
+ // Troubleshooting
546
+ lines.push('TROUBLESHOOTING');
547
+ lines.push('--------------------------------------------------------------------------------');
548
+ for (const issue of data.troubleshooting) {
549
+ lines.push(` Error: "${issue.error}"`);
550
+ lines.push(` Cause: ${issue.cause}`);
551
+ lines.push(` Solution: ${issue.solution}`);
552
+ lines.push('');
553
+ }
554
+ // Footer
555
+ lines.push('================================================================================');
556
+ lines.push('For more information: https://github.com/gwicho38/lsh');
557
+ lines.push('Static context file: llms.txt (in repository root)');
558
+ lines.push('================================================================================');
559
+ return lines.join('\n');
560
+ }
561
+ /**
562
+ * Register the context command
563
+ */
564
+ export function registerContextCommand(program) {
565
+ program
566
+ .command('context')
567
+ .description('Show comprehensive ML/agent-friendly usage documentation')
568
+ .option('--json', 'Output as JSON for programmatic parsing')
569
+ .action((options) => {
570
+ const data = buildContextData();
571
+ if (options.json) {
572
+ console.log(JSON.stringify(data, null, 2));
573
+ }
574
+ else {
575
+ console.log(formatAsText(data));
576
+ }
577
+ });
578
+ }