lsh-framework 3.1.21 → 3.1.23

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.
@@ -24,6 +24,9 @@ export function registerSyncCommands(program) {
24
24
  // Show help when running `lsh sync` without subcommand
25
25
  console.log(chalk.bold.cyan('\nšŸ”„ LSH Sync - IPFS Secrets Sync\n'));
26
26
  console.log(chalk.gray('Sync encrypted secrets via native IPFS (no auth required)\n'));
27
+ console.log(chalk.bold('Quick:'));
28
+ console.log(` ${chalk.cyan('now')} ⚔ Auto-setup and push secrets in one command`);
29
+ console.log('');
27
30
  console.log(chalk.bold('Setup:'));
28
31
  console.log(` ${chalk.cyan('init')} šŸš€ Full setup: install IPFS, initialize, and start daemon`);
29
32
  console.log(` ${chalk.cyan('status')} šŸ“Š Show IPFS client, daemon, and sync status`);
@@ -38,8 +41,8 @@ export function registerSyncCommands(program) {
38
41
  console.log(` ${chalk.cyan('clear')} šŸ—‘ļø Clear sync history`);
39
42
  console.log('');
40
43
  console.log(chalk.bold('Examples:'));
41
- console.log(chalk.gray(' lsh sync init # One-time setup'));
42
- console.log(chalk.gray(' lsh sync push # Push secrets, get CID'));
44
+ console.log(chalk.gray(' lsh sync now # Auto-setup + push (recommended)'));
45
+ console.log(chalk.gray(' lsh sync now -d "v1.0" # Push with description'));
43
46
  console.log(chalk.gray(' lsh sync pull <cid> # Pull secrets by CID'));
44
47
  console.log('');
45
48
  console.log(chalk.gray('Run "lsh sync <command> --help" for more info.'));
@@ -126,6 +129,132 @@ export function registerSyncCommands(program) {
126
129
  console.log(chalk.cyan(' lsh sync pull <cid> # Pull secrets by CID'));
127
130
  console.log('');
128
131
  });
132
+ // lsh sync now - convenience command that does everything
133
+ syncCommand
134
+ .command('now')
135
+ .description('⚔ Auto-setup and push secrets in one command')
136
+ .option('-f, --file <path>', 'Path to .env file', '.env')
137
+ .option('-e, --env <name>', 'Environment name', '')
138
+ .option('-d, --description <text>', 'Description for this sync')
139
+ .action(async (options) => {
140
+ console.log(chalk.bold.cyan('\n⚔ LSH Sync Now\n'));
141
+ const manager = new IPFSClientManager();
142
+ const ipfsSync = getIPFSSync();
143
+ const gitInfo = getGitRepoInfo();
144
+ // Step 1: Ensure IPFS is set up and daemon is running
145
+ let daemonReady = await ipfsSync.checkDaemon();
146
+ if (!daemonReady) {
147
+ // Check if IPFS is installed
148
+ const clientInfo = await manager.detect();
149
+ if (!clientInfo.installed) {
150
+ const installSpinner = ora('Installing IPFS client...').start();
151
+ try {
152
+ await manager.install();
153
+ installSpinner.succeed(chalk.green('IPFS client installed'));
154
+ }
155
+ catch (error) {
156
+ const err = error;
157
+ installSpinner.fail(chalk.red('Failed to install IPFS'));
158
+ console.error(chalk.red(err.message));
159
+ process.exit(1);
160
+ }
161
+ }
162
+ // Initialize repo if needed
163
+ const initSpinner = ora('Initializing IPFS...').start();
164
+ try {
165
+ await manager.init();
166
+ initSpinner.succeed(chalk.green('IPFS initialized'));
167
+ }
168
+ catch (error) {
169
+ const err = error;
170
+ if (!err.message.includes('already') && !err.message.includes('exists')) {
171
+ initSpinner.fail(chalk.red('Failed to initialize IPFS'));
172
+ console.error(chalk.red(err.message));
173
+ process.exit(1);
174
+ }
175
+ initSpinner.succeed(chalk.green('IPFS already initialized'));
176
+ }
177
+ // Start daemon
178
+ const startSpinner = ora('Starting IPFS daemon...').start();
179
+ try {
180
+ await manager.start();
181
+ // Give daemon a moment to start accepting connections
182
+ await new Promise(resolve => { setTimeout(resolve, 2000); });
183
+ startSpinner.succeed(chalk.green('IPFS daemon started'));
184
+ daemonReady = true;
185
+ }
186
+ catch (error) {
187
+ const err = error;
188
+ startSpinner.fail(chalk.red('Failed to start daemon'));
189
+ console.error(chalk.red(err.message));
190
+ process.exit(1);
191
+ }
192
+ }
193
+ else {
194
+ console.log(chalk.green('āœ“ IPFS daemon running'));
195
+ }
196
+ // Step 2: Read and validate .env file
197
+ const envPath = path.resolve(options.file);
198
+ if (!fs.existsSync(envPath)) {
199
+ console.error(chalk.red(`\nāœ– File not found: ${envPath}`));
200
+ process.exit(1);
201
+ }
202
+ const content = fs.readFileSync(envPath, 'utf-8');
203
+ // Step 3: Get encryption key
204
+ let encryptionKey = process.env[ENV_VARS.LSH_SECRETS_KEY];
205
+ if (!encryptionKey) {
206
+ const keyMatch = content.match(/^LSH_SECRETS_KEY=(.+)$/m);
207
+ if (keyMatch) {
208
+ encryptionKey = keyMatch[1].trim();
209
+ if ((encryptionKey.startsWith('"') && encryptionKey.endsWith('"')) ||
210
+ (encryptionKey.startsWith("'") && encryptionKey.endsWith("'"))) {
211
+ encryptionKey = encryptionKey.slice(1, -1);
212
+ }
213
+ }
214
+ }
215
+ if (!encryptionKey) {
216
+ console.error(chalk.red('\nāœ– LSH_SECRETS_KEY not set'));
217
+ console.log('');
218
+ console.log(chalk.gray('Generate a key with:'));
219
+ console.log(chalk.cyan(' lsh key'));
220
+ process.exit(1);
221
+ }
222
+ // Step 4: Encrypt and upload
223
+ const uploadSpinner = ora('Encrypting and uploading...').start();
224
+ try {
225
+ const key = crypto.createHash('sha256').update(encryptionKey).digest();
226
+ const iv = crypto.randomBytes(16);
227
+ const cipher = crypto.createCipheriv('aes-256-cbc', key, iv);
228
+ let encrypted = cipher.update(content, 'utf8', 'hex');
229
+ encrypted += cipher.final('hex');
230
+ const encryptedData = iv.toString('hex') + ':' + encrypted;
231
+ const filename = `lsh-secrets-${options.env || 'default'}.encrypted`;
232
+ const cid = await ipfsSync.upload(Buffer.from(encryptedData, 'utf-8'), filename, {
233
+ environment: options.env || undefined,
234
+ gitRepo: gitInfo?.repoName || undefined,
235
+ });
236
+ if (!cid) {
237
+ uploadSpinner.fail(chalk.red('Upload failed'));
238
+ process.exit(1);
239
+ }
240
+ uploadSpinner.succeed(chalk.green('Synced to IPFS!'));
241
+ console.log('');
242
+ console.log(chalk.bold('CID:'), chalk.cyan(cid));
243
+ if (options.description) {
244
+ console.log(chalk.bold('Description:'), chalk.gray(options.description));
245
+ }
246
+ console.log('');
247
+ console.log(chalk.gray('Pull on another machine:'));
248
+ console.log(chalk.cyan(` lsh sync pull ${cid}`));
249
+ console.log('');
250
+ }
251
+ catch (error) {
252
+ const err = error;
253
+ uploadSpinner.fail(chalk.red('Sync failed'));
254
+ console.error(chalk.red(err.message));
255
+ process.exit(1);
256
+ }
257
+ });
129
258
  // lsh sync push
130
259
  syncCommand
131
260
  .command('push')
@@ -5,7 +5,7 @@
5
5
  import * as fs from 'fs';
6
6
  import * as path from 'path';
7
7
  import * as os from 'os';
8
- import { exec } from 'child_process';
8
+ import { exec, spawn } from 'child_process';
9
9
  import { promisify } from 'util';
10
10
  import { createLogger } from './logger.js';
11
11
  import { getPlatformInfo } from './platform-utils.js';
@@ -183,10 +183,15 @@ export class IPFSClientManager {
183
183
  const ipfsRepoPath = path.join(this.ipfsDir, 'repo');
184
184
  const ipfsCmd = clientInfo.path || 'ipfs';
185
185
  try {
186
- // Start daemon in background
187
- const daemon = exec(`${ipfsCmd} daemon`, {
186
+ // Start daemon as fully detached background process
187
+ // Using spawn with detached:true and stdio:'ignore' allows parent to exit
188
+ const daemon = spawn(ipfsCmd, ['daemon'], {
188
189
  env: { ...process.env, IPFS_PATH: ipfsRepoPath },
190
+ detached: true,
191
+ stdio: 'ignore',
189
192
  });
193
+ // Unref the child so parent process can exit independently
194
+ daemon.unref();
190
195
  // Log PID for management
191
196
  const pidPath = path.join(this.ipfsDir, 'daemon.pid');
192
197
  if (daemon.pid) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "lsh-framework",
3
- "version": "3.1.21",
3
+ "version": "3.1.23",
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": {