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,565 @@
1
+ /**
2
+ * Daemon Command Registrar
3
+ * Registers all daemon-related CLI commands using BaseCommandRegistrar
4
+ */
5
+ import { BaseCommandRegistrar } from '../../lib/base-command-registrar.js';
6
+ import * as fs from 'fs';
7
+ import { exec } from 'child_process';
8
+ import { promisify } from 'util';
9
+ const execAsync = promisify(exec);
10
+ export class DaemonCommandRegistrar extends BaseCommandRegistrar {
11
+ constructor() {
12
+ super('Daemon');
13
+ }
14
+ async register(program) {
15
+ const daemonCmd = this.createCommand(program, 'daemon', 'LSH daemon management commands');
16
+ this.registerDaemonControlCommands(daemonCmd);
17
+ this.registerJobManagementCommands(daemonCmd);
18
+ this.registerDatabaseCommands(daemonCmd);
19
+ }
20
+ registerDaemonControlCommands(daemonCmd) {
21
+ // Status command
22
+ this.addSubcommand(daemonCmd, {
23
+ name: 'status',
24
+ description: 'Get daemon status',
25
+ action: async () => {
26
+ const status = await this.withDaemonAction(async (client) => {
27
+ return await client.getStatus();
28
+ });
29
+ this.logInfo('Daemon Status:');
30
+ this.logInfo(` PID: ${status.pid}`);
31
+ this.logInfo(` Uptime: ${Math.floor(status.uptime / 60)} minutes`);
32
+ this.logInfo(` Memory: ${Math.round(status.memoryUsage.heapUsed / 1024 / 1024)} MB`);
33
+ this.logInfo(` Jobs: ${status.jobs.total} total, ${status.jobs.running} running`);
34
+ }
35
+ });
36
+ // Start command
37
+ this.addSubcommand(daemonCmd, {
38
+ name: 'start',
39
+ description: 'Start the daemon',
40
+ action: async () => {
41
+ const { spawn } = await import('child_process');
42
+ const socketPath = `/tmp/lsh-job-daemon-${process.env.USER || 'user'}.sock`;
43
+ const daemonProcess = spawn('node', ['dist/daemon/lshd.js', 'start', socketPath], {
44
+ detached: true,
45
+ stdio: 'ignore'
46
+ });
47
+ daemonProcess.unref();
48
+ this.logSuccess('Daemon started');
49
+ this.logInfo('Check status with: lsh daemon status');
50
+ }
51
+ });
52
+ // Stop command
53
+ this.addSubcommand(daemonCmd, {
54
+ name: 'stop',
55
+ description: 'Stop the daemon',
56
+ action: async () => {
57
+ if (!this.isDaemonRunning()) {
58
+ this.logWarning('Daemon is not running');
59
+ return;
60
+ }
61
+ await this.withDaemonAction(async (client) => {
62
+ await client.stopDaemon();
63
+ });
64
+ this.logSuccess('Daemon stopped');
65
+ }
66
+ });
67
+ // Restart command
68
+ this.addSubcommand(daemonCmd, {
69
+ name: 'restart',
70
+ description: 'Restart the daemon',
71
+ action: async () => {
72
+ if (this.isDaemonRunning()) {
73
+ await this.withDaemonAction(async (client) => {
74
+ await client.restartDaemon();
75
+ });
76
+ this.logSuccess('Daemon restarted');
77
+ }
78
+ else {
79
+ this.logWarning('Daemon is not running, starting...');
80
+ const { spawn } = await import('child_process');
81
+ const daemonProcess = spawn('node', ['dist/daemon/lshd.js', 'start'], {
82
+ detached: true,
83
+ stdio: 'ignore'
84
+ });
85
+ daemonProcess.unref();
86
+ this.logSuccess('Daemon started');
87
+ }
88
+ }
89
+ });
90
+ // Cleanup command
91
+ this.addSubcommand(daemonCmd, {
92
+ name: 'cleanup',
93
+ description: 'Clean up daemon processes and files',
94
+ options: [
95
+ { flags: '-f, --force', description: 'Force cleanup without prompts', defaultValue: false }
96
+ ],
97
+ action: async (options) => {
98
+ await this.cleanupDaemon(options.force);
99
+ }
100
+ });
101
+ }
102
+ registerJobManagementCommands(daemonCmd) {
103
+ const jobCmd = daemonCmd
104
+ .command('job')
105
+ .description('Job management commands');
106
+ // Create job
107
+ this.addSubcommand(jobCmd, {
108
+ name: 'create',
109
+ description: 'Create a new cron job',
110
+ options: [
111
+ { flags: '-n, --name <name>', description: 'Job name' },
112
+ { flags: '-c, --command <command>', description: 'Command to execute' },
113
+ { flags: '-s, --schedule <schedule>', description: 'Cron schedule (e.g., "0 2 * * *")' },
114
+ { flags: '-i, --interval <interval>', description: 'Interval in milliseconds' },
115
+ { flags: '-d, --description <description>', description: 'Job description' },
116
+ { flags: '-w, --working-dir <dir>', description: 'Working directory' },
117
+ { flags: '-e, --env <env>', description: 'Environment variables (JSON)' },
118
+ { flags: '-t, --tags <tags>', description: 'Comma-separated tags' },
119
+ { flags: '-p, --priority <priority>', description: 'Priority (0-10)', defaultValue: '5' },
120
+ { flags: '--max-retries <retries>', description: 'Maximum retries', defaultValue: '3' },
121
+ { flags: '--timeout <timeout>', description: 'Timeout in milliseconds', defaultValue: '0' },
122
+ { flags: '--no-database-sync', description: 'Disable database synchronization' }
123
+ ],
124
+ action: async (options) => {
125
+ if (!options.name || !options.command || (!options.schedule && !options.interval)) {
126
+ throw new Error('Missing required options: --name, --command, and (--schedule or --interval)');
127
+ }
128
+ const jobSpec = this.createJobSpec(options);
129
+ await this.withDaemonAction(async (client) => {
130
+ await client.createDatabaseCronJob(jobSpec);
131
+ });
132
+ this.logSuccess('Job created successfully:');
133
+ this.logInfo(` ID: ${jobSpec.id}`);
134
+ this.logInfo(` Name: ${jobSpec.name}`);
135
+ this.logInfo(` Command: ${jobSpec.command}`);
136
+ this.logInfo(` Schedule: ${this.formatSchedule(jobSpec.schedule)}`);
137
+ this.logInfo(` Database Sync: ${jobSpec.databaseSync ? 'Enabled' : 'Disabled'}`);
138
+ }
139
+ });
140
+ // List jobs
141
+ this.addSubcommand(jobCmd, {
142
+ name: 'list',
143
+ description: 'List all jobs',
144
+ options: [
145
+ { flags: '-f, --filter <filter>', description: 'Filter jobs by status' }
146
+ ],
147
+ action: async (options) => {
148
+ const jobs = await this.withDaemonAction(async (client) => {
149
+ return await client.listJobs(options.filter ? { status: options.filter } : undefined);
150
+ });
151
+ this.displayJobs(jobs);
152
+ }
153
+ });
154
+ // Start job
155
+ this.addSubcommand(jobCmd, {
156
+ name: 'start',
157
+ description: 'Start a job',
158
+ arguments: [{ name: 'jobId', required: true }],
159
+ action: async (jobId) => {
160
+ await this.withDaemonAction(async (client) => {
161
+ await client.startJob(jobId);
162
+ });
163
+ this.logSuccess(`Job ${jobId} started`);
164
+ }
165
+ });
166
+ // Trigger job
167
+ this.addSubcommand(jobCmd, {
168
+ name: 'trigger',
169
+ description: 'Trigger a job to run immediately (bypass schedule)',
170
+ arguments: [{ name: 'jobId', required: true }],
171
+ action: async (jobId) => {
172
+ const result = await this.withDaemonAction(async (client) => await client.triggerJob(jobId), { forUser: true });
173
+ this.logSuccess(`Job ${jobId} triggered successfully`);
174
+ if (result.output) {
175
+ this.logInfo('Output:');
176
+ this.logInfo(result.output);
177
+ }
178
+ }
179
+ });
180
+ // Trigger all jobs
181
+ this.addSubcommand(jobCmd, {
182
+ name: 'trigger-all',
183
+ description: 'Trigger all active jobs to run immediately',
184
+ options: [
185
+ { flags: '-f, --filter <status>', description: 'Filter by job status', defaultValue: 'created' }
186
+ ],
187
+ action: async (options) => {
188
+ await this.withDaemonAction(async (client) => {
189
+ const jobs = await client.listJobs({ status: options.filter });
190
+ this.logInfo(`Triggering ${jobs.length} jobs...`);
191
+ for (const job of jobs) {
192
+ try {
193
+ this.logInfo(` Triggering ${job.name} (${job.id})...`);
194
+ const result = await client.triggerJob(job.id);
195
+ this.logSuccess(` ${job.name} completed`);
196
+ if (result.output) {
197
+ const preview = result.output.substring(0, 100);
198
+ this.logInfo(` Output: ${preview}${result.output.length > 100 ? '...' : ''}`);
199
+ }
200
+ }
201
+ catch (error) {
202
+ this.logError(` ${job.name} failed: ${error.message || error}`);
203
+ }
204
+ }
205
+ }, { forUser: true });
206
+ this.logSuccess('All jobs triggered');
207
+ }
208
+ });
209
+ // Stop job
210
+ this.addSubcommand(jobCmd, {
211
+ name: 'stop',
212
+ description: 'Stop a job',
213
+ arguments: [{ name: 'jobId', required: true }],
214
+ options: [
215
+ { flags: '-s, --signal <signal>', description: 'Signal to send', defaultValue: 'SIGTERM' }
216
+ ],
217
+ action: async (jobId, options) => {
218
+ await this.withDaemonAction(async (client) => {
219
+ await client.stopJob(jobId, options.signal);
220
+ });
221
+ this.logSuccess(`Job ${jobId} stopped with signal ${options.signal}`);
222
+ }
223
+ });
224
+ // Remove job
225
+ this.addSubcommand(jobCmd, {
226
+ name: 'remove',
227
+ description: 'Remove a job',
228
+ arguments: [{ name: 'jobId', required: true }],
229
+ options: [
230
+ { flags: '-f, --force', description: 'Force removal', defaultValue: false }
231
+ ],
232
+ action: async (jobId, options) => {
233
+ await this.withDaemonAction(async (client) => {
234
+ await client.removeJob(jobId, options.force);
235
+ });
236
+ this.logSuccess(`Job ${jobId} removed`);
237
+ }
238
+ });
239
+ // Job info
240
+ this.addSubcommand(jobCmd, {
241
+ name: 'info',
242
+ description: 'Get job information',
243
+ arguments: [{ name: 'jobId', required: true }],
244
+ action: async (jobId) => {
245
+ const job = await this.withDaemonAction(async (client) => {
246
+ return await client.getJob(jobId);
247
+ });
248
+ if (!job) {
249
+ throw new Error(`Job ${jobId} not found`);
250
+ }
251
+ this.logInfo(`Job Information: ${jobId}`);
252
+ this.logInfo(` Name: ${job.name}`);
253
+ this.logInfo(` Command: ${job.command}`);
254
+ this.logInfo(` Status: ${job.status}`);
255
+ this.logInfo(` Priority: ${job.priority}`);
256
+ this.logInfo(` Working Directory: ${job.cwd}`);
257
+ this.logInfo(` User: ${job.user}`);
258
+ this.logInfo(` Tags: ${job.tags?.join(', ') || 'None'}`);
259
+ if (job.schedule) {
260
+ this.logInfo(` Schedule: ${this.formatSchedule(job.schedule)}`);
261
+ }
262
+ }
263
+ });
264
+ }
265
+ registerDatabaseCommands(daemonCmd) {
266
+ const dbCmd = daemonCmd
267
+ .command('db')
268
+ .description('Database integration commands');
269
+ // Job history
270
+ this.addSubcommand(dbCmd, {
271
+ name: 'history',
272
+ description: 'Get job execution history from database',
273
+ options: [
274
+ { flags: '-j, --job-id <jobId>', description: 'Filter by job ID' },
275
+ { flags: '-l, --limit <limit>', description: 'Limit number of results', defaultValue: '50' }
276
+ ],
277
+ action: async (options) => {
278
+ const jobs = await this.withDaemonAction(async (client) => await client.getJobHistory(options.jobId, parseInt(options.limit)), { forUser: true, requireRunning: false });
279
+ this.logInfo(`Job History (${jobs.length} records):`);
280
+ jobs.forEach(job => {
281
+ const started = new Date(job.started_at).toLocaleString();
282
+ const completed = job.completed_at ? new Date(job.completed_at).toLocaleString() : 'Running';
283
+ this.logInfo(` ${job.job_id}: ${job.command}`);
284
+ this.logInfo(` Started: ${started}`);
285
+ this.logInfo(` Completed: ${completed}`);
286
+ this.logInfo(` Status: ${job.status}`);
287
+ this.logInfo(` Exit Code: ${job.exit_code || 'N/A'}`);
288
+ this.logInfo('');
289
+ });
290
+ }
291
+ });
292
+ // Job statistics
293
+ this.addSubcommand(dbCmd, {
294
+ name: 'stats',
295
+ description: 'Get job statistics from database',
296
+ options: [
297
+ { flags: '-j, --job-id <jobId>', description: 'Filter by job ID' }
298
+ ],
299
+ action: async (options) => {
300
+ const stats = await this.withDaemonAction(async (client) => await client.getJobStatistics(options.jobId), { forUser: true, requireRunning: false });
301
+ this.logInfo('Job Statistics:');
302
+ this.logInfo(` Total Jobs: ${stats.totalJobs}`);
303
+ this.logInfo(` Success Rate: ${stats.successRate.toFixed(1)}%`);
304
+ this.logInfo(` Last Execution: ${stats.lastExecution || 'Never'}`);
305
+ this.logInfo('\n Status Breakdown:');
306
+ Object.entries(stats.byStatus).forEach(([status, count]) => {
307
+ this.logInfo(` ${status}: ${count}`);
308
+ });
309
+ }
310
+ });
311
+ // Recent executions
312
+ this.addSubcommand(dbCmd, {
313
+ name: 'recent',
314
+ description: 'Show most recent job executions with output',
315
+ options: [
316
+ { flags: '-l, --limit <limit>', description: 'Number of recent executions to show', defaultValue: '5' }
317
+ ],
318
+ action: async (options) => {
319
+ const jobs = await this.withDaemonAction(async (client) => await client.getJobHistory(undefined, parseInt(options.limit)), { forUser: true });
320
+ this.logInfo(`Recent Job Executions (${jobs.length} records):`);
321
+ jobs.forEach((job, index) => {
322
+ const started = new Date(job.started_at).toLocaleString();
323
+ const status = job.status === 'completed' ? '✅' :
324
+ job.status === 'failed' ? '❌' :
325
+ job.status === 'running' ? '⏳' : '⏸️';
326
+ this.logInfo(`\n${index + 1}. ${status} ${job.job_id}`);
327
+ this.logInfo(` Executed: ${started}`);
328
+ this.logInfo(` Status: ${job.status}`);
329
+ if (job.output) {
330
+ const preview = job.output.substring(0, 200);
331
+ this.logInfo(` Output: ${preview}${job.output.length > 200 ? '...' : ''}`);
332
+ }
333
+ if (job.error) {
334
+ this.logInfo(` Error: ${job.error}`);
335
+ }
336
+ });
337
+ if (jobs.length === 0) {
338
+ this.logInfo('\nNo recent executions found.');
339
+ this.logInfo(' Trigger jobs to see execution history:');
340
+ this.logInfo(' lsh daemon job trigger <jobId>');
341
+ this.logInfo(' lsh daemon job trigger-all');
342
+ }
343
+ }
344
+ });
345
+ // Job status with executions
346
+ this.addSubcommand(dbCmd, {
347
+ name: 'status',
348
+ description: 'Show detailed status and recent executions of a specific job',
349
+ arguments: [{ name: 'jobId', required: true }],
350
+ action: async (jobId) => {
351
+ await this.withDaemonAction(async (client) => {
352
+ const jobs = await client.listJobs();
353
+ const job = jobs.find(j => j.id === jobId);
354
+ if (!job) {
355
+ throw new Error(`Job ${jobId} not found in daemon registry`);
356
+ }
357
+ this.logInfo(`Job Status: ${job.name} (${jobId})`);
358
+ const cmdPreview = job.command.substring(0, 100);
359
+ this.logInfo(` Command: ${cmdPreview}${job.command.length > 100 ? '...' : ''}`);
360
+ this.logInfo(` Status: ${job.status}`);
361
+ this.logInfo(` Schedule: ${this.formatSchedule(job.schedule)}`);
362
+ this.logInfo(` Priority: ${job.priority}`);
363
+ const executions = await client.getJobHistory(jobId, 5);
364
+ if (executions.length > 0) {
365
+ this.logInfo(`\nRecent Executions (${executions.length} records):`);
366
+ executions.forEach((exec, index) => {
367
+ const started = new Date(exec.started_at).toLocaleString();
368
+ const status = exec.status === 'completed' ? '✅' :
369
+ exec.status === 'failed' ? '❌' :
370
+ exec.status === 'running' ? '⏳' : '⏸️';
371
+ this.logInfo(`\n${index + 1}. ${status} ${started}`);
372
+ this.logInfo(` Status: ${exec.status} (Exit Code: ${exec.exit_code || 'N/A'})`);
373
+ if (exec.output) {
374
+ const preview = exec.output.substring(0, 150);
375
+ this.logInfo(` Output: ${preview}${exec.output.length > 150 ? '...' : ''}`);
376
+ }
377
+ });
378
+ }
379
+ else {
380
+ this.logInfo('\nNo execution history found for this job.');
381
+ this.logInfo(` Run: lsh daemon job trigger ${jobId}`);
382
+ }
383
+ }, { forUser: true });
384
+ }
385
+ });
386
+ // Sync jobs to database
387
+ this.addSubcommand(dbCmd, {
388
+ name: 'sync',
389
+ description: 'Sync current in-memory jobs to database',
390
+ action: async () => {
391
+ const synced = await this.withDaemonAction(async (client) => {
392
+ const jobs = await client.listJobs();
393
+ this.logInfo(`Syncing ${jobs.length} jobs to database...`);
394
+ let syncCount = 0;
395
+ for (const job of jobs) {
396
+ try {
397
+ const dbStatus = job.status === 'created' ? 'stopped' :
398
+ job.status === 'running' ? 'running' :
399
+ job.status === 'completed' ? 'completed' : 'failed';
400
+ await client.syncJobToDatabase({
401
+ id: job.id,
402
+ name: job.name,
403
+ command: job.command,
404
+ schedule: job.schedule,
405
+ enabled: true,
406
+ databaseSync: true
407
+ }, dbStatus);
408
+ this.logSuccess(` Synced ${job.name} (${job.id}) - status: ${dbStatus}`);
409
+ syncCount++;
410
+ }
411
+ catch (error) {
412
+ this.logError(` Failed to sync ${job.name}: ${error.message}`);
413
+ }
414
+ }
415
+ return { syncCount, totalJobs: jobs.length };
416
+ }, { forUser: true });
417
+ this.logSuccess(`\nSuccessfully synced ${synced.syncCount}/${synced.totalJobs} jobs to database`);
418
+ this.logInfo('\nCheck results with:');
419
+ this.logInfo(' lsh daemon db stats');
420
+ this.logInfo(' lsh daemon db history');
421
+ }
422
+ });
423
+ }
424
+ async cleanupDaemon(force = false) {
425
+ this.logInfo('LSH Daemon Cleanup');
426
+ this.logInfo('====================');
427
+ this.logInfo('');
428
+ try {
429
+ // 1. Find daemon processes
430
+ this.logInfo('Finding LSH daemon processes...');
431
+ let daemonPids = [];
432
+ try {
433
+ const { stdout } = await execAsync('pgrep -f "lshd.js"');
434
+ daemonPids = stdout.trim().split('\n').filter(pid => pid.length > 0);
435
+ }
436
+ catch (_error) {
437
+ // No processes found
438
+ }
439
+ if (daemonPids.length > 0) {
440
+ this.logInfo('Found daemon processes:');
441
+ try {
442
+ const { stdout } = await execAsync('ps aux | grep lshd | grep -v grep');
443
+ stdout.split('\n').filter(line => line.trim()).forEach(line => this.logInfo(` ${line}`));
444
+ }
445
+ catch (_error) {
446
+ this.logInfo(` PIDs: ${daemonPids.join(', ')}`);
447
+ }
448
+ this.logInfo('');
449
+ if (force) {
450
+ this.logInfo('Force mode: Killing daemon processes...');
451
+ try {
452
+ await execAsync('pkill -f "lshd.js"');
453
+ this.logSuccess('Daemon processes killed');
454
+ }
455
+ catch (_error) {
456
+ this.logWarning('Some processes may require manual cleanup');
457
+ }
458
+ }
459
+ else {
460
+ this.logWarning('Found running daemon processes. Use --force to kill them automatically');
461
+ this.logInfo(' Or manually run: pkill -f "lshd.js"');
462
+ }
463
+ }
464
+ else {
465
+ this.logInfo('No daemon processes found');
466
+ }
467
+ // 2. Clean up socket files
468
+ this.logInfo('Cleaning up socket files...');
469
+ try {
470
+ const { stdout } = await execAsync(`find /tmp -name "lsh-*daemon*.sock" 2>/dev/null || echo ""`);
471
+ const socketFiles = stdout.trim().split('\n').filter(file => file.length > 0);
472
+ if (socketFiles.length > 0) {
473
+ this.logInfo('Found socket files:');
474
+ socketFiles.forEach(file => this.logInfo(` ${file}`));
475
+ for (const socket of socketFiles) {
476
+ try {
477
+ if (fs.existsSync(socket)) {
478
+ fs.unlinkSync(socket);
479
+ this.logSuccess(`Removed: ${socket}`);
480
+ }
481
+ }
482
+ catch (error) {
483
+ this.logWarning(`Could not remove: ${socket} (${error.message})`);
484
+ }
485
+ }
486
+ }
487
+ else {
488
+ this.logInfo('No socket files found');
489
+ }
490
+ }
491
+ catch (_error) {
492
+ this.logInfo('No socket files found');
493
+ }
494
+ // 3. Clean up PID files
495
+ this.logInfo('Cleaning up PID files...');
496
+ try {
497
+ const { stdout } = await execAsync(`find /tmp -name "lsh-*daemon*.pid" 2>/dev/null || echo ""`);
498
+ const pidFiles = stdout.trim().split('\n').filter(file => file.length > 0);
499
+ if (pidFiles.length > 0) {
500
+ this.logInfo('Found PID files:');
501
+ pidFiles.forEach(file => this.logInfo(` ${file}`));
502
+ for (const pidFile of pidFiles) {
503
+ try {
504
+ if (fs.existsSync(pidFile)) {
505
+ fs.unlinkSync(pidFile);
506
+ this.logSuccess(`Removed: ${pidFile}`);
507
+ }
508
+ }
509
+ catch (error) {
510
+ this.logWarning(`Could not remove: ${pidFile} (${error.message})`);
511
+ }
512
+ }
513
+ }
514
+ else {
515
+ this.logInfo('No PID files found');
516
+ }
517
+ }
518
+ catch (_error) {
519
+ this.logInfo('No PID files found');
520
+ }
521
+ // 4. Verify cleanup
522
+ this.logInfo('');
523
+ this.logInfo('Verifying cleanup...');
524
+ let remainingProcesses = [];
525
+ try {
526
+ const { stdout } = await execAsync('pgrep -f "lshd.js"');
527
+ remainingProcesses = stdout.trim().split('\n').filter(pid => pid.length > 0);
528
+ }
529
+ catch (_error) {
530
+ // No processes found (good)
531
+ }
532
+ let remainingSockets = [];
533
+ try {
534
+ const { stdout } = await execAsync(`find /tmp -name "lsh-*daemon*.sock" 2>/dev/null || echo ""`);
535
+ remainingSockets = stdout.trim().split('\n').filter(file => file.length > 0);
536
+ }
537
+ catch (_error) {
538
+ // No sockets found (good)
539
+ }
540
+ if (remainingProcesses.length === 0 && remainingSockets.length === 0) {
541
+ this.logSuccess('Cleanup completed successfully!');
542
+ this.logInfo('');
543
+ this.logInfo('Next steps:');
544
+ this.logInfo(' 1. Start fresh daemon: lsh daemon start');
545
+ this.logInfo(' 2. Check status: lsh daemon status');
546
+ this.logInfo(' 3. View dashboard: ./scripts/monitor-dashboard.sh');
547
+ }
548
+ else {
549
+ this.logWarning('Some items may still remain:');
550
+ if (remainingProcesses.length > 0) {
551
+ this.logInfo(` Processes: ${remainingProcesses.join(', ')}`);
552
+ }
553
+ if (remainingSockets.length > 0) {
554
+ this.logInfo(` Sockets: ${remainingSockets.join(', ')}`);
555
+ }
556
+ this.logInfo('');
557
+ this.logInfo('Try running with --force for complete cleanup:');
558
+ this.logInfo(' lsh daemon cleanup --force');
559
+ }
560
+ }
561
+ catch (error) {
562
+ throw new Error(`Cleanup failed: ${error.message}`);
563
+ }
564
+ }
565
+ }
@@ -0,0 +1,9 @@
1
+ /**
2
+ * Daemon Service - CLI command registration
3
+ * Uses DaemonCommandRegistrar for clean, maintainable command setup
4
+ */
5
+ import { DaemonCommandRegistrar } from './daemon-registrar.js';
6
+ export async function init_daemon(program) {
7
+ const registrar = new DaemonCommandRegistrar();
8
+ await registrar.register(program);
9
+ }
@@ -0,0 +1,86 @@
1
+ import { getFiles } from "../../util/lib.util.js";
2
+ async function parseCommands(files) {
3
+ const commands = {};
4
+ for (const file of files) {
5
+ if (file !== "lib.ts") {
6
+ const cmd_exports = await import(`./${file.split(".")[0]}.js`);
7
+ for (const [key, value] of Object.entries(cmd_exports)) {
8
+ if (key.indexOf("cmd") !== -1) {
9
+ commands[key.split("cmd_")[1]] = value;
10
+ }
11
+ }
12
+ }
13
+ }
14
+ return commands;
15
+ }
16
+ export async function loadCommands() {
17
+ const files = await getFiles();
18
+ const cmdMap = await parseCommands(files);
19
+ return cmdMap;
20
+ }
21
+ async function _makeCommand(commander) {
22
+ const _commands = await loadCommands();
23
+ commander.command("jug").action(() => {
24
+ console.log("heat jug");
25
+ });
26
+ commander.command("pot").action(() => {
27
+ console.log("heat pot");
28
+ });
29
+ return commander;
30
+ }
31
+ // export async function init_lib_cmd(program: Command) {
32
+ // const brew = program.command("lib");
33
+ // // const commands = await loadCommands();
34
+ // // await set(lsh.commands, commands);
35
+ // brew.command("tea").action(() => {
36
+ // console.log("brew tea");
37
+ // });
38
+ // brew.command("coffee").action(() => {
39
+ // console.log("brew coffee");
40
+ // });
41
+ // await makeCommand(brew);
42
+ // // for (let c in commands) {
43
+ // // brew.command(c).action(() => console.log(c));
44
+ // // }
45
+ // // .command("lib").description("lsh lib commands");
46
+ // // lib
47
+ // // .showHelpAfterError(true)
48
+ // // .showSuggestionAfterError(true);
49
+ // // const commands = await loadCommands();
50
+ // // set(lsh.commands, commands);
51
+ // // for (const [key, value] of Object.entries(get(lsh.commands))) {
52
+ // // // console.log(`${key} : ${value}`);
53
+ // // lib.command(key).action(() => {console.log(value)});
54
+ // // };
55
+ // // .action(async (type: String, action: String, spec: Spec) => {
56
+ // // const commands = await loadCommands();
57
+ // // set(lsh.commands, commands);
58
+ // // switch (type) {
59
+ // // case "ls":
60
+ // // // console.log("lsh called");
61
+ // // // console.log(get(lsh.commands)['rand']());
62
+ // // break;
63
+ // // default:
64
+ // // console.log("default");
65
+ // // }
66
+ // // });
67
+ // }
68
+ export async function init_lib(program) {
69
+ const lib = program.command("lib");
70
+ // Load and register dynamic commands
71
+ const commands = await loadCommands();
72
+ for (const commandName of Object.keys(commands)) {
73
+ lib
74
+ .command(commandName)
75
+ .action(async () => {
76
+ console.log(commands[commandName]());
77
+ })
78
+ .description("commandName")
79
+ .usage(`${commandName} used as follows:`);
80
+ }
81
+ // Optional: Enhance the 'lib' command group with additional descriptions and error handling
82
+ lib
83
+ .showHelpAfterError("Command not recognized, here's some help.")
84
+ .showSuggestionAfterError(true);
85
+ return lib;
86
+ }