cloudburn 0.16.0 → 0.17.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.
Files changed (3) hide show
  1. package/README.md +21 -0
  2. package/dist/cli.js +50 -9
  3. package/package.json +4 -2
package/README.md CHANGED
@@ -115,6 +115,27 @@ cloudburn rules list --service ec2 --source discovery --severity high
115
115
 
116
116
  The discovery config equivalent is `discovery.fail-on`.
117
117
 
118
+ Discovery has a five-minute deadline. Use `--timeout <seconds>` to change it, for example `cloudburn discover --timeout 600`. If the deadline expires, the command exits with code 2 and reports the timeout.
119
+
120
+ Discovery reuses fresh, complete evidence from a per-user cache at `$XDG_CACHE_HOME/cloudburn/evidence`, or
121
+ `~/.cache/cloudburn/evidence` when `XDG_CACHE_HOME` is unset. Rules and configuration are evaluated on every run.
122
+
123
+ ```bash
124
+ cloudburn discover --cache refresh
125
+ cloudburn discover --cache off
126
+ cloudburn discover --cache-dir /path/to/evidence --cache-context policy-revision-2
127
+ ```
128
+
129
+ `--cache refresh` collects current evidence and never silently falls back when collection fails. `--cache off` bypasses
130
+ persistent reads and writes. Customer evidence reuse requires a safe temporary AWS credential session scope or an
131
+ explicit `--cache-context` identifying the effective authorization and session-policy revision. An account ID or role
132
+ ARN alone is insufficient. Change the context when permissions change. Public pricing is cached independently of
133
+ customer evidence, and credentials are never stored.
134
+
135
+ Table output includes a freshness summary; `--format json` includes full evidence provenance and coverage. TTLs are
136
+ tunable starting policies rather than measured optimal defaults. See the [cache options](../../docs/reference/commands.md#discovery-evidence-cache)
137
+ and [SDK README](../sdk/README.md) for modes, defaults, and freshness tradeoffs.
138
+
118
139
  The CLI targets one region per run. Multi-region discovery remains available through the SDK and still needs an AWS Resource Explorer aggregator plus an unfiltered default view in the aggregator region.
119
140
  `CLDBRN-AWS-TAGGING-1` is opt-in and requires an accessible aggregator; a local-only setup cannot run account-wide tagging discovery.
120
141
  `CLDBRN-AWS-LAMBDA-4` is opt-in and requires AWS Compute Optimizer enrollment.
package/dist/cli.js CHANGED
@@ -654,20 +654,34 @@ ${regionsTable}`;
654
654
  const findingRows = projectFindingRows(response.result);
655
655
  const diagnosticRows = projectDiagnosticRows(response.result);
656
656
  const suppressedCount = response.result.suppressed?.length ?? 0;
657
- const withSuppressedCount = (content) => suppressedCount > 0 ? `${content}
658
-
659
- Suppressed: ${suppressedCount}` : content;
657
+ const withScanSummary = (content) => {
658
+ const sections = [content];
659
+ if (suppressedCount > 0) {
660
+ sections.push(`Suppressed: ${suppressedCount}`);
661
+ }
662
+ const evidence = response.result.evidence ?? [];
663
+ if (evidence.length > 0) {
664
+ const cachedCount = evidence.filter((entry) => entry.source === "cache").length;
665
+ const incompleteCount = evidence.filter((entry) => !entry.complete).length;
666
+ const oldestObservation = evidence.map((entry) => entry.observedAt).sort()[0];
667
+ sections.push(
668
+ `Evidence: ${cachedCount} cached, ${evidence.length - cachedCount} collected; ${incompleteCount} incomplete.
669
+ Oldest observation: ${oldestObservation}`
670
+ );
671
+ }
672
+ return sections.join("\n\n");
673
+ };
660
674
  if (findingRows.length === 0 && diagnosticRows.length === 0) {
661
- return withSuppressedCount(suppressedCount > 0 ? "No active findings." : "No findings.");
675
+ return withScanSummary(suppressedCount > 0 ? "No active findings." : "No findings.");
662
676
  }
663
677
  if (findingRows.length === 0) {
664
- return withSuppressedCount(`Diagnostics
678
+ return withScanSummary(`Diagnostics
665
679
  ${renderAsciiTable(diagnosticRows, diagnosticColumns)}`);
666
680
  }
