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,285 @@
1
+ /**
2
+ * Associative Arrays Implementation
3
+ * Provides ZSH-compatible associative array functionality
4
+ */
5
+ export class AssociativeArrayManager {
6
+ context;
7
+ constructor() {
8
+ this.context = {
9
+ arrays: new Map(),
10
+ arrayTypes: new Map(),
11
+ };
12
+ }
13
+ /**
14
+ * Declare an associative array with typeset -A
15
+ */
16
+ declareAssociativeArray(name) {
17
+ this.context.arrays.set(name, {});
18
+ this.context.arrayTypes.set(name, 'associative');
19
+ }
20
+ /**
21
+ * Declare an indexed array with typeset -a
22
+ */
23
+ declareIndexedArray(name) {
24
+ this.context.arrays.set(name, {});
25
+ this.context.arrayTypes.set(name, 'indexed');
26
+ }
27
+ /**
28
+ * Set a value in an associative array
29
+ */
30
+ setAssociativeValue(arrayName, key, value) {
31
+ if (!this.context.arrays.has(arrayName)) {
32
+ this.declareAssociativeArray(arrayName);
33
+ }
34
+ const array = this.context.arrays.get(arrayName);
35
+ array[key] = value;
36
+ }
37
+ /**
38
+ * Set a value in an indexed array
39
+ */
40
+ setIndexedValue(arrayName, index, value) {
41
+ if (!this.context.arrays.has(arrayName)) {
42
+ this.declareIndexedArray(arrayName);
43
+ }
44
+ const array = this.context.arrays.get(arrayName);
45
+ array[index.toString()] = value;
46
+ }
47
+ /**
48
+ * Get a value from an array
49
+ */
50
+ getValue(arrayName, key) {
51
+ const array = this.context.arrays.get(arrayName);
52
+ return array ? array[key] : undefined;
53
+ }
54
+ /**
55
+ * Get all keys from an associative array
56
+ */
57
+ getKeys(arrayName) {
58
+ const array = this.context.arrays.get(arrayName);
59
+ if (!array)
60
+ return [];
61
+ const type = this.context.arrayTypes.get(arrayName);
62
+ if (type === 'indexed') {
63
+ // For indexed arrays, return sorted numeric keys
64
+ return Object.keys(array)
65
+ .map(k => parseInt(k, 10))
66
+ .filter(k => !isNaN(k))
67
+ .sort((a, b) => a - b)
68
+ .map(k => k.toString());
69
+ }
70
+ else {
71
+ // For associative arrays, return all keys
72
+ return Object.keys(array);
73
+ }
74
+ }
75
+ /**
76
+ * Get all values from an array
77
+ */
78
+ getValues(arrayName) {
79
+ const array = this.context.arrays.get(arrayName);
80
+ if (!array)
81
+ return [];
82
+ const type = this.context.arrayTypes.get(arrayName);
83
+ if (type === 'indexed') {
84
+ // For indexed arrays, return values in order
85
+ const keys = this.getKeys(arrayName);
86
+ return keys.map(key => array[key]);
87
+ }
88
+ else {
89
+ // For associative arrays, return all values
90
+ return Object.values(array);
91
+ }
92
+ }
93
+ /**
94
+ * Get array length
95
+ */
96
+ getLength(arrayName) {
97
+ const array = this.context.arrays.get(arrayName);
98
+ return array ? Object.keys(array).length : 0;
99
+ }
100
+ /**
101
+ * Check if an array exists
102
+ */
103
+ hasArray(arrayName) {
104
+ return this.context.arrays.has(arrayName);
105
+ }
106
+ /**
107
+ * Get array type
108
+ */
109
+ getArrayType(arrayName) {
110
+ return this.context.arrayTypes.get(arrayName);
111
+ }
112
+ /**
113
+ * Remove an array
114
+ */
115
+ removeArray(arrayName) {
116
+ const hadArray = this.context.arrays.has(arrayName);
117
+ this.context.arrays.delete(arrayName);
118
+ this.context.arrayTypes.delete(arrayName);
119
+ return hadArray;
120
+ }
121
+ /**
122
+ * Get all array names
123
+ */
124
+ getAllArrayNames() {
125
+ return Array.from(this.context.arrays.keys());
126
+ }
127
+ /**
128
+ * Clear all arrays
129
+ */
130
+ clearAllArrays() {
131
+ this.context.arrays.clear();
132
+ this.context.arrayTypes.clear();
133
+ }
134
+ /**
135
+ * Get array slice (for indexed arrays)
136
+ */
137
+ getSlice(arrayName, start, end) {
138
+ const array = this.context.arrays.get(arrayName);
139
+ if (!array)
140
+ return [];
141
+ const type = this.context.arrayTypes.get(arrayName);
142
+ if (type !== 'indexed')
143
+ return [];
144
+ const keys = this.getKeys(arrayName);
145
+ const startIdx = Math.max(0, start - 1); // Convert to 0-based
146
+ const endIdx = end ? Math.min(keys.length, end) : keys.length;
147
+ return keys.slice(startIdx, endIdx).map(key => array[key]);
148
+ }
149
+ /**
150
+ * Expand array reference like ${array[key]} or ${array[@]}
151
+ */
152
+ expandArrayReference(reference) {
153
+ // Match patterns like ${array[key]}, ${array[@]}, ${array[*]}
154
+ const match = reference.match(/^\$\{([^[\]]+)(?:\[([^\]]+)\])?(?:\[@\*\]|\[@\]|\[\*\]|\[@\])?\}$/);
155
+ if (!match)
156
+ return [];
157
+ const arrayName = match[1];
158
+ const key = match[2];
159
+ if (!this.hasArray(arrayName))
160
+ return [];
161
+ if (key) {
162
+ // Single element access
163
+ const value = this.getValue(arrayName, key);
164
+ return value !== undefined ? [value] : [];
165
+ }
166
+ else {
167
+ // All elements access
168
+ return this.getValues(arrayName);
169
+ }
170
+ }
171
+ /**
172
+ * Expand array keys like ${(k)array}
173
+ */
174
+ expandArrayKeys(reference) {
175
+ const match = reference.match(/^\$\{\(k\)([^}]+)\}$/);
176
+ if (!match)
177
+ return [];
178
+ const arrayName = match[1];
179
+ return this.getKeys(arrayName);
180
+ }
181
+ /**
182
+ * Expand array values like ${(v)array}
183
+ */
184
+ expandArrayValues(reference) {
185
+ const match = reference.match(/^\$\{\(v\)([^}]+)\}$/);
186
+ if (!match)
187
+ return [];
188
+ const arrayName = match[1];
189
+ return this.getValues(arrayName);
190
+ }
191
+ /**
192
+ * Expand array length like ${#array}
193
+ */
194
+ expandArrayLength(reference) {
195
+ const match = reference.match(/^\$\{#([^}]+)\}$/);
196
+ if (!match)
197
+ return '';
198
+ const arrayName = match[1];
199
+ return this.getLength(arrayName).toString();
200
+ }
201
+ /**
202
+ * Parse typeset command
203
+ */
204
+ parseTypesetCommand(args) {
205
+ if (args.length === 0) {
206
+ return { success: false, message: 'typeset: missing arguments' };
207
+ }
208
+ for (const arg of args) {
209
+ if (arg === '-A') {
210
+ // Declare associative array - next argument should be the name
211
+ const nameIndex = args.indexOf('-A') + 1;
212
+ if (nameIndex < args.length) {
213
+ const name = args[nameIndex];
214
+ this.declareAssociativeArray(name);
215
+ }
216
+ }
217
+ else if (arg === '-a') {
218
+ // Declare indexed array - next argument should be the name
219
+ const nameIndex = args.indexOf('-a') + 1;
220
+ if (nameIndex < args.length) {
221
+ const name = args[nameIndex];
222
+ this.declareIndexedArray(name);
223
+ }
224
+ }
225
+ else if (arg.includes('=')) {
226
+ // Assignment: name=value or name[key]=value
227
+ const [left, right] = arg.split('=', 2);
228
+ const arrayMatch = left.match(/^([^[\]]+)(?:\[([^\]]+)\])?$/);
229
+ if (arrayMatch) {
230
+ const arrayName = arrayMatch[1];
231
+ const key = arrayMatch[2];
232
+ if (key) {
233
+ // Associative array assignment
234
+ this.setAssociativeValue(arrayName, key, right);
235
+ }
236
+ else {
237
+ // Regular variable assignment (not array)
238
+ // This would be handled by the regular variable system
239
+ }
240
+ }
241
+ }
242
+ }
243
+ return { success: true, message: '' };
244
+ }
245
+ /**
246
+ * Get array information for display
247
+ */
248
+ getArrayInfo(arrayName) {
249
+ if (!this.hasArray(arrayName))
250
+ return null;
251
+ const type = this.getArrayType(arrayName);
252
+ const length = this.getLength(arrayName);
253
+ const keys = this.getKeys(arrayName);
254
+ const values = this.getValues(arrayName);
255
+ return {
256
+ type: type || 'unknown',
257
+ length,
258
+ keys,
259
+ values,
260
+ };
261
+ }
262
+ /**
263
+ * Export array data for serialization
264
+ */
265
+ exportArrays() {
266
+ const arrays = {};
267
+ const types = {};
268
+ for (const [name, array] of this.context.arrays) {
269
+ arrays[name] = { ...array };
270
+ types[name] = this.context.arrayTypes.get(name) || 'unknown';
271
+ }
272
+ return { arrays, types };
273
+ }
274
+ /**
275
+ * Import array data from serialization
276
+ */
277
+ importArrays(data) {
278
+ this.clearAllArrays();
279
+ for (const [name, array] of Object.entries(data.arrays)) {
280
+ this.context.arrays.set(name, { ...array });
281
+ this.context.arrayTypes.set(name, data.types[name]);
282
+ }
283
+ }
284
+ }
285
+ export default AssociativeArrayManager;
@@ -0,0 +1,290 @@
1
+ /**
2
+ * Base API Server
3
+ * Abstract base class for all API servers to eliminate duplication in:
4
+ * - Express middleware setup
5
+ * - Server lifecycle management
6
+ * - Signal handling
7
+ * - Error handling
8
+ */
9
+ import express from 'express';
10
+ import cors from 'cors';
11
+ import helmet from 'helmet';
12
+ import { EventEmitter } from 'events';
13
+ import { createLogger } from './logger.js';
14
+ /**
15
+ * Abstract base class for API servers
16
+ *
17
+ * Provides:
18
+ * - Express app setup with common middleware
19
+ * - Server lifecycle (start/stop)
20
+ * - Signal handling (SIGTERM, SIGINT, SIGHUP)
21
+ * - Error handling (uncaughtException, unhandledRejection)
22
+ * - Request logging
23
+ * - Structured logging
24
+ *
25
+ * @example
26
+ * ```typescript
27
+ * class MyAPIServer extends BaseAPIServer {
28
+ * constructor() {
29
+ * super({ port: 3000 }, 'MyAPI');
30
+ * }
31
+ *
32
+ * protected setupRoutes(): void {
33
+ * this.app.get('/api/hello', (req, res) => {
34
+ * res.json({ message: 'Hello World' });
35
+ * });
36
+ * }
37
+ * }
38
+ * ```
39
+ */
40
+ export class BaseAPIServer extends EventEmitter {
41
+ app;
42
+ server;
43
+ config;
44
+ logger;
45
+ isShuttingDown = false;
46
+ /**
47
+ * Create a new API server
48
+ *
49
+ * @param config - Server configuration
50
+ * @param loggerName - Name for the logger context
51
+ */
52
+ constructor(config, loggerName) {
53
+ super();
54
+ this.config = {
55
+ port: 3000,
56
+ corsOrigins: '*',
57
+ enableHelmet: true,
58
+ jsonLimit: '10mb',
59
+ enableRequestLogging: true,
60
+ enableSignalHandlers: true,
61
+ enableErrorHandlers: true,
62
+ ...config
63
+ };
64
+ this.logger = createLogger(loggerName);
65
+ this.app = express();
66
+ this.setupMiddleware();
67
+ if (this.config.enableErrorHandlers) {
68
+ this.setupErrorHandlers();
69
+ }
70
+ if (this.config.enableSignalHandlers) {
71
+ this.setupSignalHandlers();
72
+ }
73
+ }
74
+ /**
75
+ * Setup Express middleware
76
+ * Can be overridden for custom middleware setup
77
+ */
78
+ setupMiddleware() {
79
+ // Security middleware
80
+ if (this.config.enableHelmet) {
81
+ this.app.use(helmet({
82
+ crossOriginEmbedderPolicy: false,
83
+ }));
84
+ }
85
+ // CORS
86
+ this.app.use(this.configureCORS());
87
+ // Body parsing
88
+ this.app.use(express.json({ limit: this.config.jsonLimit }));
89
+ this.app.use(express.urlencoded({ extended: true }));
90
+ // Request logging
91
+ if (this.config.enableRequestLogging) {
92
+ this.app.use(this.requestLogger.bind(this));
93
+ }
94
+ }
95
+ /**
96
+ * Configure CORS middleware
97
+ * Can be overridden for custom CORS configuration
98
+ */
99
+ configureCORS() {
100
+ const origins = this.config.corsOrigins;
101
+ if (origins === '*') {
102
+ return cors();
103
+ }
104
+ if (Array.isArray(origins)) {
105
+ return cors({
106
+ origin: (origin, callback) => {
107
+ if (!origin || origins.some(pattern => {
108
+ const regex = new RegExp(pattern.replace(/\*/g, '.*'));
109
+ return regex.test(origin);
110
+ })) {
111
+ callback(null, true);
112
+ }
113
+ else {
114
+ callback(new Error('Not allowed by CORS'));
115
+ }
116
+ }
117
+ });
118
+ }
119
+ return cors({ origin: origins });
120
+ }
121
+ /**
122
+ * Request logging middleware
123
+ */
124
+ requestLogger(req, res, next) {
125
+ this.logger.info(`${req.method} ${req.path}`);
126
+ next();
127
+ }
128
+ /**
129
+ * Setup error handlers for uncaught exceptions and unhandled rejections
130
+ */
131
+ setupErrorHandlers() {
132
+ process.on('uncaughtException', (error) => {
133
+ this.logger.error('Uncaught Exception', error);
134
+ this.handleFatalError(error);
135
+ });
136
+ process.on('unhandledRejection', (reason, promise) => {
137
+ this.logger.error('Unhandled Rejection', reason instanceof Error ? reason : new Error(String(reason)), {
138
+ promise: String(promise)
139
+ });
140
+ });
141
+ }
142
+ /**
143
+ * Setup signal handlers for graceful shutdown
144
+ */
145
+ setupSignalHandlers() {
146
+ const signals = ['SIGTERM', 'SIGINT'];
147
+ signals.forEach(signal => {
148
+ process.on(signal, () => {
149
+ this.logger.info(`Received ${signal}, initiating graceful shutdown`);
150
+ this.gracefulShutdown(signal);
151
+ });
152
+ });
153
+ process.on('SIGHUP', () => {
154
+ this.logger.info('Received SIGHUP');
155
+ this.handleSIGHUP();
156
+ });
157
+ }
158
+ /**
159
+ * Handle SIGHUP signal - can be overridden for custom behavior (e.g., reload config)
160
+ */
161
+ handleSIGHUP() {
162
+ this.logger.info('SIGHUP handler not implemented, ignoring');
163
+ }
164
+ /**
165
+ * Handle fatal errors
166
+ * @param error - The fatal error
167
+ */
168
+ handleFatalError(error) {
169
+ this.logger.error('Fatal error, shutting down', error);
170
+ process.exit(1);
171
+ }
172
+ /**
173
+ * Perform graceful shutdown
174
+ * @param signal - The signal that triggered the shutdown
175
+ */
176
+ async gracefulShutdown(signal) {
177
+ if (this.isShuttingDown) {
178
+ this.logger.warn('Shutdown already in progress');
179
+ return;
180
+ }
181
+ this.isShuttingDown = true;
182
+ this.logger.info(`Graceful shutdown initiated by ${signal}`);
183
+ try {
184
+ // Give ongoing requests time to complete
185
+ await this.stop();
186
+ this.logger.info('Server stopped successfully');
187
+ process.exit(0);
188
+ }
189
+ catch (error) {
190
+ this.logger.error('Error during shutdown', error);
191
+ process.exit(1);
192
+ }
193
+ }
194
+ /**
195
+ * Start the API server
196
+ * @returns Promise that resolves when server is listening
197
+ */
198
+ async start() {
199
+ return new Promise((resolve, reject) => {
200
+ try {
201
+ // Setup routes before starting server
202
+ this.setupRoutes();
203
+ this.server = this.app.listen(this.config.port, () => {
204
+ this.logger.info(`Server started on port ${this.config.port}`);
205
+ this.emit('started');
206
+ resolve();
207
+ });
208
+ this.server.on('error', (error) => {
209
+ this.logger.error('Server error', error);
210
+ this.emit('error', error);
211
+ reject(error);
212
+ });
213
+ }
214
+ catch (error) {
215
+ this.logger.error('Failed to start server', error);
216
+ reject(error);
217
+ }
218
+ });
219
+ }
220
+ /**
221
+ * Stop the API server
222
+ * @param timeout - Maximum time to wait for connections to close (ms)
223
+ * @returns Promise that resolves when server is stopped
224
+ */
225
+ async stop(timeout = 5000) {
226
+ return new Promise((resolve, reject) => {
227
+ if (!this.server) {
228
+ resolve();
229
+ return;
230
+ }
231
+ this.logger.info('Stopping server...');
232
+ // Set timeout for force shutdown
233
+ const forceShutdownTimer = setTimeout(() => {
234
+ this.logger.warn('Force closing server after timeout');
235
+ this.server?.close();
236
+ resolve();
237
+ }, timeout);
238
+ this.server?.close((error) => {
239
+ clearTimeout(forceShutdownTimer);
240
+ if (error) {
241
+ this.logger.error('Error stopping server', error);
242
+ reject(error);
243
+ }
244
+ else {
245
+ this.logger.info('Server stopped');
246
+ this.emit('stopped');
247
+ resolve();
248
+ }
249
+ });
250
+ // Allow subclasses to cleanup
251
+ this.onStop();
252
+ });
253
+ }
254
+ /**
255
+ * Hook called when server is stopping
256
+ * Override this to cleanup resources, close connections, etc.
257
+ */
258
+ onStop() {
259
+ // Override in subclasses if needed
260
+ }
261
+ /**
262
+ * Get the Express application
263
+ * @returns The Express app instance
264
+ */
265
+ getApp() {
266
+ return this.app;
267
+ }
268
+ /**
269
+ * Get the HTTP server
270
+ * @returns The HTTP server instance
271
+ */
272
+ getServer() {
273
+ return this.server;
274
+ }
275
+ /**
276
+ * Check if server is running
277
+ * @returns True if server is running
278
+ */
279
+ isRunning() {
280
+ return !!this.server?.listening;
281
+ }
282
+ /**
283
+ * Get server configuration
284
+ * @returns The server configuration
285
+ */
286
+ getConfig() {
287
+ return { ...this.config };
288
+ }
289
+ }
290
+ export default BaseAPIServer;