lsh-framework 0.8.3 → 0.9.1
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/README.md +75 -1
- package/dist/cli.js +14 -10
- package/dist/daemon/lshd.js +23 -13
- package/dist/lib/api-error-handler.js +16 -14
- package/dist/lib/base-command-registrar.js +6 -5
- package/dist/lib/daemon-client.js +13 -9
- package/dist/lib/database-persistence.js +8 -8
- package/dist/lib/env-validator.js +0 -3
- package/dist/lib/logger.js +0 -1
- package/dist/lib/secrets-manager.js +119 -25
- package/dist/lib/zsh-import-manager.js +17 -9
- package/dist/pipeline/job-tracker.js +1 -1
- package/dist/pipeline/mcli-bridge.js +11 -5
- package/dist/pipeline/workflow-engine.js +10 -7
- package/dist/services/cron/cron-registrar.js +27 -22
- package/dist/services/daemon/daemon-registrar.js +27 -13
- package/dist/services/secrets/secrets.js +37 -28
- package/dist/services/supabase/supabase-registrar.js +40 -33
- package/package.json +1 -1
|
@@ -29,8 +29,12 @@ export class DaemonCommandRegistrar extends BaseCommandRegistrar {
|
|
|
29
29
|
this.logInfo('Daemon Status:');
|
|
30
30
|
this.logInfo(` PID: ${status.pid}`);
|
|
31
31
|
this.logInfo(` Uptime: ${Math.floor(status.uptime / 60)} minutes`);
|
|
32
|
-
|
|
33
|
-
|
|
32
|
+
if (status.memoryUsage) {
|
|
33
|
+
this.logInfo(` Memory: ${Math.round(status.memoryUsage.heapUsed / 1024 / 1024)} MB`);
|
|
34
|
+
}
|
|
35
|
+
if (status.jobs) {
|
|
36
|
+
this.logInfo(` Jobs: ${status.jobs.total} total, ${status.jobs.running} running`);
|
|
37
|
+
}
|
|
34
38
|
}
|
|
35
39
|
});
|
|
36
40
|
// Start command
|
|
@@ -95,7 +99,8 @@ export class DaemonCommandRegistrar extends BaseCommandRegistrar {
|
|
|
95
99
|
{ flags: '-f, --force', description: 'Force cleanup without prompts', defaultValue: false }
|
|
96
100
|
],
|
|
97
101
|
action: async (options) => {
|
|
98
|
-
|
|
102
|
+
const opts = options;
|
|
103
|
+
await this.cleanupDaemon(opts.force);
|
|
99
104
|
}
|
|
100
105
|
});
|
|
101
106
|
}
|
|
@@ -122,11 +127,13 @@ export class DaemonCommandRegistrar extends BaseCommandRegistrar {
|
|
|
122
127
|
{ flags: '--no-database-sync', description: 'Disable database synchronization' }
|
|
123
128
|
],
|
|
124
129
|
action: async (options) => {
|
|
125
|
-
|
|
130
|
+
const opts = options;
|
|
131
|
+
if (!opts.name || !opts.command || (!opts.schedule && !opts.interval)) {
|
|
126
132
|
throw new Error('Missing required options: --name, --command, and (--schedule or --interval)');
|
|
127
133
|
}
|
|
128
|
-
const jobSpec = this.createJobSpec(
|
|
134
|
+
const jobSpec = this.createJobSpec(opts);
|
|
129
135
|
await this.withDaemonAction(async (client) => {
|
|
136
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
130
137
|
await client.createDatabaseCronJob(jobSpec);
|
|
131
138
|
});
|
|
132
139
|
this.logSuccess('Job created successfully:');
|
|
@@ -145,8 +152,9 @@ export class DaemonCommandRegistrar extends BaseCommandRegistrar {
|
|
|
145
152
|
{ flags: '-f, --filter <filter>', description: 'Filter jobs by status' }
|
|
146
153
|
],
|
|
147
154
|
action: async (options) => {
|
|
155
|
+
const opts = options;
|
|
148
156
|
const jobs = await this.withDaemonAction(async (client) => {
|
|
149
|
-
return await client.listJobs(
|
|
157
|
+
return await client.listJobs(opts.filter ? { status: opts.filter } : undefined);
|
|
150
158
|
});
|
|
151
159
|
this.displayJobs(jobs);
|
|
152
160
|
}
|
|
@@ -185,8 +193,9 @@ export class DaemonCommandRegistrar extends BaseCommandRegistrar {
|
|
|
185
193
|
{ flags: '-f, --filter <status>', description: 'Filter by job status', defaultValue: 'created' }
|
|
186
194
|
],
|
|
187
195
|
action: async (options) => {
|
|
196
|
+
const opts = options;
|
|
188
197
|
await this.withDaemonAction(async (client) => {
|
|
189
|
-
const jobs = await client.listJobs({ status:
|
|
198
|
+
const jobs = await client.listJobs({ status: opts.filter });
|
|
190
199
|
this.logInfo(`Triggering ${jobs.length} jobs...`);
|
|
191
200
|
for (const job of jobs) {
|
|
192
201
|
try {
|
|
@@ -215,10 +224,11 @@ export class DaemonCommandRegistrar extends BaseCommandRegistrar {
|
|
|
215
224
|
{ flags: '-s, --signal <signal>', description: 'Signal to send', defaultValue: 'SIGTERM' }
|
|
216
225
|
],
|
|
217
226
|
action: async (jobId, options) => {
|
|
227
|
+
const opts = options;
|
|
218
228
|
await this.withDaemonAction(async (client) => {
|
|
219
|
-
await client.stopJob(jobId,
|
|
229
|
+
await client.stopJob(jobId, opts.signal);
|
|
220
230
|
});
|
|
221
|
-
this.logSuccess(`Job ${jobId} stopped with signal ${
|
|
231
|
+
this.logSuccess(`Job ${jobId} stopped with signal ${opts.signal}`);
|
|
222
232
|
}
|
|
223
233
|
});
|
|
224
234
|
// Remove job
|
|
@@ -230,8 +240,9 @@ export class DaemonCommandRegistrar extends BaseCommandRegistrar {
|
|
|
230
240
|
{ flags: '-f, --force', description: 'Force removal', defaultValue: false }
|
|
231
241
|
],
|
|
232
242
|
action: async (jobId, options) => {
|
|
243
|
+
const opts = options;
|
|
233
244
|
await this.withDaemonAction(async (client) => {
|
|
234
|
-
await client.removeJob(jobId,
|
|
245
|
+
await client.removeJob(jobId, opts.force);
|
|
235
246
|
});
|
|
236
247
|
this.logSuccess(`Job ${jobId} removed`);
|
|
237
248
|
}
|
|
@@ -275,7 +286,8 @@ export class DaemonCommandRegistrar extends BaseCommandRegistrar {
|
|
|
275
286
|
{ flags: '-l, --limit <limit>', description: 'Limit number of results', defaultValue: '50' }
|
|
276
287
|
],
|
|
277
288
|
action: async (options) => {
|
|
278
|
-
const
|
|
289
|
+
const opts = options;
|
|
290
|
+
const jobs = await this.withDaemonAction(async (client) => await client.getJobHistory(opts.jobId, parseInt(opts.limit)), { forUser: true, requireRunning: false });
|
|
279
291
|
this.logInfo(`Job History (${jobs.length} records):`);
|
|
280
292
|
jobs.forEach(job => {
|
|
281
293
|
const started = new Date(job.started_at).toLocaleString();
|
|
@@ -297,7 +309,8 @@ export class DaemonCommandRegistrar extends BaseCommandRegistrar {
|
|
|
297
309
|
{ flags: '-j, --job-id <jobId>', description: 'Filter by job ID' }
|
|
298
310
|
],
|
|
299
311
|
action: async (options) => {
|
|
300
|
-
const
|
|
312
|
+
const opts = options;
|
|
313
|
+
const stats = await this.withDaemonAction(async (client) => await client.getJobStatistics(opts.jobId), { forUser: true, requireRunning: false });
|
|
301
314
|
this.logInfo('Job Statistics:');
|
|
302
315
|
this.logInfo(` Total Jobs: ${stats.totalJobs}`);
|
|
303
316
|
this.logInfo(` Success Rate: ${stats.successRate.toFixed(1)}%`);
|
|
@@ -316,7 +329,8 @@ export class DaemonCommandRegistrar extends BaseCommandRegistrar {
|
|
|
316
329
|
{ flags: '-l, --limit <limit>', description: 'Number of recent executions to show', defaultValue: '5' }
|
|
317
330
|
],
|
|
318
331
|
action: async (options) => {
|
|
319
|
-
const
|
|
332
|
+
const opts = options;
|
|
333
|
+
const jobs = await this.withDaemonAction(async (client) => await client.getJobHistory(undefined, parseInt(opts.limit)), { forUser: true });
|
|
320
334
|
this.logInfo(`Recent Job Executions (${jobs.length} records):`);
|
|
321
335
|
jobs.forEach((job, index) => {
|
|
322
336
|
const started = new Date(job.started_at).toLocaleString();
|
|
@@ -7,27 +7,26 @@ import * as fs from 'fs';
|
|
|
7
7
|
import * as path from 'path';
|
|
8
8
|
import * as readline from 'readline';
|
|
9
9
|
export async function init_secrets(program) {
|
|
10
|
-
const secretsCmd = program
|
|
11
|
-
.command('secrets')
|
|
12
|
-
.description('Manage environment secrets across machines');
|
|
13
10
|
// Push secrets to cloud
|
|
14
|
-
|
|
11
|
+
program
|
|
15
12
|
.command('push')
|
|
16
13
|
.description('Push local .env to encrypted cloud storage')
|
|
17
14
|
.option('-f, --file <path>', 'Path to .env file', '.env')
|
|
18
15
|
.option('-e, --env <name>', 'Environment name (dev/staging/prod)', 'dev')
|
|
16
|
+
.option('--force', 'Force push even if destructive changes detected')
|
|
19
17
|
.action(async (options) => {
|
|
20
18
|
try {
|
|
21
19
|
const manager = new SecretsManager();
|
|
22
|
-
await manager.push(options.file, options.env);
|
|
20
|
+
await manager.push(options.file, options.env, options.force);
|
|
23
21
|
}
|
|
24
22
|
catch (error) {
|
|
25
|
-
|
|
23
|
+
const err = error;
|
|
24
|
+
console.error('❌ Failed to push secrets:', err.message);
|
|
26
25
|
process.exit(1);
|
|
27
26
|
}
|
|
28
27
|
});
|
|
29
28
|
// Pull secrets from cloud
|
|
30
|
-
|
|
29
|
+
program
|
|
31
30
|
.command('pull')
|
|
32
31
|
.description('Pull .env from encrypted cloud storage')
|
|
33
32
|
.option('-f, --file <path>', 'Path to .env file', '.env')
|
|
@@ -39,12 +38,13 @@ export async function init_secrets(program) {
|
|
|
39
38
|
await manager.pull(options.file, options.env, options.force);
|
|
40
39
|
}
|
|
41
40
|
catch (error) {
|
|
42
|
-
|
|
41
|
+
const err = error;
|
|
42
|
+
console.error('❌ Failed to pull secrets:', err.message);
|
|
43
43
|
process.exit(1);
|
|
44
44
|
}
|
|
45
45
|
});
|
|
46
46
|
// List environments
|
|
47
|
-
|
|
47
|
+
program
|
|
48
48
|
.command('list [environment]')
|
|
49
49
|
.alias('ls')
|
|
50
50
|
.description('List all stored environments or show secrets for specific environment')
|
|
@@ -56,7 +56,7 @@ export async function init_secrets(program) {
|
|
|
56
56
|
if (options.allFiles) {
|
|
57
57
|
const files = await manager.listAllFiles();
|
|
58
58
|
if (files.length === 0) {
|
|
59
|
-
console.log('No .env files found. Push your first file with: lsh
|
|
59
|
+
console.log('No .env files found. Push your first file with: lsh push --file <filename>');
|
|
60
60
|
return;
|
|
61
61
|
}
|
|
62
62
|
console.log('\n📦 Tracked .env files:\n');
|
|
@@ -74,7 +74,7 @@ export async function init_secrets(program) {
|
|
|
74
74
|
// Otherwise, list all environments
|
|
75
75
|
const envs = await manager.listEnvironments();
|
|
76
76
|
if (envs.length === 0) {
|
|
77
|
-
console.log('No environments found. Push your first .env with: lsh
|
|
77
|
+
console.log('No environments found. Push your first .env with: lsh push');
|
|
78
78
|
return;
|
|
79
79
|
}
|
|
80
80
|
console.log('\n📦 Available environments:\n');
|
|
@@ -84,12 +84,13 @@ export async function init_secrets(program) {
|
|
|
84
84
|
console.log();
|
|
85
85
|
}
|
|
86
86
|
catch (error) {
|
|
87
|
-
|
|
87
|
+
const err = error;
|
|
88
|
+
console.error('❌ Failed to list environments:', err.message);
|
|
88
89
|
process.exit(1);
|
|
89
90
|
}
|
|
90
91
|
});
|
|
91
92
|
// Show secrets (masked)
|
|
92
|
-
|
|
93
|
+
program
|
|
93
94
|
.command('show')
|
|
94
95
|
.description('Show secrets for an environment (masked)')
|
|
95
96
|
.option('-e, --env <name>', 'Environment name', 'dev')
|
|
@@ -99,12 +100,13 @@ export async function init_secrets(program) {
|
|
|
99
100
|
await manager.show(options.env);
|
|
100
101
|
}
|
|
101
102
|
catch (error) {
|
|
102
|
-
|
|
103
|
+
const err = error;
|
|
104
|
+
console.error('❌ Failed to show secrets:', err.message);
|
|
103
105
|
process.exit(1);
|
|
104
106
|
}
|
|
105
107
|
});
|
|
106
108
|
// Generate encryption key
|
|
107
|
-
|
|
109
|
+
program
|
|
108
110
|
.command('key')
|
|
109
111
|
.description('Generate a new encryption key')
|
|
110
112
|
.action(async () => {
|
|
@@ -116,7 +118,7 @@ export async function init_secrets(program) {
|
|
|
116
118
|
console.log(' Never commit it to git!\n');
|
|
117
119
|
});
|
|
118
120
|
// Create .env file
|
|
119
|
-
|
|
121
|
+
program
|
|
120
122
|
.command('create')
|
|
121
123
|
.description('Create a new .env file')
|
|
122
124
|
.option('-f, --file <path>', 'Path to .env file', '.env')
|
|
@@ -165,12 +167,13 @@ API_KEY=
|
|
|
165
167
|
console.log('');
|
|
166
168
|
}
|
|
167
169
|
catch (error) {
|
|
168
|
-
|
|
170
|
+
const err = error;
|
|
171
|
+
console.error('❌ Failed to create .env file:', err.message);
|
|
169
172
|
process.exit(1);
|
|
170
173
|
}
|
|
171
174
|
});
|
|
172
175
|
// Sync command - automatically set up and synchronize secrets
|
|
173
|
-
|
|
176
|
+
program
|
|
174
177
|
.command('sync')
|
|
175
178
|
.description('Automatically set up and synchronize secrets (smart mode)')
|
|
176
179
|
.option('-f, --file <path>', 'Path to .env file', '.env')
|
|
@@ -178,6 +181,7 @@ API_KEY=
|
|
|
178
181
|
.option('--dry-run', 'Show what would be done without executing')
|
|
179
182
|
.option('--legacy', 'Use legacy sync mode (suggestions only)')
|
|
180
183
|
.option('--load', 'Output eval-able export commands for loading secrets')
|
|
184
|
+
.option('--force', 'Force sync even if destructive changes detected')
|
|
181
185
|
.action(async (options) => {
|
|
182
186
|
try {
|
|
183
187
|
const manager = new SecretsManager();
|
|
@@ -187,16 +191,17 @@ API_KEY=
|
|
|
187
191
|
}
|
|
188
192
|
else {
|
|
189
193
|
// Use new smart sync (auto-execute)
|
|
190
|
-
await manager.smartSync(options.file, options.env, !options.dryRun, options.load);
|
|
194
|
+
await manager.smartSync(options.file, options.env, !options.dryRun, options.load, options.force);
|
|
191
195
|
}
|
|
192
196
|
}
|
|
193
197
|
catch (error) {
|
|
194
|
-
|
|
198
|
+
const err = error;
|
|
199
|
+
console.error('❌ Failed to sync:', err.message);
|
|
195
200
|
process.exit(1);
|
|
196
201
|
}
|
|
197
202
|
});
|
|
198
203
|
// Status command - get detailed status info
|
|
199
|
-
|
|
204
|
+
program
|
|
200
205
|
.command('status')
|
|
201
206
|
.description('Get detailed secrets status (JSON output)')
|
|
202
207
|
.option('-f, --file <path>', 'Path to .env file', '.env')
|
|
@@ -208,12 +213,13 @@ API_KEY=
|
|
|
208
213
|
console.log(JSON.stringify(status, null, 2));
|
|
209
214
|
}
|
|
210
215
|
catch (error) {
|
|
211
|
-
|
|
216
|
+
const err = error;
|
|
217
|
+
console.error('❌ Failed to get status:', err.message);
|
|
212
218
|
process.exit(1);
|
|
213
219
|
}
|
|
214
220
|
});
|
|
215
221
|
// Get a specific secret value
|
|
216
|
-
|
|
222
|
+
program
|
|
217
223
|
.command('get <key>')
|
|
218
224
|
.description('Get a specific secret value from .env file')
|
|
219
225
|
.option('-f, --file <path>', 'Path to .env file', '.env')
|
|
@@ -245,12 +251,13 @@ API_KEY=
|
|
|
245
251
|
process.exit(1);
|
|
246
252
|
}
|
|
247
253
|
catch (error) {
|
|
248
|
-
|
|
254
|
+
const err = error;
|
|
255
|
+
console.error('❌ Failed to get secret:', err.message);
|
|
249
256
|
process.exit(1);
|
|
250
257
|
}
|
|
251
258
|
});
|
|
252
259
|
// Set a specific secret value
|
|
253
|
-
|
|
260
|
+
program
|
|
254
261
|
.command('set <key> <value>')
|
|
255
262
|
.description('Set a specific secret value in .env file')
|
|
256
263
|
.option('-f, --file <path>', 'Path to .env file', '.env')
|
|
@@ -297,12 +304,13 @@ API_KEY=
|
|
|
297
304
|
console.log(`✅ Set ${key} in ${options.file}`);
|
|
298
305
|
}
|
|
299
306
|
catch (error) {
|
|
300
|
-
|
|
307
|
+
const err = error;
|
|
308
|
+
console.error('❌ Failed to set secret:', err.message);
|
|
301
309
|
process.exit(1);
|
|
302
310
|
}
|
|
303
311
|
});
|
|
304
312
|
// Delete .env file with confirmation
|
|
305
|
-
|
|
313
|
+
program
|
|
306
314
|
.command('delete')
|
|
307
315
|
.description('Delete .env file (requires confirmation)')
|
|
308
316
|
.option('-f, --file <path>', 'Path to .env file', '.env')
|
|
@@ -351,7 +359,8 @@ API_KEY=
|
|
|
351
359
|
console.log('');
|
|
352
360
|
}
|
|
353
361
|
catch (error) {
|
|
354
|
-
|
|
362
|
+
const err = error;
|
|
363
|
+
console.error('❌ Failed to delete .env file:', err.message);
|
|
355
364
|
process.exit(1);
|
|
356
365
|
}
|
|
357
366
|
});
|
|
@@ -89,9 +89,10 @@ export class SupabaseCommandRegistrar extends BaseCommandRegistrar {
|
|
|
89
89
|
{ flags: '-s, --search <query>', description: 'Search history entries' }
|
|
90
90
|
],
|
|
91
91
|
action: async (options) => {
|
|
92
|
+
const opts = options;
|
|
92
93
|
const persistence = new DatabasePersistence();
|
|
93
|
-
if (
|
|
94
|
-
const count = parseInt(
|
|
94
|
+
if (opts.list) {
|
|
95
|
+
const count = parseInt(opts.count);
|
|
95
96
|
const entries = await persistence.getHistoryEntries(count);
|
|
96
97
|
this.logInfo(`Recent ${entries.length} history entries:`);
|
|
97
98
|
entries.forEach((entry, index) => {
|
|
@@ -100,9 +101,9 @@ export class SupabaseCommandRegistrar extends BaseCommandRegistrar {
|
|
|
100
101
|
this.logInfo(`${index + 1}. [${timestamp}] ${entry.command}${exitCode}`);
|
|
101
102
|
});
|
|
102
103
|
}
|
|
103
|
-
else if (
|
|
104
|
+
else if (opts.search) {
|
|
104
105
|
const entries = await persistence.getHistoryEntries(100);
|
|
105
|
-
const filtered = entries.filter(entry => entry.command.toLowerCase().includes(
|
|
106
|
+
const filtered = entries.filter(entry => entry.command.toLowerCase().includes(opts.search.toLowerCase()));
|
|
106
107
|
this.logInfo(`Found ${filtered.length} matching entries:`);
|
|
107
108
|
filtered.forEach((entry, index) => {
|
|
108
109
|
const timestamp = new Date(entry.timestamp).toLocaleString();
|
|
@@ -126,33 +127,34 @@ export class SupabaseCommandRegistrar extends BaseCommandRegistrar {
|
|
|
126
127
|
{ flags: '-e, --export', description: 'Export configuration to JSON', defaultValue: false }
|
|
127
128
|
],
|
|
128
129
|
action: async (options) => {
|
|
130
|
+
const opts = options;
|
|
129
131
|
const configManager = new CloudConfigManager();
|
|
130
|
-
if (
|
|
132
|
+
if (opts.list) {
|
|
131
133
|
const config = configManager.getAll();
|
|
132
134
|
this.logInfo('Current configuration:');
|
|
133
135
|
config.forEach(item => {
|
|
134
136
|
this.logInfo(` ${item.key}: ${JSON.stringify(item.value)}`);
|
|
135
137
|
});
|
|
136
138
|
}
|
|
137
|
-
else if (
|
|
138
|
-
const value = configManager.get(
|
|
139
|
+
else if (opts.get) {
|
|
140
|
+
const value = configManager.get(opts.get);
|
|
139
141
|
if (value !== undefined) {
|
|
140
|
-
this.logInfo(`${
|
|
142
|
+
this.logInfo(`${opts.get}: ${JSON.stringify(value)}`);
|
|
141
143
|
}
|
|
142
144
|
else {
|
|
143
|
-
this.logWarning(`Configuration key '${
|
|
145
|
+
this.logWarning(`Configuration key '${opts.get}' not found`);
|
|
144
146
|
}
|
|
145
147
|
}
|
|
146
|
-
else if (
|
|
147
|
-
const [key, value] =
|
|
148
|
+
else if (opts.set) {
|
|
149
|
+
const [key, value] = opts.set;
|
|
148
150
|
configManager.set(key, value);
|
|
149
151
|
this.logSuccess(`Configuration '${key}' set to: ${value}`);
|
|
150
152
|
}
|
|
151
|
-
else if (
|
|
152
|
-
configManager.delete(
|
|
153
|
-
this.logSuccess(`Configuration '${
|
|
153
|
+
else if (opts.delete) {
|
|
154
|
+
configManager.delete(opts.delete);
|
|
155
|
+
this.logSuccess(`Configuration '${opts.delete}' deleted`);
|
|
154
156
|
}
|
|
155
|
-
else if (
|
|
157
|
+
else if (opts.export) {
|
|
156
158
|
const exported = configManager.export();
|
|
157
159
|
this.logInfo(exported);
|
|
158
160
|
}
|
|
@@ -170,8 +172,9 @@ export class SupabaseCommandRegistrar extends BaseCommandRegistrar {
|
|
|
170
172
|
{ flags: '-h, --history', description: 'List job history', defaultValue: false }
|
|
171
173
|
],
|
|
172
174
|
action: async (options) => {
|
|
175
|
+
const opts = options;
|
|
173
176
|
const persistence = new DatabasePersistence();
|
|
174
|
-
if (
|
|
177
|
+
if (opts.list) {
|
|
175
178
|
const jobs = await persistence.getActiveJobs();
|
|
176
179
|
this.logInfo(`Active jobs (${jobs.length}):`);
|
|
177
180
|
jobs.forEach(job => {
|
|
@@ -179,7 +182,7 @@ export class SupabaseCommandRegistrar extends BaseCommandRegistrar {
|
|
|
179
182
|
this.logInfo(`${job.job_id}: ${job.command} (${job.status}) - Started: ${started}`);
|
|
180
183
|
});
|
|
181
184
|
}
|
|
182
|
-
else if (
|
|
185
|
+
else if (opts.history) {
|
|
183
186
|
this.logInfo('Job history feature not yet implemented');
|
|
184
187
|
}
|
|
185
188
|
else {
|
|
@@ -196,17 +199,18 @@ export class SupabaseCommandRegistrar extends BaseCommandRegistrar {
|
|
|
196
199
|
{ flags: '-t, --table <name>', description: 'Show rows from specific table only' }
|
|
197
200
|
],
|
|
198
201
|
action: async (options) => {
|
|
202
|
+
const opts = options;
|
|
199
203
|
const persistence = new DatabasePersistence();
|
|
200
|
-
const limit = parseInt(
|
|
204
|
+
const limit = parseInt(opts.limit);
|
|
201
205
|
// Test connection first
|
|
202
206
|
const isConnected = await persistence.testConnection();
|
|
203
207
|
if (!isConnected) {
|
|
204
208
|
throw new Error('Cannot fetch rows - database not available');
|
|
205
209
|
}
|
|
206
|
-
if (
|
|
210
|
+
if (opts.table) {
|
|
207
211
|
// Show rows from specific table
|
|
208
|
-
this.logInfo(`Latest ${limit} entries from table '${
|
|
209
|
-
const rows = await persistence.getLatestRowsFromTable(
|
|
212
|
+
this.logInfo(`Latest ${limit} entries from table '${opts.table}':`);
|
|
213
|
+
const rows = await persistence.getLatestRowsFromTable(opts.table, limit);
|
|
210
214
|
if (rows.length === 0) {
|
|
211
215
|
this.logInfo('No entries found.');
|
|
212
216
|
}
|
|
@@ -252,13 +256,14 @@ export class SupabaseCommandRegistrar extends BaseCommandRegistrar {
|
|
|
252
256
|
{ flags: '--dataset <name>', description: 'Dataset name for new job' }
|
|
253
257
|
],
|
|
254
258
|
action: async (options) => {
|
|
255
|
-
|
|
259
|
+
const opts = options;
|
|
260
|
+
if (opts.list) {
|
|
256
261
|
let query = supabaseClient.getClient()
|
|
257
262
|
.from('ml_training_jobs')
|
|
258
263
|
.select('*')
|
|
259
264
|
.order('created_at', { ascending: false });
|
|
260
|
-
if (
|
|
261
|
-
query = query.eq('status',
|
|
265
|
+
if (opts.status) {
|
|
266
|
+
query = query.eq('status', opts.status);
|
|
262
267
|
}
|
|
263
268
|
const { data: jobs, error } = await query.limit(20);
|
|
264
269
|
if (error) {
|
|
@@ -273,16 +278,16 @@ export class SupabaseCommandRegistrar extends BaseCommandRegistrar {
|
|
|
273
278
|
this.logInfo(` Dataset: ${job.dataset_name}`);
|
|
274
279
|
});
|
|
275
280
|
}
|
|
276
|
-
else if (
|
|
277
|
-
if (!
|
|
281
|
+
else if (opts.create) {
|
|
282
|
+
if (!opts.modelType || !opts.dataset) {
|
|
278
283
|
throw new Error('Both --model-type and --dataset are required to create a job');
|
|
279
284
|
}
|
|
280
285
|
const { data, error } = await supabaseClient.getClient()
|
|
281
286
|
.from('ml_training_jobs')
|
|
282
287
|
.insert({
|
|
283
|
-
job_name:
|
|
284
|
-
model_type:
|
|
285
|
-
dataset_name:
|
|
288
|
+
job_name: opts.create,
|
|
289
|
+
model_type: opts.modelType,
|
|
290
|
+
dataset_name: opts.dataset,
|
|
286
291
|
status: 'pending',
|
|
287
292
|
created_at: new Date().toISOString()
|
|
288
293
|
})
|
|
@@ -290,7 +295,7 @@ export class SupabaseCommandRegistrar extends BaseCommandRegistrar {
|
|
|
290
295
|
if (error) {
|
|
291
296
|
throw new Error(`Failed to create training job: ${error.message}`);
|
|
292
297
|
}
|
|
293
|
-
this.logSuccess(`Created training job: ${
|
|
298
|
+
this.logSuccess(`Created training job: ${opts.create}`);
|
|
294
299
|
this.logInfo(JSON.stringify(data, null, 2));
|
|
295
300
|
}
|
|
296
301
|
else {
|
|
@@ -307,12 +312,13 @@ export class SupabaseCommandRegistrar extends BaseCommandRegistrar {
|
|
|
307
312
|
{ flags: '--deployed', description: 'Filter by deployed models only', defaultValue: false }
|
|
308
313
|
],
|
|
309
314
|
action: async (options) => {
|
|
310
|
-
|
|
315
|
+
const opts = options;
|
|
316
|
+
if (opts.list) {
|
|
311
317
|
let query = supabaseClient.getClient()
|
|
312
318
|
.from('ml_models')
|
|
313
319
|
.select('*')
|
|
314
320
|
.order('created_at', { ascending: false });
|
|
315
|
-
if (
|
|
321
|
+
if (opts.deployed) {
|
|
316
322
|
query = query.eq('deployed', true);
|
|
317
323
|
}
|
|
318
324
|
const { data: models, error } = await query.limit(20);
|
|
@@ -342,7 +348,8 @@ export class SupabaseCommandRegistrar extends BaseCommandRegistrar {
|
|
|
342
348
|
{ flags: '-l, --list', description: 'List feature definitions', defaultValue: false }
|
|
343
349
|
],
|
|
344
350
|
action: async (options) => {
|
|
345
|
-
|
|
351
|
+
const opts = options;
|
|
352
|
+
if (opts.list) {
|
|
346
353
|
const { data: features, error } = await supabaseClient.getClient()
|
|
347
354
|
.from('ml_features')
|
|
348
355
|
.select('*')
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "lsh-framework",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.9.1",
|
|
4
4
|
"description": "Encrypted secrets manager with automatic rotation, team sync, and multi-environment support. Built on a powerful shell with daemon scheduling and CI/CD integration.",
|
|
5
5
|
"main": "dist/app.js",
|
|
6
6
|
"bin": {
|