lsh-framework 3.2.4 → 3.5.0

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 (53) 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/sync.js +51 -39
  5. package/dist/constants/config.js +3 -0
  6. package/dist/lib/floating-point-arithmetic.js +2 -2
  7. package/dist/lib/ipfs-client-manager.js +51 -13
  8. package/dist/lib/ipfs-secrets-storage.js +21 -16
  9. package/dist/lib/ipfs-sync.js +88 -14
  10. package/dist/lib/secrets-manager.js +117 -47
  11. package/dist/lib/sync-key-store.js +87 -0
  12. package/dist/services/secrets/secrets.js +77 -39
  13. package/package.json +16 -16
  14. package/dist/__tests__/fixtures/job-fixtures.js +0 -204
  15. package/dist/__tests__/fixtures/supabase-mocks.js +0 -252
  16. package/dist/daemon/job-registry.js +0 -556
  17. package/dist/daemon/lshd.js +0 -968
  18. package/dist/daemon/saas-api-routes.js +0 -599
  19. package/dist/daemon/saas-api-server.js +0 -231
  20. package/dist/examples/supabase-integration.js +0 -106
  21. package/dist/lib/api-response.js +0 -226
  22. package/dist/lib/base-command-registrar.js +0 -287
  23. package/dist/lib/base-job-manager.js +0 -295
  24. package/dist/lib/cloud-config-manager.js +0 -348
  25. package/dist/lib/cron-job-manager.js +0 -368
  26. package/dist/lib/daemon-client-helper.js +0 -145
  27. package/dist/lib/daemon-client.js +0 -513
  28. package/dist/lib/database-persistence.js +0 -727
  29. package/dist/lib/database-schema.js +0 -259
  30. package/dist/lib/database-types.js +0 -90
  31. package/dist/lib/enhanced-history-system.js +0 -247
  32. package/dist/lib/history-system.js +0 -246
  33. package/dist/lib/job-manager.js +0 -436
  34. package/dist/lib/job-storage-database.js +0 -164
  35. package/dist/lib/job-storage-memory.js +0 -73
  36. package/dist/lib/local-storage-adapter.js +0 -507
  37. package/dist/lib/optimized-job-scheduler.js +0 -356
  38. package/dist/lib/saas-audit.js +0 -215
  39. package/dist/lib/saas-auth.js +0 -465
  40. package/dist/lib/saas-billing.js +0 -503
  41. package/dist/lib/saas-email.js +0 -403
  42. package/dist/lib/saas-encryption.js +0 -221
  43. package/dist/lib/saas-organizations.js +0 -662
  44. package/dist/lib/saas-secrets.js +0 -408
  45. package/dist/lib/saas-types.js +0 -165
  46. package/dist/lib/supabase-client.js +0 -125
  47. package/dist/lib/supabase-utils.js +0 -396
  48. package/dist/services/cron/cron-registrar.js +0 -240
  49. package/dist/services/cron/cron.js +0 -9
  50. package/dist/services/daemon/daemon-registrar.js +0 -585
  51. package/dist/services/daemon/daemon.js +0 -9
  52. package/dist/services/supabase/supabase-registrar.js +0 -375
  53. package/dist/services/supabase/supabase.js +0 -9
