@jadenrazo/cloudcost-mcp 1.0.0 → 1.2.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.
@@ -121,6 +121,34 @@ function getRegionPriceMultipliers() {
121
121
  }
122
122
  return _regionPriceMultipliers;
123
123
  }
124
+ var _fallbackMeta = {};
125
+ function getFallbackMetadata(provider) {
126
+ if (_fallbackMeta[provider] !== void 0) return _fallbackMeta[provider];
127
+ const relPath = `${provider}-pricing/metadata.json`;
128
+ try {
129
+ _fallbackMeta[provider] = loadJsonFile(relPath);
130
+ } catch {
131
+ _fallbackMeta[provider] = null;
132
+ }
133
+ return _fallbackMeta[provider];
134
+ }
135
+ var FRESH_MAX_AGE_DAYS = 14;
136
+ var AGING_MAX_AGE_DAYS = 45;
137
+ var MS_PER_DAY = 864e5;
138
+ function getPricingMetadata(provider, now = /* @__PURE__ */ new Date()) {
139
+ const meta = getFallbackMetadata(provider);
140
+ const source = meta?.source ?? "unknown";
141
+ if (!meta?.last_updated) {
142
+ return { source, as_of: null, age_days: null, staleness: "stale" };
143
+ }
144
+ const asOf = new Date(meta.last_updated);
145
+ if (Number.isNaN(asOf.getTime())) {
146
+ return { source, as_of: meta.last_updated, age_days: null, staleness: "stale" };
147
+ }
148
+ const ageDays = Math.max(0, Math.floor((now.getTime() - asOf.getTime()) / MS_PER_DAY));
149
+ const staleness = ageDays < FRESH_MAX_AGE_DAYS ? "fresh" : ageDays < AGING_MAX_AGE_DAYS ? "aging" : "stale";
150
+ return { source, as_of: meta.last_updated, age_days: ageDays, staleness };
151
+ }
124
152
  function _resetLoaderCache() {
125
153
  _resourceEquivalents = null;
126
154
  _instanceMap = null;
@@ -149,6 +177,8 @@ export {
149
177
  getGcpStoragePricing,
150
178
  getGcpDiskPricing,
151
179
  getRegionPriceMultipliers,
180
+ getFallbackMetadata,
181
+ getPricingMetadata,
152
182
  _resetLoaderCache
153
183
  };
154
- //# sourceMappingURL=chunk-TRRAOOVF.js.map
184
+ //# sourceMappingURL=chunk-LWTVE7UY.js.map
package/dist/cli.d.ts CHANGED
@@ -1 +1,42 @@
1
1
  #!/usr/bin/env node
2
+ /**
3
+ * CloudCost MCP CLI
4
+ *
5
+ * Standalone command-line interface for the CloudCost MCP server tools.
6
+ *
7
+ * Usage:
8
+ * cloudcost analyze <path>
9
+ * cloudcost estimate <path> [--provider aws|azure|gcp] [--region <region>] [--currency <code>]
10
+ * cloudcost compare <path> [--format markdown|json|csv|focus] [--currency <code>]
11
+ * cloudcost optimize <path> [--providers aws,azure,gcp]
12
+ * cloudcost what-if <path> --changes <changes.json> [--provider aws|azure|gcp] [--region <region>]
13
+ *
14
+ * Flags:
15
+ * --json Output raw JSON instead of formatted text
16
+ * --help, -h Show usage
17
+ */
18
+ interface ParsedArgs {
19
+ command: string | undefined;
20
+ path: string | undefined;
21
+ provider: string;
22
+ region: string | undefined;
23
+ format: string;
24
+ providers: string[];
25
+ currency: string;
26
+ changes: string | undefined;
27
+ json: boolean;
28
+ help: boolean;
29
+ }
30
+ declare const VALID_PROVIDERS: readonly ["aws", "azure", "gcp"];
31
+ type ValidProvider = (typeof VALID_PROVIDERS)[number];
32
+ declare const VALID_FORMATS: readonly ["markdown", "json", "csv", "focus"];
33
+ type ValidFormat = (typeof VALID_FORMATS)[number];
34
+ declare function parseArgs(argv: string[]): ParsedArgs;
35
+ /**
36
+ * Validate option values after parsing. Returns a human-readable usage error
37
+ * for the first invalid value, or null when everything is valid. Kept
38
+ * separate from parseArgs so both are unit-testable without process.exit.
39
+ */
40
+ declare function validateArgs(args: ParsedArgs): string | null;
41
+
42
+ export { type ParsedArgs, VALID_FORMATS, VALID_PROVIDERS, type ValidFormat, type ValidProvider, parseArgs, validateArgs };
package/dist/cli.js CHANGED
@@ -3,19 +3,23 @@
3
3
  import {
4
4
  PricingCache,
5
5
  PricingEngine,
6
+ SUPPORTED_CURRENCIES,
6
7
  analyzeTerraform,
7
8
  compareProviders,
8
9
  estimateCost,
9
10
  loadConfig,
10
11
  optimizeCost,
11
12
  whatIf
12
- } from "./chunk-E7KOWAMW.js";
13
- import "./chunk-TRRAOOVF.js";
13
+ } from "./chunk-CGMU4ETN.js";
14
+ import "./chunk-LWTVE7UY.js";
14
15
 
