@jadenrazo/cloudcost-mcp 1.0.1 → 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.
- package/CHANGELOG.md +40 -5
- package/README.md +70 -115
- package/VERSIONING.md +99 -0
- package/data/aws-pricing/metadata.json +1 -1
- package/data/azure-pricing/metadata.json +1 -1
- package/dist/{chunk-6O2Y6MKU.js → chunk-CGMU4ETN.js} +927 -1067
- package/dist/{chunk-MNFT5YKN.js → chunk-LWTVE7UY.js} +19 -1
- package/dist/cli.d.ts +41 -0
- package/dist/cli.js +63 -22
- package/dist/index.js +361 -137
- package/dist/{loader-UWVEXYMR.js → loader-CMBV7346.js} +4 -2
- package/package.json +34 -21
- package/MIGRATION.md +0 -40
- package/STABILITY.md +0 -71
|
@@ -132,6 +132,23 @@ function getFallbackMetadata(provider) {
|
|
|
132
132
|
}
|
|
133
133
|
return _fallbackMeta[provider];
|
|
134
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
|
+
}
|
|
135
152
|
function _resetLoaderCache() {
|
|
136
153
|
_resourceEquivalents = null;
|
|
137
154
|
_instanceMap = null;
|
|
@@ -161,6 +178,7 @@ export {
|
|
|
161
178
|
getGcpDiskPricing,
|
|
162
179
|
getRegionPriceMultipliers,
|
|
163
180
|
getFallbackMetadata,
|
|
181
|
+
getPricingMetadata,
|
|
164
182
|
_resetLoaderCache
|
|
165
183
|
};
|
|
166
|
-
//# sourceMappingURL=chunk-
|
|
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-
|
|
13
|
-
import "./chunk-
|
|
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>
|
|
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:
|
|
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
|
|
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:
|
|
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
|
|
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
|
|
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
|
-
|
|
341
|
-
|
|
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
|
-
|
|
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
|