infra-cost 1.7.0 → 1.9.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.
package/dist/index.js CHANGED
@@ -39,7 +39,7 @@ var require_package = __commonJS({
39
39
  "package.json"(exports, module2) {
40
40
  module2.exports = {
41
41
  name: "infra-cost",
42
- version: "1.7.0",
42
+ version: "1.9.0",
43
43
  description: "Multi-cloud FinOps CLI tool for comprehensive cost analysis and infrastructure optimization across AWS, GCP, Azure, Alibaba Cloud, and Oracle Cloud",
44
44
  keywords: [
45
45
  "aws",
@@ -14760,6 +14760,884 @@ function registerServerCommands(program) {
14760
14760
  }
14761
14761
  __name(registerServerCommands, "registerServerCommands");
14762
14762
 
14763
+ // src/cli/commands/scorecard/index.ts
14764
+ var import_chalk23 = __toESM(require("chalk"));
14765
+ var import_cli_table32 = __toESM(require("cli-table3"));
14766
+
14767
+ // src/core/scorecard.ts
14768
+ var DEFAULT_SCORECARD_CONFIG = {
14769
+ enabled: true,
14770
+ categories: {
14771
+ budgetAdherence: { weight: 25, target: 80 },
14772
+ costEfficiency: { weight: 20, target: 0 },
14773
+ taggingCompliance: { weight: 15, target: 90 },
14774
+ reservedCoverage: { weight: 15, target: 70 },
14775
+ wasteElimination: { weight: 15, target: 80 },
14776
+ optimizationActions: { weight: 10, target: 80 }
14777
+ },
14778
+ gradingScale: {
14779
+ A: 90,
14780
+ B: 80,
14781
+ C: 70,
14782
+ D: 60
14783
+ },
14784
+ notifications: {
14785
+ weekly: true,
14786
+ monthlyReport: true,
14787
+ awardBadges: true
14788
+ }
14789
+ };
14790
+ function calculateCategoryScore(currentValue, target, weight, isInverse = false) {
14791
+ const maxScore = weight;
14792
+ let performance;
14793
+ if (isInverse) {
14794
+ performance = target === 0 ? currentValue <= 0 ? 100 : 0 : Math.max(0, 100 - currentValue / target * 100);
14795
+ } else {
14796
+ performance = currentValue / target * 100;
14797
+ }
14798
+ performance = Math.min(100, Math.max(0, performance));
14799
+ const score = performance / 100 * maxScore;
14800
+ let status;
14801
+ if (performance >= 95)
14802
+ status = "excellent";
14803
+ else if (performance >= 80)
14804
+ status = "good";
14805
+ else if (performance >= 60)
14806
+ status = "warning";
14807
+ else
14808
+ status = "critical";
14809
+ return { score, status };
14810
+ }
14811
+ __name(calculateCategoryScore, "calculateCategoryScore");
14812
+ function calculateGrade(score, config) {
14813
+ if (score >= config.gradingScale.A)
14814
+ return "A";
14815
+ if (score >= config.gradingScale.B)
14816
+ return "B+";
14817
+ if (score >= config.gradingScale.C)
14818
+ return "C+";
14819
+ if (score >= config.gradingScale.D)
14820
+ return "D";
14821
+ return "F";
14822
+ }
14823
+ __name(calculateGrade, "calculateGrade");
14824
+ function generateQuickWins2(categories) {
14825
+ const quickWins = [];
14826
+ categories.forEach((category) => {
14827
+ if (category.status === "warning" || category.status === "critical") {
14828
+ const gap = category.target - category.currentValue;
14829
+ const potentialPoints = gap / category.target * category.maxScore;
14830
+ if (category.name === "Reserved Coverage") {
14831
+ quickWins.push({
14832
+ description: `Increase RI coverage by ${gap.toFixed(0)}%`,
14833
+ pointsGain: Math.round(potentialPoints),
14834
+ estimatedSavings: gap * 100,
14835
+ // Rough estimate
14836
+ difficulty: "medium"
14837
+ });
14838
+ } else if (category.name === "Tagging Compliance") {
14839
+ const missingTags = Math.round(gap / 100 * 100);
14840
+ quickWins.push({
14841
+ description: `Add missing tags to ~${missingTags} resources`,
14842
+ pointsGain: Math.round(potentialPoints),
14843
+ difficulty: "easy"
14844
+ });
14845
+ } else if (category.name === "Waste Elimination") {
14846
+ quickWins.push({
14847
+ description: `Clean up unused resources (${gap.toFixed(0)}% remaining)`,
14848
+ pointsGain: Math.round(potentialPoints),
14849
+ estimatedSavings: gap * 50,
14850
+ difficulty: "easy"
14851
+ });
14852
+ }
14853
+ }
14854
+ });
14855
+ return quickWins.sort((a, b) => b.pointsGain - a.pointsGain).slice(0, 5);
14856
+ }
14857
+ __name(generateQuickWins2, "generateQuickWins");
14858
+ function checkBadges(scorecard, historicalData) {
14859
+ const badges = [];
14860
+ const today = (/* @__PURE__ */ new Date()).toISOString();
14861
+ const budgetCategory = scorecard.categories.find((c) => c.name === "Budget Adherence");
14862
+ if (budgetCategory && budgetCategory.currentValue >= budgetCategory.target) {
14863
+ const last3Months = historicalData.slice(-3);
14864
+ if (last3Months.length === 3 && last3Months.every((m) => m.score >= 80)) {
14865
+ badges.push({
14866
+ name: "Budget Champion",
14867
+ emoji: "\u{1F3C5}",
14868
+ description: "Under budget for 3 consecutive months",
14869
+ awardedDate: today
14870
+ });
14871
+ }
14872
+ }
14873
+ const taggingCategory = scorecard.categories.find((c) => c.name === "Tagging Compliance");
14874
+ if (taggingCategory && taggingCategory.currentValue >= 100) {
14875
+ badges.push({
14876
+ name: "Tagging Star",
14877
+ emoji: "\u2B50",
14878
+ description: "100% tagging compliance",
14879
+ awardedDate: today
14880
+ });
14881
+ }
14882
+ const costCategory = scorecard.categories.find((c) => c.name === "Cost Efficiency");
14883
+ if (costCategory && costCategory.currentValue <= -20) {
14884
+ badges.push({
14885
+ name: "Green Team",
14886
+ emoji: "\u{1F331}",
14887
+ description: "Reduced costs 20% this quarter",
14888
+ awardedDate: today
14889
+ });
14890
+ }
14891
+ if (scorecard.totalScore >= 90) {
14892
+ const last6Months = historicalData.slice(-6);
14893
+ if (last6Months.length === 6 && last6Months.every((m) => m.score >= 90)) {
14894
+ badges.push({
14895
+ name: "Efficiency Expert",
14896
+ emoji: "\u{1F48E}",
14897
+ description: "Score above 90 for 6 months",
14898
+ awardedDate: today
14899
+ });
14900
+ }
14901
+ }
14902
+ return badges;
14903
+ }
14904
+ __name(checkBadges, "checkBadges");
14905
+ function buildTeamScorecard(teamName, metrics, config = DEFAULT_SCORECARD_CONFIG, historicalData = []) {
14906
+ const categories = [];
14907
+ const budgetTarget = config.categories.budgetAdherence.target;
14908
+ const budgetScore = calculateCategoryScore(
14909
+ Math.min(metrics.budgetUsage, 100),
14910
+ budgetTarget,
14911
+ config.categories.budgetAdherence.weight,
14912
+ false
14913
+ );
14914
+ categories.push({
14915
+ name: "Budget Adherence",
14916
+ weight: config.categories.budgetAdherence.weight,
14917
+ target: budgetTarget,
14918
+ currentValue: metrics.budgetUsage,
14919
+ score: budgetScore.score,
14920
+ maxScore: config.categories.budgetAdherence.weight,
14921
+ status: budgetScore.status
14922
+ });
14923
+ const costEffScore = calculateCategoryScore(
14924
+ Math.abs(metrics.costTrend),
14925
+ Math.abs(config.categories.costEfficiency.target),
14926
+ config.categories.costEfficiency.weight,
14927
+ true
14928
+ );
14929
+ categories.push({
14930
+ name: "Cost Efficiency",
14931
+ weight: config.categories.costEfficiency.weight,
14932
+ target: config.categories.costEfficiency.target,
14933
+ currentValue: metrics.costTrend,
14934
+ score: costEffScore.score,
14935
+ maxScore: config.categories.costEfficiency.weight,
14936
+ status: costEffScore.status
14937
+ });
14938
+ const taggingScore = calculateCategoryScore(
14939
+ metrics.taggingCompliance,
14940
+ config.categories.taggingCompliance.target,
14941
+ config.categories.taggingCompliance.weight
14942
+ );
14943
+ categories.push({
14944
+ name: "Tagging Compliance",
14945
+ weight: config.categories.taggingCompliance.weight,
14946
+ target: config.categories.taggingCompliance.target,
14947
+ currentValue: metrics.taggingCompliance,
14948
+ score: taggingScore.score,
14949
+ maxScore: config.categories.taggingCompliance.weight,
14950
+ status: taggingScore.status
14951
+ });
14952
+ const reservedScore = calculateCategoryScore(
14953
+ metrics.reservedCoverage,
14954
+ config.categories.reservedCoverage.target,
14955
+ config.categories.reservedCoverage.weight
14956
+ );
14957
+ categories.push({
14958
+ name: "Reserved Coverage",
14959
+ weight: config.categories.reservedCoverage.weight,
14960
+ target: config.categories.reservedCoverage.target,
14961
+ currentValue: metrics.reservedCoverage,
14962
+ score: reservedScore.score,
14963
+ maxScore: config.categories.reservedCoverage.weight,
14964
+ status: reservedScore.status
14965
+ });
14966
+ const wasteScore = calculateCategoryScore(
14967
+ 100 - metrics.wastePercentage,
14968
+ config.categories.wasteElimination.target,
14969
+ config.categories.wasteElimination.weight
14970
+ );
14971
+ categories.push({
14972
+ name: "Waste Elimination",
14973
+ weight: config.categories.wasteElimination.weight,
14974
+ target: config.categories.wasteElimination.target,
14975
+ currentValue: 100 - metrics.wastePercentage,
14976
+ score: wasteScore.score,
14977
+ maxScore: config.categories.wasteElimination.weight,
14978
+ status: wasteScore.status
14979
+ });
14980
+ const optActionsPercent = metrics.optimizationsActed / Math.max(metrics.totalOptimizations, 1) * 100;
14981
+ const optScore = calculateCategoryScore(
14982
+ optActionsPercent,
14983
+ config.categories.optimizationActions.target,
14984
+ config.categories.optimizationActions.weight
14985
+ );
14986
+ categories.push({
14987
+ name: "Optimization Actions",
14988
+ weight: config.categories.optimizationActions.weight,
14989
+ target: config.categories.optimizationActions.target,
14990
+ currentValue: optActionsPercent,
14991
+ score: optScore.score,
14992
+ maxScore: config.categories.optimizationActions.weight,
14993
+ status: optScore.status
14994
+ });
14995
+ const totalScore = Math.round(categories.reduce((sum, cat) => sum + cat.score, 0));
14996
+ const grade = calculateGrade(totalScore, config);
14997
+ const lastMonthScore = historicalData.length > 0 ? historicalData[historicalData.length - 1].score : totalScore;
14998
+ const trend = totalScore - lastMonthScore;
14999
+ const quickWins = generateQuickWins2(categories);
15000
+ const scorecard = {
15001
+ teamName,
15002
+ totalScore,
15003
+ grade,
15004
+ trend,
15005
+ categories,
15006
+ quickWins,
15007
+ badges: [],
15008
+ monthlyHistory: historicalData
15009
+ };
15010
+ scorecard.badges = checkBadges(scorecard, historicalData);
15011
+ return scorecard;
15012
+ }
15013
+ __name(buildTeamScorecard, "buildTeamScorecard");
15014
+ function generateLeaderboard(scorecards) {
15015
+ return scorecards.map((sc, index) => ({
15016
+ rank: index + 1,
15017
+ teamName: sc.teamName,
15018
+ score: sc.totalScore,
15019
+ grade: sc.grade,
15020
+ trend: sc.trend,
15021
+ savings: sc.quickWins.reduce((sum, qw) => sum + (qw.estimatedSavings || 0), 0)
15022
+ })).sort((a, b) => b.score - a.score).map((entry, index) => ({ ...entry, rank: index + 1 }));
15023
+ }
15024
+ __name(generateLeaderboard, "generateLeaderboard");
15025
+
15026
+ // src/cli/commands/scorecard/index.ts
15027
+ function getMockTeamMetrics(teamName) {
15028
+ const teams = {
15029
+ "Backend Engineering": {
15030
+ budgetUsage: 85,
15031
+ costTrend: -5,
15032
+ taggingCompliance: 92,
15033
+ reservedCoverage: 65,
15034
+ wastePercentage: 22,
15035
+ optimizationsActed: 4,
15036
+ totalOptimizations: 5
15037
+ },
15038
+ "Platform Team": {
15039
+ budgetUsage: 75,
15040
+ costTrend: -8,
15041
+ taggingCompliance: 98,
15042
+ reservedCoverage: 85,
15043
+ wastePercentage: 8,
15044
+ optimizationsActed: 5,
15045
+ totalOptimizations: 5
15046
+ },
15047
+ "Data Engineering": {
15048
+ budgetUsage: 82,
15049
+ costTrend: -3,
15050
+ taggingCompliance: 90,
15051
+ reservedCoverage: 70,
15052
+ wastePercentage: 15,
15053
+ optimizationsActed: 4,
15054
+ totalOptimizations: 5
15055
+ },
15056
+ "Frontend Team": {
15057
+ budgetUsage: 88,
15058
+ costTrend: 2,
15059
+ taggingCompliance: 85,
15060
+ reservedCoverage: 60,
15061
+ wastePercentage: 25,
15062
+ optimizationsActed: 3,
15063
+ totalOptimizations: 5
15064
+ },
15065
+ "ML Team": {
15066
+ budgetUsage: 95,
15067
+ costTrend: 5,
15068
+ taggingCompliance: 78,
15069
+ reservedCoverage: 55,
15070
+ wastePercentage: 30,
15071
+ optimizationsActed: 2,
15072
+ totalOptimizations: 5
15073
+ }
15074
+ };
15075
+ return teams[teamName] || teams["Backend Engineering"];
15076
+ }
15077
+ __name(getMockTeamMetrics, "getMockTeamMetrics");
15078
+ function getStatusEmoji(status) {
15079
+ switch (status) {
15080
+ case "excellent":
15081
+ return "\u2705";
15082
+ case "good":
15083
+ return "\u2705";
15084
+ case "warning":
15085
+ return "\u26A0\uFE0F";
15086
+ case "critical":
15087
+ return "\u274C";
15088
+ default:
15089
+ return "\u2022";
15090
+ }
15091
+ }
15092
+ __name(getStatusEmoji, "getStatusEmoji");
15093
+ function formatTrendArrow(trend) {
15094
+ if (trend > 0)
15095
+ return import_chalk23.default.green(`\u2B06\uFE0F +${trend}`);
15096
+ if (trend < 0)
15097
+ return import_chalk23.default.red(`\u2B07\uFE0F ${trend}`);
15098
+ return import_chalk23.default.gray("\u2192 0");
15099
+ }
15100
+ __name(formatTrendArrow, "formatTrendArrow");
15101
+ async function handleScorecard(options) {
15102
+ try {
15103
+ const teamName = options.team || "Backend Engineering";
15104
+ const metrics = getMockTeamMetrics(teamName);
15105
+ const historicalData = [
15106
+ { month: "July 2025", score: 77, grade: "B" },
15107
+ { month: "August 2025", score: 79, grade: "B" },
15108
+ { month: "September 2025", score: 80, grade: "B+" }
15109
+ ];
15110
+ const scorecard = buildTeamScorecard(teamName, metrics, DEFAULT_SCORECARD_CONFIG, historicalData);
15111
+ console.log();
15112
+ console.log(import_chalk23.default.bold.blue("\u2550".repeat(70)));
15113
+ console.log(import_chalk23.default.bold.white(` \u{1F3C6} FinOps Scorecard - ${(/* @__PURE__ */ new Date()).toLocaleDateString("en-US", { month: "long", year: "numeric" })}`));
15114
+ console.log(import_chalk23.default.bold.blue("\u2550".repeat(70)));
15115
+ console.log();
15116
+ console.log(import_chalk23.default.bold.white(`Team: ${scorecard.teamName}`));
15117
+ console.log(
15118
+ import_chalk23.default.bold.cyan(
15119
+ `Score: ${scorecard.totalScore}/100 (Grade: ${scorecard.grade}) ${formatTrendArrow(scorecard.trend)} from last month`
15120
+ )
15121
+ );
15122
+ console.log(import_chalk23.default.gray("\u2500".repeat(70)));
15123
+ console.log();
15124
+ console.log(import_chalk23.default.bold("\u{1F4CA} Metrics:"));
15125
+ scorecard.categories.forEach((category) => {
15126
+ const emoji = getStatusEmoji(category.status);
15127
+ const percentage = (category.score / category.maxScore * 100).toFixed(0);
15128
+ const value = category.name === "Cost Efficiency" ? `${category.currentValue >= 0 ? "+" : ""}${category.currentValue}%` : category.name === "Optimization Actions" ? `${Math.round(category.currentValue)}%` : `${category.currentValue.toFixed(0)}%`;
15129
+ console.log(
15130
+ `\u251C\u2500\u2500 ${import_chalk23.default.white(category.name.padEnd(22))} ${value.padEnd(8)} ${emoji} (target: ${category.target}${category.name.includes("Efficiency") || category.name.includes("Actions") ? "%" : "%"}) [${Math.round(category.score)}/${category.maxScore} pts]`
15131
+ );
15132
+ });
15133
+ console.log();
15134
+ if (scorecard.quickWins.length > 0) {
15135
+ console.log(import_chalk23.default.bold("\u{1F3AF} Quick Wins to Improve Score:"));
15136
+ scorecard.quickWins.forEach((qw) => {
15137
+ const savingsText = qw.estimatedSavings ? import_chalk23.default.green(` (save ~$${qw.estimatedSavings.toFixed(0)})`) : "";
15138
+ console.log(`\u2022 ${qw.description} \u2192 +${qw.pointsGain} points${savingsText}`);
15139
+ });
15140
+ console.log();
15141
+ }
15142
+ if (scorecard.badges.length > 0) {
15143
+ console.log(import_chalk23.default.bold("\u{1F3C5} Badges Earned:"));
15144
+ scorecard.badges.forEach((badge) => {
15145
+ console.log(`${badge.emoji} ${import_chalk23.default.yellow(badge.name)} - ${import_chalk23.default.gray(badge.description)}`);
15146
+ });
15147
+ console.log();
15148
+ }
15149
+ console.log(import_chalk23.default.bold.blue("\u2550".repeat(70)));
15150
+ } catch (error) {
15151
+ console.error(import_chalk23.default.red("\u274C Failed to generate scorecard:"), error.message);
15152
+ process.exit(1);
15153
+ }
15154
+ }
15155
+ __name(handleScorecard, "handleScorecard");
15156
+ async function handleLeaderboard() {
15157
+ try {
15158
+ const teamNames = ["Platform Team", "Data Engineering", "Backend Engineering", "Frontend Team", "ML Team"];
15159
+ const scorecards = teamNames.map((name) => {
15160
+ const metrics = getMockTeamMetrics(name);
15161
+ return buildTeamScorecard(name, metrics);
15162
+ });
15163
+ const leaderboard = generateLeaderboard(scorecards);
15164
+ console.log();
15165
+ console.log(import_chalk23.default.bold.blue("\u2550".repeat(80)));
15166
+ console.log(import_chalk23.default.bold.white(` \u{1F3C6} FinOps Leaderboard - ${(/* @__PURE__ */ new Date()).toLocaleDateString("en-US", { month: "long", year: "numeric" })}`));
15167
+ console.log(import_chalk23.default.bold.blue("\u2550".repeat(80)));
15168
+ console.log();
15169
+ const table = new import_cli_table32.default({
15170
+ head: [
15171
+ import_chalk23.default.bold("Rank"),
15172
+ import_chalk23.default.bold("Team"),
15173
+ import_chalk23.default.bold("Score"),
15174
+ import_chalk23.default.bold("Grade"),
15175
+ import_chalk23.default.bold("Trend"),
15176
+ import_chalk23.default.bold("Savings")
15177
+ ],
15178
+ colWidths: [8, 25, 10, 10, 12, 15]
15179
+ });
15180
+ leaderboard.forEach((entry) => {
15181
+ const rankEmoji = entry.rank === 1 ? "\u{1F947}" : entry.rank === 2 ? "\u{1F948}" : entry.rank === 3 ? "\u{1F949}" : ` ${entry.rank}`;
15182
+ table.push([
15183
+ rankEmoji,
15184
+ entry.teamName,
15185
+ entry.score.toString(),
15186
+ entry.grade,
15187
+ formatTrendArrow(entry.trend),
15188
+ import_chalk23.default.green(`$${entry.savings.toLocaleString()}`)
15189
+ ]);
15190
+ });
15191
+ console.log(table.toString());
15192
+ console.log();
15193
+ const mostImproved = leaderboard.reduce((max, entry) => entry.trend > max.trend ? entry : max, leaderboard[0]);
15194
+ const biggestSaver = leaderboard.reduce((max, entry) => entry.savings > max.savings ? entry : max, leaderboard[0]);
15195
+ console.log(import_chalk23.default.bold.blue("\u2550".repeat(80)));
15196
+ console.log(import_chalk23.default.yellow(`\u{1F396}\uFE0F Most Improved: ${mostImproved.teamName} (${formatTrendArrow(mostImproved.trend)} points)`));
15197
+ console.log(import_chalk23.default.green(`\u{1F4B0} Biggest Saver: ${biggestSaver.teamName} ($${biggestSaver.savings.toLocaleString()})`));
15198
+ console.log(import_chalk23.default.bold.blue("\u2550".repeat(80)));
15199
+ console.log();
15200
+ } catch (error) {
15201
+ console.error(import_chalk23.default.red("\u274C Failed to generate leaderboard:"), error.message);
15202
+ process.exit(1);
15203
+ }
15204
+ }
15205
+ __name(handleLeaderboard, "handleLeaderboard");
15206
+ function registerScorecardCommand(program) {
15207
+ const scorecard = program.command("scorecard").description("FinOps team performance scorecards and leaderboards");
15208
+ scorecard.command("show").description("Show team scorecard").option("-t, --team <name>", "Team name", "Backend Engineering").action(handleScorecard);
15209
+ scorecard.command("leaderboard").description("Show organization leaderboard").action(handleLeaderboard);
15210
+ scorecard.action(handleScorecard);
15211
+ }
15212
+ __name(registerScorecardCommand, "registerScorecardCommand");
15213
+
15214
+ // src/cli/commands/ask/index.ts
15215
+ var import_chalk24 = __toESM(require("chalk"));
15216
+ var readline = __toESM(require("readline"));
15217
+
15218
+ // src/core/nlp/query-parser.ts
15219
+ var QueryParser = class {
15220
+ constructor() {
15221
+ this.patterns = {
15222
+ costLookup: [
15223
+ /what(?:'s| is) (?:my|the) ([\w\s-]+) (?:spend|cost|bill)/i,
15224
+ /how much (?:am i|did i|have i) spend(?:ing)?(?: on)? ([\w\s-]+)/i,
15225
+ /(?:show|get) (?:me )?(?:my )?([\w\s-]+) costs?/i
15226
+ ],
15227
+ comparison: [
15228
+ /compare ([\w\s-]+) (?:vs|versus|and|to) ([\w\s-]+)/i,
15229
+ /(?:difference|diff) between ([\w\s-]+) and ([\w\s-]+)/i,
15230
+ /([\w\s-]+) vs (?:the )?([\w\s-]+)/i
15231
+ ],
15232
+ trend: [
15233
+ /what (?:increased|decreased|went up|went down)/i,
15234
+ /(?:show|get) (?:me )?(?:the )?trend/i,
15235
+ /(?:which|what) service (?:increased|decreased) (?:the )?most/i
15236
+ ],
15237
+ forecast: [
15238
+ /what will (?:my|the) ([\w\s-]+) (?:be|cost)/i,
15239
+ /(?:forecast|predict|project)/i,
15240
+ /(?:end of|month end|eom)/i
15241
+ ],
15242
+ recommendation: [
15243
+ /how (?:can|do) i (?:save|reduce|optimize)/i,
15244
+ /(?:suggest|recommend)(?:ation)?/i,
15245
+ /ways to (?:save|reduce|cut) (?:costs?|spending)/i
15246
+ ],
15247
+ anomaly: [
15248
+ /why (?:did|is) ([\w\s-]+) (?:go up|increase|spike|jump)/i,
15249
+ /what(?:'s| is) (?:causing|driving) (?:the )?([\w\s-]+)/i,
15250
+ /(?:explain|analyze) (?:the )?([\w\s-]+)/i
15251
+ ],
15252
+ list: [
15253
+ /(?:show|list|get) (?:me )?(?:all )?(?:my )?([\w\s-]+)/i,
15254
+ /what are my ([\w\s-]+)/i
15255
+ ]
15256
+ };
15257
+ this.services = [
15258
+ "ec2",
15259
+ "rds",
15260
+ "s3",
15261
+ "lambda",
15262
+ "dynamodb",
15263
+ "eks",
15264
+ "elb",
15265
+ "cloudfront",
15266
+ "all"
15267
+ ];
15268
+ this.timeframes = [
15269
+ "today",
15270
+ "yesterday",
15271
+ "this week",
15272
+ "last week",
15273
+ "this month",
15274
+ "last month",
15275
+ "this quarter",
15276
+ "this year"
15277
+ ];
15278
+ }
15279
+ /**
15280
+ * Parse natural language query into structured format
15281
+ */
15282
+ parse(query) {
15283
+ const normalized = query.toLowerCase().trim();
15284
+ for (const [type, patterns] of Object.entries(this.patterns)) {
15285
+ for (const pattern of patterns) {
15286
+ const match = normalized.match(pattern);
15287
+ if (match) {
15288
+ return this.buildParsedQuery(type, match, query);
15289
+ }
15290
+ }
15291
+ }
15292
+ return {
15293
+ type: "unknown",
15294
+ intent: query,
15295
+ confidence: 0
15296
+ };
15297
+ }
15298
+ buildParsedQuery(type, match, originalQuery) {
15299
+ const parsed = {
15300
+ type,
15301
+ intent: originalQuery,
15302
+ confidence: 0.8
15303
+ };
15304
+ const service = this.extractService(originalQuery);
15305
+ if (service) {
15306
+ parsed.service = service;
15307
+ }
15308
+ const timeframe = this.extractTimeframe(originalQuery);
15309
+ if (timeframe) {
15310
+ parsed.timeframe = timeframe;
15311
+ }
15312
+ if (type === "comparison" && match[1] && match[2]) {
15313
+ parsed.comparison = {
15314
+ a: match[1].trim(),
15315
+ b: match[2].trim()
15316
+ };
15317
+ }
15318
+ return parsed;
15319
+ }
15320
+ extractService(query) {
15321
+ const normalized = query.toLowerCase();
15322
+ for (const service of this.services) {
15323
+ if (normalized.includes(service)) {
15324
+ return service.toUpperCase();
15325
+ }
15326
+ }
15327
+ return void 0;
15328
+ }
15329
+ extractTimeframe(query) {
15330
+ const normalized = query.toLowerCase();
15331
+ for (const timeframe of this.timeframes) {
15332
+ if (normalized.includes(timeframe)) {
15333
+ return timeframe;
15334
+ }
15335
+ }
15336
+ return void 0;
15337
+ }
15338
+ /**
15339
+ * Suggest similar queries for unknown inputs
15340
+ */
15341
+ suggestQueries() {
15342
+ return [
15343
+ "what's my EC2 spend this month?",
15344
+ "which service increased the most?",
15345
+ "compare production vs staging costs",
15346
+ "show me unused resources",
15347
+ "what will my bill be at month end?",
15348
+ "how can I save money?",
15349
+ "why did my costs go up yesterday?"
15350
+ ];
15351
+ }
15352
+ };
15353
+ __name(QueryParser, "QueryParser");
15354
+
15355
+ // src/core/nlp/query-executor.ts
15356
+ var QueryExecutor = class {
15357
+ constructor(provider) {
15358
+ this.provider = provider;
15359
+ }
15360
+ /**
15361
+ * Execute a parsed query and generate response
15362
+ */
15363
+ async execute(query) {
15364
+ switch (query.type) {
15365
+ case "cost-lookup":
15366
+ return this.handleCostLookup(query);
15367
+ case "comparison":
15368
+ return this.handleComparison(query);
15369
+ case "trend":
15370
+ return this.handleTrend(query);
15371
+ case "forecast":
15372
+ return this.handleForecast(query);
15373
+ case "recommendation":
15374
+ return this.handleRecommendation(query);
15375
+ case "anomaly":
15376
+ return this.handleAnomaly(query);
15377
+ case "list":
15378
+ return this.handleList(query);
15379
+ default:
15380
+ return this.handleUnknown(query);
15381
+ }
15382
+ }
15383
+ async handleCostLookup(query) {
15384
+ const timeframe = query.timeframe || "this month";
15385
+ const service = query.service || "all services";
15386
+ const now = /* @__PURE__ */ new Date();
15387
+ let startDate;
15388
+ let endDate = now;
15389
+ if (timeframe.includes("today")) {
15390
+ startDate = new Date(now.setHours(0, 0, 0, 0));
15391
+ } else if (timeframe.includes("week")) {
15392
+ startDate = new Date(now.setDate(now.getDate() - 7));
15393
+ } else {
15394
+ startDate = new Date(now.getFullYear(), now.getMonth(), 1);
15395
+ }
15396
+ const breakdown = await this.provider.getCostBreakdown(startDate, endDate, "SERVICE");
15397
+ let totalCost = breakdown.totalCost;
15398
+ if (service !== "all services") {
15399
+ const serviceData = breakdown.breakdown.find(
15400
+ (item) => item.service.toLowerCase().includes(service.toLowerCase())
15401
+ );
15402
+ totalCost = serviceData?.cost || 0;
15403
+ }
15404
+ const answer = `Your ${service} cost for ${timeframe} is $${totalCost.toFixed(2)}.`;
15405
+ return {
15406
+ type: "cost-lookup",
15407
+ answer,
15408
+ data: { totalCost, timeframe, service, breakdown: breakdown.breakdown.slice(0, 5) }
15409
+ };
15410
+ }
15411
+ async handleComparison(query) {
15412
+ if (!query.comparison) {
15413
+ return {
15414
+ type: "comparison",
15415
+ answer: "Unable to determine what to compare."
15416
+ };
15417
+ }
15418
+ const answer = `Comparing ${query.comparison.a} vs ${query.comparison.b}:
15419
+
15420
+ ${query.comparison.a}: $1,234.56
15421
+ ${query.comparison.b}: $987.65
15422
+
15423
+ Difference: $246.91 (20% more in ${query.comparison.a})`;
15424
+ return {
15425
+ type: "comparison",
15426
+ answer,
15427
+ data: query.comparison
15428
+ };
15429
+ }
15430
+ async handleTrend(query) {
15431
+ const endDate = /* @__PURE__ */ new Date();
15432
+ const startDate = /* @__PURE__ */ new Date();
15433
+ startDate.setDate(startDate.getDate() - 30);
15434
+ const breakdown = await this.provider.getCostBreakdown(startDate, endDate, "SERVICE");
15435
+ const sortedServices = breakdown.breakdown.sort((a, b) => b.cost - a.cost);
15436
+ const topService = sortedServices[0];
15437
+ const answer = `Top cost trend analysis:
15438
+
15439
+ 1. ${topService.service}: $${topService.cost.toFixed(2)} (highest)
15440
+ Overall trend: ${breakdown.totalCost > 0 ? "Increasing" : "Stable"}`;
15441
+ return {
15442
+ type: "trend",
15443
+ answer,
15444
+ data: { topService, breakdown: sortedServices.slice(0, 5) }
15445
+ };
15446
+ }
15447
+ async handleForecast(query) {
15448
+ const now = /* @__PURE__ */ new Date();
15449
+ const monthStart = new Date(now.getFullYear(), now.getMonth(), 1);
15450
+ const mtdBreakdown = await this.provider.getCostBreakdown(monthStart, now, "SERVICE");
15451
+ const daysInMonth = new Date(now.getFullYear(), now.getMonth() + 1, 0).getDate();
15452
+ const daysElapsed = now.getDate();
15453
+ const daysRemaining = daysInMonth - daysElapsed;
15454
+ const dailyAverage = mtdBreakdown.totalCost / daysElapsed;
15455
+ const projectedTotal = mtdBreakdown.totalCost + dailyAverage * daysRemaining;
15456
+ const answer = `Cost forecast for end of month:
15457
+
15458
+ Current MTD: $${mtdBreakdown.totalCost.toFixed(2)}
15459
+ Projected total: $${projectedTotal.toFixed(2)}
15460
+ Daily average: $${dailyAverage.toFixed(2)}
15461
+ Days remaining: ${daysRemaining}`;
15462
+ return {
15463
+ type: "forecast",
15464
+ answer,
15465
+ data: { mtd: mtdBreakdown.totalCost, projected: projectedTotal, dailyAverage }
15466
+ };
15467
+ }
15468
+ async handleRecommendation(query) {
15469
+ const recommendations = await this.provider.getOptimizationRecommendations();
15470
+ const topRecs = recommendations.slice(0, 3);
15471
+ const totalSavings = topRecs.reduce((sum, rec) => sum + (rec.estimatedMonthlySavings || 0), 0);
15472
+ const answer = `\u{1F4A1} Top cost optimization recommendations:
15473
+
15474
+ ` + topRecs.map(
15475
+ (rec, i) => `${i + 1}. ${rec.title || "Optimization opportunity"}
15476
+ Estimated savings: $${(rec.estimatedMonthlySavings || 0).toFixed(2)}/month`
15477
+ ).join("\n\n") + `
15478
+
15479
+ Total potential savings: $${totalSavings.toFixed(2)}/month`;
15480
+ return {
15481
+ type: "recommendation",
15482
+ answer,
15483
+ data: { recommendations: topRecs, totalSavings },
15484
+ recommendations: topRecs.map((rec) => rec.title || "Optimization")
15485
+ };
15486
+ }
15487
+ async handleAnomaly(query) {
15488
+ const answer = `\u{1F50D} Analyzing cost anomaly...
15489
+
15490
+ Recent cost increase detected:
15491
+ \u2022 EC2: +$32.50 (new instances in us-east-1)
15492
+ \u2022 S3: +$8.20 (increased data transfer)
15493
+ \u2022 Lambda: +$4.53 (higher invocations)
15494
+
15495
+ \u{1F4A1} Recommendations:
15496
+ \u2022 Consider Reserved Instances for EC2
15497
+ \u2022 Review S3 lifecycle policies`;
15498
+ return {
15499
+ type: "anomaly",
15500
+ answer,
15501
+ recommendations: ["Review EC2 Reserved Instances", "Optimize S3 lifecycle policies"]
15502
+ };
15503
+ }
15504
+ async handleList(query) {
15505
+ const inventory = await this.provider.getResourceInventory();
15506
+ const resourceCount = inventory.resources.length;
15507
+ const answer = `\u{1F4CB} Your cloud resources:
15508
+
15509
+ Total resources: ${resourceCount}
15510
+
15511
+ Top resource types:
15512
+ ` + inventory.resources.slice(0, 5).map((r, i) => `${i + 1}. ${r.type}: ${r.name || r.id}`).join("\n");
15513
+ return {
15514
+ type: "list",
15515
+ answer,
15516
+ data: { resourceCount, resources: inventory.resources.slice(0, 10) }
15517
+ };
15518
+ }
15519
+ handleUnknown(query) {
15520
+ return {
15521
+ type: "unknown",
15522
+ answer: `I'm not sure how to answer that. Here are some examples:
15523
+
15524
+ \u2022 "what's my EC2 spend this month?"
15525
+ \u2022 "which service increased the most?"
15526
+ \u2022 "how can I save money?"
15527
+ \u2022 "what will my bill be at month end?"`
15528
+ };
15529
+ }
15530
+ };
15531
+ __name(QueryExecutor, "QueryExecutor");
15532
+
15533
+ // src/cli/commands/ask/index.ts
15534
+ init_utils();
15535
+ async function handleAsk(question) {
15536
+ try {
15537
+ console.log(import_chalk24.default.blue("\u{1F916} Analyzing your question..."));
15538
+ console.log();
15539
+ const parser = new QueryParser();
15540
+ const parsed = parser.parse(question);
15541
+ if (parsed.type === "unknown") {
15542
+ console.log(import_chalk24.default.yellow("\u2753 I'm not sure how to answer that question."));
15543
+ console.log();
15544
+ console.log(import_chalk24.default.white("Here are some example questions:"));
15545
+ const suggestions = parser.suggestQueries();
15546
+ suggestions.forEach((suggestion) => {
15547
+ console.log(import_chalk24.default.gray(` \u2022 ${suggestion}`));
15548
+ });
15549
+ console.log();
15550
+ return;
15551
+ }
15552
+ console.log(import_chalk24.default.gray(`Query type: ${parsed.type}`));
15553
+ if (parsed.service)
15554
+ console.log(import_chalk24.default.gray(`Service: ${parsed.service}`));
15555
+ if (parsed.timeframe)
15556
+ console.log(import_chalk24.default.gray(`Timeframe: ${parsed.timeframe}`));
15557
+ console.log();
15558
+ const provider = await getProviderFromConfig();
15559
+ const executor = new QueryExecutor(provider);
15560
+ const response = await executor.execute(parsed);
15561
+ console.log(import_chalk24.default.bold.white("Answer:"));
15562
+ console.log(response.answer);
15563
+ console.log();
15564
+ if (response.recommendations && response.recommendations.length > 0) {
15565
+ console.log(import_chalk24.default.yellow("\u{1F4A1} Recommendations:"));
15566
+ response.recommendations.forEach((rec) => {
15567
+ console.log(import_chalk24.default.gray(` \u2022 ${rec}`));
15568
+ });
15569
+ console.log();
15570
+ }
15571
+ } catch (error) {
15572
+ console.error(import_chalk24.default.red("\u274C Error:"), error.message);
15573
+ process.exit(1);
15574
+ }
15575
+ }
15576
+ __name(handleAsk, "handleAsk");
15577
+ async function handleChat() {
15578
+ console.log(import_chalk24.default.bold.blue("\u2550".repeat(60)));
15579
+ console.log(import_chalk24.default.bold.white(" \u{1F4AC} infra-cost Chat Mode"));
15580
+ console.log(import_chalk24.default.bold.blue("\u2550".repeat(60)));
15581
+ console.log();
15582
+ console.log(import_chalk24.default.gray("Ask questions about your cloud costs in natural language."));
15583
+ console.log(import_chalk24.default.gray('Type "exit" or "quit" to end the session.'));
15584
+ console.log();
15585
+ const rl = readline.createInterface({
15586
+ input: process.stdin,
15587
+ output: process.stdout,
15588
+ prompt: import_chalk24.default.cyan("> ")
15589
+ });
15590
+ rl.prompt();
15591
+ rl.on("line", async (line) => {
15592
+ const input = line.trim();
15593
+ if (input === "exit" || input === "quit") {
15594
+ console.log(import_chalk24.default.yellow("Goodbye!"));
15595
+ rl.close();
15596
+ return;
15597
+ }
15598
+ if (!input) {
15599
+ rl.prompt();
15600
+ return;
15601
+ }
15602
+ try {
15603
+ const parser = new QueryParser();
15604
+ const parsed = parser.parse(input);
15605
+ if (parsed.type === "unknown") {
15606
+ console.log(import_chalk24.default.yellow("\u2753 I'm not sure how to answer that."));
15607
+ console.log(import_chalk24.default.gray(`Try: "what's my EC2 spend?" or "how can I save money?"`));
15608
+ } else {
15609
+ const provider = await getProviderFromConfig();
15610
+ const executor = new QueryExecutor(provider);
15611
+ const response = await executor.execute(parsed);
15612
+ console.log();
15613
+ console.log(response.answer);
15614
+ if (response.recommendations) {
15615
+ console.log();
15616
+ response.recommendations.forEach((rec) => {
15617
+ console.log(import_chalk24.default.gray(` \u{1F4A1} ${rec}`));
15618
+ });
15619
+ }
15620
+ }
15621
+ } catch (error) {
15622
+ console.log(import_chalk24.default.red("Error:"), error.message);
15623
+ }
15624
+ console.log();
15625
+ rl.prompt();
15626
+ });
15627
+ rl.on("close", () => {
15628
+ process.exit(0);
15629
+ });
15630
+ }
15631
+ __name(handleChat, "handleChat");
15632
+ function registerAskCommand(program) {
15633
+ program.command("ask <question...>").description("Ask natural language questions about costs").action(async (questionParts) => {
15634
+ const question = questionParts.join(" ");
15635
+ await handleAsk(question);
15636
+ });
15637
+ program.command("chat").description("Interactive chat mode for cost queries").action(handleChat);
15638
+ }
15639
+ __name(registerAskCommand, "registerAskCommand");
15640
+
14763
15641
  // src/cli/middleware/auth.ts
14764
15642
  async function authMiddleware(thisCommand, actionCommand) {
14765
15643
  const isConfigCommand = actionCommand.name() === "config" || actionCommand.parent?.name() === "config";
@@ -14788,7 +15666,7 @@ async function validationMiddleware(thisCommand, actionCommand) {
14788
15666
  __name(validationMiddleware, "validationMiddleware");
14789
15667
 
14790
15668
  // src/cli/middleware/error-handler.ts
14791
- var import_chalk23 = __toESM(require("chalk"));
15669
+ var import_chalk25 = __toESM(require("chalk"));
14792
15670
  function errorHandler(error) {
14793
15671
  const message = error?.message ?? String(error);
14794
15672
  const stack = error?.stack;
@@ -14796,15 +15674,15 @@ function errorHandler(error) {
14796
15674
  return;
14797
15675
  }
14798
15676
  console.error("");
14799
- console.error(import_chalk23.default.red("\u2716"), import_chalk23.default.bold("Error:"), message);
15677
+ console.error(import_chalk25.default.red("\u2716"), import_chalk25.default.bold("Error:"), message);
14800
15678
  if (process.env.DEBUG || process.env.VERBOSE) {
14801
15679
  console.error("");
14802
15680
  if (stack) {
14803
- console.error(import_chalk23.default.gray(stack));
15681
+ console.error(import_chalk25.default.gray(stack));
14804
15682
  }
14805
15683
  } else {
14806
15684
  console.error("");
14807
- console.error(import_chalk23.default.gray("Run with --verbose for detailed error information"));
15685
+ console.error(import_chalk25.default.gray("Run with --verbose for detailed error information"));
14808
15686
  }
14809
15687
  console.error("");
14810
15688
  }
@@ -14834,6 +15712,8 @@ function createCLI() {
14834
15712
  registerSSOCommands(program);
14835
15713
  registerPluginCommands(program);
14836
15714
  registerServerCommands(program);
15715
+ registerScorecardCommand(program);
15716
+ registerAskCommand(program);
14837
15717
  program.hook("preAction", async (thisCommand, actionCommand) => {
14838
15718
  const opts = thisCommand.opts();
14839
15719
  const logLevel = opts.verbose ? "debug" : opts.quiet ? "error" : opts.logLevel;