15
16
  // src/cli.ts
16
- import { readFileSync, existsSync, statSync } from "fs";
17
+ import { readFileSync, existsSync, statSync, realpathSync } from "fs";
17
18
  import { resolve, extname, join } from "path";
18
19
  import { readdirSync } from "fs";
20
+ import { pathToFileURL } from "url";
21
+ var VALID_PROVIDERS = ["aws", "azure", "gcp"];
22
+ var VALID_FORMATS = ["markdown", "json", "csv", "focus"];
19
23
  function parseArgs(argv) {
20
24
  const args = argv.slice(2);
21
25
  const parsed = {
@@ -25,6 +29,7 @@ function parseArgs(argv) {
25
29
  region: void 0,
26
30
  format: "markdown",
27
31
  providers: ["aws", "azure", "gcp"],
32
+ currency: "USD",
28
33
  changes: void 0,
29
34
  json: false,
30
35
  help: false
@@ -44,6 +49,8 @@ function parseArgs(argv) {
44
49
  parsed.format = args[++i];
45
50
  } else if (arg === "--providers" && args[i + 1]) {
46
51
  parsed.providers = args[++i].split(",").map((p) => p.trim());
52
+ } else if (arg === "--currency" && args[i + 1]) {
53
+ parsed.currency = args[++i].toUpperCase();
47
54
  } else if (arg === "--changes" && args[i + 1]) {
48
55
  parsed.changes = args[++i];
49
56
  } else if (!arg.startsWith("--")) {
@@ -57,6 +64,23 @@ function parseArgs(argv) {
57
64
  }
58
65
  return parsed;
59
66
  }
67
+ function validateArgs(args) {
68
+ if (!VALID_PROVIDERS.includes(args.provider)) {
69
+ return `Invalid --provider "${args.provider}". Must be one of: ${VALID_PROVIDERS.join(", ")}`;
70
+ }
71
+ if (!VALID_FORMATS.includes(args.format)) {
72
+ return `Invalid --format "${args.format}". Must be one of: ${VALID_FORMATS.join(", ")}`;
73
+ }
74
+ if (!SUPPORTED_CURRENCIES.includes(args.currency)) {
75
+ return `Invalid --currency "${args.currency}". Must be one of: ${SUPPORTED_CURRENCIES.join(", ")}`;
76
+ }
77
+ for (const p of args.providers) {
78
+ if (!VALID_PROVIDERS.includes(p)) {
79
+ return `Invalid provider "${p}" in --providers. Must be a comma-separated list of: ${VALID_PROVIDERS.join(", ")}`;
80
+ }
81
+ }
82
+ return null;
83
+ }
60
84
  function loadTerraformFiles(inputPath) {
61
85
  const resolved = resolve(inputPath);
62
86
  if (!existsSync(resolved)) {
@@ -117,8 +141,9 @@ Usage:
117
141
  Options:
118
142
  --provider <aws|azure|gcp> Target provider (default: aws)
119
143
  --region <region> Target region (default: auto-detect)
120
- --format <markdown|json|csv> Report format for compare (default: markdown)
144
+ --format <markdown|json|csv|focus> Report format for compare (default: markdown)
121
145
  --providers <aws,azure,gcp> Comma-separated providers for compare/optimize
146
+ --currency <USD|EUR|GBP|JPY|CAD|AUD|INR|BRL> Output currency for estimate/compare (default: USD)
122
147
  --changes <path> JSON file with changes array for what-if
123
148
  --json Output raw JSON
124
149
  --help, -h Show this help
@@ -142,17 +167,13 @@ async function runAnalyze(args, files, tfvars) {
142
167
  }
143
168
  }
144
169
  async function runEstimate(args, files, tfvars, pricingEngine, config) {
145
- const validProviders = ["aws", "azure", "gcp"];
146
- if (!validProviders.includes(args.provider)) {
147
- throw new Error(`Invalid provider "${args.provider}". Must be one of: aws, azure, gcp`);
148
- }
149
170
  const result = await estimateCost(
150
171
  {
151
172
  files,
152
173
  tfvars,
153
174
  provider: args.provider,
154
175
  region: args.region,
155
- currency: "USD"
176
+ currency: args.currency
156
177
  },
157
178
  pricingEngine,
158
179
  config
@@ -164,19 +185,15 @@ async function runEstimate(args, files, tfvars, pricingEngine, config) {
164
185
  }
165
186
  }
166
187
  async function runCompare(args, files, tfvars, pricingEngine, config) {
167
- const validFormats = ["markdown", "json", "csv"];
168
- if (!validFormats.includes(args.format)) {
169
- throw new Error(`Invalid format "${args.format}". Must be one of: markdown, json, csv`);
170
- }
171
188
  const validatedProviders = args.providers.filter(
172
- (p) => p === "aws" || p === "azure" || p === "gcp"
189
+ (p) => VALID_PROVIDERS.includes(p)
173
190
  );
174
191
  const result = await compareProviders(
175
192
  {
176
193
  files,
177
194
  tfvars,
178
195
  format: args.format,
179
- currency: "USD",
196
+ currency: args.currency,
180
197
  providers: validatedProviders.length > 0 ? validatedProviders : ["aws", "azure", "gcp"]
181
198
  },
182
199
  pricingEngine,
@@ -195,7 +212,7 @@ async function runCompare(args, files, tfvars, pricingEngine, config) {
195
212
  }
196
213
  async function runOptimize(args, files, tfvars, pricingEngine, config) {
197
214
  const validatedProviders = args.providers.filter(
198
- (p) => p === "aws" || p === "azure" || p === "gcp"
215
+ (p) => VALID_PROVIDERS.includes(p)
199
216
  );
200
217
  const result = await optimizeCost(
201
218
  {
@@ -258,8 +275,7 @@ async function runWhatIf(args, files, tfvars, pricingEngine, config) {
258
275
  new_value: c["new_value"]
259
276
  };
260
277
  });
261
- const validProviders = ["aws", "azure", "gcp"];
262
- const provider = validProviders.includes(args.provider) ? args.provider : void 0;
278
+ const provider = VALID_PROVIDERS.includes(args.provider) ? args.provider : void 0;
263
279
  const result = await whatIf(
264
280
  {
265
281
  files,
@@ -292,6 +308,14 @@ async function main() {
292
308
  }
293
309
  if (!args.path) {
294
310
  process.stderr.write(`Error: path argument is required
311
+ `);
312
+ printUsage();
313
+ process.exit(1);
314
+ }
315
+ const argError = validateArgs(args);
316
+ if (argError) {
317
+ process.stderr.write(`Error: ${argError}
318
+
295
319
  `);
296
320
  printUsage();
297
321
  process.exit(1);
@@ -337,9 +361,26 @@ async function main() {
337
361
  cache.close();
338
362
  }
339
363
  }
340
- main().catch((err) => {
341
- process.stderr.write(`Fatal error: ${err instanceof Error ? err.message : String(err)}
364
+ function isMainModule() {
365
+ const entry = process.argv[1];
366
+ if (!entry) return false;
367
+ try {
368
+ return import.meta.url === pathToFileURL(realpathSync(entry)).href;
369
+ } catch {
370
+ return false;
371
+ }
372
+ }
373
+ if (isMainModule()) {
374
+ main().catch((err) => {
375
+ process.stderr.write(`Fatal error: ${err instanceof Error ? err.message : String(err)}
342
376
  `);
343
- process.exit(1);
344
- });
377
+ process.exit(1);
378
+ });
379
+ }
380
+ export {
381
+ VALID_FORMATS,
382
+ VALID_PROVIDERS,
383
+ parseArgs,
384
+ validateArgs
385
+ };
345
386
  //# sourceMappingURL=cli.js.map