@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,805 @@
1
+ /**
2
+ * AWS Terraform Command
3
+ *
4
+ * Generate Terraform configurations from AWS infrastructure
5
+ *
6
+ * Usage: nimbus aws terraform [options]
7
+ */
8
+
9
+ import { logger } from '../utils';
10
+ import { RestClient } from '../clients';
11
+ import {
12
+ createWizard,
13
+ ui,
14
+ select,
15
+ confirm,
16
+ pathInput,
17
+ type WizardStep,
18
+ type StepResult,
19
+ } from '../wizard';
20
+ import { awsDiscoverCommand, type AwsDiscoverOptions } from './aws-discover';
21
+ import { readFile, writeFile } from 'node:fs/promises';
22
+ import * as path from 'path';
23
+ import * as fs from 'fs';
24
+
25
+ // AWS Tools Service client
26
+ const awsToolsUrl = process.env.AWS_TOOLS_SERVICE_URL || 'http://localhost:3009';
27
+ const awsClient = new RestClient(awsToolsUrl);
28
+
29
+ /**
30
+ * Terraform generation context
31
+ */
32
+ export interface AwsTerraformContext {
33
+ // Discovery input
34
+ discoverySessionId?: string;
35
+ resources?: DiscoveredResource[];
36
+
37
+ // Generation options
38
+ outputPath?: string;
39
+ organizeByService?: boolean;
40
+ generateImportBlocks?: boolean;
41
+ generateImportScript?: boolean;
42
+ terraformVersion?: string;
43
+ awsProviderVersion?: string;
44
+
45
+ // Starter kit options
46
+ includeReadme?: boolean;
47
+ includeGitignore?: boolean;
48
+ includeMakefile?: boolean;
49
+
50
+ // Output
51
+ terraformSessionId?: string;
52
+ generatedFiles?: Record<string, string>;
53
+ summary?: GenerationSummary;
54
+ }
55
+
56
+ /**
57
+ * Discovered resource
58
+ */
59
+ interface DiscoveredResource {
60
+ id: string;
61
+ type: string;
62
+ region: string;
63
+ name?: string;
64
+ tags?: Record<string, string>;
65
+ properties: Record<string, unknown>;
66
+ }
67
+
68
+ /**
69
+ * Generation summary
70
+ */
71
+ interface GenerationSummary {
72
+ totalResources: number;
73
+ mappedResources: number;
74
+ unmappedResources: number;
75
+ filesGenerated: number;
76
+ servicesIncluded: string[];
77
+ regionsIncluded: string[];
78
+ }
79
+
80
+ /**
81
+ * Command options from CLI arguments
82
+ */
83
+ export interface AwsTerraformOptions {
84
+ // Discovery options (for full flow)
85
+ profile?: string;
86
+ regions?: string[];
87
+ services?: string[];
88
+
89
+ // Direct generation options
90
+ sessionId?: string; // Use existing discovery session
91
+ resourcesFile?: string; // Load resources from JSON file
92
+
93
+ // Generation options
94
+ output?: string;
95
+ organizeByService?: boolean;
96
+ importBlocks?: boolean;
97
+ importScript?: boolean;
98
+ terraformVersion?: string;
99
+ awsProviderVersion?: string;
100
+
101
+ // Starter kit
102
+ includeStarterKit?: boolean;
103
+ includeReadme?: boolean;
104
+ includeGitignore?: boolean;
105
+ includeMakefile?: boolean;
106
+
107
+ // Mode
108
+ nonInteractive?: boolean;
109
+ skipDiscovery?: boolean;
110
+ }
111
+
112
+ /**
113
+ * Run the AWS terraform command
114
+ */
115
+ export async function awsTerraformCommand(options: AwsTerraformOptions = {}): Promise<void> {
116
+ logger.info('Starting AWS Terraform generation');
117
+
118
+ // Non-interactive mode
119
+ if (options.nonInteractive) {
120
+ await runNonInteractive(options);
121
+ return;
122
+ }
123
+
124
+ // Check if we have resources to generate from
125
+ let resources: DiscoveredResource[] | undefined;
126
+ let discoverySessionId: string | undefined;
127
+
128
+ // Option 1: Use existing discovery session
129
+ if (options.sessionId) {
130
+ discoverySessionId = options.sessionId;
131
+ ui.info(`Using existing discovery session: ${options.sessionId}`);
132
+ }
133
+ // Option 2: Load resources from file
134
+ else if (options.resourcesFile) {
135
+ ui.startSpinner({ message: 'Loading resources from file...' });
136
+ try {
137
+ const fileContent = await readFile(options.resourcesFile, 'utf-8');
138
+ const data = JSON.parse(fileContent);
139
+ resources = data.resources || data;
140
+ ui.stopSpinnerSuccess(`Loaded ${resources!.length} resources from file`);
141
+ } catch (error: any) {
142
+ ui.stopSpinnerFail(`Failed to load resources: ${error.message}`);
143
+ return;
144
+ }
145
+ }
146
+ // Option 3: Run discovery first
147
+ else if (!options.skipDiscovery) {
148
+ const discoveryOptions: AwsDiscoverOptions = {
149
+ profile: options.profile,
150
+ regions: options.regions,
151
+ services: options.services,
152
+ nonInteractive: false,
153
+ };
154
+
155
+ ui.header('nimbus aws terraform', 'Step 1: Infrastructure Discovery');
156
+ ui.newLine();
157
+
158
+ const inventory = await awsDiscoverCommand(discoveryOptions);
159
+ if (!inventory) {
160
+ ui.error('Discovery failed, cannot generate Terraform');
161
+ return;
162
+ }
163
+
164
+ resources = inventory.resources;
165
+ ui.newLine();
166
+ ui.header('nimbus aws terraform', 'Step 2: Terraform Generation');
167
+ ui.newLine();
168
+ }
169
+
170
+ // Interactive wizard for generation options
171
+ const wizard = createWizard<AwsTerraformContext>({
172
+ title: 'Terraform Generation',
173
+ description: 'Configure Terraform generation options',
174
+ initialContext: {
175
+ discoverySessionId,
176
+ resources,
177
+ outputPath: options.output,
178
+ organizeByService: options.organizeByService ?? true,
179
+ generateImportBlocks: options.importBlocks ?? true,
180
+ generateImportScript: options.importScript ?? true,
181
+ terraformVersion: options.terraformVersion,
182
+ awsProviderVersion: options.awsProviderVersion,
183
+ includeReadme: options.includeReadme ?? options.includeStarterKit,
184
+ includeGitignore: options.includeGitignore ?? options.includeStarterKit,
185
+ includeMakefile: options.includeMakefile ?? options.includeStarterKit,
186
+ },
187
+ steps: createWizardSteps(!!discoverySessionId || !!resources),
188
+ onEvent: event => {
189
+ logger.debug('Wizard event', { type: event.type });
190
+ },
191
+ });
192
+
193
+ const result = await wizard.run();
194
+
195
+ if (result.success) {
196
+ displayCompletionMessage(result.context);
197
+ } else {
198
+ ui.error(`Generation failed: ${result.error?.message || 'Unknown error'}`);
199
+ process.exit(1);
200
+ }
201
+ }
202
+
203
+ /**
204
+ * Create wizard steps
205
+ */
206
+ function createWizardSteps(_hasResources: boolean): WizardStep<AwsTerraformContext>[] {
207
+ const steps: WizardStep<AwsTerraformContext>[] = [];
208
+
209
+ // Step 1: Generation Options
210
+ steps.push({
211
+ id: 'generation-options',
212
+ title: 'Generation Options',
213
+ description: 'Configure how Terraform files should be generated',
214
+ execute: generationOptionsStep,
215
+ });
216
+
217
+ // Step 2: Output Location
218
+ steps.push({
219
+ id: 'output-location',
220
+ title: 'Output Location',
221
+ description: 'Where should the Terraform files be saved?',
222
+ execute: outputLocationStep,
223
+ });
224
+
225
+ // Step 3: Generate
226
+ steps.push({
227
+ id: 'generate',
228
+ title: 'Generate Terraform',
229
+ description: 'Generating Terraform configurations...',
230
+ execute: generateStep,
231
+ });
232
+
233
+ // Step 4: Write Files
234
+ steps.push({
235
+ id: 'write-files',
236
+ title: 'Write Files',
237
+ description: 'Writing files to disk...',
238
+ execute: writeFilesStep,
239
+ });
240
+
241
+ return steps;
242
+ }
243
+
244
+ /**
245
+ * Step 1: Generation Options
246
+ */
247
+ async function generationOptionsStep(ctx: AwsTerraformContext): Promise<StepResult> {
248
+ // Organization style
249
+ const organizeChoice = await select<'service' | 'single'>({
250
+ message: 'How should Terraform files be organized?',
251
+ options: [
252
+ {
253
+ value: 'service',
254
+ label: 'By service (Recommended)',
255
+ description: 'Separate files for each AWS service (ec2.tf, s3.tf, etc.)',
256
+ },
257
+ {
258
+ value: 'single',
259
+ label: 'Single file',
260
+ description: 'All resources in main.tf',
261
+ },
262
+ ],
263
+ defaultValue: ctx.organizeByService !== false ? 'service' : 'single',
264
+ });
265
+
266
+ // Import method
267
+ ui.newLine();
268
+ const importMethod = await select<'both' | 'blocks' | 'script' | 'none'>({
269
+ message: 'How should imports be generated?',
270
+ options: [
271
+ {
272
+ value: 'both',
273
+ label: 'Both import blocks and shell script (Recommended)',
274
+ description: 'Maximum compatibility with all Terraform versions',
275
+ },
276
+ {
277
+ value: 'blocks',
278
+ label: 'Import blocks only (Terraform 1.5+)',
279
+ description: 'Modern declarative imports in .tf files',
280
+ },
281
+ {
282
+ value: 'script',
283
+ label: 'Shell script only',
284
+ description: 'Traditional terraform import commands',
285
+ },
286
+ {
287
+ value: 'none',
288
+ label: 'No imports',
289
+ description: 'Generate resource definitions only',
290
+ },
291
+ ],
292
+ defaultValue: 'both',
293
+ });
294
+
295
+ // Terraform version
296
+ ui.newLine();
297
+ const terraformVersion = await select({
298
+ message: 'Target Terraform version:',
299
+ options: [
300
+ { value: '1.5.0', label: '1.5.0+', description: 'Supports import blocks' },
301
+ { value: '1.4.0', label: '1.4.0', description: 'Latest stable without import blocks' },
302
+ { value: '1.3.0', label: '1.3.0', description: 'Older version' },
303
+ ],
304
+ defaultValue: ctx.terraformVersion || '1.5.0',
305
+ });
306
+
307
+ // Starter kit
308
+ ui.newLine();
309
+ const includeStarterKit = await confirm({
310
+ message: 'Include starter kit (README, .gitignore, Makefile)?',
311
+ defaultValue: true,
312
+ });
313
+
314
+ return {
315
+ success: true,
316
+ data: {
317
+ organizeByService: organizeChoice === 'service',
318
+ generateImportBlocks: importMethod === 'both' || importMethod === 'blocks',
319
+ generateImportScript: importMethod === 'both' || importMethod === 'script',
320
+ terraformVersion,
321
+ includeReadme: includeStarterKit,
322
+ includeGitignore: includeStarterKit,
323
+ includeMakefile: includeStarterKit,
324
+ },
325
+ };
326
+ }
327
+
328
+ /**
329
+ * Step 2: Output Location
330
+ */
331
+ async function outputLocationStep(ctx: AwsTerraformContext): Promise<StepResult> {
332
+ const outputPath = await pathInput(
333
+ 'Where should the Terraform files be saved?',
334
+ ctx.outputPath || './terraform-aws'
335
+ );
336
+
337
+ if (!outputPath) {
338
+ return { success: false, error: 'Output path is required' };
339
+ }
340
+
341
+ // Check if directory exists
342
+ const exists = fs.existsSync(outputPath);
343
+ if (exists) {
344
+ const files = fs.readdirSync(outputPath);
345
+ if (files.length > 0) {
346
+ ui.newLine();
347
+ ui.warning(`Directory ${outputPath} is not empty (${files.length} files)`);
348
+
349
+ const overwrite = await confirm({
350
+ message: 'Overwrite existing files?',
351
+ defaultValue: false,
352
+ });
353
+
354
+ if (!overwrite) {
355
+ return { success: false, error: 'User cancelled - directory not empty' };
356
+ }
357
+ }
358
+ }
359
+
360
+ return {
361
+ success: true,
362
+ data: { outputPath },
363
+ };
364
+ }
365
+
366
+ /**
367
+ * Step 3: Generate Terraform
368
+ */
369
+ async function generateStep(ctx: AwsTerraformContext): Promise<StepResult> {
370
+ ui.startSpinner({ message: 'Generating Terraform configurations...' });
371
+
372
+ try {
373
+ let response: any;
374
+
375
+ // Generate from discovery session or direct resources
376
+ if (ctx.discoverySessionId) {
377
+ response = await awsClient.post<{
378
+ terraformSessionId: string;
379
+ files: Record<string, string>;
380
+ summary: GenerationSummary;
381
+ imports: any[];
382
+ importScript: string;
383
+ }>('/api/aws/terraform/generate', {
384
+ sessionId: ctx.discoverySessionId,
385
+ options: {
386
+ organizeByService: ctx.organizeByService,
387
+ generateImportBlocks: ctx.generateImportBlocks,
388
+ terraformVersion: ctx.terraformVersion,
389
+ awsProviderVersion: ctx.awsProviderVersion,
390
+ },
391
+ });
392
+ } else if (ctx.resources && ctx.resources.length > 0) {
393
+ response = await awsClient.post<{
394
+ terraformSessionId: string;
395
+ files: Record<string, string>;
396
+ summary: GenerationSummary;
397
+ imports: any[];
398
+ importScript: string;
399
+ }>('/api/aws/terraform/generate-direct', {
400
+ resources: ctx.resources,
401
+ options: {
402
+ organizeByService: ctx.organizeByService,
403
+ generateImportBlocks: ctx.generateImportBlocks,
404
+ terraformVersion: ctx.terraformVersion,
405
+ awsProviderVersion: ctx.awsProviderVersion,
406
+ },
407
+ });
408
+ } else {
409
+ ui.stopSpinnerFail('No resources to generate from');
410
+ return { success: false, error: 'No resources available' };
411
+ }
412
+
413
+ if (!response.success || !response.data) {
414
+ ui.stopSpinnerFail(`Generation failed: ${response.error || 'Unknown error'}`);
415
+ return { success: false, error: response.error || 'Generation failed' };
416
+ }
417
+
418
+ const { terraformSessionId, files, summary, importScript } = response.data;
419
+
420
+ ui.stopSpinnerSuccess(`Generated ${Object.keys(files).length} files`);
421
+
422
+ // Add starter kit files if requested
423
+ const allFiles = { ...files };
424
+
425
+ if (ctx.includeReadme) {
426
+ allFiles['README.md'] = generateReadme(summary);
427
+ }
428
+
429
+ if (ctx.includeGitignore) {
430
+ allFiles['.gitignore'] = generateGitignore();
431
+ }
432
+
433
+ if (ctx.includeMakefile) {
434
+ allFiles['Makefile'] = generateMakefile();
435
+ }
436
+
437
+ if (ctx.generateImportScript && importScript) {
438
+ allFiles['import.sh'] = importScript;
439
+ }
440
+
441
+ return {
442
+ success: true,
443
+ data: {
444
+ terraformSessionId,
445
+ generatedFiles: allFiles,
446
+ summary,
447
+ },
448
+ };
449
+ } catch (error: any) {
450
+ ui.stopSpinnerFail(`Generation failed: ${error.message}`);
451
+ return { success: false, error: error.message };
452
+ }
453
+ }
454
+
455
+ /**
456
+ * Step 4: Write Files
457
+ */
458
+ async function writeFilesStep(ctx: AwsTerraformContext): Promise<StepResult> {
459
+ if (!ctx.generatedFiles || !ctx.outputPath) {
460
+ return { success: false, error: 'No files to write' };
461
+ }
462
+
463
+ ui.startSpinner({ message: 'Writing files to disk...' });
464
+
465
+ try {
466
+ // Create output directory
467
+ if (!fs.existsSync(ctx.outputPath)) {
468
+ fs.mkdirSync(ctx.outputPath, { recursive: true });
469
+ }
470
+
471
+ // Write each file
472
+ const fileNames = Object.keys(ctx.generatedFiles);
473
+ for (const fileName of fileNames) {
474
+ const filePath = path.join(ctx.outputPath, fileName);
475
+ const content = ctx.generatedFiles[fileName];
476
+
477
+ // Create subdirectories if needed
478
+ const dir = path.dirname(filePath);
479
+ if (!fs.existsSync(dir)) {
480
+ fs.mkdirSync(dir, { recursive: true });
481
+ }
482
+
483
+ await writeFile(filePath, content, 'utf-8');
484
+ }
485
+
486
+ // Make import script executable
487
+ if (ctx.generatedFiles['import.sh']) {
488
+ const scriptPath = path.join(ctx.outputPath, 'import.sh');
489
+ fs.chmodSync(scriptPath, '755');
490
+ }
491
+
492
+ ui.stopSpinnerSuccess(`Wrote ${fileNames.length} files to ${ctx.outputPath}`);
493
+
494
+ return {
495
+ success: true,
496
+ data: { filesWritten: fileNames.length },
497
+ };
498
+ } catch (error: any) {
499
+ ui.stopSpinnerFail(`Failed to write files: ${error.message}`);
500
+ return { success: false, error: error.message };
501
+ }
502
+ }
503
+
504
+ /**
505
+ * Generate README.md content
506
+ */
507
+ function generateReadme(summary: GenerationSummary): string {
508
+ return `# Terraform AWS Infrastructure
509
+
510
+ Generated by Nimbus CLI
511
+
512
+ ## Summary
513
+
514
+ - **Total Resources**: ${summary.totalResources}
515
+ - **Mapped Resources**: ${summary.mappedResources}
516
+ - **Unmapped Resources**: ${summary.unmappedResources}
517
+ - **Files Generated**: ${summary.filesGenerated}
518
+
519
+ ### Services
520
+
521
+ ${summary.servicesIncluded.map(s => `- ${s}`).join('\n')}
522
+
523
+ ### Regions
524
+
525
+ ${summary.regionsIncluded.map(r => `- ${r}`).join('\n')}
526
+
527
+ ## Getting Started
528
+
529
+ 1. **Initialize Terraform**:
530
+ \`\`\`bash
531
+ terraform init
532
+ \`\`\`
533
+
534
+ 2. **Import existing resources** (choose one):
535
+
536
+ Using import blocks (Terraform 1.5+):
537
+ \`\`\`bash
538
+ terraform plan -generate-config-out=generated.tf
539
+ \`\`\`
540
+
541
+ Using import script:
542
+ \`\`\`bash
543
+ ./import.sh
544
+ \`\`\`
545
+
546
+ 3. **Review the plan**:
547
+ \`\`\`bash
548
+ terraform plan
549
+ \`\`\`
550
+
551
+ 4. **Apply changes** (should show no changes if imports were successful):
552
+ \`\`\`bash
553
+ terraform apply
554
+ \`\`\`
555
+
556
+ ## File Structure
557
+
558
+ - \`providers.tf\` - AWS provider configuration
559
+ - \`variables.tf\` - Input variables
560
+ - \`outputs.tf\` - Output values
561
+ - \`*.tf\` - Resource definitions by service
562
+ - \`import.sh\` - Import script for existing resources
563
+
564
+ ## Notes
565
+
566
+ - Review all generated configurations before applying
567
+ - Some sensitive values may need to be filled in manually
568
+ - Consider using Terraform workspaces for different environments
569
+ `;
570
+ }
571
+
572
+ /**
573
+ * Generate .gitignore content
574
+ */
575
+ function generateGitignore(): string {
576
+ return `# Terraform
577
+ *.tfstate
578
+ *.tfstate.*
579
+ .terraform/
580
+ .terraform.lock.hcl
581
+ crash.log
582
+ crash.*.log
583
+ *.tfvars
584
+ *.tfvars.json
585
+ override.tf
586
+ override.tf.json
587
+ *_override.tf
588
+ *_override.tf.json
589
+
590
+ # Sensitive files
591
+ *.pem
592
+ *.key
593
+ .env
594
+ .env.*
595
+
596
+ # IDE
597
+ .idea/
598
+ .vscode/
599
+ *.swp
600
+ *.swo
601
+
602
+ # OS
603
+ .DS_Store
604
+ Thumbs.db
605
+ `;
606
+ }
607
+
608
+ /**
609
+ * Generate Makefile content
610
+ */
611
+ function generateMakefile(): string {
612
+ return `# Terraform Makefile
613
+
614
+ .PHONY: init plan apply destroy fmt validate import clean
615
+
616
+ # Initialize Terraform
617
+ init:
618
+ terraform init
619
+
620
+ # Plan changes
621
+ plan:
622
+ terraform plan
623
+
624
+ # Apply changes
625
+ apply:
626
+ terraform apply
627
+
628
+ # Destroy infrastructure
629
+ destroy:
630
+ terraform destroy
631
+
632
+ # Format code
633
+ fmt:
634
+ terraform fmt -recursive
635
+
636
+ # Validate configuration
637
+ validate:
638
+ terraform validate
639
+
640
+ # Import existing resources
641
+ import:
642
+ ./import.sh
643
+
644
+ # Clean up
645
+ clean:
646
+ rm -rf .terraform
647
+ rm -f .terraform.lock.hcl
648
+
649
+ # Full workflow
650
+ all: init fmt validate plan
651
+ `;
652
+ }
653
+
654
+ /**
655
+ * Display completion message
656
+ */
657
+ function displayCompletionMessage(ctx: AwsTerraformContext): void {
658
+ ui.newLine();
659
+ ui.box({
660
+ title: 'Terraform Generation Complete!',
661
+ content: [
662
+ `Output: ${ctx.outputPath}`,
663
+ `Files: ${Object.keys(ctx.generatedFiles || {}).length}`,
664
+ '',
665
+ 'Summary:',
666
+ ` Resources: ${ctx.summary?.mappedResources || 0} mapped, ${ctx.summary?.unmappedResources || 0} unmapped`,
667
+ ` Services: ${ctx.summary?.servicesIncluded?.join(', ') || 'N/A'}`,
668
+ '',
669
+ 'Next steps:',
670
+ ` 1. cd ${ctx.outputPath}`,
671
+ ' 2. terraform init',
672
+ ' 3. ./import.sh # Import existing resources',
673
+ ' 4. terraform plan',
674
+ ],
675
+ style: 'rounded',
676
+ borderColor: 'green',
677
+ padding: 1,
678
+ });
679
+ }
680
+
681
+ /**
682
+ * Run in non-interactive mode
683
+ */
684
+ async function runNonInteractive(options: AwsTerraformOptions): Promise<void> {
685
+ ui.header('nimbus aws terraform', 'Non-interactive mode');
686
+
687
+ // Must have either session ID, resources file, or profile for discovery
688
+ if (!options.sessionId && !options.resourcesFile && !options.profile) {
689
+ ui.error('One of --session-id, --resources-file, or --profile is required');
690
+ process.exit(1);
691
+ }
692
+
693
+ let resources: DiscoveredResource[] | undefined;
694
+
695
+ // Load from file if specified
696
+ if (options.resourcesFile) {
697
+ ui.startSpinner({ message: 'Loading resources from file...' });
698
+ try {
699
+ const fileContent = await readFile(options.resourcesFile, 'utf-8');
700
+ const data = JSON.parse(fileContent);
701
+ resources = data.resources || data;
702
+ ui.stopSpinnerSuccess(`Loaded ${resources!.length} resources`);
703
+ } catch (error: any) {
704
+ ui.stopSpinnerFail(`Failed to load resources: ${error.message}`);
705
+ process.exit(1);
706
+ }
707
+ }
708
+ // Run discovery if profile specified
709
+ else if (options.profile && !options.sessionId) {
710
+ const discoveryOptions: AwsDiscoverOptions = {
711
+ profile: options.profile,
712
+ regions: options.regions,
713
+ services: options.services,
714
+ nonInteractive: true,
715
+ };
716
+
717
+ const inventory = await awsDiscoverCommand(discoveryOptions);
718
+ if (!inventory) {
719
+ ui.error('Discovery failed');
720
+ process.exit(1);
721
+ }
722
+ resources = inventory.resources;
723
+ }
724
+
725
+ // Generate Terraform
726
+ ui.startSpinner({ message: 'Generating Terraform configurations...' });
727
+
728
+ try {
729
+ let response: any;
730
+
731
+ if (options.sessionId) {
732
+ response = await awsClient.post('/api/aws/terraform/generate', {
733
+ sessionId: options.sessionId,
734
+ options: {
735
+ organizeByService: options.organizeByService ?? true,
736
+ generateImportBlocks: options.importBlocks ?? true,
737
+ terraformVersion: options.terraformVersion || '1.5.0',
738
+ },
739
+ });
740
+ } else if (resources) {
741
+ response = await awsClient.post('/api/aws/terraform/generate-direct', {
742
+ resources,
743
+ options: {
744
+ organizeByService: options.organizeByService ?? true,
745
+ generateImportBlocks: options.importBlocks ?? true,
746
+ terraformVersion: options.terraformVersion || '1.5.0',
747
+ },
748
+ });
749
+ }
750
+
751
+ if (!response.success || !response.data) {
752
+ ui.stopSpinnerFail('Generation failed');
753
+ process.exit(1);
754
+ }
755
+
756
+ const { files, summary, importScript } = response.data;
757
+ ui.stopSpinnerSuccess(`Generated ${Object.keys(files).length} files`);
758
+
759
+ // Write files
760
+ const outputPath = options.output || './terraform-aws';
761
+ ui.startSpinner({ message: 'Writing files...' });
762
+
763
+ if (!fs.existsSync(outputPath)) {
764
+ fs.mkdirSync(outputPath, { recursive: true });
765
+ }
766
+
767
+ // Add starter kit if requested
768
+ if (options.includeStarterKit || options.includeReadme) {
769
+ files['README.md'] = generateReadme(summary);
770
+ }
771
+ if (options.includeStarterKit || options.includeGitignore) {
772
+ files['.gitignore'] = generateGitignore();
773
+ }
774
+ if (options.includeStarterKit || options.includeMakefile) {
775
+ files['Makefile'] = generateMakefile();
776
+ }
777
+ if (importScript && (options.importScript ?? true)) {
778
+ files['import.sh'] = importScript;
779
+ }
780
+
781
+ for (const [fileName, content] of Object.entries(files)) {
782
+ const filePath = path.join(outputPath, fileName);
783
+ await writeFile(filePath, content as string, 'utf-8');
784
+ }
785
+
786
+ if (files['import.sh']) {
787
+ fs.chmodSync(path.join(outputPath, 'import.sh'), '755');
788
+ }
789
+
790
+ ui.stopSpinnerSuccess(`Wrote ${Object.keys(files).length} files to ${outputPath}`);
791
+
792
+ // Show summary
793
+ ui.newLine();
794
+ ui.success('Generation complete!');
795
+ ui.print(` Output: ${outputPath}`);
796
+ ui.print(
797
+ ` Resources: ${summary.mappedResources} mapped, ${summary.unmappedResources} unmapped`
798
+ );
799
+ } catch (error: any) {
800
+ ui.stopSpinnerFail(`Generation failed: ${error.message}`);
801
+ process.exit(1);
802
+ }
803
+ }
804
+
805
+ export default awsTerraformCommand;