@@ -1,368 +0,0 @@
1
- /**
2
- * Cron Job Manager with Supabase Integration
3
- * Manages scheduled jobs with database persistence and monitoring
4
- *
5
- * REFACTORED: Now extends BaseJobManager for unified job management interface
6
- */
7
- import { BaseJobManager, } from './base-job-manager.js';
8
- import DatabaseJobStorage from './job-storage-database.js';
9
- import DaemonClient from './daemon-client.js';
10
- import DatabasePersistence from './database-persistence.js';
11
- import { DEFAULTS } from '../constants/index.js';
12
- export class CronJobManager extends BaseJobManager {
13
- daemonClient;
14
- databasePersistence;
15
- templates = new Map();
16
- userId;
17
- constructor(userId) {
18
- super(new DatabaseJobStorage(userId), 'CronJobManager');
19
- this.userId = userId;
20
- this.daemonClient = new DaemonClient(undefined, userId);
21
- this.databasePersistence = new DatabasePersistence(userId);
22
- this.loadTemplates();
23
- }
24
- /**
25
- * Load predefined job templates
26
- */
27
- loadTemplates() {
28
- const templates = [
29
- {
30
- id: 'database-backup',
31
- name: 'Database Backup',
32
- description: 'Daily database backup',
33
- command: 'pg_dump -h localhost -U postgres mydb > /backups/mydb_$(date +%Y%m%d).sql',
34
- schedule: '0 2 * * *',
35
- category: 'backup',
36
- tags: ['database', 'backup', 'daily'],
37
- workingDirectory: '/backups',
38
- priority: 8,
39
- maxRetries: 3,
40
- timeout: DEFAULTS.JOB_TIMEOUT_1H_MS,
41
- },
42
- {
43
- id: 'log-cleanup',
44
- name: 'Log Cleanup',
45
- description: 'Clean old log files',
46
- command: 'find /var/log -name "*.log" -mtime +30 -delete',
47
- schedule: '0 3 * * 0',
48
- category: 'maintenance',
49
- tags: ['logs', 'cleanup', 'weekly'],
50
- priority: 3,
51
- maxRetries: 2,
52
- timeout: DEFAULTS.JOB_TIMEOUT_5M_MS,
53
- },
54
- {
55
- id: 'disk-monitor',
56
- name: 'Disk Space Monitor',
57
- description: 'Monitor disk space usage',
58
- command: 'df -h | awk \'$5 > 80 {print $0}\' | mail -s "Disk Space Alert" admin@example.com',
59
- schedule: '*/15 * * * *',
60
- category: 'monitoring',
61
- tags: ['monitoring', 'disk', 'alert'],
62
- priority: 7,
63
- maxRetries: 1,
64
- timeout: DEFAULTS.JOB_TIMEOUT_1M_MS,
65
- },
66
- {
67
- id: 'data-sync',
68
- name: 'Data Synchronization',
69
- description: 'Sync data with external systems',
70
- command: 'rsync -av /data/ user@remote:/backup/data/',
71
- schedule: '0 1 * * *',
72
- category: 'data-processing',
73
- tags: ['sync', 'data', 'daily'],
74
- workingDirectory: '/data',
75
- priority: 6,
76
- maxRetries: 5,
77
- timeout: DEFAULTS.JOB_TIMEOUT_2H_MS,
78
- },
79
- ];
80
- templates.forEach(template => {
81
- this.templates.set(template.id, template);
82
- });
83
- }
84
- /**
85
- * Connect to daemon
86
- */
87
- async connect() {
88
- try {
89
- return await this.daemonClient.connect();
90
- }
91
- catch (error) {
92
- console.error('Failed to connect to daemon:', error);
93
- return false;
94
- }
95
- }
96
- /**
97
- * Disconnect from daemon
98
- */
99
- disconnect() {
100
- this.daemonClient.disconnect();
101
- }
102
- /**
103
- * Create a job from template
104
- */
105
- async createJobFromTemplate(templateId, customizations) {
106
- const template = this.templates.get(templateId);
107
- if (!template) {
108
- throw new Error(`Template ${templateId} not found`);
109
- }
110
- const jobSpec = {
111
- id: customizations?.id || `job_${templateId}_${Date.now()}`,
112
- name: customizations?.name || template.name,
113
- description: customizations?.description || template.description,
114
- command: customizations?.command || template.command,
115
- schedule: {
116
- cron: customizations?.schedule?.cron || template.schedule,
117
- timezone: customizations?.schedule?.timezone,
118
- },
119
- environment: customizations?.environment || template.environment,
120
- workingDirectory: customizations?.workingDirectory || template.workingDirectory,
121
- priority: customizations?.priority || template.priority,
122
- tags: customizations?.tags || template.tags,
123
- maxRetries: customizations?.maxRetries || template.maxRetries,
124
- timeout: customizations?.timeout || template.timeout,
125
- databaseSync: true,
126
- };
127
- return await this.daemonClient.createDatabaseCronJob(jobSpec);
128
- }
129
- /**
130
- * Create a custom job
131
- */
132
- async createCustomJob(jobSpec) {
133
- return await this.daemonClient.createDatabaseCronJob({
134
- ...jobSpec,
135
- databaseSync: true,
136
- });
137
- }
138
- /**
139
- * List all available templates
140
- */
141
- listTemplates() {
142
- return Array.from(this.templates.values());
143
- }
144
- /**
145
- * Get template by ID
146
- */
147
- getTemplate(templateId) {
148
- return this.templates.get(templateId);
149
- }
150
- /**
151
- * List all jobs - overrides BaseJobManager to use daemon client
152
- * Returns jobs from daemon rather than storage layer
153
- */
154
- async listJobs(filter) {
155
- const daemonJobs = await this.daemonClient.listJobs(filter);
156
- // Daemon jobs are compatible with BaseJobSpec structure
157
- return daemonJobs;
158
- }
159
- /**
160
- * Get job execution report
161
- */
162
- async getJobReport(jobId) {
163
- // Try to get historical data from database if available, otherwise use current job info
164
- // Using any[] because getJobHistory returns ShellJob (snake_case properties)
165
- // but getJob returns JobSpec (camelCase properties), and this code handles both
166
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
167
- let jobs = [];
168
- try {
169
- jobs = await this.daemonClient.getJobHistory(jobId, 1000);
170
- }
171
- catch (_error) {
172
- // Fallback: use current job information for basic report
173
- const currentJob = await this.daemonClient.getJob(jobId);
174
- if (currentJob) {
175
- jobs = [currentJob];
176
- }
177
- }
178
- const executions = jobs.length;
179
- const successes = jobs.filter(job => job.status === 'completed').length;
180
- const failures = jobs.filter(job => job.status === 'failed').length;
181
- const successRate = executions > 0 ? (successes / executions) * 100 : 0;
182
- const durations = jobs
183
- .filter(job => job.duration_ms)
184
- .map(job => job.duration_ms);
185
- const averageDuration = durations.length > 0
186
- ? durations.reduce((sum, duration) => sum + duration, 0) / durations.length
187
- : 0;
188
- const lastExecution = jobs.length > 0 ? (jobs[0].startedAt || jobs[0].createdAt || jobs[0].started_at)
189
- ? new Date(jobs[0].startedAt || jobs[0].createdAt || jobs[0].started_at)
190
- : undefined : undefined;
191
- const lastSuccess = jobs.find(job => job.status === 'completed')
192
- ? new Date(jobs.find(job => job.status === 'completed').startedAt || jobs.find(job => job.status === 'completed').createdAt || jobs.find(job => job.status === 'completed').started_at)
193
- : undefined;
194
- const lastFailure = jobs.find(job => job.status === 'failed')
195
- ? new Date(jobs.find(job => job.status === 'failed').startedAt || jobs.find(job => job.status === 'failed').createdAt || jobs.find(job => job.status === 'failed').started_at)
196
- : undefined;
197
- // Analyze common errors
198
- const errorCounts = new Map();
199
- jobs.filter(job => job.status === 'failed' && (job.error || job.stderr)).forEach(job => {
200
- const error = job.error || job.stderr || 'Unknown error';
201
- errorCounts.set(error, (errorCounts.get(error) || 0) + 1);
202
- });
203
- const commonErrors = Array.from(errorCounts.entries())
204
- .map(([error, count]) => ({ error, count }))
205
- .sort((a, b) => b.count - a.count)
206
- .slice(0, 5);
207
- return {
208
- jobId,
209
- executions,
210
- successes,
211
- failures,
212
- successRate,
213
- averageDuration,
214
- lastExecution,
215
- lastSuccess,
216
- lastFailure,
217
- commonErrors,
218
- };
219
- }
220
- /**
221
- * Get all job reports
222
- */
223
- async getAllJobReports() {
224
- const jobs = await this.daemonClient.listJobs();
225
- const reports = [];
226
- for (const job of jobs) {
227
- try {
228
- const report = await this.getJobReport(job.id);
229
- reports.push(report);
230
- }
231
- catch (error) {
232
- console.error(`Failed to get report for job ${job.id}:`, error);
233
- }
234
- }
235
- return reports.sort((a, b) => b.executions - a.executions);
236
- }
237
- /**
238
- * Start a job - implements BaseJobManager abstract method
239
- * Delegates to daemon client and updates status
240
- */
241
- async startJob(jobId) {
242
- // Delegate to daemon
243
- const daemonResult = await this.daemonClient.startJob(jobId);
244
- // Update job status in our storage
245
- const job = await this.updateJobStatus(jobId, 'running', {
246
- startedAt: new Date(),
247
- pid: daemonResult.pid,
248
- });
249
- return job;
250
- }
251
- /**
252
- * Stop a job - implements BaseJobManager abstract method
253
- * Delegates to daemon client and updates status
254
- */
255
- async stopJob(jobId, signal = 'SIGTERM') {
256
- // Delegate to daemon
257
- await this.daemonClient.stopJob(jobId, signal);
258
- // Update job status in our storage
259
- const job = await this.updateJobStatus(jobId, 'stopped', {
260
- completedAt: new Date(),
261
- });
262
- return job;
263
- }
264
- /**
265
- * Remove a job - overrides BaseJobManager to use daemon client
266
- */
267
- async removeJob(jobId, force = false) {
268
- const result = await this.daemonClient.removeJob(jobId, force);
269
- // Also remove from our storage if it exists
270
- try {
271
- await this.storage.delete(jobId);
272
- this.jobs.delete(jobId);
273
- }
274
- catch (_error) {
275
- // Job may not exist in storage, that's okay
276
- this.logger.debug(`Job ${jobId} not found in storage during removal`);
277
- }
278
- return result;
279
- }
280
- /**
281
- * Get job information - overrides BaseJobManager to use daemon client
282
- * Returns job from daemon rather than storage layer
283
- */
284
- async getJob(jobId) {
285
- const daemonJob = await this.daemonClient.getJob(jobId);
286
- // Daemon job is compatible with BaseJobSpec structure
287
- return daemonJob ? daemonJob : null;
288
- }
289
- /**
290
- * Get daemon status
291
- */
292
- async getDaemonStatus() {
293
- return await this.daemonClient.getStatus();
294
- }
295
- /**
296
- * Generate comprehensive job report
297
- */
298
- async generateComprehensiveReport() {
299
- const daemonStatus = await this.getDaemonStatus();
300
- const jobReports = await this.getAllJobReports();
301
- const jobs = await this.listJobs();
302
- let report = `# LSH Cron Job Report\n`;
303
- report += `Generated: ${new Date().toISOString()}\n\n`;
304
- report += `## Daemon Status\n`;
305
- report += `- PID: ${daemonStatus.pid}\n`;
306
- report += `- Uptime: ${Math.floor(daemonStatus.uptime / 60)} minutes\n`;
307
- report += `- Memory Usage: ${Math.round((daemonStatus.memoryUsage?.heapUsed || 0) / 1024 / 1024)} MB\n`;
308
- report += `- Total Jobs: ${jobs.length}\n`;
309
- report += `- Running Jobs: ${jobs.filter(j => j.status === 'running').length}\n\n`;
310
- report += `## Job Summary\n`;
311
- const totalExecutions = jobReports.reduce((sum, r) => sum + r.executions, 0);
312
- const totalSuccesses = jobReports.reduce((sum, r) => sum + r.successes, 0);
313
- const overallSuccessRate = totalExecutions > 0 ? (totalSuccesses / totalExecutions) * 100 : 0;
314
- report += `- Total Executions: ${totalExecutions}\n`;
315
- report += `- Overall Success Rate: ${overallSuccessRate.toFixed(1)}%\n\n`;
316
- report += `## Individual Job Reports\n`;
317
- jobReports.forEach(jobReport => {
318
- report += `### ${jobReport.jobId}\n`;
319
- report += `- Executions: ${jobReport.executions}\n`;
320
- report += `- Success Rate: ${jobReport.successRate.toFixed(1)}%\n`;
321
- report += `- Average Duration: ${Math.round(jobReport.averageDuration)}ms\n`;
322
- report += `- Last Execution: ${jobReport.lastExecution?.toISOString() || 'Never'}\n`;
323
- if (jobReport.commonErrors.length > 0) {
324
- report += `- Common Errors:\n`;
325
- jobReport.commonErrors.forEach(error => {
326
- report += ` - ${error.error} (${error.count} times)\n`;
327
- });
328
- }
329
- report += `\n`;
330
- });
331
- return report;
332
- }
333
- /**
334
- * Export job data
335
- */
336
- async exportJobData(format = 'json') {
337
- const jobs = await this.daemonClient.listJobs();
338
- const jobReports = await this.getAllJobReports();
339
- if (format === 'csv') {
340
- let csv = 'Job ID,Name,Status,Executions,Success Rate,Last Execution\n';
341
- jobReports.forEach(report => {
342
- const job = jobs.find(j => j.id === report.jobId);
343
- csv += `${report.jobId},"${job?.name || ''}",${job?.status || ''},${report.executions},${report.successRate.toFixed(1)},${report.lastExecution?.toISOString() || ''}\n`;
344
- });
345
- return csv;
346
- }
347
- else {
348
- return JSON.stringify({
349
- jobs,
350
- reports: jobReports,
351
- exportedAt: new Date().toISOString(),
352
- }, null, 2);
353
- }
354
- }
355
- /**
356
- * Check if daemon is running
357
- */
358
- isDaemonRunning() {
359
- return this.daemonClient.isDaemonRunning();
360
- }
361
- /**
362
- * Check if connected to daemon
363
- */
364
- isConnected() {
365
- return this.daemonClient.isConnected();
366
- }
367
- }
368
- export default CronJobManager;
@@ -1,145 +0,0 @@
1
- /**
2
- * Daemon Client Helper
3
- * Provides wrapper utilities to eliminate repetitive daemon client connection boilerplate
4
- */
5
- import DaemonClient from './daemon-client.js';
6
- import { getPlatformPaths } from './platform-utils.js';
7
- /**
8
- * Default socket path for the daemon (cross-platform)
9
- */
10
- export function getDefaultSocketPath() {
11
- const platformPaths = getPlatformPaths('lsh');
12
- return platformPaths.socketPath;
13
- }
14
- /**
15
- * Get default user ID (cross-platform)
16
- */
17
- export function getDefaultUserId() {
18
- const platformPaths = getPlatformPaths('lsh');
19
- return platformPaths.user;
20
- }
21
- /**
22
- * Execute an operation with a daemon client, handling all connection boilerplate
23
- *
24
- * This wrapper eliminates the need to:
25
- * - Create DaemonClient instance
26
- * - Check if daemon is running
27
- * - Connect to daemon
28
- * - Handle errors
29
- * - Disconnect from daemon
30
- *
31
- * @param operation - Async function that receives a connected DaemonClient
32
- * @param config - Optional configuration
33
- * @returns Promise resolving to the operation result
34
- *
35
- * @example
36
- * ```typescript
37
- * const status = await withDaemonClient(async (client) => {
38
- * return await client.getStatus();
39
- * });
40
- * ```
41
- */
42
- export async function withDaemonClient(operation, config = {}) {
43
- const { socketPath = getDefaultSocketPath(), userId, requireRunning = true, exitOnError = true } = config;
44
- const client = new DaemonClient(socketPath, userId);
45
- try {
46
- // Check if daemon is running (if required)
47
- if (requireRunning && !client.isDaemonRunning()) {
48
- const error = new Error('Daemon is not running. Start it with: lsh daemon start');
49
- if (exitOnError) {
50
- console.error('❌', error.message);
51
- process.exit(1);
52
- }
53
- throw error;
54
- }
55
- // Connect to daemon
56
- await client.connect();
57
- // Execute the operation
58
- const result = await operation(client);
59
- // Disconnect
60
- client.disconnect();
61
- return result;
62
- }
63
- catch (error) {
64
- const err = error;
65
- // Always disconnect on error
66
- client.disconnect();
67
- // Handle errors with helpful messages
68
- if (err.message.includes('Permission denied')) {
69
- const enhancedError = new Error(`❌ ${err.message}\n` +
70
- `The daemon socket may be owned by another user.\n` +
71
- `Try starting your own daemon with: lsh daemon start`);
72
- if (exitOnError) {
73
- console.error(enhancedError.message);
74
- process.exit(1);
75
- }
76
- throw enhancedError;
77
- }
78
- else if (err.message.includes('not found') || err.message.includes('ENOENT')) {
79
- const enhancedError = new Error(`❌ Daemon socket not found.\n` +
80
- `Start the daemon with: lsh daemon start`);
81
- if (exitOnError) {
82
- console.error(enhancedError.message);
83
- process.exit(1);
84
- }
85
- throw enhancedError;
86
- }
87
- else if (err.message.includes('ECONNREFUSED')) {
88
- const enhancedError = new Error(`❌ Daemon is not responding.\n` +
89
- `The daemon may have crashed. Try restarting with: lsh daemon restart`);
90
- if (exitOnError) {
91
- console.error(enhancedError.message);
92
- process.exit(1);
93
- }
94
- throw enhancedError;
95
- }
96
- else {
97
- if (exitOnError) {
98
- console.error('❌ Error:', err.message);
99
- process.exit(1);
100
- }
101
- throw error;
102
- }
103
- }
104
- }
105
- /**
106
- * Execute an operation with a daemon client that includes user ID
107
- *
108
- * @param operation - Async function that receives a connected DaemonClient
109
- * @param config - Optional configuration (userId will default to current user)
110
- * @returns Promise resolving to the operation result
111
- *
112
- * @example
113
- * ```typescript
114
- * const history = await withDaemonClientForUser(async (client) => {
115
- * return await client.getJobHistory('job-123');
116
- * });
117
- * ```
118
- */
119
- export async function withDaemonClientForUser(operation, config = {}) {
120
- return withDaemonClient(operation, {
121
- ...config,
122
- userId: config.userId || getDefaultUserId()
123
- });
124
- }
125
- /**
126
- * Check if daemon is running without connecting
127
- *
128
- * @param socketPath - Optional custom socket path
129
- * @returns true if daemon is running
130
- */
131
- export function isDaemonRunning(socketPath) {
132
- const client = new DaemonClient(socketPath || getDefaultSocketPath());
133
- return client.isDaemonRunning();
134
- }
135
- /**
136
- * Create a new daemon client with default configuration
137
- *
138
- * @param config - Optional configuration
139
- * @returns Configured DaemonClient instance
140
- */
141
- export function createDaemonClient(config = {}) {
142
- const socketPath = config.socketPath || getDefaultSocketPath();
143
- const userId = config.userId;
144
- return new DaemonClient(socketPath, userId);
145
- }