lsh-framework 3.1.25 → 3.1.26

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
@@ -13,6 +13,7 @@ import { registerSyncHistoryCommands } from './commands/sync-history.js';
13
13
  import { registerSyncCommands } from './commands/sync.js';
14
14
  import { registerMigrateCommand } from './commands/migrate.js';
15
15
  import { registerContextCommand } from './commands/context.js';
16
+ import { registerIPFSCommands } from './commands/ipfs.js';
16
17
  import { init_secrets } from './services/secrets/secrets.js';
17
18
  import { loadGlobalConfigSync } from './lib/config-manager.js';
18
19
  import { CLI_TEXT, CLI_HELP } from './constants/ui.js';
@@ -147,6 +148,7 @@ function findSimilarCommands(input, validCommands) {
147
148
  registerSyncCommands(program);
148
149
  registerMigrateCommand(program);
149
150
  registerContextCommand(program);
151
+ registerIPFSCommands(program);
150
152
  // Secrets management (primary feature)
151
153
  await init_secrets(program);
152
154
  // Shell completion
@@ -171,6 +171,12 @@ export class IPFSClientManager {
171
171
  throw error;
172
172
  }
173
173
  }
174
+ /**
175
+ * Get the IPFS repo path used by lsh
176
+ */
177
+ getRepoPath() {
178
+ return path.join(this.ipfsDir, 'repo');
179
+ }
174
180
  /**
175
181
  * Start IPFS daemon
176
182
  */
@@ -179,9 +185,23 @@ export class IPFSClientManager {
179
185
  if (!clientInfo.installed) {
180
186
  throw new Error('IPFS client not installed. Run: lsh ipfs install');
181
187
  }
182
- logger.info('🚀 Starting IPFS daemon...');
183
- const ipfsRepoPath = path.join(this.ipfsDir, 'repo');
188
+ const ipfsRepoPath = this.getRepoPath();
184
189
  const ipfsCmd = clientInfo.path || 'ipfs';
190
+ // Auto-initialize repo if it doesn't exist
191
+ if (!fs.existsSync(path.join(ipfsRepoPath, 'config'))) {
192
+ logger.info('🔧 IPFS repository not found, initializing...');
193
+ try {
194
+ await execAsync(`${ipfsCmd} init`, {
195
+ env: { ...process.env, IPFS_PATH: ipfsRepoPath },
196
+ });
197
+ logger.info('✅ IPFS repository initialized');
198
+ }
199
+ catch (initError) {
200
+ const err = initError;
201
+ throw new Error(`Failed to auto-initialize IPFS repo: ${err.message}`);
202
+ }
203
+ }
204
+ logger.info('🚀 Starting IPFS daemon...');
185
205
  try {
186
206
  // Start daemon as fully detached background process
187
207
  // Using spawn with detached:true and stdio:'ignore' allows parent to exit
@@ -197,6 +217,17 @@ export class IPFSClientManager {
197
217
  if (daemon.pid) {
198
218
  fs.writeFileSync(pidPath, daemon.pid.toString(), 'utf8');
199
219
  }
220
+ // Wait for daemon to actually be ready (poll the API)
221
+ const ready = await this.waitForDaemon(10000);
222
+ if (!ready) {
223
+ // Clean up PID file since daemon didn't start
224
+ if (fs.existsSync(pidPath)) {
225
+ fs.unlinkSync(pidPath);
226
+ }
227
+ throw new Error('IPFS daemon process started but API is not responding. ' +
228
+ 'The daemon may have crashed. Check if IPFS repo is properly initialized: ' +
229
+ `IPFS_PATH=${ipfsRepoPath}`);
230
+ }
200
231
  logger.info('✅ IPFS daemon started');
201
232
  logger.info(` PID: ${daemon.pid}`);
202
233
  logger.info(' API: http://localhost:5001');
@@ -208,6 +239,27 @@ export class IPFSClientManager {
208
239
  throw error;
209
240
  }
210
241
  }
242
+ /**
243
+ * Wait for daemon API to become ready
244
+ */
245
+ async waitForDaemon(timeoutMs) {
246
+ const start = Date.now();
247
+ while (Date.now() - start < timeoutMs) {
248
+ try {
249
+ const response = await fetch('http://127.0.0.1:5001/api/v0/id', {
250
+ method: 'POST',
251
+ signal: AbortSignal.timeout(2000),
252
+ });
253
+ if (response.ok)
254
+ return true;
255
+ }
256
+ catch {
257
+ // Daemon not ready yet, keep polling
258
+ }
259
+ await new Promise(resolve => setTimeout(resolve, 500));
260
+ }
261
+ return false;
262
+ }
211
263
  /**
212
264
  * Stop IPFS daemon
213
265
  */
@@ -130,14 +130,16 @@ export class IPFSSync {
130
130
  }
131
131
  /**
132
132
  * Download data from IPFS
133
- * Tries local daemon first, then falls back to public gateways
133
+ * Tries local daemon first (with longer timeout for DHT discovery),
134
+ * then falls back to public gateways
134
135
  */
135
136
  async download(cid) {
136
- // Try local daemon first (fastest if available)
137
+ // Try local daemon first use a longer timeout (60s) because
138
+ // the local node may need time for DHT content discovery
137
139
  try {
138
140
  const localResponse = await fetch(`${this.LOCAL_IPFS_API}/cat?arg=${cid}`, {
139
141
  method: 'POST',
140
- signal: AbortSignal.timeout(30000),
142
+ signal: AbortSignal.timeout(60000),
141
143
  });
142
144
  if (localResponse.ok) {
143
145
  const arrayBuffer = await localResponse.arrayBuffer();
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "lsh-framework",
3
- "version": "3.1.25",
3
+ "version": "3.1.26",
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": {