lsh-framework 3.2.5 → 3.5.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 (54) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +72 -34
  3. package/dist/commands/ipfs.js +7 -12
  4. package/dist/commands/self.js +22 -16
  5. package/dist/commands/sync.js +49 -38
  6. package/dist/constants/config.js +3 -0
  7. package/dist/lib/floating-point-arithmetic.js +2 -2
  8. package/dist/lib/ipfs-client-manager.js +51 -13
  9. package/dist/lib/ipfs-secrets-storage.js +21 -16
  10. package/dist/lib/ipfs-sync.js +88 -14
  11. package/dist/lib/secrets-manager.js +117 -47
  12. package/dist/lib/sync-key-store.js +87 -0
  13. package/dist/services/secrets/secrets.js +77 -39
  14. package/package.json +16 -16
  15. package/dist/__tests__/fixtures/job-fixtures.js +0 -204
  16. package/dist/__tests__/fixtures/supabase-mocks.js +0 -252
  17. package/dist/daemon/job-registry.js +0 -556
  18. package/dist/daemon/lshd.js +0 -968
  19. package/dist/daemon/saas-api-routes.js +0 -599
  20. package/dist/daemon/saas-api-server.js +0 -231
  21. package/dist/examples/supabase-integration.js +0 -106
  22. package/dist/lib/api-response.js +0 -226
  23. package/dist/lib/base-command-registrar.js +0 -287
  24. package/dist/lib/base-job-manager.js +0 -295
  25. package/dist/lib/cloud-config-manager.js +0 -348
  26. package/dist/lib/cron-job-manager.js +0 -368
  27. package/dist/lib/daemon-client-helper.js +0 -145
  28. package/dist/lib/daemon-client.js +0 -513
  29. package/dist/lib/database-persistence.js +0 -727
  30. package/dist/lib/database-schema.js +0 -259
  31. package/dist/lib/database-types.js +0 -90
  32. package/dist/lib/enhanced-history-system.js +0 -247
  33. package/dist/lib/history-system.js +0 -246
  34. package/dist/lib/job-manager.js +0 -436
  35. package/dist/lib/job-storage-database.js +0 -164
  36. package/dist/lib/job-storage-memory.js +0 -73
  37. package/dist/lib/local-storage-adapter.js +0 -507
  38. package/dist/lib/optimized-job-scheduler.js +0 -356
  39. package/dist/lib/saas-audit.js +0 -215
  40. package/dist/lib/saas-auth.js +0 -465
  41. package/dist/lib/saas-billing.js +0 -503
  42. package/dist/lib/saas-email.js +0 -403
  43. package/dist/lib/saas-encryption.js +0 -221
  44. package/dist/lib/saas-organizations.js +0 -662
  45. package/dist/lib/saas-secrets.js +0 -408
  46. package/dist/lib/saas-types.js +0 -165
  47. package/dist/lib/supabase-client.js +0 -125
  48. package/dist/lib/supabase-utils.js +0 -396
  49. package/dist/services/cron/cron-registrar.js +0 -240
  50. package/dist/services/cron/cron.js +0 -9
  51. package/dist/services/daemon/daemon-registrar.js +0 -585
  52. package/dist/services/daemon/daemon.js +0 -9
  53. package/dist/services/supabase/supabase-registrar.js +0 -375
  54. package/dist/services/supabase/supabase.js +0 -9
