lsh-framework 0.5.4

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 (90) hide show
  1. package/.env.example +51 -0
  2. package/README.md +399 -0
  3. package/dist/app.js +33 -0
  4. package/dist/cicd/analytics.js +261 -0
  5. package/dist/cicd/auth.js +269 -0
  6. package/dist/cicd/cache-manager.js +172 -0
  7. package/dist/cicd/data-retention.js +305 -0
  8. package/dist/cicd/performance-monitor.js +224 -0
  9. package/dist/cicd/webhook-receiver.js +634 -0
  10. package/dist/cli.js +500 -0
  11. package/dist/commands/api.js +343 -0
  12. package/dist/commands/self.js +318 -0
  13. package/dist/commands/theme.js +257 -0
  14. package/dist/commands/zsh-import.js +240 -0
  15. package/dist/components/App.js +1 -0
  16. package/dist/components/Divider.js +29 -0
  17. package/dist/components/REPL.js +43 -0
  18. package/dist/components/Terminal.js +232 -0
  19. package/dist/components/UserInput.js +30 -0
  20. package/dist/daemon/api-server.js +315 -0
  21. package/dist/daemon/job-registry.js +554 -0
  22. package/dist/daemon/lshd.js +822 -0
  23. package/dist/daemon/monitoring-api.js +220 -0
  24. package/dist/examples/supabase-integration.js +106 -0
  25. package/dist/lib/api-error-handler.js +183 -0
  26. package/dist/lib/associative-arrays.js +285 -0
  27. package/dist/lib/base-api-server.js +290 -0
  28. package/dist/lib/base-command-registrar.js +286 -0
  29. package/dist/lib/base-job-manager.js +293 -0
  30. package/dist/lib/brace-expansion.js +160 -0
  31. package/dist/lib/builtin-commands.js +439 -0
  32. package/dist/lib/cloud-config-manager.js +347 -0
  33. package/dist/lib/command-validator.js +190 -0
  34. package/dist/lib/completion-system.js +344 -0
  35. package/dist/lib/cron-job-manager.js +364 -0
  36. package/dist/lib/daemon-client-helper.js +141 -0
  37. package/dist/lib/daemon-client.js +501 -0
  38. package/dist/lib/database-persistence.js +638 -0
  39. package/dist/lib/database-schema.js +259 -0
  40. package/dist/lib/enhanced-history-system.js +246 -0
  41. package/dist/lib/env-validator.js +265 -0
  42. package/dist/lib/executors/builtin-executor.js +52 -0
  43. package/dist/lib/extended-globbing.js +411 -0
  44. package/dist/lib/extended-parameter-expansion.js +227 -0
  45. package/dist/lib/floating-point-arithmetic.js +256 -0
  46. package/dist/lib/history-system.js +245 -0
  47. package/dist/lib/interactive-shell.js +460 -0
  48. package/dist/lib/job-builtins.js +580 -0
  49. package/dist/lib/job-manager.js +386 -0
  50. package/dist/lib/job-storage-database.js +156 -0
  51. package/dist/lib/job-storage-memory.js +73 -0
  52. package/dist/lib/logger.js +274 -0
  53. package/dist/lib/lshrc-init.js +177 -0
  54. package/dist/lib/pathname-expansion.js +216 -0
  55. package/dist/lib/prompt-system.js +328 -0
  56. package/dist/lib/script-runner.js +226 -0
  57. package/dist/lib/secrets-manager.js +193 -0
  58. package/dist/lib/shell-executor.js +2504 -0
  59. package/dist/lib/shell-parser.js +958 -0
  60. package/dist/lib/shell-types.js +6 -0
  61. package/dist/lib/shell.lib.js +40 -0
  62. package/dist/lib/supabase-client.js +58 -0
  63. package/dist/lib/theme-manager.js +476 -0
  64. package/dist/lib/variable-expansion.js +385 -0
  65. package/dist/lib/zsh-compatibility.js +658 -0
  66. package/dist/lib/zsh-import-manager.js +699 -0
  67. package/dist/lib/zsh-options.js +328 -0
  68. package/dist/pipeline/job-tracker.js +491 -0
  69. package/dist/pipeline/mcli-bridge.js +302 -0
  70. package/dist/pipeline/pipeline-service.js +1116 -0
  71. package/dist/pipeline/workflow-engine.js +867 -0
  72. package/dist/services/api/api.js +58 -0
  73. package/dist/services/api/auth.js +35 -0
  74. package/dist/services/api/config.js +7 -0
  75. package/dist/services/api/file.js +22 -0
  76. package/dist/services/cron/cron-registrar.js +235 -0
  77. package/dist/services/cron/cron.js +9 -0
  78. package/dist/services/daemon/daemon-registrar.js +565 -0
  79. package/dist/services/daemon/daemon.js +9 -0
  80. package/dist/services/lib/lib.js +86 -0
  81. package/dist/services/log-file-extractor.js +170 -0
  82. package/dist/services/secrets/secrets.js +94 -0
  83. package/dist/services/shell/shell.js +28 -0
  84. package/dist/services/supabase/supabase-registrar.js +367 -0
  85. package/dist/services/supabase/supabase.js +9 -0
  86. package/dist/services/zapier.js +16 -0
  87. package/dist/simple-api-server.js +148 -0
  88. package/dist/store/store.js +31 -0
  89. package/dist/util/lib.util.js +11 -0
  90. package/package.json +144 -0
