@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.
Files changed (313) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +628 -0
  3. package/bin/nimbus +38 -0
  4. package/package.json +80 -0
  5. package/src/__tests__/app.test.ts +76 -0
  6. package/src/__tests__/audit.test.ts +877 -0
  7. package/src/__tests__/circuit-breaker.test.ts +116 -0
  8. package/src/__tests__/cli-run.test.ts +115 -0
  9. package/src/__tests__/context-manager.test.ts +502 -0
  10. package/src/__tests__/context.test.ts +242 -0
  11. package/src/__tests__/enterprise.test.ts +401 -0
  12. package/src/__tests__/generator.test.ts +433 -0
  13. package/src/__tests__/hooks.test.ts +582 -0
  14. package/src/__tests__/init.test.ts +436 -0
  15. package/src/__tests__/intent-parser.test.ts +229 -0
  16. package/src/__tests__/llm-router.test.ts +209 -0
  17. package/src/__tests__/lsp.test.ts +293 -0
  18. package/src/__tests__/modes.test.ts +336 -0
  19. package/src/__tests__/permissions.test.ts +338 -0
  20. package/src/__tests__/serve.test.ts +275 -0
  21. package/src/__tests__/sessions.test.ts +227 -0
  22. package/src/__tests__/sharing.test.ts +288 -0
  23. package/src/__tests__/snapshots.test.ts +581 -0
  24. package/src/__tests__/state-db.test.ts +334 -0
  25. package/src/__tests__/stream-with-tools.test.ts +732 -0
  26. package/src/__tests__/subagents.test.ts +176 -0
  27. package/src/__tests__/system-prompt.test.ts +169 -0
  28. package/src/__tests__/tool-converter.test.ts +256 -0
  29. package/src/__tests__/tool-schemas.test.ts +397 -0
  30. package/src/__tests__/tools.test.ts +143 -0
  31. package/src/__tests__/version.test.ts +49 -0
  32. package/src/agent/compaction-agent.ts +227 -0
  33. package/src/agent/context-manager.ts +435 -0
  34. package/src/agent/context.ts +427 -0
  35. package/src/agent/deploy-preview.ts +426 -0
  36. package/src/agent/index.ts +68 -0
  37. package/src/agent/loop.ts +717 -0
  38. package/src/agent/modes.ts +429 -0
  39. package/src/agent/permissions.ts +466 -0
  40. package/src/agent/subagents/base.ts +116 -0
  41. package/src/agent/subagents/cost.ts +51 -0
  42. package/src/agent/subagents/explore.ts +42 -0
  43. package/src/agent/subagents/general.ts +54 -0
  44. package/src/agent/subagents/index.ts +102 -0
  45. package/src/agent/subagents/infra.ts +59 -0
  46. package/src/agent/subagents/security.ts +69 -0
  47. package/src/agent/system-prompt.ts +436 -0
  48. package/src/app.ts +122 -0
  49. package/src/audit/activity-log.ts +290 -0
  50. package/src/audit/compliance-checker.ts +540 -0
  51. package/src/audit/cost-tracker.ts +318 -0
  52. package/src/audit/index.ts +23 -0
  53. package/src/audit/security-scanner.ts +596 -0
  54. package/src/auth/guard.ts +75 -0
  55. package/src/auth/index.ts +56 -0
  56. package/src/auth/oauth.ts +455 -0
  57. package/src/auth/providers.ts +470 -0
  58. package/src/auth/sso.ts +113 -0
  59. package/src/auth/store.ts +505 -0
  60. package/src/auth/types.ts +187 -0
  61. package/src/build.ts +141 -0
  62. package/src/cli/index.ts +16 -0
  63. package/src/cli/init.ts +854 -0
  64. package/src/cli/openapi-spec.ts +356 -0
  65. package/src/cli/run.ts +237 -0
  66. package/src/cli/serve-auth.ts +80 -0
  67. package/src/cli/serve.ts +462 -0
  68. package/src/cli/web.ts +67 -0
  69. package/src/cli.ts +1417 -0
  70. package/src/clients/core-engine-client.ts +227 -0
  71. package/src/clients/enterprise-client.ts +334 -0
  72. package/src/clients/generator-client.ts +351 -0
  73. package/src/clients/git-client.ts +627 -0
  74. package/src/clients/github-client.ts +410 -0
  75. package/src/clients/helm-client.ts +504 -0
  76. package/src/clients/index.ts +80 -0
  77. package/src/clients/k8s-client.ts +497 -0
  78. package/src/clients/llm-client.ts +161 -0
  79. package/src/clients/rest-client.ts +130 -0
  80. package/src/clients/service-discovery.ts +33 -0
  81. package/src/clients/terraform-client.ts +482 -0
  82. package/src/clients/tools-client.ts +1843 -0
  83. package/src/clients/ws-client.ts +115 -0
  84. package/src/commands/analyze/index.ts +352 -0
  85. package/src/commands/apply/helm.ts +473 -0
  86. package/src/commands/apply/index.ts +213 -0
  87. package/src/commands/apply/k8s.ts +454 -0
  88. package/src/commands/apply/terraform.ts +582 -0
  89. package/src/commands/ask.ts +167 -0
  90. package/src/commands/audit/index.ts +238 -0
  91. package/src/commands/auth-cloud.ts +294 -0
  92. package/src/commands/auth-list.ts +134 -0
  93. package/src/commands/auth-profile.ts +121 -0
  94. package/src/commands/auth-status.ts +141 -0
  95. package/src/commands/aws/ec2.ts +501 -0
  96. package/src/commands/aws/iam.ts +397 -0
  97. package/src/commands/aws/index.ts +133 -0
  98. package/src/commands/aws/lambda.ts +396 -0
  99. package/src/commands/aws/rds.ts +439 -0
  100. package/src/commands/aws/s3.ts +439 -0
  101. package/src/commands/aws/vpc.ts +393 -0
  102. package/src/commands/aws-discover.ts +649 -0
  103. package/src/commands/aws-terraform.ts +805 -0
  104. package/src/commands/azure/aks.ts +376 -0
  105. package/src/commands/azure/functions.ts +253 -0
  106. package/src/commands/azure/index.ts +116 -0
  107. package/src/commands/azure/storage.ts +478 -0
  108. package/src/commands/azure/vm.ts +355 -0
  109. package/src/commands/billing/index.ts +256 -0
  110. package/src/commands/chat.ts +314 -0
  111. package/src/commands/config.ts +346 -0
  112. package/src/commands/cost/cloud-cost-estimator.ts +266 -0
  113. package/src/commands/cost/estimator.ts +79 -0
  114. package/src/commands/cost/index.ts +594 -0
  115. package/src/commands/cost/parsers/terraform.ts +273 -0
  116. package/src/commands/cost/parsers/types.ts +25 -0
  117. package/src/commands/cost/pricing/aws.ts +544 -0
  118. package/src/commands/cost/pricing/azure.ts +499 -0
  119. package/src/commands/cost/pricing/gcp.ts +396 -0
  120. package/src/commands/cost/pricing/index.ts +40 -0
  121. package/src/commands/demo.ts +250 -0
  122. package/src/commands/doctor.ts +794 -0
  123. package/src/commands/drift/index.ts +439 -0
  124. package/src/commands/explain.ts +277 -0
  125. package/src/commands/feedback.ts +389 -0
  126. package/src/commands/fix.ts +324 -0
  127. package/src/commands/fs/index.ts +402 -0
  128. package/src/commands/gcp/compute.ts +325 -0
  129. package/src/commands/gcp/functions.ts +271 -0
  130. package/src/commands/gcp/gke.ts +438 -0
  131. package/src/commands/gcp/iam.ts +344 -0
  132. package/src/commands/gcp/index.ts +129 -0
  133. package/src/commands/gcp/storage.ts +284 -0
  134. package/src/commands/generate-helm.ts +1249 -0
  135. package/src/commands/generate-k8s.ts +1560 -0
  136. package/src/commands/generate-terraform.ts +1460 -0
  137. package/src/commands/gh/index.ts +863 -0
  138. package/src/commands/git/index.ts +1343 -0
  139. package/src/commands/helm/index.ts +1126 -0
  140. package/src/commands/help.ts +539 -0
  141. package/src/commands/history.ts +142 -0
  142. package/src/commands/import.ts +868 -0
  143. package/src/commands/index.ts +367 -0
  144. package/src/commands/init.ts +1046 -0
  145. package/src/commands/k8s/index.ts +1137 -0
  146. package/src/commands/login.ts +631 -0
  147. package/src/commands/logout.ts +83 -0
  148. package/src/commands/onboarding.ts +228 -0
  149. package/src/commands/plan/display.ts +279 -0
  150. package/src/commands/plan/index.ts +599 -0
  151. package/src/commands/preview.ts +452 -0
  152. package/src/commands/questionnaire.ts +1270 -0
  153. package/src/commands/resume.ts +55 -0
  154. package/src/commands/team/index.ts +346 -0
  155. package/src/commands/template.ts +232 -0
  156. package/src/commands/tf/index.ts +1034 -0
  157. package/src/commands/upgrade.ts +550 -0
  158. package/src/commands/usage/index.ts +134 -0
  159. package/src/commands/version.ts +170 -0
  160. package/src/compat/index.ts +2 -0
  161. package/src/compat/runtime.ts +12 -0
  162. package/src/compat/sqlite.ts +107 -0
  163. package/src/config/index.ts +17 -0
  164. package/src/config/manager.ts +530 -0
  165. package/src/config/safety-policy.ts +358 -0
  166. package/src/config/schema.ts +125 -0
  167. package/src/config/types.ts +527 -0
  168. package/src/context/context-db.ts +199 -0
  169. package/src/demo/index.ts +349 -0
  170. package/src/demo/scenarios/full-journey.ts +229 -0
  171. package/src/demo/scenarios/getting-started.ts +127 -0
  172. package/src/demo/scenarios/helm-release.ts +341 -0
  173. package/src/demo/scenarios/k8s-deployment.ts +194 -0
  174. package/src/demo/scenarios/terraform-vpc.ts +170 -0
  175. package/src/demo/types.ts +92 -0
  176. package/src/engine/cost-estimator.ts +438 -0
  177. package/src/engine/diagram-generator.ts +256 -0
  178. package/src/engine/drift-detector.ts +902 -0
  179. package/src/engine/executor.ts +1035 -0
  180. package/src/engine/index.ts +76 -0
  181. package/src/engine/orchestrator.ts +636 -0
  182. package/src/engine/planner.ts +720 -0
  183. package/src/engine/safety.ts +743 -0
  184. package/src/engine/verifier.ts +770 -0
  185. package/src/enterprise/audit.ts +348 -0
  186. package/src/enterprise/auth.ts +270 -0
  187. package/src/enterprise/billing.ts +822 -0
  188. package/src/enterprise/index.ts +17 -0
  189. package/src/enterprise/teams.ts +443 -0
  190. package/src/generator/best-practices.ts +1608 -0
  191. package/src/generator/helm.ts +630 -0
  192. package/src/generator/index.ts +37 -0
  193. package/src/generator/intent-parser.ts +514 -0
  194. package/src/generator/kubernetes.ts +976 -0
  195. package/src/generator/terraform.ts +1867 -0
  196. package/src/history/index.ts +8 -0
  197. package/src/history/manager.ts +322 -0
  198. package/src/history/types.ts +34 -0
  199. package/src/hooks/config.ts +432 -0
  200. package/src/hooks/engine.ts +391 -0
  201. package/src/hooks/index.ts +4 -0
  202. package/src/llm/auth-bridge.ts +198 -0
  203. package/src/llm/circuit-breaker.ts +140 -0
  204. package/src/llm/config-loader.ts +201 -0
  205. package/src/llm/cost-calculator.ts +171 -0
  206. package/src/llm/index.ts +8 -0
  207. package/src/llm/model-aliases.ts +115 -0
  208. package/src/llm/provider-registry.ts +63 -0
  209. package/src/llm/providers/anthropic.ts +433 -0
  210. package/src/llm/providers/bedrock.ts +477 -0
  211. package/src/llm/providers/google.ts +405 -0
  212. package/src/llm/providers/ollama.ts +767 -0
  213. package/src/llm/providers/openai-compatible.ts +340 -0
  214. package/src/llm/providers/openai.ts +328 -0
  215. package/src/llm/providers/openrouter.ts +338 -0
  216. package/src/llm/router.ts +1035 -0
  217. package/src/llm/types.ts +232 -0
  218. package/src/lsp/client.ts +298 -0
  219. package/src/lsp/languages.ts +116 -0
  220. package/src/lsp/manager.ts +278 -0
  221. package/src/mcp/client.ts +402 -0
  222. package/src/mcp/index.ts +5 -0
  223. package/src/mcp/manager.ts +133 -0
  224. package/src/nimbus.ts +214 -0
  225. package/src/plugins/index.ts +27 -0
  226. package/src/plugins/loader.ts +334 -0
  227. package/src/plugins/manager.ts +376 -0
  228. package/src/plugins/types.ts +284 -0
  229. package/src/scanners/cicd-scanner.ts +258 -0
  230. package/src/scanners/cloud-scanner.ts +466 -0
  231. package/src/scanners/framework-scanner.ts +469 -0
  232. package/src/scanners/iac-scanner.ts +388 -0
  233. package/src/scanners/index.ts +539 -0
  234. package/src/scanners/language-scanner.ts +276 -0
  235. package/src/scanners/package-manager-scanner.ts +277 -0
  236. package/src/scanners/types.ts +172 -0
  237. package/src/sessions/manager.ts +365 -0
  238. package/src/sessions/types.ts +44 -0
  239. package/src/sharing/sync.ts +296 -0
  240. package/src/sharing/viewer.ts +97 -0
  241. package/src/snapshots/index.ts +2 -0
  242. package/src/snapshots/manager.ts +530 -0
  243. package/src/state/artifacts.ts +147 -0
  244. package/src/state/audit.ts +137 -0
  245. package/src/state/billing.ts +240 -0
  246. package/src/state/checkpoints.ts +117 -0
  247. package/src/state/config.ts +67 -0
  248. package/src/state/conversations.ts +14 -0
  249. package/src/state/credentials.ts +154 -0
  250. package/src/state/db.ts +58 -0
  251. package/src/state/index.ts +26 -0
  252. package/src/state/messages.ts +115 -0
  253. package/src/state/projects.ts +123 -0
  254. package/src/state/schema.ts +236 -0
  255. package/src/state/sessions.ts +147 -0
  256. package/src/state/teams.ts +200 -0
  257. package/src/telemetry.ts +108 -0
  258. package/src/tools/aws-ops.ts +952 -0
  259. package/src/tools/azure-ops.ts +579 -0
  260. package/src/tools/file-ops.ts +593 -0
  261. package/src/tools/gcp-ops.ts +625 -0
  262. package/src/tools/git-ops.ts +773 -0
  263. package/src/tools/github-ops.ts +799 -0
  264. package/src/tools/helm-ops.ts +943 -0
  265. package/src/tools/index.ts +17 -0
  266. package/src/tools/k8s-ops.ts +819 -0
  267. package/src/tools/schemas/converter.ts +184 -0
  268. package/src/tools/schemas/devops.ts +612 -0
  269. package/src/tools/schemas/index.ts +73 -0
  270. package/src/tools/schemas/standard.ts +1144 -0
  271. package/src/tools/schemas/types.ts +705 -0
  272. package/src/tools/terraform-ops.ts +862 -0
  273. package/src/types/ambient.d.ts +193 -0
  274. package/src/types/config.ts +83 -0
  275. package/src/types/drift.ts +116 -0
  276. package/src/types/enterprise.ts +335 -0
  277. package/src/types/index.ts +20 -0
  278. package/src/types/plan.ts +44 -0
  279. package/src/types/request.ts +65 -0
  280. package/src/types/response.ts +54 -0
  281. package/src/types/service.ts +51 -0
  282. package/src/ui/App.tsx +997 -0
  283. package/src/ui/DeployPreview.tsx +169 -0
  284. package/src/ui/Header.tsx +68 -0
  285. package/src/ui/InputBox.tsx +350 -0
  286. package/src/ui/MessageList.tsx +585 -0
  287. package/src/ui/PermissionPrompt.tsx +151 -0
  288. package/src/ui/StatusBar.tsx +158 -0
  289. package/src/ui/ToolCallDisplay.tsx +409 -0
  290. package/src/ui/chat-ui.ts +853 -0
  291. package/src/ui/index.ts +33 -0
  292. package/src/ui/ink/index.ts +711 -0
  293. package/src/ui/streaming.ts +176 -0
  294. package/src/ui/types.ts +57 -0
  295. package/src/utils/analytics.ts +72 -0
  296. package/src/utils/cost-warning.ts +27 -0
  297. package/src/utils/env.ts +46 -0
  298. package/src/utils/errors.ts +69 -0
  299. package/src/utils/event-bus.ts +38 -0
  300. package/src/utils/index.ts +24 -0
  301. package/src/utils/logger.ts +171 -0
  302. package/src/utils/rate-limiter.ts +121 -0
  303. package/src/utils/service-auth.ts +49 -0
  304. package/src/utils/validation.ts +53 -0
  305. package/src/version.ts +4 -0
  306. package/src/watcher/index.ts +163 -0
  307. package/src/wizard/approval.ts +383 -0
  308. package/src/wizard/index.ts +25 -0
  309. package/src/wizard/prompts.ts +338 -0
  310. package/src/wizard/types.ts +171 -0
  311. package/src/wizard/ui.ts +556 -0
  312. package/src/wizard/wizard.ts +304 -0
  313. 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
+ }