@@ -1,287 +0,0 @@
1
- /**
2
- * Base Command Registrar
3
- *
4
- * Abstract base class for command registration to eliminate duplication in:
5
- * - Command setup patterns
6
- * - Error handling
7
- * - Daemon client management
8
- * - Output formatting
9
- *
10
- * Usage:
11
- * ```typescript
12
- * class MyCommandRegistrar extends BaseCommandRegistrar {
13
- * constructor() {
14
- * super('MyService');
15
- * }
16
- *
17
- * async register(program: Command): Promise<void> {
18
- * const cmd = this.createCommand(program, 'mycommand', 'My command description');
19
- *
20
- * this.addSubcommand(cmd, {
21
- * name: 'list',
22
- * description: 'List items',
23
- * action: async () => {
24
- * await this.withDaemonAction(async (client) => {
25
- * const items = await client.listItems();
26
- * this.logSuccess('Items:', items);
27
- * });
28
- * }
29
- * });
30
- * }
31
- * }
32
- * ```
33
- */
34
- import CronJobManager from './cron-job-manager.js';
35
- import { createLogger } from './logger.js';
36
- import { withDaemonClient, withDaemonClientForUser, isDaemonRunning } from './daemon-client-helper.js';
37
- /**
38
- * Base class for command registrars
39
- */
40
- export class BaseCommandRegistrar {
41
- logger;
42
- serviceName;
43
- constructor(serviceName) {
44
- this.serviceName = serviceName;
45
- this.logger = createLogger(serviceName);
46
- }
47
- /**
48
- * Create a top-level command
49
- */
50
- createCommand(program, name, description) {
51
- return program
52
- .command(name)
53
- .description(description);
54
- }
55
- /**
56
- * Add a subcommand with automatic error handling
57
- */
58
- addSubcommand(parent, config) {
59
- let cmd = parent.command(config.name).description(config.description);
60
- // Add arguments
61
- if (config.arguments) {
62
- config.arguments.forEach(arg => {
63
- const argStr = arg.required ? `<${arg.name}>` : `[${arg.name}]`;
64
- if (arg.description) {
65
- cmd = cmd.argument(argStr, arg.description);
66
- }
67
- else {
68
- cmd = cmd.argument(argStr);
69
- }
70
- });
71
- }
72
- // Add options
73
- if (config.options) {
74
- config.options.forEach(opt => {
75
- if (opt.defaultValue !== undefined) {
76
- cmd = cmd.option(opt.flags, opt.description, opt.defaultValue);
77
- }
78
- else {
79
- cmd = cmd.option(opt.flags, opt.description);
80
- }
81
- });
82
- }
83
- // Wrap action with error handling
84
- cmd.action(async (...args) => {
85
- try {
86
- await config.action(...args);
87
- }
88
- catch (error) {
89
- const err = error;
90
- this.logError('Command failed', err);
91
- process.exit(1);
92
- }
93
- });
94
- return cmd;
95
- }
96
- /**
97
- * Execute an action with daemon client
98
- */
99
- async withDaemonAction(action, config = {}) {
100
- const { requireRunning = true, exitOnError = true, forUser = false } = config;
101
- const helper = forUser ? withDaemonClientForUser : withDaemonClient;
102
- return await helper(action, { requireRunning, exitOnError });
103
- }
104
- /**
105
- * Execute an action with CronJobManager
106
- */
107
- async withCronManager(action, config = {}) {
108
- const { requireRunning = true } = config;
109
- const manager = new CronJobManager();
110
- if (requireRunning && !manager.isDaemonRunning()) {
111
- this.logError('Daemon is not running. Start it with: lsh daemon start');
112
- process.exit(1);
113
- }
114
- try {
115
- await manager.connect();
116
- const result = await action(manager);
117
- manager.disconnect();
118
- return result;
119
- }
120
- catch (error) {
121
- manager.disconnect();
122
- throw error;
123
- }
124
- }
125
- /**
126
- * Check if daemon is running
127
- */
128
- isDaemonRunning() {
129
- return isDaemonRunning();
130
- }
131
- /**
132
- * Log success message
133
- */
134
- logSuccess(message, data) {
135
- this.logger.info(message);
136
- if (data !== undefined) {
137
- if (typeof data === 'object' && data !== null && !Array.isArray(data)) {
138
- Object.entries(data).forEach(([key, value]) => {
139
- this.logger.info(` ${key}: ${value}`);
140
- });
141
- }
142
- else if (Array.isArray(data)) {
143
- data.forEach(item => {
144
- if (typeof item === 'object' && item !== null) {
145
- this.logger.info(` ${JSON.stringify(item, null, 2)}`);
146
- }
147
- else {
148
- this.logger.info(` ${item}`);
149
- }
150
- });
151
- }
152
- else {
153
- this.logger.info(` ${data}`);
154
- }
155
- }
156
- }
157
- /**
158
- * Log error message
159
- */
160
- logError(message, error) {
161
- if (error instanceof Error) {
162
- this.logger.error(message, error);
163
- }
164
- else if (error) {
165
- this.logger.error(`${message}: ${error}`);
166
- }
167
- else {
168
- this.logger.error(message);
169
- }
170
- }
171
- /**
172
- * Log info message
173
- */
174
- logInfo(message) {
175
- this.logger.info(message);
176
- }
177
- /**
178
- * Log warning message
179
- */
180
- logWarning(message) {
181
- this.logger.warn(message);
182
- }
183
- /**
184
- * Parse JSON from string with error handling
185
- */
186
- parseJSON(jsonString, context = 'JSON') {
187
- try {
188
- return JSON.parse(jsonString);
189
- }
190
- catch (error) {
191
- throw new Error(`Invalid ${context}: ${error instanceof Error ? error.message : String(error)}`);
192
- }
193
- }
194
- /**
195
- * Parse comma-separated tags
196
- */
197
- parseTags(tagsString) {
198
- return tagsString.split(',').map(t => t.trim()).filter(t => t.length > 0);
199
- }
200
- /**
201
- * Format job schedule for display
202
- */
203
- formatSchedule(schedule) {
204
- if (schedule?.cron) {
205
- return schedule.cron;
206
- }
207
- if (schedule?.interval) {
208
- return `${schedule.interval}ms interval`;
209
- }
210
- return 'No schedule';
211
- }
212
- /**
213
- * Validate required options
214
- */
215
- validateRequired(options, required, commandName = 'command') {
216
- const missing = required.filter(key => !options[key]);
217
- if (missing.length > 0) {
218
- throw new Error(`Missing required options for ${commandName}: ${missing.map(k => `--${k}`).join(', ')}`);
219
- }
220
- }
221
- /**
222
- * Create a standardized job specification from options
223
- */
224
- createJobSpec(options) {
225
- return {
226
- id: options.id || `job_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`,
227
- name: options.name,
228
- description: options.description,
229
- command: options.command,
230
- schedule: {
231
- cron: options.schedule,
232
- interval: options.interval ? parseInt(options.interval) : undefined,
233
- },
234
- cwd: options.workingDir,
235
- env: options.env ? this.parseJSON(options.env, 'environment variables') : {},
236
- tags: options.tags ? this.parseTags(options.tags) : [],
237
- priority: options.priority ? parseInt(options.priority) : 5,
238
- maxRetries: options.maxRetries ? parseInt(options.maxRetries) : 3,
239
- timeout: options.timeout ? parseInt(options.timeout) : 0,
240
- databaseSync: options.databaseSync !== false,
241
- };
242
- }
243
- /**
244
- * Display job information
245
- */
246
- displayJob(job) {
247
- this.logInfo(` ${job.id}: ${job.name}`);
248
- this.logInfo(` Command: ${job.command}`);
249
- this.logInfo(` Schedule: ${this.formatSchedule(job.schedule)}`);
250
- this.logInfo(` Status: ${job.status}`);
251
- this.logInfo(` Priority: ${job.priority}`);
252
- if (job.tags && job.tags.length > 0) {
253
- this.logInfo(` Tags: ${job.tags.join(', ')}`);
254
- }
255
- }
256
- /**
257
- * Display multiple jobs
258
- */
259
- displayJobs(jobs) {
260
- this.logInfo(`Jobs (${jobs.length} total):`);
261
- jobs.forEach(job => {
262
- this.displayJob(job);
263
- this.logInfo('');
264
- });
265
- }
266
- /**
267
- * Display job report
268
- */
269
- displayJobReport(report) {
270
- this.logInfo(`Job Report: ${report.jobId || 'N/A'}`);
271
- this.logInfo(` Executions: ${report.executions}`);
272
- this.logInfo(` Successes: ${report.successes}`);
273
- this.logInfo(` Failures: ${report.failures}`);
274
- this.logInfo(` Success Rate: ${report.successRate.toFixed(1)}%`);
275
- this.logInfo(` Average Duration: ${Math.round(report.averageDuration)}ms`);
276
- this.logInfo(` Last Execution: ${report.lastExecution?.toISOString() || 'Never'}`);
277
- this.logInfo(` Last Success: ${report.lastSuccess?.toISOString() || 'Never'}`);
278
- this.logInfo(` Last Failure: ${report.lastFailure?.toISOString() || 'Never'}`);
279
- if (report.commonErrors && report.commonErrors.length > 0) {
280
- this.logInfo('\n Common Errors:');
281
- report.commonErrors.forEach((error) => {
282
- this.logInfo(` - ${error.error} (${error.count} times)`);
283
- });
284
- }
285
- }
286
- }
287
- export default BaseCommandRegistrar;
@@ -1,295 +0,0 @@
1
- /**
2
- * Base Job Manager
3
- * Abstract base class for all job management systems to eliminate duplication in:
4
- * - Job lifecycle management (create, start, stop, pause, resume, remove)
5
- * - Job status tracking and updates
6
- * - Event emission and handling
7
- * - Statistics and reporting
8
- * - Storage abstraction
9
- *
10
- * Subclasses implement storage-specific operations (memory, database, filesystem)
11
- */
12
- import { EventEmitter } from 'events';
13
- import { createLogger } from './logger.js';
14
- import { ENV_VARS } from '../constants/index.js';
15
- /**
16
- * Abstract base class for job managers
17
- */
18
- export class BaseJobManager extends EventEmitter {
19
- logger;
20
- storage;
21
- jobs = new Map();
22
- constructor(storage, loggerName = 'JobManager') {
23
- super();
24
- this.storage = storage;
25
- this.logger = createLogger(loggerName);
26
- }
27
- /**
28
- * Generate unique job ID
29
- */
30
- generateJobId(prefix = 'job') {
31
- return `${prefix}_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`;
32
- }
33
- /**
34
- * Validate job specification
35
- */
36
- validateJobSpec(spec) {
37
- if (!spec.name) {
38
- throw new Error('Job name is required');
39
- }
40
- if (!spec.command) {
41
- throw new Error('Job command is required');
42
- }
43
- }
44
- /**
45
- * Create a new job
46
- */
47
- async createJob(spec) {
48
- this.validateJobSpec(spec);
49
- const job = {
50
- id: spec.id || this.generateJobId(),
51
- name: spec.name,
52
- command: spec.command,
53
- args: spec.args,
54
- status: 'created',
55
- createdAt: new Date(),
56
- env: spec.env,
57
- cwd: spec.cwd || process.cwd(),
58
- user: spec.user || process.env[ENV_VARS.USER],
59
- schedule: spec.schedule,
60
- tags: spec.tags || [],
61
- description: spec.description,
62
- priority: spec.priority ?? 5,
63
- timeout: spec.timeout,
64
- maxRetries: spec.maxRetries ?? 3,
65
- retryCount: 0,
66
- databaseSync: spec.databaseSync !== false,
67
- };
68
- await this.storage.save(job);
69
- this.jobs.set(job.id, job);
70
- this.emit('job:created', job);
71
- this.logger.info(`Job created: ${job.id} (${job.name})`);
72
- return job;
73
- }
74
- /**
75
- * Get job by ID
76
- */
77
- async getJob(jobId) {
78
- // Check memory cache first
79
- const job = this.jobs.get(jobId);
80
- if (job) {
81
- return job;
82
- }
83
- // Check storage
84
- const storedJob = await this.storage.get(jobId);
85
- if (storedJob) {
86
- this.jobs.set(jobId, storedJob);
87
- return storedJob;
88
- }
89
- return null;
90
- }
91
- /**
92
- * List jobs with optional filtering
93
- */
94
- async listJobs(filter) {
95
- let jobs = await this.storage.list(filter);
96
- // Apply additional filters
97
- if (filter) {
98
- jobs = this.applyFilters(jobs, filter);
99
- }
100
- return jobs;
101
- }
102
- /**
103
- * Apply filters to job list
104
- */
105
- applyFilters(jobs, filter) {
106
- return jobs.filter(job => {
107
- // Status filter
108
- if (filter.status) {
109
- const statuses = Array.isArray(filter.status) ? filter.status : [filter.status];
110
- if (!statuses.includes(job.status)) {
111
- return false;
112
- }
113
- }
114
- // Tags filter
115
- if (filter.tags && filter.tags.length > 0) {
116
- const hasTag = filter.tags.some(tag => job.tags?.includes(tag));
117
- if (!hasTag) {
118
- return false;
119
- }
120
- }
121
- // User filter
122
- if (filter.user && job.user !== filter.user) {
123
- return false;
124
- }
125
- // Name pattern filter
126
- if (filter.namePattern) {
127
- const pattern = typeof filter.namePattern === 'string'
128
- ? new RegExp(filter.namePattern)
129
- : filter.namePattern;
130
- if (!pattern.test(job.name)) {
131
- return false;
132
- }
133
- }
134
- // Date filters
135
- if (filter.createdAfter && job.createdAt < filter.createdAfter) {
136
- return false;
137
- }
138
- if (filter.createdBefore && job.createdAt > filter.createdBefore) {
139
- return false;
140
- }
141
- return true;
142
- });
143
- }
144
- /**
145
- * Update job
146
- */
147
- async updateJob(jobId, updates) {
148
- const job = await this.getJob(jobId);
149
- if (!job) {
150
- throw new Error(`Job ${jobId} not found`);
151
- }
152
- // Apply updates
153
- if (updates.name)
154
- job.name = updates.name;
155
- if (updates.description)
156
- job.description = updates.description;
157
- if (updates.priority !== undefined)
158
- job.priority = updates.priority;
159
- if (updates.tags)
160
- job.tags = updates.tags;
161
- if (updates.schedule)
162
- job.schedule = updates.schedule;
163
- if (updates.env)
164
- job.env = { ...job.env, ...updates.env };
165
- if (updates.timeout !== undefined)
166
- job.timeout = updates.timeout;
167
- if (updates.maxRetries !== undefined)
168
- job.maxRetries = updates.maxRetries;
169
- await this.storage.update(jobId, job);
170
- this.jobs.set(jobId, job);
171
- this.emit('job:updated', job);
172
- this.logger.info(`Job updated: ${jobId}`);
173
- return job;
174
- }
175
- /**
176
- * Update job status
177
- */
178
- async updateJobStatus(jobId, status, additionalUpdates) {
179
- const job = await this.getJob(jobId);
180
- if (!job) {
181
- throw new Error(`Job ${jobId} not found`);
182
- }
183
- job.status = status;
184
- // Apply additional updates
185
- if (additionalUpdates) {
186
- Object.assign(job, additionalUpdates);
187
- }
188
- // Update timestamps
189
- if (status === 'running' && !job.startedAt) {
190
- job.startedAt = new Date();
191
- }
192
- if (status === 'completed' || status === 'failed' || status === 'killed') {
193
- job.completedAt = new Date();
194
- }
195
- await this.storage.update(jobId, job);
196
- this.jobs.set(jobId, job);
197
- this.emit(`job:${status}`, job);
198
- this.logger.info(`Job ${status}: ${jobId}`);
199
- return job;
200
- }
201
- /**
202
- * Remove job
203
- */
204
- async removeJob(jobId, force = false) {
205
- const job = await this.getJob(jobId);
206
- if (!job) {
207
- // Return false instead of throwing when job doesn't exist
208
- return false;
209
- }
210
- // Check if job is running
211
- if (job.status === 'running' && !force) {
212
- throw new Error(`Job ${jobId} is running. Use force=true to remove.`);
213
- }
214
- // Stop job if running
215
- if (job.status === 'running') {
216
- await this.stopJob(jobId);
217
- }
218
- await this.storage.delete(jobId);
219
- this.jobs.delete(jobId);
220
- this.emit('job:removed', job);
221
- this.logger.info(`Job removed: ${jobId}`);
222
- return true;
223
- }
224
- /**
225
- * Get job execution history
226
- */
227
- async getJobHistory(jobId, limit = 50) {
228
- return await this.storage.getExecutions(jobId, limit);
229
- }
230
- /**
231
- * Calculate job statistics
232
- */
233
- async getJobStatistics(jobId) {
234
- const executions = await this.getJobHistory(jobId);
235
- const job = await this.getJob(jobId);
236
- if (!job) {
237
- throw new Error(`Job ${jobId} not found`);
238
- }
239
- const totalExecutions = executions.length;
240
- const successfulExecutions = executions.filter(e => e.status === 'completed').length;
241
- const failedExecutions = executions.filter(e => e.status === 'failed').length;
242
- const completedExecutions = executions.filter(e => e.duration);
243
- const averageDuration = completedExecutions.length > 0
244
- ? completedExecutions.reduce((sum, e) => sum + (e.duration || 0), 0) / completedExecutions.length
245
- : 0;
246
- const lastExecution = executions[0]?.startTime;
247
- const lastSuccess = executions.find(e => e.status === 'completed')?.startTime;
248
- const lastFailure = executions.find(e => e.status === 'failed')?.startTime;
249
- return {
250
- jobId: job.id,
251
- jobName: job.name,
252
- totalExecutions,
253
- successfulExecutions,
254
- failedExecutions,
255
- successRate: totalExecutions > 0 ? (successfulExecutions / totalExecutions) * 100 : 0,
256
- averageDuration,
257
- lastExecution,
258
- lastSuccess,
259
- lastFailure,
260
- };
261
- }
262
- /**
263
- * Record job execution
264
- */
265
- async recordExecution(job, status, details = {}) {
266
- const execution = {
267
- executionId: this.generateJobId('exec'),
268
- jobId: job.id,
269
- jobName: job.name,
270
- command: job.command,
271
- startTime: details.startTime || new Date(),
272
- endTime: details.endTime,
273
- duration: details.duration,
274
- status,
275
- exitCode: details.exitCode,
276
- stdout: details.stdout,
277
- stderr: details.stderr,
278
- errorMessage: details.errorMessage,
279
- };
280
- await this.storage.saveExecution(execution);
281
- this.emit('job:execution', execution);
282
- return execution;
283
- }
284
- /**
285
- * Cleanup - optional override
286
- */
287
- async cleanup() {
288
- if (this.storage.cleanup) {
289
- await this.storage.cleanup();
290
- }
291
- this.jobs.clear();
292
- this.removeAllListeners();
293
- }
294
- }
295
- export default BaseJobManager;