@ganakailabs/cloudeval-cli 0.27.3 → 0.28.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.
@@ -235,7 +235,7 @@ This notice is not a substitute for legal review before public or enterprise dis
235
235
  | safer-buffer | 2.1.2 | MIT | Nikita Skovoroda | https://github.com/ChALkeR/safer-buffer#readme |
236
236
  | scheduler | 0.23.2 | MIT | NOASSERTION | https://reactjs.org/ |
237
237
  | semver | 7.7.3 | ISC | GitHub Inc. | https://github.com/npm/node-semver#readme |
238
- | shell-quote | 1.8.3 | MIT | James Halliday | https://github.com/ljharb/shell-quote |
238
+ | shell-quote | 1.8.4 | MIT | James Halliday | https://github.com/ljharb/shell-quote |
239
239
  | signal-exit | 3.0.7 | ISC | Ben Coe | https://github.com/tapjs/signal-exit |
240
240
  | skin-tone | 2.0.0 | MIT | Sindre Sorhus | https://github.com/sindresorhus/skin-tone#readme |
241
241
  | slice-ansi | 5.0.0 | MIT | NOASSERTION | https://github.com/chalk/slice-ansi#readme |
@@ -38,10 +38,10 @@ import {
38
38
  } from "./chunk-USSCB2ZU.js";
39
39
  import {
40
40
  Banner
41
- } from "./chunk-QUKE5J4T.js";
41
+ } from "./chunk-GGHX5LSI.js";
42
42
  import {
43
43
  CLI_VERSION
44
- } from "./chunk-O4KUS7UM.js";
44
+ } from "./chunk-JFJQZGZH.js";
45
45
  import {
46
46
  raisedButtonStyle,
47
47
  terminalTheme
@@ -3,8 +3,8 @@ import {
3
3
  bannerMetaColor,
4
4
  bannerSegmentColor,
5
5
  splitBannerLineSegments
6
- } from "./chunk-QUKE5J4T.js";
7
- import "./chunk-O4KUS7UM.js";
6
+ } from "./chunk-GGHX5LSI.js";
7
+ import "./chunk-JFJQZGZH.js";
8
8
  import "./chunk-ZDKRIOMB.js";
