lsh-framework 1.3.2 → 1.4.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.
- package/.env.example +43 -3
- package/README.md +25 -4
- package/dist/cli.js +6 -0
- package/dist/commands/config.js +240 -0
- package/dist/daemon/saas-api-routes.js +778 -0
- package/dist/daemon/saas-api-server.js +225 -0
- package/dist/lib/config-manager.js +321 -0
- package/dist/lib/database-persistence.js +75 -3
- package/dist/lib/env-validator.js +17 -0
- package/dist/lib/local-storage-adapter.js +493 -0
- package/dist/lib/saas-audit.js +213 -0
- package/dist/lib/saas-auth.js +427 -0
- package/dist/lib/saas-billing.js +402 -0
- package/dist/lib/saas-email.js +402 -0
- package/dist/lib/saas-encryption.js +220 -0
- package/dist/lib/saas-organizations.js +592 -0
- package/dist/lib/saas-secrets.js +378 -0
- package/dist/lib/saas-types.js +108 -0
- package/dist/lib/supabase-client.js +77 -11
- package/package.json +13 -2
package/.env.example
CHANGED
|
@@ -1,5 +1,16 @@
|
|
|
1
|
-
# LSH Shell - Environment Configuration
|
|
2
|
-
#
|
|
1
|
+
# LSH Shell - Environment Configuration (DEPRECATED for LSH config)
|
|
2
|
+
#
|
|
3
|
+
# ⚠️ IMPORTANT: LSH configuration should now be in ~/.config/lsh/lshrc
|
|
4
|
+
#
|
|
5
|
+
# Use: lsh config # Edit configuration
|
|
6
|
+
# lsh config set KEY val # Set individual values
|
|
7
|
+
#
|
|
8
|
+
# This .env.example file is kept for reference and backward compatibility.
|
|
9
|
+
# For new installations, use: lsh config init
|
|
10
|
+
#
|
|
11
|
+
# Priority: Environment variables > ~/.config/lsh/lshrc > defaults
|
|
12
|
+
#
|
|
13
|
+
# Use .env files ONLY for application secrets, NOT for LSH configuration
|
|
3
14
|
|
|
4
15
|
# Core Configuration
|
|
5
16
|
USER=your_username
|
|
@@ -28,7 +39,7 @@ JENKINS_WEBHOOK_SECRET=
|
|
|
28
39
|
# Database Configuration
|
|
29
40
|
DATABASE_URL=postgresql://localhost:5432/cicd
|
|
30
41
|
|
|
31
|
-
# Supabase (
|
|
42
|
+
# Supabase (REQUIRED for SaaS Platform, optional otherwise)
|
|
32
43
|
SUPABASE_URL=
|
|
33
44
|
SUPABASE_ANON_KEY=
|
|
34
45
|
|
|
@@ -44,8 +55,37 @@ ENABLE_ARCHIVE=false
|
|
|
44
55
|
ARCHIVE_S3_BUCKET=
|
|
45
56
|
ARCHIVE_LOCAL_PATH=/var/backups/cicd
|
|
46
57
|
|
|
58
|
+
# ============================================================================
|
|
59
|
+
# SaaS Platform Configuration
|
|
60
|
+
# ============================================================================
|
|
61
|
+
|
|
62
|
+
# SaaS API Server
|
|
63
|
+
LSH_SAAS_API_PORT=3031
|
|
64
|
+
LSH_SAAS_API_HOST=0.0.0.0
|
|
65
|
+
LSH_CORS_ORIGINS=http://localhost:3000,http://localhost:3031
|
|
66
|
+
|
|
67
|
+
# Master Encryption Key (REQUIRED - for encrypting team encryption keys)
|
|
68
|
+
# Generate with: openssl rand -hex 32
|
|
69
|
+
LSH_MASTER_KEY=
|
|
70
|
+
# Alternative: use existing secrets key
|
|
71
|
+
# LSH_SECRETS_KEY=
|
|
72
|
+
|
|
73
|
+
# Email Service (Resend)
|
|
74
|
+
RESEND_API_KEY=
|
|
75
|
+
EMAIL_FROM=noreply@yourdomain.com
|
|
76
|
+
BASE_URL=https://app.yourdomain.com
|
|
77
|
+
|
|
78
|
+
# Stripe Billing
|
|
79
|
+
STRIPE_SECRET_KEY=
|
|
80
|
+
STRIPE_WEBHOOK_SECRET=
|
|
81
|
+
STRIPE_PRICE_PRO_MONTHLY=
|
|
82
|
+
STRIPE_PRICE_PRO_YEARLY=
|
|
83
|
+
STRIPE_PRICE_ENTERPRISE_MONTHLY=
|
|
84
|
+
STRIPE_PRICE_ENTERPRISE_YEARLY=
|
|
85
|
+
|
|
47
86
|
# Security Notes:
|
|
48
87
|
# - Generate secrets: openssl rand -hex 32
|
|
49
88
|
# - Never commit .env file
|
|
50
89
|
# - Webhook secrets mandatory in production
|
|
51
90
|
# - Rotate keys regularly
|
|
91
|
+
# - LSH_MASTER_KEY is critical - keep it secure and backed up
|
package/README.md
CHANGED
|
@@ -17,15 +17,36 @@ Traditional secret management tools are either too complex, too expensive, or re
|
|
|
17
17
|
|
|
18
18
|
**Plus, you get a complete shell automation platform as a bonus.**
|
|
19
19
|
|
|
20
|
-
## Quick Start
|
|
20
|
+
## Quick Start
|
|
21
21
|
|
|
22
|
-
|
|
22
|
+
**New to LSH?** See our [Quick Start Guide](docs/QUICK_START.md) for three easy onboarding options:
|
|
23
|
+
1. **Local-Only Mode** - Zero configuration, works immediately (no database needed)
|
|
24
|
+
2. **Local PostgreSQL** - Docker-based setup for local development
|
|
25
|
+
3. **Supabase Cloud** - Full team collaboration features
|
|
26
|
+
|
|
27
|
+
### Quick Install (Works Immediately!)
|
|
28
|
+
|
|
29
|
+
```bash
|
|
30
|
+
# Install LSH
|
|
31
|
+
npm install -g lsh-framework
|
|
32
|
+
|
|
33
|
+
# That's it! LSH works without any database configuration
|
|
34
|
+
# Config: ~/.config/lsh/lshrc (auto-created)
|
|
35
|
+
# Data: ~/.lsh/data/storage.json (local storage)
|
|
36
|
+
|
|
37
|
+
# Start using it right away
|
|
38
|
+
lsh --version
|
|
39
|
+
lsh config # Edit configuration (optional)
|
|
40
|
+
lsh daemon start
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
### Smart Sync (Easiest Way for Cloud!)
|
|
23
44
|
|
|
24
45
|
```bash
|
|
25
46
|
# 1. Install
|
|
26
47
|
npm install -g lsh-framework
|
|
27
48
|
|
|
28
|
-
# 2. Configure Supabase (free tier works!)
|
|
49
|
+
# 2. Configure Supabase (optional - free tier works!)
|
|
29
50
|
# Add to .env:
|
|
30
51
|
# SUPABASE_URL=https://your-project.supabase.co
|
|
31
52
|
# SUPABASE_ANON_KEY=<your-anon-key>
|
|
@@ -38,7 +59,7 @@ lsh sync
|
|
|
38
59
|
# ✅ Auto-generates encryption key
|
|
39
60
|
# ✅ Creates .env from .env.example
|
|
40
61
|
# ✅ Adds .env to .gitignore
|
|
41
|
-
# ✅ Pushes to cloud
|
|
62
|
+
# ✅ Pushes to cloud (if configured) or local storage
|
|
42
63
|
# ✅ Namespaces by repo name
|
|
43
64
|
```
|
|
44
65
|
|
package/dist/cli.js
CHANGED
|
@@ -8,10 +8,12 @@ import selfCommand from './commands/self.js';
|
|
|
8
8
|
import { registerInitCommands } from './commands/init.js';
|
|
9
9
|
import { registerDoctorCommands } from './commands/doctor.js';
|
|
10
10
|
import { registerCompletionCommands } from './commands/completion.js';
|
|
11
|
+
import { registerConfigCommands } from './commands/config.js';
|
|
11
12
|
import { init_daemon } from './services/daemon/daemon.js';
|
|
12
13
|
import { init_supabase } from './services/supabase/supabase.js';
|
|
13
14
|
import { init_cron } from './services/cron/cron.js';
|
|
14
15
|
import { init_secrets } from './services/secrets/secrets.js';
|
|
16
|
+
import { loadGlobalConfigSync } from './lib/config-manager.js';
|
|
15
17
|
import * as fs from 'fs';
|
|
16
18
|
import * as path from 'path';
|
|
17
19
|
import { fileURLToPath } from 'url';
|
|
@@ -72,6 +74,7 @@ program
|
|
|
72
74
|
console.log(' lsh pull --env dev # Pull on another machine');
|
|
73
75
|
console.log('');
|
|
74
76
|
console.log('📚 More Commands:');
|
|
77
|
+
console.log(' config Manage LSH configuration (~/.config/lsh/lshrc)');
|
|
75
78
|
console.log(' supabase Supabase database management');
|
|
76
79
|
console.log(' daemon Daemon management');
|
|
77
80
|
console.log(' cron Cron job management');
|
|
@@ -134,8 +137,11 @@ function findSimilarCommands(input, validCommands) {
|
|
|
134
137
|
// Register async command modules
|
|
135
138
|
(async () => {
|
|
136
139
|
// Essential onboarding commands
|
|
140
|
+
// Load global configuration before anything else
|
|
141
|
+
loadGlobalConfigSync();
|
|
137
142
|
registerInitCommands(program);
|
|
138
143
|
registerDoctorCommands(program);
|
|
144
|
+
registerConfigCommands(program);
|
|
139
145
|
// Secrets management (primary feature)
|
|
140
146
|
await init_secrets(program);
|
|
141
147
|
// Supporting services
|
|
@@ -0,0 +1,240 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Configuration Management Commands
|
|
3
|
+
* Provides commands to manage LSH configuration
|
|
4
|
+
*/
|
|
5
|
+
import { spawn } from 'child_process';
|
|
6
|
+
import { getConfigManager, loadGlobalConfig } from '../lib/config-manager.js';
|
|
7
|
+
import * as fs from 'fs/promises';
|
|
8
|
+
/**
|
|
9
|
+
* Get user's preferred editor
|
|
10
|
+
*/
|
|
11
|
+
function getEditor() {
|
|
12
|
+
return process.env.VISUAL || process.env.EDITOR || 'vi';
|
|
13
|
+
}
|
|
14
|
+
/**
|
|
15
|
+
* Open config file in user's editor
|
|
16
|
+
*/
|
|
17
|
+
async function openInEditor(filePath) {
|
|
18
|
+
const editor = getEditor();
|
|
19
|
+
return new Promise((resolve, reject) => {
|
|
20
|
+
const child = spawn(editor, [filePath], {
|
|
21
|
+
stdio: 'inherit',
|
|
22
|
+
shell: true
|
|
23
|
+
});
|
|
24
|
+
child.on('exit', (code) => {
|
|
25
|
+
if (code === 0) {
|
|
26
|
+
resolve();
|
|
27
|
+
}
|
|
28
|
+
else {
|
|
29
|
+
reject(new Error(`Editor exited with code ${code}`));
|
|
30
|
+
}
|
|
31
|
+
});
|
|
32
|
+
child.on('error', (err) => {
|
|
33
|
+
reject(err);
|
|
34
|
+
});
|
|
35
|
+
});
|
|
36
|
+
}
|
|
37
|
+
/**
|
|
38
|
+
* Register config commands
|
|
39
|
+
*/
|
|
40
|
+
export function registerConfigCommands(program) {
|
|
41
|
+
const config = program
|
|
42
|
+
.command('config')
|
|
43
|
+
.description('Manage LSH configuration');
|
|
44
|
+
// lsh config (open in editor)
|
|
45
|
+
config
|
|
46
|
+
.action(async () => {
|
|
47
|
+
try {
|
|
48
|
+
const manager = getConfigManager();
|
|
49
|
+
// Initialize if doesn't exist
|
|
50
|
+
if (!(await manager.exists())) {
|
|
51
|
+
await manager.initialize();
|
|
52
|
+
}
|
|
53
|
+
const configPath = manager.getConfigPath();
|
|
54
|
+
console.log(`Opening ${configPath} in ${getEditor()}...`);
|
|
55
|
+
await openInEditor(configPath);
|
|
56
|
+
console.log('\n✓ Config file saved');
|
|
57
|
+
console.log(' Restart LSH or run a command to apply changes');
|
|
58
|
+
}
|
|
59
|
+
catch (error) {
|
|
60
|
+
console.error('Failed to open config file:', error);
|
|
61
|
+
process.exit(1);
|
|
62
|
+
}
|
|
63
|
+
});
|
|
64
|
+
// lsh config init
|
|
65
|
+
config
|
|
66
|
+
.command('init')
|
|
67
|
+
.description('Initialize config file with default template')
|
|
68
|
+
.option('-f, --force', 'Overwrite existing config file')
|
|
69
|
+
.action(async (options) => {
|
|
70
|
+
try {
|
|
71
|
+
const manager = getConfigManager();
|
|
72
|
+
const configPath = manager.getConfigPath();
|
|
73
|
+
// Check if exists
|
|
74
|
+
const exists = await manager.exists();
|
|
75
|
+
if (exists && !options.force) {
|
|
76
|
+
console.error(`Config file already exists at ${configPath}`);
|
|
77
|
+
console.error('Use --force to overwrite');
|
|
78
|
+
process.exit(1);
|
|
79
|
+
}
|
|
80
|
+
await manager.initialize();
|
|
81
|
+
}
|
|
82
|
+
catch (error) {
|
|
83
|
+
console.error('Failed to initialize config:', error);
|
|
84
|
+
process.exit(1);
|
|
85
|
+
}
|
|
86
|
+
});
|
|
87
|
+
// lsh config path
|
|
88
|
+
config
|
|
89
|
+
.command('path')
|
|
90
|
+
.description('Show config file path')
|
|
91
|
+
.action(() => {
|
|
92
|
+
const manager = getConfigManager();
|
|
93
|
+
console.log(manager.getConfigPath());
|
|
94
|
+
});
|
|
95
|
+
// lsh config get <key>
|
|
96
|
+
config
|
|
97
|
+
.command('get <key>')
|
|
98
|
+
.description('Get a config value')
|
|
99
|
+
.action(async (key) => {
|
|
100
|
+
try {
|
|
101
|
+
const manager = getConfigManager();
|
|
102
|
+
await manager.load();
|
|
103
|
+
const value = manager.get(key);
|
|
104
|
+
if (value !== undefined) {
|
|
105
|
+
console.log(value);
|
|
106
|
+
}
|
|
107
|
+
else {
|
|
108
|
+
console.error(`Key "${key}" not found in config`);
|
|
109
|
+
process.exit(1);
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
catch (error) {
|
|
113
|
+
console.error('Failed to get config value:', error);
|
|
114
|
+
process.exit(1);
|
|
115
|
+
}
|
|
116
|
+
});
|
|
117
|
+
// lsh config set <key> <value>
|
|
118
|
+
config
|
|
119
|
+
.command('set <key> <value>')
|
|
120
|
+
.description('Set a config value')
|
|
121
|
+
.action(async (key, value) => {
|
|
122
|
+
try {
|
|
123
|
+
const manager = getConfigManager();
|
|
124
|
+
await manager.load();
|
|
125
|
+
await manager.set(key, value);
|
|
126
|
+
console.log(`✓ Set ${key}=${value}`);
|
|
127
|
+
}
|
|
128
|
+
catch (error) {
|
|
129
|
+
console.error('Failed to set config value:', error);
|
|
130
|
+
process.exit(1);
|
|
131
|
+
}
|
|
132
|
+
});
|
|
133
|
+
// lsh config delete <key>
|
|
134
|
+
config
|
|
135
|
+
.command('delete <key>')
|
|
136
|
+
.alias('rm')
|
|
137
|
+
.description('Delete a config value')
|
|
138
|
+
.action(async (key) => {
|
|
139
|
+
try {
|
|
140
|
+
const manager = getConfigManager();
|
|
141
|
+
await manager.load();
|
|
142
|
+
await manager.delete(key);
|
|
143
|
+
console.log(`✓ Deleted ${key}`);
|
|
144
|
+
}
|
|
145
|
+
catch (error) {
|
|
146
|
+
console.error('Failed to delete config value:', error);
|
|
147
|
+
process.exit(1);
|
|
148
|
+
}
|
|
149
|
+
});
|
|
150
|
+
// lsh config list
|
|
151
|
+
config
|
|
152
|
+
.command('list')
|
|
153
|
+
.alias('ls')
|
|
154
|
+
.description('List all config values')
|
|
155
|
+
.option('--show-secrets', 'Show secret values (default: masked)')
|
|
156
|
+
.action(async (options) => {
|
|
157
|
+
try {
|
|
158
|
+
const manager = getConfigManager();
|
|
159
|
+
await manager.load();
|
|
160
|
+
const allConfig = manager.getAll();
|
|
161
|
+
const keys = Object.keys(allConfig).sort();
|
|
162
|
+
if (keys.length === 0) {
|
|
163
|
+
console.log('No configuration found');
|
|
164
|
+
return;
|
|
165
|
+
}
|
|
166
|
+
console.log('\nCurrent Configuration:\n');
|
|
167
|
+
const secretKeys = [
|
|
168
|
+
'LSH_SECRETS_KEY',
|
|
169
|
+
'LSH_MASTER_KEY',
|
|
170
|
+
'LSH_API_KEY',
|
|
171
|
+
'LSH_JWT_SECRET',
|
|
172
|
+
'SUPABASE_ANON_KEY',
|
|
173
|
+
'GITHUB_WEBHOOK_SECRET',
|
|
174
|
+
'GITLAB_WEBHOOK_SECRET',
|
|
175
|
+
'JENKINS_WEBHOOK_SECRET',
|
|
176
|
+
'RESEND_API_KEY',
|
|
177
|
+
'STRIPE_SECRET_KEY',
|
|
178
|
+
'STRIPE_WEBHOOK_SECRET',
|
|
179
|
+
'DATABASE_URL'
|
|
180
|
+
];
|
|
181
|
+
for (const key of keys) {
|
|
182
|
+
const value = allConfig[key];
|
|
183
|
+
if (value === undefined || value === '') {
|
|
184
|
+
continue;
|
|
185
|
+
}
|
|
186
|
+
// Mask secret values unless --show-secrets is passed
|
|
187
|
+
const isSecret = secretKeys.some(sk => key.includes(sk));
|
|
188
|
+
const displayValue = (isSecret && !options.showSecrets)
|
|
189
|
+
? '***' + value.slice(-4)
|
|
190
|
+
: value;
|
|
191
|
+
console.log(` ${key}=${displayValue}`);
|
|
192
|
+
}
|
|
193
|
+
console.log();
|
|
194
|
+
}
|
|
195
|
+
catch (error) {
|
|
196
|
+
console.error('Failed to list config:', error);
|
|
197
|
+
process.exit(1);
|
|
198
|
+
}
|
|
199
|
+
});
|
|
200
|
+
// lsh config show
|
|
201
|
+
config
|
|
202
|
+
.command('show')
|
|
203
|
+
.description('Show config file contents')
|
|
204
|
+
.action(async () => {
|
|
205
|
+
try {
|
|
206
|
+
const manager = getConfigManager();
|
|
207
|
+
const configPath = manager.getConfigPath();
|
|
208
|
+
const exists = await manager.exists();
|
|
209
|
+
if (!exists) {
|
|
210
|
+
console.error(`Config file not found at ${configPath}`);
|
|
211
|
+
console.error('Run: lsh config init');
|
|
212
|
+
process.exit(1);
|
|
213
|
+
}
|
|
214
|
+
const content = await fs.readFile(configPath, 'utf-8');
|
|
215
|
+
console.log(content);
|
|
216
|
+
}
|
|
217
|
+
catch (error) {
|
|
218
|
+
console.error('Failed to show config:', error);
|
|
219
|
+
process.exit(1);
|
|
220
|
+
}
|
|
221
|
+
});
|
|
222
|
+
// lsh config reload
|
|
223
|
+
config
|
|
224
|
+
.command('reload')
|
|
225
|
+
.description('Reload config into current environment')
|
|
226
|
+
.action(async () => {
|
|
227
|
+
try {
|
|
228
|
+
const config = await loadGlobalConfig();
|
|
229
|
+
const keys = Object.keys(config).filter(k => config[k] !== undefined && config[k] !== '');
|
|
230
|
+
console.log(`✓ Reloaded ${keys.length} config values into environment`);
|
|
231
|
+
console.log('\nNote: This only affects the current LSH process.');
|
|
232
|
+
console.log('Restart daemons or shells to apply changes globally.');
|
|
233
|
+
}
|
|
234
|
+
catch (error) {
|
|
235
|
+
console.error('Failed to reload config:', error);
|
|
236
|
+
process.exit(1);
|
|
237
|
+
}
|
|
238
|
+
});
|
|
239
|
+
}
|
|
240
|
+
export default registerConfigCommands;
|