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,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
+ }