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