lsh-framework 1.2.0 → 1.2.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/README.md +40 -3
- package/dist/cli.js +104 -486
- package/dist/commands/doctor.js +427 -0
- package/dist/commands/init.js +371 -0
- package/dist/constants/api.js +94 -0
- package/dist/constants/commands.js +64 -0
- package/dist/constants/config.js +56 -0
- package/dist/constants/database.js +21 -0
- package/dist/constants/errors.js +79 -0
- package/dist/constants/index.js +28 -0
- package/dist/constants/paths.js +28 -0
- package/dist/constants/ui.js +73 -0
- package/dist/constants/validation.js +124 -0
- package/dist/daemon/lshd.js +11 -32
- package/dist/lib/daemon-client-helper.js +7 -4
- package/dist/lib/daemon-client.js +9 -2
- package/dist/lib/format-utils.js +163 -0
- package/dist/lib/job-manager.js +2 -1
- package/dist/lib/platform-utils.js +211 -0
- package/dist/lib/secrets-manager.js +11 -1
- package/dist/lib/string-utils.js +128 -0
- package/dist/services/daemon/daemon-registrar.js +3 -2
- package/dist/services/secrets/secrets.js +54 -30
- package/package.json +10 -74
- package/dist/app.js +0 -33
- package/dist/cicd/analytics.js +0 -261
- package/dist/cicd/auth.js +0 -269
- package/dist/cicd/cache-manager.js +0 -172
- package/dist/cicd/data-retention.js +0 -305
- package/dist/cicd/performance-monitor.js +0 -224
- package/dist/cicd/webhook-receiver.js +0 -640
- package/dist/commands/api.js +0 -346
- package/dist/commands/theme.js +0 -261
- package/dist/commands/zsh-import.js +0 -240
- package/dist/components/App.js +0 -1
- package/dist/components/Divider.js +0 -29
- package/dist/components/REPL.js +0 -43
- package/dist/components/Terminal.js +0 -232
- package/dist/components/UserInput.js +0 -30
- package/dist/daemon/api-server.js +0 -316
- package/dist/daemon/monitoring-api.js +0 -220
- package/dist/lib/api-error-handler.js +0 -185
- package/dist/lib/associative-arrays.js +0 -285
- package/dist/lib/base-api-server.js +0 -290
- package/dist/lib/brace-expansion.js +0 -160
- package/dist/lib/builtin-commands.js +0 -439
- package/dist/lib/executors/builtin-executor.js +0 -52
- package/dist/lib/extended-globbing.js +0 -411
- package/dist/lib/extended-parameter-expansion.js +0 -227
- package/dist/lib/interactive-shell.js +0 -460
- package/dist/lib/job-builtins.js +0 -582
- package/dist/lib/pathname-expansion.js +0 -216
- package/dist/lib/script-runner.js +0 -226
- package/dist/lib/shell-executor.js +0 -2504
- package/dist/lib/shell-parser.js +0 -958
- package/dist/lib/shell-types.js +0 -6
- package/dist/lib/shell.lib.js +0 -40
- package/dist/lib/theme-manager.js +0 -476
- package/dist/lib/variable-expansion.js +0 -385
- package/dist/lib/zsh-compatibility.js +0 -659
- package/dist/lib/zsh-import-manager.js +0 -707
- package/dist/lib/zsh-options.js +0 -328
- package/dist/pipeline/job-tracker.js +0 -491
- package/dist/pipeline/mcli-bridge.js +0 -309
- package/dist/pipeline/pipeline-service.js +0 -1119
- package/dist/pipeline/workflow-engine.js +0 -870
- package/dist/services/api/api.js +0 -58
- package/dist/services/api/auth.js +0 -35
- package/dist/services/api/config.js +0 -7
- package/dist/services/api/file.js +0 -22
- package/dist/services/shell/shell.js +0 -28
- package/dist/services/zapier.js +0 -16
- package/dist/simple-api-server.js +0 -148
|
@@ -0,0 +1,371 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* LSH Init Command
|
|
3
|
+
* Interactive setup wizard for first-time configuration
|
|
4
|
+
*/
|
|
5
|
+
import inquirer from 'inquirer';
|
|
6
|
+
import chalk from 'chalk';
|
|
7
|
+
import * as fs from 'fs/promises';
|
|
8
|
+
import * as path from 'path';
|
|
9
|
+
import * as crypto from 'crypto';
|
|
10
|
+
import { createClient } from '@supabase/supabase-js';
|
|
11
|
+
import ora from 'ora';
|
|
12
|
+
import { getPlatformPaths } from '../lib/platform-utils.js';
|
|
13
|
+
/**
|
|
14
|
+
* Register init commands
|
|
15
|
+
*/
|
|
16
|
+
export function registerInitCommands(program) {
|
|
17
|
+
program
|
|
18
|
+
.command('init')
|
|
19
|
+
.description('Interactive setup wizard (first-time configuration)')
|
|
20
|
+
.option('--local', 'Use local-only encryption (no cloud sync)')
|
|
21
|
+
.option('--supabase', 'Use Supabase cloud storage')
|
|
22
|
+
.option('--postgres', 'Use self-hosted PostgreSQL')
|
|
23
|
+
.option('--skip-test', 'Skip connection testing')
|
|
24
|
+
.action(async (options) => {
|
|
25
|
+
try {
|
|
26
|
+
await runSetupWizard(options);
|
|
27
|
+
}
|
|
28
|
+
catch (error) {
|
|
29
|
+
const err = error;
|
|
30
|
+
console.error(chalk.red('\n❌ Setup failed:'), err.message);
|
|
31
|
+
process.exit(1);
|
|
32
|
+
}
|
|
33
|
+
});
|
|
34
|
+
}
|
|
35
|
+
/**
|
|
36
|
+
* Run the interactive setup wizard
|
|
37
|
+
*/
|
|
38
|
+
async function runSetupWizard(options) {
|
|
39
|
+
console.log(chalk.bold.cyan('\n🔐 LSH Secrets Manager - Setup Wizard'));
|
|
40
|
+
console.log(chalk.gray('━'.repeat(50)));
|
|
41
|
+
console.log('');
|
|
42
|
+
// Check if already configured
|
|
43
|
+
const existingConfig = await checkExistingConfig();
|
|
44
|
+
if (existingConfig) {
|
|
45
|
+
const { overwrite } = await inquirer.prompt([
|
|
46
|
+
{
|
|
47
|
+
type: 'confirm',
|
|
48
|
+
name: 'overwrite',
|
|
49
|
+
message: 'Configuration already exists. Overwrite?',
|
|
50
|
+
default: false,
|
|
51
|
+
},
|
|
52
|
+
]);
|
|
53
|
+
if (!overwrite) {
|
|
54
|
+
console.log(chalk.yellow('\n⚠️ Setup cancelled. Existing configuration preserved.'));
|
|
55
|
+
console.log(chalk.gray('\nRun'), chalk.cyan('lsh doctor'), chalk.gray('to check your current setup.'));
|
|
56
|
+
return;
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
// Determine storage type
|
|
60
|
+
let storageType;
|
|
61
|
+
if (options.local) {
|
|
62
|
+
storageType = 'local';
|
|
63
|
+
}
|
|
64
|
+
else if (options.postgres) {
|
|
65
|
+
storageType = 'postgres';
|
|
66
|
+
}
|
|
67
|
+
else if (options.supabase) {
|
|
68
|
+
storageType = 'supabase';
|
|
69
|
+
}
|
|
70
|
+
else {
|
|
71
|
+
// Ask user
|
|
72
|
+
const { storage } = await inquirer.prompt([
|
|
73
|
+
{
|
|
74
|
+
type: 'list',
|
|
75
|
+
name: 'storage',
|
|
76
|
+
message: 'Choose storage backend:',
|
|
77
|
+
choices: [
|
|
78
|
+
{
|
|
79
|
+
name: 'Supabase (free, cloud-hosted, recommended)',
|
|
80
|
+
value: 'supabase',
|
|
81
|
+
},
|
|
82
|
+
{
|
|
83
|
+
name: 'Local encryption (file-based, no cloud sync)',
|
|
84
|
+
value: 'local',
|
|
85
|
+
},
|
|
86
|
+
{
|
|
87
|
+
name: 'Self-hosted PostgreSQL',
|
|
88
|
+
value: 'postgres',
|
|
89
|
+
},
|
|
90
|
+
],
|
|
91
|
+
default: 'supabase',
|
|
92
|
+
},
|
|
93
|
+
]);
|
|
94
|
+
storageType = storage;
|
|
95
|
+
}
|
|
96
|
+
const config = {
|
|
97
|
+
storageType,
|
|
98
|
+
encryptionKey: generateEncryptionKey(),
|
|
99
|
+
};
|
|
100
|
+
// Configure based on storage type
|
|
101
|
+
if (storageType === 'supabase') {
|
|
102
|
+
await configureSupabase(config, options.skipTest);
|
|
103
|
+
}
|
|
104
|
+
else if (storageType === 'postgres') {
|
|
105
|
+
await configurePostgres(config, options.skipTest);
|
|
106
|
+
}
|
|
107
|
+
else {
|
|
108
|
+
await configureLocal(config);
|
|
109
|
+
}
|
|
110
|
+
// Save configuration
|
|
111
|
+
await saveConfiguration(config);
|
|
112
|
+
// Show success message
|
|
113
|
+
showSuccessMessage(config);
|
|
114
|
+
}
|
|
115
|
+
/**
|
|
116
|
+
* Check if LSH is already configured
|
|
117
|
+
*/
|
|
118
|
+
async function checkExistingConfig() {
|
|
119
|
+
try {
|
|
120
|
+
const envPath = path.join(process.cwd(), '.env');
|
|
121
|
+
// Read file directly without access check to avoid TOCTOU race condition
|
|
122
|
+
const content = await fs.readFile(envPath, 'utf-8');
|
|
123
|
+
return content.includes('LSH_SECRETS_KEY') ||
|
|
124
|
+
content.includes('SUPABASE_URL') ||
|
|
125
|
+
content.includes('DATABASE_URL');
|
|
126
|
+
}
|
|
127
|
+
catch {
|
|
128
|
+
return false;
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
/**
|
|
132
|
+
* Configure Supabase
|
|
133
|
+
*/
|
|
134
|
+
async function configureSupabase(config, skipTest) {
|
|
135
|
+
console.log(chalk.cyan('\n📦 Supabase Configuration'));
|
|
136
|
+
console.log(chalk.gray('Need credentials? Visit: https://supabase.com/dashboard'));
|
|
137
|
+
console.log('');
|
|
138
|
+
const answers = await inquirer.prompt([
|
|
139
|
+
{
|
|
140
|
+
type: 'input',
|
|
141
|
+
name: 'url',
|
|
142
|
+
message: 'Enter your Supabase URL:',
|
|
143
|
+
validate: (input) => {
|
|
144
|
+
if (!input.trim())
|
|
145
|
+
return 'URL is required';
|
|
146
|
+
if (!input.startsWith('https://'))
|
|
147
|
+
return 'URL must start with https://';
|
|
148
|
+
if (!input.includes('.supabase.co'))
|
|
149
|
+
return 'Must be a valid Supabase URL';
|
|
150
|
+
return true;
|
|
151
|
+
},
|
|
152
|
+
},
|
|
153
|
+
{
|
|
154
|
+
type: 'password',
|
|
155
|
+
name: 'key',
|
|
156
|
+
message: 'Enter your Supabase anon key:',
|
|
157
|
+
mask: '*',
|
|
158
|
+
validate: (input) => {
|
|
159
|
+
if (!input.trim())
|
|
160
|
+
return 'Anon key is required';
|
|
161
|
+
if (input.length < 100)
|
|
162
|
+
return 'Anon key seems too short';
|
|
163
|
+
return true;
|
|
164
|
+
},
|
|
165
|
+
},
|
|
166
|
+
]);
|
|
167
|
+
config.supabaseUrl = answers.url.trim();
|
|
168
|
+
config.supabaseKey = answers.key.trim();
|
|
169
|
+
// Test connection
|
|
170
|
+
if (!skipTest && config.supabaseUrl && config.supabaseKey) {
|
|
171
|
+
await testSupabaseConnection(config.supabaseUrl, config.supabaseKey);
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
/**
|
|
175
|
+
* Test Supabase connection
|
|
176
|
+
*/
|
|
177
|
+
async function testSupabaseConnection(url, key) {
|
|
178
|
+
const spinner = ora('Testing Supabase connection...').start();
|
|
179
|
+
try {
|
|
180
|
+
const supabase = createClient(url, key);
|
|
181
|
+
// Try to query the database (even if table doesn't exist, connection will work)
|
|
182
|
+
const { error } = await supabase.from('lsh_secrets').select('count').limit(0);
|
|
183
|
+
// Connection successful (404 table not found is fine - means connection works)
|
|
184
|
+
if (!error || error.code === 'PGRST116' || error.message.includes('relation')) {
|
|
185
|
+
spinner.succeed(chalk.green('✅ Connection successful!'));
|
|
186
|
+
}
|
|
187
|
+
else {
|
|
188
|
+
spinner.fail(chalk.red('❌ Connection failed'));
|
|
189
|
+
throw new Error(`Supabase error: ${error.message}`);
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
catch (error) {
|
|
193
|
+
spinner.fail(chalk.red('❌ Connection failed'));
|
|
194
|
+
const err = error;
|
|
195
|
+
throw new Error(`Could not connect to Supabase: ${err.message}`);
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
/**
|
|
199
|
+
* Configure self-hosted PostgreSQL
|
|
200
|
+
*/
|
|
201
|
+
async function configurePostgres(config, skipTest) {
|
|
202
|
+
console.log(chalk.cyan('\n🐘 PostgreSQL Configuration'));
|
|
203
|
+
console.log('');
|
|
204
|
+
const { url } = await inquirer.prompt([
|
|
205
|
+
{
|
|
206
|
+
type: 'input',
|
|
207
|
+
name: 'url',
|
|
208
|
+
message: 'Enter PostgreSQL connection URL:',
|
|
209
|
+
default: 'postgresql://user:password@localhost:5432/lsh',
|
|
210
|
+
validate: (input) => {
|
|
211
|
+
if (!input.trim())
|
|
212
|
+
return 'Connection URL is required';
|
|
213
|
+
if (!input.startsWith('postgres'))
|
|
214
|
+
return 'Must be a valid PostgreSQL URL';
|
|
215
|
+
return true;
|
|
216
|
+
},
|
|
217
|
+
},
|
|
218
|
+
]);
|
|
219
|
+
config.postgresUrl = url.trim();
|
|
220
|
+
if (!skipTest) {
|
|
221
|
+
const spinner = ora('Testing PostgreSQL connection...').start();
|
|
222
|
+
// Note: We'll skip actual testing for now as we don't have pg client imported
|
|
223
|
+
spinner.info(chalk.yellow('⚠️ Connection test skipped. Run "lsh doctor" after setup to verify.'));
|
|
224
|
+
}
|
|
225
|
+
}
|
|
226
|
+
/**
|
|
227
|
+
* Configure local-only mode
|
|
228
|
+
*/
|
|
229
|
+
async function configureLocal(config) {
|
|
230
|
+
console.log(chalk.cyan('\n💾 Local Encryption Mode'));
|
|
231
|
+
console.log(chalk.gray('Secrets will be encrypted locally. No cloud sync available.'));
|
|
232
|
+
console.log('');
|
|
233
|
+
const paths = getPlatformPaths();
|
|
234
|
+
console.log(chalk.gray('Encrypted secrets will be stored in:'));
|
|
235
|
+
console.log(chalk.cyan(` ${path.join(paths.dataDir, 'encrypted')}`));
|
|
236
|
+
console.log('');
|
|
237
|
+
}
|
|
238
|
+
/**
|
|
239
|
+
* Generate a secure encryption key
|
|
240
|
+
*/
|
|
241
|
+
function generateEncryptionKey() {
|
|
242
|
+
return crypto.randomBytes(32).toString('hex');
|
|
243
|
+
}
|
|
244
|
+
/**
|
|
245
|
+
* Save configuration to .env file
|
|
246
|
+
*/
|
|
247
|
+
async function saveConfiguration(config) {
|
|
248
|
+
const spinner = ora('Saving configuration...').start();
|
|
249
|
+
try {
|
|
250
|
+
const envPath = path.join(process.cwd(), '.env');
|
|
251
|
+
let envContent = '';
|
|
252
|
+
// Try to read existing .env
|
|
253
|
+
try {
|
|
254
|
+
envContent = await fs.readFile(envPath, 'utf-8');
|
|
255
|
+
}
|
|
256
|
+
catch {
|
|
257
|
+
// File doesn't exist, start fresh
|
|
258
|
+
}
|
|
259
|
+
// Update or add configuration
|
|
260
|
+
const updates = {
|
|
261
|
+
LSH_SECRETS_KEY: config.encryptionKey,
|
|
262
|
+
};
|
|
263
|
+
if (config.storageType === 'supabase' && config.supabaseUrl && config.supabaseKey) {
|
|
264
|
+
updates.SUPABASE_URL = config.supabaseUrl;
|
|
265
|
+
updates.SUPABASE_ANON_KEY = config.supabaseKey;
|
|
266
|
+
}
|
|
267
|
+
if (config.storageType === 'postgres' && config.postgresUrl) {
|
|
268
|
+
updates.DATABASE_URL = config.postgresUrl;
|
|
269
|
+
}
|
|
270
|
+
if (config.storageType === 'local') {
|
|
271
|
+
updates.LSH_STORAGE_MODE = 'local';
|
|
272
|
+
}
|
|
273
|
+
// Update .env content
|
|
274
|
+
for (const [key, value] of Object.entries(updates)) {
|
|
275
|
+
const regex = new RegExp(`^${key}=.*$`, 'm');
|
|
276
|
+
if (regex.test(envContent)) {
|
|
277
|
+
envContent = envContent.replace(regex, `${key}=${value}`);
|
|
278
|
+
}
|
|
279
|
+
else {
|
|
280
|
+
if (envContent && !envContent.endsWith('\n')) {
|
|
281
|
+
envContent += '\n';
|
|
282
|
+
}
|
|
283
|
+
envContent += `${key}=${value}\n`;
|
|
284
|
+
}
|
|
285
|
+
}
|
|
286
|
+
// Write .env file
|
|
287
|
+
await fs.writeFile(envPath, envContent, 'utf-8');
|
|
288
|
+
// Update .gitignore
|
|
289
|
+
await updateGitignore();
|
|
290
|
+
spinner.succeed(chalk.green('✅ Configuration saved'));
|
|
291
|
+
}
|
|
292
|
+
catch (error) {
|
|
293
|
+
spinner.fail(chalk.red('❌ Failed to save configuration'));
|
|
294
|
+
throw error;
|
|
295
|
+
}
|
|
296
|
+
}
|
|
297
|
+
/**
|
|
298
|
+
* Update .gitignore to include .env
|
|
299
|
+
*/
|
|
300
|
+
async function updateGitignore() {
|
|
301
|
+
try {
|
|
302
|
+
const gitignorePath = path.join(process.cwd(), '.gitignore');
|
|
303
|
+
let gitignoreContent = '';
|
|
304
|
+
try {
|
|
305
|
+
gitignoreContent = await fs.readFile(gitignorePath, 'utf-8');
|
|
306
|
+
}
|
|
307
|
+
catch {
|
|
308
|
+
// File doesn't exist
|
|
309
|
+
}
|
|
310
|
+
if (!gitignoreContent.includes('.env')) {
|
|
311
|
+
if (gitignoreContent && !gitignoreContent.endsWith('\n')) {
|
|
312
|
+
gitignoreContent += '\n';
|
|
313
|
+
}
|
|
314
|
+
gitignoreContent += '\n# LSH secrets\n.env\n';
|
|
315
|
+
await fs.writeFile(gitignorePath, gitignoreContent, 'utf-8');
|
|
316
|
+
}
|
|
317
|
+
}
|
|
318
|
+
catch {
|
|
319
|
+
// Ignore errors with .gitignore
|
|
320
|
+
}
|
|
321
|
+
}
|
|
322
|
+
/**
|
|
323
|
+
* Show success message with next steps
|
|
324
|
+
*/
|
|
325
|
+
function showSuccessMessage(config) {
|
|
326
|
+
console.log('');
|
|
327
|
+
console.log(chalk.bold.green('✨ Setup complete!'));
|
|
328
|
+
console.log(chalk.gray('━'.repeat(50)));
|
|
329
|
+
console.log('');
|
|
330
|
+
// Show encryption key
|
|
331
|
+
console.log(chalk.yellow('📝 Your encryption key (save this securely):'));
|
|
332
|
+
console.log(chalk.cyan(` ${config.encryptionKey}`));
|
|
333
|
+
console.log('');
|
|
334
|
+
console.log(chalk.gray(' This key is saved in your .env file.'));
|
|
335
|
+
console.log(chalk.gray(' Share it with your team to sync secrets.'));
|
|
336
|
+
console.log('');
|
|
337
|
+
// Storage info
|
|
338
|
+
if (config.storageType === 'supabase') {
|
|
339
|
+
console.log(chalk.cyan('☁️ Using Supabase cloud storage'));
|
|
340
|
+
}
|
|
341
|
+
else if (config.storageType === 'postgres') {
|
|
342
|
+
console.log(chalk.cyan('🐘 Using PostgreSQL storage'));
|
|
343
|
+
}
|
|
344
|
+
else {
|
|
345
|
+
console.log(chalk.cyan('💾 Using local encryption'));
|
|
346
|
+
}
|
|
347
|
+
console.log('');
|
|
348
|
+
// Next steps
|
|
349
|
+
console.log(chalk.bold('🚀 Next steps:'));
|
|
350
|
+
console.log('');
|
|
351
|
+
console.log(chalk.gray(' 1. Verify your setup:'));
|
|
352
|
+
console.log(chalk.cyan(' lsh doctor'));
|
|
353
|
+
console.log('');
|
|
354
|
+
if (config.storageType !== 'local') {
|
|
355
|
+
console.log(chalk.gray(' 2. Push your secrets:'));
|
|
356
|
+
console.log(chalk.cyan(' lsh push --env dev'));
|
|
357
|
+
console.log('');
|
|
358
|
+
console.log(chalk.gray(' 3. On another machine:'));
|
|
359
|
+
console.log(chalk.cyan(' lsh init ') + chalk.gray('# Use the same credentials'));
|
|
360
|
+
console.log(chalk.cyan(' lsh pull --env dev'));
|
|
361
|
+
}
|
|
362
|
+
else {
|
|
363
|
+
console.log(chalk.gray(' 2. Start managing secrets:'));
|
|
364
|
+
console.log(chalk.cyan(' lsh set API_KEY myvalue'));
|
|
365
|
+
console.log(chalk.cyan(' lsh list'));
|
|
366
|
+
}
|
|
367
|
+
console.log('');
|
|
368
|
+
console.log(chalk.gray('📖 Documentation: https://github.com/gwicho38/lsh'));
|
|
369
|
+
console.log('');
|
|
370
|
+
}
|
|
371
|
+
export default registerInitCommands;
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* HTTP/API strings
|
|
3
|
+
*
|
|
4
|
+
* All API endpoints, HTTP headers, content types, and API-related constants.
|
|
5
|
+
*/
|
|
6
|
+
export const ENDPOINTS = {
|
|
7
|
+
// Health and status
|
|
8
|
+
HEALTH: '/health',
|
|
9
|
+
ROOT: '/',
|
|
10
|
+
// Authentication
|
|
11
|
+
AUTH: '/api/auth',
|
|
12
|
+
AUTH_REGISTER: '/auth/register',
|
|
13
|
+
AUTH_LOGIN: '/auth/login',
|
|
14
|
+
AUTH_API_KEY: '/auth/api-key',
|
|
15
|
+
// Jobs API
|
|
16
|
+
API_STATUS: '/api/status',
|
|
17
|
+
API_JOBS: '/api/jobs',
|
|
18
|
+
API_JOB_BY_ID: '/api/jobs/:id',
|
|
19
|
+
API_JOB_START: '/api/jobs/:id/start',
|
|
20
|
+
API_JOB_STOP: '/api/jobs/:id/stop',
|
|
21
|
+
API_JOB_TRIGGER: '/api/jobs/:id/trigger',
|
|
22
|
+
API_JOB_DELETE: '/api/jobs/:id',
|
|
23
|
+
API_JOBS_BULK: '/api/jobs/bulk',
|
|
24
|
+
// Events and webhooks
|
|
25
|
+
API_EVENTS: '/api/events',
|
|
26
|
+
API_WEBHOOKS: '/api/webhooks',
|
|
27
|
+
// Export
|
|
28
|
+
API_EXPORT_JOBS: '/api/export/jobs',
|
|
29
|
+
// Supabase sync
|
|
30
|
+
API_SUPABASE_SYNC: '/api/supabase/sync',
|
|
31
|
+
// CI/CD Webhooks
|
|
32
|
+
WEBHOOK_GITHUB: '/webhook/github',
|
|
33
|
+
WEBHOOK_GITLAB: '/webhook/gitlab',
|
|
34
|
+
WEBHOOK_JENKINS: '/webhook/jenkins',
|
|
35
|
+
// Dashboard
|
|
36
|
+
DASHBOARD_ROOT: '/dashboard/',
|
|
37
|
+
DASHBOARD: '/dashboard/',
|
|
38
|
+
DASHBOARD_ANALYTICS: '/dashboard/analytics',
|
|
39
|
+
DASHBOARD_ADMIN: '/dashboard/admin',
|
|
40
|
+
// Pipeline and metrics API
|
|
41
|
+
API_PIPELINES: '/api/pipelines',
|
|
42
|
+
API_METRICS: '/api/metrics',
|
|
43
|
+
// Analytics endpoints
|
|
44
|
+
API_ANALYTICS_REPORT: '/api/analytics/report',
|
|
45
|
+
API_ANALYTICS_TRENDS: '/api/analytics/trends',
|
|
46
|
+
API_ANALYTICS_ANOMALIES: '/api/analytics/anomalies',
|
|
47
|
+
API_ANALYTICS_INSIGHTS: '/api/analytics/insights',
|
|
48
|
+
API_ANALYTICS_PREDICTIONS: '/api/analytics/predictions',
|
|
49
|
+
API_ANALYTICS_COSTS: '/api/analytics/costs',
|
|
50
|
+
API_ANALYTICS_BOTTLENECKS: '/api/analytics/bottlenecks',
|
|
51
|
+
};
|
|
52
|
+
export const HTTP_HEADERS = {
|
|
53
|
+
// Standard headers
|
|
54
|
+
CONTENT_TYPE: 'Content-Type',
|
|
55
|
+
CACHE_CONTROL: 'Cache-Control',
|
|
56
|
+
CONNECTION: 'Connection',
|
|
57
|
+
AUTHORIZATION: 'authorization',
|
|
58
|
+
// Custom headers
|
|
59
|
+
X_API_KEY: 'x-api-key',
|
|
60
|
+
X_ACCEL_BUFFERING: 'X-Accel-Buffering',
|
|
61
|
+
// Webhook headers
|
|
62
|
+
GITHUB_SIGNATURE: 'x-hub-signature-256',
|
|
63
|
+
GITLAB_TOKEN: 'x-gitlab-token',
|
|
64
|
+
JENKINS_TOKEN: 'x-jenkins-token',
|
|
65
|
+
};
|
|
66
|
+
export const CONTENT_TYPES = {
|
|
67
|
+
EVENT_STREAM: 'text/event-stream',
|
|
68
|
+
JSON: 'application/json',
|
|
69
|
+
TEXT_PLAIN: 'text/plain',
|
|
70
|
+
};
|
|
71
|
+
export const CACHE_CONTROL_VALUES = {
|
|
72
|
+
NO_CACHE: 'no-cache',
|
|
73
|
+
};
|
|
74
|
+
export const CONNECTION_VALUES = {
|
|
75
|
+
KEEP_ALIVE: 'keep-alive',
|
|
76
|
+
};
|
|
77
|
+
export const X_ACCEL_BUFFERING_VALUES = {
|
|
78
|
+
NO: 'no',
|
|
79
|
+
};
|
|
80
|
+
export const AUTH = {
|
|
81
|
+
BEARER_PREFIX: 'Bearer ',
|
|
82
|
+
};
|
|
83
|
+
export const SOCKET_EVENTS = {
|
|
84
|
+
PIPELINE_UPDATE: 'pipeline_event',
|
|
85
|
+
METRICS_UPDATE: 'metrics_update',
|
|
86
|
+
};
|
|
87
|
+
export const METRICS = {
|
|
88
|
+
TOTAL_BUILDS: 'total_builds',
|
|
89
|
+
SUCCESSFUL_BUILDS: 'successful_builds',
|
|
90
|
+
FAILED_BUILDS: 'failed_builds',
|
|
91
|
+
};
|
|
92
|
+
export const REDIS_KEY_TEMPLATES = {
|
|
93
|
+
PIPELINE: 'pipeline:${eventId}',
|
|
94
|
+
};
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Command names and CLI strings
|
|
3
|
+
*
|
|
4
|
+
* All command names, subcommands, and CLI-related strings.
|
|
5
|
+
*/
|
|
6
|
+
export const CLI = {
|
|
7
|
+
NAME: 'lsh',
|
|
8
|
+
DESCRIPTION: 'LSH - Encrypted secrets manager with automatic rotation and team sync',
|
|
9
|
+
BANNER: 'LSH - Encrypted Secrets Manager with Automatic Rotation',
|
|
10
|
+
};
|
|
11
|
+
export const COMMANDS = {
|
|
12
|
+
// Core commands
|
|
13
|
+
SCRIPT: 'script <file>',
|
|
14
|
+
CONFIG: 'config',
|
|
15
|
+
ZSH: 'zsh',
|
|
16
|
+
HELP: 'help',
|
|
17
|
+
// Secrets commands
|
|
18
|
+
PUSH: 'push',
|
|
19
|
+
PULL: 'pull',
|
|
20
|
+
LIST: 'list',
|
|
21
|
+
ENV: 'env [environment]',
|
|
22
|
+
KEY: 'key',
|
|
23
|
+
CREATE: 'create',
|
|
24
|
+
SYNC: 'sync',
|
|
25
|
+
STATUS: 'status',
|
|
26
|
+
INFO: 'info',
|
|
27
|
+
GET: 'get [key]',
|
|
28
|
+
SET: 'set [key] [value]',
|
|
29
|
+
DELETE: 'delete',
|
|
30
|
+
};
|
|
31
|
+
export const JOB_COMMANDS = {
|
|
32
|
+
SECRETS_SYNC: 'secrets_sync',
|
|
33
|
+
};
|
|
34
|
+
export const JOB_STATUSES = {
|
|
35
|
+
CREATED: 'created',
|
|
36
|
+
RUNNING: 'running',
|
|
37
|
+
STOPPED: 'stopped',
|
|
38
|
+
COMPLETED: 'completed',
|
|
39
|
+
FAILED: 'failed',
|
|
40
|
+
KILLED: 'killed',
|
|
41
|
+
};
|
|
42
|
+
export const JOB_TYPES = {
|
|
43
|
+
SHELL: 'shell',
|
|
44
|
+
SYSTEM: 'system',
|
|
45
|
+
SCHEDULED: 'scheduled',
|
|
46
|
+
SERVICE: 'service',
|
|
47
|
+
};
|
|
48
|
+
export const IPC_COMMANDS = {
|
|
49
|
+
STATUS: 'status',
|
|
50
|
+
ADD_JOB: 'addJob',
|
|
51
|
+
START_JOB: 'startJob',
|
|
52
|
+
TRIGGER_JOB: 'triggerJob',
|
|
53
|
+
STOP_JOB: 'stopJob',
|
|
54
|
+
LIST_JOBS: 'listJobs',
|
|
55
|
+
GET_JOB: 'getJob',
|
|
56
|
+
REMOVE_JOB: 'removeJob',
|
|
57
|
+
RESTART: 'restart',
|
|
58
|
+
STOP: 'stop',
|
|
59
|
+
};
|
|
60
|
+
export const PLATFORMS = {
|
|
61
|
+
GITHUB: 'github',
|
|
62
|
+
GITLAB: 'gitlab',
|
|
63
|
+
JENKINS: 'jenkins',
|
|
64
|
+
};
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Configuration keys and environment variables
|
|
3
|
+
*
|
|
4
|
+
* All environment variable names, configuration keys, and default values.
|
|
5
|
+
*/
|
|
6
|
+
export const ENV_VARS = {
|
|
7
|
+
// Core environment
|
|
8
|
+
NODE_ENV: 'NODE_ENV',
|
|
9
|
+
USER: 'USER',
|
|
10
|
+
HOSTNAME: 'HOSTNAME',
|
|
11
|
+
// LSH API configuration
|
|
12
|
+
LSH_API_ENABLED: 'LSH_API_ENABLED',
|
|
13
|
+
LSH_API_PORT: 'LSH_API_PORT',
|
|
14
|
+
LSH_API_KEY: 'LSH_API_KEY',
|
|
15
|
+
LSH_JWT_SECRET: 'LSH_JWT_SECRET',
|
|
16
|
+
LSH_ALLOW_DANGEROUS_COMMANDS: 'LSH_ALLOW_DANGEROUS_COMMANDS',
|
|
17
|
+
// Secrets management
|
|
18
|
+
LSH_SECRETS_KEY: 'LSH_SECRETS_KEY',
|
|
19
|
+
// Webhooks
|
|
20
|
+
LSH_ENABLE_WEBHOOKS: 'LSH_ENABLE_WEBHOOKS',
|
|
21
|
+
WEBHOOK_PORT: 'WEBHOOK_PORT',
|
|
22
|
+
GITHUB_WEBHOOK_SECRET: 'GITHUB_WEBHOOK_SECRET',
|
|
23
|
+
GITLAB_WEBHOOK_SECRET: 'GITLAB_WEBHOOK_SECRET',
|
|
24
|
+
JENKINS_WEBHOOK_SECRET: 'JENKINS_WEBHOOK_SECRET',
|
|
25
|
+
// Database and persistence
|
|
26
|
+
DATABASE_URL: 'DATABASE_URL',
|
|
27
|
+
SUPABASE_URL: 'SUPABASE_URL',
|
|
28
|
+
SUPABASE_ANON_KEY: 'SUPABASE_ANON_KEY',
|
|
29
|
+
REDIS_URL: 'REDIS_URL',
|
|
30
|
+
// Monitoring
|
|
31
|
+
MONITORING_API_PORT: 'MONITORING_API_PORT',
|
|
32
|
+
};
|
|
33
|
+
export const DEFAULTS = {
|
|
34
|
+
// Version
|
|
35
|
+
VERSION: '0.5.1',
|
|
36
|
+
// Ports
|
|
37
|
+
API_PORT: 3030,
|
|
38
|
+
WEBHOOK_PORT: 3033,
|
|
39
|
+
MONITORING_API_PORT: 3031,
|
|
40
|
+
// URLs
|
|
41
|
+
REDIS_URL: 'redis://localhost:6379',
|
|
42
|
+
DATABASE_URL: 'postgresql://localhost:5432/cicd',
|
|
43
|
+
// Timeouts and intervals
|
|
44
|
+
CHECK_INTERVAL_MS: 2000,
|
|
45
|
+
REQUEST_TIMEOUT_MS: 10000,
|
|
46
|
+
// Sizes
|
|
47
|
+
MAX_BUFFER_SIZE_BYTES: 1024 * 1024, // 1MB
|
|
48
|
+
MAX_LOG_SIZE_BYTES: 10 * 1024 * 1024, // 10MB
|
|
49
|
+
MAX_COMMAND_LENGTH: 10000,
|
|
50
|
+
// Limits
|
|
51
|
+
MAX_COMMAND_CHAINS: 5,
|
|
52
|
+
MAX_PIPE_USAGE: 3,
|
|
53
|
+
// Cache and retention
|
|
54
|
+
REDIS_CACHE_EXPIRY_SECONDS: 3600, // 1 hour
|
|
55
|
+
METRICS_RETENTION_SECONDS: 30 * 24 * 60 * 60, // 30 days
|
|
56
|
+
};
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Database tables and schema constants
|
|
3
|
+
*
|
|
4
|
+
* All database table names, column names, and database-related constants.
|
|
5
|
+
*/
|
|
6
|
+
export const TABLES = {
|
|
7
|
+
// Shell-related tables
|
|
8
|
+
SHELL_HISTORY: 'shell_history',
|
|
9
|
+
SHELL_JOBS: 'shell_jobs',
|
|
10
|
+
SHELL_CONFIGURATION: 'shell_configuration',
|
|
11
|
+
SHELL_ALIASES: 'shell_aliases',
|
|
12
|
+
SHELL_FUNCTIONS: 'shell_functions',
|
|
13
|
+
SHELL_SESSIONS: 'shell_sessions',
|
|
14
|
+
SHELL_COMPLETIONS: 'shell_completions',
|
|
15
|
+
// CI/CD tables
|
|
16
|
+
PIPELINE_EVENTS: 'pipeline_events',
|
|
17
|
+
// Trading/ML tables (legacy)
|
|
18
|
+
TRADING_DISCLOSURES: 'trading_disclosures',
|
|
19
|
+
POLITICIANS: 'politicians',
|
|
20
|
+
DATA_PULL_JOBS: 'data_pull_jobs',
|
|
21
|
+
};
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Error messages
|
|
3
|
+
*
|
|
4
|
+
* All error messages, error prefixes, and error templates used throughout LSH.
|
|
5
|
+
* Use template strings with ${variable} for dynamic content.
|
|
6
|
+
*/
|
|
7
|
+
export const ERRORS = {
|
|
8
|
+
// General errors
|
|
9
|
+
ERROR_PREFIX: 'Error: ${message}',
|
|
10
|
+
ERROR_UNKNOWN: 'Unknown error',
|
|
11
|
+
ERROR_UNKNOWN_COMMAND: 'error: unknown command',
|
|
12
|
+
ERROR_UNKNOWN_COMMAND_PREFIX: 'error: unknown command',
|
|
13
|
+
// Daemon errors
|
|
14
|
+
DAEMON_ALREADY_RUNNING: 'Daemon is already running',
|
|
15
|
+
ERROR_DAEMON_ALREADY_RUNNING: 'Another daemon instance is already running',
|
|
16
|
+
SOCKET_NOT_FOUND: 'Daemon socket not found at ${socketPath}. Is the daemon running?',
|
|
17
|
+
SOCKET_PERMISSION_DENIED: 'Permission denied to access socket at ${socketPath}. Check file permissions.',
|
|
18
|
+
NOT_CONNECTED: 'Not connected to daemon',
|
|
19
|
+
RESPONSE_TOO_LARGE: 'Daemon response too large, truncating buffer',
|
|
20
|
+
REQUEST_TIMEOUT: 'Request timeout after 10 seconds for command: ${command}',
|
|
21
|
+
// Job errors
|
|
22
|
+
JOB_NOT_FOUND: 'Job ${jobId} not found',
|
|
23
|
+
// Script and config errors
|
|
24
|
+
ERROR_SCRIPT_PREFIX: 'Script error: ${message}',
|
|
25
|
+
ERROR_CONFIG_PREFIX: 'Config error: ${message}',
|
|
26
|
+
ERROR_SCRIPT_NOT_FOUND: 'Script file not found: ${scriptPath}',
|
|
27
|
+
ERROR_CONFIG_NOT_FOUND: 'Configuration file not found: ${rcFile}',
|
|
28
|
+
// File errors
|
|
29
|
+
FILE_NOT_FOUND: 'File not found: ${filePath}',
|
|
30
|
+
INVALID_FILENAME: 'Invalid filename: ${filename}. Filenames must not contain path separators or special characters.',
|
|
31
|
+
// ZSH compatibility errors
|
|
32
|
+
ERROR_ZSH_COMPAT_PREFIX: 'ZSH compatibility error: ${message}',
|
|
33
|
+
// Environment validation errors
|
|
34
|
+
INVALID_ENV_CONFIG: 'Invalid environment configuration. Check logs for details.',
|
|
35
|
+
// Command validation errors
|
|
36
|
+
COMMAND_EMPTY_STRING: 'Command must be a non-empty string',
|
|
37
|
+
COMMAND_WHITESPACE_ONLY: 'Command cannot be empty or whitespace only',
|
|
38
|
+
COMMAND_TOO_LONG: 'Command exceeds maximum length of ${maxLength} characters',
|
|
39
|
+
COMMAND_NOT_WHITELISTED: 'Command \'${commandName}\' is not in whitelist',
|
|
40
|
+
COMMAND_VALIDATION_FAILED: 'Command validation failed: ${errors}',
|
|
41
|
+
// Secrets errors
|
|
42
|
+
INVALID_ENCRYPTED_FORMAT: 'Invalid encrypted format',
|
|
43
|
+
DECRYPTION_FAILED_MESSAGE: `Failed to decrypt secrets.
|
|
44
|
+
|
|
45
|
+
This could happen if:
|
|
46
|
+
1. The LSH_SECRETS_KEY is incorrect or has changed
|
|
47
|
+
2. The encrypted data is corrupted
|
|
48
|
+
3. The data was encrypted with a different key
|
|
49
|
+
|
|
50
|
+
To fix this:
|
|
51
|
+
- Verify LSH_SECRETS_KEY matches the key used to encrypt
|
|
52
|
+
- Or re-push your secrets with the current key: lsh push --env <environment>`,
|
|
53
|
+
DESTRUCTIVE_CHANGE: 'Destructive change detected',
|
|
54
|
+
NO_SECRETS_FOUND: 'No secrets found for file: ${filename} in environment: ${environment}',
|
|
55
|
+
NO_ENCRYPTED_DATA: 'No encrypted data found for environment: ${environment}',
|
|
56
|
+
// Security errors
|
|
57
|
+
DELETE_ROOT: 'Attempting to delete root filesystem',
|
|
58
|
+
MKFS_DETECTED: 'Filesystem formatting command detected',
|
|
59
|
+
DD_DETECTED: 'Direct disk write detected',
|
|
60
|
+
PRIV_ESCALATION: 'Privilege escalation attempt',
|
|
61
|
+
PASSWORD_MOD: 'Password modification attempt',
|
|
62
|
+
REMOTE_EXEC_CURL: 'Remote code execution via curl',
|
|
63
|
+
REMOTE_EXEC_WGET: 'Remote code execution via wget',
|
|
64
|
+
REVERSE_SHELL: 'Reverse shell attempt with netcat',
|
|
65
|
+
READ_SHADOW: 'Attempting to read shadow password file',
|
|
66
|
+
READ_PASSWD: 'Attempting to read user account file',
|
|
67
|
+
ACCESS_SSH_KEY: 'Attempting to access SSH private keys',
|
|
68
|
+
KILL_INIT: 'Attempting to kill init process',
|
|
69
|
+
KILL_SSHD: 'Attempting to kill SSH daemon',
|
|
70
|
+
BASE64_COMMAND: 'Base64 encoded command detected',
|
|
71
|
+
DYNAMIC_EVAL: 'Dynamic command evaluation detected',
|
|
72
|
+
NULL_BYTE: 'Null byte injection detected',
|
|
73
|
+
};
|
|
74
|
+
export const RISK_LEVELS = {
|
|
75
|
+
CRITICAL: 'critical',
|
|
76
|
+
HIGH: 'high',
|
|
77
|
+
MEDIUM: 'medium',
|
|
78
|
+
LOW: 'low',
|
|
79
|
+
};
|