@build-astron-co/nimbus 0.2.0 → 0.4.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 (469) hide show
  1. package/bin/nimbus +26 -10
  2. package/bin/nimbus.cmd +41 -0
  3. package/bin/nimbus.mjs +70 -0
  4. package/completions/nimbus.bash +38 -0
  5. package/completions/nimbus.fish +48 -0
  6. package/completions/nimbus.zsh +81 -0
  7. package/dist/src/agent/compaction-agent.js +215 -0
  8. package/dist/src/agent/context-manager.js +385 -0
  9. package/dist/src/agent/context.js +322 -0
  10. package/dist/src/agent/deploy-preview.js +395 -0
  11. package/dist/src/agent/expand-files.js +95 -0
  12. package/dist/src/agent/index.js +18 -0
  13. package/dist/src/agent/loop.js +1535 -0
  14. package/dist/src/agent/modes.js +347 -0
  15. package/dist/src/agent/permissions.js +396 -0
  16. package/dist/src/agent/subagents/base.js +67 -0
  17. package/dist/src/agent/subagents/cost.js +45 -0
  18. package/dist/src/agent/subagents/explore.js +36 -0
  19. package/dist/src/agent/subagents/general.js +41 -0
  20. package/dist/src/agent/subagents/index.js +88 -0
  21. package/dist/src/agent/subagents/infra.js +52 -0
  22. package/dist/src/agent/subagents/security.js +60 -0
  23. package/dist/src/agent/system-prompt.js +860 -0
  24. package/dist/src/app.js +152 -0
  25. package/dist/src/audit/activity-log.js +209 -0
  26. package/dist/src/audit/compliance-checker.js +419 -0
  27. package/dist/src/audit/cost-tracker.js +231 -0
  28. package/dist/src/audit/index.js +10 -0
  29. package/dist/src/audit/security-scanner.js +490 -0
  30. package/dist/src/auth/guard.js +64 -0
  31. package/dist/src/auth/index.js +19 -0
  32. package/dist/src/auth/keychain.js +79 -0
  33. package/dist/src/auth/oauth.js +389 -0
  34. package/dist/src/auth/providers.js +415 -0
  35. package/dist/src/auth/sso.js +87 -0
  36. package/dist/src/auth/store.js +424 -0
  37. package/dist/src/auth/types.js +5 -0
  38. package/dist/src/cli/index.js +8 -0
  39. package/dist/src/cli/init.js +1048 -0
  40. package/dist/src/cli/openapi-spec.js +346 -0
  41. package/dist/src/cli/run.js +505 -0
  42. package/dist/src/cli/serve-auth.js +56 -0
  43. package/dist/src/cli/serve.js +432 -0
  44. package/dist/src/cli/web.js +50 -0
  45. package/dist/src/cli.js +1574 -0
  46. package/dist/src/clients/core-engine-client.js +156 -0
  47. package/dist/src/clients/enterprise-client.js +246 -0
  48. package/dist/src/clients/generator-client.js +219 -0
  49. package/dist/src/clients/git-client.js +367 -0
  50. package/dist/src/clients/github-client.js +229 -0
  51. package/dist/src/clients/helm-client.js +299 -0
  52. package/dist/src/clients/index.js +18 -0
  53. package/dist/src/clients/k8s-client.js +270 -0
  54. package/dist/src/clients/llm-client.js +119 -0
  55. package/dist/src/clients/rest-client.js +104 -0
  56. package/dist/src/clients/service-discovery.js +35 -0
  57. package/dist/src/clients/terraform-client.js +302 -0
  58. package/dist/src/clients/tools-client.js +1227 -0
  59. package/dist/src/clients/ws-client.js +93 -0
  60. package/dist/src/commands/alias.js +91 -0
  61. package/dist/src/commands/analyze/index.js +313 -0
  62. package/dist/src/commands/apply/helm.js +375 -0
  63. package/dist/src/commands/apply/index.js +176 -0
  64. package/dist/src/commands/apply/k8s.js +350 -0
  65. package/dist/src/commands/apply/terraform.js +465 -0
  66. package/dist/src/commands/ask.js +137 -0
  67. package/dist/src/commands/audit/index.js +322 -0
  68. package/dist/src/commands/auth-cloud.js +345 -0
  69. package/dist/src/commands/auth-list.js +112 -0
  70. package/dist/src/commands/auth-profile.js +104 -0
  71. package/dist/src/commands/auth-refresh.js +161 -0
  72. package/dist/src/commands/auth-status.js +122 -0
  73. package/dist/src/commands/aws/ec2.js +402 -0
  74. package/dist/src/commands/aws/iam.js +304 -0
  75. package/dist/src/commands/aws/index.js +108 -0
  76. package/dist/src/commands/aws/lambda.js +317 -0
  77. package/dist/src/commands/aws/rds.js +345 -0
  78. package/dist/src/commands/aws/s3.js +346 -0
  79. package/dist/src/commands/aws/vpc.js +302 -0
  80. package/dist/src/commands/aws-discover.js +413 -0
  81. package/dist/src/commands/aws-terraform.js +618 -0
  82. package/dist/src/commands/azure/aks.js +305 -0
  83. package/dist/src/commands/azure/functions.js +200 -0
  84. package/dist/src/commands/azure/index.js +93 -0
  85. package/dist/src/commands/azure/storage.js +378 -0
  86. package/dist/src/commands/azure/vm.js +291 -0
  87. package/dist/src/commands/billing/index.js +224 -0
  88. package/dist/src/commands/chat.js +259 -0
  89. package/dist/src/commands/completions.js +255 -0
  90. package/dist/src/commands/config.js +291 -0
  91. package/dist/src/commands/cost/cloud-cost-estimator.js +211 -0
  92. package/dist/src/commands/cost/estimator.js +73 -0
  93. package/dist/src/commands/cost/index.js +625 -0
  94. package/dist/src/commands/cost/parsers/terraform.js +234 -0
  95. package/dist/src/commands/cost/parsers/types.js +4 -0
  96. package/dist/src/commands/cost/pricing/aws.js +501 -0
  97. package/dist/src/commands/cost/pricing/azure.js +462 -0
  98. package/dist/src/commands/cost/pricing/gcp.js +359 -0
  99. package/dist/src/commands/cost/pricing/index.js +24 -0
  100. package/dist/src/commands/demo.js +196 -0
  101. package/dist/src/commands/deploy.js +215 -0
  102. package/dist/src/commands/doctor.js +1291 -0
  103. package/dist/src/commands/drift/index.js +674 -0
  104. package/dist/src/commands/explain.js +235 -0
  105. package/dist/src/commands/export.js +120 -0
  106. package/dist/src/commands/feedback.js +319 -0
  107. package/dist/src/commands/fix.js +263 -0
  108. package/dist/src/commands/fs/index.js +338 -0
  109. package/dist/src/commands/gcp/compute.js +266 -0
  110. package/dist/src/commands/gcp/functions.js +221 -0
  111. package/dist/src/commands/gcp/gke.js +357 -0
  112. package/dist/src/commands/gcp/iam.js +295 -0
  113. package/dist/src/commands/gcp/index.js +105 -0
  114. package/dist/src/commands/gcp/storage.js +232 -0
  115. package/dist/src/commands/generate-helm.js +1026 -0
  116. package/dist/src/commands/generate-k8s.js +1263 -0
  117. package/dist/src/commands/generate-terraform.js +1058 -0
  118. package/dist/src/commands/gh/index.js +663 -0
  119. package/dist/src/commands/git/index.js +1208 -0
  120. package/dist/src/commands/helm/index.js +985 -0
  121. package/dist/src/commands/help.js +639 -0
  122. package/dist/src/commands/history.js +120 -0
  123. package/dist/src/commands/import.js +782 -0
  124. package/dist/src/commands/incident.js +144 -0
  125. package/dist/src/commands/index.js +109 -0
  126. package/dist/src/commands/init.js +955 -0
  127. package/dist/src/commands/k8s/index.js +979 -0
  128. package/dist/src/commands/login.js +588 -0
  129. package/dist/src/commands/logout.js +61 -0
  130. package/dist/src/commands/logs.js +160 -0
  131. package/dist/src/commands/onboarding.js +382 -0
  132. package/dist/src/commands/pipeline.js +153 -0
  133. package/dist/src/commands/plan/display.js +216 -0
  134. package/dist/src/commands/plan/index.js +525 -0
  135. package/dist/src/commands/plugin.js +325 -0
  136. package/dist/src/commands/preview.js +356 -0
  137. package/dist/src/commands/profile.js +297 -0
  138. package/dist/src/commands/questionnaire.js +1021 -0
  139. package/dist/src/commands/resume.js +35 -0
  140. package/dist/src/commands/rollback.js +259 -0
  141. package/dist/src/commands/rollout.js +74 -0
  142. package/dist/src/commands/runbook.js +307 -0
  143. package/dist/src/commands/schedule.js +202 -0
  144. package/dist/src/commands/status.js +213 -0
  145. package/dist/src/commands/team/index.js +309 -0
  146. package/dist/src/commands/team-context.js +200 -0
  147. package/dist/src/commands/template.js +204 -0
  148. package/dist/src/commands/tf/index.js +989 -0
  149. package/dist/src/commands/upgrade.js +515 -0
  150. package/dist/src/commands/usage/index.js +118 -0
  151. package/dist/src/commands/version.js +145 -0
  152. package/dist/src/commands/watch.js +127 -0
  153. package/dist/src/compat/index.js +2 -0
  154. package/dist/src/compat/runtime.js +10 -0
  155. package/dist/src/compat/sqlite.js +144 -0
  156. package/dist/src/config/index.js +6 -0
  157. package/dist/src/config/manager.js +469 -0
  158. package/dist/src/config/mode-store.js +57 -0
  159. package/dist/src/config/profiles.js +66 -0
  160. package/dist/src/config/safety-policy.js +251 -0
  161. package/dist/src/config/schema.js +107 -0
  162. package/dist/src/config/types.js +311 -0
  163. package/dist/src/config/workspace-state.js +38 -0
  164. package/dist/src/context/context-db.js +138 -0
  165. package/dist/src/demo/index.js +295 -0
  166. package/dist/src/demo/scenarios/full-journey.js +226 -0
  167. package/dist/src/demo/scenarios/getting-started.js +124 -0
  168. package/dist/src/demo/scenarios/helm-release.js +334 -0
  169. package/dist/src/demo/scenarios/k8s-deployment.js +190 -0
  170. package/dist/src/demo/scenarios/terraform-vpc.js +167 -0
  171. package/dist/src/demo/types.js +6 -0
  172. package/dist/src/engine/cost-estimator.js +334 -0
  173. package/dist/src/engine/diagram-generator.js +192 -0
  174. package/dist/src/engine/drift-detector.js +688 -0
  175. package/dist/src/engine/executor.js +832 -0
  176. package/dist/src/engine/index.js +39 -0
  177. package/dist/src/engine/orchestrator.js +436 -0
  178. package/dist/src/engine/planner.js +616 -0
  179. package/dist/src/engine/safety.js +609 -0
  180. package/dist/src/engine/verifier.js +664 -0
  181. package/dist/src/enterprise/audit.js +241 -0
  182. package/dist/src/enterprise/auth.js +189 -0
  183. package/dist/src/enterprise/billing.js +512 -0
  184. package/dist/src/enterprise/index.js +16 -0
  185. package/dist/src/enterprise/teams.js +315 -0
  186. package/dist/src/generator/best-practices.js +1375 -0
  187. package/dist/src/generator/helm.js +495 -0
  188. package/dist/src/generator/index.js +11 -0
  189. package/dist/src/generator/intent-parser.js +420 -0
  190. package/dist/src/generator/kubernetes.js +773 -0
  191. package/dist/src/generator/terraform.js +1472 -0
  192. package/dist/src/history/index.js +6 -0
  193. package/dist/src/history/manager.js +199 -0
  194. package/dist/src/history/types.js +6 -0
  195. package/dist/src/hooks/config.js +318 -0
  196. package/dist/src/hooks/engine.js +317 -0
  197. package/dist/src/hooks/index.js +2 -0
  198. package/dist/src/llm/auth-bridge.js +157 -0
  199. package/dist/src/llm/circuit-breaker.js +116 -0
  200. package/dist/src/llm/config-loader.js +172 -0
  201. package/dist/src/llm/cost-calculator.js +137 -0
  202. package/dist/src/llm/index.js +7 -0
  203. package/dist/src/llm/model-aliases.js +99 -0
  204. package/dist/src/llm/provider-registry.js +57 -0
  205. package/dist/src/llm/providers/anthropic.js +430 -0
  206. package/dist/src/llm/providers/bedrock.js +409 -0
  207. package/dist/src/llm/providers/google.js +344 -0
  208. package/dist/src/llm/providers/ollama.js +661 -0
  209. package/dist/src/llm/providers/openai-compatible.js +289 -0
  210. package/dist/src/llm/providers/openai.js +284 -0
  211. package/dist/src/llm/providers/openrouter.js +293 -0
  212. package/dist/src/llm/router.js +844 -0
  213. package/dist/src/llm/types.js +69 -0
  214. package/dist/src/lsp/client.js +239 -0
  215. package/dist/src/lsp/languages.js +95 -0
  216. package/dist/src/lsp/manager.js +243 -0
  217. package/dist/src/mcp/client.js +289 -0
  218. package/dist/src/mcp/index.js +5 -0
  219. package/dist/src/mcp/manager.js +113 -0
  220. package/dist/src/nimbus.js +212 -0
  221. package/dist/src/plugins/index.js +13 -0
  222. package/dist/src/plugins/loader.js +280 -0
  223. package/dist/src/plugins/manager.js +282 -0
  224. package/dist/src/plugins/types.js +23 -0
  225. package/dist/src/scanners/cicd-scanner.js +230 -0
  226. package/dist/src/scanners/cloud-scanner.js +415 -0
  227. package/dist/src/scanners/framework-scanner.js +430 -0
  228. package/dist/src/scanners/iac-scanner.js +350 -0
  229. package/dist/src/scanners/index.js +454 -0
  230. package/dist/src/scanners/language-scanner.js +258 -0
  231. package/dist/src/scanners/package-manager-scanner.js +252 -0
  232. package/dist/src/scanners/types.js +6 -0
  233. package/dist/src/sessions/manager.js +395 -0
  234. package/dist/src/sessions/types.js +4 -0
  235. package/dist/src/sharing/sync.js +238 -0
  236. package/dist/src/sharing/viewer.js +131 -0
  237. package/dist/src/snapshots/index.js +1 -0
  238. package/dist/src/snapshots/manager.js +432 -0
  239. package/dist/src/state/artifacts.js +94 -0
  240. package/dist/src/state/audit.js +73 -0
  241. package/dist/src/state/billing.js +126 -0
  242. package/dist/src/state/checkpoints.js +81 -0
  243. package/dist/src/state/config.js +58 -0
  244. package/dist/src/state/conversations.js +7 -0
  245. package/dist/src/state/credentials.js +96 -0
  246. package/dist/src/state/db.js +53 -0
  247. package/dist/src/state/index.js +23 -0
  248. package/dist/src/state/messages.js +76 -0
  249. package/dist/src/state/projects.js +92 -0
  250. package/dist/src/state/schema.js +233 -0
  251. package/dist/src/state/sessions.js +79 -0
  252. package/dist/src/state/teams.js +131 -0
  253. package/dist/src/telemetry.js +91 -0
  254. package/dist/src/tools/aws-ops.js +747 -0
  255. package/dist/src/tools/azure-ops.js +491 -0
  256. package/dist/src/tools/file-ops.js +451 -0
  257. package/dist/src/tools/gcp-ops.js +559 -0
  258. package/dist/src/tools/git-ops.js +557 -0
  259. package/dist/src/tools/github-ops.js +460 -0
  260. package/dist/src/tools/helm-ops.js +634 -0
  261. package/dist/src/tools/index.js +16 -0
  262. package/dist/src/tools/k8s-ops.js +579 -0
  263. package/dist/src/tools/schemas/converter.js +129 -0
  264. package/dist/src/tools/schemas/devops.js +3319 -0
  265. package/dist/src/tools/schemas/index.js +19 -0
  266. package/dist/src/tools/schemas/standard.js +966 -0
  267. package/dist/src/tools/schemas/types.js +409 -0
  268. package/dist/src/tools/spawn-exec.js +109 -0
  269. package/dist/src/tools/terraform-ops.js +627 -0
  270. package/dist/src/types/config.js +1 -0
  271. package/dist/src/types/drift.js +4 -0
  272. package/dist/src/types/enterprise.js +5 -0
  273. package/dist/src/types/index.js +14 -0
  274. package/dist/src/types/plan.js +1 -0
  275. package/dist/src/types/request.js +1 -0
  276. package/dist/src/types/response.js +1 -0
  277. package/dist/src/types/service.js +1 -0
  278. package/dist/src/ui/App.js +1672 -0
  279. package/dist/src/ui/DeployPreview.js +60 -0
  280. package/dist/src/ui/FileDiffModal.js +108 -0
  281. package/dist/src/ui/Header.js +46 -0
  282. package/dist/src/ui/HelpModal.js +9 -0
  283. package/dist/src/ui/InputBox.js +408 -0
  284. package/dist/src/ui/MessageList.js +795 -0
  285. package/dist/src/ui/PermissionPrompt.js +72 -0
  286. package/dist/src/ui/StatusBar.js +109 -0
  287. package/dist/src/ui/TerminalPane.js +31 -0
  288. package/dist/src/ui/ToolCallDisplay.js +303 -0
  289. package/dist/src/ui/TreePane.js +83 -0
  290. package/dist/src/ui/chat-ui.js +721 -0
  291. package/dist/src/ui/index.js +11 -0
  292. package/dist/src/ui/ink/index.js +1325 -0
  293. package/dist/src/ui/streaming.js +137 -0
  294. package/dist/src/ui/theme.js +78 -0
  295. package/dist/src/ui/types.js +7 -0
  296. package/dist/src/utils/analytics.js +61 -0
  297. package/dist/src/utils/cost-warning.js +25 -0
  298. package/dist/src/utils/env.js +42 -0
  299. package/dist/src/utils/errors.js +54 -0
  300. package/dist/src/utils/event-bus.js +22 -0
  301. package/dist/src/utils/index.js +16 -0
  302. package/dist/src/utils/logger.js +150 -0
  303. package/dist/src/utils/rate-limiter.js +90 -0
  304. package/dist/src/utils/service-auth.js +36 -0
  305. package/dist/src/utils/validation.js +39 -0
  306. package/dist/src/version.js +3 -0
  307. package/dist/src/watcher/index.js +192 -0
  308. package/dist/src/wizard/approval.js +275 -0
  309. package/dist/src/wizard/index.js +13 -0
  310. package/dist/src/wizard/prompts.js +273 -0
  311. package/dist/src/wizard/types.js +4 -0
  312. package/dist/src/wizard/ui.js +453 -0
  313. package/dist/src/wizard/wizard.js +227 -0
  314. package/package.json +31 -23
  315. package/src/__tests__/alias.test.ts +133 -0
  316. package/src/__tests__/app.test.ts +1 -1
  317. package/src/__tests__/audit.test.ts +1 -1
  318. package/src/__tests__/circuit-breaker.test.ts +1 -1
  319. package/src/__tests__/cli-run.test.ts +237 -1
  320. package/src/__tests__/compat-sqlite.test.ts +68 -0
  321. package/src/__tests__/context-manager.test.ts +131 -1
  322. package/src/__tests__/context.test.ts +1 -1
  323. package/src/__tests__/devops-terminal-gaps.test.ts +718 -0
  324. package/src/__tests__/doctor.test.ts +48 -0
  325. package/src/__tests__/enterprise.test.ts +1 -1
  326. package/src/__tests__/export.test.ts +236 -0
  327. package/src/__tests__/gap-11-18-20.test.ts +958 -0
  328. package/src/__tests__/generator.test.ts +1 -1
  329. package/src/__tests__/helm-streaming.test.ts +127 -0
  330. package/src/__tests__/hooks.test.ts +1 -1
  331. package/src/__tests__/incident.test.ts +179 -0
  332. package/src/__tests__/init.test.ts +55 -4
  333. package/src/__tests__/intent-parser.test.ts +1 -1
  334. package/src/__tests__/llm-router.test.ts +1 -1
  335. package/src/__tests__/logs.test.ts +107 -0
  336. package/src/__tests__/loop-errors.test.ts +244 -0
  337. package/src/__tests__/lsp.test.ts +1 -1
  338. package/src/__tests__/modes.test.ts +1 -1
  339. package/src/__tests__/perf-optimizations.test.ts +847 -0
  340. package/src/__tests__/permissions.test.ts +1 -1
  341. package/src/__tests__/pipeline.test.ts +50 -0
  342. package/src/__tests__/polish-phase3.test.ts +340 -0
  343. package/src/__tests__/profile.test.ts +237 -0
  344. package/src/__tests__/rollback.test.ts +83 -0
  345. package/src/__tests__/runbook.test.ts +219 -0
  346. package/src/__tests__/schedule.test.ts +206 -0
  347. package/src/__tests__/serve.test.ts +1 -1
  348. package/src/__tests__/sessions.test.ts +96 -1
  349. package/src/__tests__/sharing.test.ts +53 -1
  350. package/src/__tests__/snapshots.test.ts +1 -1
  351. package/src/__tests__/standalone-migration.test.ts +199 -0
  352. package/src/__tests__/state-db.test.ts +1 -1
  353. package/src/__tests__/status.test.ts +158 -0
  354. package/src/__tests__/stream-with-tools.test.ts +71 -25
  355. package/src/__tests__/subagents.test.ts +1 -1
  356. package/src/__tests__/system-prompt.test.ts +82 -3
  357. package/src/__tests__/terminal-gap-v2.test.ts +395 -0
  358. package/src/__tests__/terminal-parity.test.ts +393 -0
  359. package/src/__tests__/tf-apply.test.ts +187 -0
  360. package/src/__tests__/tool-converter.test.ts +1 -1
  361. package/src/__tests__/tool-schemas.test.ts +209 -4
  362. package/src/__tests__/tools.test.ts +4 -3
  363. package/src/__tests__/version-json.test.ts +184 -0
  364. package/src/__tests__/version.test.ts +1 -1
  365. package/src/__tests__/watch.test.ts +129 -0
  366. package/src/agent/compaction-agent.ts +40 -1
  367. package/src/agent/context-manager.ts +67 -3
  368. package/src/agent/deploy-preview.ts +62 -1
  369. package/src/agent/expand-files.ts +108 -0
  370. package/src/agent/loop.ts +1312 -31
  371. package/src/agent/permissions.ts +51 -4
  372. package/src/agent/system-prompt.ts +573 -19
  373. package/src/app.ts +58 -0
  374. package/src/audit/security-scanner.ts +45 -0
  375. package/src/auth/keychain.ts +82 -0
  376. package/src/auth/oauth.ts +15 -5
  377. package/src/cli/init.ts +378 -5
  378. package/src/cli/run.ts +407 -16
  379. package/src/cli/serve.ts +78 -1
  380. package/src/cli/web.ts +10 -6
  381. package/src/cli.ts +312 -1
  382. package/src/clients/service-discovery.ts +30 -25
  383. package/src/commands/alias.ts +100 -0
  384. package/src/commands/audit/index.ts +121 -2
  385. package/src/commands/auth-cloud.ts +113 -0
  386. package/src/commands/auth-refresh.ts +187 -0
  387. package/src/commands/aws-discover.ts +144 -251
  388. package/src/commands/aws-terraform.ts +68 -118
  389. package/src/commands/chat.ts +9 -3
  390. package/src/commands/completions.ts +268 -0
  391. package/src/commands/config.ts +26 -0
  392. package/src/commands/cost/index.ts +218 -2
  393. package/src/commands/deploy.ts +260 -0
  394. package/src/commands/doctor.ts +744 -152
  395. package/src/commands/drift/index.ts +371 -23
  396. package/src/commands/export.ts +146 -0
  397. package/src/commands/generate-k8s.ts +9 -61
  398. package/src/commands/generate-terraform.ts +191 -449
  399. package/src/commands/help.ts +212 -36
  400. package/src/commands/history.ts +8 -1
  401. package/src/commands/incident.ts +166 -0
  402. package/src/commands/init.ts +5 -0
  403. package/src/commands/login.ts +86 -1
  404. package/src/commands/logs.ts +167 -0
  405. package/src/commands/onboarding.ts +211 -34
  406. package/src/commands/pipeline.ts +186 -0
  407. package/src/commands/plugin.ts +398 -0
  408. package/src/commands/profile.ts +342 -0
  409. package/src/commands/questionnaire.ts +0 -98
  410. package/src/commands/resume.ts +26 -34
  411. package/src/commands/rollback.ts +315 -0
  412. package/src/commands/rollout.ts +88 -0
  413. package/src/commands/runbook.ts +346 -0
  414. package/src/commands/schedule.ts +236 -0
  415. package/src/commands/status.ts +252 -0
  416. package/src/commands/team-context.ts +220 -0
  417. package/src/commands/template.ts +58 -57
  418. package/src/commands/tf/index.ts +70 -11
  419. package/src/commands/upgrade.ts +57 -0
  420. package/src/commands/version.ts +54 -50
  421. package/src/commands/watch.ts +153 -0
  422. package/src/compat/runtime.ts +1 -1
  423. package/src/compat/sqlite.ts +75 -5
  424. package/src/config/mode-store.ts +62 -0
  425. package/src/config/profiles.ts +84 -0
  426. package/src/config/types.ts +83 -1
  427. package/src/config/workspace-state.ts +53 -0
  428. package/src/engine/cost-estimator.ts +52 -10
  429. package/src/engine/executor.ts +33 -2
  430. package/src/engine/planner.ts +68 -1
  431. package/src/generator/terraform.ts +8 -0
  432. package/src/history/manager.ts +2 -74
  433. package/src/hooks/engine.ts +5 -4
  434. package/src/llm/cost-calculator.ts +2 -2
  435. package/src/llm/providers/anthropic.ts +50 -21
  436. package/src/llm/router.ts +76 -7
  437. package/src/lsp/languages.ts +3 -0
  438. package/src/lsp/manager.ts +21 -5
  439. package/src/nimbus.ts +37 -18
  440. package/src/sessions/manager.ts +108 -1
  441. package/src/sharing/sync.ts +4 -0
  442. package/src/sharing/viewer.ts +66 -0
  443. package/src/tools/file-ops.ts +22 -0
  444. package/src/tools/schemas/devops.ts +3007 -117
  445. package/src/tools/schemas/standard.ts +5 -1
  446. package/src/tools/schemas/types.ts +31 -1
  447. package/src/tools/spawn-exec.ts +148 -0
  448. package/src/ui/App.tsx +1183 -66
  449. package/src/ui/DeployPreview.tsx +62 -57
  450. package/src/ui/FileDiffModal.tsx +162 -0
  451. package/src/ui/Header.tsx +87 -24
  452. package/src/ui/HelpModal.tsx +57 -0
  453. package/src/ui/InputBox.tsx +163 -10
  454. package/src/ui/MessageList.tsx +487 -40
  455. package/src/ui/PermissionPrompt.tsx +17 -5
  456. package/src/ui/StatusBar.tsx +122 -3
  457. package/src/ui/TerminalPane.tsx +84 -0
  458. package/src/ui/ToolCallDisplay.tsx +252 -18
  459. package/src/ui/TreePane.tsx +132 -0
  460. package/src/ui/chat-ui.ts +41 -44
  461. package/src/ui/ink/index.ts +771 -38
  462. package/src/ui/streaming.ts +1 -1
  463. package/src/ui/theme.ts +104 -0
  464. package/src/ui/types.ts +18 -0
  465. package/src/version.ts +1 -1
  466. package/src/watcher/index.ts +66 -15
  467. package/src/wizard/types.ts +1 -0
  468. package/src/wizard/ui.ts +1 -1
  469. package/tsconfig.json +2 -2