9
9
  export {
10
10
  Banner,
@@ -1,6 +1,6 @@
1
1
  import {
2
2
  CLI_VERSION
3
- } from "./chunk-O4KUS7UM.js";
3
+ } from "./chunk-JFJQZGZH.js";
4
4
  import {
5
5
  shouldUseColor,
6
6
  terminalTheme
@@ -1,5 +1,5 @@
1
1
  // src/version.ts
2
- var CLI_VERSION = "0.27.3";
2
+ var CLI_VERSION = "0.28.0";
3
3
 
4
4
  export {
5
5
  CLI_VERSION
package/dist/cli.js CHANGED
@@ -39,7 +39,7 @@ import {
39
39
  } from "./chunk-USSCB2ZU.js";
40
40
  import {
41
41
  CLI_VERSION
42
- } from "./chunk-O4KUS7UM.js";
42
+ } from "./chunk-JFJQZGZH.js";
43
43
 
44
44
  // src/runtime/prepareInk.ts
45
45
  import fs from "fs";
@@ -2842,10 +2842,14 @@ var namedAmount = (record) => numberFrom(
2842
2842
  record.amount,
2843
2843
  record.monthly_cost,
2844
2844
  record.monthlyCost,
2845
+ record.monthly_cost_estimate,
2846
+ record.monthlyCostEstimate,
2845
2847
  record.cost,
2846
2848
  record.value
2847
2849
  );
2848
- var namedLabel = (record, fallback) => String(record.name ?? record.service ?? record.label ?? record.category ?? fallback);
2850
+ var namedLabel = (record, fallback) => String(
2851
+ record.name ?? record.resource_name ?? record.resourceName ?? record.service ?? record.label ?? record.category ?? fallback
2852
+ );
2849
2853
  var mermaidLabel = (value) => value.replace(/"/g, "'");
2850
2854
  var joinReadableList = (values) => {
2851
2855
  if (values.length <= 1) {
@@ -2869,6 +2873,7 @@ var costServiceRows = (services, currency) => (Array.isArray(services) ? service
2869
2873
  ...rowCurrency ? { currency: rowCurrency } : {}
2870
2874
  };
2871
2875
  }).filter((service) => service !== void 0);
2876
+ var sortedPositiveCostRows = (rows) => rows.filter((row) => row.amount > 0).sort((left, right) => right.amount - left.amount);
2872
2877
  var reconcileCostServiceRows = (services, totalAmount, currency) => {
2873
2878
  const total = numberFrom(totalAmount);
2874
2879
  if (total === void 0 || services.length === 0) {
@@ -2897,7 +2902,80 @@ var reconcileCostServiceRows = (services, totalAmount, currency) => {
2897
2902
  }
2898
2903
  return services;
2899
2904
  };
2905
+ var compactCostRowsForChart = (rows, totalAmount, currency, options = {}) => {
2906
+ const maxRows = options.maxRows ?? 5;
2907
+ const remainderLabel = options.remainderLabel ?? "Unallocated";
2908
+ const total = numberFrom(totalAmount);
2909
+ const positiveRows = sortedPositiveCostRows(rows);
2910
+ const selected = positiveRows.slice(0, maxRows);
2911
+ const collapsed = positiveRows.slice(maxRows);
2912
+ const collapsedSum = Number(
2913
+ collapsed.reduce((sum, row) => sum + row.amount, 0).toFixed(3)
2914
+ );
2915
+ const result = [...selected];
2916
+ if (collapsedSum > 1e-3) {
2917
+ result.push({
2918
+ name: "Other",
2919
+ amount: collapsedSum,
2920
+ ...currency ? { currency } : {}
2921
+ });
2922
+ }
2923
+ const represented = result.reduce((sum, row) => sum + row.amount, 0);
2924
+ if (total !== void 0) {
2925
+ const delta = Number((total - represented).toFixed(3));
2926
+ if (delta > 1e-3) {
2927
+ result.push({
2928
+ name: remainderLabel,
2929
+ amount: delta,
2930
+ ...currency ? { currency } : {}
2931
+ });
2932
+ }
2933
+ }
2934
+ return result;
2935
+ };
2900
2936
  var markdownLink = (label, url) => url ? `[${label}](${url})` : label;
2937
+ var openInCloudEvalLines = (links) => {
2938
+ if (!links) {
2939
+ return [];
2940
+ }
2941
+ const reports = asRecord(links.reports);
2942
+ const downloads = asRecord(links.downloads);
2943
+ const entries = [
2944
+ ["Project preview", links.project],
2945
+ ["Architecture report", reports.architecture],
2946
+ ["Cost report", reports.cost],
2947
+ ["Validation details", reports.validation],
2948
+ ["Download PDF", downloads.pdf],
2949
+ ["Workflow run", links.workflowRun],
2950
+ ["Download review artifacts", downloads.reviewArtifacts]
2951
+ ].filter((entry) => typeof entry[1] === "string" && entry[1].length > 0);
2952
+ return entries.map(([label, url]) => `- ${markdownLink(label, url)}`);
2953
+ };
2954
+ var monthlyCostImpactLines = (currentAmount, savingsAmount, currency) => {
2955
+ const current = numberFrom(currentAmount);
2956
+ const savings = numberFrom(savingsAmount);
2957
+ if (current === void 0 || current <= 0 || savings === void 0 || savings <= 0) {
2958
+ return [];
2959
+ }
2960
+ const optimized = Math.max(current - savings, 0);
2961
+ const savingsPercent = current > 0 ? savings / current * 100 : void 0;
2962
+ const yMax = Math.max(current, optimized, savings);
2963
+ return [
2964
+ "| Metric | Amount |",
2965
+ "| --- | ---: |",
2966
+ `| Current monthly cost | **${formatMonthlyMoney(current, currency)}** |`,
2967
+ `| Potential savings | **${formatMonthlyMoney(savings, currency)}${savingsPercent !== void 0 ? ` (${trimNumber(savingsPercent, 1)}%)` : ""}** |`,
2968
+ `| Optimized monthly cost | **${formatMonthlyMoney(optimized, currency)}** |`,
2969
+ "",
2970
+ "```mermaid",
2971
+ "xychart-beta",
2972
+ ' title "Monthly cost impact"',
2973
+ ' x-axis ["Current", "Optimized"]',
2974
+ ` y-axis "${currency ? `${currency}/mo` : "monthly cost"}" 0 --> ${trimNumber(yMax, 3)}`,
2975
+ ` bar [${trimNumber(current, 3)}, ${trimNumber(optimized, 3)}]`,
2976
+ "```"
2977
+ ];
2978
+ };
2901
2979
  var extractPillars = (waf) => {
2902
2980
  const fromArray = waf?.parsed?.score?.pillars;
2903
2981
  if (Array.isArray(fromArray)) {
@@ -3125,6 +3203,16 @@ var evaluateGate = ({
3125
3203
  currency
3126
3204
  },
3127
3205
  topServices: (Array.isArray(cost?.parsed?.serviceGroups) ? cost.parsed.serviceGroups : entriesAsNamedRecords(cost?.report?.processed?.cost_by_service_family)).slice(0, 5),
3206
+ topResources: (firstArray(
3207
+ cost?.report?.raw?.resource_estimates,
3208
+ cost?.report?.raw?.resourceEstimates,
3209
+ cost?.raw?.resource_estimates,
3210
+ cost?.raw?.resourceEstimates,
3211
+ cost?.parsed?.resourceEstimates,
3212
+ cost?.parsed?.resource_estimates,
3213
+ preload?.latest_payloads?.cost?.raw?.resource_estimates,
3214
+ preload?.latest_payloads?.cost?.raw?.resourceEstimates
3215
+ ) ?? []).slice(0, 20),
3128
3216
  recommendations: (Array.isArray(cost?.parsed?.recommendations) ? cost.parsed.recommendations : Array.isArray(cost?.report?.processed?.optimization_recommendations) ? cost.report.processed.optimization_recommendations : []).slice(0, 5)
3129
3217
  };
3130
3218
  if (!gateConfig) {
@@ -3481,7 +3569,14 @@ var buildMarkdownSummary = (data) => {
3481
3569
  cost?.amount,
3482
3570
  cost?.currency
3483
3571
  );
3484
- const positiveCostServices = costServices.filter((service) => service.amount > 0);
3572
+ const resourceCostRows = compactCostRowsForChart(
3573
+ costServiceRows(data.gate?.cost?.topResources, cost?.currency),
3574
+ cost?.amount,
3575
+ cost?.currency,
3576
+ { maxRows: 5, remainderLabel: "Unallocated" }
3577
+ );
3578
+ const positiveResourceCosts = resourceCostRows.filter((resource) => resource.amount > 0);
3579
+ const openLinks = openInCloudEvalLines(data.links);
3485
3580
  const architectureLines = architectureSignalLines({
3486
3581
  architecture,
3487
3582
  costServices,
@@ -3505,6 +3600,9 @@ var buildMarkdownSummary = (data) => {
3505
3600
  `- **Ref**: \`${ref}\``,
3506
3601
  `- **Commit**: \`${commit}\``
3507
3602
  ];
3603
+ if (openLinks.length) {
3604
+ lines.push("", "#### Open in CloudEval", "", ...openLinks);
3605
+ }
3508
3606
  if (data.aiSummary?.markdown) {
3509
3607
  lines.push("", "#### AI summary", "", data.aiSummary.markdown);
3510
3608
  }
@@ -3528,11 +3626,29 @@ var buildMarkdownSummary = (data) => {
3528
3626
  }
3529
3627
  if (cost?.amount !== void 0 || cost?.threshold !== void 0) {
3530
3628
  const costLines = [];
3531
- if (data.gate?.cost?.estimatedSavings?.amount !== void 0) {
3629
+ const impactLines = monthlyCostImpactLines(
3630
+ cost?.amount,
3631
+ data.gate?.cost?.estimatedSavings?.amount,
3632
+ cost?.currency ?? data.gate?.cost?.estimatedSavings?.currency
3633
+ );
3634
+ if (impactLines.length) {
3635
+ costLines.push(...impactLines);
3636
+ } else if (data.gate?.cost?.estimatedSavings?.amount !== void 0) {
3532
3637
  costLines.push(
3533
3638
  `- Estimated savings: **${formatMonthlyMoney(data.gate.cost.estimatedSavings.amount, data.gate.cost.estimatedSavings.currency)}**`
3534
3639
  );
3535
3640
  }
3641
+ if (positiveResourceCosts.length) {
3642
+ costLines.push(
3643
+ "",
3644
+ "```mermaid",
3645
+ "pie title Monthly cost by resource",
3646
+ ...positiveResourceCosts.map(
3647
+ (resource) => ` "${mermaidLabel(resource.name)}" : ${trimNumber(resource.amount, 3)}`
3648
+ ),
3649
+ "```"
3650
+ );
3651
+ }
3536
3652
  if (costServices.length) {
3537
3653
  costLines.push(
3538
3654
  "",
@@ -3543,17 +3659,6 @@ var buildMarkdownSummary = (data) => {
3543
3659
  )
3544
3660
  );
3545
3661
  }
3546
- if (positiveCostServices.length) {
3547
- costLines.push(
3548
- "",
3549
- "```mermaid",
3550
- "pie title Monthly cost by service",
3551
- ...positiveCostServices.map(
3552
- (service) => ` "${mermaidLabel(service.name)}" : ${trimNumber(service.amount, 3)}`
3553
- ),
3554
- "```"
3555
- );
3556
- }
3557
3662
  if (costLines.length) {
3558
3663
  lines.push(
3559
3664
  "",
@@ -3673,18 +3778,58 @@ var registerReviewCommand = (program2, deps) => {
3673
3778
  }),
3674
3779
  readConfigText(cwd, options)
3675
3780
  ]);
3781
+ const frontendBaseUrl = resolveFrontendBaseUrl({ apiBaseUrl: context.baseUrl });
3782
+ const projectUrl = buildFrontendUrl({
3783
+ baseUrl: frontendBaseUrl,
3784
+ target: "project",
3785
+ projectId,
3786
+ view: "preview",
3787
+ layout: "architecture"
3788
+ });
3789
+ const workflowRunUrl = githubWorkflowRunUrl();
3676
3790
  const data = {
3677
3791
  projectId,
3678
3792
  project: {
3679
3793
  id: projectId,
3680
3794
  name: String(project?.name ?? projectId),
3681
- url: buildFrontendUrl({
3682
- baseUrl: resolveFrontendBaseUrl({ apiBaseUrl: context.baseUrl }),
3683
- target: "project",
3684
- projectId,
3685
- view: "preview",
3686
- layout: "architecture"
3687
- })
3795
+ url: projectUrl
3796
+ },
3797
+ links: {
3798
+ project: projectUrl,
3799
+ reports: {
3800
+ architecture: buildFrontendUrl({
3801
+ baseUrl: frontendBaseUrl,
3802
+ target: "reports",
3803
+ projectId,
3804
+ tab: "architecture",
3805
+ reportType: "architecture"
3806
+ }),
3807
+ cost: buildFrontendUrl({
3808
+ baseUrl: frontendBaseUrl,
3809
+ target: "reports",
3810
+ projectId,
3811
+ tab: "cost",
3812
+ reportType: "cost"
3813
+ }),
3814
+ validation: buildFrontendUrl({
3815
+ baseUrl: frontendBaseUrl,
3816
+ target: "reports",
3817
+ projectId,
3818
+ tab: "validation",
3819
+ reportType: "unit_tests"
3820
+ })
3821
+ },
3822
+ downloads: {
3823
+ pdf: buildFrontendUrl({
3824
+ baseUrl: frontendBaseUrl,
3825
+ target: "reports",
3826
+ projectId,
3827
+ downloadPdf: true,
3828
+ pdfVerbosity: "full"
3829
+ }),
3830
+ ...workflowRunUrl ? { reviewArtifacts: workflowRunUrl } : {}
3831
+ },
3832
+ ...workflowRunUrl ? { workflowRun: workflowRunUrl } : {}
3688
3833
  },
3689
3834
  repo,
3690
3835
  ref,
@@ -15626,7 +15771,7 @@ program.command("tui").description("Open the CloudEval Terminal UI").option(
15626
15771
  const { assertSecureBaseUrl } = await import("./dist-PEYJDO7A.js");
15627
15772
  const [{ render }, { App }] = await Promise.all([
15628
15773
  import("ink"),
15629
- import("./App-7QUH5EKH.js")
15774
+ import("./App-3RDW53BW.js")
15630
15775
  ]);
15631
15776
  const baseUrl = await resolveBaseUrl(options, command);
15632
15777
  assertSecureBaseUrl(baseUrl);
@@ -15684,7 +15829,7 @@ program.command("chat").description("Start an interactive chat session").option(
15684
15829
  const { assertSecureBaseUrl } = await import("./dist-PEYJDO7A.js");
15685
15830
  const [{ render }, { App }] = await Promise.all([
15686
15831
  import("ink"),
15687
- import("./App-7QUH5EKH.js")
15832
+ import("./App-3RDW53BW.js")
15688
15833
  ]);
15689
15834
  const baseUrl = await resolveBaseUrl(options, command);
15690
15835
  assertSecureBaseUrl(baseUrl);
@@ -16438,7 +16583,7 @@ Error: ${errorMsg}
16438
16583
  program.command("banner").description("Preview the startup banner and terminal capabilities").action(async () => {
16439
16584
  const { render } = await import("ink");
16440
16585
  const BannerPreview = React.lazy(async () => ({
16441
- default: (await import("./Banner-AWNB7NZK.js")).Banner
16586
+ default: (await import("./Banner-HGG5NHXF.js")).Banner
16442
16587
  }));
16443
16588
  render(
16444
16589
  /* @__PURE__ */ jsx(React.Suspense, { fallback: null, children: /* @__PURE__ */ jsx(BannerPreview, { disable: false }) })
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ganakailabs/cloudeval-cli",
3
- "version": "0.27.3",
3
+ "version": "0.28.0",
4
4
  "license": "LicenseRef-CloudEval-CLI",
5
5
  "type": "module",
6
6
  "description": "CloudEval CLI for cloud architecture, cost, report, automation, and MCP workflows.",
package/sbom.spdx.json CHANGED
@@ -14,7 +14,7 @@
14
14
  {
15
15
  "SPDXID": "SPDXRef-Package-CloudEval-CLI",
16
16
  "name": "CloudEval CLI",
17
- "versionInfo": "0.27.3",
17
+ "versionInfo": "0.28.0",
18
18
  "downloadLocation": "https://github.com/ganakailabs/cloudeval-cli",
19
19
  "filesAnalyzed": false,
20
20
  "licenseConcluded": "LicenseRef-CloudEval-CLI",
@@ -2354,9 +2354,9 @@
2354
2354
  "summary": "The semantic version parser used by npm."
2355
2355
  },
2356
2356
  {
2357
- "SPDXID": "SPDXRef-Package-shell-quote-1.8.3",
2357
+ "SPDXID": "SPDXRef-Package-shell-quote-1.8.4",
2358
2358
  "name": "shell-quote",
2359
- "versionInfo": "1.8.3",
2359
+ "versionInfo": "1.8.4",
2360
2360
  "downloadLocation": "https://github.com/ljharb/shell-quote",
2361
2361
  "filesAnalyzed": false,
2362
2362
  "licenseConcluded": "MIT",
@@ -3847,7 +3847,7 @@
3847
3847
  {
3848
3848
  "spdxElementId": "SPDXRef-Package-CloudEval-CLI",
3849
3849
  "relationshipType": "DEPENDS_ON",
3850
- "relatedSpdxElement": "SPDXRef-Package-shell-quote-1.8.3"
3850
+ "relatedSpdxElement": "SPDXRef-Package-shell-quote-1.8.4"
3851
3851
  },
3852
3852
  {
3853
3853
  "spdxElementId": "SPDXRef-Package-CloudEval-CLI",