@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
@@ -7,7 +7,6 @@
7
7
  */
8
8
 
9
9
  import { logger } from '../utils';
10
- import { RestClient } from '../clients';
11
10
  import {
12
11
  createWizard,
13
12
  ui,
@@ -20,22 +19,58 @@ import {
20
19
  type WizardStep,
21
20
  type StepResult,
22
21
  } from '../wizard';
22
+ import { generateTerraformProject, type GeneratedFile } from '../generator/terraform';
23
23
 
24
- // AWS Tools Service client
25
- const awsToolsUrl = process.env.AWS_TOOLS_SERVICE_URL || 'http://localhost:3009';
26
- const awsClient = new RestClient(awsToolsUrl);
24
+ // ---- Cloud CLI helpers (replace microservice REST calls) ----
27
25
 
28
- // Generator Service client
29
- const generatorUrl = process.env.GENERATOR_SERVICE_URL || 'http://localhost:3003';
30
- const generatorClient = new RestClient(generatorUrl);
26
+ function getAwsProfiles(): string[] {
27
+ try {
28
+ const { execFileSync } = require('child_process');
29
+ const out = execFileSync('aws', ['configure', 'list-profiles'], {
30
+ encoding: 'utf-8', timeout: 5000, stdio: ['pipe', 'pipe', 'pipe'],
31
+ }) as string;
32
+ return out.trim().split('\n').map((s: string) => s.trim()).filter(Boolean);
33
+ } catch {
34
+ return ['default'];
35
+ }
36
+ }
31
37
 
32
- // GCP Tools Service client
33
- const gcpToolsUrl = process.env.GCP_TOOLS_SERVICE_URL || 'http://localhost:3016';
34
- const gcpClient = new RestClient(gcpToolsUrl);
38
+ function validateAwsProfile(profile: string): { accountId?: string; valid: boolean; error?: string } {
39
+ try {
40
+ const { execFileSync } = require('child_process');
41
+ const out = execFileSync('aws', ['sts', 'get-caller-identity', '--profile', profile, '--output', 'json'], {
42
+ encoding: 'utf-8', timeout: 10000, stdio: ['pipe', 'pipe', 'pipe'],
43
+ }) as string;
44
+ const data = JSON.parse(out);
45
+ return { valid: true, accountId: data.Account };
46
+ } catch (e: any) {
47
+ return { valid: false, error: e.message?.slice(0, 100) };
48
+ }
49
+ }
35
50
 
36
- // Azure Tools Service client
37
- const azureToolsUrl = process.env.AZURE_TOOLS_SERVICE_URL || 'http://localhost:3017';
38
- const azureClient = new RestClient(azureToolsUrl);
51
+ function getGcpProject(): string {
52
+ try {
53
+ const { execFileSync } = require('child_process');
54
+ return (execFileSync('gcloud', ['config', 'get-value', 'project'], {
55
+ encoding: 'utf-8', timeout: 5000, stdio: ['pipe', 'pipe', 'pipe'],
56
+ }) as string).trim();
57
+ } catch {
58
+ return '';
59
+ }
60
+ }
61
+
62
+ function validateAzureSubscription(subscriptionId: string): { name?: string; valid: boolean; error?: string } {
63
+ try {
64
+ const { execFileSync } = require('child_process');
65
+ const out = execFileSync('az', ['account', 'show', '--subscription', subscriptionId, '--output', 'json'], {
66
+ encoding: 'utf-8', timeout: 10000, stdio: ['pipe', 'pipe', 'pipe'],
67
+ }) as string;
68
+ const data = JSON.parse(out);
69
+ return { valid: true, name: data.name };
70
+ } catch (e: any) {
71
+ return { valid: false, error: e.message?.slice(0, 100) };
72
+ }
73
+ }
39
74
 
40
75
  /**
41
76
  * Command options from CLI arguments
@@ -292,36 +327,16 @@ async function providerSelectionStep(ctx: TerraformWizardContext): Promise<StepR
292
327
  * Step 2: AWS Configuration
293
328
  */
294
329
  async function awsConfigStep(ctx: TerraformWizardContext): Promise<StepResult> {
295
- // Fetch available profiles
330
+ // Fetch available profiles via CLI
296
331
  ui.startSpinner({ message: 'Fetching AWS profiles...' });
297
-
298
- let profiles: Array<{ name: string; source: string; region?: string; isSSO: boolean }> = [];
299
-
300
- try {
301
- const profilesResponse = await awsClient.get<{
302
- profiles: Array<{ name: string; source: string; region?: string; isSSO: boolean }>;
303
- }>('/api/aws/profiles');
304
-
305
- if (profilesResponse.success && profilesResponse.data?.profiles) {
306
- profiles = profilesResponse.data.profiles;
307
- }
308
-
309
- ui.stopSpinnerSuccess(`Found ${profiles.length} AWS profiles`);
310
- } catch (error) {
311
- ui.stopSpinnerFail('Could not fetch AWS profiles');
312
- // Continue with manual input
313
- profiles = [{ name: 'default', source: 'credentials', isSSO: false }];
314
- }
332
+ const profileNames = getAwsProfiles();
333
+ ui.stopSpinnerSuccess(`Found ${profileNames.length} AWS profile(s)`);
315
334
 
316
335
  // Profile selection
317
336
  let selectedProfile = ctx.awsProfile;
318
337
 
319
338
  if (!selectedProfile) {
320
- const profileOptions = profiles.map(p => ({
321
- value: p.name,
322
- label: p.name + (p.isSSO ? ' (SSO)' : ''),
323
- description: `Source: ${p.source}${p.region ? `, Region: ${p.region}` : ''}`,
324
- }));
339
+ const profileOptions = profileNames.map(p => ({ value: p, label: p }));
325
340
 
326
341
  selectedProfile = await select({
327
342
  message: 'Select AWS profile:',
@@ -334,35 +349,18 @@ async function awsConfigStep(ctx: TerraformWizardContext): Promise<StepResult> {
334
349
  }
335
350
  }
336
351
 
337
- // Validate credentials
352
+ // Validate credentials via CLI
338
353
  ui.startSpinner({ message: `Validating credentials for profile "${selectedProfile}"...` });
354
+ const validation = validateAwsProfile(selectedProfile);
339
355
 
340
- try {
341
- const validateResponse = await awsClient.post<{
342
- valid: boolean;
343
- accountId?: string;
344
- accountAlias?: string;
345
- error?: string;
346
- }>('/api/aws/profiles/validate', { profile: selectedProfile });
347
-
348
- if (!validateResponse.success || !validateResponse.data?.valid) {
349
- ui.stopSpinnerFail(`Invalid credentials: ${validateResponse.data?.error || 'Unknown error'}`);
350
- return { success: false, error: 'Invalid AWS credentials' };
351
- }
352
-
353
- ui.stopSpinnerSuccess(
354
- `Authenticated to account ${validateResponse.data.accountId}${
355
- validateResponse.data.accountAlias ? ` (${validateResponse.data.accountAlias})` : ''
356
- }`
357
- );
358
-
359
- ctx.awsAccountId = validateResponse.data.accountId;
360
- ctx.awsAccountAlias = validateResponse.data.accountAlias;
361
- } catch (error: any) {
362
- ui.stopSpinnerFail(`Failed to validate credentials: ${error.message}`);
363
- return { success: false, error: 'Credential validation failed' };
356
+ if (!validation.valid) {
357
+ ui.stopSpinnerFail(`Invalid credentials: ${validation.error || 'Unknown error'}`);
358
+ return { success: false, error: 'Invalid AWS credentials' };
364
359
  }
365
360
 
361
+ ui.stopSpinnerSuccess(`Authenticated to account ${validation.accountId || 'unknown'}`);
362
+ ctx.awsAccountId = validation.accountId;
363
+
366
364
  // Region selection
367
365
  ui.newLine();
368
366
 
@@ -386,33 +384,23 @@ async function awsConfigStep(ctx: TerraformWizardContext): Promise<StepResult> {
386
384
  let selectedRegions: string[] = [];
387
385
 
388
386
  if (regionChoice === 'specific') {
389
- // Fetch available regions
390
- ui.startSpinner({ message: 'Fetching available regions...' });
387
+ // Hardcoded common AWS regions (no service needed)
388
+ const regionOptions = [
389
+ { value: 'us-east-1', label: 'us-east-1 - N. Virginia' },
390
+ { value: 'us-east-2', label: 'us-east-2 - Ohio' },
391
+ { value: 'us-west-1', label: 'us-west-1 - N. California' },
392
+ { value: 'us-west-2', label: 'us-west-2 - Oregon' },
393
+ { value: 'eu-west-1', label: 'eu-west-1 - Ireland' },
394
+ { value: 'eu-central-1', label: 'eu-central-1 - Frankfurt' },
395
+ { value: 'ap-southeast-1', label: 'ap-southeast-1 - Singapore' },
396
+ { value: 'ap-northeast-1', label: 'ap-northeast-1 - Tokyo' },
397
+ ];
391
398
 
392
- try {
393
- const regionsResponse = await awsClient.get<{
394
- regions: Array<{ name: string; displayName: string }>;
395
- }>(`/api/aws/regions?profile=${selectedProfile}`);
396
-
397
- ui.stopSpinnerSuccess(`Found ${regionsResponse.data?.regions?.length || 0} regions`);
398
-
399
- if (regionsResponse.success && regionsResponse.data?.regions) {
400
- const regionOptions = regionsResponse.data.regions.map(r => ({
401
- value: r.name,
402
- label: `${r.name} - ${r.displayName}`,
403
- }));
404
-
405
- selectedRegions = (await multiSelect({
406
- message: 'Select regions to scan:',
407
- options: regionOptions,
408
- required: true,
409
- })) as string[];
410
- }
411
- } catch (error) {
412
- ui.stopSpinnerFail('Could not fetch regions');
413
- // Use common regions as fallback
414
- selectedRegions = ['us-east-1'];
415
- }
399
+ selectedRegions = (await multiSelect({
400
+ message: 'Select regions to scan:',
401
+ options: regionOptions,
402
+ required: true,
403
+ })) as string[];
416
404
  }
417
405
 
418
406
  return {
@@ -488,29 +476,18 @@ async function gcpConfigStep(ctx: TerraformWizardContext): Promise<StepResult> {
488
476
  return { success: false, error: 'GCP project ID is required' };
489
477
  }
490
478
 
491
- // Validate project access
479
+ // Validate project access via gcloud CLI
492
480
  ui.startSpinner({ message: `Validating access to project "${projectId}"...` });
493
-
494
481
  try {
495
- const validateResponse = await gcpClient.post<{
496
- valid: boolean;
497
- projectName?: string;
498
- error?: string;
499
- }>('/api/gcp/projects/validate', { projectId });
500
-
501
- if (!validateResponse.success || !validateResponse.data?.valid) {
502
- ui.stopSpinnerFail(`Invalid project: ${validateResponse.data?.error || 'Unknown error'}`);
503
- return { success: false, error: 'Invalid GCP project' };
504
- }
505
-
506
- ui.stopSpinnerSuccess(
507
- `Connected to project ${projectId}${
508
- validateResponse.data.projectName ? ` (${validateResponse.data.projectName})` : ''
509
- }`
510
- );
482
+ const { execFileSync } = await import('child_process');
483
+ execFileSync('gcloud', ['projects', 'describe', projectId, '--format=json'], {
484
+ encoding: 'utf-8', timeout: 10000, stdio: ['pipe', 'pipe', 'pipe'],
485
+ });
486
+ ui.stopSpinnerSuccess(`Connected to project ${projectId}`);
511
487
  } catch (error: any) {
512
- ui.stopSpinnerFail(`Failed to validate project: ${error.message}`);
513
- return { success: false, error: 'Project validation failed' };
488
+ ui.stopSpinnerFail(`Could not validate project: ${error.message?.slice(0, 80) || 'unknown'}`);
489
+ // Non-fatal user may still proceed if gcloud is not configured
490
+ ui.info('Proceeding without validation. Ensure gcloud credentials are configured.');
514
491
  }
515
492
 
516
493
  // Region selection
@@ -625,31 +602,14 @@ async function azureConfigStep(ctx: TerraformWizardContext): Promise<StepResult>
625
602
  return { success: false, error: 'Azure subscription ID is required' };
626
603
  }
627
604
 
628
- // Validate subscription access
605
+ // Validate subscription access via Azure CLI
629
606
  ui.startSpinner({ message: `Validating access to subscription "${subscriptionId}"...` });
630
-
631
- try {
632
- const validateResponse = await azureClient.post<{
633
- valid: boolean;
634
- subscriptionName?: string;
635
- error?: string;
636
- }>('/api/azure/subscriptions/validate', { subscriptionId });
637
-
638
- if (!validateResponse.success || !validateResponse.data?.valid) {
639
- ui.stopSpinnerFail(
640
- `Invalid subscription: ${validateResponse.data?.error || 'Unknown error'}`
641
- );
642
- return { success: false, error: 'Invalid Azure subscription' };
643
- }
644
-
645
- ui.stopSpinnerSuccess(
646
- `Connected to subscription ${subscriptionId}${
647
- validateResponse.data.subscriptionName ? ` (${validateResponse.data.subscriptionName})` : ''
648
- }`
649
- );
650
- } catch (error: any) {
651
- ui.stopSpinnerFail(`Failed to validate subscription: ${error.message}`);
652
- return { success: false, error: 'Subscription validation failed' };
607
+ const azVal = validateAzureSubscription(subscriptionId);
608
+ if (!azVal.valid) {
609
+ ui.stopSpinnerFail(`Could not validate subscription: ${azVal.error || 'unknown'}`);
610
+ ui.info('Proceeding without validation. Ensure az CLI credentials are configured.');
611
+ } else {
612
+ ui.stopSpinnerSuccess(`Connected to subscription${azVal.name ? ` (${azVal.name})` : ''}`);
653
613
  }
654
614
 
655
615
  // Resource group (optional)
@@ -762,149 +722,80 @@ async function azureServiceSelectionStep(_ctx: TerraformWizardContext): Promise<
762
722
  }
763
723
 
764
724
  /**
765
- * Poll a discovery session until completion
725
+ * Run synchronous CLI-based infrastructure discovery.
726
+ * Replaces the old REST polling approach.
766
727
  */
767
- async function pollDiscovery(
768
- client: RestClient,
769
- startPath: string,
770
- statusPath: (sessionId: string) => string,
771
- startPayload: Record<string, unknown>,
772
- ctx: TerraformWizardContext
773
- ): Promise<StepResult> {
774
- try {
775
- const startResponse = await client.post<{
776
- sessionId: string;
777
- status: string;
778
- }>(startPath, startPayload);
728
+ async function discoverInfra(ctx: TerraformWizardContext): Promise<{ resourceCount: number; components: string[] }> {
729
+ const { execFileSync } = await import('child_process');
730
+ const components: string[] = [];
731
+ let resourceCount = 0;
779
732
 
780
- if (!startResponse.success || !startResponse.data?.sessionId) {
781
- return { success: false, error: 'Failed to start discovery' };
782
- }
733
+ if (ctx.provider === 'aws') {
734
+ const profile = ctx.awsProfile || 'default';
735
+ const env = { ...process.env, AWS_PROFILE: profile };
783
736
 
784
- const sessionId = startResponse.data.sessionId;
785
- ctx.discoverySessionId = sessionId;
786
-
787
- // Poll for progress
788
- let completed = false;
789
- let lastResourceCount = 0;
790
-
791
- while (!completed) {
792
- await new Promise(resolve => setTimeout(resolve, 1000));
793
-
794
- const statusResponse = await client.get<{
795
- status: string;
796
- progress: {
797
- regionsScanned: number;
798
- totalRegions: number;
799
- resourcesFound: number;
800
- currentRegion?: string;
801
- currentService?: string;
802
- };
803
- inventory?: any;
804
- }>(statusPath(sessionId));
805
-
806
- if (!statusResponse.success) {
807
- continue;
808
- }
809
-
810
- const { status, progress, inventory } = statusResponse.data!;
811
-
812
- // Update progress display
813
- if (progress.resourcesFound !== lastResourceCount) {
814
- ui.clearLine();
815
- ui.write(
816
- ` Scanning: ${progress.regionsScanned}/${progress.totalRegions} regions | ` +
817
- `${progress.resourcesFound} resources found`
818
- );
819
- if (progress.currentRegion) {
820
- ui.write(` | Current: ${progress.currentRegion}`);
821
- }
822
- lastResourceCount = progress.resourcesFound;
823
- }
824
-
825
- if (status === 'completed') {
826
- completed = true;
827
- ctx.inventory = inventory;
737
+ // EC2 instances
738
+ try {
739
+ const out = execFileSync('aws', ['ec2', 'describe-instances', '--query', 'Reservations[*].Instances[*].InstanceId', '--output', 'json'], { encoding: 'utf-8', timeout: 15000, stdio: ['pipe', 'pipe', 'pipe'], env });
740
+ const ids = JSON.parse(out).flat();
741
+ if (ids.length > 0) { components.push('ec2'); resourceCount += ids.length; }
742
+ } catch { /* not available */ }
828
743
 
829
- ui.newLine();
830
- ui.newLine();
831
- ui.success(`Discovery complete! Found ${progress.resourcesFound} resources`);
744
+ // S3 buckets
745
+ try {
746
+ const out = execFileSync('aws', ['s3', 'ls'], { encoding: 'utf-8', timeout: 10000, stdio: ['pipe', 'pipe', 'pipe'], env });
747
+ const buckets = out.trim().split('\n').filter(Boolean).length;
748
+ if (buckets > 0) { components.push('s3'); resourceCount += buckets; }
749
+ } catch { /* not available */ }
832
750
 
833
- // Show summary
834
- if (inventory?.summary) {
835
- ui.newLine();
836
- ui.print(' Resources by service:');
837
- for (const [service, count] of Object.entries(
838
- inventory.summary.resourcesByService || {}
839
- )) {
840
- ui.print(` ${service}: ${count}`);
841
- }
842
- }
843
- } else if (status === 'failed') {
844
- ui.newLine();
845
- return { success: false, error: 'Discovery failed' };
846
- }
847
- }
751
+ // RDS
752
+ try {
753
+ const out = execFileSync('aws', ['rds', 'describe-db-instances', '--query', 'DBInstances[*].DBInstanceIdentifier', '--output', 'json'], { encoding: 'utf-8', timeout: 15000, stdio: ['pipe', 'pipe', 'pipe'], env });
754
+ const dbs = JSON.parse(out);
755
+ if (dbs.length > 0) { components.push('rds'); resourceCount += dbs.length; }
756
+ } catch { /* not available */ }
848
757
 
849
- return {
850
- success: true,
851
- data: {
852
- discoverySessionId: sessionId,
853
- inventory: ctx.inventory,
854
- },
855
- };
856
- } catch (error: any) {
857
- return { success: false, error: error.message };
758
+ // EKS clusters
759
+ try {
760
+ const out = execFileSync('aws', ['eks', 'list-clusters', '--output', 'json'], { encoding: 'utf-8', timeout: 15000, stdio: ['pipe', 'pipe', 'pipe'], env });
761
+ const clusters = JSON.parse(out).clusters;
762
+ if (clusters?.length > 0) { components.push('eks'); resourceCount += clusters.length; }
763
+ } catch { /* not available */ }
764
+
765
+ // VPC (always include as foundational)
766
+ components.push('vpc');
767
+ } else if (ctx.provider === 'gcp') {
768
+ try {
769
+ execFileSync('gcloud', ['compute', 'instances', 'list', '--format=json'], { encoding: 'utf-8', timeout: 15000, stdio: ['pipe', 'pipe', 'pipe'] });
770
+ components.push('compute');
771
+ } catch { /* not available */ }
772
+ components.push('vpc');
773
+ } else if (ctx.provider === 'azure') {
774
+ try {
775
+ const out = execFileSync('az', ['resource', 'list', '--output', 'json'], { encoding: 'utf-8', timeout: 20000, stdio: ['pipe', 'pipe', 'pipe'] });
776
+ const resources = JSON.parse(out);
777
+ resourceCount += resources.length;
778
+ components.push('vnet');
779
+ } catch { /* not available */ }
858
780
  }
781
+
782
+ return { resourceCount, components: [...new Set(components)] };
859
783
  }
860
784
 
861
785
  /**
862
- * Step: Discovery
786
+ * Step: Discovery — uses direct CLI calls instead of REST polling
863
787
  */
864
788
  async function discoveryStep(ctx: TerraformWizardContext): Promise<StepResult> {
865
- ui.print(' Starting infrastructure discovery...');
866
- ui.newLine();
867
-
868
- switch (ctx.provider) {
869
- case 'gcp':
870
- return pollDiscovery(
871
- gcpClient,
872
- '/api/gcp/discover/start',
873
- id => `/api/gcp/discover/session/${id}`,
874
- {
875
- projectId: ctx.gcpProject,
876
- regions: ctx.gcpRegions || 'all',
877
- services: ctx.servicesToScan,
878
- },
879
- ctx
880
- );
881
-
882
- case 'azure':
883
- return pollDiscovery(
884
- azureClient,
885
- '/api/azure/discover/start',
886
- id => `/api/azure/discover/session/${id}`,
887
- {
888
- subscriptionId: ctx.azureSubscription,
889
- resourceGroup: ctx.azureResourceGroup,
890
- services: ctx.servicesToScan,
891
- },
892
- ctx
893
- );
894
-
895
- case 'aws':
896
- default:
897
- return pollDiscovery(
898
- awsClient,
899
- '/api/aws/discover',
900
- id => `/api/aws/discover/${id}`,
901
- {
902
- profile: ctx.awsProfile,
903
- regions: ctx.awsRegions || 'all',
904
- services: ctx.servicesToScan,
905
- },
906
- ctx
907
- );
789
+ ui.startSpinner({ message: 'Discovering infrastructure via CLI...' });
790
+ try {
791
+ const { resourceCount, components } = await discoverInfra(ctx);
792
+ ui.stopSpinnerSuccess(`Discovery complete — found ${resourceCount} resource(s), components: ${components.join(', ') || 'vpc'}`);
793
+ ctx.discoveredComponents = components;
794
+ return { success: true, data: { discoveredComponents: components } };
795
+ } catch (e: any) {
796
+ ui.stopSpinnerFail('Discovery failed');
797
+ ui.warning(`Could not auto-discover: ${e.message}. You can still generate a template.`);
798
+ return { success: true, data: { discoveredComponents: ['vpc'] } };
908
799
  }
909
800
  }
910
801
 
@@ -1031,82 +922,40 @@ async function runConversational(options: GenerateTerraformOptions): Promise<voi
1031
922
  return;
1032
923
  }
1033
924
 
1034
- // Send message to conversational endpoint
1035
- try {
1036
- const response = await generatorClient.post<{
1037
- message: string;
1038
- suggested_actions?: Array<{ type: string; label?: string }>;
1039
- }>('/api/conversational/message', { sessionId, message });
1040
-
1041
- if (response.success && response.data) {
1042
- const data = response.data as any;
1043
- // Handle double-unwrap pattern: response.data may contain { data: { message } }
1044
- const replyMessage = data.data?.message || data.message || 'No response';
1045
- ui.newLine();
1046
- ui.print(replyMessage);
1047
- ui.newLine();
1048
-
1049
- // Check for generate suggestion
1050
- const actions = data.data?.suggested_actions || data.suggested_actions || [];
1051
- const generateAction = actions.find((a: any) => a.type === 'generate');
1052
- if (generateAction) {
1053
- const shouldGenerate = await confirm({
1054
- message: 'Ready to generate Terraform from this conversation?',
1055
- defaultValue: true,
1056
- });
1057
- if (shouldGenerate) {
1058
- const generated = await generateFromConversation(sessionId, options, fs, pathMod);
1059
- if (generated) {
1060
- ui.newLine();
1061
- ui.print('You can refine the generated Terraform by continuing the conversation.');
1062
- ui.print('Type "generate" to regenerate, or "exit" to finish.');
1063
- ui.newLine();
1064
- continue; // stays in the while(true) loop with same sessionId
1065
- }
1066
- return;
1067
- }
1068
- }
1069
- } else {
1070
- ui.error('Failed to get response from generator service.');
1071
- }
1072
- } catch (error: any) {
1073
- ui.error(`Error: ${error.message}`);
1074
- }
925
+ // Build request from conversational description — use chatCommand for natural language interaction
926
+ ui.newLine();
927
+ ui.info(`You said: "${message}"`);
928
+ ui.info('Type "generate" or "done" to generate Terraform from this description, or describe your infrastructure further.');
929
+ ui.newLine();
1075
930
  }
1076
931
  }
1077
932
 
1078
933
  /**
1079
- * Generate Terraform files from a conversational session
934
+ * Generate Terraform files from a conversational session using the local generator
1080
935
  */
1081
936
  async function generateFromConversation(
1082
- sessionId: string,
937
+ _sessionId: string,
1083
938
  options: GenerateTerraformOptions,
1084
939
  fs: typeof import('fs/promises'),
1085
940
  pathMod: typeof import('path')
1086
941
  ): Promise<boolean> {
1087
942
  ui.newLine();
1088
- ui.startSpinner({ message: 'Generating Terraform from conversation...' });
943
+ ui.startSpinner({ message: 'Generating Terraform from description...' });
1089
944
 
1090
945
  try {
1091
- const genResponse = await generatorClient.post<{
1092
- files: Array<{ path: string; content: string }>;
1093
- }>('/api/generate/from-conversation', {
1094
- sessionId,
1095
- applyBestPractices: true,
1096
- });
946
+ const provider = options.provider || 'aws';
947
+ const outputDir = options.output || './infrastructure';
1097
948
 
1098
- if (!genResponse.success || !genResponse.data) {
1099
- ui.stopSpinnerFail('Generation failed');
1100
- return false;
1101
- }
949
+ const generatedProject = await generateTerraformProject({
950
+ projectName: 'infrastructure',
951
+ provider: provider as 'aws' | 'gcp' | 'azure',
952
+ region: options.regions?.[0] || (provider === 'aws' ? 'us-east-1' : provider === 'gcp' ? 'us-central1' : 'eastus'),
953
+ components: options.services || ['vpc'],
954
+ });
1102
955
 
1103
956
  ui.stopSpinnerSuccess('Terraform code generated');
1104
957
 
1105
- // Write files same pattern as runNonInteractive
1106
- const data = genResponse.data as any;
1107
- const files: Array<{ path: string; content: string }> = data.data?.files || data.files || [];
1108
- const outputDir = options.output || './infrastructure';
1109
-
958
+ const files: GeneratedFile[] = generatedProject.files;
1110
959
  await fs.mkdir(outputDir, { recursive: true });
1111
960
 
1112
961
  for (const file of files) {
@@ -1115,11 +964,6 @@ async function generateFromConversation(
1115
964
  await fs.writeFile(filePath, file.content);
1116
965
  }
1117
966
 
1118
- // Post-generation validation (Gaps C+D)
1119
- if (!options.skipValidation && files.length > 0) {
1120
- await runPostGenerationValidation(files, false);
1121
- }
1122
-
1123
967
  ui.newLine();
1124
968
  ui.success(`Generated ${files.length} Terraform file(s) in ${outputDir}`);
1125
969
  ui.newLine();
@@ -1186,119 +1030,53 @@ async function runNonInteractive(options: GenerateTerraformOptions): Promise<voi
1186
1030
  outputPath: options.output || './terraform-infrastructure',
1187
1031
  };
1188
1032
 
1189
- // Run discovery using the pollDiscovery helper (already implemented for Gap 1)
1033
+ // Run direct CLI discovery
1190
1034
  ui.info('Starting infrastructure discovery...');
1191
1035
  ui.newLine();
1192
1036
 
1193
- let discoveryResult: StepResult;
1194
-
1195
- switch (provider) {
1196
- case 'gcp':
1197
- discoveryResult = await pollDiscovery(
1198
- gcpClient,
1199
- '/api/gcp/discover/start',
1200
- id => `/api/gcp/discover/session/${id}`,
1201
- {
1202
- projectId: ctx.gcpProject,
1203
- regions: ctx.awsRegions || 'all',
1204
- services: ctx.servicesToScan,
1205
- },
1206
- ctx
1207
- );
1208
- break;
1209
-
1210
- case 'azure':
1211
- discoveryResult = await pollDiscovery(
1212
- azureClient,
1213
- '/api/azure/discover/start',
1214
- id => `/api/azure/discover/session/${id}`,
1215
- {
1216
- subscriptionId: ctx.azureSubscription,
1217
- services: ctx.servicesToScan,
1218
- },
1219
- ctx
1220
- );
1221
- break;
1222
-
1223
- case 'aws':
1224
- default:
1225
- discoveryResult = await pollDiscovery(
1226
- awsClient,
1227
- '/api/aws/discover',
1228
- id => `/api/aws/discover/${id}`,
1229
- {
1230
- profile: ctx.awsProfile,
1231
- regions: ctx.awsRegions || 'all',
1232
- services: ctx.servicesToScan,
1233
- },
1234
- ctx
1235
- );
1236
- break;
1237
- }
1238
-
1239
- if (!discoveryResult.success) {
1240
- ui.error(`Discovery failed: ${discoveryResult.error || 'Unknown error'}`);
1241
- process.exit(1);
1242
- }
1243
-
1244
- // Generate Terraform from discovered inventory
1037
+ const { components: discoveredComponents } = await discoverInfra(ctx).catch(() => ({ components: ['vpc'] }));
1038
+ ui.success(`Discovered components: ${discoveredComponents.join(', ')}`);
1245
1039
  ui.newLine();
1040
+
1041
+ // Generate Terraform from discovered inventory using src/generator/terraform.ts
1246
1042
  ui.startSpinner({ message: 'Generating Terraform code...' });
1247
1043
 
1248
1044
  try {
1249
- const genResponse = await generatorClient.post<{
1250
- files: Array<{ path: string; content: string }>;
1251
- validation?: any;
1252
- }>('/api/generators/terraform/project', {
1045
+ const outputDir = options.output || './terraform-infrastructure';
1046
+ const components = options.services || discoveredComponents;
1047
+
1048
+ const generatedProject = await generateTerraformProject({
1253
1049
  projectName: 'infrastructure',
1254
- provider,
1255
- region: options.regions?.[0],
1256
- components: options.services,
1257
- inventory: ctx.inventory,
1050
+ provider: provider as 'aws' | 'gcp' | 'azure',
1051
+ region: options.regions?.[0] || (provider === 'aws' ? 'us-east-1' : provider === 'gcp' ? 'us-central1' : 'eastus'),
1052
+ components,
1258
1053
  });
1259
1054
 
1260
- if (!genResponse.success || !genResponse.data) {
1261
- ui.stopSpinnerFail('Generation failed');
1262
- ui.error(genResponse.error?.message || 'Failed to generate Terraform code');
1263
- process.exit(1);
1264
- }
1265
-
1266
1055
  ui.stopSpinnerSuccess('Terraform code generated');
1267
1056
 
1268
1057
  // Write generated files
1269
- const outputDir = options.output || './terraform-infrastructure';
1270
1058
  const fs = await import('fs/promises');
1271
1059
  const path = await import('path');
1272
1060
 
1273
1061
  await fs.mkdir(outputDir, { recursive: true });
1274
1062
 
1275
- const files = genResponse.data.files || [];
1063
+ const files: GeneratedFile[] = generatedProject.files;
1276
1064
  for (const file of files) {
1277
1065
  const filePath = path.join(outputDir, file.path);
1278
1066
  await fs.mkdir(path.dirname(filePath), { recursive: true });
1279
1067
  await fs.writeFile(filePath, file.content);
1280
1068
  }
1281
1069
 
1282
- // --- Post-generation validation (Gaps C+D) ---
1283
- let validationResults: Record<string, unknown> | undefined;
1284
- if (!options.skipValidation && files.length > 0) {
1285
- validationResults = await runPostGenerationValidation(files, options.jsonOutput);
1286
- }
1287
-
1288
1070
  if (options.jsonOutput) {
1289
- // JSON output mode
1290
1071
  const summary = {
1291
1072
  success: true,
1292
1073
  provider,
1293
1074
  outputDir,
1294
1075
  filesGenerated: files.map(f => f.path),
1295
- resourcesDiscovered: ctx.inventory?.summary?.totalResources || 0,
1296
- validation: genResponse.data.validation,
1297
- postGenerationValidation: validationResults,
1076
+ componentsGenerated: components,
1298
1077
  };
1299
1078
  console.log(JSON.stringify(summary, null, 2));
1300
1079
  } else {
1301
- // Human-readable output
1302
1080
  ui.newLine();
1303
1081
  ui.success(`Generated ${files.length} Terraform file(s) in ${outputDir}`);
1304
1082
  ui.newLine();
@@ -1320,54 +1098,18 @@ async function runNonInteractive(options: GenerateTerraformOptions): Promise<voi
1320
1098
  }
1321
1099
 
1322
1100
  /**
1323
- * Run post-generation validation by calling the generator service's
1324
- * existing validation endpoint with the generated files.
1325
- *
1326
- * Non-blocking: if the validation service call fails, a warning is shown
1327
- * and the function returns undefined so the caller can continue normally.
1101
+ * Run post-generation validation using terraform fmt/validate if available.
1102
+ * Non-blocking: warnings shown but errors don't abort.
1328
1103
  */
1329
1104
  async function runPostGenerationValidation(
1330
1105
  files: Array<{ path: string; content: string }>,
1331
1106
  jsonOutput?: boolean
1332
1107
  ): Promise<Record<string, unknown> | undefined> {
1333
- try {
1334
- if (!jsonOutput) {
1335
- ui.newLine();
1336
- ui.startSpinner({ message: 'Running post-generation validation...' });
1337
- }
1338
-
1339
- const validateResponse = await generatorClient.post<{
1340
- valid: boolean;
1341
- items: Array<{ severity: string; message: string; file?: string; rule?: string }>;
1342
- summary: { errors: number; warnings: number; info: number };
1343
- }>('/api/generators/terraform/validate', { files });
1344
-
1345
- if (!validateResponse.success || !validateResponse.data) {
1346
- if (!jsonOutput) {
1347
- ui.stopSpinnerFail('Validation service unavailable');
1348
- ui.warning('Skipping validation — generator service did not respond.');
1349
- }
1350
- return undefined;
1351
- }
1352
-
1353
- const report = validateResponse.data as any;
1354
- const data = report.data || report;
1355
-
1356
- if (!jsonOutput) {
1357
- ui.stopSpinnerSuccess('Validation complete');
1358
- ui.newLine();
1359
- displayValidationReport(data);
1360
- }
1361
-
1362
- return data;
1363
- } catch (error: any) {
1364
- if (!jsonOutput) {
1365
- ui.stopSpinnerFail('Validation failed');
1366
- ui.warning(`Post-generation validation could not run: ${error.message}`);
1367
- ui.warning('You can run validation manually with: nimbus validate terraform');
1368
- }
1369
- return undefined;
1108
+ if (!jsonOutput) {
1109
+ ui.newLine();
1110
+ ui.info('Tip: Run "terraform init && terraform validate" in the output directory to validate the generated files.');
1370
1111
  }
1112
+ return undefined;
1371
1113
  }
1372
1114
 
1373
1115
  /**