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.
Files changed (73) hide show
  1. package/README.md +40 -3
  2. package/dist/cli.js +104 -486
  3. package/dist/commands/doctor.js +427 -0
  4. package/dist/commands/init.js +371 -0
  5. package/dist/constants/api.js +94 -0
  6. package/dist/constants/commands.js +64 -0
  7. package/dist/constants/config.js +56 -0
  8. package/dist/constants/database.js +21 -0
  9. package/dist/constants/errors.js +79 -0
  10. package/dist/constants/index.js +28 -0
  11. package/dist/constants/paths.js +28 -0
  12. package/dist/constants/ui.js +73 -0
  13. package/dist/constants/validation.js +124 -0
  14. package/dist/daemon/lshd.js +11 -32
  15. package/dist/lib/daemon-client-helper.js +7 -4
  16. package/dist/lib/daemon-client.js +9 -2
  17. package/dist/lib/format-utils.js +163 -0
  18. package/dist/lib/job-manager.js +2 -1
  19. package/dist/lib/platform-utils.js +211 -0
  20. package/dist/lib/secrets-manager.js +11 -1
  21. package/dist/lib/string-utils.js +128 -0
  22. package/dist/services/daemon/daemon-registrar.js +3 -2
  23. package/dist/services/secrets/secrets.js +54 -30
  24. package/package.json +10 -74
  25. package/dist/app.js +0 -33
  26. package/dist/cicd/analytics.js +0 -261
  27. package/dist/cicd/auth.js +0 -269
  28. package/dist/cicd/cache-manager.js +0 -172
  29. package/dist/cicd/data-retention.js +0 -305
  30. package/dist/cicd/performance-monitor.js +0 -224
  31. package/dist/cicd/webhook-receiver.js +0 -640
  32. package/dist/commands/api.js +0 -346
  33. package/dist/commands/theme.js +0 -261
  34. package/dist/commands/zsh-import.js +0 -240
  35. package/dist/components/App.js +0 -1
  36. package/dist/components/Divider.js +0 -29
  37. package/dist/components/REPL.js +0 -43
  38. package/dist/components/Terminal.js +0 -232
  39. package/dist/components/UserInput.js +0 -30
  40. package/dist/daemon/api-server.js +0 -316
  41. package/dist/daemon/monitoring-api.js +0 -220
  42. package/dist/lib/api-error-handler.js +0 -185
  43. package/dist/lib/associative-arrays.js +0 -285
  44. package/dist/lib/base-api-server.js +0 -290
  45. package/dist/lib/brace-expansion.js +0 -160
  46. package/dist/lib/builtin-commands.js +0 -439
  47. package/dist/lib/executors/builtin-executor.js +0 -52
  48. package/dist/lib/extended-globbing.js +0 -411
  49. package/dist/lib/extended-parameter-expansion.js +0 -227
  50. package/dist/lib/interactive-shell.js +0 -460
  51. package/dist/lib/job-builtins.js +0 -582
  52. package/dist/lib/pathname-expansion.js +0 -216
  53. package/dist/lib/script-runner.js +0 -226
  54. package/dist/lib/shell-executor.js +0 -2504
  55. package/dist/lib/shell-parser.js +0 -958
  56. package/dist/lib/shell-types.js +0 -6
  57. package/dist/lib/shell.lib.js +0 -40
  58. package/dist/lib/theme-manager.js +0 -476
  59. package/dist/lib/variable-expansion.js +0 -385
  60. package/dist/lib/zsh-compatibility.js +0 -659
  61. package/dist/lib/zsh-import-manager.js +0 -707
  62. package/dist/lib/zsh-options.js +0 -328
  63. package/dist/pipeline/job-tracker.js +0 -491
  64. package/dist/pipeline/mcli-bridge.js +0 -309
  65. package/dist/pipeline/pipeline-service.js +0 -1119
  66. package/dist/pipeline/workflow-engine.js +0 -870
  67. package/dist/services/api/api.js +0 -58
  68. package/dist/services/api/auth.js +0 -35
  69. package/dist/services/api/config.js +0 -7
  70. package/dist/services/api/file.js +0 -22
  71. package/dist/services/shell/shell.js +0 -28
  72. package/dist/services/zapier.js +0 -16
  73. 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
+ };