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