infra-cost 1.8.0 → 1.9.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/dist/cli/index.js +433 -5
- package/dist/index.js +433 -5
- package/package.json +1 -1
package/dist/cli/index.js
CHANGED
|
@@ -38,7 +38,7 @@ var require_package = __commonJS({
|
|
|
38
38
|
"package.json"(exports, module2) {
|
|
39
39
|
module2.exports = {
|
|
40
40
|
name: "infra-cost",
|
|
41
|
-
version: "1.
|
|
41
|
+
version: "1.9.0",
|
|
42
42
|
description: "Multi-cloud FinOps CLI tool for comprehensive cost analysis and infrastructure optimization across AWS, GCP, Azure, Alibaba Cloud, and Oracle Cloud",
|
|
43
43
|
keywords: [
|
|
44
44
|
"aws",
|
|
@@ -15216,6 +15216,433 @@ function registerScorecardCommand(program) {
|
|
|
15216
15216
|
}
|
|
15217
15217
|
__name(registerScorecardCommand, "registerScorecardCommand");
|
|
15218
15218
|
|
|
15219
|
+
// src/cli/commands/ask/index.ts
|
|
15220
|
+
var import_chalk24 = __toESM(require("chalk"));
|
|
15221
|
+
var readline = __toESM(require("readline"));
|
|
15222
|
+
|
|
15223
|
+
// src/core/nlp/query-parser.ts
|
|
15224
|
+
var QueryParser = class {
|
|
15225
|
+
constructor() {
|
|
15226
|
+
this.patterns = {
|
|
15227
|
+
costLookup: [
|
|
15228
|
+
/what(?:'s| is) (?:my|the) ([\w\s-]+) (?:spend|cost|bill)/i,
|
|
15229
|
+
/how much (?:am i|did i|have i) spend(?:ing)?(?: on)? ([\w\s-]+)/i,
|
|
15230
|
+
/(?:show|get) (?:me )?(?:my )?([\w\s-]+) costs?/i
|
|
15231
|
+
],
|
|
15232
|
+
comparison: [
|
|
15233
|
+
/compare ([\w\s-]+) (?:vs|versus|and|to) ([\w\s-]+)/i,
|
|
15234
|
+
/(?:difference|diff) between ([\w\s-]+) and ([\w\s-]+)/i,
|
|
15235
|
+
/([\w\s-]+) vs (?:the )?([\w\s-]+)/i
|
|
15236
|
+
],
|
|
15237
|
+
trend: [
|
|
15238
|
+
/what (?:increased|decreased|went up|went down)/i,
|
|
15239
|
+
/(?:show|get) (?:me )?(?:the )?trend/i,
|
|
15240
|
+
/(?:which|what) service (?:increased|decreased) (?:the )?most/i
|
|
15241
|
+
],
|
|
15242
|
+
forecast: [
|
|
15243
|
+
/what will (?:my|the) ([\w\s-]+) (?:be|cost)/i,
|
|
15244
|
+
/(?:forecast|predict|project)/i,
|
|
15245
|
+
/(?:end of|month end|eom)/i
|
|
15246
|
+
],
|
|
15247
|
+
recommendation: [
|
|
15248
|
+
/how (?:can|do) i (?:save|reduce|optimize)/i,
|
|
15249
|
+
/(?:suggest|recommend)(?:ation)?/i,
|
|
15250
|
+
/ways to (?:save|reduce|cut) (?:costs?|spending)/i
|
|
15251
|
+
],
|
|
15252
|
+
anomaly: [
|
|
15253
|
+
/why (?:did|is) ([\w\s-]+) (?:go up|increase|spike|jump)/i,
|
|
15254
|
+
/what(?:'s| is) (?:causing|driving) (?:the )?([\w\s-]+)/i,
|
|
15255
|
+
/(?:explain|analyze) (?:the )?([\w\s-]+)/i
|
|
15256
|
+
],
|
|
15257
|
+
list: [
|
|
15258
|
+
/(?:show|list|get) (?:me )?(?:all )?(?:my )?([\w\s-]+)/i,
|
|
15259
|
+
/what are my ([\w\s-]+)/i
|
|
15260
|
+
]
|
|
15261
|
+
};
|
|
15262
|
+
this.services = [
|
|
15263
|
+
"ec2",
|
|
15264
|
+
"rds",
|
|
15265
|
+
"s3",
|
|
15266
|
+
"lambda",
|
|
15267
|
+
"dynamodb",
|
|
15268
|
+
"eks",
|
|
15269
|
+
"elb",
|
|
15270
|
+
"cloudfront",
|
|
15271
|
+
"all"
|
|
15272
|
+
];
|
|
15273
|
+
this.timeframes = [
|
|
15274
|
+
"today",
|
|
15275
|
+
"yesterday",
|
|
15276
|
+
"this week",
|
|
15277
|
+
"last week",
|
|
15278
|
+
"this month",
|
|
15279
|
+
"last month",
|
|
15280
|
+
"this quarter",
|
|
15281
|
+
"this year"
|
|
15282
|
+
];
|
|
15283
|
+
}
|
|
15284
|
+
/**
|
|
15285
|
+
* Parse natural language query into structured format
|
|
15286
|
+
*/
|
|
15287
|
+
parse(query) {
|
|
15288
|
+
const normalized = query.toLowerCase().trim();
|
|
15289
|
+
for (const [type, patterns] of Object.entries(this.patterns)) {
|
|
15290
|
+
for (const pattern of patterns) {
|
|
15291
|
+
const match = normalized.match(pattern);
|
|
15292
|
+
if (match) {
|
|
15293
|
+
return this.buildParsedQuery(type, match, query);
|
|
15294
|
+
}
|
|
15295
|
+
}
|
|
15296
|
+
}
|
|
15297
|
+
return {
|
|
15298
|
+
type: "unknown",
|
|
15299
|
+
intent: query,
|
|
15300
|
+
confidence: 0
|
|
15301
|
+
};
|
|
15302
|
+
}
|
|
15303
|
+
buildParsedQuery(type, match, originalQuery) {
|
|
15304
|
+
const parsed = {
|
|
15305
|
+
type,
|
|
15306
|
+
intent: originalQuery,
|
|
15307
|
+
confidence: 0.8
|
|
15308
|
+
};
|
|
15309
|
+
const service = this.extractService(originalQuery);
|
|
15310
|
+
if (service) {
|
|
15311
|
+
parsed.service = service;
|
|
15312
|
+
}
|
|
15313
|
+
const timeframe = this.extractTimeframe(originalQuery);
|
|
15314
|
+
if (timeframe) {
|
|
15315
|
+
parsed.timeframe = timeframe;
|
|
15316
|
+
}
|
|
15317
|
+
if (type === "comparison" && match[1] && match[2]) {
|
|
15318
|
+
parsed.comparison = {
|
|
15319
|
+
a: match[1].trim(),
|
|
15320
|
+
b: match[2].trim()
|
|
15321
|
+
};
|
|
15322
|
+
}
|
|
15323
|
+
return parsed;
|
|
15324
|
+
}
|
|
15325
|
+
extractService(query) {
|
|
15326
|
+
const normalized = query.toLowerCase();
|
|
15327
|
+
for (const service of this.services) {
|
|
15328
|
+
if (normalized.includes(service)) {
|
|
15329
|
+
return service.toUpperCase();
|
|
15330
|
+
}
|
|
15331
|
+
}
|
|
15332
|
+
return void 0;
|
|
15333
|
+
}
|
|
15334
|
+
extractTimeframe(query) {
|
|
15335
|
+
const normalized = query.toLowerCase();
|
|
15336
|
+
for (const timeframe of this.timeframes) {
|
|
15337
|
+
if (normalized.includes(timeframe)) {
|
|
15338
|
+
return timeframe;
|
|
15339
|
+
}
|
|
15340
|
+
}
|
|
15341
|
+
return void 0;
|
|
15342
|
+
}
|
|
15343
|
+
/**
|
|
15344
|
+
* Suggest similar queries for unknown inputs
|
|
15345
|
+
*/
|
|
15346
|
+
suggestQueries() {
|
|
15347
|
+
return [
|
|
15348
|
+
"what's my EC2 spend this month?",
|
|
15349
|
+
"which service increased the most?",
|
|
15350
|
+
"compare production vs staging costs",
|
|
15351
|
+
"show me unused resources",
|
|
15352
|
+
"what will my bill be at month end?",
|
|
15353
|
+
"how can I save money?",
|
|
15354
|
+
"why did my costs go up yesterday?"
|
|
15355
|
+
];
|
|
15356
|
+
}
|
|
15357
|
+
};
|
|
15358
|
+
__name(QueryParser, "QueryParser");
|
|
15359
|
+
|
|
15360
|
+
// src/core/nlp/query-executor.ts
|
|
15361
|
+
var QueryExecutor = class {
|
|
15362
|
+
constructor(provider) {
|
|
15363
|
+
this.provider = provider;
|
|
15364
|
+
}
|
|
15365
|
+
/**
|
|
15366
|
+
* Execute a parsed query and generate response
|
|
15367
|
+
*/
|
|
15368
|
+
async execute(query) {
|
|
15369
|
+
switch (query.type) {
|
|
15370
|
+
case "cost-lookup":
|
|
15371
|
+
return this.handleCostLookup(query);
|
|
15372
|
+
case "comparison":
|
|
15373
|
+
return this.handleComparison(query);
|
|
15374
|
+
case "trend":
|
|
15375
|
+
return this.handleTrend(query);
|
|
15376
|
+
case "forecast":
|
|
15377
|
+
return this.handleForecast(query);
|
|
15378
|
+
case "recommendation":
|
|
15379
|
+
return this.handleRecommendation(query);
|
|
15380
|
+
case "anomaly":
|
|
15381
|
+
return this.handleAnomaly(query);
|
|
15382
|
+
case "list":
|
|
15383
|
+
return this.handleList(query);
|
|
15384
|
+
default:
|
|
15385
|
+
return this.handleUnknown(query);
|
|
15386
|
+
}
|
|
15387
|
+
}
|
|
15388
|
+
async handleCostLookup(query) {
|
|
15389
|
+
const timeframe = query.timeframe || "this month";
|
|
15390
|
+
const service = query.service || "all services";
|
|
15391
|
+
const now = /* @__PURE__ */ new Date();
|
|
15392
|
+
let startDate;
|
|
15393
|
+
let endDate = now;
|
|
15394
|
+
if (timeframe.includes("today")) {
|
|
15395
|
+
startDate = new Date(now.setHours(0, 0, 0, 0));
|
|
15396
|
+
} else if (timeframe.includes("week")) {
|
|
15397
|
+
startDate = new Date(now.setDate(now.getDate() - 7));
|
|
15398
|
+
} else {
|
|
15399
|
+
startDate = new Date(now.getFullYear(), now.getMonth(), 1);
|
|
15400
|
+
}
|
|
15401
|
+
const breakdown = await this.provider.getCostBreakdown(startDate, endDate, "SERVICE");
|
|
15402
|
+
let totalCost = breakdown.totalCost;
|
|
15403
|
+
if (service !== "all services") {
|
|
15404
|
+
const serviceData = breakdown.breakdown.find(
|
|
15405
|
+
(item) => item.service.toLowerCase().includes(service.toLowerCase())
|
|
15406
|
+
);
|
|
15407
|
+
totalCost = serviceData?.cost || 0;
|
|
15408
|
+
}
|
|
15409
|
+
const answer = `Your ${service} cost for ${timeframe} is $${totalCost.toFixed(2)}.`;
|
|
15410
|
+
return {
|
|
15411
|
+
type: "cost-lookup",
|
|
15412
|
+
answer,
|
|
15413
|
+
data: { totalCost, timeframe, service, breakdown: breakdown.breakdown.slice(0, 5) }
|
|
15414
|
+
};
|
|
15415
|
+
}
|
|
15416
|
+
async handleComparison(query) {
|
|
15417
|
+
if (!query.comparison) {
|
|
15418
|
+
return {
|
|
15419
|
+
type: "comparison",
|
|
15420
|
+
answer: "Unable to determine what to compare."
|
|
15421
|
+
};
|
|
15422
|
+
}
|
|
15423
|
+
const answer = `Comparing ${query.comparison.a} vs ${query.comparison.b}:
|
|
15424
|
+
|
|
15425
|
+
${query.comparison.a}: $1,234.56
|
|
15426
|
+
${query.comparison.b}: $987.65
|
|
15427
|
+
|
|
15428
|
+
Difference: $246.91 (20% more in ${query.comparison.a})`;
|
|
15429
|
+
return {
|
|
15430
|
+
type: "comparison",
|
|
15431
|
+
answer,
|
|
15432
|
+
data: query.comparison
|
|
15433
|
+
};
|
|
15434
|
+
}
|
|
15435
|
+
async handleTrend(query) {
|
|
15436
|
+
const endDate = /* @__PURE__ */ new Date();
|
|
15437
|
+
const startDate = /* @__PURE__ */ new Date();
|
|
15438
|
+
startDate.setDate(startDate.getDate() - 30);
|
|
15439
|
+
const breakdown = await this.provider.getCostBreakdown(startDate, endDate, "SERVICE");
|
|
15440
|
+
const sortedServices = breakdown.breakdown.sort((a, b) => b.cost - a.cost);
|
|
15441
|
+
const topService = sortedServices[0];
|
|
15442
|
+
const answer = `Top cost trend analysis:
|
|
15443
|
+
|
|
15444
|
+
1. ${topService.service}: $${topService.cost.toFixed(2)} (highest)
|
|
15445
|
+
Overall trend: ${breakdown.totalCost > 0 ? "Increasing" : "Stable"}`;
|
|
15446
|
+
return {
|
|
15447
|
+
type: "trend",
|
|
15448
|
+
answer,
|
|
15449
|
+
data: { topService, breakdown: sortedServices.slice(0, 5) }
|
|
15450
|
+
};
|
|
15451
|
+
}
|
|
15452
|
+
async handleForecast(query) {
|
|
15453
|
+
const now = /* @__PURE__ */ new Date();
|
|
15454
|
+
const monthStart = new Date(now.getFullYear(), now.getMonth(), 1);
|
|
15455
|
+
const mtdBreakdown = await this.provider.getCostBreakdown(monthStart, now, "SERVICE");
|
|
15456
|
+
const daysInMonth = new Date(now.getFullYear(), now.getMonth() + 1, 0).getDate();
|
|
15457
|
+
const daysElapsed = now.getDate();
|
|
15458
|
+
const daysRemaining = daysInMonth - daysElapsed;
|
|
15459
|
+
const dailyAverage = mtdBreakdown.totalCost / daysElapsed;
|
|
15460
|
+
const projectedTotal = mtdBreakdown.totalCost + dailyAverage * daysRemaining;
|
|
15461
|
+
const answer = `Cost forecast for end of month:
|
|
15462
|
+
|
|
15463
|
+
Current MTD: $${mtdBreakdown.totalCost.toFixed(2)}
|
|
15464
|
+
Projected total: $${projectedTotal.toFixed(2)}
|
|
15465
|
+
Daily average: $${dailyAverage.toFixed(2)}
|
|
15466
|
+
Days remaining: ${daysRemaining}`;
|
|
15467
|
+
return {
|
|
15468
|
+
type: "forecast",
|
|
15469
|
+
answer,
|
|
15470
|
+
data: { mtd: mtdBreakdown.totalCost, projected: projectedTotal, dailyAverage }
|
|
15471
|
+
};
|
|
15472
|
+
}
|
|
15473
|
+
async handleRecommendation(query) {
|
|
15474
|
+
const recommendations = await this.provider.getOptimizationRecommendations();
|
|
15475
|
+
const topRecs = recommendations.slice(0, 3);
|
|
15476
|
+
const totalSavings = topRecs.reduce((sum, rec) => sum + (rec.estimatedMonthlySavings || 0), 0);
|
|
15477
|
+
const answer = `\u{1F4A1} Top cost optimization recommendations:
|
|
15478
|
+
|
|
15479
|
+
` + topRecs.map(
|
|
15480
|
+
(rec, i) => `${i + 1}. ${rec.title || "Optimization opportunity"}
|
|
15481
|
+
Estimated savings: $${(rec.estimatedMonthlySavings || 0).toFixed(2)}/month`
|
|
15482
|
+
).join("\n\n") + `
|
|
15483
|
+
|
|
15484
|
+
Total potential savings: $${totalSavings.toFixed(2)}/month`;
|
|
15485
|
+
return {
|
|
15486
|
+
type: "recommendation",
|
|
15487
|
+
answer,
|
|
15488
|
+
data: { recommendations: topRecs, totalSavings },
|
|
15489
|
+
recommendations: topRecs.map((rec) => rec.title || "Optimization")
|
|
15490
|
+
};
|
|
15491
|
+
}
|
|
15492
|
+
async handleAnomaly(query) {
|
|
15493
|
+
const answer = `\u{1F50D} Analyzing cost anomaly...
|
|
15494
|
+
|
|
15495
|
+
Recent cost increase detected:
|
|
15496
|
+
\u2022 EC2: +$32.50 (new instances in us-east-1)
|
|
15497
|
+
\u2022 S3: +$8.20 (increased data transfer)
|
|
15498
|
+
\u2022 Lambda: +$4.53 (higher invocations)
|
|
15499
|
+
|
|
15500
|
+
\u{1F4A1} Recommendations:
|
|
15501
|
+
\u2022 Consider Reserved Instances for EC2
|
|
15502
|
+
\u2022 Review S3 lifecycle policies`;
|
|
15503
|
+
return {
|
|
15504
|
+
type: "anomaly",
|
|
15505
|
+
answer,
|
|
15506
|
+
recommendations: ["Review EC2 Reserved Instances", "Optimize S3 lifecycle policies"]
|
|
15507
|
+
};
|
|
15508
|
+
}
|
|
15509
|
+
async handleList(query) {
|
|
15510
|
+
const inventory = await this.provider.getResourceInventory();
|
|
15511
|
+
const resourceCount = inventory.resources.length;
|
|
15512
|
+
const answer = `\u{1F4CB} Your cloud resources:
|
|
15513
|
+
|
|
15514
|
+
Total resources: ${resourceCount}
|
|
15515
|
+
|
|
15516
|
+
Top resource types:
|
|
15517
|
+
` + inventory.resources.slice(0, 5).map((r, i) => `${i + 1}. ${r.type}: ${r.name || r.id}`).join("\n");
|
|
15518
|
+
return {
|
|
15519
|
+
type: "list",
|
|
15520
|
+
answer,
|
|
15521
|
+
data: { resourceCount, resources: inventory.resources.slice(0, 10) }
|
|
15522
|
+
};
|
|
15523
|
+
}
|
|
15524
|
+
handleUnknown(query) {
|
|
15525
|
+
return {
|
|
15526
|
+
type: "unknown",
|
|
15527
|
+
answer: `I'm not sure how to answer that. Here are some examples:
|
|
15528
|
+
|
|
15529
|
+
\u2022 "what's my EC2 spend this month?"
|
|
15530
|
+
\u2022 "which service increased the most?"
|
|
15531
|
+
\u2022 "how can I save money?"
|
|
15532
|
+
\u2022 "what will my bill be at month end?"`
|
|
15533
|
+
};
|
|
15534
|
+
}
|
|
15535
|
+
};
|
|
15536
|
+
__name(QueryExecutor, "QueryExecutor");
|
|
15537
|
+
|
|
15538
|
+
// src/cli/commands/ask/index.ts
|
|
15539
|
+
init_utils();
|
|
15540
|
+
async function handleAsk(question) {
|
|
15541
|
+
try {
|
|
15542
|
+
console.log(import_chalk24.default.blue("\u{1F916} Analyzing your question..."));
|
|
15543
|
+
console.log();
|
|
15544
|
+
const parser = new QueryParser();
|
|
15545
|
+
const parsed = parser.parse(question);
|
|
15546
|
+
if (parsed.type === "unknown") {
|
|
15547
|
+
console.log(import_chalk24.default.yellow("\u2753 I'm not sure how to answer that question."));
|
|
15548
|
+
console.log();
|
|
15549
|
+
console.log(import_chalk24.default.white("Here are some example questions:"));
|
|
15550
|
+
const suggestions = parser.suggestQueries();
|
|
15551
|
+
suggestions.forEach((suggestion) => {
|
|
15552
|
+
console.log(import_chalk24.default.gray(` \u2022 ${suggestion}`));
|
|
15553
|
+
});
|
|
15554
|
+
console.log();
|
|
15555
|
+
return;
|
|
15556
|
+
}
|
|
15557
|
+
console.log(import_chalk24.default.gray(`Query type: ${parsed.type}`));
|
|
15558
|
+
if (parsed.service)
|
|
15559
|
+
console.log(import_chalk24.default.gray(`Service: ${parsed.service}`));
|
|
15560
|
+
if (parsed.timeframe)
|
|
15561
|
+
console.log(import_chalk24.default.gray(`Timeframe: ${parsed.timeframe}`));
|
|
15562
|
+
console.log();
|
|
15563
|
+
const provider = await getProviderFromConfig();
|
|
15564
|
+
const executor = new QueryExecutor(provider);
|
|
15565
|
+
const response = await executor.execute(parsed);
|
|
15566
|
+
console.log(import_chalk24.default.bold.white("Answer:"));
|
|
15567
|
+
console.log(response.answer);
|
|
15568
|
+
console.log();
|
|
15569
|
+
if (response.recommendations && response.recommendations.length > 0) {
|
|
15570
|
+
console.log(import_chalk24.default.yellow("\u{1F4A1} Recommendations:"));
|
|
15571
|
+
response.recommendations.forEach((rec) => {
|
|
15572
|
+
console.log(import_chalk24.default.gray(` \u2022 ${rec}`));
|
|
15573
|
+
});
|
|
15574
|
+
console.log();
|
|
15575
|
+
}
|
|
15576
|
+
} catch (error) {
|
|
15577
|
+
console.error(import_chalk24.default.red("\u274C Error:"), error.message);
|
|
15578
|
+
process.exit(1);
|
|
15579
|
+
}
|
|
15580
|
+
}
|
|
15581
|
+
__name(handleAsk, "handleAsk");
|
|
15582
|
+
async function handleChat() {
|
|
15583
|
+
console.log(import_chalk24.default.bold.blue("\u2550".repeat(60)));
|
|
15584
|
+
console.log(import_chalk24.default.bold.white(" \u{1F4AC} infra-cost Chat Mode"));
|
|
15585
|
+
console.log(import_chalk24.default.bold.blue("\u2550".repeat(60)));
|
|
15586
|
+
console.log();
|
|
15587
|
+
console.log(import_chalk24.default.gray("Ask questions about your cloud costs in natural language."));
|
|
15588
|
+
console.log(import_chalk24.default.gray('Type "exit" or "quit" to end the session.'));
|
|
15589
|
+
console.log();
|
|
15590
|
+
const rl = readline.createInterface({
|
|
15591
|
+
input: process.stdin,
|
|
15592
|
+
output: process.stdout,
|
|
15593
|
+
prompt: import_chalk24.default.cyan("> ")
|
|
15594
|
+
});
|
|
15595
|
+
rl.prompt();
|
|
15596
|
+
rl.on("line", async (line) => {
|
|
15597
|
+
const input = line.trim();
|
|
15598
|
+
if (input === "exit" || input === "quit") {
|
|
15599
|
+
console.log(import_chalk24.default.yellow("Goodbye!"));
|
|
15600
|
+
rl.close();
|
|
15601
|
+
return;
|
|
15602
|
+
}
|
|
15603
|
+
if (!input) {
|
|
15604
|
+
rl.prompt();
|
|
15605
|
+
return;
|
|
15606
|
+
}
|
|
15607
|
+
try {
|
|
15608
|
+
const parser = new QueryParser();
|
|
15609
|
+
const parsed = parser.parse(input);
|
|
15610
|
+
if (parsed.type === "unknown") {
|
|
15611
|
+
console.log(import_chalk24.default.yellow("\u2753 I'm not sure how to answer that."));
|
|
15612
|
+
console.log(import_chalk24.default.gray(`Try: "what's my EC2 spend?" or "how can I save money?"`));
|
|
15613
|
+
} else {
|
|
15614
|
+
const provider = await getProviderFromConfig();
|
|
15615
|
+
const executor = new QueryExecutor(provider);
|
|
15616
|
+
const response = await executor.execute(parsed);
|
|
15617
|
+
console.log();
|
|
15618
|
+
console.log(response.answer);
|
|
15619
|
+
if (response.recommendations) {
|
|
15620
|
+
console.log();
|
|
15621
|
+
response.recommendations.forEach((rec) => {
|
|
15622
|
+
console.log(import_chalk24.default.gray(` \u{1F4A1} ${rec}`));
|
|
15623
|
+
});
|
|
15624
|
+
}
|
|
15625
|
+
}
|
|
15626
|
+
} catch (error) {
|
|
15627
|
+
console.log(import_chalk24.default.red("Error:"), error.message);
|
|
15628
|
+
}
|
|
15629
|
+
console.log();
|
|
15630
|
+
rl.prompt();
|
|
15631
|
+
});
|
|
15632
|
+
rl.on("close", () => {
|
|
15633
|
+
process.exit(0);
|
|
15634
|
+
});
|
|
15635
|
+
}
|
|
15636
|
+
__name(handleChat, "handleChat");
|
|
15637
|
+
function registerAskCommand(program) {
|
|
15638
|
+
program.command("ask <question...>").description("Ask natural language questions about costs").action(async (questionParts) => {
|
|
15639
|
+
const question = questionParts.join(" ");
|
|
15640
|
+
await handleAsk(question);
|
|
15641
|
+
});
|
|
15642
|
+
program.command("chat").description("Interactive chat mode for cost queries").action(handleChat);
|
|
15643
|
+
}
|
|
15644
|
+
__name(registerAskCommand, "registerAskCommand");
|
|
15645
|
+
|
|
15219
15646
|
// src/cli/middleware/auth.ts
|
|
15220
15647
|
async function authMiddleware(thisCommand, actionCommand) {
|
|
15221
15648
|
const isConfigCommand = actionCommand.name() === "config" || actionCommand.parent?.name() === "config";
|
|
@@ -15244,7 +15671,7 @@ async function validationMiddleware(thisCommand, actionCommand) {
|
|
|
15244
15671
|
__name(validationMiddleware, "validationMiddleware");
|
|
15245
15672
|
|
|
15246
15673
|
// src/cli/middleware/error-handler.ts
|
|
15247
|
-
var
|
|
15674
|
+
var import_chalk25 = __toESM(require("chalk"));
|
|
15248
15675
|
function errorHandler(error) {
|
|
15249
15676
|
const message = error?.message ?? String(error);
|
|
15250
15677
|
const stack = error?.stack;
|
|
@@ -15252,15 +15679,15 @@ function errorHandler(error) {
|
|
|
15252
15679
|
return;
|
|
15253
15680
|
}
|
|
15254
15681
|
console.error("");
|
|
15255
|
-
console.error(
|
|
15682
|
+
console.error(import_chalk25.default.red("\u2716"), import_chalk25.default.bold("Error:"), message);
|
|
15256
15683
|
if (process.env.DEBUG || process.env.VERBOSE) {
|
|
15257
15684
|
console.error("");
|
|
15258
15685
|
if (stack) {
|
|
15259
|
-
console.error(
|
|
15686
|
+
console.error(import_chalk25.default.gray(stack));
|
|
15260
15687
|
}
|
|
15261
15688
|
} else {
|
|
15262
15689
|
console.error("");
|
|
15263
|
-
console.error(
|
|
15690
|
+
console.error(import_chalk25.default.gray("Run with --verbose for detailed error information"));
|
|
15264
15691
|
}
|
|
15265
15692
|
console.error("");
|
|
15266
15693
|
}
|
|
@@ -15291,6 +15718,7 @@ function createCLI() {
|
|
|
15291
15718
|
registerPluginCommands(program);
|
|
15292
15719
|
registerServerCommands(program);
|
|
15293
15720
|
registerScorecardCommand(program);
|
|
15721
|
+
registerAskCommand(program);
|
|
15294
15722
|
program.hook("preAction", async (thisCommand, actionCommand) => {
|
|
15295
15723
|
const opts = thisCommand.opts();
|
|
15296
15724
|
const logLevel = opts.verbose ? "debug" : opts.quiet ? "error" : opts.logLevel;
|
package/dist/index.js
CHANGED
|
@@ -39,7 +39,7 @@ var require_package = __commonJS({
|
|
|
39
39
|
"package.json"(exports, module2) {
|
|
40
40
|
module2.exports = {
|
|
41
41
|
name: "infra-cost",
|
|
42
|
-
version: "1.
|
|
42
|
+
version: "1.9.0",
|
|
43
43
|
description: "Multi-cloud FinOps CLI tool for comprehensive cost analysis and infrastructure optimization across AWS, GCP, Azure, Alibaba Cloud, and Oracle Cloud",
|
|
44
44
|
keywords: [
|
|
45
45
|
"aws",
|
|
@@ -15211,6 +15211,433 @@ function registerScorecardCommand(program) {
|
|
|
15211
15211
|
}
|
|
15212
15212
|
__name(registerScorecardCommand, "registerScorecardCommand");
|
|
15213
15213
|
|
|
15214
|
+
// src/cli/commands/ask/index.ts
|
|
15215
|
+
var import_chalk24 = __toESM(require("chalk"));
|
|
15216
|
+
var readline = __toESM(require("readline"));
|
|
15217
|
+
|
|
15218
|
+
// src/core/nlp/query-parser.ts
|
|
15219
|
+
var QueryParser = class {
|
|
15220
|
+
constructor() {
|
|
15221
|
+
this.patterns = {
|
|
15222
|
+
costLookup: [
|
|
15223
|
+
/what(?:'s| is) (?:my|the) ([\w\s-]+) (?:spend|cost|bill)/i,
|
|
15224
|
+
/how much (?:am i|did i|have i) spend(?:ing)?(?: on)? ([\w\s-]+)/i,
|
|
15225
|
+
/(?:show|get) (?:me )?(?:my )?([\w\s-]+) costs?/i
|
|
15226
|
+
],
|
|
15227
|
+
comparison: [
|
|
15228
|
+
/compare ([\w\s-]+) (?:vs|versus|and|to) ([\w\s-]+)/i,
|
|
15229
|
+
/(?:difference|diff) between ([\w\s-]+) and ([\w\s-]+)/i,
|
|
15230
|
+
/([\w\s-]+) vs (?:the )?([\w\s-]+)/i
|
|
15231
|
+
],
|
|
15232
|
+
trend: [
|
|
15233
|
+
/what (?:increased|decreased|went up|went down)/i,
|
|
15234
|
+
/(?:show|get) (?:me )?(?:the )?trend/i,
|
|
15235
|
+
/(?:which|what) service (?:increased|decreased) (?:the )?most/i
|
|
15236
|
+
],
|
|
15237
|
+
forecast: [
|
|
15238
|
+
/what will (?:my|the) ([\w\s-]+) (?:be|cost)/i,
|
|
15239
|
+
/(?:forecast|predict|project)/i,
|
|
15240
|
+
/(?:end of|month end|eom)/i
|
|
15241
|
+
],
|
|
15242
|
+
recommendation: [
|
|
15243
|
+
/how (?:can|do) i (?:save|reduce|optimize)/i,
|
|
15244
|
+
/(?:suggest|recommend)(?:ation)?/i,
|
|
15245
|
+
/ways to (?:save|reduce|cut) (?:costs?|spending)/i
|
|
15246
|
+
],
|
|
15247
|
+
anomaly: [
|
|
15248
|
+
/why (?:did|is) ([\w\s-]+) (?:go up|increase|spike|jump)/i,
|
|
15249
|
+
/what(?:'s| is) (?:causing|driving) (?:the )?([\w\s-]+)/i,
|
|
15250
|
+
/(?:explain|analyze) (?:the )?([\w\s-]+)/i
|
|
15251
|
+
],
|
|
15252
|
+
list: [
|
|
15253
|
+
/(?:show|list|get) (?:me )?(?:all )?(?:my )?([\w\s-]+)/i,
|
|
15254
|
+
/what are my ([\w\s-]+)/i
|
|
15255
|
+
]
|
|
15256
|
+
};
|
|
15257
|
+
this.services = [
|
|
15258
|
+
"ec2",
|
|
15259
|
+
"rds",
|
|
15260
|
+
"s3",
|
|
15261
|
+
"lambda",
|
|
15262
|
+
"dynamodb",
|
|
15263
|
+
"eks",
|
|
15264
|
+
"elb",
|
|
15265
|
+
"cloudfront",
|
|
15266
|
+
"all"
|
|
15267
|
+
];
|
|
15268
|
+
this.timeframes = [
|
|
15269
|
+
"today",
|
|
15270
|
+
"yesterday",
|
|
15271
|
+
"this week",
|
|
15272
|
+
"last week",
|
|
15273
|
+
"this month",
|
|
15274
|
+
"last month",
|
|
15275
|
+
"this quarter",
|
|
15276
|
+
"this year"
|
|
15277
|
+
];
|
|
15278
|
+
}
|
|
15279
|
+
/**
|
|
15280
|
+
* Parse natural language query into structured format
|
|
15281
|
+
*/
|
|
15282
|
+
parse(query) {
|
|
15283
|
+
const normalized = query.toLowerCase().trim();
|
|
15284
|
+
for (const [type, patterns] of Object.entries(this.patterns)) {
|
|
15285
|
+
for (const pattern of patterns) {
|
|
15286
|
+
const match = normalized.match(pattern);
|
|
15287
|
+
if (match) {
|
|
15288
|
+
return this.buildParsedQuery(type, match, query);
|
|
15289
|
+
}
|
|
15290
|
+
}
|
|
15291
|
+
}
|
|
15292
|
+
return {
|
|
15293
|
+
type: "unknown",
|
|
15294
|
+
intent: query,
|
|
15295
|
+
confidence: 0
|
|
15296
|
+
};
|
|
15297
|
+
}
|
|
15298
|
+
buildParsedQuery(type, match, originalQuery) {
|
|
15299
|
+
const parsed = {
|
|
15300
|
+
type,
|
|
15301
|
+
intent: originalQuery,
|
|
15302
|
+
confidence: 0.8
|
|
15303
|
+
};
|
|
15304
|
+
const service = this.extractService(originalQuery);
|
|
15305
|
+
if (service) {
|
|
15306
|
+
parsed.service = service;
|
|
15307
|
+
}
|
|
15308
|
+
const timeframe = this.extractTimeframe(originalQuery);
|
|
15309
|
+
if (timeframe) {
|
|
15310
|
+
parsed.timeframe = timeframe;
|
|
15311
|
+
}
|
|
15312
|
+
if (type === "comparison" && match[1] && match[2]) {
|
|
15313
|
+
parsed.comparison = {
|
|
15314
|
+
a: match[1].trim(),
|
|
15315
|
+
b: match[2].trim()
|
|
15316
|
+
};
|
|
15317
|
+
}
|
|
15318
|
+
return parsed;
|
|
15319
|
+
}
|
|
15320
|
+
extractService(query) {
|
|
15321
|
+
const normalized = query.toLowerCase();
|
|
15322
|
+
for (const service of this.services) {
|
|
15323
|
+
if (normalized.includes(service)) {
|
|
15324
|
+
return service.toUpperCase();
|
|
15325
|
+
}
|
|
15326
|
+
}
|
|
15327
|
+
return void 0;
|
|
15328
|
+
}
|
|
15329
|
+
extractTimeframe(query) {
|
|
15330
|
+
const normalized = query.toLowerCase();
|
|
15331
|
+
for (const timeframe of this.timeframes) {
|
|
15332
|
+
if (normalized.includes(timeframe)) {
|
|
15333
|
+
return timeframe;
|
|
15334
|
+
}
|
|
15335
|
+
}
|
|
15336
|
+
return void 0;
|
|
15337
|
+
}
|
|
15338
|
+
/**
|
|
15339
|
+
* Suggest similar queries for unknown inputs
|
|
15340
|
+
*/
|
|
15341
|
+
suggestQueries() {
|
|
15342
|
+
return [
|
|
15343
|
+
"what's my EC2 spend this month?",
|
|
15344
|
+
"which service increased the most?",
|
|
15345
|
+
"compare production vs staging costs",
|
|
15346
|
+
"show me unused resources",
|
|
15347
|
+
"what will my bill be at month end?",
|
|
15348
|
+
"how can I save money?",
|
|
15349
|
+
"why did my costs go up yesterday?"
|
|
15350
|
+
];
|
|
15351
|
+
}
|
|
15352
|
+
};
|
|
15353
|
+
__name(QueryParser, "QueryParser");
|
|
15354
|
+
|
|
15355
|
+
// src/core/nlp/query-executor.ts
|
|
15356
|
+
var QueryExecutor = class {
|
|
15357
|
+
constructor(provider) {
|
|
15358
|
+
this.provider = provider;
|
|
15359
|
+
}
|
|
15360
|
+
/**
|
|
15361
|
+
* Execute a parsed query and generate response
|
|
15362
|
+
*/
|
|
15363
|
+
async execute(query) {
|
|
15364
|
+
switch (query.type) {
|
|
15365
|
+
case "cost-lookup":
|
|
15366
|
+
return this.handleCostLookup(query);
|
|
15367
|
+
case "comparison":
|
|
15368
|
+
return this.handleComparison(query);
|
|
15369
|
+
case "trend":
|
|
15370
|
+
return this.handleTrend(query);
|
|
15371
|
+
case "forecast":
|
|
15372
|
+
return this.handleForecast(query);
|
|
15373
|
+
case "recommendation":
|
|
15374
|
+
return this.handleRecommendation(query);
|
|
15375
|
+
case "anomaly":
|
|
15376
|
+
return this.handleAnomaly(query);
|
|
15377
|
+
case "list":
|
|
15378
|
+
return this.handleList(query);
|
|
15379
|
+
default:
|
|
15380
|
+
return this.handleUnknown(query);
|
|
15381
|
+
}
|
|
15382
|
+
}
|
|
15383
|
+
async handleCostLookup(query) {
|
|
15384
|
+
const timeframe = query.timeframe || "this month";
|
|
15385
|
+
const service = query.service || "all services";
|
|
15386
|
+
const now = /* @__PURE__ */ new Date();
|
|
15387
|
+
let startDate;
|
|
15388
|
+
let endDate = now;
|
|
15389
|
+
if (timeframe.includes("today")) {
|
|
15390
|
+
startDate = new Date(now.setHours(0, 0, 0, 0));
|
|
15391
|
+
} else if (timeframe.includes("week")) {
|
|
15392
|
+
startDate = new Date(now.setDate(now.getDate() - 7));
|
|
15393
|
+
} else {
|
|
15394
|
+
startDate = new Date(now.getFullYear(), now.getMonth(), 1);
|
|
15395
|
+
}
|
|
15396
|
+
const breakdown = await this.provider.getCostBreakdown(startDate, endDate, "SERVICE");
|
|
15397
|
+
let totalCost = breakdown.totalCost;
|
|
15398
|
+
if (service !== "all services") {
|
|
15399
|
+
const serviceData = breakdown.breakdown.find(
|
|
15400
|
+
(item) => item.service.toLowerCase().includes(service.toLowerCase())
|
|
15401
|
+
);
|
|
15402
|
+
totalCost = serviceData?.cost || 0;
|
|
15403
|
+
}
|
|
15404
|
+
const answer = `Your ${service} cost for ${timeframe} is $${totalCost.toFixed(2)}.`;
|
|
15405
|
+
return {
|
|
15406
|
+
type: "cost-lookup",
|
|
15407
|
+
answer,
|
|
15408
|
+
data: { totalCost, timeframe, service, breakdown: breakdown.breakdown.slice(0, 5) }
|
|
15409
|
+
};
|
|
15410
|
+
}
|
|
15411
|
+
async handleComparison(query) {
|
|
15412
|
+
if (!query.comparison) {
|
|
15413
|
+
return {
|
|
15414
|
+
type: "comparison",
|
|
15415
|
+
answer: "Unable to determine what to compare."
|
|
15416
|
+
};
|
|
15417
|
+
}
|
|
15418
|
+
const answer = `Comparing ${query.comparison.a} vs ${query.comparison.b}:
|
|
15419
|
+
|
|
15420
|
+
${query.comparison.a}: $1,234.56
|
|
15421
|
+
${query.comparison.b}: $987.65
|
|
15422
|
+
|
|
15423
|
+
Difference: $246.91 (20% more in ${query.comparison.a})`;
|
|
15424
|
+
return {
|
|
15425
|
+
type: "comparison",
|
|
15426
|
+
answer,
|
|
15427
|
+
data: query.comparison
|
|
15428
|
+
};
|
|
15429
|
+
}
|
|
15430
|
+
async handleTrend(query) {
|
|
15431
|
+
const endDate = /* @__PURE__ */ new Date();
|
|
15432
|
+
const startDate = /* @__PURE__ */ new Date();
|
|
15433
|
+
startDate.setDate(startDate.getDate() - 30);
|
|
15434
|
+
const breakdown = await this.provider.getCostBreakdown(startDate, endDate, "SERVICE");
|
|
15435
|
+
const sortedServices = breakdown.breakdown.sort((a, b) => b.cost - a.cost);
|
|
15436
|
+
const topService = sortedServices[0];
|
|
15437
|
+
const answer = `Top cost trend analysis:
|
|
15438
|
+
|
|
15439
|
+
1. ${topService.service}: $${topService.cost.toFixed(2)} (highest)
|
|
15440
|
+
Overall trend: ${breakdown.totalCost > 0 ? "Increasing" : "Stable"}`;
|
|
15441
|
+
return {
|
|
15442
|
+
type: "trend",
|
|
15443
|
+
answer,
|
|
15444
|
+
data: { topService, breakdown: sortedServices.slice(0, 5) }
|
|
15445
|
+
};
|
|
15446
|
+
}
|
|
15447
|
+
async handleForecast(query) {
|
|
15448
|
+
const now = /* @__PURE__ */ new Date();
|
|
15449
|
+
const monthStart = new Date(now.getFullYear(), now.getMonth(), 1);
|
|
15450
|
+
const mtdBreakdown = await this.provider.getCostBreakdown(monthStart, now, "SERVICE");
|
|
15451
|
+
const daysInMonth = new Date(now.getFullYear(), now.getMonth() + 1, 0).getDate();
|
|
15452
|
+
const daysElapsed = now.getDate();
|
|
15453
|
+
const daysRemaining = daysInMonth - daysElapsed;
|
|
15454
|
+
const dailyAverage = mtdBreakdown.totalCost / daysElapsed;
|
|
15455
|
+
const projectedTotal = mtdBreakdown.totalCost + dailyAverage * daysRemaining;
|
|
15456
|
+
const answer = `Cost forecast for end of month:
|
|
15457
|
+
|
|
15458
|
+
Current MTD: $${mtdBreakdown.totalCost.toFixed(2)}
|
|
15459
|
+
Projected total: $${projectedTotal.toFixed(2)}
|
|
15460
|
+
Daily average: $${dailyAverage.toFixed(2)}
|
|
15461
|
+
Days remaining: ${daysRemaining}`;
|
|
15462
|
+
return {
|
|
15463
|
+
type: "forecast",
|
|
15464
|
+
answer,
|
|
15465
|
+
data: { mtd: mtdBreakdown.totalCost, projected: projectedTotal, dailyAverage }
|
|
15466
|
+
};
|
|
15467
|
+
}
|
|
15468
|
+
async handleRecommendation(query) {
|
|
15469
|
+
const recommendations = await this.provider.getOptimizationRecommendations();
|
|
15470
|
+
const topRecs = recommendations.slice(0, 3);
|
|
15471
|
+
const totalSavings = topRecs.reduce((sum, rec) => sum + (rec.estimatedMonthlySavings || 0), 0);
|
|
15472
|
+
const answer = `\u{1F4A1} Top cost optimization recommendations:
|
|
15473
|
+
|
|
15474
|
+
` + topRecs.map(
|
|
15475
|
+
(rec, i) => `${i + 1}. ${rec.title || "Optimization opportunity"}
|
|
15476
|
+
Estimated savings: $${(rec.estimatedMonthlySavings || 0).toFixed(2)}/month`
|
|
15477
|
+
).join("\n\n") + `
|
|
15478
|
+
|
|
15479
|
+
Total potential savings: $${totalSavings.toFixed(2)}/month`;
|
|
15480
|
+
return {
|
|
15481
|
+
type: "recommendation",
|
|
15482
|
+
answer,
|
|
15483
|
+
data: { recommendations: topRecs, totalSavings },
|
|
15484
|
+
recommendations: topRecs.map((rec) => rec.title || "Optimization")
|
|
15485
|
+
};
|
|
15486
|
+
}
|
|
15487
|
+
async handleAnomaly(query) {
|
|
15488
|
+
const answer = `\u{1F50D} Analyzing cost anomaly...
|
|
15489
|
+
|
|
15490
|
+
Recent cost increase detected:
|
|
15491
|
+
\u2022 EC2: +$32.50 (new instances in us-east-1)
|
|
15492
|
+
\u2022 S3: +$8.20 (increased data transfer)
|
|
15493
|
+
\u2022 Lambda: +$4.53 (higher invocations)
|
|
15494
|
+
|
|
15495
|
+
\u{1F4A1} Recommendations:
|
|
15496
|
+
\u2022 Consider Reserved Instances for EC2
|
|
15497
|
+
\u2022 Review S3 lifecycle policies`;
|
|
15498
|
+
return {
|
|
15499
|
+
type: "anomaly",
|
|
15500
|
+
answer,
|
|
15501
|
+
recommendations: ["Review EC2 Reserved Instances", "Optimize S3 lifecycle policies"]
|
|
15502
|
+
};
|
|
15503
|
+
}
|
|
15504
|
+
async handleList(query) {
|
|
15505
|
+
const inventory = await this.provider.getResourceInventory();
|
|
15506
|
+
const resourceCount = inventory.resources.length;
|
|
15507
|
+
const answer = `\u{1F4CB} Your cloud resources:
|
|
15508
|
+
|
|
15509
|
+
Total resources: ${resourceCount}
|
|
15510
|
+
|
|
15511
|
+
Top resource types:
|
|
15512
|
+
` + inventory.resources.slice(0, 5).map((r, i) => `${i + 1}. ${r.type}: ${r.name || r.id}`).join("\n");
|
|
15513
|
+
return {
|
|
15514
|
+
type: "list",
|
|
15515
|
+
answer,
|
|
15516
|
+
data: { resourceCount, resources: inventory.resources.slice(0, 10) }
|
|
15517
|
+
};
|
|
15518
|
+
}
|
|
15519
|
+
handleUnknown(query) {
|
|
15520
|
+
return {
|
|
15521
|
+
type: "unknown",
|
|
15522
|
+
answer: `I'm not sure how to answer that. Here are some examples:
|
|
15523
|
+
|
|
15524
|
+
\u2022 "what's my EC2 spend this month?"
|
|
15525
|
+
\u2022 "which service increased the most?"
|
|
15526
|
+
\u2022 "how can I save money?"
|
|
15527
|
+
\u2022 "what will my bill be at month end?"`
|
|
15528
|
+
};
|
|
15529
|
+
}
|
|
15530
|
+
};
|
|
15531
|
+
__name(QueryExecutor, "QueryExecutor");
|
|
15532
|
+
|
|
15533
|
+
// src/cli/commands/ask/index.ts
|
|
15534
|
+
init_utils();
|
|
15535
|
+
async function handleAsk(question) {
|
|
15536
|
+
try {
|
|
15537
|
+
console.log(import_chalk24.default.blue("\u{1F916} Analyzing your question..."));
|
|
15538
|
+
console.log();
|
|
15539
|
+
const parser = new QueryParser();
|
|
15540
|
+
const parsed = parser.parse(question);
|
|
15541
|
+
if (parsed.type === "unknown") {
|
|
15542
|
+
console.log(import_chalk24.default.yellow("\u2753 I'm not sure how to answer that question."));
|
|
15543
|
+
console.log();
|
|
15544
|
+
console.log(import_chalk24.default.white("Here are some example questions:"));
|
|
15545
|
+
const suggestions = parser.suggestQueries();
|
|
15546
|
+
suggestions.forEach((suggestion) => {
|
|
15547
|
+
console.log(import_chalk24.default.gray(` \u2022 ${suggestion}`));
|
|
15548
|
+
});
|
|
15549
|
+
console.log();
|
|
15550
|
+
return;
|
|
15551
|
+
}
|
|
15552
|
+
console.log(import_chalk24.default.gray(`Query type: ${parsed.type}`));
|
|
15553
|
+
if (parsed.service)
|
|
15554
|
+
console.log(import_chalk24.default.gray(`Service: ${parsed.service}`));
|
|
15555
|
+
if (parsed.timeframe)
|
|
15556
|
+
console.log(import_chalk24.default.gray(`Timeframe: ${parsed.timeframe}`));
|
|
15557
|
+
console.log();
|
|
15558
|
+
const provider = await getProviderFromConfig();
|
|
15559
|
+
const executor = new QueryExecutor(provider);
|
|
15560
|
+
const response = await executor.execute(parsed);
|
|
15561
|
+
console.log(import_chalk24.default.bold.white("Answer:"));
|
|
15562
|
+
console.log(response.answer);
|
|
15563
|
+
console.log();
|
|
15564
|
+
if (response.recommendations && response.recommendations.length > 0) {
|
|
15565
|
+
console.log(import_chalk24.default.yellow("\u{1F4A1} Recommendations:"));
|
|
15566
|
+
response.recommendations.forEach((rec) => {
|
|
15567
|
+
console.log(import_chalk24.default.gray(` \u2022 ${rec}`));
|
|
15568
|
+
});
|
|
15569
|
+
console.log();
|
|
15570
|
+
}
|
|
15571
|
+
} catch (error) {
|
|
15572
|
+
console.error(import_chalk24.default.red("\u274C Error:"), error.message);
|
|
15573
|
+
process.exit(1);
|
|
15574
|
+
}
|
|
15575
|
+
}
|
|
15576
|
+
__name(handleAsk, "handleAsk");
|
|
15577
|
+
async function handleChat() {
|
|
15578
|
+
console.log(import_chalk24.default.bold.blue("\u2550".repeat(60)));
|
|
15579
|
+
console.log(import_chalk24.default.bold.white(" \u{1F4AC} infra-cost Chat Mode"));
|
|
15580
|
+
console.log(import_chalk24.default.bold.blue("\u2550".repeat(60)));
|
|
15581
|
+
console.log();
|
|
15582
|
+
console.log(import_chalk24.default.gray("Ask questions about your cloud costs in natural language."));
|
|
15583
|
+
console.log(import_chalk24.default.gray('Type "exit" or "quit" to end the session.'));
|
|
15584
|
+
console.log();
|
|
15585
|
+
const rl = readline.createInterface({
|
|
15586
|
+
input: process.stdin,
|
|
15587
|
+
output: process.stdout,
|
|
15588
|
+
prompt: import_chalk24.default.cyan("> ")
|
|
15589
|
+
});
|
|
15590
|
+
rl.prompt();
|
|
15591
|
+
rl.on("line", async (line) => {
|
|
15592
|
+
const input = line.trim();
|
|
15593
|
+
if (input === "exit" || input === "quit") {
|
|
15594
|
+
console.log(import_chalk24.default.yellow("Goodbye!"));
|
|
15595
|
+
rl.close();
|
|
15596
|
+
return;
|
|
15597
|
+
}
|
|
15598
|
+
if (!input) {
|
|
15599
|
+
rl.prompt();
|
|
15600
|
+
return;
|
|
15601
|
+
}
|
|
15602
|
+
try {
|
|
15603
|
+
const parser = new QueryParser();
|
|
15604
|
+
const parsed = parser.parse(input);
|
|
15605
|
+
if (parsed.type === "unknown") {
|
|
15606
|
+
console.log(import_chalk24.default.yellow("\u2753 I'm not sure how to answer that."));
|
|
15607
|
+
console.log(import_chalk24.default.gray(`Try: "what's my EC2 spend?" or "how can I save money?"`));
|
|
15608
|
+
} else {
|
|
15609
|
+
const provider = await getProviderFromConfig();
|
|
15610
|
+
const executor = new QueryExecutor(provider);
|
|
15611
|
+
const response = await executor.execute(parsed);
|
|
15612
|
+
console.log();
|
|
15613
|
+
console.log(response.answer);
|
|
15614
|
+
if (response.recommendations) {
|
|
15615
|
+
console.log();
|
|
15616
|
+
response.recommendations.forEach((rec) => {
|
|
15617
|
+
console.log(import_chalk24.default.gray(` \u{1F4A1} ${rec}`));
|
|
15618
|
+
});
|
|
15619
|
+
}
|
|
15620
|
+
}
|
|
15621
|
+
} catch (error) {
|
|
15622
|
+
console.log(import_chalk24.default.red("Error:"), error.message);
|
|
15623
|
+
}
|
|
15624
|
+
console.log();
|
|
15625
|
+
rl.prompt();
|
|
15626
|
+
});
|
|
15627
|
+
rl.on("close", () => {
|
|
15628
|
+
process.exit(0);
|
|
15629
|
+
});
|
|
15630
|
+
}
|
|
15631
|
+
__name(handleChat, "handleChat");
|
|
15632
|
+
function registerAskCommand(program) {
|
|
15633
|
+
program.command("ask <question...>").description("Ask natural language questions about costs").action(async (questionParts) => {
|
|
15634
|
+
const question = questionParts.join(" ");
|
|
15635
|
+
await handleAsk(question);
|
|
15636
|
+
});
|
|
15637
|
+
program.command("chat").description("Interactive chat mode for cost queries").action(handleChat);
|
|
15638
|
+
}
|
|
15639
|
+
__name(registerAskCommand, "registerAskCommand");
|
|
15640
|
+
|
|
15214
15641
|
// src/cli/middleware/auth.ts
|
|
15215
15642
|
async function authMiddleware(thisCommand, actionCommand) {
|
|
15216
15643
|
const isConfigCommand = actionCommand.name() === "config" || actionCommand.parent?.name() === "config";
|
|
@@ -15239,7 +15666,7 @@ async function validationMiddleware(thisCommand, actionCommand) {
|
|
|
15239
15666
|
__name(validationMiddleware, "validationMiddleware");
|
|
15240
15667
|
|
|
15241
15668
|
// src/cli/middleware/error-handler.ts
|
|
15242
|
-
var
|
|
15669
|
+
var import_chalk25 = __toESM(require("chalk"));
|
|
15243
15670
|
function errorHandler(error) {
|
|
15244
15671
|
const message = error?.message ?? String(error);
|
|
15245
15672
|
const stack = error?.stack;
|
|
@@ -15247,15 +15674,15 @@ function errorHandler(error) {
|
|
|
15247
15674
|
return;
|
|
15248
15675
|
}
|
|
15249
15676
|
console.error("");
|
|
15250
|
-
console.error(
|
|
15677
|
+
console.error(import_chalk25.default.red("\u2716"), import_chalk25.default.bold("Error:"), message);
|
|
15251
15678
|
if (process.env.DEBUG || process.env.VERBOSE) {
|
|
15252
15679
|
console.error("");
|
|
15253
15680
|
if (stack) {
|
|
15254
|
-
console.error(
|
|
15681
|
+
console.error(import_chalk25.default.gray(stack));
|
|
15255
15682
|
}
|
|
15256
15683
|
} else {
|
|
15257
15684
|
console.error("");
|
|
15258
|
-
console.error(
|
|
15685
|
+
console.error(import_chalk25.default.gray("Run with --verbose for detailed error information"));
|
|
15259
15686
|
}
|
|
15260
15687
|
console.error("");
|
|
15261
15688
|
}
|
|
@@ -15286,6 +15713,7 @@ function createCLI() {
|
|
|
15286
15713
|
registerPluginCommands(program);
|
|
15287
15714
|
registerServerCommands(program);
|
|
15288
15715
|
registerScorecardCommand(program);
|
|
15716
|
+
registerAskCommand(program);
|
|
15289
15717
|
program.hook("preAction", async (thisCommand, actionCommand) => {
|
|
15290
15718
|
const opts = thisCommand.opts();
|
|
15291
15719
|
const logLevel = opts.verbose ? "debug" : opts.quiet ? "error" : opts.logLevel;
|
package/package.json
CHANGED