667
681
  if (diagnosticRows.length === 0) {
668
- return withSuppressedCount(renderAsciiTable(findingRows, scanColumns));
682
+ return withScanSummary(renderAsciiTable(findingRows, scanColumns));
669
683
  }
670
- return withSuppressedCount(
684
+ return withScanSummary(
671
685
  `${renderAsciiTable(findingRows, scanColumns)}
672
686
 
673
687
  Diagnostics
@@ -1059,6 +1073,8 @@ var registerConfigCommand = (program) => {
1059
1073
  };
1060
1074
 
1061
1075
  // src/commands/discover.ts
1076
+ import { homedir } from "os";
1077
+ import { join as join2 } from "path";
1062
1078
  import {
1063
1079
  assertSupportedAwsRegion,
1064
1080
  CloudBurnClient
@@ -1142,6 +1158,19 @@ var validateServiceList = (mode, services) => {
1142
1158
  };
1143
1159
 
1144
1160
  // src/commands/discover.ts
1161
+ var parseDiscoveryTimeout = (value) => {
1162
+ const seconds = Number(value);
1163
+ if (!Number.isInteger(seconds) || seconds < 1 || seconds > 2147483) {
1164
+ throw new InvalidArgumentError3("Timeout must be a whole number of seconds between 1 and 2147483.");
1165
+ }
1166
+ return seconds;
1167
+ };
1168
+ var parseCacheMode = (value) => {
1169
+ if (value === "normal" || value === "refresh" || value === "off") {
1170
+ return value;
1171
+ }
1172
+ throw new InvalidArgumentError3("Cache mode must be normal, refresh, or off.");
1173
+ };
1145
1174
  var parseDiscoveryServiceList = (value) => validateServiceList("discovery", parseServiceList(value)) ?? [];
1146
1175
  var describeInitializationMessage = (result) => {
1147
1176
  const baseMessage = result.indexType === "aggregator" ? result.aggregatorAction === "promoted" ? `Promoted the existing local Resource Explorer index in ${result.aggregatorRegion} to the aggregator.` : result.aggregatorAction === "created" ? `Configured ${result.aggregatorRegion} as the Resource Explorer aggregator.` : result.coverage === "full" ? `Resource Explorer aggregator already exists in ${result.aggregatorRegion}.` : `Resource Explorer aggregator already exists in ${result.aggregatorRegion}, but only ${result.observedStatus.indexedRegionCount} of ${result.observedStatus.totalRegionCount} regions are indexed.` : result.status === "EXISTING" ? `Local Resource Explorer setup already exists in ${result.aggregatorRegion}.` : `Local Resource Explorer setup created in ${result.aggregatorRegion}.`;
@@ -1249,7 +1278,10 @@ var registerDiscoverCommand = (program) => {
1249
1278
  "--region <region>",
1250
1279
  "AWS region to discover. Defaults to the current AWS region from AWS_REGION when omitted.",
1251
1280
  parseAwsRegion
1252
- ).option("--config <path>", "Explicit CloudBurn config file to load (required for config files in CI)").option(
1281
+ ).option("--config <path>", "Explicit CloudBurn config file to load (required for config files in CI)").option("--timeout <seconds>", "Maximum discovery duration in seconds (default: 300).", parseDiscoveryTimeout).option("--cache <mode>", "Evidence cache mode: normal, refresh, or off (default: normal).", parseCacheMode).option(
1282
+ "--cache-dir <path>",
1283
+ "Evidence cache directory (default: $XDG_CACHE_HOME/cloudburn/evidence or ~/.cache/cloudburn/evidence)."
1284
+ ).option("--cache-context <id>", "Effective authorization/session-policy revision for evidence reuse.").option(
1253
1285
  "--enabled-rules <ruleIds>",
1254
1286
  "Comma-separated rule IDs to enable. When set, CloudBurn checks only these rules. By default, AWS Core preset rules are enabled.",
1255
1287
  parseRuleIdList
@@ -1269,6 +1301,11 @@ var registerDiscoverCommand = (program) => {
1269
1301
  const configOverride = toDiscoveryConfigOverride(options);
1270
1302
  const loadedConfig = await scanner.loadConfig(options.config);
1271
1303
  const discoveryOptions = {
1304
+ cache: {
1305
+ mode: options.cache ?? "normal",
1306
+ directory: options.cacheDir ?? join2(process.env.XDG_CACHE_HOME ?? join2(homedir(), ".cache"), "cloudburn", "evidence"),
1307
+ ...options.cacheContext === void 0 ? {} : { authorizationContext: options.cacheContext }
1308
+ },
1272
1309
  target: resolveDiscoveryTarget(options.region)
1273
1310
  };
1274
1311
  if (configOverride !== void 0) {
@@ -1280,6 +1317,9 @@ var registerDiscoverCommand = (program) => {
1280
1317
  if (onProgress !== void 0) {
1281
1318
  discoveryOptions.onProgress = onProgress;
1282
1319
  }
1320
+ if (options.timeout !== void 0) {
1321
+ discoveryOptions.timeoutMs = options.timeout * 1e3;
1322
+ }
1283
1323
  const result = await scanner.discover(discoveryOptions);
1284
1324
  const format = resolveOutputFormat(command, void 0, loadedConfig.discovery.format ?? "table");
1285
1325
  const output = renderResponse({ kind: "scan-result", result }, format);
@@ -1297,6 +1337,7 @@ var registerDiscoverCommand = (program) => {
1297
1337
  [
1298
1338
  "cloudburn discover",
1299
1339
  "cloudburn discover --region eu-central-1",
1340
+ "cloudburn discover --cache refresh",
1300
1341
  "cloudburn discover status",
1301
1342
  "cloudburn discover init"
1302
1343
  ]
@@ -1556,7 +1597,7 @@ var isCliEntrypoint = (moduleUrl, argvEntry = process.argv[1]) => {
1556
1597
  };
1557
1598
  var createProgram = () => {
1558
1599
  const program = createCliCommand();
1559
- program.name("cloudburn").usage("[command]").description("Know what you spend. Fix what you waste.").version("0.16.0").option("--debug", "Write execution trace messages to stderr").option("--format <format>", OUTPUT_FORMAT_OPTION_DESCRIPTION, parseOutputFormat);
1600
+ program.name("cloudburn").usage("[command]").description("Know what you spend. Fix what you waste.").version("0.17.0").option("--debug", "Write execution trace messages to stderr").option("--format <format>", OUTPUT_FORMAT_OPTION_DESCRIPTION, parseOutputFormat);
1560
1601
  configureCliHelp(program);
1561
1602
  program.exitOverride();
1562
1603
  registerCompletionCommand(program);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "cloudburn",
3
- "version": "0.16.0",
3
+ "version": "0.17.0",
4
4
  "description": "Cloudburn CLI for cloud cost optimization",
5
5
  "homepage": "https://cloudburn.io/docs",
6
6
  "bugs": {
@@ -20,7 +20,7 @@
20
20
  ],
21
21
  "dependencies": {
22
22
  "commander": "^13.1.0",
23
- "@cloudburn/sdk": "0.32.0"
23
+ "@cloudburn/sdk": "0.34.0"
24
24
  },
25
25
  "devDependencies": {
26
26
  "@biomejs/biome": "^2.5.3",
@@ -51,6 +51,8 @@
51
51
  "build": "tsup",
52
52
  "typecheck": "tsc --noEmit",
53
53
  "test": "vitest run",
54
+ "test:e2e": "node --test test/e2e/*.test.mjs",
55
+ "test:package": "node --test test/package/*.test.mjs",
54
56
  "lint": "biome check --error-on-warnings .",
55
57
  "lint:fix": "biome check --write .",
56
58
  "clean": "rm -rf dist",