infra-cost 0.1.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/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2025 codecollab.co
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/bin/index.js ADDED
@@ -0,0 +1,4 @@
1
+ #!/usr/bin/env node
2
+ 'use strict';
3
+
4
+ import '../dist/index.js';
package/dist/index.js ADDED
@@ -0,0 +1,487 @@
1
+ // src/index.ts
2
+ import { Command } from "commander";
3
+
4
+ // package.json
5
+ var package_default = {
6
+ name: "infra-cost",
7
+ version: "0.1.0",
8
+ description: "A CLI tool to perform cost analysis on your infra account's cost",
9
+ type: "module",
10
+ author: {
11
+ name: "Code Collab",
12
+ email: "codecollab.co@gmail.com",
13
+ url: "https://github.com/codecollab-co/infra-cost"
14
+ },
15
+ files: [
16
+ "!tests/**/*",
17
+ "dist/**/*",
18
+ "!dist/**/*.js.map",
19
+ "bin/**/*"
20
+ ],
21
+ bin: {
22
+ "aws-cost": "./bin/index.js"
23
+ },
24
+ scripts: {
25
+ build: "tsup",
26
+ dev: "tsup --watch",
27
+ prebuild: "run-s clean",
28
+ predev: "run-s clean",
29
+ clean: "rm -rf dist",
30
+ typecheck: "tsc --noEmit",
31
+ test: 'echo "Error: no test specified" && exit 1'
32
+ },
33
+ keywords: [
34
+ "aws",
35
+ "cost",
36
+ "cli",
37
+ "aws-cost",
38
+ "aws-cost-cli",
39
+ "aws-costs",
40
+ "typescript",
41
+ "aws cli"
42
+ ],
43
+ license: "MIT",
44
+ repository: {
45
+ type: "git",
46
+ url: "https://github.com/codecollab-co/infra-cost.git"
47
+ },
48
+ engines: {
49
+ node: ">=12.0"
50
+ },
51
+ bugs: {
52
+ url: "https://github.com/codecollab-co/infra-cost/issues"
53
+ },
54
+ homepage: "https://github.com/codecollab-co/infra-cost.git#readme",
55
+ dependencies: {
56
+ "@aws-sdk/shared-ini-file-loader": "^3.254.0",
57
+ "aws-sdk": "^2.1299.0",
58
+ chalk: "^5.2.0",
59
+ commander: "^10.0.0",
60
+ dayjs: "^1.11.7",
61
+ dotenv: "^16.0.3",
62
+ "node-fetch": "^3.3.0",
63
+ ora: "^6.1.2"
64
+ },
65
+ devDependencies: {
66
+ "@types/node": "^18.11.18",
67
+ "npm-run-all": "^4.1.5",
68
+ "ts-node": "^10.9.1",
69
+ tsup: "^8.4.0",
70
+ typescript: "^4.9.4"
71
+ }
72
+ };
73
+
74
+ // src/account.ts
75
+ import AWS from "aws-sdk";
76
+
77
+ // src/logger.ts
78
+ import chalk from "chalk";
79
+ import ora from "ora";
80
+ function printFatalError(error) {
81
+ console.error(`
82
+ ${chalk.bold.redBright.underline(`Error:`)}
83
+ ${chalk.redBright(`${error}`)}
84
+ `);
85
+ process.exit(1);
86
+ }
87
+ var spinner;
88
+ function showSpinner(text) {
89
+ if (!spinner) {
90
+ spinner = ora({ text: "" }).start();
91
+ }
92
+ spinner.text = text;
93
+ }
94
+ function hideSpinner() {
95
+ if (!spinner) {
96
+ return;
97
+ }
98
+ spinner.stop();
99
+ }
100
+
101
+ // src/account.ts
102
+ async function getAccountAlias(awsConfig2) {
103
+ var _a;
104
+ showSpinner("Getting account alias");
105
+ const iam = new AWS.IAM(awsConfig2);
106
+ const accountAliases = await iam.listAccountAliases().promise();
107
+ const foundAlias = (_a = accountAliases == null ? void 0 : accountAliases["AccountAliases"]) == null ? void 0 : _a[0];
108
+ if (foundAlias) {
109
+ return foundAlias;
110
+ }
111
+ const sts = new AWS.STS(awsConfig2);
112
+ const accountInfo = await sts.getCallerIdentity().promise();
113
+ return (accountInfo == null ? void 0 : accountInfo.Account) || "";
114
+ }
115
+
116
+ // src/config.ts
117
+ import { loadSharedConfigFiles } from "@aws-sdk/shared-ini-file-loader";
118
+ import chalk2 from "chalk";
119
+ async function getAwsConfigFromOptionsOrFile(options2) {
120
+ const { profile, accessKey, secretKey, sessionToken, region } = options2;
121
+ if (accessKey || secretKey) {
122
+ if (!accessKey || !secretKey) {
123
+ printFatalError(`
124
+ You need to provide both of the following options:
125
+ ${chalk2.bold("--access-key")}
126
+ ${chalk2.bold("--secret-key")}
127
+ `);
128
+ }
129
+ return {
130
+ credentials: {
131
+ accessKeyId: accessKey,
132
+ secretAccessKey: secretKey,
133
+ sessionToken
134
+ },
135
+ region
136
+ };
137
+ }
138
+ return {
139
+ credentials: await loadAwsCredentials(profile),
140
+ region
141
+ };
142
+ }
143
+ async function loadAwsCredentials(profile = "default") {
144
+ var _a, _b, _c;
145
+ const configFiles = await loadSharedConfigFiles();
146
+ const credentialsFile = configFiles.credentialsFile;
147
+ const accessKey = (_a = credentialsFile == null ? void 0 : credentialsFile[profile]) == null ? void 0 : _a.aws_access_key_id;
148
+ const secretKey = (_b = credentialsFile == null ? void 0 : credentialsFile[profile]) == null ? void 0 : _b.aws_secret_access_key;
149
+ const sessionToken = (_c = credentialsFile == null ? void 0 : credentialsFile[profile]) == null ? void 0 : _c.aws_session_token;
150
+ if (!accessKey || !secretKey) {
151
+ const sharedCredentialsFile = process.env.AWS_SHARED_CREDENTIALS_FILE || "~/.aws/credentials";
152
+ const sharedConfigFile = process.env.AWS_CONFIG_FILE || "~/.aws/config";
153
+ printFatalError(`
154
+ Could not find the AWS credentials in the following files for the profile "${profile}":
155
+ ${chalk2.bold(sharedCredentialsFile)}
156
+ ${chalk2.bold(sharedConfigFile)}
157
+
158
+ If the config files exist at different locations, set the following environment variables:
159
+ ${chalk2.bold(`AWS_SHARED_CREDENTIALS_FILE`)}
160
+ ${chalk2.bold(`AWS_CONFIG_FILE`)}
161
+
162
+ You can also configure the credentials via the following command:
163
+ ${chalk2.bold(`aws configure --profile ${profile}`)}
164
+
165
+ You can also provide the credentials via the following options:
166
+ ${chalk2.bold(`--access-key`)}
167
+ ${chalk2.bold(`--secret-key`)}
168
+ ${chalk2.bold(`--region`)}
169
+ `);
170
+ }
171
+ return {
172
+ accessKeyId: accessKey,
173
+ secretAccessKey: secretKey,
174
+ sessionToken
175
+ };
176
+ }
177
+
178
+ // src/cost.ts
179
+ import AWS2 from "aws-sdk";
180
+ import dayjs from "dayjs";
181
+ async function getRawCostByService(awsConfig2) {
182
+ showSpinner("Getting pricing data");
183
+ const costExplorer = new AWS2.CostExplorer(awsConfig2);
184
+ const endDate = dayjs().subtract(1, "day");
185
+ const startDate = endDate.subtract(65, "day");
186
+ const pricingData = await costExplorer.getCostAndUsage({
187
+ TimePeriod: {
188
+ Start: startDate.format("YYYY-MM-DD"),
189
+ End: endDate.format("YYYY-MM-DD")
190
+ },
191
+ Granularity: "DAILY",
192
+ Filter: {
193
+ Not: {
194
+ Dimensions: {
195
+ Key: "RECORD_TYPE",
196
+ Values: ["Credit", "Refund", "Upfront", "Support"]
197
+ }
198
+ }
199
+ },
200
+ Metrics: ["UnblendedCost"],
201
+ GroupBy: [
202
+ {
203
+ Type: "DIMENSION",
204
+ Key: "SERVICE"
205
+ }
206
+ ]
207
+ }).promise();
208
+ const costByService = {};
209
+ for (const day of pricingData.ResultsByTime) {
210
+ for (const group of day.Groups) {
211
+ const serviceName = group.Keys[0];
212
+ const cost = group.Metrics.UnblendedCost.Amount;
213
+ const costDate = day.TimePeriod.End;
214
+ costByService[serviceName] = costByService[serviceName] || {};
215
+ costByService[serviceName][costDate] = parseFloat(cost);
216
+ }
217
+ }
218
+ return costByService;
219
+ }
220
+ function calculateServiceTotals(rawCostByService) {
221
+ const totals = {
222
+ lastMonth: 0,
223
+ thisMonth: 0,
224
+ last7Days: 0,
225
+ yesterday: 0
226
+ };
227
+ const totalsByService = {
228
+ lastMonth: {},
229
+ thisMonth: {},
230
+ last7Days: {},
231
+ yesterday: {}
232
+ };
233
+ const startOfLastMonth = dayjs().subtract(1, "month").startOf("month");
234
+ const startOfThisMonth = dayjs().startOf("month");
235
+ const startOfLast7Days = dayjs().subtract(7, "day");
236
+ const startOfYesterday = dayjs().subtract(1, "day");
237
+ for (const service of Object.keys(rawCostByService)) {
238
+ const servicePrices = rawCostByService[service];
239
+ let lastMonthServiceTotal = 0;
240
+ let thisMonthServiceTotal = 0;
241
+ let last7DaysServiceTotal = 0;
242
+ let yesterdayServiceTotal = 0;
243
+ for (const date of Object.keys(servicePrices)) {
244
+ const price = servicePrices[date];
245
+ const dateObj = dayjs(date);
246
+ if (dateObj.isSame(startOfLastMonth, "month")) {
247
+ lastMonthServiceTotal += price;
248
+ }
249
+ if (dateObj.isSame(startOfThisMonth, "month")) {
250
+ thisMonthServiceTotal += price;
251
+ }
252
+ if (dateObj.isSame(startOfLast7Days, "week") && !dateObj.isSame(startOfYesterday, "day")) {
253
+ last7DaysServiceTotal += price;
254
+ }
255
+ if (dateObj.isSame(startOfYesterday, "day")) {
256
+ yesterdayServiceTotal += price;
257
+ }
258
+ }
259
+ totalsByService.lastMonth[service] = lastMonthServiceTotal;
260
+ totalsByService.thisMonth[service] = thisMonthServiceTotal;
261
+ totalsByService.last7Days[service] = last7DaysServiceTotal;
262
+ totalsByService.yesterday[service] = yesterdayServiceTotal;
263
+ totals.lastMonth += lastMonthServiceTotal;
264
+ totals.thisMonth += thisMonthServiceTotal;
265
+ totals.last7Days += last7DaysServiceTotal;
266
+ totals.yesterday += yesterdayServiceTotal;
267
+ }
268
+ return {
269
+ totals,
270
+ totalsByService
271
+ };
272
+ }
273
+ async function getTotalCosts(awsConfig2) {
274
+ const rawCosts = await getRawCostByService(awsConfig2);
275
+ const totals = calculateServiceTotals(rawCosts);
276
+ return totals;
277
+ }
278
+
279
+ // src/printers/fancy.ts
280
+ import chalk3 from "chalk";
281
+ function printFancy(accountAlias, totals, isSummary = false) {
282
+ hideSpinner();
283
+ console.clear();
284
+ const totalCosts = totals.totals;
285
+ const serviceCosts = totals.totalsByService;
286
+ const allServices = Object.keys(serviceCosts.yesterday);
287
+ const sortedServiceNames = allServices.sort((a, b) => b.length - a.length);
288
+ const maxServiceLength = sortedServiceNames.reduce((max, service) => {
289
+ return Math.max(max, service.length);
290
+ }, 0) + 1;
291
+ const totalLastMonth = chalk3.green(`$${totalCosts.lastMonth.toFixed(2)}`);
292
+ const totalThisMonth = chalk3.green(`$${totalCosts.thisMonth.toFixed(2)}`);
293
+ const totalLast7Days = chalk3.green(`$${totalCosts.last7Days.toFixed(2)}`);
294
+ const totalYesterday = chalk3.bold.yellowBright(`$${totalCosts.yesterday.toFixed(2)}`);
295
+ console.log("");
296
+ console.log(`${"AWS Cost Report:".padStart(maxServiceLength + 1)} ${chalk3.bold.yellow(accountAlias)}`);
297
+ console.log("");
298
+ console.log(`${"Last Month".padStart(maxServiceLength)}: ${totalLastMonth}`);
299
+ console.log(`${"This Month".padStart(maxServiceLength)}: ${totalThisMonth}`);
300
+ console.log(`${"Last 7 days".padStart(maxServiceLength)}: ${totalLast7Days}`);
301
+ console.log(`${chalk3.bold("Yesterday".padStart(maxServiceLength))}: ${totalYesterday}`);
302
+ console.log("");
303
+ if (isSummary) {
304
+ return;
305
+ }
306
+ const headerPadLength = 11;
307
+ const serviceHeader = chalk3.white("Service".padStart(maxServiceLength));
308
+ const lastMonthHeader = chalk3.white(`Last Month`.padEnd(headerPadLength));
309
+ const thisMonthHeader = chalk3.white(`This Month`.padEnd(headerPadLength));
310
+ const last7DaysHeader = chalk3.white(`Last 7 Days`.padEnd(headerPadLength));
311
+ const yesterdayHeader = chalk3.bold.white("Yesterday".padEnd(headerPadLength));
312
+ console.log(`${serviceHeader} ${lastMonthHeader} ${thisMonthHeader} ${last7DaysHeader} ${yesterdayHeader}`);
313
+ for (let service of sortedServiceNames) {
314
+ const serviceLabel = chalk3.cyan(service.padStart(maxServiceLength));
315
+ const lastMonthTotal = chalk3.green(`$${serviceCosts.lastMonth[service].toFixed(2)}`.padEnd(headerPadLength));
316
+ const thisMonthTotal = chalk3.green(`$${serviceCosts.thisMonth[service].toFixed(2)}`.padEnd(headerPadLength));
317
+ const last7DaysTotal = chalk3.green(`$${serviceCosts.last7Days[service].toFixed(2)}`.padEnd(headerPadLength));
318
+ const yesterdayTotal = chalk3.bold.yellowBright(
319
+ `$${serviceCosts.yesterday[service].toFixed(2)}`.padEnd(headerPadLength)
320
+ );
321
+ console.log(`${serviceLabel} ${lastMonthTotal} ${thisMonthTotal} ${last7DaysTotal} ${yesterdayTotal}`);
322
+ }
323
+ }
324
+
325
+ // src/printers/json.ts
326
+ function printJson(accountAlias, totalCosts, isSummary = false) {
327
+ hideSpinner();
328
+ if (isSummary) {
329
+ console.log(
330
+ JSON.stringify(
331
+ {
332
+ account: accountAlias,
333
+ totals: totalCosts.totals
334
+ },
335
+ null,
336
+ 2
337
+ )
338
+ );
339
+ return;
340
+ }
341
+ console.log(
342
+ JSON.stringify(
343
+ {
344
+ account: accountAlias,
345
+ ...totalCosts
346
+ },
347
+ null,
348
+ 2
349
+ )
350
+ );
351
+ }
352
+
353
+ // src/printers/slack.ts
354
+ import fetch from "node-fetch";
355
+ function formatServiceBreakdown(costs2) {
356
+ const serviceCosts = costs2.totalsByService;
357
+ const sortedServices = Object.keys(serviceCosts.yesterday).filter((service) => serviceCosts.yesterday[service] > 0).sort((a, b) => serviceCosts.yesterday[b] - serviceCosts.yesterday[a]);
358
+ const serviceCostsYesterday = sortedServices.map((service) => {
359
+ return `> ${service}: \`$${serviceCosts.yesterday[service].toFixed(2)}\``;
360
+ });
361
+ return serviceCostsYesterday.join("\n");
362
+ }
363
+ async function notifySlack(accountAlias, costs2, isSummary, slackToken, slackChannel) {
364
+ const channel = slackChannel;
365
+ const totals = costs2.totals;
366
+ const serviceCosts = costs2.totalsByService;
367
+ let serviceCostsYesterday = [];
368
+ Object.keys(serviceCosts.yesterday).forEach((service) => {
369
+ serviceCosts.yesterday[service].toFixed(2);
370
+ serviceCostsYesterday.push(`${service}: $${serviceCosts.yesterday[service].toFixed(2)}`);
371
+ });
372
+ const summary = `> *Account: ${accountAlias}*
373
+
374
+ > *Summary *
375
+ > Total Yesterday: \`$${totals.yesterday.toFixed(2)}\`
376
+ > Total This Month: \`$${totals.thisMonth.toFixed(2)}\`
377
+ > Total Last Month: \`$${totals.lastMonth.toFixed(2)}\`
378
+ `;
379
+ const breakdown = `
380
+ > *Breakdown by Service:*
381
+ ${formatServiceBreakdown(costs2)}
382
+ `;
383
+ let message = `${summary}`;
384
+ if (!isSummary) {
385
+ message += `${breakdown}`;
386
+ }
387
+ const response = await fetch("https://slack.com/api/chat.postMessage", {
388
+ method: "post",
389
+ body: JSON.stringify({
390
+ channel,
391
+ blocks: [
392
+ {
393
+ type: "section",
394
+ text: {
395
+ type: "mrkdwn",
396
+ text: message
397
+ }
398
+ }
399
+ ]
400
+ }),
401
+ headers: {
402
+ "Content-Type": "application/json; charset=utf-8",
403
+ Authorization: `Bearer ${slackToken}`
404
+ }
405
+ });
406
+ const data = await response.json();
407
+ if (!data.ok) {
408
+ const message2 = data.error || "Unknown error";
409
+ console.error(`
410
+ Failed to send message to Slack: ${message2}`);
411
+ process.exit(1);
412
+ }
413
+ console.log("\nSuccessfully sent message to Slack");
414
+ }
415
+
416
+ // src/printers/text.ts
417
+ function printPlainSummary(accountAlias, costs2) {
418
+ hideSpinner();
419
+ console.clear();
420
+ console.log("");
421
+ console.log(`Account: ${accountAlias}`);
422
+ console.log("");
423
+ console.log("Totals:");
424
+ console.log(` Last Month: $${costs2.totals.lastMonth.toFixed(2)}`);
425
+ console.log(` This Month: $${costs2.totals.thisMonth.toFixed(2)}`);
426
+ console.log(` Last 7 Days: $${costs2.totals.last7Days.toFixed(2)}`);
427
+ console.log(` Yesterday: $${costs2.totals.yesterday.toFixed(2)}`);
428
+ }
429
+ function printPlainText(accountAlias, totals, isSummary = false) {
430
+ printPlainSummary(accountAlias, totals);
431
+ if (isSummary) {
432
+ return;
433
+ }
434
+ const serviceTotals = totals.totalsByService;
435
+ const allServices = Object.keys(serviceTotals.yesterday).sort((a, b) => b.length - a.length);
436
+ console.log("");
437
+ console.log("Totals by Service:");
438
+ console.log(" Last Month:");
439
+ allServices.forEach((service) => {
440
+ console.log(` ${service}: $${serviceTotals.lastMonth[service].toFixed(2)}`);
441
+ });
442
+ console.log("");
443
+ console.log(" This Month:");
444
+ allServices.forEach((service) => {
445
+ console.log(` ${service}: $${serviceTotals.thisMonth[service].toFixed(2)}`);
446
+ });
447
+ console.log("");
448
+ console.log(" Last 7 Days:");
449
+ allServices.forEach((service) => {
450
+ console.log(` ${service}: $${serviceTotals.last7Days[service].toFixed(2)}`);
451
+ });
452
+ console.log("");
453
+ console.log(" Yesterday:");
454
+ allServices.forEach((service) => {
455
+ console.log(` ${service}: $${serviceTotals.yesterday[service].toFixed(2)}`);
456
+ });
457
+ }
458
+
459
+ // src/index.ts
460
+ process.env.AWS_SDK_JS_SUPPRESS_MAINTENANCE_MODE_MESSAGE = "1";
461
+ var program = new Command();
462
+ program.version(package_default.version).name("aws-cost").description(package_default.description).option("-p, --profile [profile]", "AWS profile to use", "default").option("-k, --access-key [key]", "AWS access key").option("-s, --secret-key [key]", "AWS secret key").option("-T, --session-token [key]", "AWS session Token").option("-r, --region [region]", "AWS region", "us-east-1").option("-j, --json", "Get the output as JSON").option("-u, --summary", "Get only the summary without service breakdown").option("-t, --text", "Get the output as plain text (no colors / tables)").option("-S, --slack-token [token]", "Token for the slack integration").option("-C, --slack-channel [channel]", "Channel to which the slack integration should post").option("-h, --help", "Get the help of the CLI").parse(process.argv);
463
+ var options = program.opts();
464
+ if (options.help) {
465
+ program.help();
466
+ process.exit(0);
467
+ }
468
+ var awsConfig = await getAwsConfigFromOptionsOrFile({
469
+ profile: options.profile,
470
+ accessKey: options.accessKey,
471
+ secretKey: options.secretKey,
472
+ sessionToken: options.sessionToken,
473
+ region: options.region
474
+ });
475
+ var alias = await getAccountAlias(awsConfig);
476
+ var costs = await getTotalCosts(awsConfig);
477
+ if (options.json) {
478
+ printJson(alias, costs, options.summary);
479
+ } else if (options.text) {
480
+ printPlainText(alias, costs, options.summary);
481
+ } else {
482
+ printFancy(alias, costs, options.summary);
483
+ }
484
+ if (options.slackToken && options.slackChannel) {
485
+ await notifySlack(alias, costs, options.summary, options.slackToken, options.slackChannel);
486
+ }
487
+ //# sourceMappingURL=index.js.map
package/package.json ADDED
@@ -0,0 +1,67 @@
1
+ {
2
+ "name": "infra-cost",
3
+ "version": "0.1.0",
4
+ "description": "A CLI tool to perform cost analysis on your infra account's cost",
5
+ "type": "module",
6
+ "author": "Code Collab <codecollab.co@gmail.com> (https://github.com/codecollab-co/infra-cost)",
7
+ "files": [
8
+ "!tests/**/*",
9
+ "dist/**/*",
10
+ "!dist/**/*.js.map",
11
+ "bin/**/*"
12
+ ],
13
+ "bin": {
14
+ "aws-cost": "bin/index.js"
15
+ },
16
+ "scripts": {
17
+ "build": "tsup",
18
+ "dev": "tsup --watch",
19
+ "prebuild": "run-s clean",
20
+ "predev": "run-s clean",
21
+ "clean": "rm -rf dist",
22
+ "typecheck": "tsc --noEmit",
23
+ "test": "echo \"Error: no test specified\" && exit 1"
24
+ },
25
+ "keywords": [
26
+ "aws",
27
+ "cost",
28
+ "cli",
29
+ "aws-cost",
30
+ "aws-cost-cli",
31
+ "aws-costs",
32
+ "typescript",
33
+ "aws cli"
34
+ ],
35
+ "license": "MIT",
36
+ "repository": {
37
+ "type": "git",
38
+ "url": "git+https://github.com/codecollab-co/infra-cost.git"
39
+ },
40
+ "engines": {
41
+ "node": ">=12.0"
42
+ },
43
+ "bugs": {
44
+ "url": "https://github.com/codecollab-co/infra-cost/issues"
45
+ },
46
+ "homepage": "https://github.com/codecollab-co/infra-cost.git#readme",
47
+ "dependencies": {
48
+ "@aws-sdk/shared-ini-file-loader": "^3.254.0",
49
+ "aws-sdk": "^2.1299.0",
50
+ "chalk": "^5.2.0",
51
+ "commander": "^10.0.0",
52
+ "dayjs": "^1.11.7",
53
+ "dotenv": "^16.0.3",
54
+ "node-fetch": "^3.3.0",
55
+ "ora": "^6.1.2"
56
+ },
57
+ "devDependencies": {
58
+ "@types/node": "^18.11.18",
59
+ "npm-run-all": "^4.1.5",
60
+ "react": "^19.0.0",
61
+ "ts-node": "^10.9.1",
62
+ "tslib": "^2.8.1",
63
+ "tsup": "^8.4.0",
64
+ "typescript": "^4.9.5"
65
+ },
66
+ "main": "index.js"
67
+ }