@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,1058 @@
1
+ /**
2
+ * Generate Terraform Command
3
+ *
4
+ * Interactive wizard for AWS infrastructure discovery and Terraform generation
5
+ *
6
+ * Usage: nimbus generate terraform [options]
7
+ */
8
+ import { logger } from '../utils';
9
+ import { createWizard, ui, select, multiSelect, confirm, input, pathInput, } from '../wizard';
10
+ import { generateTerraformProject } from '../generator/terraform';
11
+ // ---- Cloud CLI helpers (replace microservice REST calls) ----
12
+ function getAwsProfiles() {
13
+ try {
14
+ const { execFileSync } = require('child_process');
15
+ const out = execFileSync('aws', ['configure', 'list-profiles'], {
16
+ encoding: 'utf-8', timeout: 5000, stdio: ['pipe', 'pipe', 'pipe'],
17
+ });
18
+ return out.trim().split('\n').map((s) => s.trim()).filter(Boolean);
19
+ }
20
+ catch {
21
+ return ['default'];
22
+ }
23
+ }
24
+ function validateAwsProfile(profile) {
25
+ try {
26
+ const { execFileSync } = require('child_process');
27
+ const out = execFileSync('aws', ['sts', 'get-caller-identity', '--profile', profile, '--output', 'json'], {
28
+ encoding: 'utf-8', timeout: 10000, stdio: ['pipe', 'pipe', 'pipe'],
29
+ });
30
+ const data = JSON.parse(out);
31
+ return { valid: true, accountId: data.Account };
32
+ }
33
+ catch (e) {
34
+ return { valid: false, error: e.message?.slice(0, 100) };
35
+ }
36
+ }
37
+ function getGcpProject() {
38
+ try {
39
+ const { execFileSync } = require('child_process');
40
+ return execFileSync('gcloud', ['config', 'get-value', 'project'], {
41
+ encoding: 'utf-8', timeout: 5000, stdio: ['pipe', 'pipe', 'pipe'],
42
+ }).trim();
43
+ }
44
+ catch {
45
+ return '';
46
+ }
47
+ }
48
+ function validateAzureSubscription(subscriptionId) {
49
+ try {
50
+ const { execFileSync } = require('child_process');
51
+ const out = execFileSync('az', ['account', 'show', '--subscription', subscriptionId, '--output', 'json'], {
52
+ encoding: 'utf-8', timeout: 10000, stdio: ['pipe', 'pipe', 'pipe'],
53
+ });
54
+ const data = JSON.parse(out);
55
+ return { valid: true, name: data.name };
56
+ }
57
+ catch (e) {
58
+ return { valid: false, error: e.message?.slice(0, 100) };
59
+ }
60
+ }
61
+ /**
62
+ * Run the generate terraform command
63
+ */
64
+ export async function generateTerraformCommand(options = {}) {
65
+ logger.info('Starting Terraform generation wizard');
66
+ // Non-interactive mode
67
+ if (options.nonInteractive) {
68
+ await runNonInteractive(options);
69
+ return;
70
+ }
71
+ // Questionnaire mode
72
+ if (options.questionnaire) {
73
+ const { questionnaireCommand } = await import('./questionnaire');
74
+ await questionnaireCommand({
75
+ type: 'terraform',
76
+ outputDir: options.output,
77
+ });
78
+ return;
79
+ }
80
+ // Conversational mode (Mode B)
81
+ if (options.conversational) {
82
+ await runConversational(options);
83
+ return;
84
+ }
85
+ // Interactive wizard mode
86
+ const steps = createWizardSteps();
87
+ const wizard = createWizard({
88
+ title: 'nimbus generate terraform',
89
+ description: 'Generate Terraform from your cloud infrastructure',
90
+ initialContext: {
91
+ provider: 'aws',
92
+ awsProfile: options.profile,
93
+ awsRegions: options.regions,
94
+ servicesToScan: options.services,
95
+ outputPath: options.output,
96
+ },
97
+ steps,
98
+ onEvent: event => {
99
+ if (event.type === 'step:start' && process.stdout.isTTY) {
100
+ const idx = steps.findIndex(s => s.id === event.stepId);
101
+ if (idx >= 0) {
102
+ // Visual step progress bar
103
+ const progress = steps.map((s, i) => {
104
+ if (i < idx) {
105
+ return ui.color(`\u2713 ${s.title}`, 'green');
106
+ }
107
+ if (i === idx) {
108
+ return ui.color(`\u25CF ${s.title}`, 'cyan');
109
+ }
110
+ return ui.dim(`\u25CB ${s.title}`);
111
+ });
112
+ ui.newLine();
113
+ ui.print(ui.dim(' Progress: ') + progress.join(ui.dim(' \u2500 ')));
114
+ }
115
+ }
116
+ logger.debug('Wizard event', { type: event.type });
117
+ },
118
+ });
119
+ const result = await wizard.run();
120
+ if (result.success) {
121
+ ui.newLine();
122
+ ui.box({
123
+ title: 'Complete!',
124
+ content: [
125
+ 'Your infrastructure has been codified as Terraform.',
126
+ '',
127
+ 'Next steps:',
128
+ ` 1. Review the generated files in ${result.context.outputPath}`,
129
+ ' 2. Run "terraform plan" to see what will be imported',
130
+ ' 3. Run "terraform apply" to bring resources under Terraform control',
131
+ '',
132
+ 'Scan saved to history. View with: nimbus infra history',
133
+ ],
134
+ style: 'rounded',
135
+ borderColor: 'green',
136
+ padding: 1,
137
+ });
138
+ }
139
+ else {
140
+ ui.error(`Wizard failed: ${result.error?.message || 'Unknown error'}`);
141
+ process.exit(1);
142
+ }
143
+ }
144
+ /**
145
+ * Create wizard steps
146
+ */
147
+ function createWizardSteps() {
148
+ return [
149
+ // Step 1: Provider Selection
150
+ {
151
+ id: 'provider',
152
+ title: 'Cloud Provider Selection',
153
+ description: 'Select the cloud provider to scan for infrastructure',
154
+ execute: providerSelectionStep,
155
+ },
156
+ // Step 2: AWS Configuration
157
+ {
158
+ id: 'aws-config',
159
+ title: 'AWS Configuration',
160
+ description: 'Configure AWS profile and regions to scan',
161
+ condition: ctx => ctx.provider === 'aws',
162
+ execute: awsConfigStep,
163
+ },
164
+ // Step 3: Service Selection
165
+ {
166
+ id: 'services',
167
+ title: 'Service Selection',
168
+ description: 'Select which AWS services to scan',
169
+ condition: ctx => ctx.provider === 'aws',
170
+ execute: serviceSelectionStep,
171
+ },
172
+ // GCP Configuration
173
+ {
174
+ id: 'gcp-config',
175
+ title: 'GCP Configuration',
176
+ description: 'Configure GCP project and regions to scan',
177
+ condition: ctx => ctx.provider === 'gcp',
178
+ execute: gcpConfigStep,
179
+ },
180
+ // GCP Service Selection
181
+ {
182
+ id: 'gcp-services',
183
+ title: 'GCP Service Selection',
184
+ description: 'Select which GCP services to scan',
185
+ condition: ctx => ctx.provider === 'gcp',
186
+ execute: gcpServiceSelectionStep,
187
+ },
188
+ // Azure Configuration
189
+ {
190
+ id: 'azure-config',
191
+ title: 'Azure Configuration',
192
+ description: 'Configure Azure subscription and resource group',
193
+ condition: ctx => ctx.provider === 'azure',
194
+ execute: azureConfigStep,
195
+ },
196
+ // Azure Service Selection
197
+ {
198
+ id: 'azure-services',
199
+ title: 'Azure Service Selection',
200
+ description: 'Select which Azure services to scan',
201
+ condition: ctx => ctx.provider === 'azure',
202
+ execute: azureServiceSelectionStep,
203
+ },
204
+ // Step 4: Discovery
205
+ {
206
+ id: 'discovery',
207
+ title: 'Infrastructure Discovery',
208
+ description: 'Scanning your AWS infrastructure...',
209
+ execute: discoveryStep,
210
+ },
211
+ // Step 5: Generation Options
212
+ {
213
+ id: 'generation-options',
214
+ title: 'Generation Options',
215
+ description: 'Configure Terraform generation options',
216
+ execute: generationOptionsStep,
217
+ },
218
+ // Step 6: Output Location
219
+ {
220
+ id: 'output',
221
+ title: 'Output Location',
222
+ description: 'Where should the Terraform files be saved?',
223
+ execute: outputLocationStep,
224
+ },
225
+ // Future steps (Phase 2+):
226
+ // - Terraform Generation
227
+ // - Best Practices Analysis
228
+ // - Interactive Review
229
+ // - Starter Kit Generation
230
+ // - Terraform Operations
231
+ ];
232
+ }
233
+ /**
234
+ * Step 1: Provider Selection
235
+ */
236
+ async function providerSelectionStep(ctx) {
237
+ const provider = await select({
238
+ message: 'Select cloud provider:',
239
+ options: [
240
+ {
241
+ value: 'aws',
242
+ label: 'AWS (Amazon Web Services)',
243
+ description: 'Scan EC2, S3, RDS, Lambda, VPC, IAM, and more',
244
+ },
245
+ {
246
+ value: 'gcp',
247
+ label: 'GCP (Google Cloud Platform)',
248
+ description: 'Scan Compute, GCS, GKE, Cloud Functions, VPC, IAM',
249
+ },
250
+ {
251
+ value: 'azure',
252
+ label: 'Azure (Microsoft Azure)',
253
+ description: 'Scan VMs, Storage, AKS, Functions, VNet, IAM',
254
+ },
255
+ ],
256
+ defaultValue: ctx.provider || 'aws',
257
+ });
258
+ if (!provider) {
259
+ return { success: false, error: 'No provider selected' };
260
+ }
261
+ return {
262
+ success: true,
263
+ data: { provider },
264
+ };
265
+ }
266
+ /**
267
+ * Step 2: AWS Configuration
268
+ */
269
+ async function awsConfigStep(ctx) {
270
+ // Fetch available profiles via CLI
271
+ ui.startSpinner({ message: 'Fetching AWS profiles...' });
272
+ const profileNames = getAwsProfiles();
273
+ ui.stopSpinnerSuccess(`Found ${profileNames.length} AWS profile(s)`);
274
+ // Profile selection
275
+ let selectedProfile = ctx.awsProfile;
276
+ if (!selectedProfile) {
277
+ const profileOptions = profileNames.map(p => ({ value: p, label: p }));
278
+ selectedProfile = await select({
279
+ message: 'Select AWS profile:',
280
+ options: profileOptions,
281
+ defaultValue: 'default',
282
+ });
283
+ if (!selectedProfile) {
284
+ return { success: false, error: 'No profile selected' };
285
+ }
286
+ }
287
+ // Validate credentials via CLI
288
+ ui.startSpinner({ message: `Validating credentials for profile "${selectedProfile}"...` });
289
+ const validation = validateAwsProfile(selectedProfile);
290
+ if (!validation.valid) {
291
+ ui.stopSpinnerFail(`Invalid credentials: ${validation.error || 'Unknown error'}`);
292
+ return { success: false, error: 'Invalid AWS credentials' };
293
+ }
294
+ ui.stopSpinnerSuccess(`Authenticated to account ${validation.accountId || 'unknown'}`);
295
+ ctx.awsAccountId = validation.accountId;
296
+ // Region selection
297
+ ui.newLine();
298
+ const regionChoice = await select({
299
+ message: 'Select regions to scan:',
300
+ options: [
301
+ {
302
+ value: 'all',
303
+ label: 'All enabled regions',
304
+ description: 'Scan all regions enabled for your account',
305
+ },
306
+ {
307
+ value: 'specific',
308
+ label: 'Specific regions',
309
+ description: 'Select specific regions to scan',
310
+ },
311
+ ],
312
+ defaultValue: 'all',
313
+ });
314
+ let selectedRegions = [];
315
+ if (regionChoice === 'specific') {
316
+ // Hardcoded common AWS regions (no service needed)
317
+ const regionOptions = [
318
+ { value: 'us-east-1', label: 'us-east-1 - N. Virginia' },
319
+ { value: 'us-east-2', label: 'us-east-2 - Ohio' },
320
+ { value: 'us-west-1', label: 'us-west-1 - N. California' },
321
+ { value: 'us-west-2', label: 'us-west-2 - Oregon' },
322
+ { value: 'eu-west-1', label: 'eu-west-1 - Ireland' },
323
+ { value: 'eu-central-1', label: 'eu-central-1 - Frankfurt' },
324
+ { value: 'ap-southeast-1', label: 'ap-southeast-1 - Singapore' },
325
+ { value: 'ap-northeast-1', label: 'ap-northeast-1 - Tokyo' },
326
+ ];
327
+ selectedRegions = (await multiSelect({
328
+ message: 'Select regions to scan:',
329
+ options: regionOptions,
330
+ required: true,
331
+ }));
332
+ }
333
+ return {
334
+ success: true,
335
+ data: {
336
+ awsProfile: selectedProfile,
337
+ awsRegions: regionChoice === 'all' ? undefined : selectedRegions,
338
+ },
339
+ };
340
+ }
341
+ /**
342
+ * Step 3: Service Selection
343
+ */
344
+ async function serviceSelectionStep(_ctx) {
345
+ const serviceChoice = await select({
346
+ message: 'Select services to scan:',
347
+ options: [
348
+ {
349
+ value: 'all',
350
+ label: 'All supported services',
351
+ description: 'EC2, S3, RDS, Lambda, VPC, IAM, ECS, EKS, DynamoDB, CloudFront',
352
+ },
353
+ {
354
+ value: 'specific',
355
+ label: 'Specific services',
356
+ description: 'Select specific services to scan',
357
+ },
358
+ ],
359
+ defaultValue: 'all',
360
+ });
361
+ if (serviceChoice === 'all') {
362
+ return { success: true, data: { servicesToScan: undefined } };
363
+ }
364
+ const serviceOptions = [
365
+ { value: 'EC2', label: 'EC2', description: 'Instances, volumes, security groups, AMIs' },
366
+ { value: 'S3', label: 'S3', description: 'Buckets and bucket policies' },
367
+ { value: 'RDS', label: 'RDS', description: 'Database instances and clusters' },
368
+ { value: 'Lambda', label: 'Lambda', description: 'Functions and layers' },
369
+ { value: 'VPC', label: 'VPC', description: 'VPCs, subnets, route tables, NAT gateways' },
370
+ { value: 'IAM', label: 'IAM', description: 'Roles, policies, users, groups' },
371
+ { value: 'ECS', label: 'ECS', description: 'Clusters, services, task definitions' },
372
+ { value: 'EKS', label: 'EKS', description: 'Clusters and node groups' },
373
+ { value: 'DynamoDB', label: 'DynamoDB', description: 'Tables' },
374
+ { value: 'CloudFront', label: 'CloudFront', description: 'Distributions' },
375
+ ];
376
+ const selectedServices = await multiSelect({
377
+ message: 'Select services to scan:',
378
+ options: serviceOptions,
379
+ required: true,
380
+ });
381
+ return {
382
+ success: true,
383
+ data: { servicesToScan: selectedServices },
384
+ };
385
+ }
386
+ /**
387
+ * GCP Configuration Step
388
+ */
389
+ async function gcpConfigStep(ctx) {
390
+ // Project ID
391
+ const projectId = await input({
392
+ message: 'Enter your GCP project ID:',
393
+ defaultValue: ctx.gcpProject || '',
394
+ });
395
+ if (!projectId) {
396
+ return { success: false, error: 'GCP project ID is required' };
397
+ }
398
+ // Validate project access via gcloud CLI
399
+ ui.startSpinner({ message: `Validating access to project "${projectId}"...` });
400
+ try {
401
+ const { execFileSync } = await import('child_process');
402
+ execFileSync('gcloud', ['projects', 'describe', projectId, '--format=json'], {
403
+ encoding: 'utf-8', timeout: 10000, stdio: ['pipe', 'pipe', 'pipe'],
404
+ });
405
+ ui.stopSpinnerSuccess(`Connected to project ${projectId}`);
406
+ }
407
+ catch (error) {
408
+ ui.stopSpinnerFail(`Could not validate project: ${error.message?.slice(0, 80) || 'unknown'}`);
409
+ // Non-fatal — user may still proceed if gcloud is not configured
410
+ ui.info('Proceeding without validation. Ensure gcloud credentials are configured.');
411
+ }
412
+ // Region selection
413
+ ui.newLine();
414
+ const regionChoice = await select({
415
+ message: 'Select regions to scan:',
416
+ options: [
417
+ {
418
+ value: 'all',
419
+ label: 'All available regions',
420
+ description: 'Scan all GCP regions',
421
+ },
422
+ {
423
+ value: 'specific',
424
+ label: 'Specific regions',
425
+ description: 'Select specific regions to scan',
426
+ },
427
+ ],
428
+ defaultValue: 'all',
429
+ });
430
+ let selectedRegions = [];
431
+ if (regionChoice === 'specific') {
432
+ const gcpRegionOptions = [
433
+ { value: 'us-central1', label: 'us-central1 - Iowa' },
434
+ { value: 'us-east1', label: 'us-east1 - South Carolina' },
435
+ { value: 'us-east4', label: 'us-east4 - Northern Virginia' },
436
+ { value: 'us-west1', label: 'us-west1 - Oregon' },
437
+ { value: 'europe-west1', label: 'europe-west1 - Belgium' },
438
+ { value: 'europe-west2', label: 'europe-west2 - London' },
439
+ { value: 'asia-east1', label: 'asia-east1 - Taiwan' },
440
+ { value: 'asia-southeast1', label: 'asia-southeast1 - Singapore' },
441
+ ];
442
+ selectedRegions = (await multiSelect({
443
+ message: 'Select GCP regions to scan:',
444
+ options: gcpRegionOptions,
445
+ required: true,
446
+ }));
447
+ }
448
+ return {
449
+ success: true,
450
+ data: {
451
+ gcpProject: projectId,
452
+ gcpRegions: regionChoice === 'all' ? undefined : selectedRegions,
453
+ },
454
+ };
455
+ }
456
+ /**
457
+ * GCP Service Selection Step
458
+ */
459
+ async function gcpServiceSelectionStep(_ctx) {
460
+ const serviceChoice = await select({
461
+ message: 'Select GCP services to scan:',
462
+ options: [
463
+ {
464
+ value: 'all',
465
+ label: 'All supported services',
466
+ description: 'Compute, GCS, GKE, Cloud Functions, VPC, IAM, Cloud SQL, Pub/Sub',
467
+ },
468
+ {
469
+ value: 'specific',
470
+ label: 'Specific services',
471
+ description: 'Select specific services to scan',
472
+ },
473
+ ],
474
+ defaultValue: 'all',
475
+ });
476
+ if (serviceChoice === 'all') {
477
+ return { success: true, data: { servicesToScan: undefined } };
478
+ }
479
+ const serviceOptions = [
480
+ { value: 'Compute', label: 'Compute Engine', description: 'VMs, disks, images' },
481
+ { value: 'GCS', label: 'Cloud Storage', description: 'Buckets and objects' },
482
+ { value: 'GKE', label: 'Google Kubernetes Engine', description: 'Clusters and node pools' },
483
+ { value: 'CloudFunctions', label: 'Cloud Functions', description: 'Serverless functions' },
484
+ { value: 'VPC', label: 'VPC Network', description: 'Networks, subnets, firewalls' },
485
+ { value: 'IAM', label: 'IAM', description: 'Roles, service accounts, policies' },
486
+ { value: 'CloudSQL', label: 'Cloud SQL', description: 'Database instances' },
487
+ { value: 'PubSub', label: 'Pub/Sub', description: 'Topics and subscriptions' },
488
+ ];
489
+ const selectedServices = await multiSelect({
490
+ message: 'Select GCP services to scan:',
491
+ options: serviceOptions,
492
+ required: true,
493
+ });
494
+ return {
495
+ success: true,
496
+ data: { servicesToScan: selectedServices },
497
+ };
498
+ }
499
+ /**
500
+ * Azure Configuration Step
501
+ */
502
+ async function azureConfigStep(ctx) {
503
+ // Subscription ID
504
+ const subscriptionId = await input({
505
+ message: 'Enter your Azure subscription ID:',
506
+ defaultValue: ctx.azureSubscription || '',
507
+ });
508
+ if (!subscriptionId) {
509
+ return { success: false, error: 'Azure subscription ID is required' };
510
+ }
511
+ // Validate subscription access via Azure CLI
512
+ ui.startSpinner({ message: `Validating access to subscription "${subscriptionId}"...` });
513
+ const azVal = validateAzureSubscription(subscriptionId);
514
+ if (!azVal.valid) {
515
+ ui.stopSpinnerFail(`Could not validate subscription: ${azVal.error || 'unknown'}`);
516
+ ui.info('Proceeding without validation. Ensure az CLI credentials are configured.');
517
+ }
518
+ else {
519
+ ui.stopSpinnerSuccess(`Connected to subscription${azVal.name ? ` (${azVal.name})` : ''}`);
520
+ }
521
+ // Resource group (optional)
522
+ ui.newLine();
523
+ const resourceGroup = await input({
524
+ message: 'Resource group (leave empty to scan all):',
525
+ defaultValue: ctx.azureResourceGroup || '',
526
+ });
527
+ // Region selection
528
+ ui.newLine();
529
+ const regionChoice = await select({
530
+ message: 'Select regions to scan:',
531
+ options: [
532
+ {
533
+ value: 'all',
534
+ label: 'All available regions',
535
+ description: 'Scan all Azure regions',
536
+ },
537
+ {
538
+ value: 'specific',
539
+ label: 'Specific regions',
540
+ description: 'Select specific regions to scan',
541
+ },
542
+ ],
543
+ defaultValue: 'all',
544
+ });
545
+ let _selectedRegions = [];
546
+ if (regionChoice === 'specific') {
547
+ const azureRegionOptions = [
548
+ { value: 'eastus', label: 'East US' },
549
+ { value: 'eastus2', label: 'East US 2' },
550
+ { value: 'westus2', label: 'West US 2' },
551
+ { value: 'centralus', label: 'Central US' },
552
+ { value: 'westeurope', label: 'West Europe' },
553
+ { value: 'northeurope', label: 'North Europe' },
554
+ { value: 'southeastasia', label: 'Southeast Asia' },
555
+ { value: 'eastasia', label: 'East Asia' },
556
+ ];
557
+ _selectedRegions = (await multiSelect({
558
+ message: 'Select Azure regions to scan:',
559
+ options: azureRegionOptions,
560
+ required: true,
561
+ }));
562
+ }
563
+ return {
564
+ success: true,
565
+ data: {
566
+ azureSubscription: subscriptionId,
567
+ azureResourceGroup: resourceGroup || undefined,
568
+ },
569
+ };
570
+ }
571
+ /**
572
+ * Azure Service Selection Step
573
+ */
574
+ async function azureServiceSelectionStep(_ctx) {
575
+ const serviceChoice = await select({
576
+ message: 'Select Azure services to scan:',
577
+ options: [
578
+ {
579
+ value: 'all',
580
+ label: 'All supported services',
581
+ description: 'VMs, Storage, AKS, Functions, VNet, IAM, SQL, Service Bus',
582
+ },
583
+ {
584
+ value: 'specific',
585
+ label: 'Specific services',
586
+ description: 'Select specific services to scan',
587
+ },
588
+ ],
589
+ defaultValue: 'all',
590
+ });
591
+ if (serviceChoice === 'all') {
592
+ return { success: true, data: { servicesToScan: undefined } };
593
+ }
594
+ const serviceOptions = [
595
+ { value: 'VirtualMachines', label: 'Virtual Machines', description: 'VMs, disks, images' },
596
+ {
597
+ value: 'Storage',
598
+ label: 'Storage Accounts',
599
+ description: 'Blob, file, queue, table storage',
600
+ },
601
+ { value: 'AKS', label: 'Azure Kubernetes Service', description: 'Clusters and node pools' },
602
+ { value: 'Functions', label: 'Azure Functions', description: 'Serverless functions' },
603
+ { value: 'VNet', label: 'Virtual Network', description: 'VNets, subnets, NSGs' },
604
+ { value: 'IAM', label: 'IAM', description: 'Role assignments, managed identities' },
605
+ { value: 'SQLDatabase', label: 'Azure SQL', description: 'SQL databases and servers' },
606
+ { value: 'ServiceBus', label: 'Service Bus', description: 'Queues and topics' },
607
+ ];
608
+ const selectedServices = await multiSelect({
609
+ message: 'Select Azure services to scan:',
610
+ options: serviceOptions,
611
+ required: true,
612
+ });
613
+ return {
614
+ success: true,
615
+ data: { servicesToScan: selectedServices },
616
+ };
617
+ }
618
+ /**
619
+ * Run synchronous CLI-based infrastructure discovery.
620
+ * Replaces the old REST polling approach.
621
+ */
622
+ async function discoverInfra(ctx) {
623
+ const { execFileSync } = await import('child_process');
624
+ const components = [];
625
+ let resourceCount = 0;
626
+ if (ctx.provider === 'aws') {
627
+ const profile = ctx.awsProfile || 'default';
628
+ const env = { ...process.env, AWS_PROFILE: profile };
629
+ // EC2 instances
630
+ try {
631
+ const out = execFileSync('aws', ['ec2', 'describe-instances', '--query', 'Reservations[*].Instances[*].InstanceId', '--output', 'json'], { encoding: 'utf-8', timeout: 15000, stdio: ['pipe', 'pipe', 'pipe'], env });
632
+ const ids = JSON.parse(out).flat();
633
+ if (ids.length > 0) {
634
+ components.push('ec2');
635
+ resourceCount += ids.length;
636
+ }
637
+ }
638
+ catch { /* not available */ }
639
+ // S3 buckets
640
+ try {
641
+ const out = execFileSync('aws', ['s3', 'ls'], { encoding: 'utf-8', timeout: 10000, stdio: ['pipe', 'pipe', 'pipe'], env });
642
+ const buckets = out.trim().split('\n').filter(Boolean).length;
643
+ if (buckets > 0) {
644
+ components.push('s3');
645
+ resourceCount += buckets;
646
+ }
647
+ }
648
+ catch { /* not available */ }
649
+ // RDS
650
+ try {
651
+ const out = execFileSync('aws', ['rds', 'describe-db-instances', '--query', 'DBInstances[*].DBInstanceIdentifier', '--output', 'json'], { encoding: 'utf-8', timeout: 15000, stdio: ['pipe', 'pipe', 'pipe'], env });
652
+ const dbs = JSON.parse(out);
653
+ if (dbs.length > 0) {
654
+ components.push('rds');
655
+ resourceCount += dbs.length;
656
+ }
657
+ }
658
+ catch { /* not available */ }
659
+ // EKS clusters
660
+ try {
661
+ const out = execFileSync('aws', ['eks', 'list-clusters', '--output', 'json'], { encoding: 'utf-8', timeout: 15000, stdio: ['pipe', 'pipe', 'pipe'], env });
662
+ const clusters = JSON.parse(out).clusters;
663
+ if (clusters?.length > 0) {
664
+ components.push('eks');
665
+ resourceCount += clusters.length;
666
+ }
667
+ }
668
+ catch { /* not available */ }
669
+ // VPC (always include as foundational)
670
+ components.push('vpc');
671
+ }
672
+ else if (ctx.provider === 'gcp') {
673
+ try {
674
+ execFileSync('gcloud', ['compute', 'instances', 'list', '--format=json'], { encoding: 'utf-8', timeout: 15000, stdio: ['pipe', 'pipe', 'pipe'] });
675
+ components.push('compute');
676
+ }
677
+ catch { /* not available */ }
678
+ components.push('vpc');
679
+ }
680
+ else if (ctx.provider === 'azure') {
681
+ try {
682
+ const out = execFileSync('az', ['resource', 'list', '--output', 'json'], { encoding: 'utf-8', timeout: 20000, stdio: ['pipe', 'pipe', 'pipe'] });
683
+ const resources = JSON.parse(out);
684
+ resourceCount += resources.length;
685
+ components.push('vnet');
686
+ }
687
+ catch { /* not available */ }
688
+ }
689
+ return { resourceCount, components: [...new Set(components)] };
690
+ }
691
+ /**
692
+ * Step: Discovery — uses direct CLI calls instead of REST polling
693
+ */
694
+ async function discoveryStep(ctx) {
695
+ ui.startSpinner({ message: 'Discovering infrastructure via CLI...' });
696
+ try {
697
+ const { resourceCount, components } = await discoverInfra(ctx);
698
+ ui.stopSpinnerSuccess(`Discovery complete — found ${resourceCount} resource(s), components: ${components.join(', ') || 'vpc'}`);
699
+ ctx.discoveredComponents = components;
700
+ return { success: true, data: { discoveredComponents: components } };
701
+ }
702
+ catch (e) {
703
+ ui.stopSpinnerFail('Discovery failed');
704
+ ui.warning(`Could not auto-discover: ${e.message}. You can still generate a template.`);
705
+ return { success: true, data: { discoveredComponents: ['vpc'] } };
706
+ }
707
+ }
708
+ /**
709
+ * Step 5: Generation Options
710
+ */
711
+ async function generationOptionsStep(_ctx) {
712
+ // Import method
713
+ const importMethod = await select({
714
+ message: 'How should imports be generated?',
715
+ options: [
716
+ {
717
+ value: 'both',
718
+ label: 'Both import blocks and shell script (Recommended)',
719
+ description: 'Maximum compatibility with all Terraform versions',
720
+ },
721
+ {
722
+ value: 'blocks',
723
+ label: 'Import blocks only (Terraform 1.5+)',
724
+ description: 'Modern declarative imports',
725
+ },
726
+ {
727
+ value: 'script',
728
+ label: 'Shell script only',
729
+ description: 'Traditional terraform import commands',
730
+ },
731
+ ],
732
+ defaultValue: 'both',
733
+ });
734
+ // Starter kit options
735
+ ui.newLine();
736
+ const includeStarterKit = await confirm({
737
+ message: 'Generate starter kit (README, .gitignore, Makefile, CI/CD)?',
738
+ defaultValue: true,
739
+ });
740
+ return {
741
+ success: true,
742
+ data: {
743
+ importMethod,
744
+ includeReadme: includeStarterKit,
745
+ includeGitignore: includeStarterKit,
746
+ includeMakefile: includeStarterKit,
747
+ includeGithubActions: includeStarterKit,
748
+ },
749
+ };
750
+ }
751
+ /**
752
+ * Step 6: Output Location
753
+ */
754
+ async function outputLocationStep(ctx) {
755
+ const outputPath = await pathInput('Where should the Terraform files be saved?', ctx.outputPath || './terraform-infrastructure');
756
+ if (!outputPath) {
757
+ return { success: false, error: 'Output path is required' };
758
+ }
759
+ // Ask about saving preferences
760
+ ui.newLine();
761
+ const savePreferences = await confirm({
762
+ message: 'Save your preferences as organization policy for future runs?',
763
+ defaultValue: false,
764
+ });
765
+ return {
766
+ success: true,
767
+ data: {
768
+ outputPath,
769
+ savePreferences,
770
+ },
771
+ };
772
+ }
773
+ /**
774
+ * Run in conversational mode (Mode B)
775
+ * Uses the generator service's conversational endpoints to describe infrastructure
776
+ * in natural language and generate Terraform from the conversation.
777
+ */
778
+ async function runConversational(options) {
779
+ const crypto = await import('crypto');
780
+ const fs = await import('fs/promises');
781
+ const pathMod = await import('path');
782
+ const sessionId = crypto.randomUUID();
783
+ ui.header('nimbus generate terraform', 'Conversational mode');
784
+ ui.print('Describe your infrastructure in natural language.');
785
+ ui.print('Type "generate" or "done" when ready to generate Terraform.');
786
+ ui.print('Type "exit" to quit.');
787
+ ui.newLine();
788
+ for (;;) {
789
+ const message = await input({
790
+ message: 'You:',
791
+ defaultValue: '',
792
+ });
793
+ if (!message || message.trim() === '') {
794
+ continue;
795
+ }
796
+ const trimmed = message.trim().toLowerCase();
797
+ if (trimmed === 'exit') {
798
+ ui.info('Exiting conversational mode.');
799
+ return;
800
+ }
801
+ // User explicitly wants to generate
802
+ if (trimmed === 'generate' || trimmed === 'done') {
803
+ const generated = await generateFromConversation(sessionId, options, fs, pathMod);
804
+ if (generated) {
805
+ ui.newLine();
806
+ ui.print('You can refine the generated Terraform by continuing the conversation.');
807
+ ui.print('Type "generate" to regenerate, or "exit" to finish.');
808
+ ui.newLine();
809
+ continue; // stays in the while(true) loop with same sessionId
810
+ }
811
+ return;
812
+ }
813
+ // Build request from conversational description — use chatCommand for natural language interaction
814
+ ui.newLine();
815
+ ui.info(`You said: "${message}"`);
816
+ ui.info('Type "generate" or "done" to generate Terraform from this description, or describe your infrastructure further.');
817
+ ui.newLine();
818
+ }
819
+ }
820
+ /**
821
+ * Generate Terraform files from a conversational session using the local generator
822
+ */
823
+ async function generateFromConversation(_sessionId, options, fs, pathMod) {
824
+ ui.newLine();
825
+ ui.startSpinner({ message: 'Generating Terraform from description...' });
826
+ try {
827
+ const provider = options.provider || 'aws';
828
+ const outputDir = options.output || './infrastructure';
829
+ const generatedProject = await generateTerraformProject({
830
+ projectName: 'infrastructure',
831
+ provider: provider,
832
+ region: options.regions?.[0] || (provider === 'aws' ? 'us-east-1' : provider === 'gcp' ? 'us-central1' : 'eastus'),
833
+ components: options.services || ['vpc'],
834
+ });
835
+ ui.stopSpinnerSuccess('Terraform code generated');
836
+ const files = generatedProject.files;
837
+ await fs.mkdir(outputDir, { recursive: true });
838
+ for (const file of files) {
839
+ const filePath = pathMod.join(outputDir, file.path);
840
+ await fs.mkdir(pathMod.dirname(filePath), { recursive: true });
841
+ await fs.writeFile(filePath, file.content);
842
+ }
843
+ ui.newLine();
844
+ ui.success(`Generated ${files.length} Terraform file(s) in ${outputDir}`);
845
+ ui.newLine();
846
+ ui.print('Generated files:');
847
+ for (const file of files) {
848
+ ui.print(` ${ui.color('●', 'green')} ${file.path}`);
849
+ }
850
+ ui.newLine();
851
+ ui.print('Next steps:');
852
+ ui.print(` 1. Review the generated files in ${outputDir}`);
853
+ ui.print(' 2. Run "terraform plan" to preview changes');
854
+ ui.print(' 3. Run "terraform apply" to create infrastructure');
855
+ return true;
856
+ }
857
+ catch (error) {
858
+ ui.stopSpinnerFail('Generation failed');
859
+ ui.error(`Failed to generate Terraform: ${error.message}`);
860
+ return false;
861
+ }
862
+ }
863
+ /**
864
+ * Run in non-interactive mode
865
+ */
866
+ async function runNonInteractive(options) {
867
+ ui.header('nimbus generate terraform', 'Non-interactive mode');
868
+ const provider = options.provider || 'aws';
869
+ // Validate required flags per provider
870
+ if (provider === 'aws' && !options.profile) {
871
+ ui.error('AWS profile is required in non-interactive mode (--profile)');
872
+ process.exit(1);
873
+ }
874
+ if (provider === 'gcp' && !options.gcpProject) {
875
+ ui.error('GCP project is required in non-interactive mode (--gcp-project)');
876
+ process.exit(1);
877
+ }
878
+ if (provider === 'azure' && !options.azureSubscription) {
879
+ ui.error('Azure subscription is required in non-interactive mode (--azure-subscription)');
880
+ process.exit(1);
881
+ }
882
+ ui.info(`Provider: ${provider}`);
883
+ if (provider === 'aws') {
884
+ ui.info(`Profile: ${options.profile}`);
885
+ }
886
+ else if (provider === 'gcp') {
887
+ ui.info(`Project: ${options.gcpProject}`);
888
+ }
889
+ else if (provider === 'azure') {
890
+ ui.info(`Subscription: ${options.azureSubscription}`);
891
+ }
892
+ ui.info(`Regions: ${options.regions?.join(', ') || 'all'}`);
893
+ ui.info(`Services: ${options.services?.join(', ') || 'all'}`);
894
+ ui.info(`Output: ${options.output || './terraform-infrastructure'}`);
895
+ ui.newLine();
896
+ // Build discovery context
897
+ const ctx = {
898
+ provider,
899
+ awsProfile: options.profile,
900
+ awsRegions: options.regions,
901
+ gcpProject: options.gcpProject,
902
+ azureSubscription: options.azureSubscription,
903
+ servicesToScan: options.services,
904
+ outputPath: options.output || './terraform-infrastructure',
905
+ };
906
+ // Run direct CLI discovery
907
+ ui.info('Starting infrastructure discovery...');
908
+ ui.newLine();
909
+ const { components: discoveredComponents } = await discoverInfra(ctx).catch(() => ({ components: ['vpc'] }));
910
+ ui.success(`Discovered components: ${discoveredComponents.join(', ')}`);
911
+ ui.newLine();
912
+ // Generate Terraform from discovered inventory using src/generator/terraform.ts
913
+ ui.startSpinner({ message: 'Generating Terraform code...' });
914
+ try {
915
+ const outputDir = options.output || './terraform-infrastructure';
916
+ const components = options.services || discoveredComponents;
917
+ const generatedProject = await generateTerraformProject({
918
+ projectName: 'infrastructure',
919
+ provider: provider,
920
+ region: options.regions?.[0] || (provider === 'aws' ? 'us-east-1' : provider === 'gcp' ? 'us-central1' : 'eastus'),
921
+ components,
922
+ });
923
+ ui.stopSpinnerSuccess('Terraform code generated');
924
+ // Write generated files
925
+ const fs = await import('fs/promises');
926
+ const path = await import('path');
927
+ await fs.mkdir(outputDir, { recursive: true });
928
+ const files = generatedProject.files;
929
+ for (const file of files) {
930
+ const filePath = path.join(outputDir, file.path);
931
+ await fs.mkdir(path.dirname(filePath), { recursive: true });
932
+ await fs.writeFile(filePath, file.content);
933
+ }
934
+ if (options.jsonOutput) {
935
+ const summary = {
936
+ success: true,
937
+ provider,
938
+ outputDir,
939
+ filesGenerated: files.map(f => f.path),
940
+ componentsGenerated: components,
941
+ };
942
+ console.log(JSON.stringify(summary, null, 2));
943
+ }
944
+ else {
945
+ ui.newLine();
946
+ ui.success(`Generated ${files.length} Terraform file(s) in ${outputDir}`);
947
+ ui.newLine();
948
+ ui.print('Generated files:');
949
+ for (const file of files) {
950
+ ui.print(` ${ui.color('●', 'green')} ${file.path}`);
951
+ }
952
+ ui.newLine();
953
+ ui.print('Next steps:');
954
+ ui.print(` 1. Review the generated files in ${outputDir}`);
955
+ ui.print(' 2. Run "terraform plan" to see what will be imported');
956
+ ui.print(' 3. Run "terraform apply" to bring resources under Terraform control');
957
+ }
958
+ }
959
+ catch (error) {
960
+ ui.stopSpinnerFail('Generation failed');
961
+ ui.error(`Failed to generate Terraform: ${error.message}`);
962
+ process.exit(1);
963
+ }
964
+ }
965
+ /**
966
+ * Run post-generation validation using terraform fmt/validate if available.
967
+ * Non-blocking: warnings shown but errors don't abort.
968
+ */
969
+ async function runPostGenerationValidation(files, jsonOutput) {
970
+ if (!jsonOutput) {
971
+ ui.newLine();
972
+ ui.info('Tip: Run "terraform init && terraform validate" in the output directory to validate the generated files.');
973
+ }
974
+ return undefined;
975
+ }
976
+ /**
977
+ * Display a human-readable validation report.
978
+ * Shows results for terraform fmt, terraform validate, tflint, and checkov.
979
+ * Tools that are not installed show as "not installed" gracefully.
980
+ */
981
+ function displayValidationReport(report) {
982
+ const items = report.items || [];
983
+ const summary = report.summary || { errors: 0, warnings: 0, info: 0 };
984
+ // Overall status
985
+ const isValid = report.valid !== false && summary.errors === 0;
986
+ if (isValid) {
987
+ ui.print(` ${ui.color('\u2713', 'green')} Validation passed`);
988
+ }
989
+ else {
990
+ ui.print(` ${ui.color('\u2717', 'red')} Validation found issues`);
991
+ }
992
+ // Summary line
993
+ const parts = [];
994
+ if (summary.errors > 0) {
995
+ parts.push(ui.color(`${summary.errors} error(s)`, 'red'));
996
+ }
997
+ if (summary.warnings > 0) {
998
+ parts.push(ui.color(`${summary.warnings} warning(s)`, 'yellow'));
999
+ }
1000
+ if (summary.info > 0) {
1001
+ parts.push(ui.dim(`${summary.info} info`));
1002
+ }
1003
+ if (parts.length > 0) {
1004
+ ui.print(` Summary: ${parts.join(', ')}`);
1005
+ }
1006
+ // Tool-level results (grouped by rule prefix)
1007
+ const toolStatus = {
1008
+ 'terraform-fmt': 'pass',
1009
+ 'terraform-validate': 'pass',
1010
+ tflint: 'pass',
1011
+ checkov: 'pass',
1012
+ };
1013
+ for (const item of items) {
1014
+ if (item.severity === 'error' || item.severity === 'warning') {
1015
+ const rule = item.rule || '';
1016
+ if (rule.startsWith('fmt') || rule.includes('format')) {
1017
+ toolStatus['terraform-fmt'] = 'fail';
1018
+ }
1019
+ else if (rule.startsWith('hcl') || rule.includes('syntax')) {
1020
+ toolStatus['terraform-validate'] = 'fail';
1021
+ }
1022
+ else if (rule.startsWith('require-') || rule.includes('anti-pattern')) {
1023
+ toolStatus['tflint'] = 'fail';
1024
+ }
1025
+ else if (rule.startsWith('checkov') || rule.includes('security')) {
1026
+ toolStatus['checkov'] = 'fail';
1027
+ }
1028
+ }
1029
+ }
1030
+ ui.newLine();
1031
+ ui.print(' Tool Results:');
1032
+ for (const [tool, status] of Object.entries(toolStatus)) {
1033
+ const icon = status === 'pass'
1034
+ ? ui.color('\u2713', 'green')
1035
+ : status === 'fail'
1036
+ ? ui.color('\u2717', 'red')
1037
+ : ui.dim('-');
1038
+ const label = status === 'not-installed' ? ui.dim('not installed') : status;
1039
+ ui.print(` ${icon} ${tool}: ${label}`);
1040
+ }
1041
+ // Show first 5 error/warning details
1042
+ const significant = items.filter(i => i.severity === 'error' || i.severity === 'warning');
1043
+ if (significant.length > 0) {
1044
+ ui.newLine();
1045
+ ui.print(' Details:');
1046
+ const toShow = significant.slice(0, 5);
1047
+ for (const item of toShow) {
1048
+ const sevIcon = item.severity === 'error' ? ui.color('E', 'red') : ui.color('W', 'yellow');
1049
+ const fileInfo = item.file ? ` (${item.file})` : '';
1050
+ ui.print(` [${sevIcon}] ${item.message}${fileInfo}`);
1051
+ }
1052
+ if (significant.length > 5) {
1053
+ ui.print(ui.dim(` ... and ${significant.length - 5} more`));
1054
+ }
1055
+ }
1056
+ }
1057
+ // Export as default command
1058
+ export default generateTerraformCommand;