cloudops-cli 0.1.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 (86) hide show
  1. package/README.md +132 -0
  2. package/dist/api/api-client.d.ts +78 -0
  3. package/dist/api/api-client.d.ts.map +1 -0
  4. package/dist/api/api-client.js +197 -0
  5. package/dist/api/api-client.js.map +1 -0
  6. package/dist/api/index.d.ts +3 -0
  7. package/dist/api/index.d.ts.map +1 -0
  8. package/dist/api/index.js +8 -0
  9. package/dist/api/index.js.map +1 -0
  10. package/dist/auth/__mocks__/auth-handler.d.ts +27 -0
  11. package/dist/auth/__mocks__/auth-handler.d.ts.map +1 -0
  12. package/dist/auth/__mocks__/auth-handler.js +24 -0
  13. package/dist/auth/__mocks__/auth-handler.js.map +1 -0
  14. package/dist/auth/auth-handler.d.ts +80 -0
  15. package/dist/auth/auth-handler.d.ts.map +1 -0
  16. package/dist/auth/auth-handler.js +266 -0
  17. package/dist/auth/auth-handler.js.map +1 -0
  18. package/dist/auth/callback-server.d.ts +31 -0
  19. package/dist/auth/callback-server.d.ts.map +1 -0
  20. package/dist/auth/callback-server.js +143 -0
  21. package/dist/auth/callback-server.js.map +1 -0
  22. package/dist/auth/index.d.ts +3 -0
  23. package/dist/auth/index.d.ts.map +1 -0
  24. package/dist/auth/index.js +8 -0
  25. package/dist/auth/index.js.map +1 -0
  26. package/dist/cli.d.ts +3 -0
  27. package/dist/cli.d.ts.map +1 -0
  28. package/dist/cli.js +422 -0
  29. package/dist/cli.js.map +1 -0
  30. package/dist/commands/accounts.d.ts +15 -0
  31. package/dist/commands/accounts.d.ts.map +1 -0
  32. package/dist/commands/accounts.js +307 -0
  33. package/dist/commands/accounts.js.map +1 -0
  34. package/dist/commands/audit.d.ts +23 -0
  35. package/dist/commands/audit.d.ts.map +1 -0
  36. package/dist/commands/audit.js +348 -0
  37. package/dist/commands/audit.js.map +1 -0
  38. package/dist/commands/config.d.ts +15 -0
  39. package/dist/commands/config.d.ts.map +1 -0
  40. package/dist/commands/config.js +148 -0
  41. package/dist/commands/config.js.map +1 -0
  42. package/dist/commands/incident.d.ts +59 -0
  43. package/dist/commands/incident.d.ts.map +1 -0
  44. package/dist/commands/incident.js +1032 -0
  45. package/dist/commands/incident.js.map +1 -0
  46. package/dist/commands/init.d.ts +9 -0
  47. package/dist/commands/init.d.ts.map +1 -0
  48. package/dist/commands/init.js +300 -0
  49. package/dist/commands/init.js.map +1 -0
  50. package/dist/commands/investigate.d.ts +15 -0
  51. package/dist/commands/investigate.d.ts.map +1 -0
  52. package/dist/commands/investigate.js +65 -0
  53. package/dist/commands/investigate.js.map +1 -0
  54. package/dist/commands/runbook.d.ts +20 -0
  55. package/dist/commands/runbook.d.ts.map +1 -0
  56. package/dist/commands/runbook.js +265 -0
  57. package/dist/commands/runbook.js.map +1 -0
  58. package/dist/config/__mocks__/config-handler.d.ts +11 -0
  59. package/dist/config/__mocks__/config-handler.d.ts.map +1 -0
  60. package/dist/config/__mocks__/config-handler.js +31 -0
  61. package/dist/config/__mocks__/config-handler.js.map +1 -0
  62. package/dist/config/certificate-pinning.d.ts +68 -0
  63. package/dist/config/certificate-pinning.d.ts.map +1 -0
  64. package/dist/config/certificate-pinning.js +249 -0
  65. package/dist/config/certificate-pinning.js.map +1 -0
  66. package/dist/config/config-handler.d.ts +45 -0
  67. package/dist/config/config-handler.d.ts.map +1 -0
  68. package/dist/config/config-handler.js +149 -0
  69. package/dist/config/config-handler.js.map +1 -0
  70. package/dist/config/index.d.ts +2 -0
  71. package/dist/config/index.d.ts.map +1 -0
  72. package/dist/config/index.js +7 -0
  73. package/dist/config/index.js.map +1 -0
  74. package/dist/index.d.ts +4 -0
  75. package/dist/index.d.ts.map +1 -0
  76. package/dist/index.js +25 -0
  77. package/dist/index.js.map +1 -0
  78. package/dist/types/cli-options.d.ts +31 -0
  79. package/dist/types/cli-options.d.ts.map +1 -0
  80. package/dist/types/cli-options.js +3 -0
  81. package/dist/types/cli-options.js.map +1 -0
  82. package/dist/utils/output-formatter.d.ts +67 -0
  83. package/dist/utils/output-formatter.d.ts.map +1 -0
  84. package/dist/utils/output-formatter.js +147 -0
  85. package/dist/utils/output-formatter.js.map +1 -0
  86. package/package.json +65 -0
