infra-cost 1.7.0 → 1.8.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 +457 -5
- package/dist/index.js +457 -5
- package/package.json +1 -1
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.
|
|
41
|
+
version: "1.8.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,457 @@ 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
|
+
|
|
14768
15219
|
// src/cli/middleware/auth.ts
|
|
14769
15220
|
async function authMiddleware(thisCommand, actionCommand) {
|
|
14770
15221
|
const isConfigCommand = actionCommand.name() === "config" || actionCommand.parent?.name() === "config";
|
|
@@ -14793,7 +15244,7 @@ async function validationMiddleware(thisCommand, actionCommand) {
|
|
|
14793
15244
|
__name(validationMiddleware, "validationMiddleware");
|
|
14794
15245
|
|
|
14795
15246
|
// src/cli/middleware/error-handler.ts
|
|
14796
|
-
var
|
|
15247
|
+
var import_chalk24 = __toESM(require("chalk"));
|
|
14797
15248
|
function errorHandler(error) {
|
|
14798
15249
|
const message = error?.message ?? String(error);
|
|
14799
15250
|
const stack = error?.stack;
|
|
@@ -14801,15 +15252,15 @@ function errorHandler(error) {
|
|
|
14801
15252
|
return;
|
|
14802
15253
|
}
|
|
14803
15254
|
console.error("");
|
|
14804
|
-
console.error(
|
|
15255
|
+
console.error(import_chalk24.default.red("\u2716"), import_chalk24.default.bold("Error:"), message);
|
|
14805
15256
|
if (process.env.DEBUG || process.env.VERBOSE) {
|
|
14806
15257
|
console.error("");
|
|
14807
15258
|
if (stack) {
|
|
14808
|
-
console.error(
|
|
15259
|
+
console.error(import_chalk24.default.gray(stack));
|
|
14809
15260
|
}
|
|
14810
15261
|
} else {
|
|
14811
15262
|
console.error("");
|
|
14812
|
-
console.error(
|
|
15263
|
+
console.error(import_chalk24.default.gray("Run with --verbose for detailed error information"));
|
|
14813
15264
|
}
|
|
14814
15265
|
console.error("");
|
|
14815
15266
|
}
|
|
@@ -14839,6 +15290,7 @@ function createCLI() {
|
|
|
14839
15290
|
registerSSOCommands(program);
|
|
14840
15291
|
registerPluginCommands(program);
|
|
14841
15292
|
registerServerCommands(program);
|
|
15293
|
+
registerScorecardCommand(program);
|
|
14842
15294
|
program.hook("preAction", async (thisCommand, actionCommand) => {
|
|
14843
15295
|
const opts = thisCommand.opts();
|
|
14844
15296
|
const logLevel = opts.verbose ? "debug" : opts.quiet ? "error" : opts.logLevel;
|
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.
|
|
42
|
+
version: "1.8.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,457 @@ 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
|
+
|
|
14763
15214
|
// src/cli/middleware/auth.ts
|
|
14764
15215
|
async function authMiddleware(thisCommand, actionCommand) {
|
|
14765
15216
|
const isConfigCommand = actionCommand.name() === "config" || actionCommand.parent?.name() === "config";
|
|
@@ -14788,7 +15239,7 @@ async function validationMiddleware(thisCommand, actionCommand) {
|
|
|
14788
15239
|
__name(validationMiddleware, "validationMiddleware");
|
|
14789
15240
|
|
|
14790
15241
|
// src/cli/middleware/error-handler.ts
|
|
14791
|
-
var
|
|
15242
|
+
var import_chalk24 = __toESM(require("chalk"));
|
|
14792
15243
|
function errorHandler(error) {
|
|
14793
15244
|
const message = error?.message ?? String(error);
|
|
14794
15245
|
const stack = error?.stack;
|
|
@@ -14796,15 +15247,15 @@ function errorHandler(error) {
|
|
|
14796
15247
|
return;
|
|
14797
15248
|
}
|
|
14798
15249
|
console.error("");
|
|
14799
|
-
console.error(
|
|
15250
|
+
console.error(import_chalk24.default.red("\u2716"), import_chalk24.default.bold("Error:"), message);
|
|
14800
15251
|
if (process.env.DEBUG || process.env.VERBOSE) {
|
|
14801
15252
|
console.error("");
|
|
14802
15253
|
if (stack) {
|
|
14803
|
-
console.error(
|
|
15254
|
+
console.error(import_chalk24.default.gray(stack));
|
|
14804
15255
|
}
|
|
14805
15256
|
} else {
|
|
14806
15257
|
console.error("");
|
|
14807
|
-
console.error(
|
|
15258
|
+
console.error(import_chalk24.default.gray("Run with --verbose for detailed error information"));
|
|
14808
15259
|
}
|
|
14809
15260
|
console.error("");
|
|
14810
15261
|
}
|
|
@@ -14834,6 +15285,7 @@ function createCLI() {
|
|
|
14834
15285
|
registerSSOCommands(program);
|
|
14835
15286
|
registerPluginCommands(program);
|
|
14836
15287
|
registerServerCommands(program);
|
|
15288
|
+
registerScorecardCommand(program);
|
|
14837
15289
|
program.hook("preAction", async (thisCommand, actionCommand) => {
|
|
14838
15290
|
const opts = thisCommand.opts();
|
|
14839
15291
|
const logLevel = opts.verbose ? "debug" : opts.quiet ? "error" : opts.logLevel;
|
package/package.json
CHANGED