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,58 @@
|
|
|
1
|
+
import AsyncLock from 'async-lock';
|
|
2
|
+
import request from 'request';
|
|
3
|
+
import { CONFIG } from './config.js';
|
|
4
|
+
import { FILE } from './file.js';
|
|
5
|
+
const semaphore = new AsyncLock();
|
|
6
|
+
let pkgId;
|
|
7
|
+
export const makePOSTRequest = async (typeName, method, data, onSuccess) => {
|
|
8
|
+
console.log("makePostRequest");
|
|
9
|
+
const url = CONFIG.URL + '/api/8' + '/' + typeName + '/' + method;
|
|
10
|
+
console.log(url);
|
|
11
|
+
// Prevent parallel writes/deletions
|
|
12
|
+
return semaphore.acquire('request', (done) => {
|
|
13
|
+
return request.post(url, {
|
|
14
|
+
method: 'POST',
|
|
15
|
+
body: data,
|
|
16
|
+
json: true,
|
|
17
|
+
headers: {
|
|
18
|
+
Authorization: CONFIG.AUTH_TOKEN,
|
|
19
|
+
},
|
|
20
|
+
}, (err, response, body) => {
|
|
21
|
+
console.log(body);
|
|
22
|
+
onSuccess?.(response);
|
|
23
|
+
done();
|
|
24
|
+
});
|
|
25
|
+
});
|
|
26
|
+
};
|
|
27
|
+
const getMetadataPath = (path) => {
|
|
28
|
+
console.log("getMetadataPath");
|
|
29
|
+
return path.substring(path.indexOf(CONFIG.PATH_TO_PACKAGE_REPO) + CONFIG.PATH_TO_PACKAGE_REPO.length);
|
|
30
|
+
};
|
|
31
|
+
const getPkgId = async () => {
|
|
32
|
+
console.log("getPkgId");
|
|
33
|
+
if (pkgId) {
|
|
34
|
+
return pkgId;
|
|
35
|
+
}
|
|
36
|
+
await makePOSTRequest('Pkg', 'inst', ['Pkg'], (body) => {
|
|
37
|
+
pkgId = body;
|
|
38
|
+
});
|
|
39
|
+
return pkgId;
|
|
40
|
+
};
|
|
41
|
+
const _writeContent = async (path) => {
|
|
42
|
+
console.log("writeContent");
|
|
43
|
+
const pkgId = await getPkgId();
|
|
44
|
+
const metadataPath = getMetadataPath(path);
|
|
45
|
+
const content = FILE.encodeContent(path);
|
|
46
|
+
if (await content === FILE.NO_CHANGE_TO_FILE) {
|
|
47
|
+
return;
|
|
48
|
+
}
|
|
49
|
+
return makePOSTRequest('Pkg', 'writeContent', [pkgId, metadataPath, {
|
|
50
|
+
type: 'ContentValue',
|
|
51
|
+
content,
|
|
52
|
+
}], () => console.log("Success"));
|
|
53
|
+
};
|
|
54
|
+
const _deleteContent = async (path) => {
|
|
55
|
+
const pkgId = await getPkgId();
|
|
56
|
+
const metadataPath = getMetadataPath(path);
|
|
57
|
+
return makePOSTRequest('Pkg', 'deleteContent', [pkgId, metadataPath, true], () => console.log("deleted!"));
|
|
58
|
+
};
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import crypto from "crypto";
|
|
2
|
+
import "dotenv/config";
|
|
3
|
+
export const generateSessionToken = function () {
|
|
4
|
+
const USER = JSON.parse(process.env.USER || '{}');
|
|
5
|
+
return USER; // const basicUser: string = TestIdp.createTestUsersForGroup('GenAiSearch.Role.User')[0];
|
|
6
|
+
// const basicUserToken: string = SessionToken.generate(this.basicUser).signedToken;
|
|
7
|
+
// return basicUserToken;
|
|
8
|
+
};
|
|
9
|
+
/**
|
|
10
|
+
* Creates a function that generates key tokens based on a private key.
|
|
11
|
+
*
|
|
12
|
+
* @param user The username of the user to authenticate
|
|
13
|
+
* @param pvtKey The private key to use
|
|
14
|
+
*/
|
|
15
|
+
export const createGlobalAuthTokenGenerator = function (user, pvtKey) {
|
|
16
|
+
if (!pvtKey)
|
|
17
|
+
throw new Error("Cannot generate key auth token without private key");
|
|
18
|
+
const generatekeyAuthToken = function () {
|
|
19
|
+
// WARNING: The following logic is mostly copied from boot.js
|
|
20
|
+
const signAlgo = "RSA-SHA512";
|
|
21
|
+
const signatureText = Date.now().toString();
|
|
22
|
+
const signer = crypto.createSign(signAlgo);
|
|
23
|
+
signer.update(signatureText);
|
|
24
|
+
const signature = signer.sign(pvtKey, "base64");
|
|
25
|
+
const tokenString = user +
|
|
26
|
+
":" +
|
|
27
|
+
Buffer.from(signatureText).toString("base64") +
|
|
28
|
+
":" +
|
|
29
|
+
signature;
|
|
30
|
+
const authToken = "key " + Buffer.from(tokenString).toString("base64");
|
|
31
|
+
return authToken;
|
|
32
|
+
};
|
|
33
|
+
// Publish our generator to the global scope as the type system is expecting it.
|
|
34
|
+
global.generateAuthToken = generatekeyAuthToken;
|
|
35
|
+
};
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import fprint from 'fprint';
|
|
2
|
+
import fs from 'fs';
|
|
3
|
+
const IN_MEMORY_FILE_FINGERPRINTS = {};
|
|
4
|
+
const NO_CHANGE_TO_FILE = -1;
|
|
5
|
+
const encodeContent = async (path) => {
|
|
6
|
+
const fileContents = fs.readFileSync(path);
|
|
7
|
+
const fingerprint = await fprint(fileContents, 'md5');
|
|
8
|
+
if (IN_MEMORY_FILE_FINGERPRINTS[path] !== fingerprint) {
|
|
9
|
+
IN_MEMORY_FILE_FINGERPRINTS[path] = fingerprint;
|
|
10
|
+
return fs.readFileSync(path, {
|
|
11
|
+
encoding: 'base64',
|
|
12
|
+
});
|
|
13
|
+
}
|
|
14
|
+
else {
|
|
15
|
+
return NO_CHANGE_TO_FILE;
|
|
16
|
+
}
|
|
17
|
+
};
|
|
18
|
+
export const FILE = {
|
|
19
|
+
encodeContent,
|
|
20
|
+
IN_MEMORY_FILE_FINGERPRINTS,
|
|
21
|
+
NO_CHANGE_TO_FILE,
|
|
22
|
+
};
|
|
@@ -0,0 +1,235 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Cron Command Registrar
|
|
3
|
+
* Registers all cron-related CLI commands using BaseCommandRegistrar
|
|
4
|
+
*/
|
|
5
|
+
import { BaseCommandRegistrar } from '../../lib/base-command-registrar.js';
|
|
6
|
+
export class CronCommandRegistrar extends BaseCommandRegistrar {
|
|
7
|
+
constructor() {
|
|
8
|
+
super('CronService');
|
|
9
|
+
}
|
|
10
|
+
async register(program) {
|
|
11
|
+
const cronCmd = this.createCommand(program, 'cron', 'Cron job management with database integration');
|
|
12
|
+
this.registerTemplateCommands(cronCmd);
|
|
13
|
+
this.registerJobManagementCommands(cronCmd);
|
|
14
|
+
this.registerReportingCommands(cronCmd);
|
|
15
|
+
}
|
|
16
|
+
registerTemplateCommands(cronCmd) {
|
|
17
|
+
// List templates
|
|
18
|
+
this.addSubcommand(cronCmd, {
|
|
19
|
+
name: 'templates',
|
|
20
|
+
description: 'List available job templates',
|
|
21
|
+
action: async () => {
|
|
22
|
+
await this.withCronManager(async (manager) => {
|
|
23
|
+
const templates = manager.listTemplates();
|
|
24
|
+
this.logInfo('Available Job Templates:');
|
|
25
|
+
templates.forEach(template => {
|
|
26
|
+
this.logInfo(`\n ${template.id}: ${template.name}`);
|
|
27
|
+
this.logInfo(` Description: ${template.description}`);
|
|
28
|
+
this.logInfo(` Command: ${template.command}`);
|
|
29
|
+
this.logInfo(` Schedule: ${template.schedule}`);
|
|
30
|
+
this.logInfo(` Category: ${template.category}`);
|
|
31
|
+
this.logInfo(` Tags: ${template.tags.join(', ')}`);
|
|
32
|
+
});
|
|
33
|
+
}, { requireRunning: false });
|
|
34
|
+
}
|
|
35
|
+
});
|
|
36
|
+
// Create job from template
|
|
37
|
+
this.addSubcommand(cronCmd, {
|
|
38
|
+
name: 'create-from-template',
|
|
39
|
+
description: 'Create a job from a template',
|
|
40
|
+
arguments: [{ name: 'templateId', required: true }],
|
|
41
|
+
options: [
|
|
42
|
+
{ flags: '-n, --name <name>', description: 'Custom job name' },
|
|
43
|
+
{ flags: '-c, --command <command>', description: 'Custom command' },
|
|
44
|
+
{ flags: '-s, --schedule <schedule>', description: 'Custom cron schedule' },
|
|
45
|
+
{ flags: '-w, --working-dir <dir>', description: 'Working directory' },
|
|
46
|
+
{ flags: '-e, --env <env>', description: 'Environment variables (JSON)' },
|
|
47
|
+
{ flags: '-t, --tags <tags>', description: 'Comma-separated tags' },
|
|
48
|
+
{ flags: '-p, --priority <priority>', description: 'Priority (0-10)', defaultValue: '5' }
|
|
49
|
+
],
|
|
50
|
+
action: async (templateId, options) => {
|
|
51
|
+
const result = await this.withCronManager(async (manager) => {
|
|
52
|
+
const customizations = {};
|
|
53
|
+
if (options.name)
|
|
54
|
+
customizations.name = options.name;
|
|
55
|
+
if (options.command)
|
|
56
|
+
customizations.command = options.command;
|
|
57
|
+
if (options.schedule)
|
|
58
|
+
customizations.schedule = { cron: options.schedule };
|
|
59
|
+
if (options.workingDir)
|
|
60
|
+
customizations.workingDirectory = options.workingDir;
|
|
61
|
+
if (options.env)
|
|
62
|
+
customizations.environment = this.parseJSON(options.env, 'environment variables');
|
|
63
|
+
if (options.tags)
|
|
64
|
+
customizations.tags = this.parseTags(options.tags);
|
|
65
|
+
if (options.priority)
|
|
66
|
+
customizations.priority = parseInt(options.priority);
|
|
67
|
+
return await manager.createJobFromTemplate(templateId, customizations);
|
|
68
|
+
});
|
|
69
|
+
this.logSuccess('Job created from template:');
|
|
70
|
+
this.logInfo(` Template: ${templateId}`);
|
|
71
|
+
this.logInfo(` Job ID: ${result.id}`);
|
|
72
|
+
this.logInfo(` Name: ${result.name}`);
|
|
73
|
+
this.logInfo(` Command: ${result.command}`);
|
|
74
|
+
this.logInfo(` Schedule: ${result.schedule.cron}`);
|
|
75
|
+
}
|
|
76
|
+
});
|
|
77
|
+
}
|
|
78
|
+
registerJobManagementCommands(cronCmd) {
|
|
79
|
+
// List jobs
|
|
80
|
+
this.addSubcommand(cronCmd, {
|
|
81
|
+
name: 'list',
|
|
82
|
+
description: 'List all cron jobs',
|
|
83
|
+
options: [
|
|
84
|
+
{ flags: '-f, --filter <filter>', description: 'Filter by status' }
|
|
85
|
+
],
|
|
86
|
+
action: async (options) => {
|
|
87
|
+
const jobs = await this.withCronManager(async (manager) => {
|
|
88
|
+
return await manager.listJobs(options.filter ? { status: options.filter } : undefined);
|
|
89
|
+
});
|
|
90
|
+
this.logInfo(`Cron Jobs (${jobs.length} total):`);
|
|
91
|
+
jobs.forEach(job => {
|
|
92
|
+
const schedule = job.schedule?.cron || `${job.schedule?.interval}ms interval`;
|
|
93
|
+
this.logInfo(`\n ${job.id}: ${job.name}`);
|
|
94
|
+
this.logInfo(` Command: ${job.command}`);
|
|
95
|
+
this.logInfo(` Schedule: ${schedule}`);
|
|
96
|
+
this.logInfo(` Status: ${job.status}`);
|
|
97
|
+
this.logInfo(` Priority: ${job.priority}`);
|
|
98
|
+
this.logInfo(` Tags: ${job.tags?.join(', ') || 'None'}`);
|
|
99
|
+
});
|
|
100
|
+
}
|
|
101
|
+
});
|
|
102
|
+
// Get job info
|
|
103
|
+
this.addSubcommand(cronCmd, {
|
|
104
|
+
name: 'info',
|
|
105
|
+
description: 'Get job information',
|
|
106
|
+
arguments: [{ name: 'jobId', required: true }],
|
|
107
|
+
action: async (jobId) => {
|
|
108
|
+
const job = await this.withCronManager(async (manager) => {
|
|
109
|
+
return await manager.getJob(jobId);
|
|
110
|
+
});
|
|
111
|
+
if (!job) {
|
|
112
|
+
throw new Error(`Job ${jobId} not found`);
|
|
113
|
+
}
|
|
114
|
+
this.logInfo(`Job Information: ${jobId}`);
|
|
115
|
+
this.logInfo(` Name: ${job.name}`);
|
|
116
|
+
this.logInfo(` Command: ${job.command}`);
|
|
117
|
+
this.logInfo(` Status: ${job.status}`);
|
|
118
|
+
this.logInfo(` Priority: ${job.priority}`);
|
|
119
|
+
this.logInfo(` Working Directory: ${job.cwd}`);
|
|
120
|
+
this.logInfo(` User: ${job.user}`);
|
|
121
|
+
this.logInfo(` Tags: ${job.tags?.join(', ') || 'None'}`);
|
|
122
|
+
if (job.schedule) {
|
|
123
|
+
const schedule = job.schedule.cron || `${job.schedule.interval}ms interval`;
|
|
124
|
+
this.logInfo(` Schedule: ${schedule}`);
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
});
|
|
128
|
+
// Start job
|
|
129
|
+
this.addSubcommand(cronCmd, {
|
|
130
|
+
name: 'start',
|
|
131
|
+
description: 'Start a job',
|
|
132
|
+
arguments: [{ name: 'jobId', required: true }],
|
|
133
|
+
action: async (jobId) => {
|
|
134
|
+
await this.withCronManager(async (manager) => {
|
|
135
|
+
await manager.startJob(jobId);
|
|
136
|
+
});
|
|
137
|
+
this.logSuccess(`Job ${jobId} started`);
|
|
138
|
+
}
|
|
139
|
+
});
|
|
140
|
+
// Stop job
|
|
141
|
+
this.addSubcommand(cronCmd, {
|
|
142
|
+
name: 'stop',
|
|
143
|
+
description: 'Stop a job',
|
|
144
|
+
arguments: [{ name: 'jobId', required: true }],
|
|
145
|
+
options: [
|
|
146
|
+
{ flags: '-s, --signal <signal>', description: 'Signal to send', defaultValue: 'SIGTERM' }
|
|
147
|
+
],
|
|
148
|
+
action: async (jobId, options) => {
|
|
149
|
+
await this.withCronManager(async (manager) => {
|
|
150
|
+
await manager.stopJob(jobId, options.signal);
|
|
151
|
+
});
|
|
152
|
+
this.logSuccess(`Job ${jobId} stopped with signal ${options.signal}`);
|
|
153
|
+
}
|
|
154
|
+
});
|
|
155
|
+
// Remove job
|
|
156
|
+
this.addSubcommand(cronCmd, {
|
|
157
|
+
name: 'remove',
|
|
158
|
+
description: 'Remove a job',
|
|
159
|
+
arguments: [{ name: 'jobId', required: true }],
|
|
160
|
+
options: [
|
|
161
|
+
{ flags: '-f, --force', description: 'Force removal', defaultValue: false }
|
|
162
|
+
],
|
|
163
|
+
action: async (jobId, options) => {
|
|
164
|
+
await this.withCronManager(async (manager) => {
|
|
165
|
+
await manager.removeJob(jobId, options.force);
|
|
166
|
+
});
|
|
167
|
+
this.logSuccess(`Job ${jobId} removed`);
|
|
168
|
+
}
|
|
169
|
+
});
|
|
170
|
+
}
|
|
171
|
+
registerReportingCommands(cronCmd) {
|
|
172
|
+
// Get job report
|
|
173
|
+
this.addSubcommand(cronCmd, {
|
|
174
|
+
name: 'report',
|
|
175
|
+
description: 'Get detailed job execution report',
|
|
176
|
+
arguments: [{ name: 'jobId', required: true }],
|
|
177
|
+
action: async (jobId) => {
|
|
178
|
+
const report = await this.withCronManager(async (manager) => {
|
|
179
|
+
return await manager.getJobReport(jobId);
|
|
180
|
+
});
|
|
181
|
+
this.displayJobReport(report);
|
|
182
|
+
}
|
|
183
|
+
});
|
|
184
|
+
// Get all job reports
|
|
185
|
+
this.addSubcommand(cronCmd, {
|
|
186
|
+
name: 'reports',
|
|
187
|
+
description: 'Get reports for all jobs',
|
|
188
|
+
action: async () => {
|
|
189
|
+
const reports = await this.withCronManager(async (manager) => {
|
|
190
|
+
return await manager.getAllJobReports();
|
|
191
|
+
});
|
|
192
|
+
this.logInfo('All Job Reports:');
|
|
193
|
+
reports.forEach(report => {
|
|
194
|
+
this.logInfo(`\n ${report.jobId}:`);
|
|
195
|
+
this.logInfo(` Executions: ${report.executions}`);
|
|
196
|
+
this.logInfo(` Success Rate: ${report.successRate.toFixed(1)}%`);
|
|
197
|
+
this.logInfo(` Last Execution: ${report.lastExecution?.toISOString() || 'Never'}`);
|
|
198
|
+
});
|
|
199
|
+
}
|
|
200
|
+
});
|
|
201
|
+
// Generate comprehensive report
|
|
202
|
+
this.addSubcommand(cronCmd, {
|
|
203
|
+
name: 'comprehensive-report',
|
|
204
|
+
description: 'Generate comprehensive job report',
|
|
205
|
+
action: async () => {
|
|
206
|
+
const report = await this.withCronManager(async (manager) => {
|
|
207
|
+
return await manager.generateComprehensiveReport();
|
|
208
|
+
});
|
|
209
|
+
this.logInfo(report);
|
|
210
|
+
}
|
|
211
|
+
});
|
|
212
|
+
// Export job data
|
|
213
|
+
this.addSubcommand(cronCmd, {
|
|
214
|
+
name: 'export',
|
|
215
|
+
description: 'Export job data',
|
|
216
|
+
options: [
|
|
217
|
+
{ flags: '-f, --format <format>', description: 'Export format (json or csv)', defaultValue: 'json' },
|
|
218
|
+
{ flags: '-o, --output <file>', description: 'Output file path' }
|
|
219
|
+
],
|
|
220
|
+
action: async (options) => {
|
|
221
|
+
const data = await this.withCronManager(async (manager) => {
|
|
222
|
+
return await manager.exportJobData(options.format);
|
|
223
|
+
});
|
|
224
|
+
if (options.output) {
|
|
225
|
+
const fs = await import('fs');
|
|
226
|
+
fs.writeFileSync(options.output, data);
|
|
227
|
+
this.logSuccess(`Data exported to ${options.output}`);
|
|
228
|
+
}
|
|
229
|
+
else {
|
|
230
|
+
this.logInfo(data);
|
|
231
|
+
}
|
|
232
|
+
}
|
|
233
|
+
});
|
|
234
|
+
}
|
|
235
|
+
}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Cron Service - CLI command registration
|
|
3
|
+
* Uses CronCommandRegistrar for clean, maintainable command setup
|
|
4
|
+
*/
|
|
5
|
+
import { CronCommandRegistrar } from './cron-registrar.js';
|
|
6
|
+
export async function init_cron(program) {
|
|
7
|
+
const registrar = new CronCommandRegistrar();
|
|
8
|
+
await registrar.register(program);
|
|
9
|
+
}
|