@ganakailabs/cloudeval-cli 0.27.3 → 0.28.1

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-L5ICTZHW.js";
42
42
  import {
43
43
  CLI_VERSION
44
- } from "./chunk-O4KUS7UM.js";
44
+ } from "./chunk-XDMPAWK2.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-L5ICTZHW.js";
7
+ import "./chunk-XDMPAWK2.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-XDMPAWK2.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.1";
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-XDMPAWK2.js";
43
43
 
44
44
  // src/runtime/prepareInk.ts
45
45
  import fs from "fs";
@@ -609,6 +609,7 @@ var cliCommands = [
609
609
  "--wait",
610
610
  "--poll-interval",
611
611
  "--wait-timeout",
612
+ "--progress",
612
613
  "--location",
613
614
  ...outputOptions,
614
615
  ...authOptions,
@@ -2842,10 +2843,14 @@ var namedAmount = (record) => numberFrom(
2842
2843
  record.amount,
2843
2844
  record.monthly_cost,
2844
2845
  record.monthlyCost,
2846
+ record.monthly_cost_estimate,
2847
+ record.monthlyCostEstimate,
2845
2848
  record.cost,
2846
2849
  record.value
2847
2850
  );
2848
- var namedLabel = (record, fallback) => String(record.name ?? record.service ?? record.label ?? record.category ?? fallback);
2851
+ var namedLabel = (record, fallback) => String(
2852
+ record.name ?? record.resource_name ?? record.resourceName ?? record.service ?? record.label ?? record.category ?? fallback
2853
+ );
2849
2854
  var mermaidLabel = (value) => value.replace(/"/g, "'");
2850
2855
  var joinReadableList = (values) => {
2851
2856
  if (values.length <= 1) {
@@ -2869,6 +2874,7 @@ var costServiceRows = (services, currency) => (Array.isArray(services) ? service
2869
2874
  ...rowCurrency ? { currency: rowCurrency } : {}
2870
2875
  };
2871
2876
  }).filter((service) => service !== void 0);
2877
+ var sortedPositiveCostRows = (rows) => rows.filter((row) => row.amount > 0).sort((left, right) => right.amount - left.amount);
2872
2878
  var reconcileCostServiceRows = (services, totalAmount, currency) => {
2873
2879
  const total = numberFrom(totalAmount);
2874
2880
  if (total === void 0 || services.length === 0) {
@@ -2897,7 +2903,80 @@ var reconcileCostServiceRows = (services, totalAmount, currency) => {
2897
2903
  }
2898
2904
  return services;
2899
2905
  };
2906
+ var compactCostRowsForChart = (rows, totalAmount, currency, options = {}) => {
2907
+ const maxRows = options.maxRows ?? 5;
2908
+ const remainderLabel = options.remainderLabel ?? "Unallocated";
2909
+ const total = numberFrom(totalAmount);
2910
+ const positiveRows = sortedPositiveCostRows(rows);
2911
+ const selected = positiveRows.slice(0, maxRows);
2912
+ const collapsed = positiveRows.slice(maxRows);
2913
+ const collapsedSum = Number(
2914
+ collapsed.reduce((sum, row) => sum + row.amount, 0).toFixed(3)
2915
+ );
2916
+ const result = [...selected];
2917
+ if (collapsedSum > 1e-3) {
2918
+ result.push({
2919
+ name: "Other",
2920
+ amount: collapsedSum,
2921
+ ...currency ? { currency } : {}
2922
+ });
2923
+ }
2924
+ const represented = result.reduce((sum, row) => sum + row.amount, 0);
2925
+ if (total !== void 0) {
2926
+ const delta = Number((total - represented).toFixed(3));
2927
+ if (delta > 1e-3) {
2928
+ result.push({
2929
+ name: remainderLabel,
2930
+ amount: delta,
2931
+ ...currency ? { currency } : {}
2932
+ });
2933
+ }
2934
+ }
2935
+ return result;
2936
+ };
2900
2937
  var markdownLink = (label, url) => url ? `[${label}](${url})` : label;
2938
+ var openInCloudEvalLines = (links) => {
2939
+ if (!links) {
2940
+ return [];
2941
+ }
2942
+ const reports = asRecord(links.reports);
2943
+ const downloads = asRecord(links.downloads);
2944
+ const entries = [
2945
+ ["Project preview", links.project],
2946
+ ["Architecture report", reports.architecture],
2947
+ ["Cost report", reports.cost],
2948
+ ["Validation details", reports.validation],
2949
+ ["Download PDF", downloads.pdf],
2950
+ ["Workflow run", links.workflowRun],
2951
+ ["Download review artifacts", downloads.reviewArtifacts]
2952
+ ].filter((entry) => typeof entry[1] === "string" && entry[1].length > 0);
2953
+ return entries.map(([label, url]) => `- ${markdownLink(label, url)}`);
2954
+ };
2955
+ var monthlyCostImpactLines = (currentAmount, savingsAmount, currency) => {
2956
+ const current = numberFrom(currentAmount);
2957
+ const savings = numberFrom(savingsAmount);
2958
+ if (current === void 0 || current <= 0 || savings === void 0 || savings <= 0) {
2959
+ return [];
2960
+ }
2961
+ const optimized = Math.max(current - savings, 0);
2962
+ const savingsPercent = current > 0 ? savings / current * 100 : void 0;
2963
+ const yMax = Math.max(current, optimized, savings);
2964
+ return [
2965
+ "| Metric | Amount |",
2966
+ "| --- | ---: |",
2967
+ `| Current monthly cost | **${formatMonthlyMoney(current, currency)}** |`,
2968
+ `| Potential savings | **${formatMonthlyMoney(savings, currency)}${savingsPercent !== void 0 ? ` (${trimNumber(savingsPercent, 1)}%)` : ""}** |`,
2969
+ `| Optimized monthly cost | **${formatMonthlyMoney(optimized, currency)}** |`,
2970
+ "",
2971
+ "```mermaid",
2972
+ "xychart-beta",
2973
+ ' title "Monthly cost impact"',
2974
+ ' x-axis ["Current", "Optimized"]',
2975
+ ` y-axis "${currency ? `${currency}/mo` : "monthly cost"}" 0 --> ${trimNumber(yMax, 3)}`,
2976
+ ` bar [${trimNumber(current, 3)}, ${trimNumber(optimized, 3)}]`,
2977
+ "```"
2978
+ ];
2979
+ };
2901
2980
  var extractPillars = (waf) => {
2902
2981
  const fromArray = waf?.parsed?.score?.pillars;
2903
2982
  if (Array.isArray(fromArray)) {
@@ -3125,6 +3204,16 @@ var evaluateGate = ({
3125
3204
  currency
3126
3205
  },
3127
3206
  topServices: (Array.isArray(cost?.parsed?.serviceGroups) ? cost.parsed.serviceGroups : entriesAsNamedRecords(cost?.report?.processed?.cost_by_service_family)).slice(0, 5),
3207
+ topResources: (firstArray(
3208
+ cost?.report?.raw?.resource_estimates,
3209
+ cost?.report?.raw?.resourceEstimates,
3210
+ cost?.raw?.resource_estimates,
3211
+ cost?.raw?.resourceEstimates,
3212
+ cost?.parsed?.resourceEstimates,
3213
+ cost?.parsed?.resource_estimates,
3214
+ preload?.latest_payloads?.cost?.raw?.resource_estimates,
3215
+ preload?.latest_payloads?.cost?.raw?.resourceEstimates
3216
+ ) ?? []).slice(0, 20),
3128
3217
  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
3218
  };
3130
3219
  if (!gateConfig) {
@@ -3481,7 +3570,14 @@ var buildMarkdownSummary = (data) => {
3481
3570
  cost?.amount,
3482
3571
  cost?.currency
3483
3572
  );
3484
- const positiveCostServices = costServices.filter((service) => service.amount > 0);
3573
+ const resourceCostRows = compactCostRowsForChart(
3574
+ costServiceRows(data.gate?.cost?.topResources, cost?.currency),
3575
+ cost?.amount,
3576
+ cost?.currency,
3577
+ { maxRows: 5, remainderLabel: "Unallocated" }
3578
+ );
3579
+ const positiveResourceCosts = resourceCostRows.filter((resource) => resource.amount > 0);
3580
+ const openLinks = openInCloudEvalLines(data.links);
3485
3581
  const architectureLines = architectureSignalLines({
3486
3582
  architecture,
3487
3583
  costServices,
@@ -3505,6 +3601,9 @@ var buildMarkdownSummary = (data) => {
3505
3601
  `- **Ref**: \`${ref}\``,
3506
3602
  `- **Commit**: \`${commit}\``
3507
3603
  ];
3604
+ if (openLinks.length) {
3605
+ lines.push("", "#### Open in CloudEval", "", ...openLinks);
3606
+ }
3508
3607
  if (data.aiSummary?.markdown) {
3509
3608
  lines.push("", "#### AI summary", "", data.aiSummary.markdown);
3510
3609
  }
@@ -3528,11 +3627,29 @@ var buildMarkdownSummary = (data) => {
3528
3627
  }
3529
3628
  if (cost?.amount !== void 0 || cost?.threshold !== void 0) {
3530
3629
  const costLines = [];
3531
- if (data.gate?.cost?.estimatedSavings?.amount !== void 0) {
3630
+ const impactLines = monthlyCostImpactLines(
3631
+ cost?.amount,
3632
+ data.gate?.cost?.estimatedSavings?.amount,
3633
+ cost?.currency ?? data.gate?.cost?.estimatedSavings?.currency
3634
+ );
3635
+ if (impactLines.length) {
3636
+ costLines.push(...impactLines);
3637
+ } else if (data.gate?.cost?.estimatedSavings?.amount !== void 0) {
3532
3638
  costLines.push(
3533
3639
  `- Estimated savings: **${formatMonthlyMoney(data.gate.cost.estimatedSavings.amount, data.gate.cost.estimatedSavings.currency)}**`
3534
3640
  );
3535
3641
  }
3642
+ if (positiveResourceCosts.length) {
3643
+ costLines.push(
3644
+ "",
3645
+ "```mermaid",
3646
+ "pie title Monthly cost by resource",
3647
+ ...positiveResourceCosts.map(
3648
+ (resource) => ` "${mermaidLabel(resource.name)}" : ${trimNumber(resource.amount, 3)}`
3649
+ ),
3650
+ "```"
3651
+ );
3652
+ }
3536
3653
  if (costServices.length) {
3537
3654
  costLines.push(
3538
3655
  "",
@@ -3543,17 +3660,6 @@ var buildMarkdownSummary = (data) => {
3543
3660
  )
3544
3661
  );
3545
3662
  }
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
3663
  if (costLines.length) {
3558
3664
  lines.push(
3559
3665
  "",
@@ -3673,18 +3779,58 @@ var registerReviewCommand = (program2, deps) => {
3673
3779
  }),
3674
3780
  readConfigText(cwd, options)
3675
3781
  ]);
3782
+ const frontendBaseUrl = resolveFrontendBaseUrl({ apiBaseUrl: context.baseUrl });
3783
+ const projectUrl = buildFrontendUrl({
3784
+ baseUrl: frontendBaseUrl,
3785
+ target: "project",
3786
+ projectId,
3787
+ view: "preview",
3788
+ layout: "architecture"
3789
+ });
3790
+ const workflowRunUrl = githubWorkflowRunUrl();
3676
3791
  const data = {
3677
3792
  projectId,
3678
3793
  project: {
3679
3794
  id: projectId,
3680
3795
  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
- })
3796
+ url: projectUrl
3797
+ },
3798
+ links: {
3799
+ project: projectUrl,
3800
+ reports: {
3801
+ architecture: buildFrontendUrl({
3802
+ baseUrl: frontendBaseUrl,
3803
+ target: "reports",
3804
+ projectId,
3805
+ tab: "architecture",
3806
+ reportType: "architecture"
3807
+ }),
3808
+ cost: buildFrontendUrl({
3809
+ baseUrl: frontendBaseUrl,
3810
+ target: "reports",
3811
+ projectId,
3812
+ tab: "cost",
3813
+ reportType: "cost"
3814
+ }),
3815
+ validation: buildFrontendUrl({
3816
+ baseUrl: frontendBaseUrl,
3817
+ target: "reports",
3818
+ projectId,
3819
+ tab: "validation",
3820
+ reportType: "unit_tests"
3821
+ })
3822
+ },
3823
+ downloads: {
3824
+ pdf: buildFrontendUrl({
3825
+ baseUrl: frontendBaseUrl,
3826
+ target: "reports",
3827
+ projectId,
3828
+ downloadPdf: true,
3829
+ pdfVerbosity: "full"
3830
+ }),
3831
+ ...workflowRunUrl ? { reviewArtifacts: workflowRunUrl } : {}
3832
+ },
3833
+ ...workflowRunUrl ? { workflowRun: workflowRunUrl } : {}
3688
3834
  },
3689
3835
  repo,
3690
3836
  ref,
@@ -3738,12 +3884,13 @@ var registerReviewCommand = (program2, deps) => {
3738
3884
  filesWritten
3739
3885
  });
3740
3886
  if (data.gate.status === "fail") {
3741
- process.exit(1);
3887
+ process.exitCode = 1;
3888
+ return;
3742
3889
  }
3743
- process.exit(0);
3890
+ process.exitCode = 0;
3744
3891
  } catch (error) {
3745
3892
  console.error(error?.message ?? "Review failed");
3746
- process.exit(1);
3893
+ process.exitCode = 1;
3747
3894
  }
3748
3895
  });
3749
3896
  };
@@ -7692,6 +7839,51 @@ var isSuccessfulJobStatus = (value) => ["completed", "succeeded"].includes(norma
7692
7839
  var compactRecord = (value) => Object.fromEntries(
7693
7840
  Object.entries(value).filter(([, item]) => item !== void 0)
7694
7841
  );
7842
+ var basenameFromPath = (value) => {
7843
+ const trimmed = value?.trim();
7844
+ if (!trimmed) {
7845
+ return void 0;
7846
+ }
7847
+ const normalized = trimmed.replace(/\\/g, "/");
7848
+ return normalized.split("/").filter(Boolean).pop() ?? trimmed;
7849
+ };
7850
+ var isBackendTempLocation = (value) => {
7851
+ const trimmed = value?.trim();
7852
+ if (!trimmed) {
7853
+ return false;
7854
+ }
7855
+ const normalized = trimmed.replace(/\\/g, "/").toLowerCase();
7856
+ const basename = basenameFromPath(trimmed) ?? "";
7857
+ return normalized.startsWith("/tmp/") || normalized.startsWith("/private/tmp/") || normalized.includes("/var/folders/") || normalized.includes("/appdata/local/temp/") || normalized.includes("/windows/temp/") || /^tmp[\w.-]*\.json$/i.test(basename);
7858
+ };
7859
+ var displayTemplateLocation = (rawLocation, context) => {
7860
+ const trimmed = rawLocation?.trim();
7861
+ if (!trimmed) {
7862
+ return void 0;
7863
+ }
7864
+ if (isBackendTempLocation(trimmed)) {
7865
+ return basenameFromPath(context?.templatePath);
7866
+ }
7867
+ return trimmed;
7868
+ };
7869
+ var sanitizeTemplateLocationFields = (value, context) => {
7870
+ if (Array.isArray(value)) {
7871
+ return value.map((item) => sanitizeTemplateLocationFields(item, context));
7872
+ }
7873
+ const record = recordValue(value);
7874
+ if (!record) {
7875
+ return value;
7876
+ }
7877
+ return Object.fromEntries(
7878
+ Object.entries(record).flatMap(([key, item]) => {
7879
+ if (["file_path", "filePath", "location"].includes(key) && typeof item === "string") {
7880
+ const publicLocation = displayTemplateLocation(item, context);
7881
+ return publicLocation === void 0 ? [] : [[key, publicLocation]];
7882
+ }
7883
+ return [[key, sanitizeTemplateLocationFields(item, context)]];
7884
+ })
7885
+ );
7886
+ };
7695
7887
  var arrayValue = (value) => Array.isArray(value) ? value : [];
7696
7888
  var firstString = (record, fields) => {
7697
7889
  for (const field of fields) {
@@ -7702,6 +7894,283 @@ var firstString = (record, fields) => {
7702
7894
  }
7703
7895
  return void 0;
7704
7896
  };
7897
+ var publicValidationText = (value) => value?.replace(/\bPSRule(?:\s+for\s+Azure)?\b/gi, "validation rules").replace(/\bARM\s+TTK\b/gi, "template validation");
7898
+ var sanitizeTemplateOperationText = (value) => {
7899
+ if (typeof value === "string") {
7900
+ return publicValidationText(value) ?? value;
7901
+ }
7902
+ if (Array.isArray(value)) {
7903
+ return value.map(sanitizeTemplateOperationText);
7904
+ }
7905
+ const record = recordValue(value);
7906
+ if (record) {
7907
+ return Object.fromEntries(
7908
+ Object.entries(record).map(([key, item]) => [
7909
+ key,
7910
+ sanitizeTemplateOperationText(item)
7911
+ ])
7912
+ );
7913
+ }
7914
+ return value;
7915
+ };
7916
+ var firstNumber = (record, fields) => {
7917
+ for (const field of fields) {
7918
+ const value = record?.[field];
7919
+ if (typeof value === "number" && Number.isFinite(value)) {
7920
+ return value;
7921
+ }
7922
+ if (typeof value === "string" && value.trim()) {
7923
+ const parsed = Number(value);
7924
+ if (Number.isFinite(parsed)) {
7925
+ return parsed;
7926
+ }
7927
+ }
7928
+ }
7929
+ return void 0;
7930
+ };
7931
+ var firstArray2 = (record, fields) => {
7932
+ for (const field of fields) {
7933
+ const value = record?.[field];
7934
+ if (Array.isArray(value)) {
7935
+ return value;
7936
+ }
7937
+ }
7938
+ return [];
7939
+ };
7940
+ var normalizeProgressItem = (value, context) => {
7941
+ const record = recordValue(value) ?? {};
7942
+ const passed = typeof record.passed === "boolean" ? record.passed : void 0;
7943
+ return compactRecord({
7944
+ name: firstString(record, [
7945
+ "name",
7946
+ "test_name",
7947
+ "testName",
7948
+ "rule_name",
7949
+ "ruleName",
7950
+ "item",
7951
+ "current_item",
7952
+ "display_name",
7953
+ "displayName"
7954
+ ]),
7955
+ status: firstString(record, ["status", "outcome", "result"]) ?? (passed === void 0 ? void 0 : passed ? "Pass" : "Fail"),
7956
+ passed,
7957
+ category: firstString(record, ["category", "test_category", "testCategory"]),
7958
+ severity: firstString(record, ["severity", "level"]),
7959
+ message: publicValidationText(firstString(record, ["message", "description"])),
7960
+ recommendation: publicValidationText(
7961
+ firstString(record, ["recommendation", "remediation"])
7962
+ ),
7963
+ location: displayTemplateLocation(
7964
+ firstString(record, ["file_path", "filePath", "location"]),
7965
+ context
7966
+ ),
7967
+ durationMs: firstNumber(record, ["duration_ms", "durationMs"]),
7968
+ documentationUrl: firstString(record, [
7969
+ "documentation_url",
7970
+ "documentationUrl",
7971
+ "help_url",
7972
+ "helpUrl"
7973
+ ])
7974
+ });
7975
+ };
7976
+ var operationFromResult = (result) => {
7977
+ const record = recordValue(result);
7978
+ if (!record) {
7979
+ return void 0;
7980
+ }
7981
+ if ("test_results" in record || "total_tests" in record) {
7982
+ return "template_test";
7983
+ }
7984
+ if ("filtered_results" in record || "results" in record || "summary" in record) {
7985
+ return "template_validate";
7986
+ }
7987
+ return void 0;
7988
+ };
7989
+ var progressEventFromStatus = (input) => {
7990
+ const statusRecord = recordValue(input.status);
7991
+ return compactRecord({
7992
+ phase: input.phase,
7993
+ jobId: input.jobId,
7994
+ operation: firstString(statusRecord, ["operation", "operation_type", "operationType"]),
7995
+ status: firstString(statusRecord, ["status", "state"]),
7996
+ progress: firstNumber(statusRecord, [
7997
+ "progress",
7998
+ "progress_percent",
7999
+ "progressPercent",
8000
+ "percentage",
8001
+ "percent"
8002
+ ]),
8003
+ completed: firstNumber(statusRecord, [
8004
+ "completed_items",
8005
+ "completedItems",
8006
+ "completed_tests",
8007
+ "completedTests",
8008
+ "completed_rules",
8009
+ "completedRules"
8010
+ ]),
8011
+ total: firstNumber(statusRecord, [
8012
+ "total_items",
8013
+ "totalItems",
8014
+ "total_tests",
8015
+ "totalTests",
8016
+ "total_rules",
8017
+ "totalRules"
8018
+ ]),
8019
+ currentItem: firstString(statusRecord, [
8020
+ "current_item",
8021
+ "currentItem",
8022
+ "current_test",
8023
+ "currentTest",
8024
+ "current_rule",
8025
+ "currentRule"
8026
+ ]),
8027
+ message: publicValidationText(
8028
+ firstString(statusRecord, ["message", "detail", "description"])
8029
+ ),
8030
+ items: firstArray2(statusRecord, [
8031
+ "recent_events",
8032
+ "recentEvents",
8033
+ "events",
8034
+ "progress_events",
8035
+ "progressEvents"
8036
+ ]).map((item) => normalizeProgressItem(item, input.context)).filter((item) => item.name || item.message),
8037
+ elapsedMs: input.elapsedMs
8038
+ });
8039
+ };
8040
+ var failedStatus = (status) => ["fail", "failed", "error"].includes(String(status ?? "").trim().toLowerCase());
8041
+ var passedStatus = (status) => ["pass", "passed", "success", "succeeded"].includes(
8042
+ String(status ?? "").trim().toLowerCase()
8043
+ );
8044
+ var progressItemsFromDetails = (details, nameFields, context) => {
8045
+ const targetLocation = (target) => {
8046
+ const name = firstString(target, ["name", "id"]);
8047
+ const type = firstString(target, ["type"]);
8048
+ if (name && type) {
8049
+ return `${name} (${type})`;
8050
+ }
8051
+ return name ?? type;
8052
+ };
8053
+ return details.filter((detail) => failedStatus(firstString(detail, ["status", "outcome", "result"]))).map((detail) => {
8054
+ const evidence = recordValue(detail.evidence);
8055
+ return compactRecord({
8056
+ name: firstString(detail, nameFields),
8057
+ status: firstString(detail, ["status", "outcome", "result"]),
8058
+ category: firstString(detail, ["category", "test_category", "testCategory"]),
8059
+ severity: firstString(detail, ["severity", "level"]),
8060
+ message: publicValidationText(
8061
+ firstString(detail, ["message", "description"]) ?? firstString(evidence, ["description", "synopsis"])
8062
+ ),
8063
+ recommendation: publicValidationText(
8064
+ firstString(detail, ["recommendation", "remediation"]) ?? firstString(evidence, ["recommendation", "remediation"])
8065
+ ),
8066
+ location: displayTemplateLocation(
8067
+ firstString(detail, ["file_path", "filePath", "location"]),
8068
+ context
8069
+ ) ?? targetLocation(recordValue(detail.target)),
8070
+ durationMs: firstNumber(detail, ["duration_ms", "durationMs"]),
8071
+ documentationUrl: firstString(detail, [
8072
+ "documentation_url",
8073
+ "documentationUrl",
8074
+ "help_url",
8075
+ "helpUrl"
8076
+ ]) ?? firstString(evidence, [
8077
+ "documentation_url",
8078
+ "documentationUrl",
8079
+ "help_url",
8080
+ "helpUrl"
8081
+ ])
8082
+ });
8083
+ }).filter((item) => item.name || item.message);
8084
+ };
8085
+ var resultProgressSummary = (result, operation, context) => {
8086
+ const resultRecord = recordValue(result) ?? {};
8087
+ const summary = recordValue(resultRecord.summary) ?? {};
8088
+ const detectedOperation = operationFromResult(result);
8089
+ const resolvedOperation = detectedOperation ?? operation;
8090
+ const operationText = String(resolvedOperation ?? "").toLowerCase();
8091
+ if (detectedOperation === "template_test" || operationText.includes("test")) {
8092
+ const details2 = normalizeTemplateTestDetails(result, context);
8093
+ const passed2 = firstNumber(resultRecord, ["passed_tests", "passedTests"]) ?? details2.filter((detail) => detail.passed === true || passedStatus(firstString(detail, ["status"]))).length;
8094
+ const failed2 = firstNumber(resultRecord, ["failed_tests", "failedTests"]) ?? details2.filter((detail) => detail.passed === false || failedStatus(firstString(detail, ["status"]))).length;
8095
+ const skipped = firstNumber(resultRecord, ["skipped_tests", "skippedTests"]) ?? 0;
8096
+ return {
8097
+ operation: resolvedOperation,
8098
+ progress: 100,
8099
+ message: `Template tests complete: ${passed2} passed, ${failed2} failed, ${skipped} skipped`,
8100
+ items: progressItemsFromDetails(details2, ["test_name", "testName", "name"], context)
8101
+ };
8102
+ }
8103
+ const details = normalizeTemplateValidationDetails(result);
8104
+ const passed = firstNumber(summary, ["passed_rules", "passedRules"]) ?? details.filter((detail) => passedStatus(firstString(detail, ["status"]))).length;
8105
+ const failed = firstNumber(summary, ["failed_rules", "failedRules"]) ?? details.filter((detail) => failedStatus(firstString(detail, ["status"]))).length;
8106
+ const total = firstNumber(summary, ["total_rules", "totalRules"]) ?? details.length;
8107
+ return {
8108
+ operation: resolvedOperation ?? "template_validate",
8109
+ progress: 100,
8110
+ message: `Validation complete: ${passed} passed, ${failed} failed across ${total} checks`,
8111
+ items: progressItemsFromDetails(details, ["rule_id", "rule_name", "ruleName", "name"], context)
8112
+ };
8113
+ };
8114
+ var formatTemplateProgressEvent = (event, command) => {
8115
+ const appendItemDetails = (lines2, item) => {
8116
+ const metadata = [
8117
+ item.category ? `category: ${item.category}` : void 0,
8118
+ item.severity ? `severity: ${item.severity}` : void 0,
8119
+ item.location ? `location: ${item.location}` : void 0,
8120
+ typeof item.durationMs === "number" ? `duration: ${item.durationMs}ms` : void 0
8121
+ ].filter(Boolean);
8122
+ if (metadata.length) {
8123
+ lines2.push(` ${metadata.join(" | ")}`);
8124
+ }
8125
+ if (item.name && item.message) {
8126
+ lines2.push(` message: ${item.message}`);
8127
+ }
8128
+ if (item.recommendation) {
8129
+ lines2.push(` recommendation: ${item.recommendation}`);
8130
+ }
8131
+ if (item.documentationUrl) {
8132
+ lines2.push(` docs: ${item.documentationUrl}`);
8133
+ }
8134
+ };
8135
+ if (event.phase === "submitted") {
8136
+ return [`${command} job ${event.jobId} submitted`];
8137
+ }
8138
+ if (event.phase === "result") {
8139
+ const lines2 = event.message ? [event.message] : [`${command} job ${event.jobId} complete`];
8140
+ for (const item of event.items ?? []) {
8141
+ const status2 = item.status ? `${item.status} ` : "";
8142
+ lines2.push(` - ${status2}${item.name ?? item.message ?? "item"}`);
8143
+ appendItemDetails(lines2, item);
8144
+ }
8145
+ return lines2;
8146
+ }
8147
+ const status = event.status ? event.status.toUpperCase() : "RUNNING";
8148
+ const progress = typeof event.progress === "number" ? ` ${Math.round(event.progress)}%` : "";
8149
+ const completed = typeof event.completed === "number" && typeof event.total === "number" ? ` (${event.completed}/${event.total})` : "";
8150
+ const current = event.currentItem ? ` current: ${event.currentItem}` : "";
8151
+ const message = event.message ? ` ${event.message}` : "";
8152
+ const lines = [
8153
+ `${command} job ${event.jobId}: ${status}${progress}${completed}${current}${message}`
8154
+ ];
8155
+ for (const item of event.items ?? []) {
8156
+ const statusText = item.status ? `${item.status} ` : "";
8157
+ const passedText = item.status || item.passed === void 0 ? "" : item.passed ? "Pass " : "Fail ";
8158
+ lines.push(` - ${statusText}${passedText}${item.name ?? item.message ?? "item"}`);
8159
+ appendItemDetails(lines, item);
8160
+ }
8161
+ return lines;
8162
+ };
8163
+ var templateProgressEventKey = (event) => JSON.stringify({
8164
+ phase: event.phase,
8165
+ operation: event.operation,
8166
+ status: event.status,
8167
+ progress: event.progress,
8168
+ completed: event.completed,
8169
+ total: event.total,
8170
+ currentItem: event.currentItem,
8171
+ message: event.message,
8172
+ items: event.items
8173
+ });
7705
8174
  var unwrapTemplateOperationResult = (value) => {
7706
8175
  const record = recordValue(value);
7707
8176
  if (!record) {
@@ -7713,9 +8182,9 @@ var unwrapTemplateOperationResult = (value) => {
7713
8182
  }
7714
8183
  const nestedResult = recordValue(result.result);
7715
8184
  if (nestedResult) {
7716
- return nestedResult;
8185
+ return sanitizeTemplateOperationText(nestedResult);
7717
8186
  }
7718
- return result;
8187
+ return sanitizeTemplateOperationText(result);
7719
8188
  };
7720
8189
  var resolvedOperationResult = (value) => recordValue(unwrapTemplateOperationResult(value));
7721
8190
  var validationResults = (result) => {
@@ -7747,9 +8216,15 @@ var normalizeTemplateValidationDetails = (result) => validationResults(recordVal
7747
8216
  pillar: firstString(row, ["pillar"]),
7748
8217
  ...Object.keys(target).length ? { target } : {},
7749
8218
  evidence: compactRecord({
7750
- description: firstString(row, ["description", "message"]) ?? firstString(info, ["description"]),
7751
- synopsis: firstString(row, ["synopsis"]) ?? firstString(info, ["synopsis"]),
7752
- recommendation: firstString(row, ["recommendation", "remediation"]),
8219
+ description: publicValidationText(
8220
+ firstString(row, ["description", "message"]) ?? firstString(info, ["description"])
8221
+ ),
8222
+ synopsis: publicValidationText(
8223
+ firstString(row, ["synopsis"]) ?? firstString(info, ["synopsis"])
8224
+ ),
8225
+ recommendation: publicValidationText(
8226
+ firstString(row, ["recommendation", "remediation"])
8227
+ ),
7753
8228
  documentation_url: firstString(row, [
7754
8229
  "documentation_url",
7755
8230
  "documentationUrl",
@@ -7776,7 +8251,7 @@ var withTemplateValidationDetails = (value) => {
7776
8251
  details: normalizeTemplateValidationDetails(result)
7777
8252
  });
7778
8253
  };
7779
- var normalizeTemplateTestDetails = (result) => arrayValue(recordValue(result)?.test_results).map((item) => {
8254
+ var normalizeTemplateTestDetails = (result, context) => arrayValue(recordValue(result)?.test_results).map((item) => {
7780
8255
  const row = recordValue(item) ?? {};
7781
8256
  const passed = typeof row.passed === "boolean" ? row.passed : void 0;
7782
8257
  return compactRecord({
@@ -7786,17 +8261,23 @@ var normalizeTemplateTestDetails = (result) => arrayValue(recordValue(result)?.t
7786
8261
  status: passed === void 0 ? firstString(row, ["status"]) : passed ? "Pass" : "Fail",
7787
8262
  passed,
7788
8263
  severity: firstString(row, ["severity", "level"]),
7789
- message: firstString(row, ["message", "description"]),
7790
- recommendation: firstString(row, ["recommendation", "remediation"]),
8264
+ message: publicValidationText(firstString(row, ["message", "description"])),
8265
+ recommendation: publicValidationText(
8266
+ firstString(row, ["recommendation", "remediation"])
8267
+ ),
7791
8268
  duration_ms: typeof row.duration_ms === "number" ? row.duration_ms : typeof row.durationMs === "number" ? row.durationMs : void 0,
7792
- file_path: firstString(row, ["file_path", "filePath"])
8269
+ file_path: displayTemplateLocation(
8270
+ firstString(row, ["file_path", "filePath"]),
8271
+ context
8272
+ )
7793
8273
  });
7794
8274
  });
7795
- var withTemplateTestDetails = (value) => {
7796
- const result = resolvedOperationResult(value);
7797
- if (!result) {
8275
+ var withTemplateTestDetails = (value, context) => {
8276
+ const rawResult = resolvedOperationResult(value);
8277
+ if (!rawResult) {
7798
8278
  return value;
7799
8279
  }
8280
+ const result = recordValue(sanitizeTemplateLocationFields(rawResult, context)) ?? rawResult;
7800
8281
  const original = recordValue(value);
7801
8282
  const jobFields = original && ("jobId" in original || "status" in original) ? compactRecord({
7802
8283
  submitted: original.submitted,
@@ -7812,7 +8293,7 @@ var withTemplateTestDetails = (value) => {
7812
8293
  failed_tests: result.failed_tests,
7813
8294
  skipped_tests: result.skipped_tests
7814
8295
  }),
7815
- details: normalizeTemplateTestDetails(result)
8296
+ details: normalizeTemplateTestDetails(result, context)
7816
8297
  });
7817
8298
  };
7818
8299
  var getTemplateValidationJobStatus = async (input) => fetchCloudEvalJson({
@@ -7834,10 +8315,34 @@ var waitForTemplateValidationResult = async (input) => {
7834
8315
  }
7835
8316
  const waitTimeoutMs = Math.max(1, input.waitTimeoutMs ?? 6e5);
7836
8317
  const pollIntervalMs = Math.max(500, input.pollIntervalMs ?? 2500);
8318
+ const progressContext = {
8319
+ templatePath: input.templatePath,
8320
+ parametersPath: input.parametersPath
8321
+ };
7837
8322
  const deadline = Date.now() + waitTimeoutMs;
8323
+ const startedAt = Date.now();
8324
+ const elapsedMs = () => Date.now() - startedAt;
8325
+ await input.onProgress?.(
8326
+ progressEventFromStatus({
8327
+ phase: "submitted",
8328
+ jobId,
8329
+ status: input.submitted,
8330
+ elapsedMs: elapsedMs(),
8331
+ context: progressContext
8332
+ })
8333
+ );
7838
8334
  let status;
7839
8335
  for (; ; ) {
7840
8336
  status = await getTemplateValidationJobStatus({ ...input, jobId });
8337
+ await input.onProgress?.(
8338
+ progressEventFromStatus({
8339
+ phase: "status",
8340
+ jobId,
8341
+ status,
8342
+ elapsedMs: elapsedMs(),
8343
+ context: progressContext
8344
+ })
8345
+ );
7841
8346
  if (isTerminalJobStatus3(status)) {
7842
8347
  break;
7843
8348
  }
@@ -7853,13 +8358,32 @@ var waitForTemplateValidationResult = async (input) => {
7853
8358
  `Template validation job ${jobId} ended with status ${normalizedStatus(status) || "unknown"}.`
7854
8359
  );
7855
8360
  }
8361
+ const result = sanitizeTemplateLocationFields(
8362
+ unwrapTemplateOperationResult(
8363
+ await getTemplateValidationJobResult({ ...input, jobId })
8364
+ ),
8365
+ progressContext
8366
+ );
8367
+ const statusEvent = progressEventFromStatus({
8368
+ phase: "status",
8369
+ jobId,
8370
+ status,
8371
+ elapsedMs: elapsedMs(),
8372
+ context: progressContext
8373
+ });
8374
+ await input.onProgress?.(
8375
+ compactRecord({
8376
+ ...statusEvent,
8377
+ phase: "result",
8378
+ ...resultProgressSummary(result, statusEvent.operation, progressContext),
8379
+ elapsedMs: elapsedMs()
8380
+ })
8381
+ );
7856
8382
  return {
7857
8383
  submitted: input.submitted,
7858
8384
  jobId,
7859
8385
  status,
7860
- result: unwrapTemplateOperationResult(
7861
- await getTemplateValidationJobResult({ ...input, jobId })
7862
- )
8386
+ result
7863
8387
  };
7864
8388
  };
7865
8389
  var templateTestRequestBody = async (files, options) => {
@@ -9234,7 +9758,7 @@ var mcpToolDefinitions = [
9234
9758
  },
9235
9759
  wait: {
9236
9760
  type: "boolean",
9237
- description: "Poll an async validation job until results are ready.",
9761
+ description: "Poll an async validation job until results are ready. When the MCP call includes _meta.progressToken, wait progress is emitted as notifications/progress.",
9238
9762
  default: false
9239
9763
  },
9240
9764
  pollIntervalMs: {
@@ -9291,7 +9815,7 @@ var mcpToolDefinitions = [
9291
9815
  verbose: { type: "boolean", default: false },
9292
9816
  wait: {
9293
9817
  type: "boolean",
9294
- description: "Poll an async template test job until results are ready.",
9818
+ description: "Poll an async template test job until results are ready. When the MCP call includes _meta.progressToken, wait progress is emitted as notifications/progress.",
9295
9819
  default: false
9296
9820
  },
9297
9821
  pollIntervalMs: {
@@ -10753,7 +11277,7 @@ var buildToolHandlers = (serverOptions) => {
10753
11277
  });
10754
11278
  return withEnvelope({ command: "projects graph sync-runs", data });
10755
11279
  });
10756
- handlers.set("template_validate", async (args) => {
11280
+ handlers.set("template_validate", async (args, context) => {
10757
11281
  const config = await resolveInvocationConfig(serverOptions, args);
10758
11282
  const auth = await resolveAuth(config, { requireUser: true });
10759
11283
  const templatePath = stringValue(args.templatePath);
@@ -10786,14 +11310,17 @@ var buildToolHandlers = (serverOptions) => {
10786
11310
  userId: auth.user.id,
10787
11311
  submitted,
10788
11312
  pollIntervalMs: numberValue(args.pollIntervalMs),
10789
- waitTimeoutMs: numberValue(args.waitTimeoutMs)
11313
+ waitTimeoutMs: numberValue(args.waitTimeoutMs),
11314
+ templatePath,
11315
+ parametersPath: stringValue(args.parametersPath),
11316
+ onProgress: context?.sendProgress ? (event) => context.sendProgress(event, "validate template") : void 0
10790
11317
  }) : submitted;
10791
11318
  return withEnvelope({
10792
11319
  command: "validate template",
10793
11320
  data: booleanValue(args.details) ? withTemplateValidationDetails(data) : data
10794
11321
  });
10795
11322
  });
10796
- handlers.set("template_test", async (args) => {
11323
+ handlers.set("template_test", async (args, context) => {
10797
11324
  const config = await resolveInvocationConfig(serverOptions, args);
10798
11325
  const auth = await resolveAuth(config, { requireUser: true });
10799
11326
  const templatePath = stringValue(args.templatePath);
@@ -10818,11 +11345,17 @@ var buildToolHandlers = (serverOptions) => {
10818
11345
  userId: auth.user.id,
10819
11346
  submitted,
10820
11347
  pollIntervalMs: numberValue(args.pollIntervalMs),
10821
- waitTimeoutMs: numberValue(args.waitTimeoutMs)
11348
+ waitTimeoutMs: numberValue(args.waitTimeoutMs),
11349
+ templatePath,
11350
+ parametersPath: stringValue(args.parametersPath),
11351
+ onProgress: context?.sendProgress ? (event) => context.sendProgress(event, "validate tests") : void 0
10822
11352
  }) : submitted;
10823
11353
  return withEnvelope({
10824
11354
  command: "validate tests",
10825
- data: withTemplateTestDetails(data)
11355
+ data: withTemplateTestDetails(data, {
11356
+ templatePath,
11357
+ parametersPath: stringValue(args.parametersPath)
11358
+ })
10826
11359
  });
10827
11360
  });
10828
11361
  handlers.set("template_parse", async (args) => {
@@ -12013,6 +12546,34 @@ var serveMcpServer = async (options) => {
12013
12546
  transport: outputTransport
12014
12547
  });
12015
12548
  };
12549
+ const progressTokenFromParams = (params) => {
12550
+ const meta = isObject(params?._meta) ? params?._meta : void 0;
12551
+ const token = meta?.progressToken;
12552
+ if (typeof token === "string") {
12553
+ return token;
12554
+ }
12555
+ if (typeof token === "number" && Number.isFinite(token)) {
12556
+ return token;
12557
+ }
12558
+ return void 0;
12559
+ };
12560
+ const sendTemplateProgress = (progressToken, event, command) => {
12561
+ if (progressToken === void 0) {
12562
+ return;
12563
+ }
12564
+ const progress = typeof event.progress === "number" ? event.progress : typeof event.completed === "number" ? event.completed : event.phase === "result" ? 100 : 0;
12565
+ const total = typeof event.progress === "number" ? 100 : typeof event.total === "number" ? event.total : event.phase === "result" ? 100 : void 0;
12566
+ send({
12567
+ jsonrpc: "2.0",
12568
+ method: "notifications/progress",
12569
+ params: {
12570
+ progressToken,
12571
+ progress,
12572
+ ...total === void 0 ? {} : { total },
12573
+ message: formatTemplateProgressEvent(event, command).join("\n")
12574
+ }
12575
+ });
12576
+ };
12016
12577
  const handleRequest = async (request) => {
12017
12578
  debug("request received", {
12018
12579
  id: request.id,
@@ -12096,9 +12657,23 @@ var serveMcpServer = async (options) => {
12096
12657
  `Tool has no handler: ${name}`
12097
12658
  );
12098
12659
  }
12660
+ const progressToken = progressTokenFromParams(request.params);
12661
+ let lastProgressKey;
12099
12662
  const startedAt = Date.now();
12100
12663
  try {
12101
- const envelope = await handler(args);
12664
+ const envelope = await handler(args, {
12665
+ progressToken,
12666
+ sendProgress: (event, command) => {
12667
+ if (event.phase === "status") {
12668
+ const key = templateProgressEventKey(event);
12669
+ if (key === lastProgressKey) {
12670
+ return;
12671
+ }
12672
+ lastProgressKey = key;
12673
+ }
12674
+ sendTemplateProgress(progressToken, event, command);
12675
+ }
12676
+ });
12102
12677
  debug("tool call completed", { tool: name, ok: envelope.ok });
12103
12678
  await options.telemetry?.track("cli.mcp.tool", {
12104
12679
  command: "mcp",
@@ -13137,6 +13712,47 @@ var registerAgentsCommand = (program2, deps) => {
13137
13712
  };
13138
13713
 
13139
13714
  // src/validateCommand.ts
13715
+ var normalizeTemplateProgressMode = (value) => {
13716
+ if (value === true) {
13717
+ return "stderr";
13718
+ }
13719
+ if (value === void 0 || value === false || value === null) {
13720
+ return "none";
13721
+ }
13722
+ const mode = String(value).trim().toLowerCase();
13723
+ if (mode === "auto" || mode === "stderr" || mode === "ndjson" || mode === "none") {
13724
+ return mode;
13725
+ }
13726
+ throw new Error("--progress must be one of: auto, stderr, ndjson, none");
13727
+ };
13728
+ var createTemplateProgressReporter = (command, progress) => {
13729
+ const requestedMode = normalizeTemplateProgressMode(progress);
13730
+ const mode = requestedMode === "auto" ? process.stderr.isTTY ? "stderr" : "none" : requestedMode;
13731
+ if (mode === "none") {
13732
+ return void 0;
13733
+ }
13734
+ let lastStatusKey;
13735
+ return (event) => {
13736
+ if (event.phase === "status") {
13737
+ const key = templateProgressEventKey(event);
13738
+ if (key === lastStatusKey) {
13739
+ return;
13740
+ }
13741
+ lastStatusKey = key;
13742
+ }
13743
+ if (mode === "ndjson") {
13744
+ process.stderr.write(
13745
+ `${JSON.stringify({ type: "template_progress", command, ...event })}
13746
+ `
13747
+ );
13748
+ return;
13749
+ }
13750
+ for (const line of formatTemplateProgressEvent(event, command)) {
13751
+ process.stderr.write(`${line}
13752
+ `);
13753
+ }
13754
+ };
13755
+ };
13140
13756
  var addCommon5 = (command, deps) => addAuthOptions(command, deps.defaultBaseUrl).requiredOption("--template-file <path>", "Cloud template JSON file").option("--parameters-file <path>", "Optional parameters JSON file").option("--format <format>", "Output format: text, json, ndjson, markdown", "text").option("--output <file>", "Output file");
13141
13757
  var parsePositiveInteger2 = (value, optionName = "--max-results") => {
13142
13758
  if (!value) {
@@ -13158,7 +13774,11 @@ var registerValidateCommand = (program2, deps) => {
13158
13774
  "--rule <id>",
13159
13775
  "Run a specific validation check id; repeat for multiple checks",
13160
13776
  collectRule
13161
- ).option("--category <name>", "Validation category filter").option("--pillar <name>", "Architecture pillar filter").option("--min-severity <level>", "Minimum severity level").option("--max-results <count>", "Maximum validation results").option("--project <id>", "Project id for saved validation results").option("--save-report", "Persist validation results when a project is provided", false).option("--details", "Include frontend-style per-check evidence details", false).option("--wait", "Poll an async validation job until results are ready", false).option("--poll-interval <ms>", "Polling interval when --wait is set", "2500").option("--wait-timeout <ms>", "Maximum time to wait when --wait is set", "600000").action(async (options, command) => {
13777
+ ).option("--category <name>", "Validation category filter").option("--pillar <name>", "Architecture pillar filter").option("--min-severity <level>", "Minimum severity level").option("--max-results <count>", "Maximum validation results").option("--project <id>", "Project id for saved validation results").option("--save-report", "Persist validation results when a project is provided", false).option("--details", "Include frontend-style per-check evidence details", false).option("--wait", "Poll an async validation job until results are ready", false).option("--poll-interval <ms>", "Polling interval when --wait is set", "2500").option("--wait-timeout <ms>", "Maximum time to wait when --wait is set", "600000").option(
13778
+ "--progress [mode]",
13779
+ "Progress events while waiting: auto, stderr, ndjson, none",
13780
+ "none"
13781
+ ).action(async (options, command) => {
13162
13782
  try {
13163
13783
  const context = requireAuthUser(await resolveAuthContext(options, command, deps));
13164
13784
  const submitted = await validateTemplate({
@@ -13188,6 +13808,12 @@ var registerValidateCommand = (program2, deps) => {
13188
13808
  waitTimeoutMs: parsePositiveInteger2(
13189
13809
  options.waitTimeout,
13190
13810
  "--wait-timeout"
13811
+ ),
13812
+ templatePath: options.templateFile,
13813
+ parametersPath: options.parametersFile,
13814
+ onProgress: createTemplateProgressReporter(
13815
+ "validate template",
13816
+ options.progress
13191
13817
  )
13192
13818
  }) : submitted;
13193
13819
  const outputData = options.details ? withTemplateValidationDetails(data) : data;
@@ -13225,7 +13851,11 @@ var registerValidateCommand = (program2, deps) => {
13225
13851
  process.exit(1);
13226
13852
  }
13227
13853
  });
13228
- addCommon5(validate.command("tests").description("Run cloud template test checks"), deps).option("--test <name>", "Run a specific template test; repeat for multiple tests", collectRule).option("--skip-test <name>", "Skip a specific template test; repeat for multiple tests", collectRule).option("--category <name>", "Template test category").option("--group <name>", "Template test group; repeat for multiple groups", collectRule).option("--verbose", "Request verbose template test output", false).option("--wait", "Poll an async template test job until results are ready", false).option("--poll-interval <ms>", "Polling interval when --wait is set", "2500").option("--wait-timeout <ms>", "Maximum time to wait when --wait is set", "600000").action(async (options, command) => {
13854
+ addCommon5(validate.command("tests").description("Run cloud template test checks"), deps).option("--test <name>", "Run a specific template test; repeat for multiple tests", collectRule).option("--skip-test <name>", "Skip a specific template test; repeat for multiple tests", collectRule).option("--category <name>", "Template test category").option("--group <name>", "Template test group; repeat for multiple groups", collectRule).option("--verbose", "Request verbose template test output", false).option("--wait", "Poll an async template test job until results are ready", false).option("--poll-interval <ms>", "Polling interval when --wait is set", "2500").option("--wait-timeout <ms>", "Maximum time to wait when --wait is set", "600000").option(
13855
+ "--progress [mode]",
13856
+ "Progress events while waiting: auto, stderr, ndjson, none",
13857
+ "none"
13858
+ ).action(async (options, command) => {
13229
13859
  try {
13230
13860
  const context = requireAuthUser(await resolveAuthContext(options, command, deps));
13231
13861
  const submitted = await testTemplate({
@@ -13252,11 +13882,20 @@ var registerValidateCommand = (program2, deps) => {
13252
13882
  waitTimeoutMs: parsePositiveInteger2(
13253
13883
  options.waitTimeout,
13254
13884
  "--wait-timeout"
13885
+ ),
13886
+ templatePath: options.templateFile,
13887
+ parametersPath: options.parametersFile,
13888
+ onProgress: createTemplateProgressReporter(
13889
+ "validate tests",
13890
+ options.progress
13255
13891
  )
13256
13892
  }) : submitted;
13257
13893
  await writeFormattedOutput({
13258
13894
  command: "validate tests",
13259
- data: withTemplateTestDetails(data),
13895
+ data: withTemplateTestDetails(data, {
13896
+ templatePath: options.templateFile,
13897
+ parametersPath: options.parametersFile
13898
+ }),
13260
13899
  format: options.format,
13261
13900
  output: options.output
13262
13901
  });
@@ -15626,7 +16265,7 @@ program.command("tui").description("Open the CloudEval Terminal UI").option(
15626
16265
  const { assertSecureBaseUrl } = await import("./dist-PEYJDO7A.js");
15627
16266
  const [{ render }, { App }] = await Promise.all([
15628
16267
  import("ink"),
15629
- import("./App-7QUH5EKH.js")
16268
+ import("./App-FRLV34U4.js")
15630
16269
  ]);
15631
16270
  const baseUrl = await resolveBaseUrl(options, command);
15632
16271
  assertSecureBaseUrl(baseUrl);
@@ -15684,7 +16323,7 @@ program.command("chat").description("Start an interactive chat session").option(
15684
16323
  const { assertSecureBaseUrl } = await import("./dist-PEYJDO7A.js");
15685
16324
  const [{ render }, { App }] = await Promise.all([
15686
16325
  import("ink"),
15687
- import("./App-7QUH5EKH.js")
16326
+ import("./App-FRLV34U4.js")
15688
16327
  ]);
15689
16328
  const baseUrl = await resolveBaseUrl(options, command);
15690
16329
  assertSecureBaseUrl(baseUrl);
@@ -16438,7 +17077,7 @@ Error: ${errorMsg}
16438
17077
  program.command("banner").description("Preview the startup banner and terminal capabilities").action(async () => {
16439
17078
  const { render } = await import("ink");
16440
17079
  const BannerPreview = React.lazy(async () => ({
16441
- default: (await import("./Banner-AWNB7NZK.js")).Banner
17080
+ default: (await import("./Banner-IFLO2NC6.js")).Banner
16442
17081
  }));
16443
17082
  render(
16444
17083
  /* @__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.1",
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.1",
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",