@@ -0,0 +1,347 @@
1
+ /**
2
+ * Cloud Configuration Manager
3
+ * Manages shell configuration with Supabase persistence
4
+ */
5
+ import DatabasePersistence from './database-persistence.js';
6
+ import * as fs from 'fs';
7
+ import * as path from 'path';
8
+ import * as os from 'os';
9
+ export class CloudConfigManager {
10
+ databasePersistence;
11
+ options;
12
+ localConfig = new Map();
13
+ cloudConfig = new Map();
14
+ syncTimer;
15
+ constructor(options = {}) {
16
+ this.options = {
17
+ userId: undefined,
18
+ enableCloudSync: true,
19
+ localConfigPath: path.join(os.homedir(), '.lshrc'),
20
+ syncInterval: 60000, // 1 minute
21
+ ...options,
22
+ };
23
+ this.databasePersistence = new DatabasePersistence(this.options.userId);
24
+ this.loadLocalConfig();
25
+ if (this.options.enableCloudSync) {
26
+ this.initializeCloudSync();
27
+ }
28
+ }
29
+ /**
30
+ * Load local configuration file
31
+ */
32
+ loadLocalConfig() {
33
+ try {
34
+ if (fs.existsSync(this.options.localConfigPath)) {
35
+ const content = fs.readFileSync(this.options.localConfigPath, 'utf8');
36
+ const config = JSON.parse(content);
37
+ Object.entries(config).forEach(([key, value]) => {
38
+ this.localConfig.set(key, {
39
+ key,
40
+ value,
41
+ type: this.getType(value),
42
+ isDefault: false,
43
+ });
44
+ });
45
+ }
46
+ }
47
+ catch (error) {
48
+ console.error('Failed to load local config:', error);
49
+ }
50
+ }
51
+ /**
52
+ * Save local configuration file
53
+ */
54
+ saveLocalConfig() {
55
+ try {
56
+ const config = {};
57
+ this.localConfig.forEach((configValue, key) => {
58
+ config[key] = configValue.value;
59
+ });
60
+ fs.writeFileSync(this.options.localConfigPath, JSON.stringify(config, null, 2));
61
+ }
62
+ catch (error) {
63
+ console.error('Failed to save local config:', error);
64
+ }
65
+ }
66
+ /**
67
+ * Get type of a value
68
+ */
69
+ getType(value) {
70
+ if (typeof value === 'string')
71
+ return 'string';
72
+ if (typeof value === 'number')
73
+ return 'number';
74
+ if (typeof value === 'boolean')
75
+ return 'boolean';
76
+ if (Array.isArray(value))
77
+ return 'array';
78
+ if (typeof value === 'object' && value !== null)
79
+ return 'object';
80
+ return 'string';
81
+ }
82
+ /**
83
+ * Initialize cloud synchronization
84
+ */
85
+ async initializeCloudSync() {
86
+ try {
87
+ const isConnected = await this.databasePersistence.testConnection();
88
+ if (isConnected) {
89
+ console.log('✅ Cloud config sync enabled');
90
+ await this.loadCloudConfig();
91
+ this.startSyncTimer();
92
+ }
93
+ else {
94
+ console.log('⚠️ Cloud config sync disabled - database not available');
95
+ }
96
+ }
97
+ catch (error) {
98
+ console.error('Failed to initialize cloud config sync:', error);
99
+ }
100
+ }
101
+ /**
102
+ * Load configuration from cloud
103
+ */
104
+ async loadCloudConfig() {
105
+ try {
106
+ const cloudConfigs = await this.databasePersistence.getConfiguration();
107
+ cloudConfigs.forEach(config => {
108
+ this.cloudConfig.set(config.config_key, {
109
+ key: config.config_key,
110
+ value: this.parseConfigValue(config.config_value, config.config_type),
111
+ type: config.config_type,
112
+ description: config.description,
113
+ isDefault: config.is_default,
114
+ });
115
+ });
116
+ // Merge cloud config with local config
117
+ this.mergeConfigurations();
118
+ }
119
+ catch (error) {
120
+ console.error('Failed to load cloud config:', error);
121
+ }
122
+ }
123
+ /**
124
+ * Parse configuration value based on type
125
+ */
126
+ parseConfigValue(value, type) {
127
+ try {
128
+ switch (type) {
129
+ case 'string':
130
+ return value;
131
+ case 'number':
132
+ return parseFloat(value);
133
+ case 'boolean':
134
+ return value === 'true';
135
+ case 'array':
136
+ case 'object':
137
+ return JSON.parse(value);
138
+ default:
139
+ return value;
140
+ }
141
+ }
142
+ catch (error) {
143
+ console.error(`Failed to parse config value ${value} as ${type}:`, error);
144
+ return value;
145
+ }
146
+ }
147
+ /**
148
+ * Serialize configuration value to string
149
+ */
150
+ serializeConfigValue(value, type) {
151
+ switch (type) {
152
+ case 'string':
153
+ case 'number':
154
+ case 'boolean':
155
+ return String(value);
156
+ case 'array':
157
+ case 'object':
158
+ return JSON.stringify(value);
159
+ default:
160
+ return String(value);
161
+ }
162
+ }
163
+ /**
164
+ * Merge cloud and local configurations
165
+ */
166
+ mergeConfigurations() {
167
+ // Cloud config takes precedence for non-local overrides
168
+ this.cloudConfig.forEach((cloudValue, key) => {
169
+ if (!this.localConfig.has(key)) {
170
+ this.localConfig.set(key, cloudValue);
171
+ }
172
+ });
173
+ }
174
+ /**
175
+ * Start periodic synchronization timer
176
+ */
177
+ startSyncTimer() {
178
+ this.syncTimer = setInterval(() => {
179
+ this.syncToCloud();
180
+ }, this.options.syncInterval);
181
+ }
182
+ /**
183
+ * Stop synchronization timer
184
+ */
185
+ stopSyncTimer() {
186
+ if (this.syncTimer) {
187
+ clearInterval(this.syncTimer);
188
+ this.syncTimer = undefined;
189
+ }
190
+ }
191
+ /**
192
+ * Synchronize local configuration to cloud
193
+ */
194
+ async syncToCloud() {
195
+ if (!this.options.enableCloudSync) {
196
+ return;
197
+ }
198
+ try {
199
+ for (const [key, configValue] of this.localConfig) {
200
+ await this.databasePersistence.saveConfiguration({
201
+ config_key: key,
202
+ config_value: this.serializeConfigValue(configValue.value, configValue.type),
203
+ config_type: configValue.type,
204
+ description: configValue.description,
205
+ is_default: configValue.isDefault,
206
+ });
207
+ }
208
+ }
209
+ catch (error) {
210
+ console.error('Failed to sync config to cloud:', error);
211
+ }
212
+ }
213
+ /**
214
+ * Get configuration value
215
+ */
216
+ get(key, defaultValue) {
217
+ const configValue = this.localConfig.get(key);
218
+ return configValue ? configValue.value : defaultValue;
219
+ }
220
+ /**
221
+ * Set configuration value
222
+ */
223
+ set(key, value, description) {
224
+ const configValue = {
225
+ key,
226
+ value,
227
+ type: this.getType(value),
228
+ description,
229
+ isDefault: false,
230
+ };
231
+ this.localConfig.set(key, configValue);
232
+ this.saveLocalConfig();
233
+ // Sync to cloud if enabled
234
+ if (this.options.enableCloudSync) {
235
+ this.syncToCloud().catch(error => {
236
+ console.error('Failed to sync config to cloud:', error);
237
+ });
238
+ }
239
+ }
240
+ /**
241
+ * Get all configuration keys
242
+ */
243
+ getKeys() {
244
+ return Array.from(this.localConfig.keys());
245
+ }
246
+ /**
247
+ * Get all configuration entries
248
+ */
249
+ getAll() {
250
+ return Array.from(this.localConfig.values());
251
+ }
252
+ /**
253
+ * Check if configuration key exists
254
+ */
255
+ has(key) {
256
+ return this.localConfig.has(key);
257
+ }
258
+ /**
259
+ * Delete configuration key
260
+ */
261
+ delete(key) {
262
+ this.localConfig.delete(key);
263
+ this.saveLocalConfig();
264
+ // Sync to cloud if enabled
265
+ if (this.options.enableCloudSync) {
266
+ this.syncToCloud().catch(error => {
267
+ console.error('Failed to sync config deletion to cloud:', error);
268
+ });
269
+ }
270
+ }
271
+ /**
272
+ * Reset configuration to defaults
273
+ */
274
+ reset() {
275
+ this.localConfig.clear();
276
+ this.saveLocalConfig();
277
+ if (this.options.enableCloudSync) {
278
+ this.syncToCloud().catch(error => {
279
+ console.error('Failed to sync config reset to cloud:', error);
280
+ });
281
+ }
282
+ }
283
+ /**
284
+ * Export configuration to JSON
285
+ */
286
+ export() {
287
+ const config = {};
288
+ this.localConfig.forEach((configValue, key) => {
289
+ config[key] = configValue.value;
290
+ });
291
+ return JSON.stringify(config, null, 2);
292
+ }
293
+ /**
294
+ * Import configuration from JSON
295
+ */
296
+ import(configJson) {
297
+ try {
298
+ const config = JSON.parse(configJson);
299
+ Object.entries(config).forEach(([key, value]) => {
300
+ this.set(key, value);
301
+ });
302
+ }
303
+ catch (error) {
304
+ console.error('Failed to import configuration:', error);
305
+ throw new Error('Invalid configuration JSON');
306
+ }
307
+ }
308
+ /**
309
+ * Get configuration statistics
310
+ */
311
+ getStats() {
312
+ const types = {};
313
+ this.localConfig.forEach(configValue => {
314
+ types[configValue.type] = (types[configValue.type] || 0) + 1;
315
+ });
316
+ return {
317
+ totalKeys: this.localConfig.size,
318
+ localKeys: this.localConfig.size,
319
+ cloudKeys: this.cloudConfig.size,
320
+ types,
321
+ };
322
+ }
323
+ /**
324
+ * Enable or disable cloud sync
325
+ */
326
+ setCloudSyncEnabled(enabled) {
327
+ this.options.enableCloudSync = enabled;
328
+ if (enabled) {
329
+ this.initializeCloudSync();
330
+ }
331
+ else {
332
+ this.stopSyncTimer();
333
+ }
334
+ }
335
+ /**
336
+ * Cleanup resources
337
+ */
338
+ destroy() {
339
+ this.stopSyncTimer();
340
+ if (this.options.enableCloudSync) {
341
+ this.syncToCloud().catch(error => {
342
+ console.error('Failed to final config sync on destroy:', error);
343
+ });
344
+ }
345
+ }
346
+ }
347
+ export default CloudConfigManager;
@@ -0,0 +1,190 @@
1
+ /**
2
+ * Command Validation Utilities
3
+ * Provides security validation for shell commands
4
+ */
5
+ /**
6
+ * Dangerous command patterns that should trigger warnings or blocks
7
+ */
8
+ const DANGEROUS_PATTERNS = [
9
+ // System modification
10
+ { pattern: /rm\s+-rf\s+\/(?!\w)/i, message: 'Attempting to delete root filesystem', level: 'critical' },
11
+ { pattern: /mkfs/i, message: 'Filesystem formatting command detected', level: 'critical' },
12
+ { pattern: /dd\s+.*of=/i, message: 'Direct disk write detected', level: 'critical' },
13
+ // Privilege escalation
14
+ { pattern: /sudo\s+su/i, message: 'Privilege escalation attempt', level: 'high' },
15
+ { pattern: /sudo\s+.*passwd/i, message: 'Password modification attempt', level: 'high' },
16
+ // Network exfiltration
17
+ { pattern: /curl\s+.*\|\s*bash/i, message: 'Remote code execution via curl', level: 'critical' },
18
+ { pattern: /wget\s+.*\|\s*sh/i, message: 'Remote code execution via wget', level: 'critical' },
19
+ { pattern: /nc\s+.*-e/i, message: 'Reverse shell attempt with netcat', level: 'critical' },
20
+ // Data exfiltration
21
+ { pattern: /cat\s+\/etc\/shadow/i, message: 'Attempting to read shadow password file', level: 'critical' },
22
+ { pattern: /cat\s+\/etc\/passwd/i, message: 'Attempting to read user account file', level: 'high' },
23
+ { pattern: /\.ssh\/id_rsa/i, message: 'Attempting to access SSH private keys', level: 'critical' },
24
+ // Process manipulation
25
+ { pattern: /kill\s+-9\s+1\b/i, message: 'Attempting to kill init process', level: 'critical' },
26
+ { pattern: /pkill\s+-9\s+.*sshd/i, message: 'Attempting to kill SSH daemon', level: 'high' },
27
+ // Obfuscation attempts
28
+ { pattern: /\$\(.*base64.*\)/i, message: 'Base64 encoded command detected', level: 'high' },
29
+ { pattern: /eval.*\$\(/i, message: 'Dynamic command evaluation detected', level: 'high' },
30
+ // eslint-disable-next-line no-control-regex
31
+ { pattern: /\x00/i, message: 'Null byte injection detected', level: 'critical' },
32
+ ];
33
+ /**
34
+ * Patterns that should trigger warnings but might be legitimate
35
+ */
36
+ const WARNING_PATTERNS = [
37
+ { pattern: /rm\s+-rf/i, message: 'Recursive deletion command' },
38
+ { pattern: /sudo/i, message: 'Elevated privileges requested' },
39
+ { pattern: /chmod\s+777/i, message: 'Overly permissive file permissions' },
40
+ { pattern: />\s*\/dev\/sda/i, message: 'Writing to disk device' },
41
+ { pattern: /curl\s+.*-k/i, message: 'Insecure SSL certificate validation disabled' },
42
+ { pattern: /:\(\)\{.*:\|:.*\}/i, message: 'Fork bomb pattern detected' },
43
+ ];
44
+ /**
45
+ * Validate a shell command for security issues
46
+ */
47
+ export function validateCommand(command, options = {}) {
48
+ const { allowDangerousCommands = false, maxLength = 10000, requireWhitelist = false, whitelist = [] } = options;
49
+ const result = {
50
+ isValid: true,
51
+ warnings: [],
52
+ errors: [],
53
+ riskLevel: 'low'
54
+ };
55
+ // Basic validation
56
+ if (!command || typeof command !== 'string') {
57
+ result.isValid = false;
58
+ result.errors.push('Command must be a non-empty string');
59
+ result.riskLevel = 'high';
60
+ return result;
61
+ }
62
+ if (command.trim().length === 0) {
63
+ result.isValid = false;
64
+ result.errors.push('Command cannot be empty or whitespace only');
65
+ result.riskLevel = 'high';
66
+ return result;
67
+ }
68
+ if (command.length > maxLength) {
69
+ result.isValid = false;
70
+ result.errors.push(`Command exceeds maximum length of ${maxLength} characters`);
71
+ result.riskLevel = 'high';
72
+ return result;
73
+ }
74
+ // Whitelist validation
75
+ if (requireWhitelist) {
76
+ const commandName = command.trim().split(/\s+/)[0];
77
+ if (!whitelist.includes(commandName)) {
78
+ result.isValid = false;
79
+ result.errors.push(`Command '${commandName}' is not in whitelist`);
80
+ result.riskLevel = 'high';
81
+ return result;
82
+ }
83
+ }
84
+ // Check for dangerous patterns
85
+ for (const { pattern, message, level } of DANGEROUS_PATTERNS) {
86
+ if (pattern.test(command)) {
87
+ if (!allowDangerousCommands) {
88
+ result.isValid = false;
89
+ result.errors.push(`BLOCKED: ${message}`);
90
+ result.riskLevel = level;
91
+ }
92
+ else {
93
+ result.warnings.push(`${message} (allowed by configuration)`);
94
+ if (level === 'critical' || level === 'high') {
95
+ result.riskLevel = level;
96
+ }
97
+ }
98
+ }
99
+ }
100
+ // Check for warning patterns
101
+ for (const { pattern, message } of WARNING_PATTERNS) {
102
+ if (pattern.test(command)) {
103
+ result.warnings.push(message);
104
+ if (result.riskLevel === 'low') {
105
+ result.riskLevel = 'medium';
106
+ }
107
+ }
108
+ }
109
+ // Additional checks for suspicious patterns
110
+ const suspiciousChecks = [
111
+ {
112
+ test: () => (command.match(/;/g) || []).length > 5,
113
+ message: 'Excessive command chaining detected',
114
+ level: 'medium'
115
+ },
116
+ {
117
+ test: () => (command.match(/\|/g) || []).length > 3,
118
+ message: 'Excessive pipe usage detected',
119
+ level: 'medium'
120
+ },
121
+ {
122
+ test: () => /\$\([^)]*\$\(/.test(command),
123
+ message: 'Nested command substitution detected',
124
+ level: 'high'
125
+ },
126
+ {
127
+ // eslint-disable-next-line no-control-regex
128
+ test: () => /[\x00-\x08\x0B\x0C\x0E-\x1F]/.test(command),
129
+ message: 'Control characters detected in command',
130
+ level: 'high'
131
+ }
132
+ ];
133
+ for (const check of suspiciousChecks) {
134
+ if (check.test()) {
135
+ result.warnings.push(check.message);
136
+ if (check.level === 'high' && result.riskLevel !== 'critical') {
137
+ result.riskLevel = check.level;
138
+ }
139
+ else if (check.level === 'medium' && result.riskLevel === 'low') {
140
+ result.riskLevel = check.level;
141
+ }
142
+ }
143
+ }
144
+ return result;
145
+ }
146
+ /**
147
+ * Sanitize a string for use as a shell argument
148
+ * Note: This should be used for individual arguments, not full commands
149
+ */
150
+ export function sanitizeShellArgument(arg) {
151
+ // Escape dangerous shell characters
152
+ return arg.replace(/([;&|`$(){}[\]\\<>'"*?~])/g, '\\$1');
153
+ }
154
+ /**
155
+ * Quote a string for safe use in shell commands
156
+ */
157
+ export function quoteForShell(str) {
158
+ // Use single quotes to prevent expansion, escape any single quotes in the string
159
+ return `'${str.replace(/'/g, "'\\''")}'`;
160
+ }
161
+ /**
162
+ * Parse command and extract the base command name
163
+ */
164
+ export function getCommandName(command) {
165
+ const trimmed = command.trim();
166
+ // Handle sudo and other prefixes
167
+ const parts = trimmed.split(/\s+/);
168
+ let cmdIndex = 0;
169
+ // Handle 'sudo' prefix
170
+ if (parts[0] === 'sudo') {
171
+ cmdIndex = 1;
172
+ }
173
+ // Handle 'env' prefix with environment variables
174
+ else if (parts[0] === 'env') {
175
+ // Skip environment variable assignments (VAR=value format)
176
+ cmdIndex = 1;
177
+ while (cmdIndex < parts.length && parts[cmdIndex].includes('=')) {
178
+ cmdIndex++;
179
+ }
180
+ }
181
+ const cmdPart = parts[cmdIndex] || '';
182
+ // Remove path if present
183
+ return cmdPart.split('/').pop() || '';
184
+ }
185
+ export default {
186
+ validateCommand,
187
+ sanitizeShellArgument,
188
+ quoteForShell,
189
+ getCommandName
190
+ };