lua-cli 3.2.0 → 3.4.0

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 (121) hide show
  1. package/dist/api/logs.api.service.d.ts +1 -1
  2. package/dist/api/logs.api.service.js.map +1 -1
  3. package/dist/api/products.api.service.d.ts +17 -5
  4. package/dist/api/products.api.service.js +21 -9
  5. package/dist/api/products.api.service.js.map +1 -1
  6. package/dist/api/webhook.api.service.d.ts +4 -0
  7. package/dist/api/webhook.api.service.js.map +1 -1
  8. package/dist/api-exports.d.ts +19 -7
  9. package/dist/api-exports.js +20 -5
  10. package/dist/api-exports.js.map +1 -1
  11. package/dist/cli/command-definitions.js +323 -88
  12. package/dist/cli/command-definitions.js.map +1 -1
  13. package/dist/commands/apiKey.d.ts +5 -2
  14. package/dist/commands/apiKey.js +8 -2
  15. package/dist/commands/apiKey.js.map +1 -1
  16. package/dist/commands/channels.d.ts +4 -9
  17. package/dist/commands/channels.js +140 -84
  18. package/dist/commands/channels.js.map +1 -1
  19. package/dist/commands/chat.d.ts +4 -2
  20. package/dist/commands/chat.js +126 -32
  21. package/dist/commands/chat.js.map +1 -1
  22. package/dist/commands/chatClear.d.ts +3 -2
  23. package/dist/commands/chatClear.js +16 -15
  24. package/dist/commands/chatClear.js.map +1 -1
  25. package/dist/commands/compile.d.ts +5 -4
  26. package/dist/commands/compile.js +73 -9
  27. package/dist/commands/compile.js.map +1 -1
  28. package/dist/commands/deploy.d.ts +5 -24
  29. package/dist/commands/deploy.js +75 -48
  30. package/dist/commands/deploy.js.map +1 -1
  31. package/dist/commands/destroy.d.ts +5 -2
  32. package/dist/commands/destroy.js +14 -2
  33. package/dist/commands/destroy.js.map +1 -1
  34. package/dist/commands/env.d.ts +3 -1
  35. package/dist/commands/env.js +322 -122
  36. package/dist/commands/env.js.map +1 -1
  37. package/dist/commands/features.d.ts +5 -9
  38. package/dist/commands/features.js +249 -129
  39. package/dist/commands/features.js.map +1 -1
  40. package/dist/commands/init.d.ts +7 -1
  41. package/dist/commands/init.js +242 -59
  42. package/dist/commands/init.js.map +1 -1
  43. package/dist/commands/jobs.d.ts +5 -13
  44. package/dist/commands/jobs.js +523 -360
  45. package/dist/commands/jobs.js.map +1 -1
  46. package/dist/commands/logs.d.ts +5 -10
  47. package/dist/commands/logs.js +259 -103
  48. package/dist/commands/logs.js.map +1 -1
  49. package/dist/commands/marketplace.d.ts +23 -2
  50. package/dist/commands/marketplace.js +530 -7
  51. package/dist/commands/marketplace.js.map +1 -1
  52. package/dist/commands/mcp.d.ts +5 -11
  53. package/dist/commands/mcp.js +304 -294
  54. package/dist/commands/mcp.js.map +1 -1
  55. package/dist/commands/persona.d.ts +5 -9
  56. package/dist/commands/persona.js +349 -232
  57. package/dist/commands/persona.js.map +1 -1
  58. package/dist/commands/postprocessors.d.ts +6 -2
  59. package/dist/commands/postprocessors.js +387 -280
  60. package/dist/commands/postprocessors.js.map +1 -1
  61. package/dist/commands/preprocessors.d.ts +6 -2
  62. package/dist/commands/preprocessors.js +387 -280
  63. package/dist/commands/preprocessors.js.map +1 -1
  64. package/dist/commands/production.d.ts +5 -8
  65. package/dist/commands/production.js +317 -228
  66. package/dist/commands/production.js.map +1 -1
  67. package/dist/commands/push.js +385 -427
  68. package/dist/commands/push.js.map +1 -1
  69. package/dist/commands/resources.d.ts +5 -10
  70. package/dist/commands/resources.js +219 -154
  71. package/dist/commands/resources.js.map +1 -1
  72. package/dist/commands/skills.d.ts +5 -9
  73. package/dist/commands/skills.js +435 -275
  74. package/dist/commands/skills.js.map +1 -1
  75. package/dist/commands/sync.d.ts +10 -8
  76. package/dist/commands/sync.js +110 -19
  77. package/dist/commands/sync.js.map +1 -1
  78. package/dist/commands/test.d.ts +1 -11
  79. package/dist/commands/test.js +395 -438
  80. package/dist/commands/test.js.map +1 -1
  81. package/dist/commands/webhooks.d.ts +5 -11
  82. package/dist/commands/webhooks.js +431 -287
  83. package/dist/commands/webhooks.js.map +1 -1
  84. package/dist/interfaces/index.d.ts +1 -1
  85. package/dist/interfaces/mcp.d.ts +39 -19
  86. package/dist/interfaces/mcp.js +3 -0
  87. package/dist/interfaces/mcp.js.map +1 -1
  88. package/dist/interfaces/product.d.ts +26 -0
  89. package/dist/interfaces/skills.d.ts +5 -0
  90. package/dist/types/api-contracts.d.ts +8 -4
  91. package/dist/types/index.d.ts +1 -1
  92. package/dist/types/index.js.map +1 -1
  93. package/dist/types/skill.d.ts +146 -35
  94. package/dist/types/skill.js +31 -37
  95. package/dist/types/skill.js.map +1 -1
  96. package/dist/utils/bundling.d.ts +17 -0
  97. package/dist/utils/bundling.js +96 -0
  98. package/dist/utils/bundling.js.map +1 -1
  99. package/dist/utils/compile.d.ts +4 -0
  100. package/dist/utils/compile.js +5 -0
  101. package/dist/utils/compile.js.map +1 -1
  102. package/dist/utils/dev-helpers.d.ts +3 -2
  103. package/dist/utils/dev-helpers.js +3 -5
  104. package/dist/utils/dev-helpers.js.map +1 -1
  105. package/dist/utils/job-management.d.ts +4 -1
  106. package/dist/utils/job-management.js +15 -29
  107. package/dist/utils/job-management.js.map +1 -1
  108. package/dist/utils/mcp-server-management.d.ts +5 -2
  109. package/dist/utils/mcp-server-management.js +27 -43
  110. package/dist/utils/mcp-server-management.js.map +1 -1
  111. package/dist/utils/push-helpers.d.ts +1 -1
  112. package/dist/utils/push-helpers.js +5 -1
  113. package/dist/utils/push-helpers.js.map +1 -1
  114. package/dist/utils/skill-management.d.ts +7 -2
  115. package/dist/utils/skill-management.js +21 -30
  116. package/dist/utils/skill-management.js.map +1 -1
  117. package/dist/utils/webhook-management.d.ts +4 -1
  118. package/dist/utils/webhook-management.js +15 -29
  119. package/dist/utils/webhook-management.js.map +1 -1
  120. package/package.json +1 -1
  121. package/template/package.json +1 -1
