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.
@@ -7,7 +7,6 @@ import chalk from 'chalk';
7
7
  import * as fs from 'fs/promises';
8
8
  import * as path from 'path';
9
9
  import * as crypto from 'crypto';
10
- import { createClient } from '@supabase/supabase-js';
11
10
  import ora from 'ora';
12
11
  import { getPlatformPaths } from '../lib/platform-utils.js';
13
12
  import { getGitRepoInfo } from '../lib/git-utils.js';
@@ -20,11 +19,6 @@ export function registerInitCommands(program) {
20
19
  .command('init')
21
20
  .description('Interactive setup wizard (first-time configuration)')
22
21
  .option('-g, --global', 'Use global workspace ($HOME)')
23
- .option('--local', 'Use local-only encryption (no cloud sync)')
24
- .option('--storacha', 'Use Storacha IPFS network sync (recommended)')
25
- .option('--supabase', 'Use Supabase cloud storage')
26
- .option('--postgres', 'Use self-hosted PostgreSQL')
27
- .option('--skip-test', 'Skip connection testing')
28
22
  .action(async (options) => {
29
23
  try {
30
24
  await runSetupWizard(options);
@@ -75,51 +69,22 @@ async function runSetupWizard(options) {
75
69
  return;
76
70
  }
77
71
  }
78
- // Determine storage type
79
- let storageType;
80
- if (options.storacha) {
81
- storageType = 'storacha';
82
- }
83
- else if (options.local) {
84
- storageType = 'local';
85
- }
86
- else if (options.postgres) {
87
- storageType = 'postgres';
88
- }
89
- else if (options.supabase) {
90
- storageType = 'supabase';
72
+ // Check IPFS daemon status
73
+ const { getIPFSSync } = await import('../lib/ipfs-sync.js');
74
+ const ipfsSync = getIPFSSync();
75
+ const daemonRunning = await ipfsSync.checkDaemon();
76
+ if (!daemonRunning) {
77
+ console.log(chalk.yellow('\n⚠️ IPFS daemon not running'));
78
+ console.log(chalk.gray(' For network sync, start IPFS:'));
79
+ console.log(chalk.cyan(' lsh ipfs install && lsh ipfs init && lsh ipfs start'));
80
+ console.log(chalk.gray(' Setup will continue with local-only mode.'));
81
+ console.log('');
91
82
  }
92
83
  else {
93
- // Ask user
94
- const { storage } = await inquirer.prompt([
95
- {
96
- type: 'list',
97
- name: 'storage',
98
- message: 'Choose storage backend:',
99
- choices: [
100
- {
101
- name: '🌐 Storacha (IPFS network, zero-config, recommended)',
102
- value: 'storacha',
103
- },
104
- {
105
- name: '☁️ Supabase (cloud-hosted, team collaboration)',
106
- value: 'supabase',
107
- },
108
- {
109
- name: '💾 Local encryption (file-based, no cloud sync)',
110
- value: 'local',
111
- },
112
- {
113
- name: '🐘 Self-hosted PostgreSQL',
114
- value: 'postgres',
115
- },
116
- ],
117
- default: 'storacha',
118
- },
119
- ]);
120
- storageType = storage;
84
+ console.log(chalk.green('\n✅ IPFS daemon running - network sync enabled'));
85
+ console.log('');
121
86
  }
122
- // Check if secrets already exist for this repo in the cloud
87
+ // Check if secrets already exist locally
123
88
  const cloudCheck = await checkCloudSecretsExist();
124
89
  let encryptionKey;
125
90
  if (cloudCheck.exists && cloudCheck.repoName) {
@@ -167,22 +132,8 @@ async function runSetupWizard(options) {
167
132
  encryptionKey = generateEncryptionKey();
168
133
  }
169
134
  const config = {
170
- storageType,
171
135
  encryptionKey,
172
136
  };
173
- // Configure based on storage type
174
- if (storageType === 'storacha') {
175
- await configureStoracha(config);
176
- }
177
- else if (storageType === 'supabase') {
178
- await configureSupabase(config, options.skipTest);
179
- }
180
- else if (storageType === 'postgres') {
181
- await configurePostgres(config, options.skipTest);
182
- }
183
- else {
184
- await configureLocal(config);
185
- }
186
137
  // If using existing key and secrets exist, offer to pull them now
187
138
  if (cloudCheck.exists && config.encryptionKey && cloudCheck.repoName) {
188
139
  const { pullNow } = await inquirer.prompt([
@@ -240,7 +191,7 @@ async function pullSecretsAfterInit(_encryptionKey, _repoName) {
240
191
  }
241
192
  }
242
193
  /**
243
- * Check if secrets already exist in cloud for current repo
194
+ * Check if secrets already exist locally for current repo
244
195
  */
245
196
  async function checkCloudSecretsExist() {
246
197
  try {
@@ -250,7 +201,7 @@ async function checkCloudSecretsExist() {
250
201
  }
251
202
  const repoName = gitInfo.repoName;
252
203
  const environment = repoName; // Default environment for repo
253
- // First check local metadata (fast path)
204
+ // Check local metadata (fast path)
254
205
  const paths = getPlatformPaths();
255
206
  const metadataPath = path.join(paths.dataDir, 'secrets-metadata.json');
256
207
  try {
@@ -263,26 +214,19 @@ async function checkCloudSecretsExist() {
263
214
  }
264
215
  }
265
216
  catch {
266
- // Metadata file doesn't exist or can't be read - continue to network check
217
+ // Metadata file doesn't exist or can't be read
267
218
  }
268
- // Check Storacha network for registry file (works on new machines)
219
+ // Check IPFS sync history for this repo
269
220
  try {
270
- const { getStorachaClient } = await import('../lib/storacha-client.js');
271
- const storacha = getStorachaClient();
272
- // Only check network if Storacha is enabled and authenticated
273
- if (storacha.isEnabled() && await storacha.isAuthenticated()) {
274
- const spinner = ora('Checking Storacha network for existing secrets...').start();
275
- const registryExists = await storacha.checkRegistry(repoName);
276
- spinner.stop();
277
- if (registryExists) {
278
- return { exists: true, repoName, environment };
279
- }
221
+ const { getIPFSSync } = await import('../lib/ipfs-sync.js');
222
+ const ipfsSync = getIPFSSync();
223
+ const latestCid = await ipfsSync.getLatestCid(repoName);
224
+ if (latestCid) {
225
+ return { exists: true, repoName, environment };
280
226
  }
281
227
  }
282
- catch (error) {
283
- // Network check failed, but that's okay - just means no secrets found
284
- const err = error;
285
- console.log(chalk.gray(` (Network check skipped: ${err.message})`));
228
+ catch {
229
+ // IPFS sync history check failed
286
230
  }
287
231
  return { exists: false, repoName, environment };
288
232
  }
@@ -290,141 +234,6 @@ async function checkCloudSecretsExist() {
290
234
  return { exists: false };
291
235
  }
292
236
  }
293
- /**
294
- * Configure Supabase
295
- */
296
- async function configureSupabase(config, skipTest) {
297
- console.log(chalk.cyan('\n📦 Supabase Configuration'));
298
- console.log(chalk.gray('Need credentials? Visit: https://supabase.com/dashboard'));
299
- console.log('');
300
- const answers = await inquirer.prompt([
301
- {
302
- type: 'input',
303
- name: 'url',
304
- message: 'Enter your Supabase URL:',
305
- validate: (input) => {
306
- if (!input.trim())
307
- return 'URL is required';
308
- if (!input.startsWith('https://'))
309
- return 'URL must start with https://';
310
- if (!input.includes('.supabase.co'))
311
- return 'Must be a valid Supabase URL';
312
- return true;
313
- },
314
- },
315
- {
316
- type: 'password',
317
- name: 'key',
318
- message: 'Enter your Supabase anon key:',
319
- mask: '*',
320
- validate: (input) => {
321
- if (!input.trim())
322
- return 'Anon key is required';
323
- if (input.length < 100)
324
- return 'Anon key seems too short';
325
- return true;
326
- },
327
- },
328
- ]);
329
- config.supabaseUrl = answers.url.trim();
330
- config.supabaseKey = answers.key.trim();
331
- // Test connection
332
- if (!skipTest && config.supabaseUrl && config.supabaseKey) {
333
- await testSupabaseConnection(config.supabaseUrl, config.supabaseKey);
334
- }
335
- }
336
- /**
337
- * Test Supabase connection
338
- */
339
- async function testSupabaseConnection(url, key) {
340
- const spinner = ora('Testing Supabase connection...').start();
341
- try {
342
- const supabase = createClient(url, key);
343
- // Try to query the database (even if table doesn't exist, connection will work)
344
- const { error } = await supabase.from('lsh_secrets').select('count').limit(0);
345
- // Connection successful (404 table not found is fine - means connection works)
346
- if (!error || error.code === 'PGRST116' || error.message.includes('relation')) {
347
- spinner.succeed(chalk.green('✅ Connection successful!'));
348
- }
349
- else {
350
- spinner.fail(chalk.red('❌ Connection failed'));
351
- throw new Error(`Supabase error: ${error.message}`);
352
- }
353
- }
354
- catch (error) {
355
- spinner.fail(chalk.red('❌ Connection failed'));
356
- const err = error;
357
- throw new Error(`Could not connect to Supabase: ${err.message}`);
358
- }
359
- }
360
- /**
361
- * Configure Storacha IPFS network sync
362
- */
363
- async function configureStoracha(config) {
364
- console.log(chalk.cyan('\n🌐 Storacha IPFS Network Sync'));
365
- console.log(chalk.gray('Zero-config multi-host secrets sync via IPFS network'));
366
- console.log('');
367
- const answers = await inquirer.prompt([
368
- {
369
- type: 'input',
370
- name: 'email',
371
- message: 'Enter your email for Storacha authentication:',
372
- validate: (input) => {
373
- if (!input.trim())
374
- return 'Email is required';
375
- if (!input.includes('@'))
376
- return 'Must be a valid email address';
377
- return true;
378
- },
379
- },
380
- ]);
381
- config.storachaEmail = answers.email.trim();
382
- console.log('');
383
- console.log(chalk.yellow('📧 Please check your email to complete authentication.'));
384
- console.log(chalk.gray(' After setup completes, run:'));
385
- console.log(chalk.cyan(' lsh storacha login ' + config.storachaEmail));
386
- console.log('');
387
- }
388
- /**
389
- * Configure self-hosted PostgreSQL
390
- */
391
- async function configurePostgres(config, skipTest) {
392
- console.log(chalk.cyan('\n🐘 PostgreSQL Configuration'));
393
- console.log('');
394
- const { url } = await inquirer.prompt([
395
- {
396
- type: 'input',
397
- name: 'url',
398
- message: 'Enter PostgreSQL connection URL:',
399
- default: 'postgresql://user:password@localhost:5432/lsh',
400
- validate: (input) => {
401
- if (!input.trim())
402
- return 'Connection URL is required';
403
- if (!input.startsWith('postgres'))
404
- return 'Must be a valid PostgreSQL URL';
405
- return true;
406
- },
407
- },
408
- ]);
409
- config.postgresUrl = url.trim();
410
- if (!skipTest) {
411
- const spinner = ora('Testing PostgreSQL connection...').start();
412
- // Note: We'll skip actual testing for now as we don't have pg client imported
413
- spinner.info(chalk.yellow('⚠️ Connection test skipped. Run "lsh doctor" after setup to verify.'));
414
- }
415
- }
416
- /**
417
- * Configure local-only mode
418
- */
419
- async function configureLocal(_config) {
420
- console.log(chalk.cyan('\n💾 Local Encryption Mode'));
421
- console.log(chalk.gray('Secrets will be encrypted locally. No cloud sync available.'));
422
- console.log('');
423
- const paths = getPlatformPaths();
424
- console.log(chalk.gray('Encrypted secrets will be stored in:'));
425
- console.log(chalk.cyan(` ${path.join(paths.dataDir, 'encrypted')}`));
426
- console.log('');
427
- }
428
237
  /**
429
238
  * Generate a secure encryption key
430
239
  */
@@ -450,19 +259,6 @@ async function saveConfiguration(config, baseDir, globalMode) {
450
259
  const updates = {
451
260
  LSH_SECRETS_KEY: config.encryptionKey,
452
261
  };
453
- if (config.storageType === 'supabase' && config.supabaseUrl && config.supabaseKey) {
454
- updates.SUPABASE_URL = config.supabaseUrl;
455
- updates.SUPABASE_ANON_KEY = config.supabaseKey;
456
- }
457
- if (config.storageType === 'postgres' && config.postgresUrl) {
458
- updates.DATABASE_URL = config.postgresUrl;
459
- }
460
- if (config.storageType === 'local') {
461
- updates.LSH_STORAGE_MODE = 'local';
462
- }
463
- if (config.storageType === 'storacha') {
464
- updates.LSH_STORACHA_ENABLED = 'true';
465
- }
466
262
  // Update .env content
467
263
  for (const [key, value] of Object.entries(updates)) {
468
264
  const regex = new RegExp(`^${key}=.*$`, 'm');
@@ -529,55 +325,26 @@ function showSuccessMessage(config) {
529
325
  console.log(chalk.gray(' This key is saved in your .env file.'));
530
326
  console.log(chalk.gray(' Share it with your team to sync secrets.'));
531
327
  console.log('');
532
- // Storage info
533
- if (config.storageType === 'storacha') {
534
- console.log(chalk.cyan('🌐 Using Storacha IPFS network sync'));
535
- }
536
- else if (config.storageType === 'supabase') {
537
- console.log(chalk.cyan('☁️ Using Supabase cloud storage'));
538
- }
539
- else if (config.storageType === 'postgres') {
540
- console.log(chalk.cyan('🐘 Using PostgreSQL storage'));
541
- }
542
- else {
543
- console.log(chalk.cyan('💾 Using local encryption'));
544
- }
328
+ console.log(chalk.cyan('🌐 Using native IPFS for secrets sync'));
545
329
  console.log('');
546
330
  // Next steps
547
331
  console.log(chalk.bold('🚀 Next steps:'));
548
332
  console.log('');
549
- if (config.storageType === 'storacha') {
550
- console.log(chalk.gray(' 1. Authenticate with Storacha:'));
551
- console.log(chalk.cyan(` lsh storacha login ${config.storachaEmail || 'your@email.com'}`));
552
- console.log('');
553
- console.log(chalk.gray(' 2. Push your secrets:'));
554
- console.log(chalk.cyan(' lsh push --env dev'));
555
- console.log(chalk.gray(' (Automatically uploads to IPFS network)'));
556
- console.log('');
557
- console.log(chalk.gray(' 3. On another machine:'));
558
- console.log(chalk.cyan(' lsh init --storacha'));
559
- console.log(chalk.cyan(' lsh storacha login your@email.com'));
560
- console.log(chalk.cyan(' lsh pull --env dev'));
561
- console.log(chalk.gray(' (Automatically downloads from IPFS network)'));
562
- }
563
- else {
564
- console.log(chalk.gray(' 1. Verify your setup:'));
565
- console.log(chalk.cyan(' lsh doctor'));
566
- console.log('');
567
- if (config.storageType !== 'local') {
568
- console.log(chalk.gray(' 2. Push your secrets:'));
569
- console.log(chalk.cyan(' lsh push --env dev'));
570
- console.log('');
571
- console.log(chalk.gray(' 3. On another machine:'));
572
- console.log(chalk.cyan(' lsh init ') + chalk.gray('# Use the same credentials'));
573
- console.log(chalk.cyan(' lsh pull --env dev'));
574
- }
575
- else {
576
- console.log(chalk.gray(' 2. Start managing secrets:'));
577
- console.log(chalk.cyan(' lsh set API_KEY myvalue'));
578
- console.log(chalk.cyan(' lsh list'));
579
- }
580
- }
333
+ console.log(chalk.gray(' 1. (Optional) Start IPFS daemon for network sync:'));
334
+ console.log(chalk.cyan(' lsh ipfs install && lsh ipfs init && lsh ipfs start'));
335
+ console.log('');
336
+ console.log(chalk.gray(' 2. Push your secrets to IPFS:'));
337
+ console.log(chalk.cyan(' lsh sync push'));
338
+ console.log(chalk.gray(' (Returns a CID - share this with teammates)'));
339
+ console.log('');
340
+ console.log(chalk.gray(' 3. On another machine:'));
341
+ console.log(chalk.cyan(' lsh init'));
342
+ console.log(chalk.cyan(' export LSH_SECRETS_KEY=<key-from-teammate>'));
343
+ console.log(chalk.cyan(' lsh sync pull <cid>'));
344
+ console.log('');
345
+ console.log(chalk.gray(' Alternatively, use the classic push/pull commands:'));
346
+ console.log(chalk.cyan(' lsh push --env dev'));
347
+ console.log(chalk.cyan(' lsh pull --env dev'));
581
348
  console.log('');
582
349
  console.log(chalk.gray('📖 Documentation: https://github.com/gwicho38/lsh'));
583
350
  console.log('');
@@ -5,39 +5,76 @@
5
5
  import chalk from 'chalk';
6
6
  import ora from 'ora';
7
7
  import { IPFSClientManager } from '../lib/ipfs-client-manager.js';
8
+ import { getIPFSSync } from '../lib/ipfs-sync.js';
8
9
  /**
9
10
  * Register IPFS commands
10
11
  */
11
12
  export function registerIPFSCommands(program) {
12
13
  const ipfsCommand = program
13
14
  .command('ipfs')
14
- .description('Manage IPFS client installation and configuration');
15
+ .description('Manage IPFS client installation, configuration, and sync');
15
16
  // lsh ipfs status
16
17
  ipfsCommand
17
18
  .command('status')
18
- .description('Check IPFS client installation status')
19
+ .description('Check IPFS client installation and daemon status')
19
20
  .option('--json', 'Output as JSON')
20
21
  .action(async (options) => {
21
22
  try {
22
23
  const manager = new IPFSClientManager();
23
24
  const info = await manager.detect();
25
+ const ipfsSync = getIPFSSync();
26
+ const daemonInfo = await ipfsSync.getDaemonInfo();
24
27
  if (options.json) {
25
- console.log(JSON.stringify(info, null, 2));
28
+ console.log(JSON.stringify({
29
+ ...info,
30
+ daemonRunning: !!daemonInfo,
31
+ daemonInfo,
32
+ }, null, 2));
26
33
  return;
27
34
  }
28
- console.log(chalk.bold.cyan('\n📦 IPFS Client Status'));
35
+ console.log(chalk.bold.cyan('\n📦 IPFS Status'));
29
36
  console.log(chalk.gray('━'.repeat(50)));
30
37
  console.log('');
38
+ // Client installation status
39
+ console.log(chalk.bold('Client:'));
31
40
  if (info.installed) {
32
- console.log(chalk.green('✅ IPFS client installed'));
33
- console.log(` Type: ${info.type}`);
34
- console.log(` Version: ${info.version}`);
35
- console.log(` Path: ${info.path}`);
41
+ console.log(chalk.green(' ✅ IPFS client installed'));
42
+ console.log(` Type: ${info.type}`);
43
+ console.log(` Version: ${info.version}`);
44
+ console.log(` Path: ${info.path}`);
36
45
  }
37
46
  else {
38
- console.log(chalk.yellow('⚠️ IPFS client not installed'));
47
+ console.log(chalk.yellow(' ⚠️ IPFS client not installed'));
39
48
  console.log('');
40
- console.log(chalk.gray(' Install with: lsh ipfs install'));
49
+ console.log(chalk.gray(' Install with: lsh ipfs install'));
50
+ }
51
+ console.log('');
52
+ // Daemon status
53
+ console.log(chalk.bold('Daemon:'));
54
+ if (daemonInfo) {
55
+ console.log(chalk.green(' ✅ Daemon running'));
56
+ console.log(` Peer ID: ${daemonInfo.peerId.substring(0, 16)}...`);
57
+ console.log(` Version: ${daemonInfo.version}`);
58
+ console.log(' API: http://127.0.0.1:5001');
59
+ console.log(' Gateway: http://127.0.0.1:8080');
60
+ }
61
+ else {
62
+ console.log(chalk.yellow(' ⚠️ Daemon not running'));
63
+ console.log('');
64
+ console.log(chalk.gray(' Start with: lsh ipfs start'));
65
+ }
66
+ console.log('');
67
+ // Quick actions
68
+ console.log(chalk.bold('Quick Actions:'));
69
+ if (!info.installed) {
70
+ console.log(chalk.cyan(' lsh ipfs install # Install IPFS'));
71
+ }
72
+ else if (!daemonInfo) {
73
+ console.log(chalk.cyan(' lsh ipfs start # Start daemon'));
74
+ }
75
+ else {
76
+ console.log(chalk.cyan(' lsh ipfs sync push # Upload encrypted secrets'));
77
+ console.log(chalk.cyan(' lsh ipfs sync pull <cid> # Download by CID'));
41
78
  }
42
79
  console.log('');
43
80
  }