lsh-framework 2.2.0 → 2.2.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -233,7 +233,7 @@ async function checkCloudSecretsExist() {
233
233
  }
234
234
  const repoName = gitInfo.repoName;
235
235
  const environment = repoName; // Default environment for repo
236
- // Check if metadata file exists with this repo's secrets
236
+ // First check local metadata (fast path)
237
237
  const paths = getPlatformPaths();
238
238
  const metadataPath = path.join(paths.dataDir, 'secrets-metadata.json');
239
239
  try {
@@ -246,7 +246,26 @@ async function checkCloudSecretsExist() {
246
246
  }
247
247
  }
248
248
  catch {
249
- // Metadata file doesn't exist or can't be read
249
+ // Metadata file doesn't exist or can't be read - continue to network check
250
+ }
251
+ // Check Storacha network for registry file (works on new machines)
252
+ try {
253
+ const { getStorachaClient } = await import('../lib/storacha-client.js');
254
+ const storacha = getStorachaClient();
255
+ // Only check network if Storacha is enabled and authenticated
256
+ if (storacha.isEnabled() && await storacha.isAuthenticated()) {
257
+ const spinner = ora('Checking Storacha network for existing secrets...').start();
258
+ const registryExists = await storacha.checkRegistry(repoName);
259
+ spinner.stop();
260
+ if (registryExists) {
261
+ return { exists: true, repoName, environment };
262
+ }
263
+ }
264
+ }
265
+ catch (error) {
266
+ // Network check failed, but that's okay - just means no secrets found
267
+ const err = error;
268
+ console.log(chalk.gray(` (Network check skipped: ${err.message})`));
250
269
  }
251
270
  return { exists: false, repoName, environment };
252
271
  }
@@ -72,6 +72,19 @@ export class IPFSSecretsStorage {
72
72
  // encryptedData is already a Buffer, pass it directly
73
73
  await storacha.upload(Buffer.from(encryptedData), filename);
74
74
  logger.info(` ☁️ Synced to Storacha network`);
75
+ // Upload registry file if this is a git repo
76
+ // This allows detection on new machines without local metadata
77
+ if (gitRepo) {
78
+ try {
79
+ await storacha.uploadRegistry(gitRepo, environment);
80
+ logger.debug(` 📝 Registry uploaded for ${gitRepo}`);
81
+ }
82
+ catch (regError) {
83
+ // Registry upload failed, but secrets are still uploaded
84
+ const _regErr = regError;
85
+ logger.debug(` Registry upload failed: ${_regErr.message}`);
86
+ }
87
+ }
75
88
  }
76
89
  catch (error) {
77
90
  const err = error;
@@ -233,6 +233,102 @@ export class StorachaClient {
233
233
  throw new Error(`Failed to download from Storacha: ${err.message}`);
234
234
  }
235
235
  }
236
+ /**
237
+ * Upload registry file for a repo
238
+ * Registry files mark that secrets exist for a repo without exposing the secrets themselves
239
+ */
240
+ async uploadRegistry(repoName, environment) {
241
+ if (!this.isEnabled()) {
242
+ throw new Error('Storacha is not enabled');
243
+ }
244
+ if (!await this.isAuthenticated()) {
245
+ throw new Error('Not authenticated');
246
+ }
247
+ const registry = {
248
+ repoName,
249
+ environment,
250
+ timestamp: new Date().toISOString(),
251
+ version: '2.2.1',
252
+ };
253
+ const content = JSON.stringify(registry, null, 2);
254
+ const buffer = Buffer.from(content, 'utf-8');
255
+ const filename = `lsh-registry-${repoName}.json`;
256
+ const client = await this.getClient();
257
+ const uint8Array = new Uint8Array(buffer.buffer, buffer.byteOffset, buffer.byteLength);
258
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
259
+ const file = new File([uint8Array], filename, { type: 'application/json' });
260
+ const cid = await client.uploadFile(file);
261
+ logger.debug(`📝 Uploaded registry for ${repoName}: ${cid}`);
262
+ return cid.toString();
263
+ }
264
+ /**
265
+ * Check if registry exists for a repo by listing uploads
266
+ * Returns true if a registry file for this repo exists in Storacha
267
+ *
268
+ * NOTE: This is optimized to check only recent small files (likely registry files)
269
+ * to avoid downloading large encrypted secret files.
270
+ */
271
+ async checkRegistry(repoName) {
272
+ if (!this.isEnabled()) {
273
+ return false;
274
+ }
275
+ if (!await this.isAuthenticated()) {
276
+ return false;
277
+ }
278
+ try {
279
+ const client = await this.getClient();
280
+ // Only check recent uploads (limit to 20 for performance)
281
+ const pageSize = 20;
282
+ // Get first page of uploads
283
+ const results = await client.capability.upload.list({
284
+ cursor: '',
285
+ size: pageSize,
286
+ });
287
+ // Track checked count for logging
288
+ let checked = 0;
289
+ let skipped = 0;
290
+ // Check if any uploads match our registry pattern
291
+ // Registry files are small JSON files (~200 bytes)
292
+ // Skip large files (encrypted secrets are much larger)
293
+ for (const upload of results.results) {
294
+ try {
295
+ const cid = upload.root.toString();
296
+ // Quick heuristic: registry files are tiny (<1KB)
297
+ // Skip if this looks like a large encrypted file based on CID
298
+ // We'll attempt download with a timeout
299
+ const downloadPromise = this.download(cid);
300
+ const timeoutPromise = new Promise((_, reject) => setTimeout(() => reject(new Error('timeout')), 5000) // 5s timeout per file
301
+ );
302
+ const content = await Promise.race([downloadPromise, timeoutPromise]);
303
+ // Skip large files (registry should be < 1KB)
304
+ if (content.length > 1024) {
305
+ skipped++;
306
+ continue;
307
+ }
308
+ checked++;
309
+ // Try to parse as JSON
310
+ const json = JSON.parse(content.toString('utf-8'));
311
+ // Check if it's an LSH registry file for our repo
312
+ if (json.repoName === repoName && json.version) {
313
+ logger.debug(`✅ Found registry for ${repoName} at CID: ${cid} (checked ${checked} files, skipped ${skipped})`);
314
+ return true;
315
+ }
316
+ }
317
+ catch {
318
+ // Not an LSH registry file, timed out, or failed to download - continue
319
+ skipped++;
320
+ }
321
+ }
322
+ // No registry found
323
+ logger.debug(`❌ No registry found for ${repoName} (checked ${checked} files, skipped ${skipped})`);
324
+ return false;
325
+ }
326
+ catch (error) {
327
+ const err = error;
328
+ logger.debug(`Failed to check registry: ${err.message}`);
329
+ return false;
330
+ }
331
+ }
236
332
  /**
237
333
  * Enable Storacha network sync
238
334
  */
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "lsh-framework",
3
- "version": "2.2.0",
3
+ "version": "2.2.1",
4
4
  "description": "Simple, cross-platform encrypted secrets manager with automatic sync, IPFS audit logs, and multi-environment support. Just run lsh sync and start managing your secrets.",
5
5
  "main": "dist/app.js",
6
6
  "bin": {