cloud-cost-cli 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.
Files changed (43) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +235 -0
  3. package/dist/bin/cloud-cost-cli.d.ts +2 -0
  4. package/dist/bin/cloud-cost-cli.js +23 -0
  5. package/dist/src/analyzers/cost-estimator.d.ts +21 -0
  6. package/dist/src/analyzers/cost-estimator.js +86 -0
  7. package/dist/src/commands/scan.d.ts +12 -0
  8. package/dist/src/commands/scan.js +111 -0
  9. package/dist/src/providers/aws/client.d.ts +27 -0
  10. package/dist/src/providers/aws/client.js +53 -0
  11. package/dist/src/providers/aws/ebs.d.ts +3 -0
  12. package/dist/src/providers/aws/ebs.js +41 -0
  13. package/dist/src/providers/aws/ec2.d.ts +3 -0
  14. package/dist/src/providers/aws/ec2.js +75 -0
  15. package/dist/src/providers/aws/eip.d.ts +3 -0
  16. package/dist/src/providers/aws/eip.js +38 -0
  17. package/dist/src/providers/aws/elb.d.ts +3 -0
  18. package/dist/src/providers/aws/elb.js +66 -0
  19. package/dist/src/providers/aws/rds.d.ts +3 -0
  20. package/dist/src/providers/aws/rds.js +92 -0
  21. package/dist/src/providers/aws/s3.d.ts +3 -0
  22. package/dist/src/providers/aws/s3.js +54 -0
  23. package/dist/src/reporters/json.d.ts +2 -0
  24. package/dist/src/reporters/json.js +6 -0
  25. package/dist/src/reporters/table.d.ts +2 -0
  26. package/dist/src/reporters/table.js +41 -0
  27. package/dist/src/types/index.d.ts +2 -0
  28. package/dist/src/types/index.js +18 -0
  29. package/dist/src/types/opportunity.d.ts +31 -0
  30. package/dist/src/types/opportunity.js +2 -0
  31. package/dist/src/types/provider.d.ts +15 -0
  32. package/dist/src/types/provider.js +2 -0
  33. package/dist/src/utils/formatter.d.ts +3 -0
  34. package/dist/src/utils/formatter.js +16 -0
  35. package/dist/src/utils/index.d.ts +2 -0
  36. package/dist/src/utils/index.js +18 -0
  37. package/dist/src/utils/logger.d.ts +6 -0
  38. package/dist/src/utils/logger.js +30 -0
  39. package/docs/RELEASE.md +143 -0
  40. package/docs/contributing.md +201 -0
  41. package/docs/iam-policy.json +25 -0
  42. package/docs/installation.md +208 -0
  43. package/package.json +69 -0
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Phuong Vu
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/README.md ADDED
@@ -0,0 +1,235 @@
1
+ # cloud-cost-cli
2
+
3
+ **Optimize your cloud spend in seconds.**
4
+
5
+ A command-line tool that analyzes your AWS, GCP, and Azure bills to identify cost-saving opportunities — idle resources, oversized instances, unattached volumes, and more.
6
+
7
+ ---
8
+
9
+ ## The Problem
10
+
11
+ Cloud bills are growing faster than revenue. Engineering teams overprovision, forget to clean up dev resources, and lack visibility into what's actually costing money. Existing cost management tools are expensive, slow, or buried in complex dashboards.
12
+
13
+ ## The Solution
14
+
15
+ `cloud-cost-cli` connects to your cloud accounts, analyzes resource usage and billing data, and outputs a ranked list of actionable savings opportunities — all in your terminal, in under 60 seconds.
16
+
17
+ **What it finds:**
18
+ - Idle EC2/Compute instances (low CPU, stopped but still billed)
19
+ - Unattached EBS volumes and snapshots
20
+ - Oversized RDS/database instances
21
+ - Old load balancers with no traffic
22
+ - Unused Elastic IPs
23
+ - Underutilized reserved instances or savings plan mismatches
24
+ - Redundant S3 storage (lifecycle policies missing)
25
+
26
+ ---
27
+
28
+ ## Features (MVP - Week 1–2)
29
+
30
+ - ✅ AWS support (EC2, EBS, RDS, S3, ELB, Elastic IP)
31
+ - ✅ Connect via AWS credentials (IAM read-only recommended)
32
+ - ✅ Analyze last 30 days of usage
33
+ - ✅ Output top 5 savings opportunities with estimated monthly savings
34
+ - ✅ Export report as JSON, Markdown, or terminal table
35
+ - ✅ Zero third-party API dependencies (uses AWS SDK directly)
36
+
37
+ **Coming soon:**
38
+ - GCP and Azure support
39
+ - Slack/email alerts for new cost spikes
40
+ - Scheduled reports (cron-friendly)
41
+ - Team dashboard (web UI, SaaS)
42
+ - Custom rules and thresholds
43
+ - Terraform/IaC integration (flag risky configs before apply)
44
+
45
+ ---
46
+
47
+ ## Installation
48
+
49
+ **Requirements:**
50
+ - Node.js >= 18 (or use the standalone binary)
51
+ - AWS credentials configured (see [AWS CLI setup](https://docs.aws.amazon.com/cli/latest/userguide/cli-configure-files.html))
52
+
53
+ **Install via npm:**
54
+ ```bash
55
+ npm install -g cloud-cost-cli
56
+ ```
57
+
58
+ **Or download standalone binary:**
59
+ ```bash
60
+ # macOS/Linux
61
+ curl -L https://github.com/vuhp/cloud-cost-cli/releases/latest/download/cloud-cost-cli-$(uname -s)-$(uname -m) -o /usr/local/bin/cloud-cost-cli
62
+ chmod +x /usr/local/bin/cloud-cost-cli
63
+
64
+ # Windows (PowerShell)
65
+ Invoke-WebRequest -Uri "https://github.com/vuhp/cloud-cost-cli/releases/latest/download/cloud-cost-cli-windows-x64.exe" -OutFile "$env:USERPROFILE\bin\cloud-cost-cli.exe"
66
+ ```
67
+
68
+ ---
69
+
70
+ ## Usage
71
+
72
+ **Basic scan (AWS):**
73
+ ```bash
74
+ cloud-cost-cli scan --provider aws --profile default
75
+ ```
76
+
77
+ **Specify region and output format:**
78
+ ```bash
79
+ cloud-cost-cli scan --provider aws --region us-east-1 --output json > report.json
80
+ ```
81
+
82
+ **Top N opportunities:**
83
+ ```bash
84
+ cloud-cost-cli scan --provider aws --top 10
85
+ ```
86
+
87
+ **Example output:**
88
+ ```
89
+ Cloud Cost Optimization Report
90
+ Provider: AWS | Region: us-east-1 | Account: 123456789012
91
+ Analyzed: 2026-01-01 to 2026-01-31
92
+
93
+ Top 5 Savings Opportunities (est. $1,245/month):
94
+
95
+ 1. Idle EC2 instance: i-0abc123def456
96
+ Type: m5.large | Running 720h | Avg CPU: 2%
97
+ → Recommendation: Stop or downsize to t3.small
98
+ → Est. savings: $65/month
99
+
100
+ 2. Unattached EBS volume: vol-0xyz789abc
101
+ Size: 500 GB (gp3) | Age: 45 days
102
+ → Recommendation: Delete or snapshot + delete
103
+ → Est. savings: $40/month
104
+
105
+ 3. Oversized RDS instance: mydb-production
106
+ Type: db.r5.xlarge | Avg connections: 5 | Avg CPU: 15%
107
+ → Recommendation: Downsize to db.t3.large
108
+ → Est. savings: $180/month
109
+
110
+ 4. Unused Elastic Load Balancer: my-old-alb
111
+ Active targets: 0 | Requests/day: 0
112
+ → Recommendation: Delete
113
+ → Est. savings: $22/month
114
+
115
+ 5. S3 bucket with no lifecycle policy: logs-bucket-2023
116
+ Size: 12 TB | Age: 18 months
117
+ → Recommendation: Enable Intelligent-Tiering or Glacier transition
118
+ → Est. savings: $938/month
119
+
120
+ Total potential savings: $1,245/month ($14,940/year)
121
+ ```
122
+
123
+ ---
124
+
125
+ ## Configuration
126
+
127
+ Create a config file at `~/.cloud-cost-cli/config.json` (optional):
128
+
129
+ ```json
130
+ {
131
+ "aws": {
132
+ "profile": "default",
133
+ "regions": ["us-east-1", "us-west-2"],
134
+ "excludeResources": ["i-0abc123", "vol-xyz"],
135
+ "thresholds": {
136
+ "idleCpuPercent": 5,
137
+ "minAgeDays": 7
138
+ }
139
+ },
140
+ "output": {
141
+ "format": "table",
142
+ "includeRecommendations": true
143
+ }
144
+ }
145
+ ```
146
+
147
+ ---
148
+
149
+ ## Roadmap
150
+
151
+ **Phase 1 (Week 1–2): MVP**
152
+ - AWS support (EC2, EBS, RDS, S3, ELB, EIP)
153
+ - CLI with basic scan and reporting
154
+ - GitHub Sponsors setup
155
+ - Launch on Hacker News, Product Hunt, Reddit
156
+
157
+ **Phase 2 (Month 2):**
158
+ - GCP support
159
+ - Azure support
160
+ - Scheduled reports (cron mode)
161
+ - Slack webhook integration
162
+ - GitHub Sponsors perks (early access, priority support)
163
+
164
+ **Phase 3 (Month 3+):**
165
+ - SaaS version: connect account, weekly email reports, team dashboard
166
+ - Paid tiers: $29/mo (alerts + history), $99/mo (multi-account + API)
167
+ - Enterprise: on-prem, SSO, audit logs, custom rules
168
+ - Terraform provider (flag cost risks before apply)
169
+
170
+ ---
171
+
172
+ ## Contributing
173
+
174
+ Contributions welcome! Please open an issue before submitting large PRs.
175
+
176
+ **Development setup:**
177
+ ```bash
178
+ git clone https://github.com/vuhp/cloud-cost-cli.git
179
+ cd cloud-cost-cli
180
+ npm install
181
+ npm run dev
182
+ ```
183
+
184
+ **Run tests:**
185
+ ```bash
186
+ npm test
187
+ ```
188
+
189
+ ---
190
+
191
+ ## Sponsorship
192
+
193
+ If this tool saves you money, consider sponsoring development via [GitHub Sponsors](https://github.com/sponsors/vuhp).
194
+
195
+ **Sponsor tiers:**
196
+ - ☕ $5/mo: Thank you!
197
+ - 🚀 $25/mo: Early access to new features + priority support
198
+ - 💼 $100/mo: Logo on README + monthly 1:1 consultation (30 min)
199
+ - 🏢 $500/mo: Custom rule development + dedicated Slack channel
200
+
201
+ ---
202
+
203
+ ## License
204
+
205
+ MIT License - see [LICENSE](LICENSE)
206
+
207
+ ---
208
+
209
+ ## Credits
210
+
211
+ **Powered by:**
212
+ - AWS SDK for JavaScript
213
+ - Commander.js
214
+ - Chalk (terminal colors)
215
+ - Table (terminal tables)
216
+
217
+ ---
218
+
219
+ ## FAQ
220
+
221
+ **Q: Does this tool make changes to my infrastructure?**
222
+ A: No. It only reads billing and usage data. It never modifies or deletes resources.
223
+
224
+ **Q: What IAM permissions are required?**
225
+ A: Read-only permissions for Cost Explorer, EC2, EBS, RDS, S3, ELB. See [IAM policy template](docs/iam-policy.json).
226
+
227
+ **Q: How accurate are the savings estimates?**
228
+ A: Estimates are based on current pricing and usage patterns. Actual savings may vary.
229
+
230
+ **Q: Can I run this in CI/CD?**
231
+ A: Yes. Use `--output json` and fail the build if savings exceed a threshold.
232
+
233
+ ---
234
+
235
+ **Star this repo if it saves you money!** ⭐
@@ -0,0 +1,2 @@
1
+ #!/usr/bin/env node
2
+ export {};
@@ -0,0 +1,23 @@
1
+ #!/usr/bin/env node
2
+ "use strict";
3
+ Object.defineProperty(exports, "__esModule", { value: true });
4
+ const commander_1 = require("commander");
5
+ const scan_js_1 = require("../src/commands/scan.js");
6
+ const program = new commander_1.Command();
7
+ program
8
+ .name('cloud-cost-cli')
9
+ .description('Optimize your cloud spend in seconds')
10
+ .version('0.1.0');
11
+ program
12
+ .command('scan')
13
+ .description('Scan cloud account for cost savings')
14
+ .option('--provider <aws|gcp|azure>', 'Cloud provider', 'aws')
15
+ .option('--region <region>', 'Cloud region (e.g., us-east-1)')
16
+ .option('--profile <profile>', 'AWS profile name', 'default')
17
+ .option('--top <N>', 'Show top N opportunities', '5')
18
+ .option('--output <table|json|markdown>', 'Output format', 'table')
19
+ .option('--days <N>', 'Analysis period in days', '30')
20
+ .option('--min-savings <amount>', 'Filter by minimum savings ($/month)')
21
+ .option('--verbose', 'Verbose logging')
22
+ .action(scan_js_1.scanCommand);
23
+ program.parse();
@@ -0,0 +1,21 @@
1
+ export declare const EC2_PRICING: Record<string, number>;
2
+ export declare const EBS_PRICING: Record<string, number>;
3
+ export declare const RDS_PRICING: Record<string, number>;
4
+ export declare const S3_PRICING: {
5
+ standard: number;
6
+ intelligentTiering: number;
7
+ glacier: number;
8
+ glacierDeepArchive: number;
9
+ };
10
+ export declare const ELB_PRICING: {
11
+ alb: number;
12
+ nlb: number;
13
+ clb: number;
14
+ };
15
+ export declare const EIP_PRICING_HOURLY = 0.005;
16
+ export declare function getEC2MonthlyCost(instanceType: string): number;
17
+ export declare function getEBSMonthlyCost(sizeGB: number, volumeType: string): number;
18
+ export declare function getRDSMonthlyCost(instanceClass: string): number;
19
+ export declare function getS3MonthlyCost(sizeGB: number, storageClass?: string): number;
20
+ export declare function getELBMonthlyCost(type?: 'alb' | 'nlb' | 'clb'): number;
21
+ export declare function getEIPMonthlyCost(): number;
@@ -0,0 +1,86 @@
1
+ "use strict";
2
+ // EC2 instance pricing (us-east-1, Linux, on-demand, monthly estimate)
3
+ // Source: AWS pricing as of 2026-01, monthly = hourly × 730
4
+ Object.defineProperty(exports, "__esModule", { value: true });
5
+ exports.EIP_PRICING_HOURLY = exports.ELB_PRICING = exports.S3_PRICING = exports.RDS_PRICING = exports.EBS_PRICING = exports.EC2_PRICING = void 0;
6
+ exports.getEC2MonthlyCost = getEC2MonthlyCost;
7
+ exports.getEBSMonthlyCost = getEBSMonthlyCost;
8
+ exports.getRDSMonthlyCost = getRDSMonthlyCost;
9
+ exports.getS3MonthlyCost = getS3MonthlyCost;
10
+ exports.getELBMonthlyCost = getELBMonthlyCost;
11
+ exports.getEIPMonthlyCost = getEIPMonthlyCost;
12
+ exports.EC2_PRICING = {
13
+ 't2.micro': 8.47,
14
+ 't2.small': 16.79,
15
+ 't2.medium': 33.87,
16
+ 't3.micro': 7.59,
17
+ 't3.small': 15.18,
18
+ 't3.medium': 30.37,
19
+ 't3.large': 60.74,
20
+ 't3.xlarge': 121.47,
21
+ 'm5.large': 70.08,
22
+ 'm5.xlarge': 140.16,
23
+ 'm5.2xlarge': 280.32,
24
+ 'm6i.large': 69.35,
25
+ 'm6i.xlarge': 138.70,
26
+ 'c5.large': 62.05,
27
+ 'c5.xlarge': 124.10,
28
+ 'r5.large': 91.25,
29
+ 'r5.xlarge': 182.50,
30
+ };
31
+ // EBS pricing (us-east-1, per GB/month)
32
+ exports.EBS_PRICING = {
33
+ gp3: 0.08,
34
+ gp2: 0.10,
35
+ io1: 0.125,
36
+ io2: 0.125,
37
+ st1: 0.045,
38
+ sc1: 0.015,
39
+ };
40
+ // RDS pricing (us-east-1, per month)
41
+ exports.RDS_PRICING = {
42
+ 'db.t3.micro': 11.01,
43
+ 'db.t3.small': 22.63,
44
+ 'db.t3.medium': 45.26,
45
+ 'db.t3.large': 90.51,
46
+ 'db.m5.large': 127.75,
47
+ 'db.m5.xlarge': 255.50,
48
+ 'db.r5.large': 175.20,
49
+ 'db.r5.xlarge': 350.40,
50
+ 'db.r5.2xlarge': 700.80,
51
+ };
52
+ // S3 pricing (per GB/month)
53
+ exports.S3_PRICING = {
54
+ standard: 0.023,
55
+ intelligentTiering: 0.023, // same base, but auto-transitions
56
+ glacier: 0.004,
57
+ glacierDeepArchive: 0.00099,
58
+ };
59
+ // ELB pricing (per month, excluding LCU charges)
60
+ exports.ELB_PRICING = {
61
+ alb: 16.43, // Application Load Balancer
62
+ nlb: 16.43, // Network Load Balancer
63
+ clb: 18.25, // Classic Load Balancer
64
+ };
65
+ // Elastic IP pricing (per hour when not associated)
66
+ exports.EIP_PRICING_HOURLY = 0.005; // ~$3.65/month
67
+ function getEC2MonthlyCost(instanceType) {
68
+ return exports.EC2_PRICING[instanceType] || 0;
69
+ }
70
+ function getEBSMonthlyCost(sizeGB, volumeType) {
71
+ const pricePerGB = exports.EBS_PRICING[volumeType] || 0.08;
72
+ return sizeGB * pricePerGB;
73
+ }
74
+ function getRDSMonthlyCost(instanceClass) {
75
+ return exports.RDS_PRICING[instanceClass] || 0;
76
+ }
77
+ function getS3MonthlyCost(sizeGB, storageClass = 'standard') {
78
+ const pricePerGB = exports.S3_PRICING[storageClass] || exports.S3_PRICING.standard;
79
+ return sizeGB * pricePerGB;
80
+ }
81
+ function getELBMonthlyCost(type = 'alb') {
82
+ return exports.ELB_PRICING[type];
83
+ }
84
+ function getEIPMonthlyCost() {
85
+ return exports.EIP_PRICING_HOURLY * 730; // ~$3.65
86
+ }
@@ -0,0 +1,12 @@
1
+ interface ScanCommandOptions {
2
+ provider: string;
3
+ region?: string;
4
+ profile?: string;
5
+ top?: string;
6
+ output?: string;
7
+ days?: string;
8
+ minSavings?: string;
9
+ verbose?: boolean;
10
+ }
11
+ export declare function scanCommand(options: ScanCommandOptions): Promise<void>;
12
+ export {};
@@ -0,0 +1,111 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.scanCommand = scanCommand;
4
+ const client_1 = require("../providers/aws/client");
5
+ const ec2_1 = require("../providers/aws/ec2");
6
+ const ebs_1 = require("../providers/aws/ebs");
7
+ const rds_1 = require("../providers/aws/rds");
8
+ const s3_1 = require("../providers/aws/s3");
9
+ const elb_1 = require("../providers/aws/elb");
10
+ const eip_1 = require("../providers/aws/eip");
11
+ const table_1 = require("../reporters/table");
12
+ const json_1 = require("../reporters/json");
13
+ const logger_1 = require("../utils/logger");
14
+ async function scanCommand(options) {
15
+ try {
16
+ if (options.provider !== 'aws') {
17
+ (0, logger_1.error)(`Provider "${options.provider}" not yet supported. Use --provider aws`);
18
+ process.exit(1);
19
+ }
20
+ (0, logger_1.info)(`Scanning AWS account (profile: ${options.profile || 'default'}, region: ${options.region || 'us-east-1'})...`);
21
+ const client = new client_1.AWSClient({
22
+ region: options.region,
23
+ profile: options.profile,
24
+ });
25
+ // Run analyzers in parallel
26
+ (0, logger_1.info)('Analyzing EC2 instances...');
27
+ const ec2Promise = (0, ec2_1.analyzeEC2Instances)(client);
28
+ (0, logger_1.info)('Analyzing EBS volumes...');
29
+ const ebsPromise = (0, ebs_1.analyzeEBSVolumes)(client);
30
+ (0, logger_1.info)('Analyzing RDS instances...');
31
+ const rdsPromise = (0, rds_1.analyzeRDSInstances)(client);
32
+ (0, logger_1.info)('Analyzing S3 buckets...');
33
+ const s3Promise = (0, s3_1.analyzeS3Buckets)(client);
34
+ (0, logger_1.info)('Analyzing Load Balancers...');
35
+ const elbPromise = (0, elb_1.analyzeELBs)(client);
36
+ (0, logger_1.info)('Analyzing Elastic IPs...');
37
+ const eipPromise = (0, eip_1.analyzeElasticIPs)(client);
38
+ // Wait for all analyzers to complete
39
+ const [ec2Opportunities, ebsOpportunities, rdsOpportunities, s3Opportunities, elbOpportunities, eipOpportunities,] = await Promise.all([
40
+ ec2Promise,
41
+ ebsPromise,
42
+ rdsPromise,
43
+ s3Promise,
44
+ elbPromise,
45
+ eipPromise,
46
+ ]);
47
+ (0, logger_1.success)(`Found ${ec2Opportunities.length} EC2 opportunities`);
48
+ (0, logger_1.success)(`Found ${ebsOpportunities.length} EBS opportunities`);
49
+ (0, logger_1.success)(`Found ${rdsOpportunities.length} RDS opportunities`);
50
+ (0, logger_1.success)(`Found ${s3Opportunities.length} S3 opportunities`);
51
+ (0, logger_1.success)(`Found ${elbOpportunities.length} ELB opportunities`);
52
+ (0, logger_1.success)(`Found ${eipOpportunities.length} EIP opportunities`);
53
+ // Combine opportunities
54
+ const allOpportunities = [
55
+ ...ec2Opportunities,
56
+ ...ebsOpportunities,
57
+ ...rdsOpportunities,
58
+ ...s3Opportunities,
59
+ ...elbOpportunities,
60
+ ...eipOpportunities,
61
+ ];
62
+ // Filter by minimum savings if specified
63
+ const minSavings = options.minSavings ? parseFloat(options.minSavings) : 0;
64
+ const filteredOpportunities = allOpportunities.filter((opp) => opp.estimatedSavings >= minSavings);
65
+ // Calculate totals
66
+ const totalPotentialSavings = filteredOpportunities.reduce((sum, opp) => sum + opp.estimatedSavings, 0);
67
+ const summary = {
68
+ totalResources: filteredOpportunities.length,
69
+ idleResources: filteredOpportunities.filter((o) => o.category === 'idle').length,
70
+ oversizedResources: filteredOpportunities.filter((o) => o.category === 'oversized').length,
71
+ unusedResources: filteredOpportunities.filter((o) => o.category === 'unused').length,
72
+ };
73
+ const report = {
74
+ provider: 'aws',
75
+ accountId: 'N/A', // Will fetch from STS in future
76
+ region: client.region,
77
+ scanPeriod: {
78
+ start: new Date(Date.now() - (parseInt(options.days || '30') * 24 * 60 * 60 * 1000)),
79
+ end: new Date(),
80
+ },
81
+ opportunities: filteredOpportunities,
82
+ totalPotentialSavings,
83
+ summary,
84
+ };
85
+ // Render output
86
+ const topN = parseInt(options.top || '5');
87
+ if (options.output === 'json') {
88
+ (0, json_1.renderJSON)(report);
89
+ }
90
+ else {
91
+ (0, table_1.renderTable)(report, topN);
92
+ }
93
+ }
94
+ catch (err) {
95
+ if (err.name === 'CredentialsProviderError' || err.message?.includes('credentials')) {
96
+ (0, logger_1.error)('AWS credentials not found or invalid.');
97
+ console.log('\nTo configure AWS credentials:');
98
+ console.log('1. Run: aws configure');
99
+ console.log('2. Or set environment variables: AWS_ACCESS_KEY_ID, AWS_SECRET_ACCESS_KEY');
100
+ console.log('3. Or use --profile flag with a configured profile');
101
+ console.log('\nSee: https://docs.aws.amazon.com/cli/latest/userguide/cli-configure-files.html');
102
+ }
103
+ else {
104
+ (0, logger_1.error)(`Scan failed: ${err.message}`);
105
+ if (options.verbose) {
106
+ console.error(err);
107
+ }
108
+ }
109
+ process.exit(1);
110
+ }
111
+ }
@@ -0,0 +1,27 @@
1
+ import { EC2Client } from '@aws-sdk/client-ec2';
2
+ import { CloudWatchClient } from '@aws-sdk/client-cloudwatch';
3
+ import { CostExplorerClient } from '@aws-sdk/client-cost-explorer';
4
+ import { RDSClient } from '@aws-sdk/client-rds';
5
+ import { S3Client } from '@aws-sdk/client-s3';
6
+ import { ElasticLoadBalancingV2Client } from '@aws-sdk/client-elastic-load-balancing-v2';
7
+ export interface AWSClientOptions {
8
+ region?: string;
9
+ profile?: string;
10
+ }
11
+ export declare class AWSClient {
12
+ private ec2;
13
+ private cloudwatch;
14
+ private costExplorer;
15
+ private rds;
16
+ private s3;
17
+ private elb;
18
+ region: string;
19
+ profile: string;
20
+ constructor(options: AWSClientOptions);
21
+ getEC2Client(): EC2Client;
22
+ getCloudWatchClient(): CloudWatchClient;
23
+ getCostExplorerClient(): CostExplorerClient;
24
+ getRDSClient(): RDSClient;
25
+ getS3Client(): S3Client;
26
+ getELBClient(): ElasticLoadBalancingV2Client;
27
+ }
@@ -0,0 +1,53 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.AWSClient = void 0;
4
+ const client_ec2_1 = require("@aws-sdk/client-ec2");
5
+ const client_cloudwatch_1 = require("@aws-sdk/client-cloudwatch");
6
+ const client_cost_explorer_1 = require("@aws-sdk/client-cost-explorer");
7
+ const client_rds_1 = require("@aws-sdk/client-rds");
8
+ const client_s3_1 = require("@aws-sdk/client-s3");
9
+ const client_elastic_load_balancing_v2_1 = require("@aws-sdk/client-elastic-load-balancing-v2");
10
+ const credential_providers_1 = require("@aws-sdk/credential-providers");
11
+ class AWSClient {
12
+ ec2;
13
+ cloudwatch;
14
+ costExplorer;
15
+ rds;
16
+ s3;
17
+ elb;
18
+ region;
19
+ profile;
20
+ constructor(options) {
21
+ this.region = options.region || 'us-east-1';
22
+ this.profile = options.profile || 'default';
23
+ // Use environment credentials if available, otherwise fall back to profile
24
+ const credentials = process.env.AWS_ACCESS_KEY_ID
25
+ ? (0, credential_providers_1.fromEnv)()
26
+ : (0, credential_providers_1.fromIni)({ profile: this.profile });
27
+ this.ec2 = new client_ec2_1.EC2Client({ region: this.region, credentials });
28
+ this.cloudwatch = new client_cloudwatch_1.CloudWatchClient({ region: this.region, credentials });
29
+ this.costExplorer = new client_cost_explorer_1.CostExplorerClient({ region: 'us-east-1', credentials });
30
+ this.rds = new client_rds_1.RDSClient({ region: this.region, credentials });
31
+ this.s3 = new client_s3_1.S3Client({ region: this.region, credentials });
32
+ this.elb = new client_elastic_load_balancing_v2_1.ElasticLoadBalancingV2Client({ region: this.region, credentials });
33
+ }
34
+ getEC2Client() {
35
+ return this.ec2;
36
+ }
37
+ getCloudWatchClient() {
38
+ return this.cloudwatch;
39
+ }
40
+ getCostExplorerClient() {
41
+ return this.costExplorer;
42
+ }
43
+ getRDSClient() {
44
+ return this.rds;
45
+ }
46
+ getS3Client() {
47
+ return this.s3;
48
+ }
49
+ getELBClient() {
50
+ return this.elb;
51
+ }
52
+ }
53
+ exports.AWSClient = AWSClient;
@@ -0,0 +1,3 @@
1
+ import { AWSClient } from './client';
2
+ import { SavingsOpportunity } from '../../types/opportunity';
3
+ export declare function analyzeEBSVolumes(client: AWSClient): Promise<SavingsOpportunity[]>;
@@ -0,0 +1,41 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.analyzeEBSVolumes = analyzeEBSVolumes;
4
+ const client_ec2_1 = require("@aws-sdk/client-ec2");
5
+ const cost_estimator_1 = require("../../analyzers/cost-estimator");
6
+ const formatter_1 = require("../../utils/formatter");
7
+ async function analyzeEBSVolumes(client) {
8
+ const ec2Client = client.getEC2Client();
9
+ const result = await ec2Client.send(new client_ec2_1.DescribeVolumesCommand({
10
+ Filters: [{ Name: 'status', Values: ['available'] }],
11
+ }));
12
+ const volumes = result.Volumes || [];
13
+ const opportunities = [];
14
+ for (const volume of volumes) {
15
+ if (!volume.VolumeId || !volume.Size || !volume.CreateTime)
16
+ continue;
17
+ const age = (0, formatter_1.daysSince)(volume.CreateTime);
18
+ if (age > 7) {
19
+ const monthlyCost = (0, cost_estimator_1.getEBSMonthlyCost)(volume.Size, volume.VolumeType || 'gp3');
20
+ opportunities.push({
21
+ id: `ebs-unattached-${volume.VolumeId}`,
22
+ provider: 'aws',
23
+ resourceType: 'ebs',
24
+ resourceId: volume.VolumeId,
25
+ resourceName: volume.Tags?.find((t) => t.Key === 'Name')?.Value,
26
+ category: 'unused',
27
+ currentCost: monthlyCost,
28
+ estimatedSavings: monthlyCost,
29
+ confidence: 'high',
30
+ recommendation: `Snapshot and delete, or delete if redundant (age: ${age} days)`,
31
+ metadata: {
32
+ size: volume.Size,
33
+ volumeType: volume.VolumeType,
34
+ age,
35
+ },
36
+ detectedAt: new Date(),
37
+ });
38
+ }
39
+ }
40
+ return opportunities;
41
+ }
@@ -0,0 +1,3 @@
1
+ import { AWSClient } from './client';
2
+ import { SavingsOpportunity } from '../../types/opportunity';
3
+ export declare function analyzeEC2Instances(client: AWSClient): Promise<SavingsOpportunity[]>;