lsh-framework 1.3.2 → 1.4.0

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 CHANGED
@@ -28,7 +28,7 @@ JENKINS_WEBHOOK_SECRET=
28
28
  # Database Configuration
29
29
  DATABASE_URL=postgresql://localhost:5432/cicd
30
30
 
31
- # Supabase (Optional)
31
+ # Supabase (REQUIRED for SaaS Platform, optional otherwise)
32
32
  SUPABASE_URL=
33
33
  SUPABASE_ANON_KEY=
34
34
 
@@ -44,8 +44,37 @@ ENABLE_ARCHIVE=false
44
44
  ARCHIVE_S3_BUCKET=
45
45
  ARCHIVE_LOCAL_PATH=/var/backups/cicd
46
46
 
47
+ # ============================================================================
48
+ # SaaS Platform Configuration
49
+ # ============================================================================
50
+
51
+ # SaaS API Server
52
+ LSH_SAAS_API_PORT=3031
53
+ LSH_SAAS_API_HOST=0.0.0.0
54
+ LSH_CORS_ORIGINS=http://localhost:3000,http://localhost:3031
55
+
56
+ # Master Encryption Key (REQUIRED - for encrypting team encryption keys)
57
+ # Generate with: openssl rand -hex 32
58
+ LSH_MASTER_KEY=
59
+ # Alternative: use existing secrets key
60
+ # LSH_SECRETS_KEY=
61
+
62
+ # Email Service (Resend)
63
+ RESEND_API_KEY=
64
+ EMAIL_FROM=noreply@yourdomain.com
65
+ BASE_URL=https://app.yourdomain.com
66
+
67
+ # Stripe Billing
68
+ STRIPE_SECRET_KEY=
69
+ STRIPE_WEBHOOK_SECRET=
70
+ STRIPE_PRICE_PRO_MONTHLY=
71
+ STRIPE_PRICE_PRO_YEARLY=
72
+ STRIPE_PRICE_ENTERPRISE_MONTHLY=
73
+ STRIPE_PRICE_ENTERPRISE_YEARLY=
74
+
47
75
  # Security Notes:
48
76
  # - Generate secrets: openssl rand -hex 32
49
77
  # - Never commit .env file
50
78
  # - Webhook secrets mandatory in production
51
79
  # - Rotate keys regularly
80
+ # - 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 (30 seconds)
20
+ ## Quick Start
21
21
 
22
- ### New in v0.8.2+: Smart Sync (Easiest Way!)
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;