infra-cost 0.1.0 → 0.2.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.
@@ -0,0 +1,3370 @@
1
+ #!/usr/bin/env node
2
+ var __require = /* @__PURE__ */ ((x) => typeof require !== "undefined" ? require : typeof Proxy !== "undefined" ? new Proxy(x, {
3
+ get: (a, b) => (typeof require !== "undefined" ? require : a)[b]
4
+ }) : x)(function(x) {
5
+ if (typeof require !== "undefined")
6
+ return require.apply(this, arguments);
7
+ throw new Error('Dynamic require of "' + x + '" is not supported');
8
+ });
9
+
10
+ // src/visualization/multi-cloud-dashboard.ts
11
+ import chalk4 from "chalk";
12
+
13
+ // src/types/providers.ts
14
+ var CloudProviderAdapter = class {
15
+ constructor(config) {
16
+ this.config = config;
17
+ }
18
+ calculateServiceTotals(rawCostData) {
19
+ return this.processRawCostData(rawCostData);
20
+ }
21
+ processRawCostData(rawCostData) {
22
+ const totals = {
23
+ lastMonth: 0,
24
+ thisMonth: 0,
25
+ last7Days: 0,
26
+ yesterday: 0
27
+ };
28
+ const totalsByService = {
29
+ lastMonth: {},
30
+ thisMonth: {},
31
+ last7Days: {},
32
+ yesterday: {}
33
+ };
34
+ const now = /* @__PURE__ */ new Date();
35
+ const startOfThisMonth = new Date(now.getFullYear(), now.getMonth(), 1);
36
+ const startOfLastMonth = new Date(now.getFullYear(), now.getMonth() - 1, 1);
37
+ const endOfLastMonth = new Date(now.getFullYear(), now.getMonth(), 0);
38
+ const yesterday = new Date(now);
39
+ yesterday.setDate(yesterday.getDate() - 1);
40
+ const last7DaysStart = new Date(now);
41
+ last7DaysStart.setDate(last7DaysStart.getDate() - 7);
42
+ for (const [serviceName, serviceCosts] of Object.entries(rawCostData)) {
43
+ let lastMonthServiceTotal = 0;
44
+ let thisMonthServiceTotal = 0;
45
+ let last7DaysServiceTotal = 0;
46
+ let yesterdayServiceTotal = 0;
47
+ for (const [dateStr, cost] of Object.entries(serviceCosts)) {
48
+ const date = new Date(dateStr);
49
+ if (date >= startOfLastMonth && date <= endOfLastMonth) {
50
+ lastMonthServiceTotal += cost;
51
+ }
52
+ if (date >= startOfThisMonth) {
53
+ thisMonthServiceTotal += cost;
54
+ }
55
+ if (date >= last7DaysStart && date < yesterday) {
56
+ last7DaysServiceTotal += cost;
57
+ }
58
+ if (date.toDateString() === yesterday.toDateString()) {
59
+ yesterdayServiceTotal += cost;
60
+ }
61
+ }
62
+ totalsByService.lastMonth[serviceName] = lastMonthServiceTotal;
63
+ totalsByService.thisMonth[serviceName] = thisMonthServiceTotal;
64
+ totalsByService.last7Days[serviceName] = last7DaysServiceTotal;
65
+ totalsByService.yesterday[serviceName] = yesterdayServiceTotal;
66
+ totals.lastMonth += lastMonthServiceTotal;
67
+ totals.thisMonth += thisMonthServiceTotal;
68
+ totals.last7Days += last7DaysServiceTotal;
69
+ totals.yesterday += yesterdayServiceTotal;
70
+ }
71
+ return {
72
+ totals,
73
+ totalsByService
74
+ };
75
+ }
76
+ };
77
+ var ResourceType = /* @__PURE__ */ ((ResourceType2) => {
78
+ ResourceType2["COMPUTE"] = "compute";
79
+ ResourceType2["STORAGE"] = "storage";
80
+ ResourceType2["DATABASE"] = "database";
81
+ ResourceType2["NETWORK"] = "network";
82
+ ResourceType2["SECURITY"] = "security";
83
+ ResourceType2["SERVERLESS"] = "serverless";
84
+ ResourceType2["CONTAINER"] = "container";
85
+ ResourceType2["ANALYTICS"] = "analytics";
86
+ return ResourceType2;
87
+ })(ResourceType || {});
88
+
89
+ // src/providers/aws.ts
90
+ import { CostExplorerClient, GetCostAndUsageCommand } from "@aws-sdk/client-cost-explorer";
91
+ import { IAMClient, ListAccountAliasesCommand } from "@aws-sdk/client-iam";
92
+ import { STSClient, GetCallerIdentityCommand } from "@aws-sdk/client-sts";
93
+ import { EC2Client, DescribeInstancesCommand, DescribeVolumesCommand, DescribeVpcsCommand, DescribeSubnetsCommand } from "@aws-sdk/client-ec2";
94
+ import { S3Client, ListBucketsCommand } from "@aws-sdk/client-s3";
95
+ import { RDSClient, DescribeDBInstancesCommand } from "@aws-sdk/client-rds";
96
+ import { LambdaClient, ListFunctionsCommand } from "@aws-sdk/client-lambda";
97
+ import { BudgetsClient, DescribeBudgetsCommand } from "@aws-sdk/client-budgets";
98
+ import dayjs from "dayjs";
99
+
100
+ // src/logger.ts
101
+ import chalk from "chalk";
102
+ import ora from "ora";
103
+ var spinner;
104
+ function showSpinner(text) {
105
+ if (!spinner) {
106
+ spinner = ora({ text: "" }).start();
107
+ }
108
+ spinner.text = text;
109
+ }
110
+
111
+ // src/analytics/anomaly-detector.ts
112
+ var AnomalyDetector = class {
113
+ constructor(config = { sensitivity: "MEDIUM", lookbackPeriods: 14 }) {
114
+ this.config = config;
115
+ }
116
+ /**
117
+ * Detects anomalies using multiple statistical methods
118
+ */
119
+ detectAnomalies(dataPoints) {
120
+ if (dataPoints.length < this.config.lookbackPeriods) {
121
+ return [];
122
+ }
123
+ const anomalies = [];
124
+ anomalies.push(...this.detectStatisticalAnomalies(dataPoints));
125
+ anomalies.push(...this.detectTrendAnomalies(dataPoints));
126
+ anomalies.push(...this.detectSeasonalAnomalies(dataPoints));
127
+ return this.consolidateAnomalies(anomalies);
128
+ }
129
+ /**
130
+ * Statistical anomaly detection using modified Z-score
131
+ */
132
+ detectStatisticalAnomalies(dataPoints) {
133
+ const anomalies = [];
134
+ const values = dataPoints.map((dp) => dp.value);
135
+ for (let i = this.config.lookbackPeriods; i < dataPoints.length; i++) {
136
+ const currentValue = values[i];
137
+ const historicalValues = values.slice(i - this.config.lookbackPeriods, i);
138
+ const median = this.calculateMedian(historicalValues);
139
+ const mad = this.calculateMAD(historicalValues, median);
140
+ const modifiedZScore = mad === 0 ? 0 : 0.6745 * (currentValue - median) / mad;
141
+ const threshold = this.getSensitivityThreshold();
142
+ if (Math.abs(modifiedZScore) > threshold) {
143
+ const deviation = currentValue - median;
144
+ const deviationPercentage = median === 0 ? 0 : Math.abs(deviation / median) * 100;
145
+ anomalies.push({
146
+ timestamp: dataPoints[i].timestamp,
147
+ actualValue: currentValue,
148
+ expectedValue: median,
149
+ deviation: Math.abs(deviation),
150
+ deviationPercentage,
151
+ severity: this.calculateSeverity(Math.abs(modifiedZScore), threshold),
152
+ confidence: Math.min(95, Math.abs(modifiedZScore) / threshold * 100),
153
+ type: deviation > 0 ? "SPIKE" : "DROP",
154
+ description: this.generateAnomalyDescription("STATISTICAL", deviation, deviationPercentage),
155
+ potentialCauses: this.generatePotentialCauses(deviation > 0 ? "SPIKE" : "DROP", deviationPercentage)
156
+ });
157
+ }
158
+ }
159
+ return anomalies;
160
+ }
161
+ /**
162
+ * Trend-based anomaly detection
163
+ */
164
+ detectTrendAnomalies(dataPoints) {
165
+ const anomalies = [];
166
+ const values = dataPoints.map((dp) => dp.value);
167
+ const trendWindow = Math.min(7, Math.floor(this.config.lookbackPeriods / 2));
168
+ for (let i = trendWindow * 2; i < dataPoints.length; i++) {
169
+ const recentTrend = this.calculateLinearTrend(values.slice(i - trendWindow, i));
170
+ const historicalTrend = this.calculateLinearTrend(values.slice(i - trendWindow * 2, i - trendWindow));
171
+ const trendChange = Math.abs(recentTrend - historicalTrend);
172
+ const trendChangeThreshold = this.config.sensitivity === "HIGH" ? 0.1 : this.config.sensitivity === "MEDIUM" ? 0.2 : 0.3;
173
+ if (trendChange > trendChangeThreshold) {
174
+ const currentValue = values[i];
175
+ const expectedValue = values[i - 1] + historicalTrend;
176
+ const deviation = Math.abs(currentValue - expectedValue);
177
+ const deviationPercentage = expectedValue === 0 ? 0 : deviation / expectedValue * 100;
178
+ anomalies.push({
179
+ timestamp: dataPoints[i].timestamp,
180
+ actualValue: currentValue,
181
+ expectedValue,
182
+ deviation,
183
+ deviationPercentage,
184
+ severity: this.calculateSeverity(trendChange, trendChangeThreshold),
185
+ confidence: Math.min(90, trendChange / trendChangeThreshold * 100),
186
+ type: "TREND_CHANGE",
187
+ description: `Significant trend change detected: ${recentTrend > historicalTrend ? "acceleration" : "deceleration"} in cost growth`,
188
+ potentialCauses: this.generatePotentialCauses("TREND_CHANGE", deviationPercentage)
189
+ });
190
+ }
191
+ }
192
+ return anomalies;
193
+ }
194
+ /**
195
+ * Seasonal anomaly detection
196
+ */
197
+ detectSeasonalAnomalies(dataPoints) {
198
+ if (!this.config.seasonalityPeriods || dataPoints.length < this.config.seasonalityPeriods * 2) {
199
+ return [];
200
+ }
201
+ const anomalies = [];
202
+ const values = dataPoints.map((dp) => dp.value);
203
+ const seasonalPeriod = this.config.seasonalityPeriods;
204
+ for (let i = seasonalPeriod; i < dataPoints.length; i++) {
205
+ const currentValue = values[i];
206
+ const seasonalBaseline = values[i - seasonalPeriod];
207
+ const seasonalDeviation = Math.abs(currentValue - seasonalBaseline);
208
+ const seasonalDeviationPercentage = seasonalBaseline === 0 ? 0 : seasonalDeviation / seasonalBaseline * 100;
209
+ const threshold = seasonalBaseline * 0.3;
210
+ if (seasonalDeviation > threshold && seasonalDeviationPercentage > 25) {
211
+ anomalies.push({
212
+ timestamp: dataPoints[i].timestamp,
213
+ actualValue: currentValue,
214
+ expectedValue: seasonalBaseline,
215
+ deviation: seasonalDeviation,
216
+ deviationPercentage: seasonalDeviationPercentage,
217
+ severity: this.calculateSeverity(seasonalDeviationPercentage, 25),
218
+ confidence: 80,
219
+ type: "SEASONAL_ANOMALY",
220
+ description: `Unusual seasonal pattern: ${seasonalDeviationPercentage.toFixed(1)}% deviation from same period last cycle`,
221
+ potentialCauses: this.generatePotentialCauses("SEASONAL_ANOMALY", seasonalDeviationPercentage)
222
+ });
223
+ }
224
+ }
225
+ return anomalies;
226
+ }
227
+ calculateMedian(values) {
228
+ const sorted = [...values].sort((a, b) => a - b);
229
+ const mid = Math.floor(sorted.length / 2);
230
+ return sorted.length % 2 === 0 ? (sorted[mid - 1] + sorted[mid]) / 2 : sorted[mid];
231
+ }
232
+ calculateMAD(values, median) {
233
+ const deviations = values.map((v) => Math.abs(v - median));
234
+ return this.calculateMedian(deviations);
235
+ }
236
+ calculateLinearTrend(values) {
237
+ const n = values.length;
238
+ const x = Array.from({ length: n }, (_, i) => i);
239
+ const sumX = x.reduce((a, b) => a + b, 0);
240
+ const sumY = values.reduce((a, b) => a + b, 0);
241
+ const sumXY = x.reduce((sum, xi, i) => sum + xi * values[i], 0);
242
+ const sumXX = x.reduce((sum, xi) => sum + xi * xi, 0);
243
+ const slope = (n * sumXY - sumX * sumY) / (n * sumXX - sumX * sumX);
244
+ return slope;
245
+ }
246
+ getSensitivityThreshold() {
247
+ switch (this.config.sensitivity) {
248
+ case "HIGH":
249
+ return 2.5;
250
+ case "MEDIUM":
251
+ return 3.5;
252
+ case "LOW":
253
+ return 4.5;
254
+ default:
255
+ return 3.5;
256
+ }
257
+ }
258
+ calculateSeverity(score, threshold) {
259
+ const ratio = score / threshold;
260
+ if (ratio > 3)
261
+ return "CRITICAL";
262
+ if (ratio > 2)
263
+ return "HIGH";
264
+ if (ratio > 1.5)
265
+ return "MEDIUM";
266
+ return "LOW";
267
+ }
268
+ generateAnomalyDescription(type, deviation, deviationPercentage) {
269
+ const direction = deviation > 0 ? "increase" : "decrease";
270
+ const magnitude = deviationPercentage > 100 ? "massive" : deviationPercentage > 50 ? "significant" : deviationPercentage > 25 ? "notable" : "minor";
271
+ return `${type.toLowerCase()} anomaly detected: ${magnitude} ${direction} of ${deviationPercentage.toFixed(1)}%`;
272
+ }
273
+ generatePotentialCauses(type, deviationPercentage) {
274
+ const causes = [];
275
+ switch (type) {
276
+ case "SPIKE":
277
+ causes.push("Increased resource usage or traffic");
278
+ causes.push("New service deployments or scaling events");
279
+ causes.push("Data transfer spikes or storage usage increases");
280
+ if (deviationPercentage > 50) {
281
+ causes.push("Potential security incident or DDoS attack");
282
+ causes.push("Misconfigured auto-scaling rules");
283
+ }
284
+ break;
285
+ case "DROP":
286
+ causes.push("Reduced usage or traffic patterns");
287
+ causes.push("Service shutdowns or downscaling");
288
+ causes.push("Resource optimization implementations");
289
+ if (deviationPercentage > 50) {
290
+ causes.push("Service outages or failures");
291
+ causes.push("Billing or account issues");
292
+ }
293
+ break;
294
+ case "TREND_CHANGE":
295
+ causes.push("Business growth or contraction");
296
+ causes.push("Architectural changes or migrations");
297
+ causes.push("New feature rollouts or service changes");
298
+ causes.push("Seasonal business pattern shifts");
299
+ break;
300
+ case "SEASONAL_ANOMALY":
301
+ causes.push("Unusual business events or promotions");
302
+ causes.push("Holiday pattern deviations");
303
+ causes.push("Market or economic factors");
304
+ causes.push("Competitor actions or market changes");
305
+ break;
306
+ }
307
+ return causes;
308
+ }
309
+ consolidateAnomalies(anomalies) {
310
+ const groupedAnomalies = /* @__PURE__ */ new Map();
311
+ anomalies.forEach((anomaly) => {
312
+ const key = anomaly.timestamp;
313
+ if (!groupedAnomalies.has(key)) {
314
+ groupedAnomalies.set(key, []);
315
+ }
316
+ groupedAnomalies.get(key).push(anomaly);
317
+ });
318
+ const consolidated = [];
319
+ groupedAnomalies.forEach((group) => {
320
+ const sorted = group.sort((a, b) => {
321
+ const severityOrder = { "CRITICAL": 4, "HIGH": 3, "MEDIUM": 2, "LOW": 1 };
322
+ if (severityOrder[a.severity] !== severityOrder[b.severity]) {
323
+ return severityOrder[b.severity] - severityOrder[a.severity];
324
+ }
325
+ return b.confidence - a.confidence;
326
+ });
327
+ consolidated.push(sorted[0]);
328
+ });
329
+ return consolidated.sort((a, b) => new Date(a.timestamp).getTime() - new Date(b.timestamp).getTime());
330
+ }
331
+ };
332
+ var CostAnalyticsEngine = class {
333
+ constructor(config) {
334
+ this.anomalyDetector = new AnomalyDetector(config);
335
+ }
336
+ analyzeProvider(provider, costData, serviceData) {
337
+ const analytics = {
338
+ provider,
339
+ analysisDate: (/* @__PURE__ */ new Date()).toISOString(),
340
+ overallAnomalies: this.anomalyDetector.detectAnomalies(costData),
341
+ serviceAnomalies: {},
342
+ insights: this.generateInsights(costData),
343
+ recommendations: this.generateRecommendations(costData)
344
+ };
345
+ if (serviceData) {
346
+ Object.entries(serviceData).forEach(([service, data]) => {
347
+ analytics.serviceAnomalies[service] = this.anomalyDetector.detectAnomalies(data);
348
+ });
349
+ }
350
+ return analytics;
351
+ }
352
+ generateInsights(costData) {
353
+ const insights = [];
354
+ const values = costData.map((dp) => dp.value);
355
+ if (values.length < 7)
356
+ return insights;
357
+ const latest = values[values.length - 1];
358
+ const weekAgo = values[values.length - 7];
359
+ const monthAgo = values.length > 30 ? values[values.length - 30] : values[0];
360
+ const weekGrowth = weekAgo > 0 ? (latest - weekAgo) / weekAgo * 100 : 0;
361
+ const monthGrowth = monthAgo > 0 ? (latest - monthAgo) / monthAgo * 100 : 0;
362
+ if (Math.abs(weekGrowth) > 15) {
363
+ insights.push(`Significant week-over-week cost ${weekGrowth > 0 ? "increase" : "decrease"} of ${Math.abs(weekGrowth).toFixed(1)}%`);
364
+ }
365
+ if (Math.abs(monthGrowth) > 25) {
366
+ insights.push(`Notable month-over-month cost ${monthGrowth > 0 ? "growth" : "reduction"} of ${Math.abs(monthGrowth).toFixed(1)}%`);
367
+ }
368
+ const volatility = this.calculateVolatility(values);
369
+ if (volatility > 0.3) {
370
+ insights.push(`High cost volatility detected (${(volatility * 100).toFixed(1)}%) - consider investigating irregular spending patterns`);
371
+ }
372
+ return insights;
373
+ }
374
+ generateRecommendations(costData) {
375
+ const recommendations = [];
376
+ const values = costData.map((dp) => dp.value);
377
+ const volatility = this.calculateVolatility(values);
378
+ const trend = this.anomalyDetector["calculateLinearTrend"](values.slice(-14));
379
+ if (volatility > 0.2) {
380
+ recommendations.push("Implement cost budgets and alerts to better track spending variations");
381
+ recommendations.push("Consider using reserved instances or savings plans for more predictable costs");
382
+ }
383
+ if (trend > 0.1) {
384
+ recommendations.push("Cost trend is increasing - review recent resource additions and scaling policies");
385
+ recommendations.push("Consider implementing automated cost optimization tools");
386
+ }
387
+ return recommendations;
388
+ }
389
+ calculateVolatility(values) {
390
+ if (values.length < 2)
391
+ return 0;
392
+ const mean = values.reduce((a, b) => a + b, 0) / values.length;
393
+ const variance = values.reduce((sum, val) => sum + Math.pow(val - mean, 2), 0) / values.length;
394
+ const stdDev = Math.sqrt(variance);
395
+ return mean > 0 ? stdDev / mean : 0;
396
+ }
397
+ };
398
+
399
+ // src/providers/aws.ts
400
+ var AWSProvider = class extends CloudProviderAdapter {
401
+ constructor(config) {
402
+ super(config);
403
+ }
404
+ getCredentials() {
405
+ return this.config.credentials;
406
+ }
407
+ getRegion() {
408
+ return this.config.region || "us-east-1";
409
+ }
410
+ async validateCredentials() {
411
+ try {
412
+ const sts = new STSClient({
413
+ credentials: this.getCredentials(),
414
+ region: this.getRegion()
415
+ });
416
+ await sts.send(new GetCallerIdentityCommand({}));
417
+ return true;
418
+ } catch {
419
+ return false;
420
+ }
421
+ }
422
+ async getAccountInfo() {
423
+ var _a;
424
+ showSpinner("Getting AWS account information");
425
+ try {
426
+ const iam = new IAMClient({
427
+ credentials: this.getCredentials(),
428
+ region: this.getRegion()
429
+ });
430
+ const accountAliases = await iam.send(new ListAccountAliasesCommand({}));
431
+ const foundAlias = (_a = accountAliases == null ? void 0 : accountAliases.AccountAliases) == null ? void 0 : _a[0];
432
+ if (foundAlias) {
433
+ return {
434
+ id: foundAlias,
435
+ name: foundAlias,
436
+ provider: "aws" /* AWS */
437
+ };
438
+ }
439
+ const sts = new STSClient({
440
+ credentials: this.getCredentials(),
441
+ region: this.getRegion()
442
+ });
443
+ const accountInfo = await sts.send(new GetCallerIdentityCommand({}));
444
+ return {
445
+ id: accountInfo.Account || "unknown",
446
+ name: accountInfo.Account || "unknown",
447
+ provider: "aws" /* AWS */
448
+ };
449
+ } catch (error) {
450
+ throw new Error(`Failed to get AWS account information: ${error.message}`);
451
+ }
452
+ }
453
+ async getRawCostData() {
454
+ var _a, _b, _c, _d;
455
+ showSpinner("Getting AWS pricing data");
456
+ try {
457
+ const costExplorer = new CostExplorerClient({
458
+ credentials: this.getCredentials(),
459
+ region: this.getRegion()
460
+ });
461
+ const endDate = dayjs().subtract(1, "day");
462
+ const startDate = endDate.subtract(65, "day");
463
+ const pricingData = await costExplorer.send(new GetCostAndUsageCommand({
464
+ TimePeriod: {
465
+ Start: startDate.format("YYYY-MM-DD"),
466
+ End: endDate.format("YYYY-MM-DD")
467
+ },
468
+ Granularity: "DAILY",
469
+ Filter: {
470
+ Not: {
471
+ Dimensions: {
472
+ Key: "RECORD_TYPE",
473
+ Values: ["Credit", "Refund", "Upfront", "Support"]
474
+ }
475
+ }
476
+ },
477
+ Metrics: ["UnblendedCost"],
478
+ GroupBy: [
479
+ {
480
+ Type: "DIMENSION",
481
+ Key: "SERVICE"
482
+ }
483
+ ]
484
+ }));
485
+ const costByService = {};
486
+ for (const day of pricingData.ResultsByTime || []) {
487
+ for (const group of day.Groups || []) {
488
+ const serviceName = (_a = group.Keys) == null ? void 0 : _a[0];
489
+ const cost = (_c = (_b = group.Metrics) == null ? void 0 : _b.UnblendedCost) == null ? void 0 : _c.Amount;
490
+ const costDate = (_d = day.TimePeriod) == null ? void 0 : _d.End;
491
+ if (serviceName && cost && costDate) {
492
+ costByService[serviceName] = costByService[serviceName] || {};
493
+ costByService[serviceName][costDate] = parseFloat(cost);
494
+ }
495
+ }
496
+ }
497
+ return costByService;
498
+ } catch (error) {
499
+ throw new Error(`Failed to get AWS cost data: ${error.message}`);
500
+ }
501
+ }
502
+ async getCostBreakdown() {
503
+ const rawCostData = await this.getRawCostData();
504
+ return this.calculateServiceTotals(rawCostData);
505
+ }
506
+ async getResourceInventory(filters) {
507
+ showSpinner("Discovering AWS resources");
508
+ const regions = (filters == null ? void 0 : filters.regions) || [this.config.region || "us-east-1"];
509
+ const resourceTypes = (filters == null ? void 0 : filters.resourceTypes) || Object.values(ResourceType);
510
+ const includeCosts = (filters == null ? void 0 : filters.includeCosts) || false;
511
+ const inventory = {
512
+ provider: "aws" /* AWS */,
513
+ region: regions.join(", "),
514
+ totalResources: 0,
515
+ resourcesByType: {
516
+ ["compute" /* COMPUTE */]: 0,
517
+ ["storage" /* STORAGE */]: 0,
518
+ ["database" /* DATABASE */]: 0,
519
+ ["network" /* NETWORK */]: 0,
520
+ ["security" /* SECURITY */]: 0,
521
+ ["serverless" /* SERVERLESS */]: 0,
522
+ ["container" /* CONTAINER */]: 0,
523
+ ["analytics" /* ANALYTICS */]: 0
524
+ },
525
+ totalCost: 0,
526
+ resources: {
527
+ compute: [],
528
+ storage: [],
529
+ database: [],
530
+ network: [],
531
+ security: [],
532
+ serverless: [],
533
+ container: [],
534
+ analytics: []
535
+ },
536
+ lastUpdated: /* @__PURE__ */ new Date()
537
+ };
538
+ for (const region of regions) {
539
+ try {
540
+ if (resourceTypes.includes("compute" /* COMPUTE */)) {
541
+ const ec2Resources = await this.discoverEC2Instances(region, includeCosts);
542
+ inventory.resources.compute.push(...ec2Resources);
543
+ inventory.resourcesByType["compute" /* COMPUTE */] += ec2Resources.length;
544
+ }
545
+ if (resourceTypes.includes("storage" /* STORAGE */)) {
546
+ const s3Resources = await this.discoverS3Buckets(region, includeCosts);
547
+ const ebsResources = await this.discoverEBSVolumes(region, includeCosts);
548
+ inventory.resources.storage.push(...s3Resources, ...ebsResources);
549
+ inventory.resourcesByType["storage" /* STORAGE */] += s3Resources.length + ebsResources.length;
550
+ }
551
+ if (resourceTypes.includes("database" /* DATABASE */)) {
552
+ const rdsResources = await this.discoverRDSInstances(region, includeCosts);
553
+ inventory.resources.database.push(...rdsResources);
554
+ inventory.resourcesByType["database" /* DATABASE */] += rdsResources.length;
555
+ }
556
+ if (resourceTypes.includes("serverless" /* SERVERLESS */)) {
557
+ const lambdaResources = await this.discoverLambdaFunctions(region, includeCosts);
558
+ inventory.resources.serverless.push(...lambdaResources);
559
+ inventory.resourcesByType["serverless" /* SERVERLESS */] += lambdaResources.length;
560
+ }
561
+ if (resourceTypes.includes("network" /* NETWORK */)) {
562
+ const vpcResources = await this.discoverVPCs(region, includeCosts);
563
+ const subnetResources = await this.discoverSubnets(region, includeCosts);
564
+ inventory.resources.network.push(...vpcResources, ...subnetResources);
565
+ inventory.resourcesByType["network" /* NETWORK */] += vpcResources.length + subnetResources.length;
566
+ }
567
+ } catch (error) {
568
+ console.warn(`Failed to discover resources in region ${region}: ${error.message}`);
569
+ }
570
+ }
571
+ inventory.totalResources = Object.values(inventory.resourcesByType).reduce((sum, count) => sum + count, 0);
572
+ if (includeCosts) {
573
+ inventory.totalCost = Object.values(inventory.resources).flat().reduce((sum, resource) => sum + (resource.costToDate || 0), 0);
574
+ }
575
+ return inventory;
576
+ }
577
+ async discoverEC2Instances(region, includeCosts) {
578
+ var _a, _b, _c, _d, _e, _f, _g, _h;
579
+ try {
580
+ const ec2Client = new EC2Client({
581
+ credentials: this.getCredentials(),
582
+ region
583
+ });
584
+ const command = new DescribeInstancesCommand({});
585
+ const result = await ec2Client.send(command);
586
+ const instances = [];
587
+ for (const reservation of result.Reservations || []) {
588
+ for (const instance of reservation.Instances || []) {
589
+ const ec2Instance = {
590
+ id: instance.InstanceId || "",
591
+ name: ((_b = (_a = instance.Tags) == null ? void 0 : _a.find((tag) => tag.Key === "Name")) == null ? void 0 : _b.Value) || instance.InstanceId || "",
592
+ state: ((_c = instance.State) == null ? void 0 : _c.Name) || "unknown",
593
+ region,
594
+ tags: (_d = instance.Tags) == null ? void 0 : _d.reduce((acc, tag) => {
595
+ if (tag.Key && tag.Value)
596
+ acc[tag.Key] = tag.Value;
597
+ return acc;
598
+ }, {}),
599
+ createdAt: instance.LaunchTime || /* @__PURE__ */ new Date(),
600
+ provider: "aws" /* AWS */,
601
+ instanceType: instance.InstanceType,
602
+ cpu: this.getCpuCountForInstanceType(instance.InstanceType),
603
+ memory: this.getMemoryForInstanceType(instance.InstanceType),
604
+ platform: instance.Platform || "linux",
605
+ instanceId: instance.InstanceId || "",
606
+ imageId: instance.ImageId || "",
607
+ keyName: instance.KeyName,
608
+ securityGroups: ((_e = instance.SecurityGroups) == null ? void 0 : _e.map((sg) => sg.GroupId || "")) || [],
609
+ subnetId: instance.SubnetId || "",
610
+ vpcId: instance.VpcId || "",
611
+ publicDnsName: instance.PublicDnsName,
612
+ privateDnsName: instance.PrivateDnsName,
613
+ monitoring: ((_f = instance.Monitoring) == null ? void 0 : _f.State) === "enabled",
614
+ placement: {
615
+ availabilityZone: ((_g = instance.Placement) == null ? void 0 : _g.AvailabilityZone) || "",
616
+ groupName: (_h = instance.Placement) == null ? void 0 : _h.GroupName
617
+ },
618
+ publicIp: instance.PublicIpAddress,
619
+ privateIp: instance.PrivateIpAddress
620
+ };
621
+ if (includeCosts) {
622
+ ec2Instance.costToDate = await this.getResourceCosts(instance.InstanceId || "");
623
+ }
624
+ instances.push(ec2Instance);
625
+ }
626
+ }
627
+ return instances;
628
+ } catch (error) {
629
+ console.warn(`Failed to discover EC2 instances in ${region}: ${error.message}`);
630
+ return [];
631
+ }
632
+ }
633
+ async discoverS3Buckets(region, includeCosts) {
634
+ try {
635
+ const s3Client = new S3Client({
636
+ credentials: this.getCredentials(),
637
+ region
638
+ });
639
+ const command = new ListBucketsCommand({});
640
+ const result = await s3Client.send(command);
641
+ const buckets = [];
642
+ for (const bucket of result.Buckets || []) {
643
+ if (!bucket.Name)
644
+ continue;
645
+ const s3Bucket = {
646
+ id: bucket.Name,
647
+ name: bucket.Name,
648
+ state: "active",
649
+ region,
650
+ createdAt: bucket.CreationDate || /* @__PURE__ */ new Date(),
651
+ provider: "aws" /* AWS */,
652
+ sizeGB: 0,
653
+ // Would need additional API calls to get actual size
654
+ storageType: "S3",
655
+ bucketName: bucket.Name
656
+ };
657
+ if (includeCosts) {
658
+ s3Bucket.costToDate = await this.getResourceCosts(bucket.Name);
659
+ }
660
+ buckets.push(s3Bucket);
661
+ }
662
+ return buckets;
663
+ } catch (error) {
664
+ console.warn(`Failed to discover S3 buckets: ${error.message}`);
665
+ return [];
666
+ }
667
+ }
668
+ async discoverEBSVolumes(region, includeCosts) {
669
+ var _a, _b, _c, _d;
670
+ try {
671
+ const ec2Client = new EC2Client({
672
+ credentials: this.getCredentials(),
673
+ region
674
+ });
675
+ const command = new DescribeVolumesCommand({});
676
+ const result = await ec2Client.send(command);
677
+ const volumes = [];
678
+ for (const volume of result.Volumes || []) {
679
+ if (!volume.VolumeId)
680
+ continue;
681
+ const ebsVolume = {
682
+ id: volume.VolumeId,
683
+ name: ((_b = (_a = volume.Tags) == null ? void 0 : _a.find((tag) => tag.Key === "Name")) == null ? void 0 : _b.Value) || volume.VolumeId,
684
+ state: volume.State || "unknown",
685
+ region,
686
+ tags: (_c = volume.Tags) == null ? void 0 : _c.reduce((acc, tag) => {
687
+ if (tag.Key && tag.Value)
688
+ acc[tag.Key] = tag.Value;
689
+ return acc;
690
+ }, {}),
691
+ createdAt: volume.CreateTime || /* @__PURE__ */ new Date(),
692
+ provider: "aws" /* AWS */,
693
+ sizeGB: volume.Size || 0,
694
+ storageType: volume.VolumeType || "gp2",
695
+ encrypted: volume.Encrypted,
696
+ volumeId: volume.VolumeId,
697
+ volumeType: volume.VolumeType || "gp2",
698
+ iops: volume.Iops,
699
+ throughput: volume.Throughput,
700
+ attachments: (_d = volume.Attachments) == null ? void 0 : _d.map((attachment) => ({
701
+ instanceId: attachment.InstanceId || "",
702
+ device: attachment.Device || ""
703
+ })),
704
+ snapshotId: volume.SnapshotId
705
+ };
706
+ if (includeCosts) {
707
+ ebsVolume.costToDate = await this.getResourceCosts(volume.VolumeId);
708
+ }
709
+ volumes.push(ebsVolume);
710
+ }
711
+ return volumes;
712
+ } catch (error) {
713
+ console.warn(`Failed to discover EBS volumes in ${region}: ${error.message}`);
714
+ return [];
715
+ }
716
+ }
717
+ async discoverRDSInstances(region, includeCosts) {
718
+ var _a, _b;
719
+ try {
720
+ const rdsClient = new RDSClient({
721
+ credentials: this.getCredentials(),
722
+ region
723
+ });
724
+ const command = new DescribeDBInstancesCommand({});
725
+ const result = await rdsClient.send(command);
726
+ const instances = [];
727
+ for (const dbInstance of result.DBInstances || []) {
728
+ if (!dbInstance.DBInstanceIdentifier)
729
+ continue;
730
+ const rdsInstance = {
731
+ id: dbInstance.DBInstanceIdentifier,
732
+ name: dbInstance.DBName || dbInstance.DBInstanceIdentifier,
733
+ state: dbInstance.DBInstanceStatus || "unknown",
734
+ region,
735
+ createdAt: dbInstance.InstanceCreateTime || /* @__PURE__ */ new Date(),
736
+ provider: "aws" /* AWS */,
737
+ engine: dbInstance.Engine || "",
738
+ version: dbInstance.EngineVersion || "",
739
+ instanceClass: dbInstance.DBInstanceClass,
740
+ storageGB: dbInstance.AllocatedStorage,
741
+ multiAZ: dbInstance.MultiAZ,
742
+ dbInstanceIdentifier: dbInstance.DBInstanceIdentifier,
743
+ dbName: dbInstance.DBName,
744
+ masterUsername: dbInstance.MasterUsername || "",
745
+ endpoint: (_a = dbInstance.Endpoint) == null ? void 0 : _a.Address,
746
+ port: (_b = dbInstance.Endpoint) == null ? void 0 : _b.Port,
747
+ availabilityZone: dbInstance.AvailabilityZone,
748
+ backupRetentionPeriod: dbInstance.BackupRetentionPeriod,
749
+ storageEncrypted: dbInstance.StorageEncrypted
750
+ };
751
+ if (includeCosts) {
752
+ rdsInstance.costToDate = await this.getResourceCosts(dbInstance.DBInstanceIdentifier);
753
+ }
754
+ instances.push(rdsInstance);
755
+ }
756
+ return instances;
757
+ } catch (error) {
758
+ console.warn(`Failed to discover RDS instances in ${region}: ${error.message}`);
759
+ return [];
760
+ }
761
+ }
762
+ async discoverLambdaFunctions(region, includeCosts) {
763
+ try {
764
+ const lambdaClient = new LambdaClient({
765
+ credentials: this.getCredentials(),
766
+ region
767
+ });
768
+ const command = new ListFunctionsCommand({});
769
+ const result = await lambdaClient.send(command);
770
+ const functions = [];
771
+ for (const func of result.Functions || []) {
772
+ if (!func.FunctionName)
773
+ continue;
774
+ const lambdaFunction = {
775
+ id: func.FunctionArn || func.FunctionName,
776
+ name: func.FunctionName,
777
+ state: func.State || "unknown",
778
+ region,
779
+ createdAt: /* @__PURE__ */ new Date(),
780
+ // Lambda doesn't provide creation time in list
781
+ provider: "aws" /* AWS */,
782
+ functionName: func.FunctionName,
783
+ runtime: func.Runtime || "",
784
+ handler: func.Handler || "",
785
+ codeSize: func.CodeSize || 0,
786
+ timeout: func.Timeout || 0,
787
+ memorySize: func.MemorySize || 0,
788
+ lastModified: new Date(func.LastModified || ""),
789
+ version: func.Version || ""
790
+ };
791
+ if (includeCosts) {
792
+ lambdaFunction.costToDate = await this.getResourceCosts(func.FunctionName);
793
+ }
794
+ functions.push(lambdaFunction);
795
+ }
796
+ return functions;
797
+ } catch (error) {
798
+ console.warn(`Failed to discover Lambda functions in ${region}: ${error.message}`);
799
+ return [];
800
+ }
801
+ }
802
+ async discoverVPCs(region, includeCosts) {
803
+ var _a, _b, _c;
804
+ try {
805
+ const ec2Client = new EC2Client({
806
+ credentials: this.getCredentials(),
807
+ region
808
+ });
809
+ const command = new DescribeVpcsCommand({});
810
+ const result = await ec2Client.send(command);
811
+ const vpcs = [];
812
+ for (const vpc of result.Vpcs || []) {
813
+ if (!vpc.VpcId)
814
+ continue;
815
+ const awsVpc = {
816
+ id: vpc.VpcId,
817
+ name: ((_b = (_a = vpc.Tags) == null ? void 0 : _a.find((tag) => tag.Key === "Name")) == null ? void 0 : _b.Value) || vpc.VpcId,
818
+ state: vpc.State || "unknown",
819
+ region,
820
+ tags: (_c = vpc.Tags) == null ? void 0 : _c.reduce((acc, tag) => {
821
+ if (tag.Key && tag.Value)
822
+ acc[tag.Key] = tag.Value;
823
+ return acc;
824
+ }, {}),
825
+ createdAt: /* @__PURE__ */ new Date(),
826
+ // VPC doesn't provide creation time
827
+ provider: "aws" /* AWS */,
828
+ vpcId: vpc.VpcId,
829
+ cidrBlock: vpc.CidrBlock || "",
830
+ dhcpOptionsId: vpc.DhcpOptionsId || "",
831
+ isDefault: vpc.IsDefault || false
832
+ };
833
+ if (includeCosts) {
834
+ awsVpc.costToDate = await this.getResourceCosts(vpc.VpcId);
835
+ }
836
+ vpcs.push(awsVpc);
837
+ }
838
+ return vpcs;
839
+ } catch (error) {
840
+ console.warn(`Failed to discover VPCs in ${region}: ${error.message}`);
841
+ return [];
842
+ }
843
+ }
844
+ async discoverSubnets(region, includeCosts) {
845
+ var _a, _b, _c;
846
+ try {
847
+ const ec2Client = new EC2Client({
848
+ credentials: this.getCredentials(),
849
+ region
850
+ });
851
+ const command = new DescribeSubnetsCommand({});
852
+ const result = await ec2Client.send(command);
853
+ const subnets = [];
854
+ for (const subnet of result.Subnets || []) {
855
+ if (!subnet.SubnetId)
856
+ continue;
857
+ const awsSubnet = {
858
+ id: subnet.SubnetId,
859
+ name: ((_b = (_a = subnet.Tags) == null ? void 0 : _a.find((tag) => tag.Key === "Name")) == null ? void 0 : _b.Value) || subnet.SubnetId,
860
+ state: subnet.State || "unknown",
861
+ region,
862
+ tags: (_c = subnet.Tags) == null ? void 0 : _c.reduce((acc, tag) => {
863
+ if (tag.Key && tag.Value)
864
+ acc[tag.Key] = tag.Value;
865
+ return acc;
866
+ }, {}),
867
+ createdAt: /* @__PURE__ */ new Date(),
868
+ // Subnet doesn't provide creation time
869
+ provider: "aws" /* AWS */,
870
+ subnetId: subnet.SubnetId,
871
+ vpcId: subnet.VpcId || "",
872
+ cidrBlock: subnet.CidrBlock || "",
873
+ availableIpAddressCount: subnet.AvailableIpAddressCount || 0,
874
+ availabilityZone: subnet.AvailabilityZone || "",
875
+ mapPublicIpOnLaunch: subnet.MapPublicIpOnLaunch || false
876
+ };
877
+ if (includeCosts) {
878
+ awsSubnet.costToDate = await this.getResourceCosts(subnet.SubnetId);
879
+ }
880
+ subnets.push(awsSubnet);
881
+ }
882
+ return subnets;
883
+ } catch (error) {
884
+ console.warn(`Failed to discover Subnets in ${region}: ${error.message}`);
885
+ return [];
886
+ }
887
+ }
888
+ async getResourceCosts(resourceId) {
889
+ return 0;
890
+ }
891
+ async getOptimizationRecommendations() {
892
+ const recommendations = [];
893
+ try {
894
+ recommendations.push(
895
+ "Consider using Reserved Instances for long-running EC2 instances to save up to 72%",
896
+ "Enable S3 Intelligent Tiering for automatic cost optimization",
897
+ "Review underutilized RDS instances and consider right-sizing",
898
+ "Implement lifecycle policies for EBS snapshots older than 30 days",
899
+ "Consider using Spot Instances for fault-tolerant workloads"
900
+ );
901
+ } catch (error) {
902
+ console.warn(`Failed to generate optimization recommendations: ${error.message}`);
903
+ }
904
+ return recommendations;
905
+ }
906
+ async getBudgets() {
907
+ var _a, _b, _c, _d, _e, _f, _g, _h, _i, _j, _k, _l;
908
+ showSpinner("Getting AWS budgets");
909
+ try {
910
+ const budgetsClient = new BudgetsClient({
911
+ credentials: this.getCredentials(),
912
+ region: this.getRegion()
913
+ });
914
+ const sts = new STSClient({
915
+ credentials: this.getCredentials(),
916
+ region: this.getRegion()
917
+ });
918
+ const accountInfo = await sts.send(new GetCallerIdentityCommand({}));
919
+ const accountId = accountInfo.Account;
920
+ if (!accountId) {
921
+ throw new Error("Unable to determine AWS account ID");
922
+ }
923
+ const budgetsResponse = await budgetsClient.send(new DescribeBudgetsCommand({
924
+ AccountId: accountId
925
+ }));
926
+ const budgets = [];
927
+ for (const budget of budgetsResponse.Budgets || []) {
928
+ if (!budget.BudgetName || !budget.BudgetLimit)
929
+ continue;
930
+ const budgetInfo = {
931
+ budgetName: budget.BudgetName,
932
+ budgetLimit: parseFloat(budget.BudgetLimit.Amount || "0"),
933
+ actualSpend: parseFloat(((_b = (_a = budget.CalculatedSpend) == null ? void 0 : _a.ActualSpend) == null ? void 0 : _b.Amount) || "0"),
934
+ forecastedSpend: parseFloat(((_d = (_c = budget.CalculatedSpend) == null ? void 0 : _c.ForecastedSpend) == null ? void 0 : _d.Amount) || "0"),
935
+ timeUnit: budget.TimeUnit || "MONTHLY",
936
+ timePeriod: {
937
+ start: typeof ((_e = budget.TimePeriod) == null ? void 0 : _e.Start) === "string" ? (_f = budget.TimePeriod) == null ? void 0 : _f.Start : ((_g = budget.TimePeriod) == null ? void 0 : _g.Start) instanceof Date ? (_h = budget.TimePeriod) == null ? void 0 : _h.Start.toISOString() : "",
938
+ end: typeof ((_i = budget.TimePeriod) == null ? void 0 : _i.End) === "string" ? (_j = budget.TimePeriod) == null ? void 0 : _j.End : ((_k = budget.TimePeriod) == null ? void 0 : _k.End) instanceof Date ? (_l = budget.TimePeriod) == null ? void 0 : _l.End.toISOString() : ""
939
+ },
940
+ budgetType: budget.BudgetType || "COST",
941
+ status: this.determineBudgetStatus(budget),
942
+ thresholds: this.parseBudgetThresholds(budget),
943
+ costFilters: this.parseCostFilters(budget)
944
+ };
945
+ budgets.push(budgetInfo);
946
+ }
947
+ return budgets;
948
+ } catch (error) {
949
+ console.warn(`Failed to get AWS budgets: ${error.message}`);
950
+ return [];
951
+ }
952
+ }
953
+ async getBudgetAlerts() {
954
+ const budgets = await this.getBudgets();
955
+ const alerts = [];
956
+ for (const budget of budgets) {
957
+ const percentageUsed = budget.actualSpend / budget.budgetLimit * 100;
958
+ for (const threshold of budget.thresholds) {
959
+ let isExceeded = false;
960
+ let currentValue = 0;
961
+ if (threshold.thresholdType === "PERCENTAGE") {
962
+ currentValue = percentageUsed;
963
+ if (threshold.notificationType === "ACTUAL") {
964
+ isExceeded = percentageUsed >= threshold.threshold;
965
+ } else if (threshold.notificationType === "FORECASTED" && budget.forecastedSpend) {
966
+ const forecastedPercentage = budget.forecastedSpend / budget.budgetLimit * 100;
967
+ isExceeded = forecastedPercentage >= threshold.threshold;
968
+ currentValue = forecastedPercentage;
969
+ }
970
+ } else {
971
+ currentValue = budget.actualSpend;
972
+ if (threshold.notificationType === "ACTUAL") {
973
+ isExceeded = budget.actualSpend >= threshold.threshold;
974
+ } else if (threshold.notificationType === "FORECASTED" && budget.forecastedSpend) {
975
+ isExceeded = budget.forecastedSpend >= threshold.threshold;
976
+ currentValue = budget.forecastedSpend;
977
+ }
978
+ }
979
+ if (isExceeded) {
980
+ const alert = {
981
+ budgetName: budget.budgetName,
982
+ alertType: threshold.notificationType === "FORECASTED" ? "FORECAST_EXCEEDED" : "THRESHOLD_EXCEEDED",
983
+ currentSpend: budget.actualSpend,
984
+ budgetLimit: budget.budgetLimit,
985
+ threshold: threshold.threshold,
986
+ percentageUsed,
987
+ timeRemaining: this.calculateTimeRemaining(budget.timePeriod),
988
+ severity: this.determineSeverity(percentageUsed),
989
+ message: this.generateAlertMessage(budget, threshold, percentageUsed)
990
+ };
991
+ alerts.push(alert);
992
+ }
993
+ }
994
+ }
995
+ return alerts;
996
+ }
997
+ async getCostTrendAnalysis(months = 6) {
998
+ var _a, _b, _c, _d, _e;
999
+ showSpinner("Analyzing cost trends");
1000
+ try {
1001
+ const costExplorer = new CostExplorerClient({
1002
+ credentials: this.getCredentials(),
1003
+ region: this.getRegion()
1004
+ });
1005
+ const endDate = dayjs();
1006
+ const startDate = endDate.subtract(months, "month");
1007
+ const monthlyData = await costExplorer.send(new GetCostAndUsageCommand({
1008
+ TimePeriod: {
1009
+ Start: startDate.format("YYYY-MM-DD"),
1010
+ End: endDate.format("YYYY-MM-DD")
1011
+ },
1012
+ Granularity: "MONTHLY",
1013
+ Metrics: ["UnblendedCost"],
1014
+ GroupBy: [
1015
+ {
1016
+ Type: "DIMENSION",
1017
+ Key: "SERVICE"
1018
+ }
1019
+ ]
1020
+ }));
1021
+ const monthlyCosts = [];
1022
+ const monthlyBreakdown = [];
1023
+ const serviceTrends = {};
1024
+ let totalCost = 0;
1025
+ for (const result of monthlyData.ResultsByTime || []) {
1026
+ const period = ((_a = result.TimePeriod) == null ? void 0 : _a.Start) || "";
1027
+ const monthCost = ((_c = (_b = result.Total) == null ? void 0 : _b.UnblendedCost) == null ? void 0 : _c.Amount) ? parseFloat(result.Total.UnblendedCost.Amount) : 0;
1028
+ monthlyCosts.push(monthCost);
1029
+ totalCost += monthCost;
1030
+ const services = {};
1031
+ (_d = result.Groups) == null ? void 0 : _d.forEach((group) => {
1032
+ var _a2, _b2, _c2;
1033
+ const serviceName = ((_a2 = group.Keys) == null ? void 0 : _a2[0]) || "Unknown";
1034
+ const cost = parseFloat(((_c2 = (_b2 = group.Metrics) == null ? void 0 : _b2.UnblendedCost) == null ? void 0 : _c2.Amount) || "0");
1035
+ services[serviceName] = cost;
1036
+ if (!serviceTrends[serviceName]) {
1037
+ serviceTrends[serviceName] = [];
1038
+ }
1039
+ serviceTrends[serviceName].push(cost);
1040
+ });
1041
+ monthlyBreakdown.push({
1042
+ month: period,
1043
+ cost: monthCost,
1044
+ services
1045
+ });
1046
+ }
1047
+ const averageDailyCost = totalCost / (months * 30);
1048
+ const projectedMonthlyCost = monthlyCosts.length > 0 ? monthlyCosts[monthlyCosts.length - 1] : averageDailyCost * 30;
1049
+ let avgMonthOverMonthGrowth = 0;
1050
+ if (monthlyCosts.length > 1) {
1051
+ const growthRates = [];
1052
+ for (let i = 1; i < monthlyCosts.length; i++) {
1053
+ const growth = (monthlyCosts[i] - monthlyCosts[i - 1]) / monthlyCosts[i - 1] * 100;
1054
+ growthRates.push(growth);
1055
+ }
1056
+ avgMonthOverMonthGrowth = growthRates.reduce((a, b) => a + b, 0) / growthRates.length;
1057
+ }
1058
+ const costAnomalies = [];
1059
+ for (let i = 0; i < monthlyCosts.length; i++) {
1060
+ const monthCost = monthlyCosts[i];
1061
+ const period = ((_e = monthlyBreakdown[i]) == null ? void 0 : _e.month) || "";
1062
+ if (i > 0) {
1063
+ const prevMonthCost = monthlyCosts[i - 1];
1064
+ const monthOverMonthChange = (monthCost - prevMonthCost) / prevMonthCost * 100;
1065
+ if (Math.abs(monthOverMonthChange) > 25) {
1066
+ costAnomalies.push({
1067
+ date: period,
1068
+ deviation: Math.abs(monthOverMonthChange),
1069
+ severity: Math.abs(monthOverMonthChange) > 50 ? "CRITICAL" : "HIGH",
1070
+ description: `${monthOverMonthChange > 0 ? "Spike" : "Drop"} in monthly costs: ${monthOverMonthChange.toFixed(1)}% MoM change`
1071
+ });
1072
+ }
1073
+ }
1074
+ if (i > 2) {
1075
+ const avgOfPrevious = monthlyCosts.slice(0, i).reduce((a, b) => a + b, 0) / i;
1076
+ const deviation = (monthCost - avgOfPrevious) / avgOfPrevious * 100;
1077
+ if (Math.abs(deviation) > 30) {
1078
+ costAnomalies.push({
1079
+ date: period,
1080
+ deviation: Math.abs(deviation),
1081
+ severity: Math.abs(deviation) > 60 ? "CRITICAL" : "MEDIUM",
1082
+ description: `Cost ${deviation > 0 ? "above" : "below"} ${months}-month average by ${Math.abs(deviation).toFixed(1)}%`
1083
+ });
1084
+ }
1085
+ }
1086
+ }
1087
+ const serviceAnalysis = {};
1088
+ Object.entries(serviceTrends).forEach(([service, costs]) => {
1089
+ if (costs.length > 1) {
1090
+ const currentCost = costs[costs.length - 1];
1091
+ const previousCost = costs[costs.length - 2];
1092
+ const growthRate = (currentCost - previousCost) / previousCost * 100;
1093
+ const trend = Math.abs(growthRate) < 5 ? "stable" : growthRate > 0 ? "increasing" : "decreasing";
1094
+ serviceAnalysis[service] = {
1095
+ currentCost,
1096
+ growthRate,
1097
+ trend
1098
+ };
1099
+ }
1100
+ });
1101
+ const analyticsEngine = new CostAnalyticsEngine({
1102
+ sensitivity: "MEDIUM",
1103
+ lookbackPeriods: Math.min(14, monthlyBreakdown.length),
1104
+ seasonalityPeriods: 12
1105
+ // Monthly seasonality
1106
+ });
1107
+ const dataPoints = monthlyBreakdown.map((mb) => ({
1108
+ timestamp: mb.month,
1109
+ value: mb.cost
1110
+ }));
1111
+ const analytics = analyticsEngine.analyzeProvider("aws" /* AWS */, dataPoints);
1112
+ const enhancedAnomalies = [...costAnomalies];
1113
+ analytics.overallAnomalies.forEach((anomaly) => {
1114
+ enhancedAnomalies.push({
1115
+ date: anomaly.timestamp,
1116
+ deviation: anomaly.deviationPercentage,
1117
+ severity: anomaly.severity,
1118
+ description: `${anomaly.description} (${anomaly.confidence.toFixed(0)}% confidence)`
1119
+ });
1120
+ });
1121
+ return {
1122
+ totalCost,
1123
+ averageDailyCost,
1124
+ projectedMonthlyCost,
1125
+ avgMonthOverMonthGrowth,
1126
+ costAnomalies: enhancedAnomalies,
1127
+ monthlyBreakdown,
1128
+ serviceTrends: serviceAnalysis,
1129
+ forecastAccuracy: monthlyCosts.length > 3 ? this.calculateForecastAccuracy(monthlyCosts) : 0,
1130
+ analytics: {
1131
+ insights: analytics.insights,
1132
+ recommendations: analytics.recommendations,
1133
+ volatilityScore: this.calculateVolatility(monthlyCosts),
1134
+ trendStrength: this.calculateTrendStrength(monthlyCosts)
1135
+ }
1136
+ };
1137
+ } catch (error) {
1138
+ console.error("Failed to get AWS cost trend analysis:", error);
1139
+ return this.getMockTrendAnalysis(months);
1140
+ }
1141
+ }
1142
+ calculateForecastAccuracy(monthlyCosts) {
1143
+ if (monthlyCosts.length < 4)
1144
+ return 0;
1145
+ const n = monthlyCosts.length;
1146
+ const lastThreeActual = monthlyCosts.slice(-3);
1147
+ const trainData = monthlyCosts.slice(0, -3);
1148
+ const prediction = trainData.reduce((a, b) => a + b, 0) / trainData.length;
1149
+ const actualAvg = lastThreeActual.reduce((a, b) => a + b, 0) / lastThreeActual.length;
1150
+ const accuracy = Math.max(0, 100 - Math.abs(prediction - actualAvg) / actualAvg * 100);
1151
+ return Math.round(accuracy);
1152
+ }
1153
+ getMockTrendAnalysis(months) {
1154
+ const monthlyCosts = Array.from({ length: months }, (_, i) => {
1155
+ const baseCost = 1500 + Math.random() * 500;
1156
+ const trend = 1 + i * 0.02;
1157
+ const volatility = 0.8 + Math.random() * 0.4;
1158
+ return baseCost * trend * volatility;
1159
+ });
1160
+ const totalCost = monthlyCosts.reduce((sum, cost) => sum + cost, 0);
1161
+ const averageDailyCost = totalCost / (months * 30);
1162
+ const projectedMonthlyCost = monthlyCosts[monthlyCosts.length - 1];
1163
+ let avgMonthOverMonthGrowth = 0;
1164
+ if (monthlyCosts.length > 1) {
1165
+ const growthRates = [];
1166
+ for (let i = 1; i < monthlyCosts.length; i++) {
1167
+ const growth = (monthlyCosts[i] - monthlyCosts[i - 1]) / monthlyCosts[i - 1] * 100;
1168
+ growthRates.push(growth);
1169
+ }
1170
+ avgMonthOverMonthGrowth = growthRates.reduce((a, b) => a + b, 0) / growthRates.length;
1171
+ }
1172
+ const costAnomalies = [];
1173
+ if (months >= 3) {
1174
+ costAnomalies.push({
1175
+ date: new Date(Date.now() - 60 * 24 * 60 * 60 * 1e3).toISOString().split("T")[0],
1176
+ deviation: 35.2,
1177
+ severity: "MEDIUM",
1178
+ description: "Cost spike due to increased EC2 usage during Black Friday traffic"
1179
+ });
1180
+ costAnomalies.push({
1181
+ date: new Date(Date.now() - 90 * 24 * 60 * 60 * 1e3).toISOString().split("T")[0],
1182
+ deviation: 28.7,
1183
+ severity: "HIGH",
1184
+ description: "Month-over-month increase of 28.7% in data transfer costs"
1185
+ });
1186
+ }
1187
+ const mockServices = ["EC2-Instance", "S3", "RDS", "Lambda", "CloudFront"];
1188
+ const monthlyBreakdown = monthlyCosts.map((cost, i) => {
1189
+ const date = /* @__PURE__ */ new Date();
1190
+ date.setMonth(date.getMonth() - (months - i - 1));
1191
+ const services = {};
1192
+ mockServices.forEach((service) => {
1193
+ services[service] = cost * (0.1 + Math.random() * 0.3);
1194
+ });
1195
+ return {
1196
+ month: date.toISOString().split("T")[0],
1197
+ cost,
1198
+ services
1199
+ };
1200
+ });
1201
+ const serviceTrends = {
1202
+ "EC2-Instance": { currentCost: 850, growthRate: 12.3, trend: "increasing" },
1203
+ "S3": { currentCost: 125, growthRate: -5.2, trend: "decreasing" },
1204
+ "RDS": { currentCost: 420, growthRate: 2.1, trend: "stable" },
1205
+ "Lambda": { currentCost: 45, growthRate: 25.6, trend: "increasing" },
1206
+ "CloudFront": { currentCost: 78, growthRate: -1.8, trend: "stable" }
1207
+ };
1208
+ return {
1209
+ totalCost,
1210
+ averageDailyCost,
1211
+ projectedMonthlyCost,
1212
+ avgMonthOverMonthGrowth,
1213
+ costAnomalies,
1214
+ monthlyBreakdown,
1215
+ serviceTrends,
1216
+ forecastAccuracy: 87
1217
+ };
1218
+ }
1219
+ async getFinOpsRecommendations() {
1220
+ const recommendations = [
1221
+ {
1222
+ id: "aws-ri-ec2",
1223
+ type: "RESERVED_CAPACITY",
1224
+ title: "Purchase EC2 Reserved Instances",
1225
+ description: "Long-running EC2 instances can benefit from Reserved Instance pricing",
1226
+ potentialSavings: {
1227
+ amount: 500,
1228
+ percentage: 72,
1229
+ timeframe: "MONTHLY"
1230
+ },
1231
+ effort: "LOW",
1232
+ priority: "HIGH",
1233
+ implementationSteps: [
1234
+ "Analyze EC2 usage patterns over the past 30 days",
1235
+ "Identify instances running >75% of the time",
1236
+ "Purchase 1-year or 3-year Reserved Instances",
1237
+ "Monitor utilization and adjust as needed"
1238
+ ],
1239
+ tags: ["ec2", "reserved-instances", "cost-optimization"]
1240
+ },
1241
+ {
1242
+ id: "aws-s3-lifecycle",
1243
+ type: "COST_OPTIMIZATION",
1244
+ title: "Implement S3 Lifecycle Policies",
1245
+ description: "Automatically transition old S3 data to cheaper storage classes",
1246
+ potentialSavings: {
1247
+ amount: 150,
1248
+ percentage: 40,
1249
+ timeframe: "MONTHLY"
1250
+ },
1251
+ effort: "MEDIUM",
1252
+ priority: "MEDIUM",
1253
+ implementationSteps: [
1254
+ "Analyze S3 access patterns",
1255
+ "Create lifecycle policies for infrequently accessed data",
1256
+ "Transition to IA after 30 days, Glacier after 90 days",
1257
+ "Enable Intelligent Tiering for dynamic workloads"
1258
+ ],
1259
+ tags: ["s3", "lifecycle", "storage-optimization"]
1260
+ },
1261
+ {
1262
+ id: "aws-rightsizing",
1263
+ type: "RESOURCE_RIGHTSIZING",
1264
+ title: "Right-size Underutilized Resources",
1265
+ description: "Reduce costs by downsizing underutilized EC2 and RDS instances",
1266
+ potentialSavings: {
1267
+ amount: 300,
1268
+ percentage: 25,
1269
+ timeframe: "MONTHLY"
1270
+ },
1271
+ effort: "HIGH",
1272
+ priority: "HIGH",
1273
+ implementationSteps: [
1274
+ "Enable CloudWatch detailed monitoring",
1275
+ "Analyze CPU, memory, and network utilization",
1276
+ "Identify instances with <40% average utilization",
1277
+ "Plan maintenance windows for resizing",
1278
+ "Test application performance after changes"
1279
+ ],
1280
+ tags: ["rightsizing", "ec2", "rds", "performance-optimization"]
1281
+ }
1282
+ ];
1283
+ return recommendations;
1284
+ }
1285
+ // Helper methods for instance types (simplified mapping)
1286
+ getCpuCountForInstanceType(instanceType) {
1287
+ if (!instanceType)
1288
+ return 0;
1289
+ const cpuMap = {
1290
+ "t2.nano": 1,
1291
+ "t2.micro": 1,
1292
+ "t2.small": 1,
1293
+ "t2.medium": 2,
1294
+ "t2.large": 2,
1295
+ "m5.large": 2,
1296
+ "m5.xlarge": 4,
1297
+ "m5.2xlarge": 8,
1298
+ "m5.4xlarge": 16,
1299
+ "c5.large": 2,
1300
+ "c5.xlarge": 4,
1301
+ "c5.2xlarge": 8,
1302
+ "c5.4xlarge": 16
1303
+ };
1304
+ return cpuMap[instanceType] || 1;
1305
+ }
1306
+ getMemoryForInstanceType(instanceType) {
1307
+ if (!instanceType)
1308
+ return 0;
1309
+ const memoryMap = {
1310
+ "t2.nano": 0.5,
1311
+ "t2.micro": 1,
1312
+ "t2.small": 2,
1313
+ "t2.medium": 4,
1314
+ "t2.large": 8,
1315
+ "m5.large": 8,
1316
+ "m5.xlarge": 16,
1317
+ "m5.2xlarge": 32,
1318
+ "m5.4xlarge": 64,
1319
+ "c5.large": 4,
1320
+ "c5.xlarge": 8,
1321
+ "c5.2xlarge": 16,
1322
+ "c5.4xlarge": 32
1323
+ };
1324
+ return memoryMap[instanceType] || 1;
1325
+ }
1326
+ // Helper methods for budget functionality
1327
+ determineBudgetStatus(budget) {
1328
+ var _a, _b, _c;
1329
+ if (!budget.CalculatedSpend)
1330
+ return "OK";
1331
+ const actual = parseFloat(((_a = budget.CalculatedSpend.ActualSpend) == null ? void 0 : _a.Amount) || "0");
1332
+ const limit = parseFloat(((_b = budget.BudgetLimit) == null ? void 0 : _b.Amount) || "0");
1333
+ const forecasted = parseFloat(((_c = budget.CalculatedSpend.ForecastedSpend) == null ? void 0 : _c.Amount) || "0");
1334
+ if (actual >= limit)
1335
+ return "ALARM";
1336
+ if (forecasted >= limit)
1337
+ return "FORECASTED_ALARM";
1338
+ return "OK";
1339
+ }
1340
+ parseBudgetThresholds(budget) {
1341
+ return [
1342
+ {
1343
+ threshold: 80,
1344
+ thresholdType: "PERCENTAGE",
1345
+ comparisonOperator: "GREATER_THAN",
1346
+ notificationType: "ACTUAL"
1347
+ },
1348
+ {
1349
+ threshold: 100,
1350
+ thresholdType: "PERCENTAGE",
1351
+ comparisonOperator: "GREATER_THAN",
1352
+ notificationType: "FORECASTED"
1353
+ }
1354
+ ];
1355
+ }
1356
+ parseCostFilters(budget) {
1357
+ return budget.CostFilters || {};
1358
+ }
1359
+ calculateTimeRemaining(timePeriod) {
1360
+ if (!timePeriod.end)
1361
+ return "Unknown";
1362
+ const endDate = dayjs(timePeriod.end);
1363
+ const now = dayjs();
1364
+ const daysRemaining = endDate.diff(now, "day");
1365
+ if (daysRemaining <= 0)
1366
+ return "Period ended";
1367
+ if (daysRemaining === 1)
1368
+ return "1 day";
1369
+ return `${daysRemaining} days`;
1370
+ }
1371
+ determineSeverity(percentageUsed) {
1372
+ if (percentageUsed >= 100)
1373
+ return "CRITICAL";
1374
+ if (percentageUsed >= 90)
1375
+ return "HIGH";
1376
+ if (percentageUsed >= 75)
1377
+ return "MEDIUM";
1378
+ return "LOW";
1379
+ }
1380
+ generateAlertMessage(budget, threshold, percentageUsed) {
1381
+ const thresholdType = threshold.thresholdType === "PERCENTAGE" ? "%" : "$";
1382
+ return `Budget "${budget.budgetName}" has ${threshold.notificationType.toLowerCase()} ${threshold.threshold}${thresholdType} threshold (currently at ${percentageUsed.toFixed(1)}%)`;
1383
+ }
1384
+ async getTopServices(startDate, endDate) {
1385
+ return [
1386
+ {
1387
+ serviceName: "Amazon Elastic Compute Cloud - Compute",
1388
+ cost: 1250.45,
1389
+ percentage: 45.2,
1390
+ trend: "INCREASING"
1391
+ },
1392
+ {
1393
+ serviceName: "Amazon Simple Storage Service",
1394
+ cost: 680.23,
1395
+ percentage: 24.6,
1396
+ trend: "STABLE"
1397
+ },
1398
+ {
1399
+ serviceName: "Amazon Relational Database Service",
1400
+ cost: 445.67,
1401
+ percentage: 16.1,
1402
+ trend: "DECREASING"
1403
+ }
1404
+ ];
1405
+ }
1406
+ detectCostAnomalies(trendData) {
1407
+ const anomalies = [];
1408
+ for (let i = 1; i < trendData.length; i++) {
1409
+ const current = trendData[i];
1410
+ const changePercentage = Math.abs(current.changeFromPrevious.percentage);
1411
+ if (changePercentage > 50) {
1412
+ anomalies.push({
1413
+ date: current.period,
1414
+ actualCost: current.actualCost,
1415
+ expectedCost: current.actualCost - current.changeFromPrevious.amount,
1416
+ deviation: changePercentage,
1417
+ severity: changePercentage > 100 ? "HIGH" : "MEDIUM",
1418
+ possibleCause: changePercentage > 0 ? "Unexpected cost increase" : "Significant cost reduction"
1419
+ });
1420
+ }
1421
+ }
1422
+ return anomalies;
1423
+ }
1424
+ static createFromLegacyConfig(legacyConfig) {
1425
+ const config = {
1426
+ provider: "aws" /* AWS */,
1427
+ credentials: {
1428
+ accessKeyId: legacyConfig.credentials.accessKeyId,
1429
+ secretAccessKey: legacyConfig.credentials.secretAccessKey,
1430
+ sessionToken: legacyConfig.credentials.sessionToken
1431
+ },
1432
+ region: legacyConfig.region
1433
+ };
1434
+ return new AWSProvider(config);
1435
+ }
1436
+ calculateVolatility(values) {
1437
+ if (values.length < 2)
1438
+ return 0;
1439
+ const mean = values.reduce((a, b) => a + b, 0) / values.length;
1440
+ const variance = values.reduce((sum, val) => sum + Math.pow(val - mean, 2), 0) / values.length;
1441
+ const stdDev = Math.sqrt(variance);
1442
+ return mean > 0 ? stdDev / mean : 0;
1443
+ }
1444
+ calculateTrendStrength(values) {
1445
+ if (values.length < 3)
1446
+ return 0;
1447
+ const n = values.length;
1448
+ const x = Array.from({ length: n }, (_, i) => i);
1449
+ const meanX = x.reduce((a, b) => a + b, 0) / n;
1450
+ const meanY = values.reduce((a, b) => a + b, 0) / n;
1451
+ const ssXY = x.reduce((sum, xi, i) => sum + (xi - meanX) * (values[i] - meanY), 0);
1452
+ const ssXX = x.reduce((sum, xi) => sum + Math.pow(xi - meanX, 2), 0);
1453
+ const ssYY = values.reduce((sum, yi) => sum + Math.pow(yi - meanY, 2), 0);
1454
+ const correlation = ssXX === 0 || ssYY === 0 ? 0 : ssXY / Math.sqrt(ssXX * ssYY);
1455
+ return Math.abs(correlation);
1456
+ }
1457
+ };
1458
+
1459
+ // src/providers/gcp.ts
1460
+ var GCPProvider = class extends CloudProviderAdapter {
1461
+ constructor(config) {
1462
+ super(config);
1463
+ }
1464
+ async validateCredentials() {
1465
+ console.warn("GCP credential validation not yet implemented");
1466
+ return false;
1467
+ }
1468
+ async getAccountInfo() {
1469
+ showSpinner("Getting GCP project information");
1470
+ try {
1471
+ throw new Error("GCP integration not yet implemented. Please use AWS for now.");
1472
+ } catch (error) {
1473
+ throw new Error(`Failed to get GCP project information: ${error.message}`);
1474
+ }
1475
+ }
1476
+ async getRawCostData() {
1477
+ showSpinner("Getting GCP billing data");
1478
+ try {
1479
+ throw new Error("GCP cost analysis not yet implemented. Please use AWS for now.");
1480
+ } catch (error) {
1481
+ throw new Error(`Failed to get GCP cost data: ${error.message}`);
1482
+ }
1483
+ }
1484
+ async getCostBreakdown() {
1485
+ const rawCostData = await this.getRawCostData();
1486
+ return this.calculateServiceTotals(rawCostData);
1487
+ }
1488
+ async getResourceInventory(filters) {
1489
+ showSpinner("Discovering GCP resources");
1490
+ const regions = (filters == null ? void 0 : filters.regions) || [this.config.region || "us-central1"];
1491
+ const resourceTypes = (filters == null ? void 0 : filters.resourceTypes) || Object.values(ResourceType);
1492
+ const includeCosts = (filters == null ? void 0 : filters.includeCosts) || false;
1493
+ const inventory = {
1494
+ provider: "gcp" /* GOOGLE_CLOUD */,
1495
+ region: regions.join(", "),
1496
+ totalResources: 0,
1497
+ resourcesByType: {
1498
+ ["compute" /* COMPUTE */]: 0,
1499
+ ["storage" /* STORAGE */]: 0,
1500
+ ["database" /* DATABASE */]: 0,
1501
+ ["network" /* NETWORK */]: 0,
1502
+ ["security" /* SECURITY */]: 0,
1503
+ ["serverless" /* SERVERLESS */]: 0,
1504
+ ["container" /* CONTAINER */]: 0,
1505
+ ["analytics" /* ANALYTICS */]: 0
1506
+ },
1507
+ totalCost: 0,
1508
+ resources: {
1509
+ compute: [],
1510
+ storage: [],
1511
+ database: [],
1512
+ network: [],
1513
+ security: [],
1514
+ serverless: [],
1515
+ container: [],
1516
+ analytics: []
1517
+ },
1518
+ lastUpdated: /* @__PURE__ */ new Date()
1519
+ };
1520
+ const projectId = this.config.credentials.projectId;
1521
+ if (!projectId) {
1522
+ throw new Error("GCP Project ID is required for resource discovery");
1523
+ }
1524
+ try {
1525
+ if (resourceTypes.includes("compute" /* COMPUTE */)) {
1526
+ const computeResources = await this.discoverComputeInstances(projectId, regions, includeCosts);
1527
+ inventory.resources.compute.push(...computeResources);
1528
+ inventory.resourcesByType["compute" /* COMPUTE */] += computeResources.length;
1529
+ }
1530
+ if (resourceTypes.includes("storage" /* STORAGE */)) {
1531
+ const storageResources = await this.discoverStorageBuckets(projectId, includeCosts);
1532
+ inventory.resources.storage.push(...storageResources);
1533
+ inventory.resourcesByType["storage" /* STORAGE */] += storageResources.length;
1534
+ }
1535
+ if (resourceTypes.includes("database" /* DATABASE */)) {
1536
+ const databaseResources = await this.discoverCloudSQLInstances(projectId, regions, includeCosts);
1537
+ inventory.resources.database.push(...databaseResources);
1538
+ inventory.resourcesByType["database" /* DATABASE */] += databaseResources.length;
1539
+ }
1540
+ if (resourceTypes.includes("serverless" /* SERVERLESS */)) {
1541
+ const serverlessResources = await this.discoverCloudFunctions(projectId, regions, includeCosts);
1542
+ inventory.resources.serverless.push(...serverlessResources);
1543
+ inventory.resourcesByType["serverless" /* SERVERLESS */] += serverlessResources.length;
1544
+ }
1545
+ if (resourceTypes.includes("container" /* CONTAINER */)) {
1546
+ const containerResources = await this.discoverGKEClusters(projectId, regions, includeCosts);
1547
+ inventory.resources.container.push(...containerResources);
1548
+ inventory.resourcesByType["container" /* CONTAINER */] += containerResources.length;
1549
+ }
1550
+ } catch (error) {
1551
+ console.warn(`Failed to discover GCP resources: ${error.message}`);
1552
+ }
1553
+ inventory.totalResources = Object.values(inventory.resourcesByType).reduce((sum, count) => sum + count, 0);
1554
+ if (includeCosts) {
1555
+ inventory.totalCost = Object.values(inventory.resources).flat().reduce((sum, resource) => sum + (resource.costToDate || 0), 0);
1556
+ }
1557
+ return inventory;
1558
+ }
1559
+ async discoverComputeInstances(projectId, regions, includeCosts) {
1560
+ console.warn("GCP Compute Engine discovery is simulated - integrate with @google-cloud/compute for production use");
1561
+ const instances = [];
1562
+ if (process.env.GCP_MOCK_DATA === "true") {
1563
+ instances.push({
1564
+ id: "instance-1",
1565
+ name: "web-server-1",
1566
+ state: "RUNNING",
1567
+ region: regions[0],
1568
+ provider: "gcp" /* GOOGLE_CLOUD */,
1569
+ createdAt: /* @__PURE__ */ new Date("2024-01-15"),
1570
+ instanceType: "e2-medium",
1571
+ cpu: 1,
1572
+ memory: 4,
1573
+ instanceName: "web-server-1",
1574
+ machineType: "e2-medium",
1575
+ zone: `${regions[0]}-a`,
1576
+ image: "projects/ubuntu-os-cloud/global/images/ubuntu-2004-focal-v20240110",
1577
+ disks: [{
1578
+ type: "pd-standard",
1579
+ sizeGb: 20,
1580
+ boot: true
1581
+ }],
1582
+ networkInterfaces: [{
1583
+ network: "projects/my-project/global/networks/default"
1584
+ }],
1585
+ tags: {
1586
+ "Environment": "production",
1587
+ "Team": "web"
1588
+ },
1589
+ costToDate: includeCosts ? 45.67 : 0
1590
+ });
1591
+ }
1592
+ return instances;
1593
+ }
1594
+ async discoverStorageBuckets(projectId, includeCosts) {
1595
+ console.warn("GCP Cloud Storage discovery is simulated - integrate with @google-cloud/storage for production use");
1596
+ const buckets = [];
1597
+ if (process.env.GCP_MOCK_DATA === "true") {
1598
+ buckets.push({
1599
+ id: "my-app-storage-bucket",
1600
+ name: "my-app-storage-bucket",
1601
+ state: "active",
1602
+ region: "us-central1",
1603
+ provider: "gcp" /* GOOGLE_CLOUD */,
1604
+ createdAt: /* @__PURE__ */ new Date("2024-01-10"),
1605
+ sizeGB: 150,
1606
+ storageType: "STANDARD",
1607
+ bucketName: "my-app-storage-bucket",
1608
+ location: "US-CENTRAL1",
1609
+ storageClass: "STANDARD",
1610
+ tags: {
1611
+ "Project": "web-app",
1612
+ "Environment": "production"
1613
+ },
1614
+ costToDate: includeCosts ? 3.45 : 0
1615
+ });
1616
+ }
1617
+ return buckets;
1618
+ }
1619
+ async discoverCloudSQLInstances(projectId, regions, includeCosts) {
1620
+ console.warn("GCP Cloud SQL discovery is simulated - integrate with Google Cloud SQL Admin API for production use");
1621
+ const instances = [];
1622
+ if (process.env.GCP_MOCK_DATA === "true") {
1623
+ instances.push({
1624
+ id: "production-db-1",
1625
+ name: "production-db-1",
1626
+ state: "RUNNABLE",
1627
+ region: regions[0],
1628
+ provider: "gcp" /* GOOGLE_CLOUD */,
1629
+ createdAt: /* @__PURE__ */ new Date("2024-01-12"),
1630
+ engine: "POSTGRES_14",
1631
+ version: "14.9",
1632
+ instanceClass: "db-custom-2-8192",
1633
+ storageGB: 100,
1634
+ instanceId: "production-db-1",
1635
+ databaseVersion: "POSTGRES_14",
1636
+ tier: "db-custom-2-8192",
1637
+ diskSizeGb: 100,
1638
+ diskType: "PD_SSD",
1639
+ ipAddresses: [{
1640
+ type: "PRIMARY",
1641
+ ipAddress: "10.1.2.3"
1642
+ }],
1643
+ tags: {
1644
+ "Database": "primary",
1645
+ "Environment": "production"
1646
+ },
1647
+ costToDate: includeCosts ? 89.23 : 0
1648
+ });
1649
+ }
1650
+ return instances;
1651
+ }
1652
+ async discoverCloudFunctions(projectId, regions, includeCosts) {
1653
+ console.warn("GCP Cloud Functions discovery is simulated - integrate with @google-cloud/functions for production use");
1654
+ const functions = [];
1655
+ if (process.env.GCP_MOCK_DATA === "true") {
1656
+ functions.push({
1657
+ id: "projects/my-project/locations/us-central1/functions/api-handler",
1658
+ name: "api-handler",
1659
+ state: "ACTIVE",
1660
+ region: regions[0],
1661
+ provider: "gcp" /* GOOGLE_CLOUD */,
1662
+ createdAt: /* @__PURE__ */ new Date("2024-01-20"),
1663
+ functionName: "api-handler",
1664
+ runtime: "nodejs20",
1665
+ entryPoint: "handleRequest",
1666
+ availableMemoryMb: 256,
1667
+ timeout: "60s",
1668
+ tags: {
1669
+ "Function": "api",
1670
+ "Environment": "production"
1671
+ },
1672
+ costToDate: includeCosts ? 12.45 : 0
1673
+ });
1674
+ }
1675
+ return functions;
1676
+ }
1677
+ async discoverGKEClusters(projectId, regions, includeCosts) {
1678
+ console.warn("GCP GKE discovery is simulated - integrate with @google-cloud/container for production use");
1679
+ const clusters = [];
1680
+ if (process.env.GCP_MOCK_DATA === "true") {
1681
+ clusters.push({
1682
+ id: "production-cluster",
1683
+ name: "production-cluster",
1684
+ state: "RUNNING",
1685
+ region: regions[0],
1686
+ provider: "gcp" /* GOOGLE_CLOUD */,
1687
+ createdAt: /* @__PURE__ */ new Date("2024-01-18"),
1688
+ clusterName: "production-cluster",
1689
+ location: `${regions[0]}-a`,
1690
+ nodeCount: 3,
1691
+ currentMasterVersion: "1.28.3-gke.1203001",
1692
+ currentNodeVersion: "1.28.3-gke.1203001",
1693
+ network: "projects/my-project/global/networks/default",
1694
+ nodePools: [{
1695
+ name: "default-pool",
1696
+ nodeCount: 3,
1697
+ config: {
1698
+ machineType: "e2-medium",
1699
+ diskSizeGb: 100
1700
+ }
1701
+ }],
1702
+ tags: {
1703
+ "Cluster": "production",
1704
+ "Environment": "production"
1705
+ },
1706
+ costToDate: includeCosts ? 234.56 : 0
1707
+ });
1708
+ }
1709
+ return clusters;
1710
+ }
1711
+ async getResourceCosts(resourceId) {
1712
+ console.warn("GCP resource costing is not yet implemented - integrate with Google Cloud Billing API");
1713
+ return 0;
1714
+ }
1715
+ async getOptimizationRecommendations() {
1716
+ return [
1717
+ "Consider using Sustained Use Discounts for long-running Compute Engine instances",
1718
+ "Enable Cloud Storage lifecycle policies to automatically transition old data to cheaper storage classes",
1719
+ "Use Committed Use Discounts for predictable Compute Engine workloads to save up to 57%",
1720
+ "Consider using Preemptible VMs for fault-tolerant workloads to save up to 80%",
1721
+ "Review Cloud SQL instances and consider right-sizing based on actual usage",
1722
+ "Use Cloud Functions for event-driven workloads instead of always-on Compute Engine instances",
1723
+ "Implement Cloud Storage Nearline or Coldline for infrequently accessed data",
1724
+ "Consider using Google Kubernetes Engine Autopilot for optimized node management"
1725
+ ];
1726
+ }
1727
+ async getBudgets() {
1728
+ throw new Error("GCP budget tracking not yet implemented. Please use AWS for now.");
1729
+ }
1730
+ async getBudgetAlerts() {
1731
+ throw new Error("GCP budget alerts not yet implemented. Please use AWS for now.");
1732
+ }
1733
+ async getCostTrendAnalysis(months) {
1734
+ throw new Error("GCP cost trend analysis not yet implemented. Please use AWS for now.");
1735
+ }
1736
+ async getFinOpsRecommendations() {
1737
+ throw new Error("GCP FinOps recommendations not yet implemented. Please use AWS for now.");
1738
+ }
1739
+ // Helper method to validate GCP-specific configuration
1740
+ static validateGCPConfig(config) {
1741
+ const requiredFields = ["projectId"];
1742
+ for (const field of requiredFields) {
1743
+ if (!config.credentials[field]) {
1744
+ return false;
1745
+ }
1746
+ }
1747
+ return true;
1748
+ }
1749
+ // Helper method to get required credential fields for GCP
1750
+ static getRequiredCredentials() {
1751
+ return [
1752
+ "projectId",
1753
+ "keyFilePath"
1754
+ // Path to service account JSON file
1755
+ // Alternative: 'serviceAccountKey' for JSON content
1756
+ ];
1757
+ }
1758
+ };
1759
+
1760
+ // src/providers/azure.ts
1761
+ var AzureProvider = class extends CloudProviderAdapter {
1762
+ constructor(config) {
1763
+ super(config);
1764
+ }
1765
+ async validateCredentials() {
1766
+ console.warn("Azure credential validation not yet implemented");
1767
+ return false;
1768
+ }
1769
+ async getAccountInfo() {
1770
+ showSpinner("Getting Azure subscription information");
1771
+ try {
1772
+ throw new Error("Azure integration not yet implemented. Please use AWS for now.");
1773
+ } catch (error) {
1774
+ throw new Error(`Failed to get Azure subscription information: ${error.message}`);
1775
+ }
1776
+ }
1777
+ async getRawCostData() {
1778
+ showSpinner("Getting Azure cost data");
1779
+ try {
1780
+ throw new Error("Azure cost analysis not yet implemented. Please use AWS for now.");
1781
+ } catch (error) {
1782
+ throw new Error(`Failed to get Azure cost data: ${error.message}`);
1783
+ }
1784
+ }
1785
+ async getCostBreakdown() {
1786
+ const rawCostData = await this.getRawCostData();
1787
+ return this.calculateServiceTotals(rawCostData);
1788
+ }
1789
+ async getResourceInventory(filters) {
1790
+ showSpinner("Discovering Azure resources");
1791
+ const regions = (filters == null ? void 0 : filters.regions) || [this.config.region || "eastus"];
1792
+ const resourceTypes = (filters == null ? void 0 : filters.resourceTypes) || Object.values(ResourceType);
1793
+ const includeCosts = (filters == null ? void 0 : filters.includeCosts) || false;
1794
+ const inventory = {
1795
+ provider: "azure" /* AZURE */,
1796
+ region: regions.join(", "),
1797
+ totalResources: 0,
1798
+ resourcesByType: {
1799
+ ["compute" /* COMPUTE */]: 0,
1800
+ ["storage" /* STORAGE */]: 0,
1801
+ ["database" /* DATABASE */]: 0,
1802
+ ["network" /* NETWORK */]: 0,
1803
+ ["security" /* SECURITY */]: 0,
1804
+ ["serverless" /* SERVERLESS */]: 0,
1805
+ ["container" /* CONTAINER */]: 0,
1806
+ ["analytics" /* ANALYTICS */]: 0
1807
+ },
1808
+ totalCost: 0,
1809
+ resources: {
1810
+ compute: [],
1811
+ storage: [],
1812
+ database: [],
1813
+ network: [],
1814
+ security: [],
1815
+ serverless: [],
1816
+ container: [],
1817
+ analytics: []
1818
+ },
1819
+ lastUpdated: /* @__PURE__ */ new Date()
1820
+ };
1821
+ const subscriptionId = this.config.credentials.subscriptionId;
1822
+ if (!subscriptionId) {
1823
+ throw new Error("Azure Subscription ID is required for resource discovery");
1824
+ }
1825
+ try {
1826
+ if (resourceTypes.includes("compute" /* COMPUTE */)) {
1827
+ const computeResources = await this.discoverVirtualMachines(subscriptionId, regions, includeCosts);
1828
+ inventory.resources.compute.push(...computeResources);
1829
+ inventory.resourcesByType["compute" /* COMPUTE */] += computeResources.length;
1830
+ }
1831
+ if (resourceTypes.includes("storage" /* STORAGE */)) {
1832
+ const storageResources = await this.discoverStorageAccounts(subscriptionId, includeCosts);
1833
+ inventory.resources.storage.push(...storageResources);
1834
+ inventory.resourcesByType["storage" /* STORAGE */] += storageResources.length;
1835
+ }
1836
+ if (resourceTypes.includes("database" /* DATABASE */)) {
1837
+ const databaseResources = await this.discoverSQLDatabases(subscriptionId, regions, includeCosts);
1838
+ inventory.resources.database.push(...databaseResources);
1839
+ inventory.resourcesByType["database" /* DATABASE */] += databaseResources.length;
1840
+ }
1841
+ if (resourceTypes.includes("serverless" /* SERVERLESS */)) {
1842
+ const serverlessResources = await this.discoverFunctionApps(subscriptionId, regions, includeCosts);
1843
+ inventory.resources.serverless.push(...serverlessResources);
1844
+ inventory.resourcesByType["serverless" /* SERVERLESS */] += serverlessResources.length;
1845
+ }
1846
+ if (resourceTypes.includes("container" /* CONTAINER */)) {
1847
+ const containerResources = await this.discoverAKSClusters(subscriptionId, regions, includeCosts);
1848
+ inventory.resources.container.push(...containerResources);
1849
+ inventory.resourcesByType["container" /* CONTAINER */] += containerResources.length;
1850
+ }
1851
+ if (resourceTypes.includes("network" /* NETWORK */)) {
1852
+ const networkResources = await this.discoverVirtualNetworks(subscriptionId, regions, includeCosts);
1853
+ inventory.resources.network.push(...networkResources);
1854
+ inventory.resourcesByType["network" /* NETWORK */] += networkResources.length;
1855
+ }
1856
+ } catch (error) {
1857
+ console.warn(`Failed to discover Azure resources: ${error.message}`);
1858
+ }
1859
+ inventory.totalResources = Object.values(inventory.resourcesByType).reduce((sum, count) => sum + count, 0);
1860
+ if (includeCosts) {
1861
+ inventory.totalCost = Object.values(inventory.resources).flat().reduce((sum, resource) => sum + (resource.costToDate || 0), 0);
1862
+ }
1863
+ return inventory;
1864
+ }
1865
+ async discoverVirtualMachines(subscriptionId, regions, includeCosts) {
1866
+ console.warn("Azure Virtual Machines discovery is simulated - integrate with Azure SDK for production use");
1867
+ const vms = [];
1868
+ if (process.env.AZURE_MOCK_DATA === "true") {
1869
+ vms.push({
1870
+ id: "/subscriptions/sub123/resourceGroups/rg-prod/providers/Microsoft.Compute/virtualMachines/web-vm-01",
1871
+ name: "web-vm-01",
1872
+ state: "PowerState/running",
1873
+ region: regions[0],
1874
+ provider: "azure" /* AZURE */,
1875
+ createdAt: /* @__PURE__ */ new Date("2024-01-15"),
1876
+ instanceType: "Standard_D2s_v3",
1877
+ cpu: 2,
1878
+ memory: 8,
1879
+ resourceId: "/subscriptions/sub123/resourceGroups/rg-prod/providers/Microsoft.Compute/virtualMachines/web-vm-01",
1880
+ vmSize: "Standard_D2s_v3",
1881
+ osType: "Linux",
1882
+ imageReference: {
1883
+ publisher: "Canonical",
1884
+ offer: "UbuntuServer",
1885
+ sku: "20.04-LTS",
1886
+ version: "latest"
1887
+ },
1888
+ osDisk: {
1889
+ osType: "Linux",
1890
+ diskSizeGB: 30,
1891
+ managedDisk: {
1892
+ storageAccountType: "Premium_LRS"
1893
+ }
1894
+ },
1895
+ networkProfile: {
1896
+ networkInterfaces: [{
1897
+ id: "/subscriptions/sub123/resourceGroups/rg-prod/providers/Microsoft.Network/networkInterfaces/web-vm-01-nic"
1898
+ }]
1899
+ },
1900
+ tags: {
1901
+ "Environment": "production",
1902
+ "Team": "web",
1903
+ "CostCenter": "engineering"
1904
+ },
1905
+ costToDate: includeCosts ? 89.45 : 0
1906
+ });
1907
+ }
1908
+ return vms;
1909
+ }
1910
+ async discoverStorageAccounts(subscriptionId, includeCosts) {
1911
+ console.warn("Azure Storage Accounts discovery is simulated - integrate with Azure SDK for production use");
1912
+ const storageAccounts = [];
1913
+ if (process.env.AZURE_MOCK_DATA === "true") {
1914
+ storageAccounts.push({
1915
+ id: "/subscriptions/sub123/resourceGroups/rg-prod/providers/Microsoft.Storage/storageAccounts/prodstorageacct",
1916
+ name: "prodstorageacct",
1917
+ state: "active",
1918
+ region: "eastus",
1919
+ provider: "azure" /* AZURE */,
1920
+ createdAt: /* @__PURE__ */ new Date("2024-01-10"),
1921
+ sizeGB: 500,
1922
+ storageType: "Standard_LRS",
1923
+ accountName: "prodstorageacct",
1924
+ kind: "StorageV2",
1925
+ tier: "Standard",
1926
+ replicationType: "LRS",
1927
+ accessTier: "Hot",
1928
+ encryption: {
1929
+ services: {
1930
+ blob: { enabled: true },
1931
+ file: { enabled: true }
1932
+ }
1933
+ },
1934
+ tags: {
1935
+ "Environment": "production",
1936
+ "Application": "web-app"
1937
+ },
1938
+ costToDate: includeCosts ? 25.67 : 0
1939
+ });
1940
+ }
1941
+ return storageAccounts;
1942
+ }
1943
+ async discoverSQLDatabases(subscriptionId, regions, includeCosts) {
1944
+ console.warn("Azure SQL Database discovery is simulated - integrate with Azure SQL Management API for production use");
1945
+ const databases = [];
1946
+ if (process.env.AZURE_MOCK_DATA === "true") {
1947
+ databases.push({
1948
+ id: "/subscriptions/sub123/resourceGroups/rg-prod/providers/Microsoft.Sql/servers/prod-sql-server/databases/webapp-db",
1949
+ name: "webapp-db",
1950
+ state: "Online",
1951
+ region: regions[0],
1952
+ provider: "azure" /* AZURE */,
1953
+ createdAt: /* @__PURE__ */ new Date("2024-01-12"),
1954
+ engine: "Microsoft SQL Server",
1955
+ version: "12.0",
1956
+ instanceClass: "S2",
1957
+ storageGB: 250,
1958
+ databaseId: "/subscriptions/sub123/resourceGroups/rg-prod/providers/Microsoft.Sql/servers/prod-sql-server/databases/webapp-db",
1959
+ serverName: "prod-sql-server",
1960
+ edition: "Standard",
1961
+ serviceObjective: "S2",
1962
+ collation: "SQL_Latin1_General_CP1_CI_AS",
1963
+ maxSizeBytes: 268435456e3,
1964
+ status: "Online",
1965
+ elasticPoolName: void 0,
1966
+ tags: {
1967
+ "Database": "primary",
1968
+ "Environment": "production",
1969
+ "Application": "webapp"
1970
+ },
1971
+ costToDate: includeCosts ? 156.78 : 0
1972
+ });
1973
+ }
1974
+ return databases;
1975
+ }
1976
+ async discoverFunctionApps(subscriptionId, regions, includeCosts) {
1977
+ console.warn("Azure Function Apps discovery is simulated - integrate with Azure App Service API for production use");
1978
+ const functionApps = [];
1979
+ if (process.env.AZURE_MOCK_DATA === "true") {
1980
+ functionApps.push({
1981
+ id: "/subscriptions/sub123/resourceGroups/rg-prod/providers/Microsoft.Web/sites/api-functions",
1982
+ name: "api-functions",
1983
+ state: "Running",
1984
+ region: regions[0],
1985
+ provider: "azure" /* AZURE */,
1986
+ createdAt: /* @__PURE__ */ new Date("2024-01-20"),
1987
+ functionAppName: "api-functions",
1988
+ kind: "functionapp",
1989
+ runtime: "dotnet",
1990
+ runtimeVersion: "6",
1991
+ hostingPlan: {
1992
+ name: "consumption-plan",
1993
+ tier: "Dynamic"
1994
+ },
1995
+ tags: {
1996
+ "Function": "api",
1997
+ "Environment": "production"
1998
+ },
1999
+ costToDate: includeCosts ? 23.45 : 0
2000
+ });
2001
+ }
2002
+ return functionApps;
2003
+ }
2004
+ async discoverAKSClusters(subscriptionId, regions, includeCosts) {
2005
+ console.warn("Azure AKS discovery is simulated - integrate with Azure Kubernetes Service API for production use");
2006
+ const clusters = [];
2007
+ if (process.env.AZURE_MOCK_DATA === "true") {
2008
+ clusters.push({
2009
+ id: "/subscriptions/sub123/resourceGroups/rg-prod/providers/Microsoft.ContainerService/managedClusters/prod-aks",
2010
+ name: "prod-aks",
2011
+ state: "Succeeded",
2012
+ region: regions[0],
2013
+ provider: "azure" /* AZURE */,
2014
+ createdAt: /* @__PURE__ */ new Date("2024-01-18"),
2015
+ clusterName: "prod-aks",
2016
+ kubernetesVersion: "1.28.3",
2017
+ nodeCount: 3,
2018
+ dnsPrefix: "prod-aks-dns",
2019
+ agentPoolProfiles: [{
2020
+ name: "agentpool",
2021
+ count: 3,
2022
+ vmSize: "Standard_D2s_v3",
2023
+ osType: "Linux",
2024
+ osDiskSizeGB: 128
2025
+ }],
2026
+ networkProfile: {
2027
+ networkPlugin: "azure",
2028
+ serviceCidr: "10.0.0.0/16",
2029
+ dnsServiceIP: "10.0.0.10"
2030
+ },
2031
+ tags: {
2032
+ "Cluster": "production",
2033
+ "Environment": "production"
2034
+ },
2035
+ costToDate: includeCosts ? 345.67 : 0
2036
+ });
2037
+ }
2038
+ return clusters;
2039
+ }
2040
+ async discoverVirtualNetworks(subscriptionId, regions, includeCosts) {
2041
+ console.warn("Azure Virtual Networks discovery is simulated - integrate with Azure Network API for production use");
2042
+ const vnets = [];
2043
+ if (process.env.AZURE_MOCK_DATA === "true") {
2044
+ vnets.push({
2045
+ id: "/subscriptions/sub123/resourceGroups/rg-prod/providers/Microsoft.Network/virtualNetworks/prod-vnet",
2046
+ name: "prod-vnet",
2047
+ state: "active",
2048
+ region: regions[0],
2049
+ provider: "azure" /* AZURE */,
2050
+ createdAt: /* @__PURE__ */ new Date("2024-01-05"),
2051
+ vnetName: "prod-vnet",
2052
+ addressSpace: {
2053
+ addressPrefixes: ["10.1.0.0/16"]
2054
+ },
2055
+ subnets: [{
2056
+ name: "default",
2057
+ addressPrefix: "10.1.0.0/24"
2058
+ }, {
2059
+ name: "aks-subnet",
2060
+ addressPrefix: "10.1.1.0/24"
2061
+ }],
2062
+ tags: {
2063
+ "Network": "production",
2064
+ "Environment": "production"
2065
+ },
2066
+ costToDate: includeCosts ? 0 : 0
2067
+ // VNets typically don't have direct costs
2068
+ });
2069
+ }
2070
+ return vnets;
2071
+ }
2072
+ async getResourceCosts(resourceId) {
2073
+ console.warn("Azure resource costing is not yet implemented - integrate with Azure Cost Management API");
2074
+ return 0;
2075
+ }
2076
+ async getOptimizationRecommendations() {
2077
+ return [
2078
+ "Use Azure Reserved Virtual Machine Instances for consistent workloads to save up to 72%",
2079
+ "Consider Azure Spot Virtual Machines for fault-tolerant workloads to save up to 90%",
2080
+ "Implement Azure Storage lifecycle management to automatically tier data to cooler storage",
2081
+ "Use Azure SQL Database elastic pools for multiple databases with varying usage patterns",
2082
+ "Consider Azure Container Instances for short-lived containerized workloads instead of AKS",
2083
+ "Implement auto-scaling for Virtual Machine Scale Sets to optimize compute costs",
2084
+ "Use Azure Functions consumption plan for event-driven workloads with variable traffic",
2085
+ "Consider Azure Storage Archive tier for long-term retention of infrequently accessed data",
2086
+ "Use Azure Hybrid Benefit to save on Windows Server and SQL Server licensing costs",
2087
+ "Implement Azure Cost Management budgets and alerts to monitor spending"
2088
+ ];
2089
+ }
2090
+ async getBudgets() {
2091
+ throw new Error("Azure budget tracking not yet implemented. Please use AWS for now.");
2092
+ }
2093
+ async getBudgetAlerts() {
2094
+ throw new Error("Azure budget alerts not yet implemented. Please use AWS for now.");
2095
+ }
2096
+ async getCostTrendAnalysis(months) {
2097
+ throw new Error("Azure cost trend analysis not yet implemented. Please use AWS for now.");
2098
+ }
2099
+ async getFinOpsRecommendations() {
2100
+ throw new Error("Azure FinOps recommendations not yet implemented. Please use AWS for now.");
2101
+ }
2102
+ // Helper method to validate Azure-specific configuration
2103
+ static validateAzureConfig(config) {
2104
+ const requiredFields = ["subscriptionId", "tenantId", "clientId", "clientSecret"];
2105
+ for (const field of requiredFields) {
2106
+ if (!config.credentials[field]) {
2107
+ return false;
2108
+ }
2109
+ }
2110
+ return true;
2111
+ }
2112
+ // Helper method to get required credential fields for Azure
2113
+ static getRequiredCredentials() {
2114
+ return [
2115
+ "subscriptionId",
2116
+ "tenantId",
2117
+ "clientId",
2118
+ "clientSecret"
2119
+ ];
2120
+ }
2121
+ };
2122
+
2123
+ // src/providers/alicloud.ts
2124
+ var AlibabaCloudProvider = class extends CloudProviderAdapter {
2125
+ constructor(config) {
2126
+ super(config);
2127
+ }
2128
+ async validateCredentials() {
2129
+ console.warn("Alibaba Cloud credential validation not yet implemented");
2130
+ return false;
2131
+ }
2132
+ async getAccountInfo() {
2133
+ showSpinner("Getting Alibaba Cloud account information");
2134
+ try {
2135
+ throw new Error("Alibaba Cloud integration not yet implemented. Please use AWS for now.");
2136
+ } catch (error) {
2137
+ throw new Error(`Failed to get Alibaba Cloud account information: ${error.message}`);
2138
+ }
2139
+ }
2140
+ async getRawCostData() {
2141
+ showSpinner("Getting Alibaba Cloud billing data");
2142
+ try {
2143
+ throw new Error("Alibaba Cloud cost analysis not yet implemented. Please use AWS for now.");
2144
+ } catch (error) {
2145
+ throw new Error(`Failed to get Alibaba Cloud cost data: ${error.message}`);
2146
+ }
2147
+ }
2148
+ async getCostBreakdown() {
2149
+ const rawCostData = await this.getRawCostData();
2150
+ return this.calculateServiceTotals(rawCostData);
2151
+ }
2152
+ async getResourceInventory(filters) {
2153
+ throw new Error("Alibaba Cloud resource inventory not yet implemented. Please use AWS for now.");
2154
+ }
2155
+ async getResourceCosts(resourceId) {
2156
+ throw new Error("Alibaba Cloud resource costing not yet implemented. Please use AWS for now.");
2157
+ }
2158
+ async getOptimizationRecommendations() {
2159
+ throw new Error("Alibaba Cloud optimization recommendations not yet implemented. Please use AWS for now.");
2160
+ }
2161
+ async getBudgets() {
2162
+ throw new Error("Alibaba Cloud budget tracking not yet implemented. Please use AWS for now.");
2163
+ }
2164
+ async getBudgetAlerts() {
2165
+ throw new Error("Alibaba Cloud budget alerts not yet implemented. Please use AWS for now.");
2166
+ }
2167
+ async getCostTrendAnalysis(months) {
2168
+ throw new Error("Alibaba Cloud cost trend analysis not yet implemented. Please use AWS for now.");
2169
+ }
2170
+ async getFinOpsRecommendations() {
2171
+ throw new Error("Alibaba Cloud FinOps recommendations not yet implemented. Please use AWS for now.");
2172
+ }
2173
+ // Helper method to validate Alibaba Cloud-specific configuration
2174
+ static validateAlibabaCloudConfig(config) {
2175
+ const requiredFields = ["accessKeyId", "accessKeySecret"];
2176
+ for (const field of requiredFields) {
2177
+ if (!config.credentials[field]) {
2178
+ return false;
2179
+ }
2180
+ }
2181
+ return true;
2182
+ }
2183
+ // Helper method to get required credential fields for Alibaba Cloud
2184
+ static getRequiredCredentials() {
2185
+ return [
2186
+ "accessKeyId",
2187
+ "accessKeySecret"
2188
+ // Optional: 'securityToken', 'regionId'
2189
+ ];
2190
+ }
2191
+ };
2192
+
2193
+ // src/providers/oracle.ts
2194
+ var OracleCloudProvider = class extends CloudProviderAdapter {
2195
+ constructor(config) {
2196
+ super(config);
2197
+ }
2198
+ async validateCredentials() {
2199
+ console.warn("Oracle Cloud credential validation not yet implemented");
2200
+ return false;
2201
+ }
2202
+ async getAccountInfo() {
2203
+ showSpinner("Getting Oracle Cloud tenancy information");
2204
+ try {
2205
+ throw new Error("Oracle Cloud integration not yet implemented. Please use AWS for now.");
2206
+ } catch (error) {
2207
+ throw new Error(`Failed to get Oracle Cloud tenancy information: ${error.message}`);
2208
+ }
2209
+ }
2210
+ async getRawCostData() {
2211
+ showSpinner("Getting Oracle Cloud usage data");
2212
+ try {
2213
+ throw new Error("Oracle Cloud cost analysis not yet implemented. Please use AWS for now.");
2214
+ } catch (error) {
2215
+ throw new Error(`Failed to get Oracle Cloud cost data: ${error.message}`);
2216
+ }
2217
+ }
2218
+ async getCostBreakdown() {
2219
+ const rawCostData = await this.getRawCostData();
2220
+ return this.calculateServiceTotals(rawCostData);
2221
+ }
2222
+ async getResourceInventory(filters) {
2223
+ throw new Error("Oracle Cloud resource inventory not yet implemented. Please use AWS for now.");
2224
+ }
2225
+ async getResourceCosts(resourceId) {
2226
+ throw new Error("Oracle Cloud resource costing not yet implemented. Please use AWS for now.");
2227
+ }
2228
+ async getOptimizationRecommendations() {
2229
+ throw new Error("Oracle Cloud optimization recommendations not yet implemented. Please use AWS for now.");
2230
+ }
2231
+ async getBudgets() {
2232
+ throw new Error("Oracle Cloud budget tracking not yet implemented. Please use AWS for now.");
2233
+ }
2234
+ async getBudgetAlerts() {
2235
+ throw new Error("Oracle Cloud budget alerts not yet implemented. Please use AWS for now.");
2236
+ }
2237
+ async getCostTrendAnalysis(months) {
2238
+ throw new Error("Oracle Cloud cost trend analysis not yet implemented. Please use AWS for now.");
2239
+ }
2240
+ async getFinOpsRecommendations() {
2241
+ throw new Error("Oracle Cloud FinOps recommendations not yet implemented. Please use AWS for now.");
2242
+ }
2243
+ // Helper method to validate Oracle Cloud-specific configuration
2244
+ static validateOracleCloudConfig(config) {
2245
+ const requiredFields = ["userId", "tenancyId", "fingerprint", "privateKeyPath"];
2246
+ for (const field of requiredFields) {
2247
+ if (!config.credentials[field]) {
2248
+ return false;
2249
+ }
2250
+ }
2251
+ return true;
2252
+ }
2253
+ // Helper method to get required credential fields for Oracle Cloud
2254
+ static getRequiredCredentials() {
2255
+ return [
2256
+ "userId",
2257
+ // User OCID
2258
+ "tenancyId",
2259
+ // Tenancy OCID
2260
+ "fingerprint",
2261
+ // Public key fingerprint
2262
+ "privateKeyPath"
2263
+ // Path to private key file
2264
+ // Optional: 'region', 'passphrase'
2265
+ ];
2266
+ }
2267
+ };
2268
+
2269
+ // src/providers/factory.ts
2270
+ var CloudProviderFactory = class {
2271
+ createProvider(config) {
2272
+ if (!this.validateProviderConfig(config)) {
2273
+ throw new Error(`Invalid configuration for provider: ${config.provider}`);
2274
+ }
2275
+ switch (config.provider) {
2276
+ case "aws" /* AWS */:
2277
+ return new AWSProvider(config);
2278
+ case "gcp" /* GOOGLE_CLOUD */:
2279
+ return new GCPProvider(config);
2280
+ case "azure" /* AZURE */:
2281
+ return new AzureProvider(config);
2282
+ case "alicloud" /* ALIBABA_CLOUD */:
2283
+ return new AlibabaCloudProvider(config);
2284
+ case "oracle" /* ORACLE_CLOUD */:
2285
+ return new OracleCloudProvider(config);
2286
+ default:
2287
+ throw new Error(`Unsupported cloud provider: ${config.provider}`);
2288
+ }
2289
+ }
2290
+ getSupportedProviders() {
2291
+ return [
2292
+ "aws" /* AWS */,
2293
+ "gcp" /* GOOGLE_CLOUD */,
2294
+ "azure" /* AZURE */,
2295
+ "alicloud" /* ALIBABA_CLOUD */,
2296
+ "oracle" /* ORACLE_CLOUD */
2297
+ ];
2298
+ }
2299
+ validateProviderConfig(config) {
2300
+ if (!config.provider) {
2301
+ return false;
2302
+ }
2303
+ const supportedProviders = this.getSupportedProviders();
2304
+ if (!supportedProviders.includes(config.provider)) {
2305
+ return false;
2306
+ }
2307
+ return config.credentials !== null && config.credentials !== void 0;
2308
+ }
2309
+ static getProviderDisplayNames() {
2310
+ return {
2311
+ ["aws" /* AWS */]: "Amazon Web Services (AWS)",
2312
+ ["gcp" /* GOOGLE_CLOUD */]: "Google Cloud Platform (GCP)",
2313
+ ["azure" /* AZURE */]: "Microsoft Azure",
2314
+ ["alicloud" /* ALIBABA_CLOUD */]: "Alibaba Cloud",
2315
+ ["oracle" /* ORACLE_CLOUD */]: "Oracle Cloud Infrastructure (OCI)"
2316
+ };
2317
+ }
2318
+ static getProviderFromString(provider) {
2319
+ const normalizedProvider = provider.toLowerCase().trim();
2320
+ const providerMap = {
2321
+ "aws": "aws" /* AWS */,
2322
+ "amazon": "aws" /* AWS */,
2323
+ "amazonwebservices": "aws" /* AWS */,
2324
+ "gcp": "gcp" /* GOOGLE_CLOUD */,
2325
+ "google": "gcp" /* GOOGLE_CLOUD */,
2326
+ "googlecloud": "gcp" /* GOOGLE_CLOUD */,
2327
+ "azure": "azure" /* AZURE */,
2328
+ "microsoft": "azure" /* AZURE */,
2329
+ "microsoftazure": "azure" /* AZURE */,
2330
+ "alicloud": "alicloud" /* ALIBABA_CLOUD */,
2331
+ "alibaba": "alicloud" /* ALIBABA_CLOUD */,
2332
+ "alibabacloud": "alicloud" /* ALIBABA_CLOUD */,
2333
+ "oracle": "oracle" /* ORACLE_CLOUD */,
2334
+ "oci": "oracle" /* ORACLE_CLOUD */,
2335
+ "oraclecloud": "oracle" /* ORACLE_CLOUD */
2336
+ };
2337
+ return providerMap[normalizedProvider.replace(/[-_\s]/g, "")] || null;
2338
+ }
2339
+ };
2340
+
2341
+ // src/discovery/profile-discovery.ts
2342
+ import { existsSync, readFileSync } from "fs";
2343
+ import { join } from "path";
2344
+ import { homedir } from "os";
2345
+ import chalk2 from "chalk";
2346
+ var CloudProfileDiscovery = class {
2347
+ constructor() {
2348
+ this.warnings = [];
2349
+ this.homeDirectory = homedir();
2350
+ }
2351
+ /**
2352
+ * Discover all available cloud provider profiles
2353
+ */
2354
+ async discoverAllProfiles() {
2355
+ console.log(chalk2.yellow("\u{1F50D} Discovering cloud provider profiles..."));
2356
+ const results = {
2357
+ totalFound: 0,
2358
+ byProvider: {
2359
+ ["aws" /* AWS */]: [],
2360
+ ["gcp" /* GOOGLE_CLOUD */]: [],
2361
+ ["azure" /* AZURE */]: [],
2362
+ ["alicloud" /* ALIBABA_CLOUD */]: [],
2363
+ ["oracle" /* ORACLE_CLOUD */]: []
2364
+ },
2365
+ recommended: null,
2366
+ warnings: []
2367
+ };
2368
+ const awsProfiles = await this.discoverAWSProfiles();
2369
+ const gcpProfiles = await this.discoverGCPProfiles();
2370
+ const azureProfiles = await this.discoverAzureProfiles();
2371
+ const alicloudProfiles = await this.discoverAlibabaCloudProfiles();
2372
+ const oracleProfiles = await this.discoverOracleCloudProfiles();
2373
+ results.byProvider["aws" /* AWS */] = awsProfiles;
2374
+ results.byProvider["gcp" /* GOOGLE_CLOUD */] = gcpProfiles;
2375
+ results.byProvider["azure" /* AZURE */] = azureProfiles;
2376
+ results.byProvider["alicloud" /* ALIBABA_CLOUD */] = alicloudProfiles;
2377
+ results.byProvider["oracle" /* ORACLE_CLOUD */] = oracleProfiles;
2378
+ results.totalFound = Object.values(results.byProvider).reduce((total, profiles) => total + profiles.length, 0);
2379
+ results.recommended = this.determineRecommendedProfile(results.byProvider);
2380
+ results.warnings = this.warnings;
2381
+ return results;
2382
+ }
2383
+ /**
2384
+ * Discover AWS profiles from ~/.aws/credentials and ~/.aws/config
2385
+ */
2386
+ async discoverAWSProfiles() {
2387
+ const profiles = [];
2388
+ const credentialsPath = join(this.homeDirectory, ".aws", "credentials");
2389
+ const configPath = join(this.homeDirectory, ".aws", "config");
2390
+ if (!existsSync(credentialsPath) && !existsSync(configPath)) {
2391
+ return profiles;
2392
+ }
2393
+ try {
2394
+ const credentialsProfiles = existsSync(credentialsPath) ? this.parseIniFile(credentialsPath) : {};
2395
+ const configProfiles = existsSync(configPath) ? this.parseAWSConfigFile(configPath) : {};
2396
+ const allProfileNames = /* @__PURE__ */ new Set([
2397
+ ...Object.keys(credentialsProfiles),
2398
+ ...Object.keys(configProfiles)
2399
+ ]);
2400
+ for (const profileName of allProfileNames) {
2401
+ const credConfig = credentialsProfiles[profileName] || {};
2402
+ const fileConfig = configProfiles[profileName] || {};
2403
+ if (!credConfig.aws_access_key_id && !fileConfig.role_arn && !fileConfig.sso_start_url) {
2404
+ continue;
2405
+ }
2406
+ profiles.push({
2407
+ name: profileName,
2408
+ provider: "aws" /* AWS */,
2409
+ region: fileConfig.region || credConfig.region || "us-east-1",
2410
+ isDefault: profileName === "default",
2411
+ credentialsPath: existsSync(credentialsPath) ? credentialsPath : void 0,
2412
+ configPath: existsSync(configPath) ? configPath : void 0,
2413
+ status: this.validateAWSProfile(credConfig, fileConfig),
2414
+ lastUsed: this.getLastUsedDate(credentialsPath)
2415
+ });
2416
+ }
2417
+ } catch (error) {
2418
+ this.warnings.push(`Failed to parse AWS profiles: ${error instanceof Error ? error.message : "Unknown error"}`);
2419
+ }
2420
+ return profiles;
2421
+ }
2422
+ /**
2423
+ * Discover Google Cloud profiles from gcloud CLI
2424
+ */
2425
+ async discoverGCPProfiles() {
2426
+ const profiles = [];
2427
+ const gcpConfigDir = join(this.homeDirectory, ".config", "gcloud");
2428
+ if (!existsSync(gcpConfigDir)) {
2429
+ return profiles;
2430
+ }
2431
+ try {
2432
+ const configurationsPath = join(gcpConfigDir, "configurations");
2433
+ if (existsSync(configurationsPath)) {
2434
+ const configFiles = __require("fs").readdirSync(configurationsPath);
2435
+ for (const configFile of configFiles) {
2436
+ if (configFile.startsWith("config_")) {
2437
+ const profileName = configFile.replace("config_", "");
2438
+ const configPath = join(configurationsPath, configFile);
2439
+ profiles.push({
2440
+ name: profileName,
2441
+ provider: "gcp" /* GOOGLE_CLOUD */,
2442
+ isDefault: profileName === "default",
2443
+ configPath,
2444
+ status: "available",
2445
+ lastUsed: this.getLastUsedDate(configPath)
2446
+ });
2447
+ }
2448
+ }
2449
+ }
2450
+ const adcPath = join(gcpConfigDir, "application_default_credentials.json");
2451
+ if (existsSync(adcPath)) {
2452
+ profiles.push({
2453
+ name: "application-default",
2454
+ provider: "gcp" /* GOOGLE_CLOUD */,
2455
+ isDefault: true,
2456
+ credentialsPath: adcPath,
2457
+ status: "available"
2458
+ });
2459
+ }
2460
+ } catch (error) {
2461
+ this.warnings.push(`Failed to discover GCP profiles: ${error instanceof Error ? error.message : "Unknown error"}`);
2462
+ }
2463
+ return profiles;
2464
+ }
2465
+ /**
2466
+ * Discover Azure profiles from Azure CLI
2467
+ */
2468
+ async discoverAzureProfiles() {
2469
+ const profiles = [];
2470
+ const azureConfigDir = join(this.homeDirectory, ".azure");
2471
+ if (!existsSync(azureConfigDir)) {
2472
+ return profiles;
2473
+ }
2474
+ try {
2475
+ const profilesFile = join(azureConfigDir, "azureProfile.json");
2476
+ if (existsSync(profilesFile)) {
2477
+ const profilesData = JSON.parse(readFileSync(profilesFile, "utf8"));
2478
+ if (profilesData.subscriptions && Array.isArray(profilesData.subscriptions)) {
2479
+ profilesData.subscriptions.forEach((sub, index) => {
2480
+ profiles.push({
2481
+ name: sub.name || `subscription-${index + 1}`,
2482
+ provider: "azure" /* AZURE */,
2483
+ isDefault: sub.isDefault === true,
2484
+ status: sub.state === "Enabled" ? "available" : "invalid",
2485
+ configPath: profilesFile
2486
+ });
2487
+ });
2488
+ }
2489
+ }
2490
+ } catch (error) {
2491
+ this.warnings.push(`Failed to discover Azure profiles: ${error instanceof Error ? error.message : "Unknown error"}`);
2492
+ }
2493
+ return profiles;
2494
+ }
2495
+ /**
2496
+ * Discover Alibaba Cloud profiles
2497
+ */
2498
+ async discoverAlibabaCloudProfiles() {
2499
+ const profiles = [];
2500
+ const alicloudConfigPath = join(this.homeDirectory, ".aliyun", "config.json");
2501
+ if (!existsSync(alicloudConfigPath)) {
2502
+ return profiles;
2503
+ }
2504
+ try {
2505
+ const configData = JSON.parse(readFileSync(alicloudConfigPath, "utf8"));
2506
+ if (configData.profiles && Array.isArray(configData.profiles)) {
2507
+ configData.profiles.forEach((profile) => {
2508
+ profiles.push({
2509
+ name: profile.name || "default",
2510
+ provider: "alicloud" /* ALIBABA_CLOUD */,
2511
+ region: profile.region_id || "cn-hangzhou",
2512
+ isDefault: profile.name === "default",
2513
+ configPath: alicloudConfigPath,
2514
+ status: profile.access_key_id ? "available" : "invalid"
2515
+ });
2516
+ });
2517
+ }
2518
+ } catch (error) {
2519
+ this.warnings.push(`Failed to discover Alibaba Cloud profiles: ${error instanceof Error ? error.message : "Unknown error"}`);
2520
+ }
2521
+ return profiles;
2522
+ }
2523
+ /**
2524
+ * Discover Oracle Cloud profiles
2525
+ */
2526
+ async discoverOracleCloudProfiles() {
2527
+ const profiles = [];
2528
+ const ociConfigPath = join(this.homeDirectory, ".oci", "config");
2529
+ if (!existsSync(ociConfigPath)) {
2530
+ return profiles;
2531
+ }
2532
+ try {
2533
+ const configProfiles = this.parseIniFile(ociConfigPath);
2534
+ for (const [profileName, config] of Object.entries(configProfiles)) {
2535
+ profiles.push({
2536
+ name: profileName,
2537
+ provider: "oracle" /* ORACLE_CLOUD */,
2538
+ region: config.region || "us-phoenix-1",
2539
+ isDefault: profileName === "DEFAULT",
2540
+ configPath: ociConfigPath,
2541
+ status: config.user && config.key_file ? "available" : "invalid"
2542
+ });
2543
+ }
2544
+ } catch (error) {
2545
+ this.warnings.push(`Failed to discover Oracle Cloud profiles: ${error instanceof Error ? error.message : "Unknown error"}`);
2546
+ }
2547
+ return profiles;
2548
+ }
2549
+ /**
2550
+ * Display discovered profiles in a formatted table
2551
+ */
2552
+ displayDiscoveryResults(results) {
2553
+ console.log("\n" + chalk2.bold.cyan("\u{1F3AF} Profile Discovery Results"));
2554
+ console.log("\u2550".repeat(60));
2555
+ if (results.totalFound === 0) {
2556
+ console.log(chalk2.yellow("\u26A0\uFE0F No cloud provider profiles found"));
2557
+ console.log(chalk2.gray(" Make sure you have configured at least one cloud provider CLI"));
2558
+ return;
2559
+ }
2560
+ console.log(chalk2.green(`\u2705 Found ${results.totalFound} profiles across ${Object.keys(results.byProvider).length} providers`));
2561
+ for (const [provider, profiles] of Object.entries(results.byProvider)) {
2562
+ if (profiles.length > 0) {
2563
+ console.log(`
2564
+ ${this.getProviderIcon(provider)} ${chalk2.bold(provider.toUpperCase())}: ${profiles.length} profiles`);
2565
+ profiles.forEach((profile, index) => {
2566
+ const statusIcon = profile.status === "available" ? "\u2705" : profile.status === "invalid" ? "\u274C" : "\u26A0\uFE0F";
2567
+ const defaultBadge = profile.isDefault ? chalk2.green(" (default)") : "";
2568
+ console.log(` ${index + 1}. ${profile.name}${defaultBadge} ${statusIcon}`);
2569
+ if (profile.region) {
2570
+ console.log(` Region: ${chalk2.gray(profile.region)}`);
2571
+ }
2572
+ });
2573
+ }
2574
+ }
2575
+ if (results.recommended) {
2576
+ console.log("\n" + chalk2.bold.green("\u{1F31F} Recommended Profile:"));
2577
+ console.log(` ${results.recommended.provider.toUpperCase()}: ${results.recommended.name}`);
2578
+ console.log(chalk2.gray(` Use: --provider ${results.recommended.provider} --profile ${results.recommended.name}`));
2579
+ }
2580
+ if (results.warnings.length > 0) {
2581
+ console.log("\n" + chalk2.bold.yellow("\u26A0\uFE0F Warnings:"));
2582
+ results.warnings.forEach((warning) => {
2583
+ console.log(chalk2.yellow(` \u2022 ${warning}`));
2584
+ });
2585
+ }
2586
+ console.log("\n" + chalk2.bold.cyan("\u{1F4A1} Usage Examples:"));
2587
+ console.log(chalk2.gray(" infra-cost --provider aws --profile production"));
2588
+ console.log(chalk2.gray(" infra-cost --provider gcp --profile my-project"));
2589
+ console.log(chalk2.gray(" infra-cost --all-profiles # Use all available profiles"));
2590
+ }
2591
+ /**
2592
+ * Auto-select best profile based on discovery results
2593
+ */
2594
+ autoSelectProfile(results) {
2595
+ if (results.recommended && results.recommended.status === "available") {
2596
+ return results.recommended;
2597
+ }
2598
+ for (const profiles of Object.values(results.byProvider)) {
2599
+ const defaultProfile = profiles.find((p) => p.isDefault && p.status === "available");
2600
+ if (defaultProfile)
2601
+ return defaultProfile;
2602
+ }
2603
+ for (const profiles of Object.values(results.byProvider)) {
2604
+ const availableProfile = profiles.find((p) => p.status === "available");
2605
+ if (availableProfile)
2606
+ return availableProfile;
2607
+ }
2608
+ return null;
2609
+ }
2610
+ // Helper methods
2611
+ parseIniFile(filePath) {
2612
+ const content = readFileSync(filePath, "utf8");
2613
+ const profiles = {};
2614
+ let currentProfile = "";
2615
+ content.split("\n").forEach((line) => {
2616
+ line = line.trim();
2617
+ if (line.startsWith("[") && line.endsWith("]")) {
2618
+ currentProfile = line.slice(1, -1);
2619
+ profiles[currentProfile] = {};
2620
+ } else if (currentProfile && line.includes("=")) {
2621
+ const [key, ...valueParts] = line.split("=");
2622
+ profiles[currentProfile][key.trim()] = valueParts.join("=").trim();
2623
+ }
2624
+ });
2625
+ return profiles;
2626
+ }
2627
+ parseAWSConfigFile(filePath) {
2628
+ const content = readFileSync(filePath, "utf8");
2629
+ const profiles = {};
2630
+ let currentProfile = "";
2631
+ content.split("\n").forEach((line) => {
2632
+ line = line.trim();
2633
+ if (line.startsWith("[") && line.endsWith("]")) {
2634
+ const profileMatch = line.match(/\[(?:profile\s+)?(.+)\]/);
2635
+ currentProfile = profileMatch ? profileMatch[1] : "";
2636
+ if (currentProfile) {
2637
+ profiles[currentProfile] = {};
2638
+ }
2639
+ } else if (currentProfile && line.includes("=")) {
2640
+ const [key, ...valueParts] = line.split("=");
2641
+ profiles[currentProfile][key.trim()] = valueParts.join("=").trim();
2642
+ }
2643
+ });
2644
+ return profiles;
2645
+ }
2646
+ validateAWSProfile(credConfig, fileConfig) {
2647
+ if (credConfig.aws_access_key_id && credConfig.aws_secret_access_key) {
2648
+ return "available";
2649
+ }
2650
+ if (fileConfig.role_arn || fileConfig.sso_start_url) {
2651
+ return "available";
2652
+ }
2653
+ return "invalid";
2654
+ }
2655
+ getLastUsedDate(filePath) {
2656
+ try {
2657
+ const stats = __require("fs").statSync(filePath);
2658
+ return stats.mtime;
2659
+ } catch {
2660
+ return void 0;
2661
+ }
2662
+ }
2663
+ determineRecommendedProfile(byProvider) {
2664
+ const priority = [
2665
+ "aws" /* AWS */,
2666
+ "gcp" /* GOOGLE_CLOUD */,
2667
+ "azure" /* AZURE */,
2668
+ "alicloud" /* ALIBABA_CLOUD */,
2669
+ "oracle" /* ORACLE_CLOUD */
2670
+ ];
2671
+ for (const provider of priority) {
2672
+ const profiles = byProvider[provider];
2673
+ const defaultProfile = profiles.find((p) => p.isDefault && p.status === "available");
2674
+ if (defaultProfile)
2675
+ return defaultProfile;
2676
+ const availableProfile = profiles.find((p) => p.status === "available");
2677
+ if (availableProfile)
2678
+ return availableProfile;
2679
+ }
2680
+ return null;
2681
+ }
2682
+ getProviderIcon(provider) {
2683
+ const icons = {
2684
+ ["aws" /* AWS */]: "\u2601\uFE0F",
2685
+ ["gcp" /* GOOGLE_CLOUD */]: "\u{1F310}",
2686
+ ["azure" /* AZURE */]: "\u{1F537}",
2687
+ ["alicloud" /* ALIBABA_CLOUD */]: "\u{1F7E0}",
2688
+ ["oracle" /* ORACLE_CLOUD */]: "\u{1F534}"
2689
+ };
2690
+ return icons[provider] || "\u2601\uFE0F";
2691
+ }
2692
+ };
2693
+
2694
+ // src/visualization/terminal-ui.ts
2695
+ import Table from "cli-table3";
2696
+ import chalk3 from "chalk";
2697
+ import cliProgress from "cli-progress";
2698
+ import moment from "moment";
2699
+ var TerminalUIEngine = class {
2700
+ constructor() {
2701
+ this.progressBar = null;
2702
+ }
2703
+ /**
2704
+ * Creates a formatted table with enhanced styling
2705
+ */
2706
+ createTable(columns, rows) {
2707
+ const table = new Table({
2708
+ head: columns.map((col) => chalk3.bold(col.header)),
2709
+ colWidths: columns.map((col) => col.width || 20),
2710
+ colAligns: columns.map((col) => col.align || "left"),
2711
+ style: {
2712
+ head: [],
2713
+ border: [],
2714
+ compact: false
2715
+ },
2716
+ chars: {
2717
+ "top": "\u2500",
2718
+ "top-mid": "\u252C",
2719
+ "top-left": "\u250C",
2720
+ "top-right": "\u2510",
2721
+ "bottom": "\u2500",
2722
+ "bottom-mid": "\u2534",
2723
+ "bottom-left": "\u2514",
2724
+ "bottom-right": "\u2518",
2725
+ "left": "\u2502",
2726
+ "left-mid": "\u251C",
2727
+ "mid": "\u2500",
2728
+ "mid-mid": "\u253C",
2729
+ "right": "\u2502",
2730
+ "right-mid": "\u2524",
2731
+ "middle": "\u2502"
2732
+ }
2733
+ });
2734
+ rows.forEach((row) => {
2735
+ const formattedRow = columns.map((col, index) => {
2736
+ const value = Object.values(row)[index];
2737
+ const colorKey = col.color;
2738
+ if (colorKey && typeof value === "string") {
2739
+ return chalk3[colorKey](value);
2740
+ }
2741
+ return String(value);
2742
+ });
2743
+ table.push(formattedRow);
2744
+ });
2745
+ return table.toString();
2746
+ }
2747
+ /**
2748
+ * Creates a cost breakdown table with rich formatting
2749
+ * Optimized for large datasets with pagination and filtering
2750
+ */
2751
+ createCostTable(costBreakdown, options = {
2752
+ showPercentages: true,
2753
+ highlightTop: 5,
2754
+ currency: "USD",
2755
+ compact: false
2756
+ }) {
2757
+ const { totals, totalsByService } = costBreakdown;
2758
+ let output = "\n" + chalk3.bold.cyan("\u{1F4B0} Cost Analysis Summary") + "\n";
2759
+ output += "\u2550".repeat(50) + "\n\n";
2760
+ const summaryColumns = [
2761
+ { header: "Period", width: 15, align: "left", color: "cyan" },
2762
+ { header: "Cost", width: 15, align: "right", color: "yellow" },
2763
+ { header: "Change", width: 20, align: "right" }
2764
+ ];
2765
+ const summaryRows = [
2766
+ {
2767
+ period: "Yesterday",
2768
+ cost: this.formatCurrency(totals.yesterday, options.currency),
2769
+ change: ""
2770
+ },
2771
+ {
2772
+ period: "Last 7 Days",
2773
+ cost: this.formatCurrency(totals.last7Days, options.currency),
2774
+ change: this.calculateChange(totals.last7Days, totals.yesterday * 7)
2775
+ },
2776
+ {
2777
+ period: "This Month",
2778
+ cost: this.formatCurrency(totals.thisMonth, options.currency),
2779
+ change: this.calculateChange(totals.thisMonth, totals.lastMonth)
2780
+ },
2781
+ {
2782
+ period: "Last Month",
2783
+ cost: this.formatCurrency(totals.lastMonth, options.currency),
2784
+ change: ""
2785
+ }
2786
+ ];
2787
+ output += this.createTable(summaryColumns, summaryRows) + "\n\n";
2788
+ output += chalk3.bold.cyan("\u{1F4CA} Service Breakdown (This Month)") + "\n";
2789
+ output += "\u2550".repeat(50) + "\n\n";
2790
+ const allServiceEntries = Object.entries(totalsByService.thisMonth);
2791
+ const significantServices = allServiceEntries.filter(([_, cost]) => cost > 0.01).sort(([, a], [, b]) => b - a);
2792
+ const maxDisplay = options.highlightTop || 15;
2793
+ const serviceEntries = significantServices.slice(0, maxDisplay);
2794
+ if (allServiceEntries.length > maxDisplay) {
2795
+ const hiddenServices = allServiceEntries.length - maxDisplay;
2796
+ const hiddenCost = significantServices.slice(maxDisplay).reduce((sum, [_, cost]) => sum + cost, 0);
2797
+ if (hiddenCost > 0) {
2798
+ output += chalk3.gray(`Showing top ${maxDisplay} of ${allServiceEntries.length} services `) + chalk3.gray(`(${hiddenServices} services with $${hiddenCost.toFixed(2)} hidden)
2799
+
2800
+ `);
2801
+ }
2802
+ }
2803
+ const serviceColumns = [
2804
+ { header: "Service", width: 25, align: "left", color: "blue" },
2805
+ { header: "Cost", width: 15, align: "right", color: "yellow" },
2806
+ { header: "Share", width: 10, align: "right" },
2807
+ { header: "Trend", width: 15, align: "center" }
2808
+ ];
2809
+ const serviceRows = serviceEntries.map(([service, cost]) => {
2810
+ const share = (cost / totals.thisMonth * 100).toFixed(1);
2811
+ const lastMonthCost = totalsByService.lastMonth[service] || 0;
2812
+ const trend = this.getTrendIndicator(cost, lastMonthCost);
2813
+ return {
2814
+ service,
2815
+ cost: this.formatCurrency(cost, options.currency),
2816
+ share: `${share}%`,
2817
+ trend
2818
+ };
2819
+ });
2820
+ output += this.createTable(serviceColumns, serviceRows);
2821
+ return output;
2822
+ }
2823
+ /**
2824
+ * Creates ASCII trend chart for cost visualization
2825
+ */
2826
+ createTrendChart(trendData, options = {
2827
+ width: 60,
2828
+ showLabels: true,
2829
+ currency: "USD"
2830
+ }) {
2831
+ if (!trendData || trendData.length === 0) {
2832
+ return chalk3.red("No trend data available");
2833
+ }
2834
+ let output = "\n" + chalk3.bold.cyan("\u{1F4C8} Cost Trend Analysis") + "\n";
2835
+ output += "\u2550".repeat(50) + "\n\n";
2836
+ const maxCost = Math.max(...trendData.map((d) => d.actualCost));
2837
+ const minCost = Math.min(...trendData.map((d) => d.actualCost));
2838
+ const range = maxCost - minCost;
2839
+ trendData.forEach((data, index) => {
2840
+ const normalizedValue = range > 0 ? (data.actualCost - minCost) / range : 0.5;
2841
+ const barLength = Math.round(normalizedValue * options.width);
2842
+ const bar = "\u2588".repeat(barLength) + "\u2591".repeat(options.width - barLength);
2843
+ const coloredBar = this.colorizeBar(bar, normalizedValue, options.colorThreshold);
2844
+ const period = moment(data.period).format("MMM YYYY");
2845
+ const cost = this.formatCurrency(data.actualCost, options.currency);
2846
+ const change = data.changeFromPrevious ? this.formatChangeIndicator(data.changeFromPrevious.percentage) : "";
2847
+ output += `${period.padEnd(10)} ${coloredBar} ${cost.padStart(10)} ${change}
2848
+ `;
2849
+ });
2850
+ output += "\n" + "\u2500".repeat(options.width + 25) + "\n";
2851
+ output += `Range: ${this.formatCurrency(minCost, options.currency)} - ${this.formatCurrency(maxCost, options.currency)}
2852
+ `;
2853
+ return output;
2854
+ }
2855
+ /**
2856
+ * Creates a progress bar for long-running operations
2857
+ */
2858
+ startProgress(label, total = 100) {
2859
+ if (this.progressBar) {
2860
+ this.progressBar.stop();
2861
+ }
2862
+ this.progressBar = new cliProgress.SingleBar({
2863
+ format: `${chalk3.cyan(label)} ${chalk3.cyan("[")}${chalk3.yellow("{bar}")}${chalk3.cyan("]")} {percentage}% | ETA: {eta}s | {value}/{total}`,
2864
+ barCompleteChar: "\u2588",
2865
+ barIncompleteChar: "\u2591",
2866
+ hideCursor: true
2867
+ });
2868
+ this.progressBar.start(total, 0);
2869
+ }
2870
+ /**
2871
+ * Updates progress bar
2872
+ */
2873
+ updateProgress(value, payload) {
2874
+ if (this.progressBar) {
2875
+ this.progressBar.update(value, payload);
2876
+ }
2877
+ }
2878
+ /**
2879
+ * Stops and clears progress bar
2880
+ */
2881
+ stopProgress() {
2882
+ if (this.progressBar) {
2883
+ this.progressBar.stop();
2884
+ this.progressBar = null;
2885
+ }
2886
+ }
2887
+ /**
2888
+ * Creates a cost anomaly alert box
2889
+ */
2890
+ createAnomalyAlert(anomalies) {
2891
+ if (anomalies.length === 0) {
2892
+ return chalk3.green("\u2705 No cost anomalies detected");
2893
+ }
2894
+ let output = "\n" + chalk3.bold.red("\u{1F6A8} Cost Anomalies Detected") + "\n";
2895
+ output += "\u2550".repeat(50) + "\n\n";
2896
+ anomalies.forEach((anomaly) => {
2897
+ const severityColor = this.getSeverityColor(anomaly.severity);
2898
+ const icon = this.getSeverityIcon(anomaly.severity);
2899
+ output += chalk3[severityColor](`${icon} ${anomaly.date}
2900
+ `);
2901
+ output += ` Expected: ${this.formatCurrency(anomaly.expectedCost, "USD")}
2902
+ `;
2903
+ output += ` Actual: ${this.formatCurrency(anomaly.actualCost, "USD")}
2904
+ `;
2905
+ output += ` Deviation: ${anomaly.deviation > 0 ? "+" : ""}${anomaly.deviation.toFixed(1)}%
2906
+ `;
2907
+ if (anomaly.description) {
2908
+ output += ` ${chalk3.gray(anomaly.description)}
2909
+ `;
2910
+ }
2911
+ output += "\n";
2912
+ });
2913
+ return output;
2914
+ }
2915
+ /**
2916
+ * Creates a fancy header with branding
2917
+ */
2918
+ createHeader(title, subtitle) {
2919
+ const width = 60;
2920
+ let output = "\n";
2921
+ output += chalk3.cyan("\u250C" + "\u2500".repeat(width - 2) + "\u2510") + "\n";
2922
+ const titlePadding = Math.floor((width - title.length - 4) / 2);
2923
+ output += chalk3.cyan("\u2502") + " ".repeat(titlePadding) + chalk3.bold.white(title) + " ".repeat(width - title.length - titlePadding - 2) + chalk3.cyan("\u2502") + "\n";
2924
+ if (subtitle) {
2925
+ const subtitlePadding = Math.floor((width - subtitle.length - 4) / 2);
2926
+ output += chalk3.cyan("\u2502") + " ".repeat(subtitlePadding) + chalk3.gray(subtitle) + " ".repeat(width - subtitle.length - subtitlePadding - 2) + chalk3.cyan("\u2502") + "\n";
2927
+ }
2928
+ output += chalk3.cyan("\u2514" + "\u2500".repeat(width - 2) + "\u2518") + "\n\n";
2929
+ return output;
2930
+ }
2931
+ // Helper methods
2932
+ formatCurrency(amount, currency) {
2933
+ return new Intl.NumberFormat("en-US", {
2934
+ style: "currency",
2935
+ currency,
2936
+ minimumFractionDigits: 2,
2937
+ maximumFractionDigits: 2
2938
+ }).format(amount);
2939
+ }
2940
+ calculateChange(current, previous) {
2941
+ if (previous === 0)
2942
+ return "";
2943
+ const change = (current - previous) / previous * 100;
2944
+ const changeStr = `${change >= 0 ? "+" : ""}${change.toFixed(1)}%`;
2945
+ if (change > 0) {
2946
+ return chalk3.red(`\u2197 ${changeStr}`);
2947
+ } else if (change < 0) {
2948
+ return chalk3.green(`\u2198 ${changeStr}`);
2949
+ } else {
2950
+ return chalk3.gray("\u2192 0.0%");
2951
+ }
2952
+ }
2953
+ getTrendIndicator(current, previous) {
2954
+ if (previous === 0)
2955
+ return chalk3.gray("\u2500");
2956
+ const change = (current - previous) / previous * 100;
2957
+ if (change > 10)
2958
+ return chalk3.red("\u2197\u2197");
2959
+ if (change > 0)
2960
+ return chalk3.yellow("\u2197");
2961
+ if (change < -10)
2962
+ return chalk3.green("\u2198\u2198");
2963
+ if (change < 0)
2964
+ return chalk3.green("\u2198");
2965
+ return chalk3.gray("\u2192");
2966
+ }
2967
+ colorizeBar(bar, normalizedValue, threshold) {
2968
+ const thresholdValue = threshold || 0.7;
2969
+ if (normalizedValue > thresholdValue) {
2970
+ return chalk3.red(bar);
2971
+ } else if (normalizedValue > 0.4) {
2972
+ return chalk3.yellow(bar);
2973
+ } else {
2974
+ return chalk3.green(bar);
2975
+ }
2976
+ }
2977
+ formatChangeIndicator(percentage) {
2978
+ const indicator = percentage >= 0 ? "\u2197" : "\u2198";
2979
+ const color = percentage >= 0 ? "red" : "green";
2980
+ const sign = percentage >= 0 ? "+" : "";
2981
+ return chalk3[color](`${indicator} ${sign}${percentage.toFixed(1)}%`);
2982
+ }
2983
+ getSeverityColor(severity) {
2984
+ switch (severity.toLowerCase()) {
2985
+ case "critical":
2986
+ return "red";
2987
+ case "high":
2988
+ return "red";
2989
+ case "medium":
2990
+ return "yellow";
2991
+ case "low":
2992
+ return "cyan";
2993
+ default:
2994
+ return "gray";
2995
+ }
2996
+ }
2997
+ getSeverityIcon(severity) {
2998
+ switch (severity.toLowerCase()) {
2999
+ case "critical":
3000
+ return "\u{1F534}";
3001
+ case "high":
3002
+ return "\u{1F7E0}";
3003
+ case "medium":
3004
+ return "\u{1F7E1}";
3005
+ case "low":
3006
+ return "\u{1F535}";
3007
+ default:
3008
+ return "\u26AA";
3009
+ }
3010
+ }
3011
+ };
3012
+
3013
+ // src/visualization/multi-cloud-dashboard.ts
3014
+ var MultiCloudDashboard = class {
3015
+ constructor() {
3016
+ this.ui = new TerminalUIEngine();
3017
+ this.factory = new CloudProviderFactory();
3018
+ this.discovery = new CloudProfileDiscovery();
3019
+ }
3020
+ /**
3021
+ * Create comprehensive multi-cloud inventory dashboard
3022
+ */
3023
+ async generateMultiCloudInventoryDashboard(providers) {
3024
+ console.log(chalk4.yellow("\u{1F310} Gathering multi-cloud inventory..."));
3025
+ const summary = await this.collectMultiCloudInventory(providers);
3026
+ return this.renderMultiCloudDashboard(summary);
3027
+ }
3028
+ /**
3029
+ * Collect inventory from all available cloud providers
3030
+ */
3031
+ async collectMultiCloudInventory(providers) {
3032
+ const summary = {
3033
+ totalProviders: 0,
3034
+ totalResources: 0,
3035
+ totalCost: 0,
3036
+ providerBreakdown: {},
3037
+ consolidatedResourcesByType: {
3038
+ ["compute" /* COMPUTE */]: 0,
3039
+ ["storage" /* STORAGE */]: 0,
3040
+ ["database" /* DATABASE */]: 0,
3041
+ ["network" /* NETWORK */]: 0,
3042
+ ["security" /* SECURITY */]: 0,
3043
+ ["serverless" /* SERVERLESS */]: 0,
3044
+ ["container" /* CONTAINER */]: 0,
3045
+ ["analytics" /* ANALYTICS */]: 0
3046
+ },
3047
+ topResourcesByProvider: []
3048
+ };
3049
+ const discoveryResults = await this.discovery.discoverAllProfiles();
3050
+ const targetProviders = providers || this.factory.getSupportedProviders();
3051
+ for (const provider of targetProviders) {
3052
+ const profiles = discoveryResults.byProvider[provider];
3053
+ summary.providerBreakdown[provider] = {
3054
+ inventory: null,
3055
+ status: "unavailable",
3056
+ resourceCount: 0,
3057
+ cost: 0
3058
+ };
3059
+ if (profiles.length === 0) {
3060
+ continue;
3061
+ }
3062
+ try {
3063
+ const profile = profiles.find((p) => p.status === "available");
3064
+ if (!profile) {
3065
+ summary.providerBreakdown[provider].status = "unavailable";
3066
+ continue;
3067
+ }
3068
+ console.log(chalk4.gray(` Scanning ${provider.toUpperCase()} resources...`));
3069
+ const providerAdapter = this.createProviderAdapter(provider, profile);
3070
+ if (!providerAdapter) {
3071
+ summary.providerBreakdown[provider].status = "error";
3072
+ summary.providerBreakdown[provider].errorMessage = "Failed to initialize provider";
3073
+ continue;
3074
+ }
3075
+ const inventory = await providerAdapter.getResourceInventory({
3076
+ includeCosts: true,
3077
+ resourceTypes: Object.values(ResourceType)
3078
+ });
3079
+ summary.providerBreakdown[provider] = {
3080
+ inventory,
3081
+ status: "active",
3082
+ resourceCount: inventory.totalResources,
3083
+ cost: inventory.totalCost
3084
+ };
3085
+ summary.totalProviders++;
3086
+ summary.totalResources += inventory.totalResources;
3087
+ summary.totalCost += inventory.totalCost;
3088
+ Object.entries(inventory.resourcesByType).forEach(([type, count]) => {
3089
+ summary.consolidatedResourcesByType[type] += count;
3090
+ });
3091
+ } catch (error) {
3092
+ console.warn(chalk4.yellow(`\u26A0\uFE0F Failed to scan ${provider}: ${error instanceof Error ? error.message : "Unknown error"}`));
3093
+ summary.providerBreakdown[provider].status = "error";
3094
+ summary.providerBreakdown[provider].errorMessage = error instanceof Error ? error.message : "Unknown error";
3095
+ }
3096
+ }
3097
+ summary.topResourcesByProvider = this.calculateTopResourcesByProvider(summary);
3098
+ return summary;
3099
+ }
3100
+ /**
3101
+ * Render the multi-cloud dashboard
3102
+ */
3103
+ renderMultiCloudDashboard(summary) {
3104
+ let output = "";
3105
+ output += this.ui.createHeader(
3106
+ "\u{1F310} Multi-Cloud Infrastructure Dashboard",
3107
+ `Comprehensive view across ${summary.totalProviders} cloud providers`
3108
+ );
3109
+ output += chalk4.bold.cyan("\u{1F4CA} Executive Summary") + "\n";
3110
+ output += "\u2550".repeat(60) + "\n\n";
3111
+ const summaryTable = this.ui.createTable([
3112
+ { header: "Metric", width: 25, align: "left", color: "cyan" },
3113
+ { header: "Value", width: 20, align: "right", color: "yellow" },
3114
+ { header: "Details", width: 30, align: "left" }
3115
+ ], [
3116
+ {
3117
+ metric: "Active Providers",
3118
+ value: summary.totalProviders.toString(),
3119
+ details: this.getActiveProvidersList(summary)
3120
+ },
3121
+ {
3122
+ metric: "Total Resources",
3123
+ value: summary.totalResources.toLocaleString(),
3124
+ details: this.getResourceTypeBreakdown(summary)
3125
+ },
3126
+ {
3127
+ metric: "Total Monthly Cost",
3128
+ value: `$${summary.totalCost.toFixed(2)}`,
3129
+ details: this.getCostBreakdownByProvider(summary)
3130
+ }
3131
+ ]);
3132
+ output += summaryTable + "\n\n";
3133
+ output += chalk4.bold.cyan("\u2601\uFE0F Provider Breakdown") + "\n";
3134
+ output += "\u2550".repeat(60) + "\n\n";
3135
+ for (const [provider, data] of Object.entries(summary.providerBreakdown)) {
3136
+ const providerName = this.getProviderDisplayName(provider);
3137
+ const statusIcon = this.getStatusIcon(data.status);
3138
+ const statusColor = this.getStatusColor(data.status);
3139
+ output += `${statusIcon} ${chalk4.bold[statusColor](providerName)}
3140
+ `;
3141
+ if (data.status === "active" && data.inventory) {
3142
+ output += ` Resources: ${chalk4.yellow(data.resourceCount.toLocaleString())}
3143
+ `;
3144
+ output += ` Cost: ${chalk4.green(`$${data.cost.toFixed(2)}`)}
3145
+ `;
3146
+ output += ` Regions: ${chalk4.blue(data.inventory.region)}
3147
+ `;
3148
+ output += ` Last Updated: ${chalk4.gray(data.inventory.lastUpdated.toLocaleString())}
3149
+ `;
3150
+ const topTypes = Object.entries(data.inventory.resourcesByType).filter(([, count]) => count > 0).sort(([, a], [, b]) => b - a).slice(0, 3).map(([type, count]) => `${count} ${type}`).join(", ");
3151
+ if (topTypes) {
3152
+ output += ` Top Types: ${chalk4.gray(topTypes)}
3153
+ `;
3154
+ }
3155
+ } else if (data.status === "error") {
3156
+ output += ` ${chalk4.red("Error: " + (data.errorMessage || "Unknown error"))}
3157
+ `;
3158
+ } else if (data.status === "unavailable") {
3159
+ output += ` ${chalk4.gray("No credentials or profiles configured")}
3160
+ `;
3161
+ }
3162
+ output += "\n";
3163
+ }
3164
+ output += chalk4.bold.cyan("\u{1F4C8} Consolidated Resource Analysis") + "\n";
3165
+ output += "\u2550".repeat(60) + "\n\n";
3166
+ const resourceTypeTable = this.ui.createTable([
3167
+ { header: "Resource Type", width: 20, align: "left", color: "blue" },
3168
+ { header: "Total Count", width: 15, align: "right", color: "yellow" },
3169
+ { header: "Percentage", width: 12, align: "right" },
3170
+ { header: "Top Provider", width: 20, align: "left" }
3171
+ ], this.createResourceTypeRows(summary));
3172
+ output += resourceTypeTable + "\n\n";
3173
+ output += this.generateMultiCloudInsights(summary);
3174
+ output += chalk4.bold.cyan("\u{1F4A1} Multi-Cloud Recommendations") + "\n";
3175
+ output += "\u2550".repeat(60) + "\n";
3176
+ output += this.generateMultiCloudRecommendations(summary);
3177
+ return output;
3178
+ }
3179
+ /**
3180
+ * Generate actionable multi-cloud insights
3181
+ */
3182
+ generateMultiCloudInsights(summary) {
3183
+ let output = chalk4.bold.cyan("\u{1F9E0} Multi-Cloud Insights") + "\n";
3184
+ output += "\u2550".repeat(60) + "\n\n";
3185
+ const insights = [];
3186
+ const activeProviders = Object.values(summary.providerBreakdown).filter((p) => p.status === "active").length;
3187
+ if (activeProviders === 1) {
3188
+ insights.push("\u{1F4CD} Single-cloud deployment detected - consider multi-cloud strategy for resilience");
3189
+ } else if (activeProviders > 3) {
3190
+ insights.push("\u{1F30D} Excellent cloud diversity - you have strong vendor independence");
3191
+ }
3192
+ const providerCosts = Object.entries(summary.providerBreakdown).filter(([, data]) => data.status === "active").map(([provider, data]) => ({ provider, cost: data.cost })).sort((a, b) => b.cost - a.cost);
3193
+ if (providerCosts.length > 1) {
3194
+ const topProvider = providerCosts[0];
3195
+ const costPercentage = topProvider.cost / summary.totalCost * 100;
3196
+ if (costPercentage > 70) {
3197
+ insights.push(`\u{1F4B0} ${topProvider.provider.toUpperCase()} dominates ${costPercentage.toFixed(1)}% of costs - consider rebalancing`);
3198
+ }
3199
+ }
3200
+ const topResourceType = Object.entries(summary.consolidatedResourcesByType).sort(([, a], [, b]) => b - a)[0];
3201
+ if (topResourceType && topResourceType[1] > 0) {
3202
+ const percentage = topResourceType[1] / summary.totalResources * 100;
3203
+ insights.push(`\u{1F527} ${topResourceType[0]} resources account for ${percentage.toFixed(1)}% of total infrastructure`);
3204
+ }
3205
+ insights.forEach((insight, index) => {
3206
+ output += `${index + 1}. ${insight}
3207
+ `;
3208
+ });
3209
+ return output + "\n";
3210
+ }
3211
+ /**
3212
+ * Generate multi-cloud optimization recommendations
3213
+ */
3214
+ generateMultiCloudRecommendations(summary) {
3215
+ let output = "";
3216
+ const recommendations = [];
3217
+ const activeProviders = Object.entries(summary.providerBreakdown).filter(([, data]) => data.status === "active");
3218
+ if (activeProviders.length > 1) {
3219
+ recommendations.push("\u{1F504} Use --compare-clouds to identify cost arbitrage opportunities");
3220
+ recommendations.push("\u{1F4CA} Run --optimization-report for cross-cloud resource rightsizing");
3221
+ }
3222
+ const unavailableProviders = Object.entries(summary.providerBreakdown).filter(([, data]) => data.status === "unavailable");
3223
+ if (unavailableProviders.length > 0) {
3224
+ recommendations.push("\u{1F527} Configure credentials for unavailable providers to get complete visibility");
3225
+ recommendations.push("\u{1F50D} Use --discover-profiles to check for existing but unconfigured profiles");
3226
+ }
3227
+ if (summary.totalCost > 1e3) {
3228
+ recommendations.push("\u{1F4C8} Set up --monitor for real-time cost tracking across all providers");
3229
+ recommendations.push("\u{1F6A8} Configure --alert-threshold for multi-cloud budget management");
3230
+ }
3231
+ recommendations.push("\u{1F3F7}\uFE0F Implement consistent tagging strategy across all cloud providers");
3232
+ recommendations.push("\u{1F512} Use --dependency-mapping to understand cross-cloud resource relationships");
3233
+ recommendations.forEach((rec, index) => {
3234
+ output += chalk4.gray(`${index + 1}. ${rec}
3235
+ `);
3236
+ });
3237
+ output += "\n" + chalk4.bold.yellow("\u26A1 Quick Actions:") + "\n";
3238
+ output += chalk4.gray("\u2022 infra-cost --all-profiles --combine-profiles # Aggregate view\n");
3239
+ output += chalk4.gray("\u2022 infra-cost --compare-clouds aws,gcp,azure # Cost comparison\n");
3240
+ output += chalk4.gray("\u2022 infra-cost --inventory --group-by provider # Detailed inventory\n");
3241
+ return output;
3242
+ }
3243
+ // Helper methods
3244
+ createProviderAdapter(provider, profile) {
3245
+ try {
3246
+ const config = {
3247
+ provider,
3248
+ credentials: profile.credentials || {},
3249
+ region: profile.region,
3250
+ profile: profile.name
3251
+ };
3252
+ return this.factory.createProvider(config);
3253
+ } catch (error) {
3254
+ console.warn(`Failed to create provider adapter for ${provider}: ${error instanceof Error ? error.message : "Unknown error"}`);
3255
+ return null;
3256
+ }
3257
+ }
3258
+ getProviderDisplayName(provider) {
3259
+ const names = CloudProviderFactory.getProviderDisplayNames();
3260
+ return names[provider] || provider.toUpperCase();
3261
+ }
3262
+ getStatusIcon(status) {
3263
+ switch (status) {
3264
+ case "active":
3265
+ return "\u2705";
3266
+ case "unavailable":
3267
+ return "\u26AA";
3268
+ case "error":
3269
+ return "\u274C";
3270
+ default:
3271
+ return "\u26AA";
3272
+ }
3273
+ }
3274
+ getStatusColor(status) {
3275
+ switch (status) {
3276
+ case "active":
3277
+ return "green";
3278
+ case "unavailable":
3279
+ return "gray";
3280
+ case "error":
3281
+ return "red";
3282
+ default:
3283
+ return "gray";
3284
+ }
3285
+ }
3286
+ getActiveProvidersList(summary) {
3287
+ const active = Object.entries(summary.providerBreakdown).filter(([, data]) => data.status === "active").map(([provider]) => provider.toUpperCase());
3288
+ return active.join(", ") || "None";
3289
+ }
3290
+ getResourceTypeBreakdown(summary) {
3291
+ const top3 = Object.entries(summary.consolidatedResourcesByType).filter(([, count]) => count > 0).sort(([, a], [, b]) => b - a).slice(0, 3).map(([type, count]) => `${count} ${type}`);
3292
+ return top3.join(", ") || "None";
3293
+ }
3294
+ getCostBreakdownByProvider(summary) {
3295
+ const costs = Object.entries(summary.providerBreakdown).filter(([, data]) => data.status === "active" && data.cost > 0).map(([provider, data]) => `${provider}: $${data.cost.toFixed(0)}`);
3296
+ return costs.join(", ") || "None";
3297
+ }
3298
+ createResourceTypeRows(summary) {
3299
+ return Object.entries(summary.consolidatedResourcesByType).filter(([, count]) => count > 0).sort(([, a], [, b]) => b - a).map(([type, count]) => {
3300
+ const percentage = (count / summary.totalResources * 100).toFixed(1) + "%";
3301
+ const topProvider = this.getTopProviderForResourceType(summary, type);
3302
+ return {
3303
+ resourceType: type.charAt(0).toUpperCase() + type.slice(1),
3304
+ totalCount: count.toLocaleString(),
3305
+ percentage,
3306
+ topProvider: topProvider ? topProvider.toUpperCase() : "N/A"
3307
+ };
3308
+ });
3309
+ }
3310
+ getTopProviderForResourceType(summary, resourceType) {
3311
+ let maxCount = 0;
3312
+ let topProvider = null;
3313
+ Object.entries(summary.providerBreakdown).forEach(([provider, data]) => {
3314
+ if (data.inventory && data.inventory.resourcesByType[resourceType] > maxCount) {
3315
+ maxCount = data.inventory.resourcesByType[resourceType];
3316
+ topProvider = provider;
3317
+ }
3318
+ });
3319
+ return topProvider;
3320
+ }
3321
+ calculateTopResourcesByProvider(summary) {
3322
+ const results = [];
3323
+ Object.entries(summary.providerBreakdown).forEach(([provider, data]) => {
3324
+ if (data.inventory && data.resourceCount > 0) {
3325
+ Object.entries(data.inventory.resourcesByType).forEach(([type, count]) => {
3326
+ if (count > 0) {
3327
+ results.push({
3328
+ provider,
3329
+ resourceType: type,
3330
+ count,
3331
+ percentage: count / summary.totalResources * 100
3332
+ });
3333
+ }
3334
+ });
3335
+ }
3336
+ });
3337
+ return results.sort((a, b) => b.count - a.count).slice(0, 10);
3338
+ }
3339
+ };
3340
+
3341
+ // src/demo/test-multi-cloud-dashboard.ts
3342
+ async function testMultiCloudDashboard() {
3343
+ console.log("\u{1F680} Testing Multi-Cloud Dashboard...\n");
3344
+ try {
3345
+ const dashboard = new MultiCloudDashboard();
3346
+ console.log("\u{1F4CA} Test 1: Full Multi-Cloud Dashboard");
3347
+ console.log("\u2500".repeat(50));
3348
+ const fullDashboard = await dashboard.generateMultiCloudInventoryDashboard();
3349
+ console.log(fullDashboard);
3350
+ console.log("\n\u{1F4CA} Test 2: Specific Providers Dashboard (AWS + GCP)");
3351
+ console.log("\u2500".repeat(50));
3352
+ const specificProviders = await dashboard.generateMultiCloudInventoryDashboard([
3353
+ "aws" /* AWS */,
3354
+ "gcp" /* GOOGLE_CLOUD */
3355
+ ]);
3356
+ console.log(specificProviders);
3357
+ console.log("\n\u2705 Multi-Cloud Dashboard tests completed!");
3358
+ console.log("\n\u{1F4A1} Usage Examples:");
3359
+ console.log(" infra-cost --multi-cloud-dashboard");
3360
+ console.log(" infra-cost --all-clouds-inventory");
3361
+ console.log(" infra-cost --multi-cloud-dashboard --compare-clouds aws,gcp,azure");
3362
+ console.log(" infra-cost --inventory --all-profiles");
3363
+ } catch (error) {
3364
+ console.error("\u274C Error testing multi-cloud dashboard:", error instanceof Error ? error.message : error);
3365
+ }
3366
+ }
3367
+ if (import.meta.url === `file://${process.argv[1]}`) {
3368
+ testMultiCloudDashboard();
3369
+ }
3370
+ //# sourceMappingURL=test-multi-cloud-dashboard.js.map