lsh-framework 3.1.23 → 3.1.25
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 +1 -0
- package/dist/constants/ui.js +1 -0
- package/dist/lib/optimized-job-scheduler.js +3 -2
- package/dist/services/secrets/secrets.js +125 -0
- package/package.json +1 -1
- package/dist/commands/storacha.js +0 -268
- package/dist/lib/input-validator.js +0 -318
- package/dist/lib/safe-eval.js +0 -124
- package/dist/lib/storacha-client.js +0 -692
package/dist/cli.js
CHANGED
package/dist/constants/ui.js
CHANGED
|
@@ -232,6 +232,7 @@ export const CLI_HELP = {
|
|
|
232
232
|
CMD_GET: ' get <key> Get a specific secret value (--all for all)',
|
|
233
233
|
CMD_SET: ' set <key> <value> Set a specific secret value',
|
|
234
234
|
CMD_DELETE: ' delete Delete .env file',
|
|
235
|
+
CMD_CP: ' cp <from> <to> Copy env variables between files (--name to copy one variable)',
|
|
235
236
|
CMD_STATUS: ' status Get detailed secrets status',
|
|
236
237
|
// IPFS commands
|
|
237
238
|
CMD_SYNC_INIT: ' sync init Full IPFS setup (install, init, start)',
|
|
@@ -226,8 +226,9 @@ export class OptimizedJobScheduler extends EventEmitter {
|
|
|
226
226
|
try {
|
|
227
227
|
const [minute, hour, day, month, weekday] = cronExpr.split(' ');
|
|
228
228
|
const now = new Date(from);
|
|
229
|
-
// Try to find next matching time within the next
|
|
230
|
-
|
|
229
|
+
// Try to find next matching time within the next 32 days
|
|
230
|
+
// (covers monthly cron expressions like "0 0 1 * *")
|
|
231
|
+
for (let i = 0; i < 32 * 24 * 60; i++) {
|
|
231
232
|
const checkTime = new Date(now.getTime() + i * 60000);
|
|
232
233
|
if (this.matchesCronField(minute, checkTime.getMinutes(), 0, 59) &&
|
|
233
234
|
this.matchesCronField(hour, checkTime.getHours(), 0, 23) &&
|
|
@@ -1090,5 +1090,130 @@ API_KEY=
|
|
|
1090
1090
|
process.exit(1);
|
|
1091
1091
|
}
|
|
1092
1092
|
});
|
|
1093
|
+
// Copy env variables from one file to another
|
|
1094
|
+
program
|
|
1095
|
+
.command('cp <from> <to>')
|
|
1096
|
+
.description('Copy env variables from <from> to <to> (like cp, but for .env files)')
|
|
1097
|
+
.option('--merge', 'Merge into destination instead of overwriting; source takes precedence on conflicts')
|
|
1098
|
+
.option('--no-overwrite', 'With --merge, skip keys that already exist in destination')
|
|
1099
|
+
.option('-n, --name <key>', 'Copy only the specified variable (implies --merge into destination)')
|
|
1100
|
+
.action(async (from, to, options) => {
|
|
1101
|
+
try {
|
|
1102
|
+
const fromPath = path.resolve(from);
|
|
1103
|
+
const toPath = path.resolve(to);
|
|
1104
|
+
if (!fs.existsSync(fromPath)) {
|
|
1105
|
+
console.error(`❌ Source file not found: ${fromPath}`);
|
|
1106
|
+
process.exit(1);
|
|
1107
|
+
}
|
|
1108
|
+
// Parse source file
|
|
1109
|
+
const srcContent = fs.readFileSync(fromPath, 'utf8');
|
|
1110
|
+
const srcLines = srcContent.split('\n');
|
|
1111
|
+
const srcVars = new Map();
|
|
1112
|
+
for (const line of srcLines) {
|
|
1113
|
+
if (line.trim().startsWith('#') || !line.trim())
|
|
1114
|
+
continue;
|
|
1115
|
+
const match = line.match(/^(?:export\s+)?([^=]+)=(.*)$/);
|
|
1116
|
+
if (match) {
|
|
1117
|
+
const key = match[1].trim();
|
|
1118
|
+
let value = match[2].trim();
|
|
1119
|
+
if ((value.startsWith('"') && value.endsWith('"')) ||
|
|
1120
|
+
(value.startsWith("'") && value.endsWith("'"))) {
|
|
1121
|
+
value = value.slice(1, -1);
|
|
1122
|
+
}
|
|
1123
|
+
srcVars.set(key, value);
|
|
1124
|
+
}
|
|
1125
|
+
}
|
|
1126
|
+
if (srcVars.size === 0) {
|
|
1127
|
+
console.error(`❌ No env variables found in: ${fromPath}`);
|
|
1128
|
+
process.exit(1);
|
|
1129
|
+
}
|
|
1130
|
+
// --name: copy only a single named variable (always merges into destination)
|
|
1131
|
+
if (options.name) {
|
|
1132
|
+
const targetKey = options.name;
|
|
1133
|
+
if (!srcVars.has(targetKey)) {
|
|
1134
|
+
console.error(`❌ Variable '${targetKey}' not found in ${from}`);
|
|
1135
|
+
process.exit(1);
|
|
1136
|
+
}
|
|
1137
|
+
const targetValue = srcVars.get(targetKey);
|
|
1138
|
+
await setSingleSecret(toPath, targetKey, targetValue);
|
|
1139
|
+
console.log(`✅ Copied ${targetKey} from ${from} to ${to}`);
|
|
1140
|
+
return;
|
|
1141
|
+
}
|
|
1142
|
+
if (!options.merge) {
|
|
1143
|
+
// Default: overwrite destination entirely (like regular cp)
|
|
1144
|
+
const newLines = [];
|
|
1145
|
+
for (const [key, value] of srcVars.entries()) {
|
|
1146
|
+
newLines.push(formatEnvLine(key, value, toPath));
|
|
1147
|
+
}
|
|
1148
|
+
fs.writeFileSync(toPath, newLines.join('\n') + '\n', 'utf8');
|
|
1149
|
+
console.log(`✅ Copied ${srcVars.size} variable(s) from ${from} to ${to}`);
|
|
1150
|
+
return;
|
|
1151
|
+
}
|
|
1152
|
+
// Merge mode: apply source vars into destination
|
|
1153
|
+
const noOverwrite = options.overwrite === false;
|
|
1154
|
+
const destLines = [];
|
|
1155
|
+
if (fs.existsSync(toPath)) {
|
|
1156
|
+
const destContent = fs.readFileSync(toPath, 'utf8');
|
|
1157
|
+
for (const line of destContent.split('\n')) {
|
|
1158
|
+
destLines.push(line);
|
|
1159
|
+
}
|
|
1160
|
+
}
|
|
1161
|
+
// Build merged content: walk destination lines, update/keep keys
|
|
1162
|
+
const processedKeys = new Set();
|
|
1163
|
+
const mergedLines = [];
|
|
1164
|
+
for (const line of destLines) {
|
|
1165
|
+
if (line.trim().startsWith('#') || !line.trim()) {
|
|
1166
|
+
mergedLines.push(line);
|
|
1167
|
+
continue;
|
|
1168
|
+
}
|
|
1169
|
+
const match = line.match(/^(?:export\s+)?([^=]+)=(.*)$/);
|
|
1170
|
+
if (match) {
|
|
1171
|
+
const key = match[1].trim();
|
|
1172
|
+
if (srcVars.has(key)) {
|
|
1173
|
+
if (noOverwrite) {
|
|
1174
|
+
mergedLines.push(line);
|
|
1175
|
+
}
|
|
1176
|
+
else {
|
|
1177
|
+
mergedLines.push(formatEnvLine(key, srcVars.get(key), toPath));
|
|
1178
|
+
}
|
|
1179
|
+
processedKeys.add(key);
|
|
1180
|
+
}
|
|
1181
|
+
else {
|
|
1182
|
+
mergedLines.push(line);
|
|
1183
|
+
}
|
|
1184
|
+
}
|
|
1185
|
+
else {
|
|
1186
|
+
mergedLines.push(line);
|
|
1187
|
+
}
|
|
1188
|
+
}
|
|
1189
|
+
// Append new keys from source not in destination
|
|
1190
|
+
let added = 0;
|
|
1191
|
+
for (const [key, value] of srcVars.entries()) {
|
|
1192
|
+
if (!processedKeys.has(key)) {
|
|
1193
|
+
mergedLines.push(formatEnvLine(key, value, toPath));
|
|
1194
|
+
added++;
|
|
1195
|
+
}
|
|
1196
|
+
}
|
|
1197
|
+
let finalContent = mergedLines.join('\n');
|
|
1198
|
+
if (!finalContent.endsWith('\n'))
|
|
1199
|
+
finalContent += '\n';
|
|
1200
|
+
fs.writeFileSync(toPath, finalContent, 'utf8');
|
|
1201
|
+
const updated = srcVars.size - added;
|
|
1202
|
+
const skipped = noOverwrite ? updated : 0;
|
|
1203
|
+
const overwritten = noOverwrite ? 0 : updated;
|
|
1204
|
+
console.log(`✅ Merged ${from} into ${to}:`);
|
|
1205
|
+
if (added > 0)
|
|
1206
|
+
console.log(` Added: ${added} new variable(s)`);
|
|
1207
|
+
if (overwritten > 0)
|
|
1208
|
+
console.log(` Overwritten: ${overwritten} existing variable(s)`);
|
|
1209
|
+
if (skipped > 0)
|
|
1210
|
+
console.log(` Skipped: ${skipped} existing variable(s) (--no-overwrite)`);
|
|
1211
|
+
}
|
|
1212
|
+
catch (error) {
|
|
1213
|
+
const err = error;
|
|
1214
|
+
console.error('❌ Failed to copy env file:', err.message);
|
|
1215
|
+
process.exit(1);
|
|
1216
|
+
}
|
|
1217
|
+
});
|
|
1093
1218
|
}
|
|
1094
1219
|
export default init_secrets;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "lsh-framework",
|
|
3
|
-
"version": "3.1.
|
|
3
|
+
"version": "3.1.25",
|
|
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": {
|
|
@@ -1,268 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Storacha Commands
|
|
3
|
-
* Manage Storacha (IPFS network) authentication and configuration
|
|
4
|
-
*/
|
|
5
|
-
import { getStorachaClient } from '../lib/storacha-client.js';
|
|
6
|
-
export function registerStorachaCommands(program) {
|
|
7
|
-
const storacha = program
|
|
8
|
-
.command('storacha')
|
|
9
|
-
.description('Manage Storacha (IPFS network) sync');
|
|
10
|
-
// Login command
|
|
11
|
-
storacha
|
|
12
|
-
.command('login <email>')
|
|
13
|
-
.description('Authenticate with Storacha via email')
|
|
14
|
-
.action(async (email) => {
|
|
15
|
-
try {
|
|
16
|
-
const client = getStorachaClient();
|
|
17
|
-
console.log('\n🔐 Storacha Authentication\n');
|
|
18
|
-
console.log('━'.repeat(60));
|
|
19
|
-
console.log('');
|
|
20
|
-
await client.login(email);
|
|
21
|
-
}
|
|
22
|
-
catch (error) {
|
|
23
|
-
const err = error;
|
|
24
|
-
console.error('\n❌ Authentication failed:', err.message);
|
|
25
|
-
console.error('');
|
|
26
|
-
console.error('💡 Troubleshooting:');
|
|
27
|
-
console.error(' - Check your email for the verification link');
|
|
28
|
-
console.error(' - Complete signup at: https://console.storacha.network/');
|
|
29
|
-
console.error(' - Ensure you have an active internet connection');
|
|
30
|
-
console.error('');
|
|
31
|
-
process.exit(1);
|
|
32
|
-
}
|
|
33
|
-
});
|
|
34
|
-
// Status command
|
|
35
|
-
storacha
|
|
36
|
-
.command('status')
|
|
37
|
-
.description('Show Storacha authentication and configuration status')
|
|
38
|
-
.action(async () => {
|
|
39
|
-
try {
|
|
40
|
-
const client = getStorachaClient();
|
|
41
|
-
const status = await client.getStatus();
|
|
42
|
-
console.log('\n☁️ Storacha Status\n');
|
|
43
|
-
console.log('━'.repeat(60));
|
|
44
|
-
console.log('');
|
|
45
|
-
// Authentication status
|
|
46
|
-
console.log('🔐 Authentication:');
|
|
47
|
-
if (status.authenticated) {
|
|
48
|
-
console.log(` Status: ✅ Authenticated`);
|
|
49
|
-
if (status.email) {
|
|
50
|
-
console.log(` Email: ${status.email}`);
|
|
51
|
-
}
|
|
52
|
-
}
|
|
53
|
-
else {
|
|
54
|
-
console.log(` Status: ❌ Not authenticated`);
|
|
55
|
-
console.log('');
|
|
56
|
-
console.log('💡 To authenticate:');
|
|
57
|
-
console.log(' lsh storacha login [email protected]');
|
|
58
|
-
}
|
|
59
|
-
console.log('');
|
|
60
|
-
// Network sync status
|
|
61
|
-
console.log('🌐 Network Sync:');
|
|
62
|
-
console.log(` Enabled: ${status.enabled ? '✅ Yes' : '❌ No'}`);
|
|
63
|
-
if (!status.enabled) {
|
|
64
|
-
console.log('');
|
|
65
|
-
console.log('💡 To enable:');
|
|
66
|
-
console.log(' export LSH_STORACHA_ENABLED=true');
|
|
67
|
-
console.log(' # Add to ~/.bashrc or ~/.zshrc for persistence');
|
|
68
|
-
}
|
|
69
|
-
console.log('');
|
|
70
|
-
// Spaces status
|
|
71
|
-
if (status.authenticated) {
|
|
72
|
-
console.log('📦 Spaces:');
|
|
73
|
-
if (status.spaces.length === 0) {
|
|
74
|
-
console.log(' No spaces found');
|
|
75
|
-
console.log('');
|
|
76
|
-
console.log('💡 Create a space:');
|
|
77
|
-
console.log(' lsh storacha space create my-space');
|
|
78
|
-
}
|
|
79
|
-
else {
|
|
80
|
-
console.log(` Total: ${status.spaces.length}`);
|
|
81
|
-
if (status.currentSpace) {
|
|
82
|
-
console.log(` Current: ${status.currentSpace}`);
|
|
83
|
-
}
|
|
84
|
-
console.log('');
|
|
85
|
-
console.log(' Available spaces:');
|
|
86
|
-
status.spaces.forEach(space => {
|
|
87
|
-
const marker = space.name === status.currentSpace ? '→' : ' ';
|
|
88
|
-
console.log(` ${marker} ${space.name}`);
|
|
89
|
-
});
|
|
90
|
-
}
|
|
91
|
-
console.log('');
|
|
92
|
-
}
|
|
93
|
-
// Quick actions
|
|
94
|
-
console.log('💡 Quick Actions:');
|
|
95
|
-
if (!status.authenticated) {
|
|
96
|
-
console.log(' lsh storacha login [email protected]');
|
|
97
|
-
}
|
|
98
|
-
else if (!status.enabled) {
|
|
99
|
-
console.log(' export LSH_STORACHA_ENABLED=true');
|
|
100
|
-
}
|
|
101
|
-
else {
|
|
102
|
-
console.log(' lsh push # Will sync to Storacha network');
|
|
103
|
-
console.log(' lsh pull # Will download from Storacha if needed');
|
|
104
|
-
}
|
|
105
|
-
console.log('');
|
|
106
|
-
console.log('━'.repeat(60));
|
|
107
|
-
console.log('');
|
|
108
|
-
}
|
|
109
|
-
catch (error) {
|
|
110
|
-
const err = error;
|
|
111
|
-
console.error('❌ Failed to get status:', err.message);
|
|
112
|
-
process.exit(1);
|
|
113
|
-
}
|
|
114
|
-
});
|
|
115
|
-
// Space commands
|
|
116
|
-
const space = storacha
|
|
117
|
-
.command('space')
|
|
118
|
-
.description('Manage Storacha spaces');
|
|
119
|
-
space
|
|
120
|
-
.command('create <name>')
|
|
121
|
-
.description('Create a new space')
|
|
122
|
-
.action(async (name) => {
|
|
123
|
-
try {
|
|
124
|
-
const client = getStorachaClient();
|
|
125
|
-
const status = await client.getStatus();
|
|
126
|
-
if (!status.authenticated) {
|
|
127
|
-
console.error('❌ Not authenticated');
|
|
128
|
-
console.error('');
|
|
129
|
-
console.error('💡 First, authenticate:');
|
|
130
|
-
console.error(' lsh storacha login [email protected]');
|
|
131
|
-
process.exit(1);
|
|
132
|
-
}
|
|
133
|
-
console.log(`\n🆕 Creating space: ${name}...\n`);
|
|
134
|
-
await client.createSpace(name);
|
|
135
|
-
console.log('');
|
|
136
|
-
}
|
|
137
|
-
catch (error) {
|
|
138
|
-
const err = error;
|
|
139
|
-
console.error('\n❌ Failed to create space:', err.message);
|
|
140
|
-
process.exit(1);
|
|
141
|
-
}
|
|
142
|
-
});
|
|
143
|
-
space
|
|
144
|
-
.command('auto')
|
|
145
|
-
.description('Auto-select space based on current project (git repo or directory name)')
|
|
146
|
-
.action(async () => {
|
|
147
|
-
try {
|
|
148
|
-
const client = getStorachaClient();
|
|
149
|
-
const status = await client.getStatus();
|
|
150
|
-
if (!status.authenticated) {
|
|
151
|
-
console.error('❌ Not authenticated');
|
|
152
|
-
console.error('');
|
|
153
|
-
console.error('💡 First, authenticate:');
|
|
154
|
-
console.error(' lsh storacha login [email protected]');
|
|
155
|
-
process.exit(1);
|
|
156
|
-
}
|
|
157
|
-
const projectName = client.getProjectName();
|
|
158
|
-
console.log(`\n🔍 Detected project: ${projectName}\n`);
|
|
159
|
-
const spaceName = await client.ensureProjectSpace();
|
|
160
|
-
console.log(`✅ Active space: ${spaceName}\n`);
|
|
161
|
-
}
|
|
162
|
-
catch (error) {
|
|
163
|
-
const err = error;
|
|
164
|
-
console.error('\n❌ Failed to auto-select space:', err.message);
|
|
165
|
-
process.exit(1);
|
|
166
|
-
}
|
|
167
|
-
});
|
|
168
|
-
space
|
|
169
|
-
.command('use <name>')
|
|
170
|
-
.description('Switch to a specific space')
|
|
171
|
-
.action(async (name) => {
|
|
172
|
-
try {
|
|
173
|
-
const client = getStorachaClient();
|
|
174
|
-
const status = await client.getStatus();
|
|
175
|
-
if (!status.authenticated) {
|
|
176
|
-
console.error('❌ Not authenticated');
|
|
177
|
-
console.error('');
|
|
178
|
-
console.error('💡 First, authenticate:');
|
|
179
|
-
console.error(' lsh storacha login [email protected]');
|
|
180
|
-
process.exit(1);
|
|
181
|
-
}
|
|
182
|
-
const found = await client.selectSpace(name);
|
|
183
|
-
if (found) {
|
|
184
|
-
console.log(`\n✅ Switched to space: ${name}\n`);
|
|
185
|
-
}
|
|
186
|
-
else {
|
|
187
|
-
console.error(`\n❌ Space not found: ${name}`);
|
|
188
|
-
console.error('');
|
|
189
|
-
console.error('💡 To list available spaces:');
|
|
190
|
-
console.error(' lsh storacha space list');
|
|
191
|
-
console.error('');
|
|
192
|
-
console.error('💡 To create a new space:');
|
|
193
|
-
console.error(` lsh storacha space create ${name}`);
|
|
194
|
-
process.exit(1);
|
|
195
|
-
}
|
|
196
|
-
}
|
|
197
|
-
catch (error) {
|
|
198
|
-
const err = error;
|
|
199
|
-
console.error('\n❌ Failed to switch space:', err.message);
|
|
200
|
-
process.exit(1);
|
|
201
|
-
}
|
|
202
|
-
});
|
|
203
|
-
space
|
|
204
|
-
.command('list')
|
|
205
|
-
.description('List all spaces')
|
|
206
|
-
.action(async () => {
|
|
207
|
-
try {
|
|
208
|
-
const client = getStorachaClient();
|
|
209
|
-
const status = await client.getStatus();
|
|
210
|
-
if (!status.authenticated) {
|
|
211
|
-
console.error('❌ Not authenticated');
|
|
212
|
-
console.error('');
|
|
213
|
-
console.error('💡 First, authenticate:');
|
|
214
|
-
console.error(' lsh storacha login [email protected]');
|
|
215
|
-
process.exit(1);
|
|
216
|
-
}
|
|
217
|
-
console.log('\n📦 Storacha Spaces\n');
|
|
218
|
-
console.log('━'.repeat(60));
|
|
219
|
-
console.log('');
|
|
220
|
-
if (status.spaces.length === 0) {
|
|
221
|
-
console.log('No spaces found');
|
|
222
|
-
console.log('');
|
|
223
|
-
console.log('💡 Create a space:');
|
|
224
|
-
console.log(' lsh storacha space create my-space');
|
|
225
|
-
}
|
|
226
|
-
else {
|
|
227
|
-
status.spaces.forEach((space, index) => {
|
|
228
|
-
const marker = space.name === status.currentSpace ? '→' : ' ';
|
|
229
|
-
console.log(`${marker} ${index + 1}. ${space.name}`);
|
|
230
|
-
console.log(` DID: ${space.did}`);
|
|
231
|
-
console.log(` Registered: ${space.registered}`);
|
|
232
|
-
console.log('');
|
|
233
|
-
});
|
|
234
|
-
if (status.currentSpace) {
|
|
235
|
-
console.log(`Current space: ${status.currentSpace}`);
|
|
236
|
-
}
|
|
237
|
-
}
|
|
238
|
-
console.log('━'.repeat(60));
|
|
239
|
-
console.log('');
|
|
240
|
-
}
|
|
241
|
-
catch (error) {
|
|
242
|
-
const err = error;
|
|
243
|
-
console.error('❌ Failed to list spaces:', err.message);
|
|
244
|
-
process.exit(1);
|
|
245
|
-
}
|
|
246
|
-
});
|
|
247
|
-
// Enable/disable commands
|
|
248
|
-
storacha
|
|
249
|
-
.command('enable')
|
|
250
|
-
.description('Enable Storacha network sync')
|
|
251
|
-
.action(() => {
|
|
252
|
-
const client = getStorachaClient();
|
|
253
|
-
client.enable();
|
|
254
|
-
console.log('');
|
|
255
|
-
console.log('💡 For persistence, add to your shell profile:');
|
|
256
|
-
console.log(' echo "export LSH_STORACHA_ENABLED=true" >> ~/.bashrc');
|
|
257
|
-
console.log(' # or ~/.zshrc for zsh');
|
|
258
|
-
console.log('');
|
|
259
|
-
});
|
|
260
|
-
storacha
|
|
261
|
-
.command('disable')
|
|
262
|
-
.description('Disable Storacha network sync (local cache only)')
|
|
263
|
-
.action(() => {
|
|
264
|
-
const client = getStorachaClient();
|
|
265
|
-
client.disable();
|
|
266
|
-
console.log('');
|
|
267
|
-
});
|
|
268
|
-
}
|