@build-astron-co/nimbus 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +628 -0
- package/bin/nimbus +38 -0
- package/package.json +80 -0
- package/src/__tests__/app.test.ts +76 -0
- package/src/__tests__/audit.test.ts +877 -0
- package/src/__tests__/circuit-breaker.test.ts +116 -0
- package/src/__tests__/cli-run.test.ts +115 -0
- package/src/__tests__/context-manager.test.ts +502 -0
- package/src/__tests__/context.test.ts +242 -0
- package/src/__tests__/enterprise.test.ts +401 -0
- package/src/__tests__/generator.test.ts +433 -0
- package/src/__tests__/hooks.test.ts +582 -0
- package/src/__tests__/init.test.ts +436 -0
- package/src/__tests__/intent-parser.test.ts +229 -0
- package/src/__tests__/llm-router.test.ts +209 -0
- package/src/__tests__/lsp.test.ts +293 -0
- package/src/__tests__/modes.test.ts +336 -0
- package/src/__tests__/permissions.test.ts +338 -0
- package/src/__tests__/serve.test.ts +275 -0
- package/src/__tests__/sessions.test.ts +227 -0
- package/src/__tests__/sharing.test.ts +288 -0
- package/src/__tests__/snapshots.test.ts +581 -0
- package/src/__tests__/state-db.test.ts +334 -0
- package/src/__tests__/stream-with-tools.test.ts +732 -0
- package/src/__tests__/subagents.test.ts +176 -0
- package/src/__tests__/system-prompt.test.ts +169 -0
- package/src/__tests__/tool-converter.test.ts +256 -0
- package/src/__tests__/tool-schemas.test.ts +397 -0
- package/src/__tests__/tools.test.ts +143 -0
- package/src/__tests__/version.test.ts +49 -0
- package/src/agent/compaction-agent.ts +227 -0
- package/src/agent/context-manager.ts +435 -0
- package/src/agent/context.ts +427 -0
- package/src/agent/deploy-preview.ts +426 -0
- package/src/agent/index.ts +68 -0
- package/src/agent/loop.ts +717 -0
- package/src/agent/modes.ts +429 -0
- package/src/agent/permissions.ts +466 -0
- package/src/agent/subagents/base.ts +116 -0
- package/src/agent/subagents/cost.ts +51 -0
- package/src/agent/subagents/explore.ts +42 -0
- package/src/agent/subagents/general.ts +54 -0
- package/src/agent/subagents/index.ts +102 -0
- package/src/agent/subagents/infra.ts +59 -0
- package/src/agent/subagents/security.ts +69 -0
- package/src/agent/system-prompt.ts +436 -0
- package/src/app.ts +122 -0
- package/src/audit/activity-log.ts +290 -0
- package/src/audit/compliance-checker.ts +540 -0
- package/src/audit/cost-tracker.ts +318 -0
- package/src/audit/index.ts +23 -0
- package/src/audit/security-scanner.ts +596 -0
- package/src/auth/guard.ts +75 -0
- package/src/auth/index.ts +56 -0
- package/src/auth/oauth.ts +455 -0
- package/src/auth/providers.ts +470 -0
- package/src/auth/sso.ts +113 -0
- package/src/auth/store.ts +505 -0
- package/src/auth/types.ts +187 -0
- package/src/build.ts +141 -0
- package/src/cli/index.ts +16 -0
- package/src/cli/init.ts +854 -0
- package/src/cli/openapi-spec.ts +356 -0
- package/src/cli/run.ts +237 -0
- package/src/cli/serve-auth.ts +80 -0
- package/src/cli/serve.ts +462 -0
- package/src/cli/web.ts +67 -0
- package/src/cli.ts +1417 -0
- package/src/clients/core-engine-client.ts +227 -0
- package/src/clients/enterprise-client.ts +334 -0
- package/src/clients/generator-client.ts +351 -0
- package/src/clients/git-client.ts +627 -0
- package/src/clients/github-client.ts +410 -0
- package/src/clients/helm-client.ts +504 -0
- package/src/clients/index.ts +80 -0
- package/src/clients/k8s-client.ts +497 -0
- package/src/clients/llm-client.ts +161 -0
- package/src/clients/rest-client.ts +130 -0
- package/src/clients/service-discovery.ts +33 -0
- package/src/clients/terraform-client.ts +482 -0
- package/src/clients/tools-client.ts +1843 -0
- package/src/clients/ws-client.ts +115 -0
- package/src/commands/analyze/index.ts +352 -0
- package/src/commands/apply/helm.ts +473 -0
- package/src/commands/apply/index.ts +213 -0
- package/src/commands/apply/k8s.ts +454 -0
- package/src/commands/apply/terraform.ts +582 -0
- package/src/commands/ask.ts +167 -0
- package/src/commands/audit/index.ts +238 -0
- package/src/commands/auth-cloud.ts +294 -0
- package/src/commands/auth-list.ts +134 -0
- package/src/commands/auth-profile.ts +121 -0
- package/src/commands/auth-status.ts +141 -0
- package/src/commands/aws/ec2.ts +501 -0
- package/src/commands/aws/iam.ts +397 -0
- package/src/commands/aws/index.ts +133 -0
- package/src/commands/aws/lambda.ts +396 -0
- package/src/commands/aws/rds.ts +439 -0
- package/src/commands/aws/s3.ts +439 -0
- package/src/commands/aws/vpc.ts +393 -0
- package/src/commands/aws-discover.ts +649 -0
- package/src/commands/aws-terraform.ts +805 -0
- package/src/commands/azure/aks.ts +376 -0
- package/src/commands/azure/functions.ts +253 -0
- package/src/commands/azure/index.ts +116 -0
- package/src/commands/azure/storage.ts +478 -0
- package/src/commands/azure/vm.ts +355 -0
- package/src/commands/billing/index.ts +256 -0
- package/src/commands/chat.ts +314 -0
- package/src/commands/config.ts +346 -0
- package/src/commands/cost/cloud-cost-estimator.ts +266 -0
- package/src/commands/cost/estimator.ts +79 -0
- package/src/commands/cost/index.ts +594 -0
- package/src/commands/cost/parsers/terraform.ts +273 -0
- package/src/commands/cost/parsers/types.ts +25 -0
- package/src/commands/cost/pricing/aws.ts +544 -0
- package/src/commands/cost/pricing/azure.ts +499 -0
- package/src/commands/cost/pricing/gcp.ts +396 -0
- package/src/commands/cost/pricing/index.ts +40 -0
- package/src/commands/demo.ts +250 -0
- package/src/commands/doctor.ts +794 -0
- package/src/commands/drift/index.ts +439 -0
- package/src/commands/explain.ts +277 -0
- package/src/commands/feedback.ts +389 -0
- package/src/commands/fix.ts +324 -0
- package/src/commands/fs/index.ts +402 -0
- package/src/commands/gcp/compute.ts +325 -0
- package/src/commands/gcp/functions.ts +271 -0
- package/src/commands/gcp/gke.ts +438 -0
- package/src/commands/gcp/iam.ts +344 -0
- package/src/commands/gcp/index.ts +129 -0
- package/src/commands/gcp/storage.ts +284 -0
- package/src/commands/generate-helm.ts +1249 -0
- package/src/commands/generate-k8s.ts +1560 -0
- package/src/commands/generate-terraform.ts +1460 -0
- package/src/commands/gh/index.ts +863 -0
- package/src/commands/git/index.ts +1343 -0
- package/src/commands/helm/index.ts +1126 -0
- package/src/commands/help.ts +539 -0
- package/src/commands/history.ts +142 -0
- package/src/commands/import.ts +868 -0
- package/src/commands/index.ts +367 -0
- package/src/commands/init.ts +1046 -0
- package/src/commands/k8s/index.ts +1137 -0
- package/src/commands/login.ts +631 -0
- package/src/commands/logout.ts +83 -0
- package/src/commands/onboarding.ts +228 -0
- package/src/commands/plan/display.ts +279 -0
- package/src/commands/plan/index.ts +599 -0
- package/src/commands/preview.ts +452 -0
- package/src/commands/questionnaire.ts +1270 -0
- package/src/commands/resume.ts +55 -0
- package/src/commands/team/index.ts +346 -0
- package/src/commands/template.ts +232 -0
- package/src/commands/tf/index.ts +1034 -0
- package/src/commands/upgrade.ts +550 -0
- package/src/commands/usage/index.ts +134 -0
- package/src/commands/version.ts +170 -0
- package/src/compat/index.ts +2 -0
- package/src/compat/runtime.ts +12 -0
- package/src/compat/sqlite.ts +107 -0
- package/src/config/index.ts +17 -0
- package/src/config/manager.ts +530 -0
- package/src/config/safety-policy.ts +358 -0
- package/src/config/schema.ts +125 -0
- package/src/config/types.ts +527 -0
- package/src/context/context-db.ts +199 -0
- package/src/demo/index.ts +349 -0
- package/src/demo/scenarios/full-journey.ts +229 -0
- package/src/demo/scenarios/getting-started.ts +127 -0
- package/src/demo/scenarios/helm-release.ts +341 -0
- package/src/demo/scenarios/k8s-deployment.ts +194 -0
- package/src/demo/scenarios/terraform-vpc.ts +170 -0
- package/src/demo/types.ts +92 -0
- package/src/engine/cost-estimator.ts +438 -0
- package/src/engine/diagram-generator.ts +256 -0
- package/src/engine/drift-detector.ts +902 -0
- package/src/engine/executor.ts +1035 -0
- package/src/engine/index.ts +76 -0
- package/src/engine/orchestrator.ts +636 -0
- package/src/engine/planner.ts +720 -0
- package/src/engine/safety.ts +743 -0
- package/src/engine/verifier.ts +770 -0
- package/src/enterprise/audit.ts +348 -0
- package/src/enterprise/auth.ts +270 -0
- package/src/enterprise/billing.ts +822 -0
- package/src/enterprise/index.ts +17 -0
- package/src/enterprise/teams.ts +443 -0
- package/src/generator/best-practices.ts +1608 -0
- package/src/generator/helm.ts +630 -0
- package/src/generator/index.ts +37 -0
- package/src/generator/intent-parser.ts +514 -0
- package/src/generator/kubernetes.ts +976 -0
- package/src/generator/terraform.ts +1867 -0
- package/src/history/index.ts +8 -0
- package/src/history/manager.ts +322 -0
- package/src/history/types.ts +34 -0
- package/src/hooks/config.ts +432 -0
- package/src/hooks/engine.ts +391 -0
- package/src/hooks/index.ts +4 -0
- package/src/llm/auth-bridge.ts +198 -0
- package/src/llm/circuit-breaker.ts +140 -0
- package/src/llm/config-loader.ts +201 -0
- package/src/llm/cost-calculator.ts +171 -0
- package/src/llm/index.ts +8 -0
- package/src/llm/model-aliases.ts +115 -0
- package/src/llm/provider-registry.ts +63 -0
- package/src/llm/providers/anthropic.ts +433 -0
- package/src/llm/providers/bedrock.ts +477 -0
- package/src/llm/providers/google.ts +405 -0
- package/src/llm/providers/ollama.ts +767 -0
- package/src/llm/providers/openai-compatible.ts +340 -0
- package/src/llm/providers/openai.ts +328 -0
- package/src/llm/providers/openrouter.ts +338 -0
- package/src/llm/router.ts +1035 -0
- package/src/llm/types.ts +232 -0
- package/src/lsp/client.ts +298 -0
- package/src/lsp/languages.ts +116 -0
- package/src/lsp/manager.ts +278 -0
- package/src/mcp/client.ts +402 -0
- package/src/mcp/index.ts +5 -0
- package/src/mcp/manager.ts +133 -0
- package/src/nimbus.ts +214 -0
- package/src/plugins/index.ts +27 -0
- package/src/plugins/loader.ts +334 -0
- package/src/plugins/manager.ts +376 -0
- package/src/plugins/types.ts +284 -0
- package/src/scanners/cicd-scanner.ts +258 -0
- package/src/scanners/cloud-scanner.ts +466 -0
- package/src/scanners/framework-scanner.ts +469 -0
- package/src/scanners/iac-scanner.ts +388 -0
- package/src/scanners/index.ts +539 -0
- package/src/scanners/language-scanner.ts +276 -0
- package/src/scanners/package-manager-scanner.ts +277 -0
- package/src/scanners/types.ts +172 -0
- package/src/sessions/manager.ts +365 -0
- package/src/sessions/types.ts +44 -0
- package/src/sharing/sync.ts +296 -0
- package/src/sharing/viewer.ts +97 -0
- package/src/snapshots/index.ts +2 -0
- package/src/snapshots/manager.ts +530 -0
- package/src/state/artifacts.ts +147 -0
- package/src/state/audit.ts +137 -0
- package/src/state/billing.ts +240 -0
- package/src/state/checkpoints.ts +117 -0
- package/src/state/config.ts +67 -0
- package/src/state/conversations.ts +14 -0
- package/src/state/credentials.ts +154 -0
- package/src/state/db.ts +58 -0
- package/src/state/index.ts +26 -0
- package/src/state/messages.ts +115 -0
- package/src/state/projects.ts +123 -0
- package/src/state/schema.ts +236 -0
- package/src/state/sessions.ts +147 -0
- package/src/state/teams.ts +200 -0
- package/src/telemetry.ts +108 -0
- package/src/tools/aws-ops.ts +952 -0
- package/src/tools/azure-ops.ts +579 -0
- package/src/tools/file-ops.ts +593 -0
- package/src/tools/gcp-ops.ts +625 -0
- package/src/tools/git-ops.ts +773 -0
- package/src/tools/github-ops.ts +799 -0
- package/src/tools/helm-ops.ts +943 -0
- package/src/tools/index.ts +17 -0
- package/src/tools/k8s-ops.ts +819 -0
- package/src/tools/schemas/converter.ts +184 -0
- package/src/tools/schemas/devops.ts +612 -0
- package/src/tools/schemas/index.ts +73 -0
- package/src/tools/schemas/standard.ts +1144 -0
- package/src/tools/schemas/types.ts +705 -0
- package/src/tools/terraform-ops.ts +862 -0
- package/src/types/ambient.d.ts +193 -0
- package/src/types/config.ts +83 -0
- package/src/types/drift.ts +116 -0
- package/src/types/enterprise.ts +335 -0
- package/src/types/index.ts +20 -0
- package/src/types/plan.ts +44 -0
- package/src/types/request.ts +65 -0
- package/src/types/response.ts +54 -0
- package/src/types/service.ts +51 -0
- package/src/ui/App.tsx +997 -0
- package/src/ui/DeployPreview.tsx +169 -0
- package/src/ui/Header.tsx +68 -0
- package/src/ui/InputBox.tsx +350 -0
- package/src/ui/MessageList.tsx +585 -0
- package/src/ui/PermissionPrompt.tsx +151 -0
- package/src/ui/StatusBar.tsx +158 -0
- package/src/ui/ToolCallDisplay.tsx +409 -0
- package/src/ui/chat-ui.ts +853 -0
- package/src/ui/index.ts +33 -0
- package/src/ui/ink/index.ts +711 -0
- package/src/ui/streaming.ts +176 -0
- package/src/ui/types.ts +57 -0
- package/src/utils/analytics.ts +72 -0
- package/src/utils/cost-warning.ts +27 -0
- package/src/utils/env.ts +46 -0
- package/src/utils/errors.ts +69 -0
- package/src/utils/event-bus.ts +38 -0
- package/src/utils/index.ts +24 -0
- package/src/utils/logger.ts +171 -0
- package/src/utils/rate-limiter.ts +121 -0
- package/src/utils/service-auth.ts +49 -0
- package/src/utils/validation.ts +53 -0
- package/src/version.ts +4 -0
- package/src/watcher/index.ts +163 -0
- package/src/wizard/approval.ts +383 -0
- package/src/wizard/index.ts +25 -0
- package/src/wizard/prompts.ts +338 -0
- package/src/wizard/types.ts +171 -0
- package/src/wizard/ui.ts +556 -0
- package/src/wizard/wizard.ts +304 -0
- package/tsconfig.json +24 -0
|
@@ -0,0 +1,594 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Cost Commands
|
|
3
|
+
*
|
|
4
|
+
* Commands for infrastructure cost estimation and tracking
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import { ui } from '../../wizard/ui';
|
|
8
|
+
import { select } from '../../wizard/prompts';
|
|
9
|
+
import * as fs from 'node:fs';
|
|
10
|
+
import * as path from 'node:path';
|
|
11
|
+
import { execSync } from 'node:child_process';
|
|
12
|
+
import { CostEstimator } from './estimator';
|
|
13
|
+
|
|
14
|
+
// ==========================================
|
|
15
|
+
// Types
|
|
16
|
+
// ==========================================
|
|
17
|
+
|
|
18
|
+
export interface CostEstimateOptions {
|
|
19
|
+
/** Directory containing Terraform/infrastructure code */
|
|
20
|
+
directory?: string;
|
|
21
|
+
/** Output format */
|
|
22
|
+
format?: 'table' | 'json' | 'html';
|
|
23
|
+
/** Show detailed breakdown */
|
|
24
|
+
detailed?: boolean;
|
|
25
|
+
/** Compare with baseline */
|
|
26
|
+
compare?: string;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
export interface CostHistoryOptions {
|
|
30
|
+
/** Number of days to show */
|
|
31
|
+
days?: number;
|
|
32
|
+
/** Group by resource, service, or tag */
|
|
33
|
+
groupBy?: 'resource' | 'service' | 'tag';
|
|
34
|
+
/** Cloud provider */
|
|
35
|
+
provider?: 'aws' | 'gcp' | 'azure';
|
|
36
|
+
/** Output format */
|
|
37
|
+
format?: 'table' | 'json';
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
export interface CostResource {
|
|
41
|
+
name: string;
|
|
42
|
+
resourceType: string;
|
|
43
|
+
monthlyQuantity?: number;
|
|
44
|
+
unit?: string;
|
|
45
|
+
monthlyCost: number;
|
|
46
|
+
hourlyCost?: number;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
export interface CostEstimate {
|
|
50
|
+
version: string;
|
|
51
|
+
currency: string;
|
|
52
|
+
projects: {
|
|
53
|
+
name: string;
|
|
54
|
+
metadata: Record<string, string>;
|
|
55
|
+
pastTotalMonthlyCost: number;
|
|
56
|
+
pastTotalHourlyCost: number;
|
|
57
|
+
diffTotalMonthlyCost: number;
|
|
58
|
+
diffTotalHourlyCost: number;
|
|
59
|
+
totalMonthlyCost: number;
|
|
60
|
+
totalHourlyCost: number;
|
|
61
|
+
resources: CostResource[];
|
|
62
|
+
}[];
|
|
63
|
+
totalMonthlyCost: number;
|
|
64
|
+
totalHourlyCost: number;
|
|
65
|
+
diffTotalMonthlyCost: number;
|
|
66
|
+
timeGenerated: string;
|
|
67
|
+
summary: {
|
|
68
|
+
totalDetectedResources: number;
|
|
69
|
+
totalSupportedResources: number;
|
|
70
|
+
totalUnsupportedResources: number;
|
|
71
|
+
totalUsageBasedResources: number;
|
|
72
|
+
totalNoPriceResources: number;
|
|
73
|
+
unsupportedResourceCounts: Record<string, number>;
|
|
74
|
+
noPriceResourceCounts: Record<string, number>;
|
|
75
|
+
};
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
export interface CostHistoryEntry {
|
|
79
|
+
date: string;
|
|
80
|
+
service: string;
|
|
81
|
+
resource?: string;
|
|
82
|
+
cost: number;
|
|
83
|
+
change?: number;
|
|
84
|
+
tags?: Record<string, string>;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
// ==========================================
|
|
88
|
+
// Parsers
|
|
89
|
+
// ==========================================
|
|
90
|
+
|
|
91
|
+
/**
|
|
92
|
+
* Parse cost estimate options
|
|
93
|
+
*/
|
|
94
|
+
export function parseCostEstimateOptions(args: string[]): CostEstimateOptions {
|
|
95
|
+
const options: CostEstimateOptions = {};
|
|
96
|
+
|
|
97
|
+
for (let i = 0; i < args.length; i++) {
|
|
98
|
+
const arg = args[i];
|
|
99
|
+
if (arg === '--directory' && args[i + 1]) {
|
|
100
|
+
options.directory = args[++i];
|
|
101
|
+
} else if (arg === '-d' && args[i + 1]) {
|
|
102
|
+
options.directory = args[++i];
|
|
103
|
+
} else if (arg === '--format' && args[i + 1]) {
|
|
104
|
+
options.format = args[++i] as 'table' | 'json' | 'html';
|
|
105
|
+
} else if (arg === '--detailed') {
|
|
106
|
+
options.detailed = true;
|
|
107
|
+
} else if (arg === '--compare' && args[i + 1]) {
|
|
108
|
+
options.compare = args[++i];
|
|
109
|
+
} else if (!arg.startsWith('-') && !options.directory) {
|
|
110
|
+
options.directory = arg;
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
return options;
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
/**
|
|
118
|
+
* Parse cost history options
|
|
119
|
+
*/
|
|
120
|
+
export function parseCostHistoryOptions(args: string[]): CostHistoryOptions {
|
|
121
|
+
const options: CostHistoryOptions = {
|
|
122
|
+
days: 30,
|
|
123
|
+
groupBy: 'service',
|
|
124
|
+
};
|
|
125
|
+
|
|
126
|
+
for (let i = 0; i < args.length; i++) {
|
|
127
|
+
const arg = args[i];
|
|
128
|
+
if (arg === '--days' && args[i + 1]) {
|
|
129
|
+
options.days = parseInt(args[++i], 10);
|
|
130
|
+
} else if (arg === '--group-by' && args[i + 1]) {
|
|
131
|
+
options.groupBy = args[++i] as 'resource' | 'service' | 'tag';
|
|
132
|
+
} else if (arg === '--provider' && args[i + 1]) {
|
|
133
|
+
options.provider = args[++i] as 'aws' | 'gcp' | 'azure';
|
|
134
|
+
} else if (arg === '--format' && args[i + 1]) {
|
|
135
|
+
options.format = args[++i] as 'table' | 'json';
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
return options;
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
// ==========================================
|
|
143
|
+
// Helpers
|
|
144
|
+
// ==========================================
|
|
145
|
+
|
|
146
|
+
/**
|
|
147
|
+
* Check if infracost is installed
|
|
148
|
+
*/
|
|
149
|
+
function checkInfracost(): boolean {
|
|
150
|
+
try {
|
|
151
|
+
execSync('infracost --version', { stdio: 'pipe' });
|
|
152
|
+
return true;
|
|
153
|
+
} catch {
|
|
154
|
+
return false;
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
/**
|
|
159
|
+
* Run infracost breakdown
|
|
160
|
+
*/
|
|
161
|
+
function runInfracostBreakdown(directory: string): CostEstimate | null {
|
|
162
|
+
try {
|
|
163
|
+
const result = execSync(`infracost breakdown --path "${directory}" --format json`, {
|
|
164
|
+
stdio: 'pipe',
|
|
165
|
+
encoding: 'utf-8',
|
|
166
|
+
maxBuffer: 10 * 1024 * 1024,
|
|
167
|
+
});
|
|
168
|
+
return JSON.parse(result) as CostEstimate;
|
|
169
|
+
} catch (error) {
|
|
170
|
+
return null;
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
/**
|
|
175
|
+
* Format currency
|
|
176
|
+
*/
|
|
177
|
+
function formatCurrency(amount: number, currency: string = 'USD'): string {
|
|
178
|
+
return new Intl.NumberFormat('en-US', {
|
|
179
|
+
style: 'currency',
|
|
180
|
+
currency,
|
|
181
|
+
minimumFractionDigits: 2,
|
|
182
|
+
maximumFractionDigits: 2,
|
|
183
|
+
}).format(amount);
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
/**
|
|
187
|
+
* Format change with color
|
|
188
|
+
*/
|
|
189
|
+
function formatChange(amount: number, currency: string = 'USD'): string {
|
|
190
|
+
if (amount === 0) {
|
|
191
|
+
return ui.color('$0.00', 'dim');
|
|
192
|
+
} else if (amount > 0) {
|
|
193
|
+
return ui.color(`+${formatCurrency(amount, currency)}`, 'red');
|
|
194
|
+
} else {
|
|
195
|
+
return ui.color(formatCurrency(amount, currency), 'green');
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
/**
|
|
200
|
+
* Get mock cost history (in real implementation, would query cloud provider)
|
|
201
|
+
*/
|
|
202
|
+
function getMockCostHistory(options: CostHistoryOptions): CostHistoryEntry[] {
|
|
203
|
+
const entries: CostHistoryEntry[] = [];
|
|
204
|
+
const now = new Date();
|
|
205
|
+
const days = options.days || 30;
|
|
206
|
+
|
|
207
|
+
// Generate mock data for demonstration
|
|
208
|
+
const services = ['EC2', 'RDS', 'S3', 'Lambda', 'CloudWatch'];
|
|
209
|
+
|
|
210
|
+
for (let i = days - 1; i >= 0; i--) {
|
|
211
|
+
const date = new Date(now);
|
|
212
|
+
date.setDate(date.getDate() - i);
|
|
213
|
+
const dateStr = date.toISOString().split('T')[0];
|
|
214
|
+
|
|
215
|
+
for (const service of services) {
|
|
216
|
+
const baseCost =
|
|
217
|
+
{
|
|
218
|
+
EC2: 45,
|
|
219
|
+
RDS: 35,
|
|
220
|
+
S3: 5,
|
|
221
|
+
Lambda: 10,
|
|
222
|
+
CloudWatch: 3,
|
|
223
|
+
}[service] || 10;
|
|
224
|
+
|
|
225
|
+
// Add some variance
|
|
226
|
+
const variance = (Math.random() - 0.5) * baseCost * 0.2;
|
|
227
|
+
const cost = baseCost + variance;
|
|
228
|
+
|
|
229
|
+
entries.push({
|
|
230
|
+
date: dateStr,
|
|
231
|
+
service,
|
|
232
|
+
cost: parseFloat(cost.toFixed(2)),
|
|
233
|
+
change: parseFloat(variance.toFixed(2)),
|
|
234
|
+
});
|
|
235
|
+
}
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
return entries;
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
// ==========================================
|
|
242
|
+
// Display Functions
|
|
243
|
+
// ==========================================
|
|
244
|
+
|
|
245
|
+
/**
|
|
246
|
+
* Display cost estimate
|
|
247
|
+
*/
|
|
248
|
+
function displayCostEstimate(estimate: CostEstimate, detailed: boolean = false): void {
|
|
249
|
+
ui.newLine();
|
|
250
|
+
ui.section('Cost Estimate');
|
|
251
|
+
|
|
252
|
+
ui.print(` ${ui.dim('Generated:')} ${new Date(estimate.timeGenerated).toLocaleString()}`);
|
|
253
|
+
ui.print(` ${ui.dim('Currency:')} ${estimate.currency}`);
|
|
254
|
+
ui.newLine();
|
|
255
|
+
|
|
256
|
+
// Summary
|
|
257
|
+
ui.print(
|
|
258
|
+
` ${ui.bold('Monthly Cost:')} ${ui.color(formatCurrency(estimate.totalMonthlyCost), 'cyan')}`
|
|
259
|
+
);
|
|
260
|
+
ui.print(` ${ui.bold('Hourly Cost:')} ${formatCurrency(estimate.totalHourlyCost)}`);
|
|
261
|
+
|
|
262
|
+
if (estimate.diffTotalMonthlyCost !== 0) {
|
|
263
|
+
ui.print(` ${ui.bold('Change:')} ${formatChange(estimate.diffTotalMonthlyCost)}`);
|
|
264
|
+
}
|
|
265
|
+
ui.newLine();
|
|
266
|
+
|
|
267
|
+
// Resource summary
|
|
268
|
+
ui.print(` ${ui.dim('Resources detected:')} ${estimate.summary.totalDetectedResources}`);
|
|
269
|
+
ui.print(` ${ui.dim('Resources supported:')} ${estimate.summary.totalSupportedResources}`);
|
|
270
|
+
ui.print(` ${ui.dim('Resources unsupported:')} ${estimate.summary.totalUnsupportedResources}`);
|
|
271
|
+
ui.newLine();
|
|
272
|
+
|
|
273
|
+
// Project breakdown
|
|
274
|
+
for (const project of estimate.projects) {
|
|
275
|
+
ui.section(`Project: ${project.name}`);
|
|
276
|
+
|
|
277
|
+
ui.print(` ${ui.dim('Monthly:')} ${formatCurrency(project.totalMonthlyCost)}`);
|
|
278
|
+
|
|
279
|
+
if (project.diffTotalMonthlyCost !== 0) {
|
|
280
|
+
ui.print(` ${ui.dim('Change:')} ${formatChange(project.diffTotalMonthlyCost)}`);
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
if (detailed && project.resources.length > 0) {
|
|
284
|
+
ui.newLine();
|
|
285
|
+
ui.print(' Resources:');
|
|
286
|
+
|
|
287
|
+
// Group by type
|
|
288
|
+
const byType: Record<string, CostResource[]> = {};
|
|
289
|
+
for (const resource of project.resources) {
|
|
290
|
+
const type = resource.resourceType;
|
|
291
|
+
if (!byType[type]) {
|
|
292
|
+
byType[type] = [];
|
|
293
|
+
}
|
|
294
|
+
byType[type].push(resource);
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
for (const [type, resources] of Object.entries(byType)) {
|
|
298
|
+
const typeCost = resources.reduce((sum, r) => sum + r.monthlyCost, 0);
|
|
299
|
+
ui.newLine();
|
|
300
|
+
ui.print(` ${ui.bold(type)} (${resources.length}) - ${formatCurrency(typeCost)}/mo`);
|
|
301
|
+
|
|
302
|
+
for (const resource of resources.slice(0, 5)) {
|
|
303
|
+
const cost = formatCurrency(resource.monthlyCost);
|
|
304
|
+
ui.print(` ${resource.name}: ${cost}/mo`);
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
if (resources.length > 5) {
|
|
308
|
+
ui.print(ui.dim(` ... and ${resources.length - 5} more`));
|
|
309
|
+
}
|
|
310
|
+
}
|
|
311
|
+
}
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
// Unsupported resources warning
|
|
315
|
+
if (Object.keys(estimate.summary.unsupportedResourceCounts).length > 0) {
|
|
316
|
+
ui.newLine();
|
|
317
|
+
ui.warning('Some resources could not be priced:');
|
|
318
|
+
for (const [type, count] of Object.entries(estimate.summary.unsupportedResourceCounts)) {
|
|
319
|
+
ui.print(` ${ui.dim('•')} ${type}: ${count}`);
|
|
320
|
+
}
|
|
321
|
+
}
|
|
322
|
+
}
|
|
323
|
+
|
|
324
|
+
/**
|
|
325
|
+
* Display cost history
|
|
326
|
+
*/
|
|
327
|
+
function displayCostHistory(entries: CostHistoryEntry[], groupBy: string): void {
|
|
328
|
+
ui.newLine();
|
|
329
|
+
ui.section('Cost History');
|
|
330
|
+
|
|
331
|
+
if (entries.length === 0) {
|
|
332
|
+
ui.info('No cost data available for the specified period.');
|
|
333
|
+
return;
|
|
334
|
+
}
|
|
335
|
+
|
|
336
|
+
// Group entries by the specified field
|
|
337
|
+
const groups: Record<string, CostHistoryEntry[]> = {};
|
|
338
|
+
|
|
339
|
+
for (const entry of entries) {
|
|
340
|
+
const key = groupBy === 'service' ? entry.service : entry.resource || entry.service;
|
|
341
|
+
if (!groups[key]) {
|
|
342
|
+
groups[key] = [];
|
|
343
|
+
}
|
|
344
|
+
groups[key].push(entry);
|
|
345
|
+
}
|
|
346
|
+
|
|
347
|
+
// Calculate totals per group
|
|
348
|
+
const totals: { group: string; total: number; avg: number; trend: number }[] = [];
|
|
349
|
+
|
|
350
|
+
for (const [group, groupEntries] of Object.entries(groups)) {
|
|
351
|
+
const total = groupEntries.reduce((sum, e) => sum + e.cost, 0);
|
|
352
|
+
const avg = total / groupEntries.length;
|
|
353
|
+
|
|
354
|
+
// Calculate trend (last 7 days vs previous 7 days)
|
|
355
|
+
const recent = groupEntries.slice(-7).reduce((sum, e) => sum + e.cost, 0);
|
|
356
|
+
const previous = groupEntries.slice(-14, -7).reduce((sum, e) => sum + e.cost, 0);
|
|
357
|
+
const trend = previous > 0 ? ((recent - previous) / previous) * 100 : 0;
|
|
358
|
+
|
|
359
|
+
totals.push({ group, total, avg, trend });
|
|
360
|
+
}
|
|
361
|
+
|
|
362
|
+
// Sort by total cost
|
|
363
|
+
totals.sort((a, b) => b.total - a.total);
|
|
364
|
+
|
|
365
|
+
// Display table
|
|
366
|
+
const grandTotal = totals.reduce((sum, t) => sum + t.total, 0);
|
|
367
|
+
|
|
368
|
+
ui.print(` ${ui.dim('Total period cost:')} ${ui.color(formatCurrency(grandTotal), 'cyan')}`);
|
|
369
|
+
ui.newLine();
|
|
370
|
+
|
|
371
|
+
// Table header
|
|
372
|
+
const colWidths = { group: 20, total: 15, avg: 15, trend: 15 };
|
|
373
|
+
const header = [
|
|
374
|
+
groupBy.charAt(0).toUpperCase() + groupBy.slice(1).padEnd(colWidths.group),
|
|
375
|
+
'Total'.padStart(colWidths.total),
|
|
376
|
+
'Daily Avg'.padStart(colWidths.avg),
|
|
377
|
+
'Trend (7d)'.padStart(colWidths.trend),
|
|
378
|
+
].join(' ');
|
|
379
|
+
|
|
380
|
+
ui.print(` ${ui.dim(header)}`);
|
|
381
|
+
ui.print(` ${ui.dim('-'.repeat(header.length))}`);
|
|
382
|
+
|
|
383
|
+
for (const { group, total, avg, trend } of totals.slice(0, 10)) {
|
|
384
|
+
const trendStr =
|
|
385
|
+
trend > 0
|
|
386
|
+
? ui.color(`+${trend.toFixed(1)}%`, 'red')
|
|
387
|
+
: trend < 0
|
|
388
|
+
? ui.color(`${trend.toFixed(1)}%`, 'green')
|
|
389
|
+
: ui.color('0.0%', 'dim');
|
|
390
|
+
|
|
391
|
+
const row = [
|
|
392
|
+
group.substring(0, colWidths.group).padEnd(colWidths.group),
|
|
393
|
+
formatCurrency(total).padStart(colWidths.total),
|
|
394
|
+
formatCurrency(avg).padStart(colWidths.avg),
|
|
395
|
+
trendStr.padStart(colWidths.trend + 10), // Extra for ANSI codes
|
|
396
|
+
].join(' ');
|
|
397
|
+
|
|
398
|
+
ui.print(` ${row}`);
|
|
399
|
+
}
|
|
400
|
+
|
|
401
|
+
if (totals.length > 10) {
|
|
402
|
+
ui.newLine();
|
|
403
|
+
ui.print(ui.dim(` ... and ${totals.length - 10} more ${groupBy}s`));
|
|
404
|
+
}
|
|
405
|
+
}
|
|
406
|
+
|
|
407
|
+
// ==========================================
|
|
408
|
+
// Commands
|
|
409
|
+
// ==========================================
|
|
410
|
+
|
|
411
|
+
/**
|
|
412
|
+
* Cost parent command
|
|
413
|
+
*/
|
|
414
|
+
export async function costCommand(args: string[]): Promise<void> {
|
|
415
|
+
if (args.length === 0) {
|
|
416
|
+
ui.header('Nimbus Cost', 'Infrastructure cost estimation and tracking');
|
|
417
|
+
ui.newLine();
|
|
418
|
+
ui.print('Usage: nimbus cost <command> [options]');
|
|
419
|
+
ui.newLine();
|
|
420
|
+
ui.print('Commands:');
|
|
421
|
+
ui.print(` ${ui.bold('estimate')} Estimate infrastructure costs from Terraform`);
|
|
422
|
+
ui.print(` ${ui.bold('history')} View historical cost data`);
|
|
423
|
+
ui.newLine();
|
|
424
|
+
ui.print('Examples:');
|
|
425
|
+
ui.print(' nimbus cost estimate');
|
|
426
|
+
ui.print(' nimbus cost estimate -d ./terraform --detailed');
|
|
427
|
+
ui.print(' nimbus cost history --days 30 --group-by service');
|
|
428
|
+
return;
|
|
429
|
+
}
|
|
430
|
+
|
|
431
|
+
const subcommand = args[0];
|
|
432
|
+
const subArgs = args.slice(1);
|
|
433
|
+
|
|
434
|
+
switch (subcommand) {
|
|
435
|
+
case 'estimate':
|
|
436
|
+
await costEstimateCommand(parseCostEstimateOptions(subArgs));
|
|
437
|
+
break;
|
|
438
|
+
case 'history':
|
|
439
|
+
await costHistoryCommand(parseCostHistoryOptions(subArgs));
|
|
440
|
+
break;
|
|
441
|
+
default:
|
|
442
|
+
ui.error(`Unknown cost command: ${subcommand}`);
|
|
443
|
+
ui.info('Run "nimbus cost" for usage');
|
|
444
|
+
}
|
|
445
|
+
}
|
|
446
|
+
|
|
447
|
+
/**
|
|
448
|
+
* Cost estimate command
|
|
449
|
+
*/
|
|
450
|
+
export async function costEstimateCommand(options: CostEstimateOptions): Promise<void> {
|
|
451
|
+
const directory = options.directory || process.cwd();
|
|
452
|
+
|
|
453
|
+
ui.header('Nimbus Cost Estimate', directory);
|
|
454
|
+
|
|
455
|
+
// Check for Terraform files
|
|
456
|
+
const hasTerraform =
|
|
457
|
+
fs.existsSync(path.join(directory, 'main.tf')) ||
|
|
458
|
+
fs.existsSync(path.join(directory, 'terraform.tf')) ||
|
|
459
|
+
fs.readdirSync(directory).some(f => f.endsWith('.tf'));
|
|
460
|
+
|
|
461
|
+
if (!hasTerraform) {
|
|
462
|
+
ui.warning('No Terraform files found in the specified directory.');
|
|
463
|
+
ui.info('Cost estimation requires Terraform configuration files.');
|
|
464
|
+
return;
|
|
465
|
+
}
|
|
466
|
+
|
|
467
|
+
// Check for infracost
|
|
468
|
+
if (!checkInfracost()) {
|
|
469
|
+
ui.info('Infracost is not installed. Using built-in cost estimator.');
|
|
470
|
+
ui.newLine();
|
|
471
|
+
|
|
472
|
+
ui.startSpinner({ message: 'Running built-in cost estimation...' });
|
|
473
|
+
|
|
474
|
+
try {
|
|
475
|
+
const result = await CostEstimator.estimateDirectory(directory);
|
|
476
|
+
ui.stopSpinnerSuccess('Cost estimation complete');
|
|
477
|
+
|
|
478
|
+
if (options.format === 'json') {
|
|
479
|
+
console.log(JSON.stringify(result, null, 2));
|
|
480
|
+
return;
|
|
481
|
+
}
|
|
482
|
+
|
|
483
|
+
displayCostEstimate(result, options.detailed || false);
|
|
484
|
+
ui.newLine();
|
|
485
|
+
ui.info('For more accurate pricing, install Infracost:');
|
|
486
|
+
ui.print(` ${ui.dim('brew install infracost')} (macOS)`);
|
|
487
|
+
ui.print(
|
|
488
|
+
` ${ui.dim('curl -fsSL https://raw.githubusercontent.com/infracost/infracost/master/scripts/install.sh | sh')} (Linux)`
|
|
489
|
+
);
|
|
490
|
+
} catch (error) {
|
|
491
|
+
ui.stopSpinnerFail('Cost estimation failed');
|
|
492
|
+
ui.error(
|
|
493
|
+
`Built-in estimator error: ${error instanceof Error ? error.message : String(error)}`
|
|
494
|
+
);
|
|
495
|
+
}
|
|
496
|
+
|
|
497
|
+
return;
|
|
498
|
+
}
|
|
499
|
+
|
|
500
|
+
ui.startSpinner({ message: 'Running cost estimation...' });
|
|
501
|
+
|
|
502
|
+
const estimate = runInfracostBreakdown(directory);
|
|
503
|
+
|
|
504
|
+
if (!estimate) {
|
|
505
|
+
ui.stopSpinnerFail('Cost estimation failed');
|
|
506
|
+
ui.error(
|
|
507
|
+
'Failed to run infracost. Make sure you have authenticated with "infracost auth login"'
|
|
508
|
+
);
|
|
509
|
+
return;
|
|
510
|
+
}
|
|
511
|
+
|
|
512
|
+
ui.stopSpinnerSuccess('Cost estimation complete');
|
|
513
|
+
|
|
514
|
+
if (options.format === 'json') {
|
|
515
|
+
console.log(JSON.stringify(estimate, null, 2));
|
|
516
|
+
return;
|
|
517
|
+
}
|
|
518
|
+
|
|
519
|
+
displayCostEstimate(estimate, options.detailed || false);
|
|
520
|
+
}
|
|
521
|
+
|
|
522
|
+
/**
|
|
523
|
+
* Cost history command
|
|
524
|
+
*/
|
|
525
|
+
export async function costHistoryCommand(options: CostHistoryOptions): Promise<void> {
|
|
526
|
+
ui.header('Nimbus Cost History', `Last ${options.days} days`);
|
|
527
|
+
|
|
528
|
+
// In a real implementation, this would query cloud provider cost APIs
|
|
529
|
+
// For now, we'll use mock data or show instructions
|
|
530
|
+
|
|
531
|
+
if (!options.provider) {
|
|
532
|
+
const providerChoice = await select({
|
|
533
|
+
message: 'Select cloud provider:',
|
|
534
|
+
options: [
|
|
535
|
+
{ label: 'AWS', value: 'aws', description: 'Amazon Web Services Cost Explorer' },
|
|
536
|
+
{ label: 'GCP', value: 'gcp', description: 'Google Cloud Billing' },
|
|
537
|
+
{ label: 'Azure', value: 'azure', description: 'Azure Cost Management' },
|
|
538
|
+
{ label: 'Demo', value: 'demo', description: 'Show demo data' },
|
|
539
|
+
],
|
|
540
|
+
});
|
|
541
|
+
options.provider = providerChoice as any;
|
|
542
|
+
}
|
|
543
|
+
|
|
544
|
+
if (options.provider === ('demo' as any)) {
|
|
545
|
+
ui.startSpinner({ message: 'Loading cost history...' });
|
|
546
|
+
|
|
547
|
+
// Simulate API call delay
|
|
548
|
+
await new Promise(resolve => setTimeout(resolve, 1000));
|
|
549
|
+
|
|
550
|
+
const entries = getMockCostHistory(options);
|
|
551
|
+
ui.stopSpinnerSuccess(`Loaded ${entries.length} entries`);
|
|
552
|
+
|
|
553
|
+
if (options.format === 'json') {
|
|
554
|
+
console.log(JSON.stringify(entries, null, 2));
|
|
555
|
+
return;
|
|
556
|
+
}
|
|
557
|
+
|
|
558
|
+
displayCostHistory(entries, options.groupBy || 'service');
|
|
559
|
+
ui.newLine();
|
|
560
|
+
ui.warning('This is demo data. Connect to a real cloud provider for actual costs.');
|
|
561
|
+
return;
|
|
562
|
+
}
|
|
563
|
+
|
|
564
|
+
// Real provider - show instructions
|
|
565
|
+
ui.newLine();
|
|
566
|
+
ui.info(
|
|
567
|
+
`To view ${options.provider?.toUpperCase()} cost history, you need to configure credentials.`
|
|
568
|
+
);
|
|
569
|
+
ui.newLine();
|
|
570
|
+
|
|
571
|
+
switch (options.provider) {
|
|
572
|
+
case 'aws':
|
|
573
|
+
ui.print('AWS Cost Explorer API requires:');
|
|
574
|
+
ui.print(' 1. AWS credentials configured (aws configure)');
|
|
575
|
+
ui.print(' 2. Cost Explorer enabled in your AWS account');
|
|
576
|
+
ui.print(' 3. IAM permissions for ce:GetCostAndUsage');
|
|
577
|
+
break;
|
|
578
|
+
case 'gcp':
|
|
579
|
+
ui.print('GCP Billing API requires:');
|
|
580
|
+
ui.print(' 1. GCP credentials configured (gcloud auth)');
|
|
581
|
+
ui.print(' 2. Billing export enabled to BigQuery');
|
|
582
|
+
ui.print(' 3. IAM permissions for bigquery.jobs.create');
|
|
583
|
+
break;
|
|
584
|
+
case 'azure':
|
|
585
|
+
ui.print('Azure Cost Management API requires:');
|
|
586
|
+
ui.print(' 1. Azure CLI authenticated (az login)');
|
|
587
|
+
ui.print(' 2. Reader role on the subscription');
|
|
588
|
+
ui.print(' 3. Cost Management permissions');
|
|
589
|
+
break;
|
|
590
|
+
}
|
|
591
|
+
|
|
592
|
+
ui.newLine();
|
|
593
|
+
ui.print('Run with --provider demo to see sample data.');
|
|
594
|
+
}
|