@@ -0,0 +1,1032 @@
1
+ "use strict";
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ var desc = Object.getOwnPropertyDescriptor(m, k);
5
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
+ desc = { enumerable: true, get: function() { return m[k]; } };
7
+ }
8
+ Object.defineProperty(o, k2, desc);
9
+ }) : (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ o[k2] = m[k];
12
+ }));
13
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
14
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
15
+ }) : function(o, v) {
16
+ o["default"] = v;
17
+ });
18
+ var __importStar = (this && this.__importStar) || (function () {
19
+ var ownKeys = function(o) {
20
+ ownKeys = Object.getOwnPropertyNames || function (o) {
21
+ var ar = [];
22
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
23
+ return ar;
24
+ };
25
+ return ownKeys(o);
26
+ };
27
+ return function (mod) {
28
+ if (mod && mod.__esModule) return mod;
29
+ var result = {};
30
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
31
+ __setModuleDefault(result, mod);
32
+ return result;
33
+ };
34
+ })();
35
+ var __importDefault = (this && this.__importDefault) || function (mod) {
36
+ return (mod && mod.__esModule) ? mod : { "default": mod };
37
+ };
38
+ Object.defineProperty(exports, "__esModule", { value: true });
39
+ exports.incidentStatusCommand = incidentStatusCommand;
40
+ exports.incidentContextCommand = incidentContextCommand;
41
+ exports.incidentDiagnoseCommand = incidentDiagnoseCommand;
42
+ exports.incidentAskCommand = incidentAskCommand;
43
+ exports.incidentRemediateCommand = incidentRemediateCommand;
44
+ exports.incidentPlanShowCommand = incidentPlanShowCommand;
45
+ exports.incidentPlanEditCommand = incidentPlanEditCommand;
46
+ exports.incidentExecuteCommand = incidentExecuteCommand;
47
+ exports.incidentListCommand = incidentListCommand;
48
+ const chalk_1 = __importDefault(require("chalk"));
49
+ const cli_table3_1 = __importDefault(require("cli-table3"));
50
+ const fs = __importStar(require("fs"));
51
+ const path = __importStar(require("path"));
52
+ const api_client_1 = require("../api/api-client");
53
+ /**
54
+ * Get incident status
55
+ * Displays current status and metadata for an incident
56
+ */
57
+ async function incidentStatusCommand(incidentId, _options) {
58
+ if (!incidentId || incidentId.trim().length === 0) {
59
+ console.error(chalk_1.default.red('Error: Incident ID is required'));
60
+ process.exit(1);
61
+ }
62
+ console.log(chalk_1.default.bold.blue(`\n📊 Fetching incident status...\n`));
63
+ try {
64
+ const apiClient = (0, api_client_1.createAPIClient)();
65
+ const incident = await apiClient.get(`/api/v1/incidents/${incidentId}`);
66
+ // Display incident details
67
+ console.log(chalk_1.default.green('✓ Incident found\n'));
68
+ const table = new cli_table3_1.default({
69
+ colWidths: [25, 55],
70
+ wordWrap: true,
71
+ });
72
+ table.push([chalk_1.default.bold('Incident ID'), chalk_1.default.cyan(incident.incident_id)], [chalk_1.default.bold('Description'), incident.description], [chalk_1.default.bold('Status'), formatStatus(incident.status)], [chalk_1.default.bold('Severity'), formatSeverity(incident.severity)], [chalk_1.default.bold('Created By'), incident.created_by], [chalk_1.default.bold('Created At'), new Date(incident.created_at).toLocaleString()], [chalk_1.default.bold('Updated At'), new Date(incident.updated_at).toLocaleString()]);
73
+ if (incident.context_gathered_at) {
74
+ table.push([
75
+ chalk_1.default.bold('Context Gathered'),
76
+ new Date(incident.context_gathered_at).toLocaleString(),
77
+ ]);
78
+ }
79
+ if (incident.diagnosed_at) {
80
+ table.push([
81
+ chalk_1.default.bold('Diagnosed At'),
82
+ new Date(incident.diagnosed_at).toLocaleString(),
83
+ ]);
84
+ }
85
+ if (incident.resolved_at) {
86
+ table.push([
87
+ chalk_1.default.bold('Resolved At'),
88
+ new Date(incident.resolved_at).toLocaleString(),
89
+ ]);
90
+ }
91
+ if (incident.tags && Object.keys(incident.tags).length > 0) {
92
+ table.push([
93
+ chalk_1.default.bold('Tags'),
94
+ Object.entries(incident.tags)
95
+ .map(([k, v]) => `${k}=${v}`)
96
+ .join(', '),
97
+ ]);
98
+ }
99
+ console.log(table.toString());
100
+ console.log();
101
+ // Show next steps based on status
102
+ showNextSteps(incident);
103
+ }
104
+ catch (error) {
105
+ handleAPIError(error, 'fetch incident status');
106
+ }
107
+ }
108
+ /**
109
+ * Get incident context
110
+ * Displays gathered context including metrics, logs, events, topology, and changes
111
+ */
112
+ async function incidentContextCommand(incidentId, options) {
113
+ if (!incidentId || incidentId.trim().length === 0) {
114
+ console.error(chalk_1.default.red('Error: Incident ID is required'));
115
+ process.exit(1);
116
+ }
117
+ console.log(chalk_1.default.bold.blue(`\n📦 Fetching incident context...\n`));
118
+ try {
119
+ const apiClient = (0, api_client_1.createAPIClient)();
120
+ const response = await apiClient.get(`/api/v1/incidents/${incidentId}/context`);
121
+ const context = response.context;
122
+ // Export to file if requested
123
+ if (options.export) {
124
+ exportContext(context, options.export, incidentId);
125
+ return;
126
+ }
127
+ // Display context summary
128
+ console.log(chalk_1.default.green('✓ Context retrieved\n'));
129
+ const summaryTable = new cli_table3_1.default({
130
+ head: [chalk_1.default.bold('Category'), chalk_1.default.bold('Count'), chalk_1.default.bold('Details')],
131
+ colWidths: [20, 10, 50],
132
+ wordWrap: true,
133
+ });
134
+ summaryTable.push([
135
+ 'Metrics',
136
+ context.metrics.length,
137
+ context.metrics.length > 0
138
+ ? `Sources: ${[...new Set(context.metrics.map((m) => m.source))].join(', ')}`
139
+ : 'No metrics gathered',
140
+ ], [
141
+ 'Logs',
142
+ context.logs.length,
143
+ context.logs.length > 0
144
+ ? `Levels: ${[...new Set(context.logs.map((l) => l.level))].join(', ')}`
145
+ : 'No logs gathered',
146
+ ], [
147
+ 'Events',
148
+ context.events.length,
149
+ context.events.length > 0
150
+ ? `Types: ${[...new Set(context.events.map((e) => e.event_type))].slice(0, 3).join(', ')}`
151
+ : 'No events gathered',
152
+ ], [
153
+ 'Resources',
154
+ context.topology.resources.length,
155
+ context.topology.resources.length > 0
156
+ ? `Providers: ${[...new Set(context.topology.resources.map((r) => r.provider))].join(', ')}`
157
+ : 'No resources mapped',
158
+ ], [
159
+ 'Dependencies',
160
+ context.topology.dependencies.length,
161
+ context.topology.dependencies.length > 0
162
+ ? `Types: ${[...new Set(context.topology.dependencies.map((d) => d.type))].join(', ')}`
163
+ : 'No dependencies mapped',
164
+ ], [
165
+ 'Recent Changes',
166
+ context.recent_changes.length,
167
+ context.recent_changes.length > 0
168
+ ? `Types: ${[...new Set(context.recent_changes.map((c) => c.type))].join(', ')}`
169
+ : 'No recent changes',
170
+ ]);
171
+ console.log(summaryTable.toString());
172
+ console.log();
173
+ // Display metadata
174
+ console.log(chalk_1.default.bold('Context Metadata:'));
175
+ console.log(chalk_1.default.gray(` Gathered at: ${new Date(context.gathered_at).toLocaleString()}`));
176
+ console.log(chalk_1.default.gray(` Duration: ${context.duration_ms}ms`));
177
+ console.log();
178
+ // Show sample data if available
179
+ if (context.metrics.length > 0) {
180
+ console.log(chalk_1.default.bold('Sample Metrics:'));
181
+ context.metrics.slice(0, 3).forEach((metric) => {
182
+ console.log(chalk_1.default.gray(` • ${metric.metric_name} (${metric.resource})`));
183
+ console.log(chalk_1.default.gray(` Range: ${metric.time_range.start} to ${metric.time_range.end}`));
184
+ console.log(chalk_1.default.gray(` Stats: min=${metric.statistics.min}, max=${metric.statistics.max}, avg=${metric.statistics.avg.toFixed(2)}`));
185
+ });
186
+ if (context.metrics.length > 3) {
187
+ console.log(chalk_1.default.gray(` ... and ${context.metrics.length - 3} more`));
188
+ }
189
+ console.log();
190
+ }
191
+ if (context.logs.length > 0) {
192
+ console.log(chalk_1.default.bold('Recent Logs:'));
193
+ context.logs.slice(0, 5).forEach((log) => {
194
+ const levelColor = getLevelColor(log.level);
195
+ console.log(chalk_1.default.gray(` [${new Date(log.timestamp).toLocaleTimeString()}] `) +
196
+ levelColor(log.level.toUpperCase()) +
197
+ chalk_1.default.gray(` ${log.message.substring(0, 80)}${log.message.length > 80 ? '...' : ''}`));
198
+ });
199
+ if (context.logs.length > 5) {
200
+ console.log(chalk_1.default.gray(` ... and ${context.logs.length - 5} more`));
201
+ }
202
+ console.log();
203
+ }
204
+ if (context.recent_changes.length > 0) {
205
+ console.log(chalk_1.default.bold('Recent Changes:'));
206
+ context.recent_changes.slice(0, 3).forEach((change) => {
207
+ console.log(chalk_1.default.gray(` • [${new Date(change.timestamp).toLocaleString()}] `) +
208
+ chalk_1.default.yellow(change.type) +
209
+ chalk_1.default.gray(` on ${change.resource}`));
210
+ console.log(chalk_1.default.gray(` ${change.description}`));
211
+ });
212
+ if (context.recent_changes.length > 3) {
213
+ console.log(chalk_1.default.gray(` ... and ${context.recent_changes.length - 3} more`));
214
+ }
215
+ console.log();
216
+ }
217
+ // Show export option
218
+ console.log(chalk_1.default.bold('Export Options:'));
219
+ console.log(chalk_1.default.gray(` Export full context: cloudops incident ${incidentId} context --export <file>`));
220
+ console.log();
221
+ }
222
+ catch (error) {
223
+ handleAPIError(error, 'fetch incident context');
224
+ }
225
+ }
226
+ /**
227
+ * Export context to file
228
+ */
229
+ function exportContext(context, filePath, _incidentId) {
230
+ try {
231
+ // Resolve file path
232
+ const resolvedPath = path.resolve(filePath);
233
+ const dir = path.dirname(resolvedPath);
234
+ // Ensure directory exists
235
+ if (!fs.existsSync(dir)) {
236
+ fs.mkdirSync(dir, { recursive: true });
237
+ }
238
+ // Write context to file
239
+ fs.writeFileSync(resolvedPath, JSON.stringify(context, null, 2), 'utf-8');
240
+ console.log(chalk_1.default.green(`✓ Context exported successfully\n`));
241
+ console.log(chalk_1.default.bold('File:'), chalk_1.default.cyan(resolvedPath));
242
+ console.log(chalk_1.default.gray('Size:'), `${(fs.statSync(resolvedPath).size / 1024).toFixed(2)} KB`);
243
+ console.log();
244
+ console.log(chalk_1.default.bold('Context Summary:'));
245
+ console.log(chalk_1.default.gray(` Metrics: ${context.metrics.length}`));
246
+ console.log(chalk_1.default.gray(` Logs: ${context.logs.length}`));
247
+ console.log(chalk_1.default.gray(` Events: ${context.events.length}`));
248
+ console.log(chalk_1.default.gray(` Resources: ${context.topology.resources.length}`));
249
+ console.log(chalk_1.default.gray(` Dependencies: ${context.topology.dependencies.length}`));
250
+ console.log(chalk_1.default.gray(` Recent Changes: ${context.recent_changes.length}`));
251
+ console.log();
252
+ }
253
+ catch (error) {
254
+ console.error(chalk_1.default.red('\n✗ Failed to export context:'));
255
+ console.error(chalk_1.default.red(` ${error instanceof Error ? error.message : 'Unknown error'}`));
256
+ process.exit(1);
257
+ }
258
+ }
259
+ /**
260
+ * Format incident status with color
261
+ */
262
+ function formatStatus(status) {
263
+ const statusColors = {
264
+ created: chalk_1.default.gray,
265
+ gathering_context: chalk_1.default.blue,
266
+ context_ready: chalk_1.default.cyan,
267
+ diagnosing: chalk_1.default.blue,
268
+ diagnosed: chalk_1.default.cyan,
269
+ planning_remediation: chalk_1.default.blue,
270
+ plan_ready: chalk_1.default.cyan,
271
+ awaiting_approval: chalk_1.default.yellow,
272
+ approved: chalk_1.default.green,
273
+ executing: chalk_1.default.blue,
274
+ resolved: chalk_1.default.green,
275
+ failed: chalk_1.default.red,
276
+ };
277
+ const colorFn = statusColors[status] || chalk_1.default.white;
278
+ return colorFn(status.replace(/_/g, ' ').toUpperCase());
279
+ }
280
+ /**
281
+ * Format severity with color
282
+ */
283
+ function formatSeverity(severity) {
284
+ const severityColors = {
285
+ low: chalk_1.default.gray,
286
+ medium: chalk_1.default.yellow,
287
+ high: chalk_1.default.red,
288
+ critical: chalk_1.default.red.bold,
289
+ };
290
+ const colorFn = severityColors[severity] || chalk_1.default.white;
291
+ return colorFn(severity.toUpperCase());
292
+ }
293
+ /**
294
+ * Get color function for log level
295
+ */
296
+ function getLevelColor(level) {
297
+ const levelColors = {
298
+ debug: chalk_1.default.gray,
299
+ info: chalk_1.default.blue,
300
+ warn: chalk_1.default.yellow,
301
+ error: chalk_1.default.red,
302
+ fatal: chalk_1.default.red.bold,
303
+ };
304
+ return levelColors[level] || chalk_1.default.white;
305
+ }
306
+ /**
307
+ * Show next steps based on incident status
308
+ */
309
+ function showNextSteps(incident) {
310
+ console.log(chalk_1.default.bold('Next Steps:'));
311
+ switch (incident.status) {
312
+ case 'created':
313
+ case 'gathering_context':
314
+ console.log(chalk_1.default.gray(` • Wait for context gathering to complete (usually < 60 seconds)`));
315
+ console.log(chalk_1.default.gray(` • Check status: cloudops incident ${incident.incident_id} status`));
316
+ break;
317
+ case 'context_ready':
318
+ console.log(chalk_1.default.gray(` • View context: cloudops incident ${incident.incident_id} context`));
319
+ console.log(chalk_1.default.gray(` • Start diagnosis: cloudops incident ${incident.incident_id} diagnose`));
320
+ break;
321
+ case 'diagnosing':
322
+ console.log(chalk_1.default.gray(` • Wait for AI diagnosis to complete (usually < 30 seconds)`));
323
+ console.log(chalk_1.default.gray(` • Check status: cloudops incident ${incident.incident_id} status`));
324
+ break;
325
+ case 'diagnosed':
326
+ console.log(chalk_1.default.gray(` • View diagnosis: cloudops incident ${incident.incident_id} diagnose`));
327
+ console.log(chalk_1.default.gray(` • Ask questions: cloudops incident ${incident.incident_id} ask "<question>"`));
328
+ console.log(chalk_1.default.gray(` • Generate plan: cloudops incident ${incident.incident_id} remediate`));
329
+ break;
330
+ case 'planning_remediation':
331
+ console.log(chalk_1.default.gray(` • Wait for remediation plan generation`));
332
+ break;
333
+ case 'plan_ready':
334
+ console.log(chalk_1.default.gray(` • Review plan: cloudops incident ${incident.incident_id} plan show`));
335
+ console.log(chalk_1.default.gray(` • Execute plan: cloudops incident ${incident.incident_id} execute`));
336
+ break;
337
+ case 'awaiting_approval':
338
+ console.log(chalk_1.default.gray(` • Waiting for approval from designated approvers`));
339
+ console.log(chalk_1.default.gray(` • Check status: cloudops incident ${incident.incident_id} status`));
340
+ break;
341
+ case 'approved':
342
+ case 'executing':
343
+ console.log(chalk_1.default.gray(` • Execution in progress via GitOps workflow`));
344
+ console.log(chalk_1.default.gray(` • Check status: cloudops incident ${incident.incident_id} status`));
345
+ break;
346
+ case 'resolved':
347
+ console.log(chalk_1.default.green(` ✓ Incident resolved successfully`));
348
+ break;
349
+ case 'failed':
350
+ console.log(chalk_1.default.red(` ✗ Incident resolution failed`));
351
+ console.log(chalk_1.default.gray(` • Review context: cloudops incident ${incident.incident_id} context`));
352
+ console.log(chalk_1.default.gray(` • Check diagnosis: cloudops incident ${incident.incident_id} diagnose`));
353
+ break;
354
+ default:
355
+ console.log(chalk_1.default.gray(` • Check status: cloudops incident ${incident.incident_id} status`));
356
+ }
357
+ console.log();
358
+ }
359
+ /**
360
+ * Handle API errors consistently
361
+ */
362
+ function handleAPIError(error, action) {
363
+ if (error instanceof api_client_1.APIError) {
364
+ console.error(chalk_1.default.red(`\n✗ Failed to ${action}:`));
365
+ console.error(chalk_1.default.red(` ${error.message}`));
366
+ if (error.statusCode === 401) {
367
+ console.error(chalk_1.default.yellow('\nAuthentication required. Please run: cloudops init'));
368
+ }
369
+ else if (error.statusCode === 403) {
370
+ console.error(chalk_1.default.yellow('\nAccess denied. Check your permissions.'));
371
+ }
372
+ else if (error.statusCode === 404) {
373
+ console.error(chalk_1.default.yellow('\nIncident not found. Check the incident ID.'));
374
+ }
375
+ else if (error.details) {
376
+ console.error(chalk_1.default.gray('\nDetails:'), error.details);
377
+ }
378
+ }
379
+ else {
380
+ console.error(chalk_1.default.red(`\n✗ Unexpected error:`));
381
+ console.error(chalk_1.default.red(` ${error instanceof Error ? error.message : 'Unknown error'}`));
382
+ }
383
+ process.exit(1);
384
+ }
385
+ /**
386
+ * Get incident diagnosis
387
+ * Displays AI-generated root cause analysis with evidence
388
+ */
389
+ async function incidentDiagnoseCommand(incidentId, options) {
390
+ if (!incidentId || incidentId.trim().length === 0) {
391
+ console.error(chalk_1.default.red('Error: Incident ID is required'));
392
+ process.exit(1);
393
+ }
394
+ console.log(chalk_1.default.bold.blue(`\n🔍 Fetching incident diagnosis...\n`));
395
+ try {
396
+ const apiClient = (0, api_client_1.createAPIClient)();
397
+ // Build request body
398
+ const requestBody = {};
399
+ if (options.useProfile) {
400
+ requestBody.use_profile = options.useProfile;
401
+ }
402
+ // Call diagnose endpoint
403
+ const response = await apiClient.post(`/api/v1/incidents/${incidentId}/diagnose`, requestBody);
404
+ const diagnosis = response.diagnosis;
405
+ // Display diagnosis header
406
+ console.log(chalk_1.default.green('✓ Diagnosis completed\n'));
407
+ // Display metadata
408
+ const metadataTable = new cli_table3_1.default({
409
+ colWidths: [25, 55],
410
+ wordWrap: true,
411
+ });
412
+ metadataTable.push([chalk_1.default.bold('Generated At'), new Date(diagnosis.generated_at).toLocaleString()], [chalk_1.default.bold('Model Used'), diagnosis.model_used], [chalk_1.default.bold('Overall Confidence'), formatConfidence(diagnosis.confidence)], [chalk_1.default.bold('Root Causes Found'), diagnosis.root_causes.length.toString()]);
413
+ console.log(metadataTable.toString());
414
+ console.log();
415
+ // Display root causes
416
+ if (diagnosis.root_causes.length === 0) {
417
+ console.log(chalk_1.default.yellow('⚠ No root causes identified'));
418
+ console.log(chalk_1.default.gray(' The AI was unable to determine a probable root cause.'));
419
+ console.log(chalk_1.default.gray(' Try gathering more context or asking specific questions.'));
420
+ console.log();
421
+ return;
422
+ }
423
+ console.log(chalk_1.default.bold('Root Cause Analysis:\n'));
424
+ diagnosis.root_causes.forEach((rootCause, index) => {
425
+ // Root cause header
426
+ console.log(chalk_1.default.bold(`${index + 1}. `) +
427
+ chalk_1.default.cyan(rootCause.description) +
428
+ chalk_1.default.gray(` (${formatConfidence(rootCause.confidence)})`));
429
+ console.log();
430
+ // Reasoning
431
+ console.log(chalk_1.default.bold(' Reasoning:'));
432
+ console.log(chalk_1.default.gray(` ${rootCause.reasoning}`));
433
+ console.log();
434
+ // Evidence
435
+ if (rootCause.evidence.length > 0) {
436
+ console.log(chalk_1.default.bold(' Evidence:'));
437
+ rootCause.evidence.forEach((evidence, evidenceIndex) => {
438
+ const evidenceIcon = getEvidenceIcon(evidence.type);
439
+ console.log(chalk_1.default.gray(` ${evidenceIcon} [${evidence.type.toUpperCase()}] `) +
440
+ chalk_1.default.white(evidence.source));
441
+ console.log(chalk_1.default.gray(` Time: ${new Date(evidence.timestamp).toLocaleString()}`));
442
+ console.log(chalk_1.default.gray(` Relevance: ${evidence.relevance}`));
443
+ // Display evidence data summary
444
+ if (evidence.data) {
445
+ const dataSummary = formatEvidenceData(evidence.data, evidence.type);
446
+ if (dataSummary) {
447
+ console.log(chalk_1.default.gray(` Data: ${dataSummary}`));
448
+ }
449
+ }
450
+ if (evidenceIndex < rootCause.evidence.length - 1) {
451
+ console.log();
452
+ }
453
+ });
454
+ console.log();
455
+ }
456
+ // Separator between root causes
457
+ if (index < diagnosis.root_causes.length - 1) {
458
+ console.log(chalk_1.default.gray(' ' + '─'.repeat(70)));
459
+ console.log();
460
+ }
461
+ });
462
+ // Show next steps
463
+ console.log(chalk_1.default.bold('Next Steps:'));
464
+ console.log(chalk_1.default.gray(` • Ask follow-up questions: cloudops incident ${incidentId} ask "<question>"`));
465
+ console.log(chalk_1.default.gray(` • Generate remediation plan: cloudops incident ${incidentId} remediate`));
466
+ console.log();
467
+ }
468
+ catch (error) {
469
+ handleAPIError(error, 'fetch incident diagnosis');
470
+ }
471
+ }
472
+ /**
473
+ * Format confidence score as percentage with color
474
+ */
475
+ function formatConfidence(confidence) {
476
+ const percentage = (confidence * 100).toFixed(0);
477
+ const percentageNum = parseInt(percentage);
478
+ if (percentageNum >= 80) {
479
+ return chalk_1.default.green(`${percentage}% confidence`);
480
+ }
481
+ else if (percentageNum >= 60) {
482
+ return chalk_1.default.yellow(`${percentage}% confidence`);
483
+ }
484
+ else {
485
+ return chalk_1.default.red(`${percentage}% confidence`);
486
+ }
487
+ }
488
+ /**
489
+ * Get icon for evidence type
490
+ */
491
+ function getEvidenceIcon(type) {
492
+ const icons = {
493
+ metric: '📊',
494
+ log: '📝',
495
+ event: '⚡',
496
+ topology: '🗺️',
497
+ };
498
+ return icons[type] || '•';
499
+ }
500
+ /**
501
+ * Format evidence data for display
502
+ */
503
+ function formatEvidenceData(data, type) {
504
+ if (!data || typeof data !== 'object') {
505
+ return null;
506
+ }
507
+ try {
508
+ const dataObj = data;
509
+ switch (type) {
510
+ case 'metric':
511
+ if (dataObj.value !== undefined) {
512
+ return `value=${dataObj.value}${dataObj.unit ? ' ' + dataObj.unit : ''}`;
513
+ }
514
+ if (dataObj.min !== undefined && dataObj.max !== undefined) {
515
+ return `range=${dataObj.min}-${dataObj.max}${dataObj.unit ? ' ' + dataObj.unit : ''}`;
516
+ }
517
+ break;
518
+ case 'log':
519
+ if (dataObj.message) {
520
+ const message = dataObj.message.toString();
521
+ return message.length > 80 ? message.substring(0, 77) + '...' : message;
522
+ }
523
+ break;
524
+ case 'event':
525
+ if (dataObj.event_type) {
526
+ return `type=${dataObj.event_type}`;
527
+ }
528
+ break;
529
+ case 'topology':
530
+ if (dataObj.resource_type) {
531
+ return `resource=${dataObj.resource_type}`;
532
+ }
533
+ break;
534
+ }
535
+ // Fallback: show first few keys
536
+ const keys = Object.keys(dataObj).slice(0, 3);
537
+ if (keys.length > 0) {
538
+ return keys.map((k) => `${k}=${JSON.stringify(dataObj[k])}`).join(', ');
539
+ }
540
+ }
541
+ catch {
542
+ // Ignore formatting errors
543
+ }
544
+ return null;
545
+ }
546
+ /**
547
+ * Ask a follow-up question about an incident
548
+ * Displays AI response based on existing context and conversation history
549
+ */
550
+ async function incidentAskCommand(incidentId, question, options) {
551
+ if (!incidentId || incidentId.trim().length === 0) {
552
+ console.error(chalk_1.default.red('Error: Incident ID is required'));
553
+ process.exit(1);
554
+ }
555
+ if (!question || question.trim().length === 0) {
556
+ console.error(chalk_1.default.red('Error: Question is required'));
557
+ process.exit(1);
558
+ }
559
+ console.log(chalk_1.default.bold.blue(`\n💬 Asking question about incident...\n`));
560
+ console.log(chalk_1.default.gray(`Question: ${question}`));
561
+ console.log();
562
+ try {
563
+ const apiClient = (0, api_client_1.createAPIClient)();
564
+ // Build request body
565
+ const requestBody = {
566
+ question,
567
+ };
568
+ if (options.useProfile) {
569
+ requestBody.use_profile = options.useProfile;
570
+ }
571
+ // Call ask endpoint
572
+ const response = await apiClient.post(`/api/v1/incidents/${incidentId}/ask`, requestBody);
573
+ // Display response
574
+ console.log(chalk_1.default.green('✓ Response received\n'));
575
+ // Display metadata
576
+ const metadataTable = new cli_table3_1.default({
577
+ colWidths: [25, 55],
578
+ wordWrap: true,
579
+ });
580
+ metadataTable.push([chalk_1.default.bold('Model Used'), response.model_used], [chalk_1.default.bold('Timestamp'), new Date(response.timestamp).toLocaleString()]);
581
+ console.log(metadataTable.toString());
582
+ console.log();
583
+ // Display answer
584
+ console.log(chalk_1.default.bold('Answer:'));
585
+ console.log();
586
+ console.log(chalk_1.default.white(response.answer));
587
+ console.log();
588
+ // Show next steps
589
+ console.log(chalk_1.default.bold('Next Steps:'));
590
+ console.log(chalk_1.default.gray(` • Ask another question: cloudops incident ${incidentId} ask "<question>"`));
591
+ console.log(chalk_1.default.gray(` • Generate remediation plan: cloudops incident ${incidentId} remediate`));
592
+ console.log();
593
+ }
594
+ catch (error) {
595
+ handleAPIError(error, 'ask question');
596
+ }
597
+ }
598
+ /**
599
+ * Generate remediation plan for an incident
600
+ * Displays remediation plan with risk assessment
601
+ */
602
+ async function incidentRemediateCommand(incidentId, options) {
603
+ if (!incidentId || incidentId.trim().length === 0) {
604
+ console.error(chalk_1.default.red('Error: Incident ID is required'));
605
+ process.exit(1);
606
+ }
607
+ console.log(chalk_1.default.bold.blue(`\n🔧 Generating remediation plan...\n`));
608
+ try {
609
+ const apiClient = (0, api_client_1.createAPIClient)();
610
+ // Build request body
611
+ const requestBody = {};
612
+ if (options.useProfile) {
613
+ requestBody.use_profile = options.useProfile;
614
+ }
615
+ if (options.iacTool) {
616
+ requestBody.iac_tool = options.iacTool;
617
+ }
618
+ // Call remediate endpoint
619
+ const response = await apiClient.post(`/api/v1/incidents/${incidentId}/remediate`, requestBody);
620
+ const plan = response.plan;
621
+ // Display plan header
622
+ console.log(chalk_1.default.green('✓ Remediation plan generated\n'));
623
+ // Display plan metadata
624
+ const metadataTable = new cli_table3_1.default({
625
+ colWidths: [25, 55],
626
+ wordWrap: true,
627
+ });
628
+ metadataTable.push([chalk_1.default.bold('Plan ID'), chalk_1.default.cyan(plan.plan_id)], [chalk_1.default.bold('Generated At'), new Date(plan.generated_at).toLocaleString()], [chalk_1.default.bold('Total Steps'), plan.steps.length.toString()], [chalk_1.default.bold('Estimated Duration'), plan.estimated_duration], [chalk_1.default.bold('Total Risk Score'), formatRiskScore(plan.total_risk_score)], [chalk_1.default.bold('Requires Approval'), plan.requires_approval ? chalk_1.default.yellow('YES') : chalk_1.default.green('NO')]);
629
+ console.log(metadataTable.toString());
630
+ console.log();
631
+ // Display approval rules if required
632
+ if (plan.requires_approval && plan.approval_rules.length > 0) {
633
+ console.log(chalk_1.default.bold('Approval Requirements:\n'));
634
+ plan.approval_rules.forEach((rule) => {
635
+ console.log(chalk_1.default.yellow(` Level ${rule.level}:`));
636
+ console.log(chalk_1.default.gray(` Approvers: ${rule.approvers.join(', ')}`));
637
+ console.log(chalk_1.default.gray(` Timeout: ${rule.timeout}`));
638
+ if (rule.escalation_policy) {
639
+ console.log(chalk_1.default.gray(` Escalation: ${rule.escalation_policy}`));
640
+ }
641
+ });
642
+ console.log();
643
+ }
644
+ // Display remediation steps
645
+ if (plan.steps.length === 0) {
646
+ console.log(chalk_1.default.yellow('⚠ No remediation steps generated'));
647
+ console.log(chalk_1.default.gray(' The AI was unable to generate a remediation plan.'));
648
+ console.log();
649
+ return;
650
+ }
651
+ console.log(chalk_1.default.bold('Remediation Steps:\n'));
652
+ plan.steps.forEach((step) => {
653
+ // Step header
654
+ console.log(chalk_1.default.bold(`Step ${step.step_number}: `) +
655
+ chalk_1.default.cyan(step.action) +
656
+ chalk_1.default.gray(` [${formatRiskLevel(step.risk_level)}]`));
657
+ console.log();
658
+ // Description
659
+ console.log(chalk_1.default.gray(` ${step.description}`));
660
+ console.log();
661
+ // Risk assessment
662
+ console.log(chalk_1.default.bold(' Risk Assessment:'));
663
+ console.log(chalk_1.default.gray(` Risk Level: ${formatRiskLevel(step.risk_level)}`));
664
+ console.log(chalk_1.default.gray(` Blast Radius: ${step.blast_radius.scope.replace(/_/g, ' ')}`));
665
+ console.log(chalk_1.default.gray(` Affected Resources: ${step.blast_radius.affected_resources.length}`));
666
+ if (step.blast_radius.affected_users > 0) {
667
+ console.log(chalk_1.default.gray(` Affected Users: ${step.blast_radius.affected_users}`));
668
+ }
669
+ console.log(chalk_1.default.gray(` Downtime Risk: ${step.blast_radius.downtime_risk ? chalk_1.default.red('YES') : chalk_1.default.green('NO')}`));
670
+ console.log();
671
+ // IaC details
672
+ console.log(chalk_1.default.bold(' Infrastructure as Code:'));
673
+ console.log(chalk_1.default.gray(` Tool: ${step.iac_tool.toUpperCase()}`));
674
+ console.log(chalk_1.default.gray(` Code Preview:`));
675
+ const codeLines = step.iac_code.split('\n').slice(0, 5);
676
+ codeLines.forEach((line) => {
677
+ console.log(chalk_1.default.gray(` ${line}`));
678
+ });
679
+ if (step.iac_code.split('\n').length > 5) {
680
+ console.log(chalk_1.default.gray(` ... (${step.iac_code.split('\n').length - 5} more lines)`));
681
+ }
682
+ console.log();
683
+ // Verification steps
684
+ if (step.verification_steps.length > 0) {
685
+ console.log(chalk_1.default.bold(' Verification Steps:'));
686
+ step.verification_steps.forEach((verifyStep, idx) => {
687
+ console.log(chalk_1.default.gray(` ${idx + 1}. ${verifyStep}`));
688
+ });
689
+ console.log();
690
+ }
691
+ // Rollback plan
692
+ console.log(chalk_1.default.bold(' Rollback Plan:'));
693
+ console.log(chalk_1.default.gray(` ${step.rollback_plan}`));
694
+ console.log();
695
+ // Estimated duration
696
+ console.log(chalk_1.default.bold(' Estimated Duration:'), chalk_1.default.gray(step.estimated_duration));
697
+ console.log();
698
+ // Separator between steps
699
+ if (step.step_number < plan.steps.length) {
700
+ console.log(chalk_1.default.gray(' ' + '─'.repeat(70)));
701
+ console.log();
702
+ }
703
+ });
704
+ // Show next steps
705
+ console.log(chalk_1.default.bold('Next Steps:'));
706
+ console.log(chalk_1.default.gray(` • Review plan: cloudops incident ${incidentId} plan show`));
707
+ console.log(chalk_1.default.gray(` • Edit plan: cloudops incident ${incidentId} plan edit`));
708
+ console.log(chalk_1.default.gray(` • Execute plan: cloudops incident ${incidentId} execute`));
709
+ console.log();
710
+ }
711
+ catch (error) {
712
+ handleAPIError(error, 'generate remediation plan');
713
+ }
714
+ }
715
+ /**
716
+ * Show remediation plan for an incident
717
+ */
718
+ async function incidentPlanShowCommand(incidentId, _options) {
719
+ if (!incidentId || incidentId.trim().length === 0) {
720
+ console.error(chalk_1.default.red('Error: Incident ID is required'));
721
+ process.exit(1);
722
+ }
723
+ console.log(chalk_1.default.bold.blue(`\n📋 Fetching remediation plan...\n`));
724
+ try {
725
+ const apiClient = (0, api_client_1.createAPIClient)();
726
+ // Call plan get endpoint
727
+ const response = await apiClient.get(`/api/v1/incidents/${incidentId}/plan`);
728
+ const plan = response.plan;
729
+ // Display plan (reuse the display logic from remediate command)
730
+ console.log(chalk_1.default.green('✓ Remediation plan retrieved\n'));
731
+ // Display plan metadata
732
+ const metadataTable = new cli_table3_1.default({
733
+ colWidths: [25, 55],
734
+ wordWrap: true,
735
+ });
736
+ metadataTable.push([chalk_1.default.bold('Plan ID'), chalk_1.default.cyan(plan.plan_id)], [chalk_1.default.bold('Generated At'), new Date(plan.generated_at).toLocaleString()], [chalk_1.default.bold('Total Steps'), plan.steps.length.toString()], [chalk_1.default.bold('Estimated Duration'), plan.estimated_duration], [chalk_1.default.bold('Total Risk Score'), formatRiskScore(plan.total_risk_score)], [chalk_1.default.bold('Requires Approval'), plan.requires_approval ? chalk_1.default.yellow('YES') : chalk_1.default.green('NO')]);
737
+ console.log(metadataTable.toString());
738
+ console.log();
739
+ // Display approval rules if required
740
+ if (plan.requires_approval && plan.approval_rules.length > 0) {
741
+ console.log(chalk_1.default.bold('Approval Requirements:\n'));
742
+ plan.approval_rules.forEach((rule) => {
743
+ console.log(chalk_1.default.yellow(` Level ${rule.level}:`));
744
+ console.log(chalk_1.default.gray(` Approvers: ${rule.approvers.join(', ')}`));
745
+ console.log(chalk_1.default.gray(` Timeout: ${rule.timeout}`));
746
+ if (rule.escalation_policy) {
747
+ console.log(chalk_1.default.gray(` Escalation: ${rule.escalation_policy}`));
748
+ }
749
+ });
750
+ console.log();
751
+ }
752
+ // Display remediation steps summary
753
+ if (plan.steps.length === 0) {
754
+ console.log(chalk_1.default.yellow('⚠ No remediation steps in plan'));
755
+ console.log();
756
+ return;
757
+ }
758
+ console.log(chalk_1.default.bold('Remediation Steps Summary:\n'));
759
+ const stepsTable = new cli_table3_1.default({
760
+ head: [
761
+ chalk_1.default.bold('Step'),
762
+ chalk_1.default.bold('Action'),
763
+ chalk_1.default.bold('Risk'),
764
+ chalk_1.default.bold('Duration'),
765
+ ],
766
+ colWidths: [8, 35, 15, 15],
767
+ wordWrap: true,
768
+ });
769
+ plan.steps.forEach((step) => {
770
+ stepsTable.push([
771
+ step.step_number.toString(),
772
+ step.action,
773
+ formatRiskLevel(step.risk_level),
774
+ step.estimated_duration,
775
+ ]);
776
+ });
777
+ console.log(stepsTable.toString());
778
+ console.log();
779
+ // Show next steps
780
+ console.log(chalk_1.default.bold('Next Steps:'));
781
+ console.log(chalk_1.default.gray(` • Edit plan: cloudops incident ${incidentId} plan edit`));
782
+ console.log(chalk_1.default.gray(` • Execute plan: cloudops incident ${incidentId} execute`));
783
+ console.log();
784
+ }
785
+ catch (error) {
786
+ handleAPIError(error, 'fetch remediation plan');
787
+ }
788
+ }
789
+ /**
790
+ * Edit remediation plan for an incident
791
+ * Opens the plan in the default editor
792
+ */
793
+ async function incidentPlanEditCommand(incidentId, _options) {
794
+ if (!incidentId || incidentId.trim().length === 0) {
795
+ console.error(chalk_1.default.red('Error: Incident ID is required'));
796
+ process.exit(1);
797
+ }
798
+ console.log(chalk_1.default.bold.blue(`\n✏️ Editing remediation plan...\n`));
799
+ try {
800
+ const apiClient = (0, api_client_1.createAPIClient)();
801
+ // Fetch current plan
802
+ const response = await apiClient.get(`/api/v1/incidents/${incidentId}/plan`);
803
+ const plan = response.plan;
804
+ // Create temporary file
805
+ const tmpDir = path.join(process.cwd(), '.cloudops-tmp');
806
+ if (!fs.existsSync(tmpDir)) {
807
+ fs.mkdirSync(tmpDir, { recursive: true });
808
+ }
809
+ const tmpFile = path.join(tmpDir, `plan-${incidentId}.json`);
810
+ fs.writeFileSync(tmpFile, JSON.stringify(plan, null, 2), 'utf-8');
811
+ console.log(chalk_1.default.green('✓ Plan saved to temporary file\n'));
812
+ console.log(chalk_1.default.bold('File:'), chalk_1.default.cyan(tmpFile));
813
+ console.log();
814
+ console.log(chalk_1.default.yellow('⚠ Manual editing not yet implemented'));
815
+ console.log(chalk_1.default.gray(' This feature will open the plan in your default editor.'));
816
+ console.log(chalk_1.default.gray(' After editing, the plan will be validated and saved.'));
817
+ console.log();
818
+ console.log(chalk_1.default.bold('Current Plan:'));
819
+ console.log(chalk_1.default.gray(` Plan ID: ${plan.plan_id}`));
820
+ console.log(chalk_1.default.gray(` Steps: ${plan.steps.length}`));
821
+ console.log(chalk_1.default.gray(` Risk Score: ${plan.total_risk_score}`));
822
+ console.log();
823
+ // TODO: Implement actual editing workflow
824
+ // 1. Open file in editor (using $EDITOR or default)
825
+ // 2. Wait for editor to close
826
+ // 3. Read modified file
827
+ // 4. Validate changes
828
+ // 5. Send PUT request to update plan
829
+ // 6. Display confirmation
830
+ console.log(chalk_1.default.bold('Next Steps:'));
831
+ console.log(chalk_1.default.gray(` • View plan: cloudops incident ${incidentId} plan show`));
832
+ console.log(chalk_1.default.gray(` • Execute plan: cloudops incident ${incidentId} execute`));
833
+ console.log();
834
+ }
835
+ catch (error) {
836
+ handleAPIError(error, 'edit remediation plan');
837
+ }
838
+ }
839
+ /**
840
+ * Execute remediation plan for an incident
841
+ * Handles approval workflow and displays PR URL and pipeline status
842
+ */
843
+ async function incidentExecuteCommand(incidentId, options) {
844
+ if (!incidentId || incidentId.trim().length === 0) {
845
+ console.error(chalk_1.default.red('Error: Incident ID is required'));
846
+ process.exit(1);
847
+ }
848
+ console.log(chalk_1.default.bold.blue(`\n🚀 Executing remediation plan...\n`));
849
+ try {
850
+ const apiClient = (0, api_client_1.createAPIClient)();
851
+ // First, get the plan to show what will be executed
852
+ const planResponse = await apiClient.get(`/api/v1/incidents/${incidentId}/plan`);
853
+ const plan = planResponse.plan;
854
+ // Display execution summary
855
+ console.log(chalk_1.default.bold('Execution Summary:\n'));
856
+ const summaryTable = new cli_table3_1.default({
857
+ colWidths: [25, 55],
858
+ wordWrap: true,
859
+ });
860
+ summaryTable.push([chalk_1.default.bold('Plan ID'), chalk_1.default.cyan(plan.plan_id)], [chalk_1.default.bold('Total Steps'), plan.steps.length.toString()], [chalk_1.default.bold('Estimated Duration'), plan.estimated_duration], [chalk_1.default.bold('Total Risk Score'), formatRiskScore(plan.total_risk_score)], [chalk_1.default.bold('Requires Approval'), plan.requires_approval ? chalk_1.default.yellow('YES') : chalk_1.default.green('NO')]);
861
+ console.log(summaryTable.toString());
862
+ console.log();
863
+ // Show approval requirements if needed
864
+ if (plan.requires_approval && !options.skipApproval) {
865
+ console.log(chalk_1.default.yellow('⚠ This plan requires approval before execution\n'));
866
+ plan.approval_rules.forEach((rule) => {
867
+ console.log(chalk_1.default.bold(` Approval Level ${rule.level}:`));
868
+ console.log(chalk_1.default.gray(` Approvers: ${rule.approvers.join(', ')}`));
869
+ console.log(chalk_1.default.gray(` Timeout: ${rule.timeout}`));
870
+ });
871
+ console.log();
872
+ }
873
+ // Build request body
874
+ const requestBody = {
875
+ plan_id: plan.plan_id,
876
+ };
877
+ if (options.skipApproval) {
878
+ requestBody.skip_approval = true;
879
+ }
880
+ // Call execute endpoint
881
+ const executeResponse = await apiClient.post(`/api/v1/incidents/${incidentId}/execute`, requestBody);
882
+ // Display execution status
883
+ console.log(chalk_1.default.green('✓ Execution initiated\n'));
884
+ const statusTable = new cli_table3_1.default({
885
+ colWidths: [25, 55],
886
+ wordWrap: true,
887
+ });
888
+ statusTable.push([chalk_1.default.bold('Execution ID'), chalk_1.default.cyan(executeResponse.execution_id)], [chalk_1.default.bold('Status'), formatExecutionStatus(executeResponse.status)]);
889
+ if (executeResponse.pr_url) {
890
+ statusTable.push([chalk_1.default.bold('Pull Request'), chalk_1.default.cyan(executeResponse.pr_url)]);
891
+ }
892
+ if (executeResponse.pipeline_url) {
893
+ statusTable.push([chalk_1.default.bold('Pipeline'), chalk_1.default.cyan(executeResponse.pipeline_url)]);
894
+ }
895
+ console.log(statusTable.toString());
896
+ console.log();
897
+ // Show status-specific messages
898
+ switch (executeResponse.status) {
899
+ case 'pending_approval':
900
+ console.log(chalk_1.default.yellow('⏳ Waiting for approval'));
901
+ console.log(chalk_1.default.gray(' Approval requests have been sent to designated approvers.'));
902
+ console.log(chalk_1.default.gray(' You will be notified once the plan is approved.'));
903
+ break;
904
+ case 'approved':
905
+ console.log(chalk_1.default.green('✓ Plan approved'));
906
+ console.log(chalk_1.default.gray(' Execution will begin shortly via GitOps workflow.'));
907
+ break;
908
+ case 'executing':
909
+ console.log(chalk_1.default.blue('⚙️ Execution in progress'));
910
+ console.log(chalk_1.default.gray(' Changes are being applied via GitOps workflow.'));
911
+ if (executeResponse.pr_url) {
912
+ console.log(chalk_1.default.gray(` Monitor PR: ${executeResponse.pr_url}`));
913
+ }
914
+ if (executeResponse.pipeline_url) {
915
+ console.log(chalk_1.default.gray(` Monitor pipeline: ${executeResponse.pipeline_url}`));
916
+ }
917
+ break;
918
+ case 'completed':
919
+ console.log(chalk_1.default.green('✓ Execution completed successfully'));
920
+ break;
921
+ case 'failed':
922
+ console.log(chalk_1.default.red('✗ Execution failed'));
923
+ console.log(chalk_1.default.gray(' Check the pipeline logs for details.'));
924
+ break;
925
+ }
926
+ console.log();
927
+ // Show next steps
928
+ console.log(chalk_1.default.bold('Next Steps:'));
929
+ console.log(chalk_1.default.gray(` • Check status: cloudops incident ${incidentId} status`));
930
+ if (executeResponse.pr_url) {
931
+ console.log(chalk_1.default.gray(` • Review PR: ${executeResponse.pr_url}`));
932
+ }
933
+ if (executeResponse.pipeline_url) {
934
+ console.log(chalk_1.default.gray(` • Monitor pipeline: ${executeResponse.pipeline_url}`));
935
+ }
936
+ console.log();
937
+ }
938
+ catch (error) {
939
+ handleAPIError(error, 'execute remediation plan');
940
+ }
941
+ }
942
+ /**
943
+ * Format risk score with color
944
+ */
945
+ function formatRiskScore(score) {
946
+ if (score >= 80) {
947
+ return chalk_1.default.red.bold(`${score} (CRITICAL)`);
948
+ }
949
+ else if (score >= 60) {
950
+ return chalk_1.default.red(`${score} (HIGH)`);
951
+ }
952
+ else if (score >= 40) {
953
+ return chalk_1.default.yellow(`${score} (MEDIUM)`);
954
+ }
955
+ else {
956
+ return chalk_1.default.green(`${score} (LOW)`);
957
+ }
958
+ }
959
+ /**
960
+ * Format risk level with color
961
+ */
962
+ function formatRiskLevel(level) {
963
+ const levelColors = {
964
+ low: chalk_1.default.green,
965
+ medium: chalk_1.default.yellow,
966
+ high: chalk_1.default.red,
967
+ critical: chalk_1.default.red.bold,
968
+ };
969
+ const colorFn = levelColors[level] || chalk_1.default.white;
970
+ return colorFn(level.toUpperCase());
971
+ }
972
+ /**
973
+ * Format execution status with color
974
+ */
975
+ function formatExecutionStatus(status) {
976
+ const statusColors = {
977
+ pending_approval: chalk_1.default.yellow,
978
+ approved: chalk_1.default.green,
979
+ executing: chalk_1.default.blue,
980
+ completed: chalk_1.default.green,
981
+ failed: chalk_1.default.red,
982
+ };
983
+ const colorFn = statusColors[status] || chalk_1.default.white;
984
+ return colorFn(status.replace(/_/g, ' ').toUpperCase());
985
+ }
986
+ /**
987
+ * List all incidents with optional filtering
988
+ * Displays incidents in a table format
989
+ */
990
+ async function incidentListCommand(options) {
991
+ console.log(chalk_1.default.blue('\n📋 Fetching incidents...\n'));
992
+ try {
993
+ const apiClient = (0, api_client_1.createAPIClient)();
994
+ // Build query parameters
995
+ const params = new URLSearchParams();
996
+ if (options.status) {
997
+ params.append('status', options.status);
998
+ }
999
+ if (options.severity) {
1000
+ params.append('severity', options.severity);
1001
+ }
1002
+ const queryString = params.toString();
1003
+ const url = queryString ? `/api/v1/incidents?${queryString}` : '/api/v1/incidents';
1004
+ const response = await apiClient.get(url);
1005
+ if (!response.incidents || response.incidents.length === 0) {
1006
+ console.log(chalk_1.default.yellow('No incidents found.'));
1007
+ return;
1008
+ }
1009
+ console.log(chalk_1.default.green(`✓ Found ${response.incidents.length} incident(s)\n`));
1010
+ // Display incidents in a table
1011
+ const table = new cli_table3_1.default({
1012
+ head: ['ID', 'Description', 'Status', 'Severity', 'Created'],
1013
+ colWidths: [30, 50, 20, 15, 25],
1014
+ wordWrap: true,
1015
+ });
1016
+ response.incidents.forEach((incident) => {
1017
+ table.push([
1018
+ chalk_1.default.cyan(incident.incident_id),
1019
+ incident.description.substring(0, 47) + (incident.description.length > 47 ? '...' : ''),
1020
+ formatStatus(incident.status),
1021
+ formatSeverity(incident.severity),
1022
+ new Date(incident.created_at).toLocaleString(),
1023
+ ]);
1024
+ });
1025
+ console.log(table.toString());
1026
+ console.log();
1027
+ }
1028
+ catch (error) {
1029
+ handleAPIError(error, 'list incidents');
1030
+ }
1031
+ }
1032
+ //# sourceMappingURL=incident.js.map