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