@@ -9,23 +9,22 @@ import { BASE_URLS } from '../config/constants.js';
9
9
  import { safePrompt } from '../utils/prompt-handler.js';
10
10
  import { validateConfig, validateAgentConfig, } from '../utils/dev-helpers.js';
11
11
  import JobApi from '../api/job.api.service.js';
12
+ // ─────────────────────────────────────────────────────────────────────────────
13
+ // Main Command Entry
14
+ // ─────────────────────────────────────────────────────────────────────────────
12
15
  /**
13
- * Main jobs command - manages agent jobs
16
+ * Main jobs command - manages agent jobs.
14
17
  *
15
- * Features:
16
- * - View deployed jobs
17
- * - View job versions
18
- * - Deploy job versions to production
19
- * - Activate/deactivate, pause/resume jobs
20
- * - Trigger jobs manually
21
- * - View execution history
22
- *
23
- * Note: For local testing, use `lua test job`
24
- *
25
- * @returns Promise that resolves when command completes
18
+ * Supports both interactive and non-interactive modes:
19
+ * - Interactive: prompts for action and job selection
20
+ * - Non-interactive: use action argument with -i and -v flags
26
21
  */
27
- export async function jobsCommand() {
22
+ export async function jobsCommand(action, cmdObj) {
28
23
  return withErrorHandling(async () => {
24
+ const options = {
25
+ jobName: cmdObj?.jobName || null,
26
+ jobVersion: cmdObj?.jobVersion || null,
27
+ };
29
28
  // Step 1: Load configuration
30
29
  const config = readSkillConfig();
31
30
  validateConfig(config);
@@ -44,13 +43,411 @@ export async function jobsCommand() {
44
43
  agentId,
45
44
  apiKey,
46
45
  };
47
- // Start job management
48
- await manageProductionJobs(context, config);
46
+ // Determine if non-interactive mode
47
+ if (action) {
48
+ await executeNonInteractive(context, config, action, options);
49
+ }
50
+ else {
51
+ await manageProductionJobs(context, config);
52
+ }
49
53
  }, "jobs");
50
54
  }
55
+ // ─────────────────────────────────────────────────────────────────────────────
56
+ // Shared Core Functions (used by both interactive and non-interactive modes)
57
+ // ─────────────────────────────────────────────────────────────────────────────
51
58
  /**
52
- * Manage production jobs - view, deploy, pause/resume, trigger
59
+ * Fetches and displays all jobs with their current status.
53
60
  */
61
+ async function displayJobsCore(context, jobs) {
62
+ console.log("\n" + "=".repeat(60));
63
+ console.log("⚙️ Production Jobs");
64
+ console.log("=".repeat(60) + "\n");
65
+ for (const job of jobs) {
66
+ try {
67
+ const response = await fetch(`${BASE_URLS.API}/developer/jobs/${context.agentId}/${job.jobId}`, {
68
+ method: 'GET',
69
+ headers: {
70
+ 'Authorization': `Bearer ${context.apiKey}`,
71
+ 'Content-Type': 'application/json'
72
+ }
73
+ });
74
+ if (response.ok) {
75
+ const data = await response.json();
76
+ const jobData = data.data || data;
77
+ console.log(`⏰ ${job.name}`);
78
+ console.log(` Job ID: ${job.jobId}`);
79
+ console.log(` Status: ${jobData.status || 'active'}`);
80
+ if (jobData.schedule) {
81
+ console.log(` Schedule: ${formatSchedule(jobData.schedule)}`);
82
+ }
83
+ if (jobData.lastRunAt) {
84
+ console.log(` Last Run: ${new Date(jobData.lastRunAt).toLocaleString()}`);
85
+ }
86
+ if (jobData.nextRunAt) {
87
+ console.log(` Next Run: ${new Date(jobData.nextRunAt).toLocaleString()}`);
88
+ }
89
+ console.log();
90
+ }
91
+ else {
92
+ displayJobError(job, "Unable to fetch info");
93
+ }
94
+ }
95
+ catch {
96
+ displayJobError(job, "Error loading info");
97
+ }
98
+ }
99
+ console.log("=".repeat(60));
100
+ }
101
+ function displayJobError(job, status) {
102
+ console.log(`⏰ ${job.name}`);
103
+ console.log(` Job ID: ${job.jobId}`);
104
+ console.log(` Status: ${status}`);
105
+ console.log();
106
+ }
107
+ /**
108
+ * Fetches and returns sorted job versions.
109
+ */
110
+ async function fetchVersionsCore(context, job) {
111
+ const jobApi = new JobApi(BASE_URLS.API, context.apiKey, context.agentId);
112
+ const response = await jobApi.getJobVersions(job.jobId);
113
+ if (!response.success || !response.data) {
114
+ console.error(`❌ Failed to fetch versions: ${response.error?.message || 'Unknown error'}`);
115
+ return null;
116
+ }
117
+ return response.data.sort((a, b) => new Date(b.createdAt).getTime() - new Date(a.createdAt).getTime());
118
+ }
119
+ /**
120
+ * Displays versions for a job.
121
+ */
122
+ function displayVersionsCore(job, versions) {
123
+ console.log("\n" + "=".repeat(60));
124
+ console.log(`📜 Versions for ${job.name}`);
125
+ console.log("=".repeat(60) + "\n");
126
+ versions.forEach((version) => {
127
+ const isActive = version.jobId === job.activeVersionId;
128
+ console.log(`${isActive ? '⭐' : ' '} Version ${version.version}`);
129
+ console.log(` Created: ${new Date(version.createdAt).toLocaleString()}`);
130
+ if (version.schedule) {
131
+ console.log(` Schedule: ${formatSchedule(version.schedule)}`);
132
+ }
133
+ console.log();
134
+ });
135
+ console.log("=".repeat(60));
136
+ }
137
+ /**
138
+ * Deploys a specific version of a job.
139
+ */
140
+ async function deployVersionCore(context, job, version) {
141
+ writeProgress(`🔄 Deploying version ${version} of "${job.name}"...`);
142
+ const jobApi = new JobApi(BASE_URLS.API, context.apiKey, context.agentId);
143
+ const deployResponse = await jobApi.publishJobVersion(job.jobId, version);
144
+ if (!deployResponse.success) {
145
+ console.error(`❌ Deploy Error: ${deployResponse.error?.message || 'Unknown error'}`);
146
+ return false;
147
+ }
148
+ writeSuccess(`✅ Version ${version} of "${job.name}" deployed successfully`);
149
+ writeInfo("💡 The new version is now active and will run on schedule.");
150
+ return true;
151
+ }
152
+ /**
153
+ * Activates a job.
154
+ */
155
+ async function activateJobCore(context, job) {
156
+ writeProgress(`🔄 Activating job "${job.name}"...`);
157
+ const jobApi = new JobApi(BASE_URLS.API, context.apiKey, context.agentId);
158
+ const response = await jobApi.activateJob(job.jobId);
159
+ if (response.success) {
160
+ writeSuccess(`✅ Job "${job.name}" activated successfully`);
161
+ writeInfo("💡 The job is now enabled and will run on schedule.");
162
+ return true;
163
+ }
164
+ else {
165
+ console.error(`❌ Failed to activate job: ${response.error?.message || 'Unknown error'}`);
166
+ return false;
167
+ }
168
+ }
169
+ /**
170
+ * Deactivates a job.
171
+ */
172
+ async function deactivateJobCore(context, job) {
173
+ writeProgress(`🔄 Deactivating job "${job.name}"...`);
174
+ const jobApi = new JobApi(BASE_URLS.API, context.apiKey, context.agentId);
175
+ const response = await jobApi.deactivateJob(job.jobId);
176
+ if (response.success) {
177
+ writeSuccess(`✅ Job "${job.name}" deactivated successfully`);
178
+ writeInfo("💡 The job is now disabled and will not run.");
179
+ return true;
180
+ }
181
+ else {
182
+ console.error(`❌ Failed to deactivate job: ${response.error?.message || 'Unknown error'}`);
183
+ return false;
184
+ }
185
+ }
186
+ /**
187
+ * Deletes a job from the server.
188
+ * If the job has versions, it will be deactivated instead of deleted.
189
+ */
190
+ async function deleteJobCore(context, job) {
191
+ writeProgress(`🗑️ Deleting job "${job.name}"...`);
192
+ const jobApi = new JobApi(BASE_URLS.API, context.apiKey, context.agentId);
193
+ const response = await jobApi.deleteJob(job.jobId);
194
+ if (!response.success || !response.data) {
195
+ console.error(`❌ Delete Error: ${response.error?.message || 'Unknown error'}`);
196
+ return false;
197
+ }
198
+ if (response.data.deleted) {
199
+ writeSuccess(`✅ Job "${job.name}" deleted successfully`);
200
+ writeInfo("💡 Remove this job from your lua.skill.yaml to keep it in sync.");
201
+ }
202
+ else if (response.data.deactivated) {
203
+ writeSuccess(`⚠️ Job "${job.name}" has versions and was deactivated instead of deleted`);
204
+ writeInfo("💡 The job is now inactive and won't run on schedule.");
205
+ }
206
+ return true;
207
+ }
208
+ /**
209
+ * Triggers a job execution.
210
+ */
211
+ async function triggerJobCore(context, job) {
212
+ writeProgress(`⚡ Triggering job "${job.name}"...`);
213
+ const jobApi = new JobApi(BASE_URLS.API, context.apiKey, context.agentId);
214
+ const response = await jobApi.triggerJob(job.jobId);
215
+ if (response.success && response.data) {
216
+ writeSuccess(`✅ Job "${job.name}" triggered successfully`);
217
+ writeInfo(`📋 Execution ID: ${response.data.id || 'N/A'}`);
218
+ return true;
219
+ }
220
+ else {
221
+ console.error(`❌ Failed to trigger job: ${response.error?.message || 'Unknown error'}`);
222
+ return false;
223
+ }
224
+ }
225
+ /**
226
+ * Fetches and displays execution history for a job.
227
+ */
228
+ async function fetchAndDisplayHistoryCore(context, job) {
229
+ const jobApi = new JobApi(BASE_URLS.API, context.apiKey, context.agentId);
230
+ const response = await jobApi.getJobExecutions(job.jobId, 20);
231
+ if (!response.success || !response.data) {
232
+ console.error(`❌ Failed to fetch history: ${response.error?.message || 'Unknown error'}`);
233
+ return false;
234
+ }
235
+ const executions = response.data.executions || [];
236
+ if (executions.length === 0) {
237
+ console.log(`ℹ️ No execution history found for ${job.name}.`);
238
+ return true;
239
+ }
240
+ console.log("\n" + "=".repeat(60));
241
+ console.log(`📊 Execution History for ${job.name}`);
242
+ console.log("=".repeat(60) + "\n");
243
+ executions.forEach((execution, index) => {
244
+ const startedAt = new Date(execution.startedAt);
245
+ const status = execution.status;
246
+ const statusEmoji = status === 'completed' ? '✅' :
247
+ status === 'failed' ? '❌' :
248
+ status === 'running' ? '🔄' : '⏳';
249
+ console.log(`${index + 1}. ${statusEmoji} ${status.toUpperCase()}`);
250
+ console.log(` Execution ID: ${execution.executionId}`);
251
+ console.log(` Started: ${startedAt.toLocaleString()}`);
252
+ if (execution.completedAt) {
253
+ const completedAt = new Date(execution.completedAt);
254
+ const duration = completedAt.getTime() - startedAt.getTime();
255
+ console.log(` Completed: ${completedAt.toLocaleString()}`);
256
+ console.log(` Duration: ${Math.round(duration / 1000)}s`);
257
+ }
258
+ if (execution.result) {
259
+ console.log(` Result: ${JSON.stringify(execution.result).substring(0, 100)}...`);
260
+ }
261
+ if (execution.error) {
262
+ console.log(` Error: ${execution.error}`);
263
+ }
264
+ console.log();
265
+ });
266
+ console.log("=".repeat(60));
267
+ console.log(`Showing last ${executions.length} executions`);
268
+ return true;
269
+ }
270
+ /**
271
+ * Resolves a version string to an actual version, supporting "latest".
272
+ */
273
+ function resolveVersion(versions, versionArg) {
274
+ if (versionArg.toLowerCase() === 'latest') {
275
+ return versions[0].version;
276
+ }
277
+ const exists = versions.some((v) => v.version === versionArg);
278
+ if (!exists) {
279
+ console.error(`❌ Version "${versionArg}" not found`);
280
+ console.log('\nAvailable versions:');
281
+ versions.slice(0, 5).forEach((v) => console.log(` - ${v.version}`));
282
+ return null;
283
+ }
284
+ return versionArg;
285
+ }
286
+ /**
287
+ * Prompts user to select a job from a list.
288
+ */
289
+ async function promptJobSelection(jobs, message) {
290
+ const jobAnswer = await safePrompt([
291
+ {
292
+ type: 'list',
293
+ name: 'selectedJob',
294
+ message,
295
+ choices: jobs.map((job) => ({
296
+ name: `${job.name} (${job.jobId})`,
297
+ value: job
298
+ }))
299
+ }
300
+ ]);
301
+ return jobAnswer?.selectedJob || null;
302
+ }
303
+ /**
304
+ * Prompts user to select a version from a list.
305
+ */
306
+ async function promptVersionSelection(versions, activeVersionId) {
307
+ const versionAnswer = await safePrompt([
308
+ {
309
+ type: 'list',
310
+ name: 'selectedVersion',
311
+ message: 'Select a version to deploy:',
312
+ choices: versions.map((version) => {
313
+ const isActive = version.jobId === activeVersionId;
314
+ return {
315
+ name: `Version ${version.version} (${new Date(version.createdAt).toLocaleDateString()})${isActive ? ' ⭐ CURRENT' : ''}`,
316
+ value: version
317
+ };
318
+ })
319
+ }
320
+ ]);
321
+ return versionAnswer?.selectedVersion || null;
322
+ }
323
+ // ─────────────────────────────────────────────────────────────────────────────
324
+ // Non-Interactive Mode
325
+ // ─────────────────────────────────────────────────────────────────────────────
326
+ async function executeNonInteractive(context, config, action, options) {
327
+ const validActions = ['view', 'versions', 'deploy', 'activate', 'deactivate', 'trigger', 'history', 'delete'];
328
+ const normalizedAction = action.toLowerCase();
329
+ if (!validActions.includes(normalizedAction)) {
330
+ console.error(`❌ Invalid action: "${action}"`);
331
+ console.log('\nValid actions: view, versions, deploy, activate, deactivate, trigger, history, delete');
332
+ console.log('\nExamples:');
333
+ console.log(' lua jobs view List all jobs');
334
+ console.log(' lua jobs trigger -i healthCheck Trigger a job');
335
+ console.log(' lua jobs deploy -i myJob -v 1.0.3 Deploy specific version');
336
+ console.log(' lua jobs delete -i myJob Delete a job');
337
+ process.exit(1);
338
+ }
339
+ const jobs = config.jobs || [];
340
+ // View action doesn't require job selection
341
+ if (normalizedAction === 'view') {
342
+ if (jobs.length === 0) {
343
+ console.log("ℹ️ No jobs found in configuration.");
344
+ return;
345
+ }
346
+ await displayJobsCore(context, jobs);
347
+ return;
348
+ }
349
+ // All other actions require job selection
350
+ if (!options.jobName) {
351
+ console.error(`❌ --job-name is required for action "${normalizedAction}"`);
352
+ console.log(`\nUsage: lua jobs ${normalizedAction} --job-name <name>`);
353
+ process.exit(1);
354
+ }
355
+ let selectedJob = jobs.find((j) => j.jobId === options.jobName || j.name === options.jobName);
356
+ // For delete action, also check server jobs (for orphaned jobs not in local YAML)
357
+ if (!selectedJob && normalizedAction === 'delete') {
358
+ const jobApi = new JobApi(BASE_URLS.API, context.apiKey, context.agentId);
359
+ const serverResponse = await jobApi.getJobs();
360
+ if (serverResponse.success && serverResponse.data?.jobs) {
361
+ const serverJob = serverResponse.data.jobs.find((j) => j.id === options.jobName || j.name === options.jobName);
362
+ if (serverJob) {
363
+ // Map server job format to local format for deleteJobCore
364
+ selectedJob = {
365
+ name: serverJob.name,
366
+ jobId: serverJob.id,
367
+ };
368
+ }
369
+ }
370
+ }
371
+ if (!selectedJob) {
372
+ console.error(`❌ Job "${options.jobName}" not found`);
373
+ console.log('\nAvailable jobs in local config:');
374
+ jobs.forEach((j) => console.log(` - ${j.name} (${j.jobId})`));
375
+ if (normalizedAction === 'delete') {
376
+ console.log('\n💡 Tip: The job may have already been deleted from the server.');
377
+ }
378
+ process.exit(1);
379
+ }
380
+ switch (normalizedAction) {
381
+ case 'versions': {
382
+ const versions = await fetchVersionsCore(context, selectedJob);
383
+ if (!versions)
384
+ process.exit(1);
385
+ if (versions.length === 0) {
386
+ console.log(`ℹ️ No versions found for ${selectedJob.name}.`);
387
+ console.log("💡 Push a version first using 'lua push job'.");
388
+ return;
389
+ }
390
+ displayVersionsCore(selectedJob, versions);
391
+ break;
392
+ }
393
+ case 'deploy': {
394
+ if (!options.jobVersion) {
395
+ console.error('❌ --job-version is required for deploy action');
396
+ console.log('\nUsage: lua jobs deploy --job-name myJob --job-version 1.0.3');
397
+ console.log(' lua jobs deploy -i myJob -v latest');
398
+ process.exit(1);
399
+ }
400
+ const versions = await fetchVersionsCore(context, selectedJob);
401
+ if (!versions)
402
+ process.exit(1);
403
+ if (versions.length === 0) {
404
+ console.error(`❌ No versions found for ${selectedJob.name}.`);
405
+ console.log("💡 Push a version first using 'lua push job'.");
406
+ process.exit(1);
407
+ }
408
+ const resolvedVersion = resolveVersion(versions, options.jobVersion);
409
+ if (!resolvedVersion)
410
+ process.exit(1);
411
+ const success = await deployVersionCore(context, selectedJob, resolvedVersion);
412
+ if (!success)
413
+ process.exit(1);
414
+ break;
415
+ }
416
+ case 'activate': {
417
+ const success = await activateJobCore(context, selectedJob);
418
+ if (!success)
419
+ process.exit(1);
420
+ break;
421
+ }
422
+ case 'deactivate': {
423
+ const success = await deactivateJobCore(context, selectedJob);
424
+ if (!success)
425
+ process.exit(1);
426
+ break;
427
+ }
428
+ case 'trigger': {
429
+ const success = await triggerJobCore(context, selectedJob);
430
+ if (!success)
431
+ process.exit(1);
432
+ break;
433
+ }
434
+ case 'history': {
435
+ const success = await fetchAndDisplayHistoryCore(context, selectedJob);
436
+ if (!success)
437
+ process.exit(1);
438
+ break;
439
+ }
440
+ case 'delete': {
441
+ const success = await deleteJobCore(context, selectedJob);
442
+ if (!success)
443
+ process.exit(1);
444
+ break;
445
+ }
446
+ }
447
+ }
448
+ // ─────────────────────────────────────────────────────────────────────────────
449
+ // Interactive Mode
450
+ // ─────────────────────────────────────────────────────────────────────────────
54
451
  async function manageProductionJobs(context, config) {
55
452
  let continueManaging = true;
56
453
  while (continueManaging) {
@@ -70,6 +467,7 @@ async function manageProductionJobs(context, config) {
70
467
  { name: '🚫 Deactivate a job', value: 'deactivate' },
71
468
  { name: '⚡ Trigger job now', value: 'trigger' },
72
469
  { name: '📊 View execution history', value: 'history' },
470
+ { name: '🗑️ Delete a job', value: 'delete' },
73
471
  { name: '❌ Exit', value: 'exit' }
74
472
  ]
75
473
  }
@@ -79,25 +477,28 @@ async function manageProductionJobs(context, config) {
79
477
  const { action } = actionAnswer;
80
478
  switch (action) {
81
479
  case 'view':
82
- await viewDeployedJobs(context, config);
480
+ await viewDeployedJobsInteractive(context, config);
83
481
  break;
84
482
  case 'versions':
85
- await viewJobVersions(context, config);
483
+ await viewJobVersionsInteractive(context, config);
86
484
  break;
87
485
  case 'deploy':
88
- await deployJobVersion(context, config);
486
+ await deployJobVersionInteractive(context, config);
89
487
  break;
90
488
  case 'activate':
91
- await activateJob(context, config);
489
+ await activateJobInteractive(context, config);
92
490
  break;
93
491
  case 'deactivate':
94
- await deactivateJob(context, config);
492
+ await deactivateJobInteractive(context, config);
95
493
  break;
96
494
  case 'trigger':
97
- await triggerJob(context, config);
495
+ await triggerJobInteractive(context, config);
98
496
  break;
99
497
  case 'history':
100
- await viewExecutionHistory(context, config);
498
+ await viewExecutionHistoryInteractive(context, config);
499
+ break;
500
+ case 'delete':
501
+ await deleteJobInteractive(context, config);
101
502
  break;
102
503
  case 'exit':
103
504
  continueManaging = false;
@@ -106,292 +507,115 @@ async function manageProductionJobs(context, config) {
106
507
  }
107
508
  }
108
509
  }
109
- /**
110
- * View deployed jobs in production
111
- */
112
- async function viewDeployedJobs(context, config) {
510
+ async function viewDeployedJobsInteractive(context, config) {
113
511
  writeProgress("🔄 Loading job information...");
114
- try {
115
- const jobs = config.jobs || [];
116
- if (jobs.length === 0) {
117
- console.log("\nℹ️ No jobs found in configuration.\n");
118
- await safePrompt([{ type: 'input', name: 'continue', message: 'Press Enter to continue...' }]);
119
- return;
120
- }
121
- console.log("\n" + "=".repeat(60));
122
- console.log("⚙️ Production Jobs");
123
- console.log("=".repeat(60) + "\n");
124
- for (const job of jobs) {
125
- try {
126
- const response = await fetch(`${BASE_URLS.API}/developer/jobs/${context.agentId}/${job.jobId}`, {
127
- method: 'GET',
128
- headers: {
129
- 'Authorization': `Bearer ${context.apiKey}`,
130
- 'Content-Type': 'application/json'
131
- }
132
- });
133
- if (response.ok) {
134
- const data = await response.json();
135
- const jobData = data.data || data;
136
- console.log(`⏰ ${job.name}`);
137
- console.log(` Job ID: ${job.jobId}`);
138
- console.log(` Status: ${jobData.status || 'active'}`);
139
- if (jobData.schedule) {
140
- console.log(` Schedule: ${formatSchedule(jobData.schedule)}`);
141
- }
142
- if (jobData.lastRunAt) {
143
- const lastRun = new Date(jobData.lastRunAt);
144
- console.log(` Last Run: ${lastRun.toLocaleString()}`);
145
- }
146
- if (jobData.nextRunAt) {
147
- const nextRun = new Date(jobData.nextRunAt);
148
- console.log(` Next Run: ${nextRun.toLocaleString()}`);
149
- }
150
- console.log();
151
- }
152
- else {
153
- console.log(`⏰ ${job.name}`);
154
- console.log(` Job ID: ${job.jobId}`);
155
- console.log(` Status: Unable to fetch info`);
156
- console.log();
157
- }
158
- }
159
- catch (error) {
160
- console.log(`⏰ ${job.name}`);
161
- console.log(` Job ID: ${job.jobId}`);
162
- console.log(` Status: Error loading info`);
163
- console.log();
164
- }
165
- }
166
- console.log("=".repeat(60) + "\n");
167
- await safePrompt([{ type: 'input', name: 'continue', message: 'Press Enter to continue...' }]);
512
+ const jobs = config.jobs || [];
513
+ if (jobs.length === 0) {
514
+ console.log("\nℹ️ No jobs found in configuration.\n");
168
515
  }
169
- catch (error) {
170
- console.error('❌ Error loading job information:', error);
171
- await safePrompt([{ type: 'input', name: 'continue', message: 'Press Enter to continue...' }]);
516
+ else {
517
+ await displayJobsCore(context, jobs);
518
+ console.log();
172
519
  }
520
+ await safePrompt([{ type: 'input', name: 'continue', message: 'Press Enter to continue...' }]);
173
521
  }
174
- /**
175
- * View all versions for a specific job
176
- */
177
- async function viewJobVersions(context, config) {
522
+ async function viewJobVersionsInteractive(context, config) {
178
523
  const jobs = config.jobs || [];
179
524
  if (jobs.length === 0) {
180
525
  console.log("\nℹ️ No jobs found in configuration.\n");
181
526
  await safePrompt([{ type: 'input', name: 'continue', message: 'Press Enter to continue...' }]);
182
527
  return;
183
528
  }
184
- // Prompt to select a job
185
- const jobAnswer = await safePrompt([
186
- {
187
- type: 'list',
188
- name: 'selectedJob',
189
- message: 'Select a job to view versions:',
190
- choices: jobs.map((job) => ({
191
- name: `${job.name} (${job.jobId})`,
192
- value: job
193
- }))
194
- }
195
- ]);
196
- if (!jobAnswer)
529
+ const selectedJob = await promptJobSelection(jobs, 'Select a job to view versions:');
530
+ if (!selectedJob)
197
531
  return;
198
- const selectedJob = jobAnswer.selectedJob;
199
532
  writeProgress(`🔄 Loading versions for ${selectedJob.name}...`);
200
- try {
201
- const jobApi = new JobApi(BASE_URLS.API, context.apiKey, context.agentId);
202
- const response = await jobApi.getJobVersions(selectedJob.jobId);
203
- if (!response.success || !response.data) {
204
- throw new Error(response.error?.message || 'Failed to fetch versions');
205
- }
206
- const versions = response.data;
207
- const activeVersionId = selectedJob.activeVersionId;
208
- if (versions.length === 0) {
209
- console.log(`\nℹ️ No versions found for ${selectedJob.name}.\n`);
210
- console.log("💡 Push a version first using 'lua push job'.\n");
211
- await safePrompt([{ type: 'input', name: 'continue', message: 'Press Enter to continue...' }]);
212
- return;
213
- }
214
- // Sort versions by date (newest first)
215
- const sortedVersions = versions.sort((a, b) => {
216
- const dateA = new Date(a.createdAt).getTime();
217
- const dateB = new Date(b.createdAt).getTime();
218
- return dateB - dateA;
219
- });
220
- console.log("\n" + "=".repeat(60));
221
- console.log(`📜 Versions for ${selectedJob.name}`);
222
- console.log("=".repeat(60) + "\n");
223
- sortedVersions.forEach((version) => {
224
- const isActive = version.jobId === activeVersionId;
225
- const date = new Date(version.createdAt);
226
- console.log(`${isActive ? '⭐' : ' '} Version ${version.version}`);
227
- console.log(` Created: ${date.toLocaleString()}`);
228
- if (version.schedule) {
229
- console.log(` Schedule: ${formatSchedule(version.schedule)}`);
230
- }
231
- console.log();
232
- });
233
- console.log("=".repeat(60) + "\n");
533
+ const versions = await fetchVersionsCore(context, selectedJob);
534
+ if (!versions) {
234
535
  await safePrompt([{ type: 'input', name: 'continue', message: 'Press Enter to continue...' }]);
536
+ return;
235
537
  }
236
- catch (error) {
237
- console.error('\n Error loading versions:', error);
238
- await safePrompt([{ type: 'input', name: 'continue', message: 'Press Enter to continue...' }]);
538
+ if (versions.length === 0) {
539
+ console.log(`\nℹ️ No versions found for ${selectedJob.name}.\n`);
540
+ console.log("💡 Push a version first using 'lua push job'.\n");
239
541
  }
542
+ else {
543
+ displayVersionsCore(selectedJob, versions);
544
+ console.log();
545
+ }
546
+ await safePrompt([{ type: 'input', name: 'continue', message: 'Press Enter to continue...' }]);
240
547
  }
241
- /**
242
- * Deploy a job version to production
243
- */
244
- async function deployJobVersion(context, config) {
548
+ async function deployJobVersionInteractive(context, config) {
245
549
  const jobs = config.jobs || [];
246
550
  if (jobs.length === 0) {
247
551
  console.log("\nℹ️ No jobs found in configuration.\n");
248
552
  await safePrompt([{ type: 'input', name: 'continue', message: 'Press Enter to continue...' }]);
249
553
  return;
250
554
  }
251
- // Prompt to select a job
252
- const jobAnswer = await safePrompt([
253
- {
254
- type: 'list',
255
- name: 'selectedJob',
256
- message: 'Select a job to deploy:',
257
- choices: jobs.map((job) => ({
258
- name: `${job.name} (${job.jobId})`,
259
- value: job
260
- }))
261
- }
262
- ]);
263
- if (!jobAnswer)
555
+ const selectedJob = await promptJobSelection(jobs, 'Select a job to deploy:');
556
+ if (!selectedJob)
264
557
  return;
265
- const selectedJob = jobAnswer.selectedJob;
266
558
  writeProgress(`🔄 Loading versions for ${selectedJob.name}...`);
267
- try {
268
- const jobApi = new JobApi(BASE_URLS.API, context.apiKey, context.agentId);
269
- const response = await jobApi.getJobVersions(selectedJob.jobId);
270
- if (!response.success || !response.data) {
271
- throw new Error(response.error?.message || 'Failed to fetch versions');
272
- }
273
- const versions = response.data;
274
- const activeVersionId = selectedJob.activeVersionId;
275
- if (versions.length === 0) {
276
- console.log(`\nℹ️ No versions found for ${selectedJob.name}.\n`);
277
- console.log("💡 Push a version first using 'lua push job'.\n");
278
- await safePrompt([{ type: 'input', name: 'continue', message: 'Press Enter to continue...' }]);
279
- return;
280
- }
281
- // Sort versions by date (newest first)
282
- const sortedVersions = versions.sort((a, b) => {
283
- const dateA = new Date(a.createdAt).getTime();
284
- const dateB = new Date(b.createdAt).getTime();
285
- return dateB - dateA;
286
- });
287
- // Prompt to select a version
288
- const versionAnswer = await safePrompt([
289
- {
290
- type: 'list',
291
- name: 'selectedVersion',
292
- message: 'Select a version to deploy:',
293
- choices: sortedVersions.map((version) => {
294
- const isActive = version.jobId === activeVersionId;
295
- const date = new Date(version.createdAt);
296
- return {
297
- name: `Version ${version.version} (${date.toLocaleDateString()})${isActive ? ' ⭐ CURRENT' : ''}`,
298
- value: version
299
- };
300
- })
301
- }
302
- ]);
303
- if (!versionAnswer)
304
- return;
305
- const selectedVersion = versionAnswer.selectedVersion;
306
- // Show warning
307
- console.log("\n⚠️ WARNING: You are about to deploy to PRODUCTION!");
308
- console.log("⚠️ This will affect ALL users immediately.\n");
309
- console.log(`Job: ${selectedJob.name}`);
310
- console.log(`Version: ${selectedVersion.version}`);
311
- console.log(`Schedule: ${formatSchedule(selectedJob.schedule)}\n`);
312
- const confirmAnswer = await safePrompt([
313
- {
314
- type: 'confirm',
315
- name: 'confirm',
316
- message: 'Are you absolutely sure you want to deploy this version?',
317
- default: false
318
- }
319
- ]);
320
- if (!confirmAnswer || !confirmAnswer.confirm) {
321
- console.log("\n❌ Deployment cancelled.\n");
322
- return;
323
- }
324
- writeProgress("🔄 Deploying version...");
325
- // Reuse the same jobApi instance
326
- const deployResponse = await jobApi.publishJobVersion(selectedJob.jobId, selectedVersion.version);
327
- if (!deployResponse.success) {
328
- console.error(`\n❌ Deploy Error: ${deployResponse.error?.message || 'Unknown error'}\n`);
329
- throw new Error(deployResponse.error?.message || 'Failed to publish job version');
330
- }
331
- writeSuccess(`\n✅ Version ${selectedVersion.version} of "${selectedJob.name}" deployed successfully to production\n`);
332
- writeInfo("💡 The new version is now active and will run on schedule.");
559
+ const versions = await fetchVersionsCore(context, selectedJob);
560
+ if (!versions) {
333
561
  await safePrompt([{ type: 'input', name: 'continue', message: 'Press Enter to continue...' }]);
562
+ return;
334
563
  }
335
- catch (error) {
336
- console.error('\n Error deploying version:', error);
564
+ if (versions.length === 0) {
565
+ console.log(`\nℹ️ No versions found for ${selectedJob.name}.\n`);
566
+ console.log("💡 Push a version first using 'lua push job'.\n");
337
567
  await safePrompt([{ type: 'input', name: 'continue', message: 'Press Enter to continue...' }]);
338
- }
339
- }
340
- async function activateJob(context, config) {
341
- const jobs = config.jobs || [];
342
- if (jobs.length === 0) {
343
- console.log("\nℹ️ No jobs found.\n");
344
568
  return;
345
569
  }
346
- const jobAnswer = await safePrompt([
570
+ const selectedVersion = await promptVersionSelection(versions, selectedJob.activeVersionId);
571
+ if (!selectedVersion)
572
+ return;
573
+ // Show warning and confirm
574
+ console.log("\n⚠️ WARNING: You are about to deploy to PRODUCTION!");
575
+ console.log("⚠️ This will affect ALL users immediately.\n");
576
+ console.log(`Job: ${selectedJob.name}`);
577
+ console.log(`Version: ${selectedVersion.version}`);
578
+ console.log(`Schedule: ${formatSchedule(selectedJob.schedule)}\n`);
579
+ const confirmAnswer = await safePrompt([
347
580
  {
348
- type: 'list',
349
- name: 'selectedJob',
350
- message: 'Select a job to activate:',
351
- choices: jobs.map((job) => ({
352
- name: `${job.name} (${job.jobId})`,
353
- value: job
354
- }))
581
+ type: 'confirm',
582
+ name: 'confirm',
583
+ message: 'Are you absolutely sure you want to deploy this version?',
584
+ default: false
355
585
  }
356
586
  ]);
357
- if (!jobAnswer)
587
+ if (!confirmAnswer || !confirmAnswer.confirm) {
588
+ console.log("\n❌ Deployment cancelled.\n");
358
589
  return;
359
- writeProgress(`🔄 Activating job "${jobAnswer.selectedJob.name}"...`);
360
- const jobApi = new JobApi(BASE_URLS.API, context.apiKey, context.agentId);
361
- const response = await jobApi.activateJob(jobAnswer.selectedJob.jobId);
362
- if (response.success) {
363
- writeSuccess(`✅ Job "${jobAnswer.selectedJob.name}" activated successfully`);
364
- writeInfo("💡 The job is now enabled and will run on schedule.");
365
590
  }
366
- else {
367
- console.error(`❌ Failed to activate job: ${response.error?.message || 'Unknown error'}`);
591
+ await deployVersionCore(context, selectedJob, selectedVersion.version);
592
+ await safePrompt([{ type: 'input', name: 'continue', message: 'Press Enter to continue...' }]);
593
+ }
594
+ async function activateJobInteractive(context, config) {
595
+ const jobs = config.jobs || [];
596
+ if (jobs.length === 0) {
597
+ console.log("\nℹ️ No jobs found.\n");
598
+ return;
368
599
  }
600
+ const selectedJob = await promptJobSelection(jobs, 'Select a job to activate:');
601
+ if (!selectedJob)
602
+ return;
603
+ await activateJobCore(context, selectedJob);
369
604
  }
370
- async function deactivateJob(context, config) {
605
+ async function deactivateJobInteractive(context, config) {
371
606
  const jobs = config.jobs || [];
372
607
  if (jobs.length === 0) {
373
608
  console.log("\nℹ️ No jobs found.\n");
374
609
  return;
375
610
  }
376
- const jobAnswer = await safePrompt([
377
- {
378
- type: 'list',
379
- name: 'selectedJob',
380
- message: 'Select a job to deactivate:',
381
- choices: jobs.map((job) => ({
382
- name: `${job.name} (${job.jobId})`,
383
- value: job
384
- }))
385
- }
386
- ]);
387
- if (!jobAnswer)
611
+ const selectedJob = await promptJobSelection(jobs, 'Select a job to deactivate:');
612
+ if (!selectedJob)
388
613
  return;
389
- // Confirm deactivation
390
614
  const confirmAnswer = await safePrompt([
391
615
  {
392
616
  type: 'confirm',
393
617
  name: 'confirm',
394
- message: `Are you sure you want to deactivate "${jobAnswer.selectedJob.name}"? It will stop running on schedule.`,
618
+ message: `Are you sure you want to deactivate "${selectedJob.name}"? It will stop running on schedule.`,
395
619
  default: false
396
620
  }
397
621
  ]);
@@ -399,124 +623,63 @@ async function deactivateJob(context, config) {
399
623
  console.log("\n❌ Deactivation cancelled.\n");
400
624
  return;
401
625
  }
402
- writeProgress(`🔄 Deactivating job "${jobAnswer.selectedJob.name}"...`);
403
- const jobApi = new JobApi(BASE_URLS.API, context.apiKey, context.agentId);
404
- const response = await jobApi.deactivateJob(jobAnswer.selectedJob.jobId);
405
- if (response.success) {
406
- writeSuccess(`✅ Job "${jobAnswer.selectedJob.name}" deactivated successfully`);
407
- writeInfo("💡 The job is now disabled and will not run.");
408
- }
409
- else {
410
- console.error(`❌ Failed to deactivate job: ${response.error?.message || 'Unknown error'}`);
411
- }
626
+ await deactivateJobCore(context, selectedJob);
412
627
  }
413
- async function triggerJob(context, config) {
628
+ async function triggerJobInteractive(context, config) {
414
629
  const jobs = config.jobs || [];
415
630
  if (jobs.length === 0) {
416
631
  console.log("\nℹ️ No jobs found.\n");
417
632
  return;
418
633
  }
419
- const jobAnswer = await safePrompt([
420
- {
421
- type: 'list',
422
- name: 'selectedJob',
423
- message: 'Select a job to trigger:',
424
- choices: jobs.map((job) => ({
425
- name: `${job.name} (${job.jobId})`,
426
- value: job
427
- }))
428
- }
429
- ]);
430
- if (!jobAnswer)
634
+ const selectedJob = await promptJobSelection(jobs, 'Select a job to trigger:');
635
+ if (!selectedJob)
636
+ return;
637
+ await triggerJobCore(context, selectedJob);
638
+ }
639
+ async function viewExecutionHistoryInteractive(context, config) {
640
+ const jobs = config.jobs || [];
641
+ if (jobs.length === 0) {
642
+ console.log("\nℹ️ No jobs found in configuration.\n");
643
+ await safePrompt([{ type: 'input', name: 'continue', message: 'Press Enter to continue...' }]);
431
644
  return;
432
- writeProgress("⚡ Triggering job...");
433
- const jobApi = new JobApi(BASE_URLS.API, context.apiKey, context.agentId);
434
- const response = await jobApi.triggerJob(jobAnswer.selectedJob.jobId);
435
- if (response.success && response.data) {
436
- writeSuccess(`✅ Job "${jobAnswer.selectedJob.name}" triggered successfully`);
437
- writeInfo(`📋 Execution ID: ${response.data.id || 'N/A'}`);
438
- }
439
- else {
440
- console.error(`❌ Failed to trigger job: ${response.error?.message || 'Unknown error'}`);
441
645
  }
646
+ const selectedJob = await promptJobSelection(jobs, 'Select a job to view execution history:');
647
+ if (!selectedJob)
648
+ return;
649
+ writeProgress(`🔄 Loading execution history for ${selectedJob.name}...`);
650
+ await fetchAndDisplayHistoryCore(context, selectedJob);
651
+ await safePrompt([{ type: 'input', name: 'continue', message: 'Press Enter to continue...' }]);
442
652
  }
443
- /**
444
- * View execution history for a job
445
- */
446
- async function viewExecutionHistory(context, config) {
653
+ async function deleteJobInteractive(context, config) {
447
654
  const jobs = config.jobs || [];
448
655
  if (jobs.length === 0) {
449
656
  console.log("\nℹ️ No jobs found in configuration.\n");
450
657
  await safePrompt([{ type: 'input', name: 'continue', message: 'Press Enter to continue...' }]);
451
658
  return;
452
659
  }
453
- // Prompt to select a job
454
- const jobAnswer = await safePrompt([
660
+ const selectedJob = await promptJobSelection(jobs, 'Select a job to delete:');
661
+ if (!selectedJob)
662
+ return;
663
+ console.log("\n⚠️ WARNING: You are about to delete a job!");
664
+ console.log("⚠️ If the job has versions, it will be deactivated instead.\n");
665
+ const confirmAnswer = await safePrompt([
455
666
  {
456
- type: 'list',
457
- name: 'selectedJob',
458
- message: 'Select a job to view execution history:',
459
- choices: jobs.map((job) => ({
460
- name: `${job.name} (${job.jobId})`,
461
- value: job
462
- }))
667
+ type: 'confirm',
668
+ name: 'confirm',
669
+ message: `Are you sure you want to delete "${selectedJob.name}"?`,
670
+ default: false
463
671
  }
464
672
  ]);
465
- if (!jobAnswer)
673
+ if (!confirmAnswer || !confirmAnswer.confirm) {
674
+ console.log("\n❌ Deletion cancelled.\n");
466
675
  return;
467
- const selectedJob = jobAnswer.selectedJob;
468
- writeProgress(`🔄 Loading execution history for ${selectedJob.name}...`);
469
- try {
470
- const jobApi = new JobApi(BASE_URLS.API, context.apiKey, context.agentId);
471
- const response = await jobApi.getJobExecutions(selectedJob.jobId, 20); // Get last 20 executions
472
- if (!response.success || !response.data) {
473
- throw new Error(response.error?.message || 'Failed to fetch execution history');
474
- }
475
- const executions = response.data.executions || [];
476
- if (executions.length === 0) {
477
- console.log(`\nℹ️ No execution history found for ${selectedJob.name}.\n`);
478
- console.log("💡 The job hasn't run yet or has no recorded executions.\n");
479
- await safePrompt([{ type: 'input', name: 'continue', message: 'Press Enter to continue...' }]);
480
- return;
481
- }
482
- console.log("\n" + "=".repeat(60));
483
- console.log(`📊 Execution History for ${selectedJob.name}`);
484
- console.log("=".repeat(60) + "\n");
485
- executions.forEach((execution, index) => {
486
- const startedAt = new Date(execution.startedAt);
487
- const status = execution.status;
488
- const statusEmoji = status === 'completed' ? '✅' :
489
- status === 'failed' ? '❌' :
490
- status === 'running' ? '🔄' : '⏳';
491
- console.log(`${index + 1}. ${statusEmoji} ${status.toUpperCase()}`);
492
- console.log(` Execution ID: ${execution.executionId}`);
493
- console.log(` Started: ${startedAt.toLocaleString()}`);
494
- if (execution.completedAt) {
495
- const completedAt = new Date(execution.completedAt);
496
- const duration = completedAt.getTime() - startedAt.getTime();
497
- console.log(` Completed: ${completedAt.toLocaleString()}`);
498
- console.log(` Duration: ${Math.round(duration / 1000)}s`);
499
- }
500
- if (execution.result) {
501
- console.log(` Result: ${JSON.stringify(execution.result).substring(0, 100)}...`);
502
- }
503
- if (execution.error) {
504
- console.log(` Error: ${execution.error}`);
505
- }
506
- console.log();
507
- });
508
- console.log("=".repeat(60) + "\n");
509
- console.log(`Showing last ${executions.length} executions\n`);
510
- await safePrompt([{ type: 'input', name: 'continue', message: 'Press Enter to continue...' }]);
511
- }
512
- catch (error) {
513
- console.error('\n❌ Error loading execution history:', error);
514
- await safePrompt([{ type: 'input', name: 'continue', message: 'Press Enter to continue...' }]);
515
676
  }
677
+ await deleteJobCore(context, selectedJob);
678
+ await safePrompt([{ type: 'input', name: 'continue', message: 'Press Enter to continue...' }]);
516
679
  }
517
- /**
518
- * Format schedule object for display
519
- */
680
+ // ─────────────────────────────────────────────────────────────────────────────
681
+ // Utility Functions
682
+ // ─────────────────────────────────────────────────────────────────────────────
520
683
  function formatSchedule(schedule) {
521
684
  if (!schedule)
522
685
  return 'Not configured';