@@ -0,0 +1,1472 @@
1
+ /**
2
+ * Terraform Project Generator
3
+ *
4
+ * Generates complete Terraform project structures with environment separation,
5
+ * module scaffolding, and post-generation validation pipeline.
6
+ *
7
+ * Addresses:
8
+ * - Gap #9: Post-generation validation pipeline
9
+ * - Gap #10: Environment separation (dev/staging/prod tfvars)
10
+ * - Gap #11: Full project structure generation
11
+ * - Gap #12: tflint-style checks
12
+ * - Gap #16: .gitignore in scaffolded projects
13
+ */
14
+ import { spawn } from 'node:child_process';
15
+ import { logger } from '../utils';
16
+ // ==========================================
17
+ // Generator
18
+ // ==========================================
19
+ /**
20
+ * Generates a complete Terraform project structure including:
21
+ * - Root configuration files (main.tf, variables.tf, outputs.tf, versions.tf, backend.tf)
22
+ * - Environment-specific tfvars (dev, staging, prod)
23
+ * - Component modules with main, variables, and outputs
24
+ * - .gitignore for Terraform projects
25
+ * - Post-generation validation pipeline
26
+ */
27
+ export class TerraformProjectGenerator {
28
+ /**
29
+ * Generate a full Terraform project from the given configuration.
30
+ */
31
+ async generate(config) {
32
+ logger.info(`Generating Terraform project: ${config.projectName}`);
33
+ const files = [];
34
+ // 1. Root configuration files
35
+ files.push(this.generateMainTf(config));
36
+ files.push(this.generateVariablesTf(config));
37
+ files.push(this.generateOutputsTf(config));
38
+ files.push(this.generateVersionsTf(config));
39
+ files.push(this.generateBackendTf(config));
40
+ // 2. Example tfvars
41
+ files.push(this.generateTfvarsExample(config));
42
+ // 3. README
43
+ files.push(this.generateReadme(config));
44
+ // 4. Environment-specific tfvars (Gap #10)
45
+ files.push(this.generateEnvTfvars(config, 'dev'));
46
+ files.push(this.generateEnvTfvars(config, 'staging'));
47
+ files.push(this.generateEnvTfvars(config, 'prod'));
48
+ // 5. Module files for each component (Gap #11)
49
+ for (const component of config.components) {
50
+ files.push(...this.generateModuleFiles(config, component));
51
+ }
52
+ // 6. .gitignore for Terraform projects (Gap #16)
53
+ files.push(this.generateGitignore());
54
+ // 7. Run validation pipeline (Gap #9 + #12)
55
+ const validation = this.validateProject(files, config);
56
+ // Subprocess validation (D1) is available via validateWithSubprocess()
57
+ // but is NOT auto-run here because terraform init can be slow (downloads providers).
58
+ // Callers should invoke validateWithSubprocess() separately when needed.
59
+ return { files, validation };
60
+ }
61
+ // ===== File Generators =====
62
+ /**
63
+ * Generate a standard Terraform .gitignore file.
64
+ * Excludes state files, provider caches, variable overrides,
65
+ * and other files that should not be committed to version control.
66
+ */
67
+ generateGitignore() {
68
+ return {
69
+ path: '.gitignore',
70
+ content: `# Terraform
71
+ *.tfstate
72
+ *.tfstate.*
73
+ .terraform/
74
+ .terraform.lock.hcl
75
+ crash.log
76
+ override.tf
77
+ override.tf.json
78
+ *_override.tf
79
+ *_override.tf.json
80
+ *.tfvars
81
+ *.tfvars.json
82
+ .terraformrc
83
+ terraform.rc
84
+ `,
85
+ };
86
+ }
87
+ generateMainTf(config) {
88
+ const providerBlock = this.getProviderBlock(config);
89
+ const moduleBlocks = config.components.map(c => this.getModuleBlock(config, c)).join('\n\n');
90
+ return {
91
+ path: 'main.tf',
92
+ content: `# ${config.projectName} - Main Configuration
93
+ # Generated by Nimbus
94
+
95
+ ${providerBlock}
96
+
97
+ ${moduleBlocks}
98
+ `,
99
+ };
100
+ }
101
+ generateVariablesTf(config) {
102
+ const vars = [
103
+ `# ${config.projectName} - Variables`,
104
+ '# Generated by Nimbus',
105
+ '',
106
+ 'variable "project_name" {',
107
+ ' description = "Name of the project"',
108
+ ' type = string',
109
+ ` default = "${config.projectName}"`,
110
+ '}',
111
+ '',
112
+ 'variable "environment" {',
113
+ ' description = "Environment (dev, staging, prod)"',
114
+ ' type = string',
115
+ ` default = "${config.environment || 'dev'}"`,
116
+ '',
117
+ ' validation {',
118
+ ' condition = contains(["dev", "staging", "prod"], var.environment)',
119
+ ' error_message = "Environment must be dev, staging, or prod."',
120
+ ' }',
121
+ '}',
122
+ '',
123
+ 'variable "region" {',
124
+ ' description = "Cloud provider region"',
125
+ ' type = string',
126
+ ` default = "${config.region}"`,
127
+ '}',
128
+ '',
129
+ 'variable "tags" {',
130
+ ' description = "Common tags for all resources"',
131
+ ' type = map(string)',
132
+ ' default = {',
133
+ ` Project = "${config.projectName}"`,
134
+ ' ManagedBy = "terraform"',
135
+ ' Environment = "dev"',
136
+ ' }',
137
+ '}',
138
+ ];
139
+ // Add component-specific variables
140
+ if (config.components.includes('vpc')) {
141
+ vars.push('', 'variable "vpc_cidr" {', ' description = "VPC CIDR block"', ' type = string', ' default = "10.0.0.0/16"', '}');
142
+ vars.push('', 'variable "availability_zones" {', ' description = "List of availability zones"', ' type = list(string)', ` default = ["${config.region}a", "${config.region}b"]`, '}');
143
+ }
144
+ if (config.components.includes('eks')) {
145
+ vars.push('', 'variable "cluster_version" {', ' description = "EKS cluster version"', ' type = string', ' default = "1.28"', '}');
146
+ vars.push('', 'variable "node_instance_type" {', ' description = "EKS node instance type"', ' type = string', ' default = "t3.medium"', '}');
147
+ vars.push('', 'variable "node_count" {', ' description = "Number of EKS worker nodes"', ' type = number', ' default = 2', '}');
148
+ }
149
+ if (config.components.includes('rds')) {
150
+ vars.push('', 'variable "db_instance_class" {', ' description = "RDS instance class"', ' type = string', ' default = "db.t3.micro"', '}');
151
+ vars.push('', 'variable "db_engine" {', ' description = "Database engine"', ' type = string', ' default = "postgres"', '}');
152
+ vars.push('', 'variable "db_storage_size" {', ' description = "Database storage size in GB"', ' type = number', ' default = 20', '}');
153
+ }
154
+ if (config.components.includes('s3')) {
155
+ vars.push('', 'variable "bucket_name" {', ' description = "S3 bucket name"', ' type = string', ` default = "${config.projectName}-storage"`, '}');
156
+ }
157
+ if (config.components.includes('ecs')) {
158
+ vars.push('', 'variable "container_image" {', ' description = "Docker image for the ECS task"', ' type = string', ` default = "${config.projectName}:latest"`, '}');
159
+ vars.push('', 'variable "container_port" {', ' description = "Port exposed by the container"', ' type = number', ' default = 8080', '}');
160
+ vars.push('', 'variable "ecs_cpu" {', ' description = "Fargate task CPU units (256, 512, 1024, 2048, 4096)"', ' type = number', ' default = 256', '}');
161
+ vars.push('', 'variable "ecs_memory" {', ' description = "Fargate task memory in MiB"', ' type = number', ' default = 512', '}');
162
+ vars.push('', 'variable "desired_count" {', ' description = "Number of ECS tasks to run"', ' type = number', ' default = 2', '}');
163
+ }
164
+ if (config.components.includes('kms')) {
165
+ vars.push('', 'variable "kms_key_alias" {', ' description = "Alias for the KMS key"', ' type = string', ` default = "${config.projectName}-key"`, '}');
166
+ vars.push('', 'variable "kms_deletion_window" {', ' description = "Number of days before KMS key is deleted after destruction"', ' type = number', ' default = 30', '}');
167
+ }
168
+ return { path: 'variables.tf', content: `${vars.join('\n')}\n` };
169
+ }
170
+ generateOutputsTf(config) {
171
+ const outputs = [`# ${config.projectName} - Outputs`, '# Generated by Nimbus', ''];
172
+ if (config.components.includes('vpc')) {
173
+ outputs.push('output "vpc_id" {', ' description = "VPC ID"', ' value = module.vpc.vpc_id', '}', '');
174
+ }
175
+ if (config.components.includes('eks')) {
176
+ outputs.push('output "eks_cluster_endpoint" {', ' description = "EKS cluster endpoint"', ' value = module.eks.cluster_endpoint', '}', '');
177
+ outputs.push('output "eks_cluster_name" {', ' description = "EKS cluster name"', ' value = module.eks.cluster_name', '}', '');
178
+ }
179
+ if (config.components.includes('rds')) {
180
+ outputs.push('output "rds_endpoint" {', ' description = "RDS endpoint"', ' value = module.rds.endpoint', ' sensitive = true', '}', '');
181
+ }
182
+ if (config.components.includes('s3')) {
183
+ outputs.push('output "s3_bucket_arn" {', ' description = "S3 bucket ARN"', ' value = module.s3.bucket_arn', '}', '');
184
+ }
185
+ if (config.components.includes('ecs')) {
186
+ outputs.push('output "ecs_cluster_name" {', ' description = "ECS cluster name"', ' value = module.ecs.cluster_name', '}', '');
187
+ outputs.push('output "ecs_service_name" {', ' description = "ECS service name"', ' value = module.ecs.service_name', '}', '');
188
+ outputs.push('output "alb_dns_name" {', ' description = "ALB DNS name"', ' value = module.ecs.alb_dns_name', '}', '');
189
+ }
190
+ if (config.components.includes('kms')) {
191
+ outputs.push('output "kms_key_arn" {', ' description = "KMS key ARN"', ' value = module.kms.key_arn', '}', '');
192
+ outputs.push('output "kms_key_id" {', ' description = "KMS key ID"', ' value = module.kms.key_id', '}', '');
193
+ }
194
+ return { path: 'outputs.tf', content: outputs.join('\n') };
195
+ }
196
+ generateVersionsTf(config) {
197
+ const providerSource = config.provider === 'aws'
198
+ ? 'hashicorp/aws'
199
+ : config.provider === 'gcp'
200
+ ? 'hashicorp/google'
201
+ : 'hashicorp/azurerm';
202
+ const providerVersion = config.provider === 'aws' ? '~> 5.0' : config.provider === 'gcp' ? '~> 5.0' : '~> 3.0';
203
+ const providerName = config.provider === 'gcp' ? 'google' : config.provider;
204
+ return {
205
+ path: 'versions.tf',
206
+ content: `# Terraform and Provider Versions
207
+ # Generated by Nimbus
208
+
209
+ terraform {
210
+ required_version = ">= 1.5.0"
211
+
212
+ required_providers {
213
+ ${providerName} = {
214
+ source = "${providerSource}"
215
+ version = "${providerVersion}"
216
+ }
217
+ }
218
+ }
219
+ `,
220
+ };
221
+ }
222
+ generateBackendTf(config) {
223
+ const bucket = config.backendConfig?.bucket || `${config.projectName}-tfstate`;
224
+ const key = config.backendConfig?.key || `${config.projectName}/terraform.tfstate`;
225
+ const dynamodbTable = config.backendConfig?.dynamodbTable || `${config.projectName}-tflock`;
226
+ if (config.provider === 'aws') {
227
+ return {
228
+ path: 'backend.tf',
229
+ content: `# Remote State Configuration
230
+ # Generated by Nimbus
231
+
232
+ terraform {
233
+ backend "s3" {
234
+ bucket = "${bucket}"
235
+ key = "${key}"
236
+ region = "${config.region}"
237
+ dynamodb_table = "${dynamodbTable}"
238
+ encrypt = true
239
+ }
240
+ }
241
+ `,
242
+ };
243
+ }
244
+ // GCP/Azure backends
245
+ return {
246
+ path: 'backend.tf',
247
+ content: `# Remote State Configuration
248
+ # Generated by Nimbus
249
+ # Configure your backend before running terraform init
250
+
251
+ # terraform {
252
+ # backend "${config.provider === 'gcp' ? 'gcs' : 'azurerm'}" {
253
+ # # Configure your backend settings
254
+ # }
255
+ # }
256
+ `,
257
+ };
258
+ }
259
+ generateTfvarsExample(config) {
260
+ const lines = [
261
+ `# ${config.projectName} - Example Variables`,
262
+ '# Copy this file and customize for your environment',
263
+ '# Generated by Nimbus',
264
+ '',
265
+ `project_name = "${config.projectName}"`,
266
+ 'environment = "dev"',
267
+ `region = "${config.region}"`,
268
+ '',
269
+ ];
270
+ if (config.components.includes('vpc')) {
271
+ lines.push('vpc_cidr = "10.0.0.0/16"');
272
+ }
273
+ if (config.components.includes('eks')) {
274
+ lines.push('node_instance_type = "t3.medium"');
275
+ lines.push('node_count = 2');
276
+ }
277
+ if (config.components.includes('rds')) {
278
+ lines.push('db_instance_class = "db.t3.micro"');
279
+ }
280
+ if (config.components.includes('ecs')) {
281
+ lines.push('container_image = "nginx:latest"');
282
+ lines.push('container_port = 8080');
283
+ lines.push('ecs_cpu = 256');
284
+ lines.push('ecs_memory = 512');
285
+ lines.push('desired_count = 2');
286
+ }
287
+ if (config.components.includes('kms')) {
288
+ lines.push(`kms_key_alias = "${config.projectName}-key"`);
289
+ lines.push('kms_deletion_window = 30');
290
+ }
291
+ return { path: 'terraform.tfvars.example', content: `${lines.join('\n')}\n` };
292
+ }
293
+ generateReadme(config) {
294
+ return {
295
+ path: 'README.md',
296
+ content: `# ${config.projectName}
297
+
298
+ Infrastructure as Code managed by Terraform. Generated by Nimbus.
299
+
300
+ ## Components
301
+ ${config.components.map(c => `- ${c.toUpperCase()}`).join('\n')}
302
+
303
+ ## Environments
304
+ - \`dev\` - Development environment
305
+ - \`staging\` - Staging environment
306
+ - \`prod\` - Production environment
307
+
308
+ ## Usage
309
+
310
+ \`\`\`bash
311
+ # Initialize
312
+ terraform init
313
+
314
+ # Plan with environment-specific vars
315
+ terraform plan -var-file="environments/dev/terraform.tfvars"
316
+
317
+ # Apply
318
+ terraform apply -var-file="environments/dev/terraform.tfvars"
319
+ \`\`\`
320
+
321
+ ## Structure
322
+ \`\`\`
323
+ .
324
+ ├── main.tf # Main configuration
325
+ ├── variables.tf # Variable definitions
326
+ ├── outputs.tf # Output definitions
327
+ ├── versions.tf # Terraform and provider versions
328
+ ├── backend.tf # Remote state configuration
329
+ ├── terraform.tfvars.example # Example variable values
330
+ ├── .gitignore # Git ignore rules
331
+ ├── environments/
332
+ │ ├── dev/terraform.tfvars # Dev environment values
333
+ │ ├── staging/terraform.tfvars # Staging environment values
334
+ │ └── prod/terraform.tfvars # Production environment values
335
+ └── modules/ # Component modules
336
+ ${config.components.map(c => ` └── ${c}/`).join('\n')}
337
+ \`\`\`
338
+ `,
339
+ };
340
+ }
341
+ generateEnvTfvars(config, env) {
342
+ const envConfigs = {
343
+ dev: {
344
+ instanceType: 't3.small',
345
+ nodeCount: 1,
346
+ dbClass: 'db.t3.micro',
347
+ dbStorage: 20,
348
+ azCount: 2,
349
+ cidr: '10.0.0.0/16',
350
+ ecsCpu: 256,
351
+ ecsMemory: 512,
352
+ ecsDesiredCount: 1,
353
+ },
354
+ staging: {
355
+ instanceType: 't3.medium',
356
+ nodeCount: 2,
357
+ dbClass: 'db.t3.small',
358
+ dbStorage: 50,
359
+ azCount: 2,
360
+ cidr: '10.1.0.0/16',
361
+ ecsCpu: 512,
362
+ ecsMemory: 1024,
363
+ ecsDesiredCount: 2,
364
+ },
365
+ prod: {
366
+ instanceType: 't3.large',
367
+ nodeCount: 3,
368
+ dbClass: 'db.r6g.large',
369
+ dbStorage: 100,
370
+ azCount: 3,
371
+ cidr: '10.2.0.0/16',
372
+ ecsCpu: 1024,
373
+ ecsMemory: 2048,
374
+ ecsDesiredCount: 3,
375
+ },
376
+ };
377
+ const c = envConfigs[env];
378
+ const azs = Array.from({ length: c.azCount }, (_, i) => `"${config.region}${String.fromCharCode(97 + i)}"`);
379
+ const lines = [
380
+ `# ${config.projectName} - ${env.charAt(0).toUpperCase() + env.slice(1)} Environment`,
381
+ '# Generated by Nimbus',
382
+ '',
383
+ `project_name = "${config.projectName}"`,
384
+ `environment = "${env}"`,
385
+ `region = "${config.region}"`,
386
+ '',
387
+ 'tags = {',
388
+ ` Project = "${config.projectName}"`,
389
+ ` Environment = "${env}"`,
390
+ ' ManagedBy = "terraform"',
391
+ '}',
392
+ '',
393
+ ];
394
+ if (config.components.includes('vpc')) {
395
+ lines.push(`vpc_cidr = "${c.cidr}"`);
396
+ lines.push(`availability_zones = [${azs.join(', ')}]`);
397
+ lines.push('');
398
+ }
399
+ if (config.components.includes('eks')) {
400
+ lines.push(`node_instance_type = "${c.instanceType}"`);
401
+ lines.push(`node_count = ${c.nodeCount}`);
402
+ lines.push('');
403
+ }
404
+ if (config.components.includes('rds')) {
405
+ lines.push(`db_instance_class = "${c.dbClass}"`);
406
+ lines.push(`db_storage_size = ${c.dbStorage}`);
407
+ lines.push('');
408
+ }
409
+ if (config.components.includes('ecs')) {
410
+ lines.push(`ecs_cpu = ${c.ecsCpu}`);
411
+ lines.push(`ecs_memory = ${c.ecsMemory}`);
412
+ lines.push(`desired_count = ${c.ecsDesiredCount}`);
413
+ lines.push('');
414
+ }
415
+ if (config.components.includes('kms')) {
416
+ lines.push(`kms_key_alias = "${config.projectName}-key"`);
417
+ lines.push(`kms_deletion_window = ${env === 'prod' ? 30 : 7}`);
418
+ lines.push('');
419
+ }
420
+ return {
421
+ path: `environments/${env}/terraform.tfvars`,
422
+ content: lines.join('\n'),
423
+ };
424
+ }
425
+ generateModuleFiles(config, component) {
426
+ const files = [];
427
+ files.push({
428
+ path: `modules/${component}/main.tf`,
429
+ content: this.getModuleMainTf(config, component),
430
+ });
431
+ files.push({
432
+ path: `modules/${component}/variables.tf`,
433
+ content: this.getModuleVariablesTf(component),
434
+ });
435
+ files.push({
436
+ path: `modules/${component}/outputs.tf`,
437
+ content: this.getModuleOutputsTf(component),
438
+ });
439
+ return files;
440
+ }
441
+ // ===== Validation Pipeline (Gap #9 + #12) =====
442
+ /**
443
+ * Validate a set of generated Terraform files.
444
+ * Runs structural, syntactic, and best-practice checks similar to tflint.
445
+ */
446
+ validateProject(files, _config) {
447
+ const items = [];
448
+ // 1. Check required files are present
449
+ items.push(...this.checkRequiredFiles(files));
450
+ // 2. Basic HCL syntax validation
451
+ items.push(...this.checkHclSyntax(files));
452
+ // 3. Check for anti-patterns (tflint-style)
453
+ items.push(...this.checkAntiPatterns(files));
454
+ // 4. Check for missing tags on resources
455
+ items.push(...this.checkMissingTags(files));
456
+ const errors = items.filter(i => i.severity === 'error').length;
457
+ const warnings = items.filter(i => i.severity === 'warning').length;
458
+ const info = items.filter(i => i.severity === 'info').length;
459
+ return {
460
+ valid: errors === 0,
461
+ items,
462
+ summary: { errors, warnings, info },
463
+ };
464
+ }
465
+ checkRequiredFiles(files) {
466
+ const items = [];
467
+ const requiredFiles = ['main.tf', 'variables.tf', 'outputs.tf', 'versions.tf', 'backend.tf'];
468
+ const filePaths = files.map(f => f.path);
469
+ for (const required of requiredFiles) {
470
+ if (!filePaths.includes(required)) {
471
+ items.push({
472
+ severity: 'error',
473
+ message: `Required file missing: ${required}`,
474
+ rule: 'required-files',
475
+ });
476
+ }
477
+ }
478
+ // Check environment files
479
+ for (const env of ['dev', 'staging', 'prod']) {
480
+ if (!filePaths.includes(`environments/${env}/terraform.tfvars`)) {
481
+ items.push({
482
+ severity: 'warning',
483
+ message: `Missing environment tfvars: environments/${env}/terraform.tfvars`,
484
+ rule: 'env-separation',
485
+ });
486
+ }
487
+ }
488
+ return items;
489
+ }
490
+ checkHclSyntax(files) {
491
+ const items = [];
492
+ for (const file of files) {
493
+ if (!file.path.endsWith('.tf')) {
494
+ continue;
495
+ }
496
+ // Check matching braces
497
+ const openBraces = (file.content.match(/\{/g) || []).length;
498
+ const closeBraces = (file.content.match(/\}/g) || []).length;
499
+ if (openBraces !== closeBraces) {
500
+ items.push({
501
+ severity: 'error',
502
+ message: `Mismatched braces: ${openBraces} open, ${closeBraces} close`,
503
+ file: file.path,
504
+ rule: 'hcl-syntax',
505
+ });
506
+ }
507
+ // Check matching quotes
508
+ const quoteCount = (file.content.match(/"/g) || []).length;
509
+ if (quoteCount % 2 !== 0) {
510
+ items.push({
511
+ severity: 'error',
512
+ message: 'Unmatched quotes detected',
513
+ file: file.path,
514
+ rule: 'hcl-syntax',
515
+ });
516
+ }
517
+ // Check for valid resource/module/variable declarations
518
+ const lines = file.content.split('\n');
519
+ for (let i = 0; i < lines.length; i++) {
520
+ const line = lines[i].trim();
521
+ if (line.startsWith('resource ') ||
522
+ line.startsWith('module ') ||
523
+ line.startsWith('variable ')) {
524
+ if (!line.includes('{') && !line.includes('"')) {
525
+ items.push({
526
+ severity: 'warning',
527
+ message: `Potentially malformed declaration: ${line.substring(0, 60)}`,
528
+ file: file.path,
529
+ line: i + 1,
530
+ rule: 'hcl-syntax',
531
+ });
532
+ }
533
+ }
534
+ }
535
+ }
536
+ return items;
537
+ }
538
+ checkAntiPatterns(files) {
539
+ const items = [];
540
+ for (const file of files) {
541
+ if (!file.path.endsWith('.tf')) {
542
+ continue;
543
+ }
544
+ const lines = file.content.split('\n');
545
+ for (let i = 0; i < lines.length; i++) {
546
+ const line = lines[i];
547
+ // Check for hardcoded AWS account IDs (12-digit numbers)
548
+ if (/\d{12}/.test(line) && !line.trim().startsWith('#')) {
549
+ items.push({
550
+ severity: 'warning',
551
+ message: 'Possible hardcoded AWS account ID',
552
+ file: file.path,
553
+ line: i + 1,
554
+ rule: 'no-hardcoded-values',
555
+ });
556
+ }
557
+ // Check for hardcoded secrets/passwords
558
+ if (/password\s*=\s*"[^"]*[^v][^a][^r]/.test(line.toLowerCase()) &&
559
+ !line.trim().startsWith('#')) {
560
+ items.push({
561
+ severity: 'error',
562
+ message: 'Possible hardcoded password',
563
+ file: file.path,
564
+ line: i + 1,
565
+ rule: 'no-hardcoded-secrets',
566
+ });
567
+ }
568
+ // Check for publicly accessible resources
569
+ if (/publicly_accessible\s*=\s*true/.test(line) && !line.trim().startsWith('#')) {
570
+ items.push({
571
+ severity: 'warning',
572
+ message: 'Resource is publicly accessible',
573
+ file: file.path,
574
+ line: i + 1,
575
+ rule: 'no-public-access',
576
+ });
577
+ }
578
+ }
579
+ }
580
+ return items;
581
+ }
582
+ checkMissingTags(files) {
583
+ const items = [];
584
+ for (const file of files) {
585
+ if (!file.path.endsWith('.tf') ||
586
+ file.path.includes('variables') ||
587
+ file.path.includes('outputs') ||
588
+ file.path.includes('versions')) {
589
+ continue;
590
+ }
591
+ // Check if resource blocks have tags
592
+ const hasResources = /resource\s+"/.test(file.content);
593
+ const hasTags = /tags\s*=/.test(file.content) || /tags\s*\{/.test(file.content);
594
+ if (hasResources && !hasTags) {
595
+ items.push({
596
+ severity: 'warning',
597
+ message: 'Resource blocks without tags attribute',
598
+ file: file.path,
599
+ rule: 'require-tags',
600
+ });
601
+ }
602
+ }
603
+ return items;
604
+ }
605
+ // ===== Subprocess Validation (D1) =====
606
+ /**
607
+ * Validate generated Terraform files by writing them to a temp directory and
608
+ * running real CLI tools: `terraform fmt`, `terraform validate`, and optionally `tflint`.
609
+ *
610
+ * This is a best-effort operation. If the terraform binary is not installed, the
611
+ * individual SubprocessResult entries will contain the error in stderr and
612
+ * success will be false. The caller (generate()) catches any top-level throw
613
+ * so that subprocess validation never blocks project generation.
614
+ */
615
+ async validateWithSubprocess(files, validationMode = 'required') {
616
+ const { mkdtempSync, writeFileSync, mkdirSync, rmSync } = await import('node:fs');
617
+ const { join, dirname } = await import('node:path');
618
+ const { tmpdir } = await import('node:os');
619
+ const tmpDir = mkdtempSync(join(tmpdir(), 'nimbus-tf-validate-'));
620
+ try {
621
+ // Write all generated files into the temp directory
622
+ for (const file of files) {
623
+ const filePath = join(tmpDir, file.path);
624
+ const dir = dirname(filePath);
625
+ mkdirSync(dir, { recursive: true });
626
+ writeFileSync(filePath, file.content, 'utf-8');
627
+ }
628
+ // 1. terraform fmt -check -diff
629
+ const fmtCheck = await this.runCommand('terraform', ['fmt', '-check', '-diff', tmpDir]);
630
+ // 2. terraform init -backend=false + terraform validate
631
+ let terraformValidate;
632
+ const initResult = await this.runCommand('terraform', [
633
+ `-chdir=${tmpDir}`,
634
+ 'init',
635
+ '-backend=false',
636
+ ]);
637
+ if (initResult.success) {
638
+ terraformValidate = await this.runCommand('terraform', [`-chdir=${tmpDir}`, 'validate']);
639
+ }
640
+ else {
641
+ terraformValidate = {
642
+ success: false,
643
+ stdout: '',
644
+ stderr: `init failed: ${initResult.stderr}`,
645
+ };
646
+ }
647
+ // 3. tflint
648
+ let tflint = null;
649
+ const tflintInstalled = await this.commandExists('tflint');
650
+ if (tflintInstalled) {
651
+ tflint = await this.runCommand('tflint', [`--chdir=${tmpDir}`]);
652
+ }
653
+ else if (validationMode === 'required') {
654
+ tflint = {
655
+ success: false,
656
+ stdout: '',
657
+ stderr: 'tflint is required but not installed. Install: brew install tflint',
658
+ };
659
+ }
660
+ // 4. checkov
661
+ let checkov = null;
662
+ const checkovInstalled = await this.commandExists('checkov');
663
+ if (checkovInstalled) {
664
+ checkov = await this.runCommand('checkov', [
665
+ '-d',
666
+ tmpDir,
667
+ '--framework',
668
+ 'terraform',
669
+ '--quiet',
670
+ '--compact',
671
+ ]);
672
+ }
673
+ else if (validationMode === 'required') {
674
+ checkov = {
675
+ success: false,
676
+ stdout: '',
677
+ stderr: 'checkov is required but not installed. Install: pip install checkov',
678
+ };
679
+ }
680
+ return { fmtCheck, terraformValidate, tflint, checkov };
681
+ }
682
+ finally {
683
+ // Always clean up the temp directory
684
+ try {
685
+ rmSync(tmpDir, { recursive: true, force: true });
686
+ }
687
+ catch {
688
+ // Best-effort cleanup
689
+ }
690
+ }
691
+ }
692
+ /**
693
+ * Run an external command and capture its stdout, stderr, and exit code.
694
+ * Uses child_process.spawn for subprocess execution with a configurable timeout
695
+ * (default 10 seconds) to prevent blocking on slow network operations
696
+ * such as `terraform init` downloading providers.
697
+ */
698
+ async runCommand(cmd, args, timeoutMs = 3_000) {
699
+ try {
700
+ const result = await new Promise(resolve => {
701
+ const proc = spawn(cmd, args, { stdio: ['ignore', 'pipe', 'pipe'] });
702
+ let stdout = '';
703
+ let stderr = '';
704
+ proc.stdout?.on('data', (data) => {
705
+ stdout += data.toString();
706
+ });
707
+ proc.stderr?.on('data', (data) => {
708
+ stderr += data.toString();
709
+ });
710
+ proc.on('close', code => {
711
+ resolve({ success: code === 0, stdout, stderr });
712
+ });
713
+ proc.on('error', err => {
714
+ resolve({ success: false, stdout, stderr: stderr || err.message });
715
+ });
716
+ // Timeout guard
717
+ setTimeout(() => {
718
+ try {
719
+ proc.kill('SIGTERM');
720
+ }
721
+ catch {
722
+ /* already exited */
723
+ }
724
+ resolve({
725
+ success: false,
726
+ stdout: '',
727
+ stderr: `Command timed out after ${timeoutMs}ms`,
728
+ });
729
+ }, timeoutMs);
730
+ });
731
+ return result;
732
+ }
733
+ catch (error) {
734
+ return {
735
+ success: false,
736
+ stdout: '',
737
+ stderr: error.message,
738
+ };
739
+ }
740
+ }
741
+ /**
742
+ * Check if a command exists on PATH by running `which`.
743
+ */
744
+ async commandExists(cmd) {
745
+ try {
746
+ const result = await new Promise(resolve => {
747
+ const proc = spawn('which', [cmd], { stdio: ['ignore', 'pipe', 'pipe'] });
748
+ proc.on('close', code => {
749
+ resolve(code ?? 1);
750
+ });
751
+ proc.on('error', () => {
752
+ resolve(1);
753
+ });
754
+ });
755
+ return result === 0;
756
+ }
757
+ catch {
758
+ return false;
759
+ }
760
+ }
761
+ // ===== Helper Methods =====
762
+ getProviderBlock(config) {
763
+ if (config.provider === 'aws') {
764
+ return `provider "aws" {
765
+ region = var.region
766
+
767
+ default_tags {
768
+ tags = var.tags
769
+ }
770
+ }`;
771
+ }
772
+ if (config.provider === 'gcp') {
773
+ return `provider "google" {
774
+ region = var.region
775
+ project = var.project_name
776
+ }`;
777
+ }
778
+ return `provider "azurerm" {
779
+ features {}
780
+ }`;
781
+ }
782
+ getModuleBlock(config, component) {
783
+ const commonVars = ` project_name = var.project_name
784
+ environment = var.environment
785
+ tags = var.tags`;
786
+ switch (component) {
787
+ case 'vpc':
788
+ return `module "vpc" {
789
+ source = "./modules/vpc"
790
+
791
+ ${commonVars}
792
+ vpc_cidr = var.vpc_cidr
793
+ availability_zones = var.availability_zones
794
+ }`;
795
+ case 'eks':
796
+ return `module "eks" {
797
+ source = "./modules/eks"
798
+
799
+ ${commonVars}
800
+ vpc_id = module.vpc.vpc_id
801
+ subnet_ids = module.vpc.private_subnet_ids
802
+ cluster_version = var.cluster_version
803
+ node_instance_type = var.node_instance_type
804
+ node_count = var.node_count
805
+
806
+ depends_on = [module.vpc]
807
+ }`;
808
+ case 'rds':
809
+ return `module "rds" {
810
+ source = "./modules/rds"
811
+
812
+ ${commonVars}
813
+ vpc_id = module.vpc.vpc_id
814
+ subnet_ids = module.vpc.private_subnet_ids
815
+ instance_class = var.db_instance_class
816
+ engine = var.db_engine
817
+ storage_size = var.db_storage_size
818
+
819
+ depends_on = [module.vpc]
820
+ }`;
821
+ case 's3':
822
+ return `module "s3" {
823
+ source = "./modules/s3"
824
+
825
+ ${commonVars}
826
+ bucket_name = var.bucket_name
827
+ }`;
828
+ case 'ecs':
829
+ return `module "ecs" {
830
+ source = "./modules/ecs"
831
+
832
+ ${commonVars}
833
+ vpc_id = module.vpc.vpc_id
834
+ public_subnet_ids = module.vpc.public_subnet_ids
835
+ private_subnet_ids = module.vpc.private_subnet_ids
836
+ container_image = var.container_image
837
+ container_port = var.container_port
838
+ cpu = var.ecs_cpu
839
+ memory = var.ecs_memory
840
+ desired_count = var.desired_count
841
+
842
+ depends_on = [module.vpc]
843
+ }`;
844
+ case 'kms':
845
+ return `module "kms" {
846
+ source = "./modules/kms"
847
+
848
+ ${commonVars}
849
+ key_alias = var.kms_key_alias
850
+ deletion_window_in_days = var.kms_deletion_window
851
+ }`;
852
+ default:
853
+ return `module "${component}" {
854
+ source = "./modules/${component}"
855
+
856
+ ${commonVars}
857
+ }`;
858
+ }
859
+ }
860
+ getModuleMainTf(_config, component) {
861
+ switch (component) {
862
+ case 'vpc':
863
+ return `# VPC Module
864
+ # Generated by Nimbus
865
+
866
+ resource "aws_vpc" "main" {
867
+ cidr_block = var.vpc_cidr
868
+ enable_dns_hostnames = true
869
+ enable_dns_support = true
870
+
871
+ tags = merge(var.tags, {
872
+ Name = "\${var.project_name}-\${var.environment}-vpc"
873
+ })
874
+ }
875
+
876
+ resource "aws_subnet" "private" {
877
+ count = length(var.availability_zones)
878
+ vpc_id = aws_vpc.main.id
879
+ cidr_block = cidrsubnet(var.vpc_cidr, 8, count.index)
880
+ availability_zone = var.availability_zones[count.index]
881
+
882
+ tags = merge(var.tags, {
883
+ Name = "\${var.project_name}-\${var.environment}-private-\${count.index}"
884
+ Type = "private"
885
+ })
886
+ }
887
+
888
+ resource "aws_subnet" "public" {
889
+ count = length(var.availability_zones)
890
+ vpc_id = aws_vpc.main.id
891
+ cidr_block = cidrsubnet(var.vpc_cidr, 8, count.index + length(var.availability_zones))
892
+ availability_zone = var.availability_zones[count.index]
893
+ map_public_ip_on_launch = true
894
+
895
+ tags = merge(var.tags, {
896
+ Name = "\${var.project_name}-\${var.environment}-public-\${count.index}"
897
+ Type = "public"
898
+ })
899
+ }
900
+ `;
901
+ case 'eks':
902
+ return `# EKS Module
903
+ # Generated by Nimbus
904
+
905
+ resource "aws_eks_cluster" "main" {
906
+ name = "\${var.project_name}-\${var.environment}"
907
+ role_arn = aws_iam_role.cluster.arn
908
+ version = var.cluster_version
909
+
910
+ vpc_config {
911
+ subnet_ids = var.subnet_ids
912
+ endpoint_private_access = true
913
+ endpoint_public_access = false
914
+ }
915
+
916
+ encryption_config {
917
+ resources = ["secrets"]
918
+ provider {
919
+ key_arn = aws_kms_key.eks.arn
920
+ }
921
+ }
922
+
923
+ tags = var.tags
924
+ }
925
+ `;
926
+ case 'rds':
927
+ return `# RDS Module
928
+ # Generated by Nimbus
929
+
930
+ resource "aws_db_instance" "main" {
931
+ identifier = "\${var.project_name}-\${var.environment}"
932
+ instance_class = var.instance_class
933
+ engine = var.engine
934
+ allocated_storage = var.storage_size
935
+
936
+ storage_encrypted = true
937
+ backup_retention_period = 7
938
+ publicly_accessible = false
939
+ multi_az = var.environment == "prod" ? true : false
940
+
941
+ db_subnet_group_name = aws_db_subnet_group.main.name
942
+
943
+ tags = var.tags
944
+ }
945
+
946
+ resource "aws_db_subnet_group" "main" {
947
+ name = "\${var.project_name}-\${var.environment}"
948
+ subnet_ids = var.subnet_ids
949
+
950
+ tags = var.tags
951
+ }
952
+ `;
953
+ case 's3':
954
+ return `# S3 Module
955
+ # Generated by Nimbus
956
+
957
+ resource "aws_s3_bucket" "main" {
958
+ bucket = "\${var.bucket_name}-\${var.environment}"
959
+
960
+ tags = var.tags
961
+ }
962
+
963
+ resource "aws_s3_bucket_versioning" "main" {
964
+ bucket = aws_s3_bucket.main.id
965
+ versioning_configuration {
966
+ status = "Enabled"
967
+ }
968
+ }
969
+
970
+ resource "aws_s3_bucket_server_side_encryption_configuration" "main" {
971
+ bucket = aws_s3_bucket.main.id
972
+ rule {
973
+ apply_server_side_encryption_by_default {
974
+ sse_algorithm = "aws:kms"
975
+ }
976
+ }
977
+ }
978
+
979
+ resource "aws_s3_bucket_public_access_block" "main" {
980
+ bucket = aws_s3_bucket.main.id
981
+
982
+ block_public_acls = true
983
+ block_public_policy = true
984
+ ignore_public_acls = true
985
+ restrict_public_buckets = true
986
+ }
987
+ `;
988
+ case 'ecs':
989
+ return `# ECS Fargate Module
990
+ # Generated by Nimbus
991
+
992
+ resource "aws_ecs_cluster" "main" {
993
+ name = "\${var.project_name}-\${var.environment}"
994
+
995
+ setting {
996
+ name = "containerInsights"
997
+ value = "enabled"
998
+ }
999
+
1000
+ tags = var.tags
1001
+ }
1002
+
1003
+ resource "aws_ecs_task_definition" "main" {
1004
+ family = "\${var.project_name}-\${var.environment}"
1005
+ network_mode = "awsvpc"
1006
+ requires_compatibilities = ["FARGATE"]
1007
+ cpu = var.cpu
1008
+ memory = var.memory
1009
+ execution_role_arn = aws_iam_role.ecs_task_execution.arn
1010
+ task_role_arn = aws_iam_role.ecs_task.arn
1011
+
1012
+ container_definitions = jsonencode([
1013
+ {
1014
+ name = var.project_name
1015
+ image = var.container_image
1016
+ essential = true
1017
+ portMappings = [
1018
+ {
1019
+ containerPort = var.container_port
1020
+ hostPort = var.container_port
1021
+ protocol = "tcp"
1022
+ }
1023
+ ]
1024
+ logConfiguration = {
1025
+ logDriver = "awslogs"
1026
+ options = {
1027
+ "awslogs-group" = aws_cloudwatch_log_group.ecs.name
1028
+ "awslogs-region" = data.aws_region.current.name
1029
+ "awslogs-stream-prefix" = "ecs"
1030
+ }
1031
+ }
1032
+ }
1033
+ ])
1034
+
1035
+ tags = var.tags
1036
+ }
1037
+
1038
+ resource "aws_ecs_service" "main" {
1039
+ name = "\${var.project_name}-\${var.environment}"
1040
+ cluster = aws_ecs_cluster.main.id
1041
+ task_definition = aws_ecs_task_definition.main.arn
1042
+ desired_count = var.desired_count
1043
+ launch_type = "FARGATE"
1044
+
1045
+ network_configuration {
1046
+ subnets = var.private_subnet_ids
1047
+ security_groups = [aws_security_group.ecs_tasks.id]
1048
+ assign_public_ip = false
1049
+ }
1050
+
1051
+ load_balancer {
1052
+ target_group_arn = aws_lb_target_group.main.arn
1053
+ container_name = var.project_name
1054
+ container_port = var.container_port
1055
+ }
1056
+
1057
+ deployment_circuit_breaker {
1058
+ enable = true
1059
+ rollback = true
1060
+ }
1061
+
1062
+ tags = var.tags
1063
+ }
1064
+
1065
+ resource "aws_lb" "main" {
1066
+ name = "\${var.project_name}-\${var.environment}-alb"
1067
+ internal = false
1068
+ load_balancer_type = "application"
1069
+ security_groups = [aws_security_group.alb.id]
1070
+ subnets = var.public_subnet_ids
1071
+
1072
+ tags = var.tags
1073
+ }
1074
+
1075
+ resource "aws_lb_target_group" "main" {
1076
+ name = "\${var.project_name}-\${var.environment}-tg"
1077
+ port = var.container_port
1078
+ protocol = "HTTP"
1079
+ vpc_id = var.vpc_id
1080
+ target_type = "ip"
1081
+
1082
+ health_check {
1083
+ path = "/health"
1084
+ port = "traffic-port"
1085
+ protocol = "HTTP"
1086
+ }
1087
+
1088
+ tags = var.tags
1089
+ }
1090
+
1091
+ resource "aws_lb_listener" "http" {
1092
+ load_balancer_arn = aws_lb.main.arn
1093
+ port = "80"
1094
+ protocol = "HTTP"
1095
+
1096
+ default_action {
1097
+ type = "forward"
1098
+ target_group_arn = aws_lb_target_group.main.arn
1099
+ }
1100
+ }
1101
+
1102
+ resource "aws_security_group" "alb" {
1103
+ name = "\${var.project_name}-\${var.environment}-alb-sg"
1104
+ description = "ALB security group"
1105
+ vpc_id = var.vpc_id
1106
+
1107
+ ingress {
1108
+ from_port = 80
1109
+ to_port = 80
1110
+ protocol = "tcp"
1111
+ cidr_blocks = ["0.0.0.0/0"]
1112
+ }
1113
+
1114
+ ingress {
1115
+ from_port = 443
1116
+ to_port = 443
1117
+ protocol = "tcp"
1118
+ cidr_blocks = ["0.0.0.0/0"]
1119
+ }
1120
+
1121
+ egress {
1122
+ from_port = 0
1123
+ to_port = 0
1124
+ protocol = "-1"
1125
+ cidr_blocks = ["0.0.0.0/0"]
1126
+ }
1127
+
1128
+ tags = var.tags
1129
+ }
1130
+
1131
+ resource "aws_security_group" "ecs_tasks" {
1132
+ name = "\${var.project_name}-\${var.environment}-ecs-tasks-sg"
1133
+ description = "ECS tasks security group"
1134
+ vpc_id = var.vpc_id
1135
+
1136
+ ingress {
1137
+ from_port = var.container_port
1138
+ to_port = var.container_port
1139
+ protocol = "tcp"
1140
+ security_groups = [aws_security_group.alb.id]
1141
+ }
1142
+
1143
+ egress {
1144
+ from_port = 0
1145
+ to_port = 0
1146
+ protocol = "-1"
1147
+ cidr_blocks = ["0.0.0.0/0"]
1148
+ }
1149
+
1150
+ tags = var.tags
1151
+ }
1152
+
1153
+ resource "aws_iam_role" "ecs_task_execution" {
1154
+ name = "\${var.project_name}-\${var.environment}-ecs-exec-role"
1155
+
1156
+ assume_role_policy = jsonencode({
1157
+ Version = "2012-10-17"
1158
+ Statement = [{
1159
+ Action = "sts:AssumeRole"
1160
+ Effect = "Allow"
1161
+ Principal = { Service = "ecs-tasks.amazonaws.com" }
1162
+ }]
1163
+ })
1164
+
1165
+ tags = var.tags
1166
+ }
1167
+
1168
+ resource "aws_iam_role_policy_attachment" "ecs_task_execution" {
1169
+ role = aws_iam_role.ecs_task_execution.name
1170
+ policy_arn = "arn:aws:iam::aws:policy/service-role/AmazonECSTaskExecutionRolePolicy"
1171
+ }
1172
+
1173
+ resource "aws_iam_role" "ecs_task" {
1174
+ name = "\${var.project_name}-\${var.environment}-ecs-task-role"
1175
+
1176
+ assume_role_policy = jsonencode({
1177
+ Version = "2012-10-17"
1178
+ Statement = [{
1179
+ Action = "sts:AssumeRole"
1180
+ Effect = "Allow"
1181
+ Principal = { Service = "ecs-tasks.amazonaws.com" }
1182
+ }]
1183
+ })
1184
+
1185
+ tags = var.tags
1186
+ }
1187
+
1188
+ resource "aws_cloudwatch_log_group" "ecs" {
1189
+ name = "/ecs/\${var.project_name}-\${var.environment}"
1190
+ retention_in_days = 30
1191
+
1192
+ tags = var.tags
1193
+ }
1194
+
1195
+ data "aws_region" "current" {}
1196
+ `;
1197
+ case 'kms':
1198
+ return `# KMS Module
1199
+ # Generated by Nimbus
1200
+
1201
+ data "aws_caller_identity" "current" {}
1202
+
1203
+ resource "aws_kms_key" "main" {
1204
+ description = "KMS key for \${var.project_name} \${var.environment}"
1205
+ deletion_window_in_days = var.deletion_window_in_days
1206
+ enable_key_rotation = true
1207
+
1208
+ policy = jsonencode({
1209
+ Version = "2012-10-17"
1210
+ Statement = [
1211
+ {
1212
+ Sid = "EnableRootAccountAccess"
1213
+ Effect = "Allow"
1214
+ Principal = {
1215
+ AWS = "arn:aws:iam::\${data.aws_caller_identity.current.account_id}:root"
1216
+ }
1217
+ Action = "kms:*"
1218
+ Resource = "*"
1219
+ }
1220
+ ]
1221
+ })
1222
+
1223
+ tags = var.tags
1224
+ }
1225
+
1226
+ resource "aws_kms_alias" "main" {
1227
+ name = "alias/\${var.key_alias}"
1228
+ target_key_id = aws_kms_key.main.key_id
1229
+ }
1230
+ `;
1231
+ default:
1232
+ return `# ${component} Module\n# Generated by Nimbus\n`;
1233
+ }
1234
+ }
1235
+ getModuleVariablesTf(component) {
1236
+ const common = `variable "project_name" {
1237
+ type = string
1238
+ }
1239
+
1240
+ variable "environment" {
1241
+ type = string
1242
+ }
1243
+
1244
+ variable "tags" {
1245
+ type = map(string)
1246
+ default = {}
1247
+ }
1248
+ `;
1249
+ switch (component) {
1250
+ case 'vpc':
1251
+ return `${common}
1252
+ variable "vpc_cidr" {
1253
+ type = string
1254
+ default = "10.0.0.0/16"
1255
+ }
1256
+
1257
+ variable "availability_zones" {
1258
+ type = list(string)
1259
+ }
1260
+ `;
1261
+ case 'eks':
1262
+ return `${common}
1263
+ variable "vpc_id" {
1264
+ type = string
1265
+ }
1266
+
1267
+ variable "subnet_ids" {
1268
+ type = list(string)
1269
+ }
1270
+
1271
+ variable "cluster_version" {
1272
+ type = string
1273
+ default = "1.28"
1274
+ }
1275
+
1276
+ variable "node_instance_type" {
1277
+ type = string
1278
+ default = "t3.medium"
1279
+ }
1280
+
1281
+ variable "node_count" {
1282
+ type = number
1283
+ default = 2
1284
+ }
1285
+ `;
1286
+ case 'rds':
1287
+ return `${common}
1288
+ variable "vpc_id" {
1289
+ type = string
1290
+ }
1291
+
1292
+ variable "subnet_ids" {
1293
+ type = list(string)
1294
+ }
1295
+
1296
+ variable "instance_class" {
1297
+ type = string
1298
+ default = "db.t3.micro"
1299
+ }
1300
+
1301
+ variable "engine" {
1302
+ type = string
1303
+ default = "postgres"
1304
+ }
1305
+
1306
+ variable "storage_size" {
1307
+ type = number
1308
+ default = 20
1309
+ }
1310
+ `;
1311
+ case 's3':
1312
+ return `${common}
1313
+ variable "bucket_name" {
1314
+ type = string
1315
+ }
1316
+ `;
1317
+ case 'ecs':
1318
+ return `${common}
1319
+ variable "vpc_id" {
1320
+ description = "VPC ID for the ECS service"
1321
+ type = string
1322
+ }
1323
+
1324
+ variable "public_subnet_ids" {
1325
+ description = "Public subnet IDs for the ALB"
1326
+ type = list(string)
1327
+ }
1328
+
1329
+ variable "private_subnet_ids" {
1330
+ description = "Private subnet IDs for the ECS tasks"
1331
+ type = list(string)
1332
+ }
1333
+
1334
+ variable "container_image" {
1335
+ description = "Docker image for the ECS task"
1336
+ type = string
1337
+ }
1338
+
1339
+ variable "container_port" {
1340
+ description = "Port exposed by the container"
1341
+ type = number
1342
+ default = 8080
1343
+ }
1344
+
1345
+ variable "cpu" {
1346
+ description = "Fargate task CPU units"
1347
+ type = number
1348
+ default = 256
1349
+ }
1350
+
1351
+ variable "memory" {
1352
+ description = "Fargate task memory in MiB"
1353
+ type = number
1354
+ default = 512
1355
+ }
1356
+
1357
+ variable "desired_count" {
1358
+ description = "Number of ECS tasks to run"
1359
+ type = number
1360
+ default = 2
1361
+ }
1362
+ `;
1363
+ case 'kms':
1364
+ return `${common}
1365
+ variable "key_alias" {
1366
+ description = "Alias for the KMS key"
1367
+ type = string
1368
+ }
1369
+
1370
+ variable "deletion_window_in_days" {
1371
+ description = "Number of days before the key is permanently deleted"
1372
+ type = number
1373
+ default = 30
1374
+ }
1375
+ `;
1376
+ default:
1377
+ return common;
1378
+ }
1379
+ }
1380
+ getModuleOutputsTf(component) {
1381
+ switch (component) {
1382
+ case 'vpc':
1383
+ return `output "vpc_id" {
1384
+ value = aws_vpc.main.id
1385
+ }
1386
+
1387
+ output "private_subnet_ids" {
1388
+ value = aws_subnet.private[*].id
1389
+ }
1390
+
1391
+ output "public_subnet_ids" {
1392
+ value = aws_subnet.public[*].id
1393
+ }
1394
+ `;
1395
+ case 'eks':
1396
+ return `output "cluster_endpoint" {
1397
+ value = aws_eks_cluster.main.endpoint
1398
+ }
1399
+
1400
+ output "cluster_name" {
1401
+ value = aws_eks_cluster.main.name
1402
+ }
1403
+ `;
1404
+ case 'rds':
1405
+ return `output "endpoint" {
1406
+ value = aws_db_instance.main.endpoint
1407
+ sensitive = true
1408
+ }
1409
+ `;
1410
+ case 's3':
1411
+ return `output "bucket_arn" {
1412
+ value = aws_s3_bucket.main.arn
1413
+ }
1414
+
1415
+ output "bucket_name" {
1416
+ value = aws_s3_bucket.main.id
1417
+ }
1418
+ `;
1419
+ case 'ecs':
1420
+ return `output "cluster_name" {
1421
+ description = "ECS cluster name"
1422
+ value = aws_ecs_cluster.main.name
1423
+ }
1424
+
1425
+ output "service_name" {
1426
+ description = "ECS service name"
1427
+ value = aws_ecs_service.main.name
1428
+ }
1429
+
1430
+ output "alb_dns_name" {
1431
+ description = "ALB DNS name"
1432
+ value = aws_lb.main.dns_name
1433
+ }
1434
+
1435
+ output "alb_arn" {
1436
+ description = "ALB ARN"
1437
+ value = aws_lb.main.arn
1438
+ }
1439
+
1440
+ output "task_definition_arn" {
1441
+ description = "Task definition ARN"
1442
+ value = aws_ecs_task_definition.main.arn
1443
+ }
1444
+ `;
1445
+ case 'kms':
1446
+ return `output "key_id" {
1447
+ description = "KMS key ID"
1448
+ value = aws_kms_key.main.key_id
1449
+ }
1450
+
1451
+ output "key_arn" {
1452
+ description = "KMS key ARN"
1453
+ value = aws_kms_key.main.arn
1454
+ }
1455
+
1456
+ output "alias_arn" {
1457
+ description = "KMS alias ARN"
1458
+ value = aws_kms_alias.main.arn
1459
+ }
1460
+ `;
1461
+ default:
1462
+ return '';
1463
+ }
1464
+ }
1465
+ }
1466
+ /**
1467
+ * Convenience function — instantiate the generator and run it.
1468
+ * Used by generate-terraform.ts and aws-terraform.ts.
1469
+ */
1470
+ export async function generateTerraformProject(config) {
1471
+ return new TerraformProjectGenerator().generate(config);
1472
+ }