@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.
- package/bin/nimbus +26 -10
- package/bin/nimbus.cmd +41 -0
- package/bin/nimbus.mjs +70 -0
- package/completions/nimbus.bash +38 -0
- package/completions/nimbus.fish +48 -0
- package/completions/nimbus.zsh +81 -0
- package/dist/src/agent/compaction-agent.js +215 -0
- package/dist/src/agent/context-manager.js +385 -0
- package/dist/src/agent/context.js +322 -0
- package/dist/src/agent/deploy-preview.js +395 -0
- package/dist/src/agent/expand-files.js +95 -0
- package/dist/src/agent/index.js +18 -0
- package/dist/src/agent/loop.js +1535 -0
- package/dist/src/agent/modes.js +347 -0
- package/dist/src/agent/permissions.js +396 -0
- package/dist/src/agent/subagents/base.js +67 -0
- package/dist/src/agent/subagents/cost.js +45 -0
- package/dist/src/agent/subagents/explore.js +36 -0
- package/dist/src/agent/subagents/general.js +41 -0
- package/dist/src/agent/subagents/index.js +88 -0
- package/dist/src/agent/subagents/infra.js +52 -0
- package/dist/src/agent/subagents/security.js +60 -0
- package/dist/src/agent/system-prompt.js +860 -0
- package/dist/src/app.js +152 -0
- package/dist/src/audit/activity-log.js +209 -0
- package/dist/src/audit/compliance-checker.js +419 -0
- package/dist/src/audit/cost-tracker.js +231 -0
- package/dist/src/audit/index.js +10 -0
- package/dist/src/audit/security-scanner.js +490 -0
- package/dist/src/auth/guard.js +64 -0
- package/dist/src/auth/index.js +19 -0
- package/dist/src/auth/keychain.js +79 -0
- package/dist/src/auth/oauth.js +389 -0
- package/dist/src/auth/providers.js +415 -0
- package/dist/src/auth/sso.js +87 -0
- package/dist/src/auth/store.js +424 -0
- package/dist/src/auth/types.js +5 -0
- package/dist/src/cli/index.js +8 -0
- package/dist/src/cli/init.js +1048 -0
- package/dist/src/cli/openapi-spec.js +346 -0
- package/dist/src/cli/run.js +505 -0
- package/dist/src/cli/serve-auth.js +56 -0
- package/dist/src/cli/serve.js +432 -0
- package/dist/src/cli/web.js +50 -0
- package/dist/src/cli.js +1574 -0
- package/dist/src/clients/core-engine-client.js +156 -0
- package/dist/src/clients/enterprise-client.js +246 -0
- package/dist/src/clients/generator-client.js +219 -0
- package/dist/src/clients/git-client.js +367 -0
- package/dist/src/clients/github-client.js +229 -0
- package/dist/src/clients/helm-client.js +299 -0
- package/dist/src/clients/index.js +18 -0
- package/dist/src/clients/k8s-client.js +270 -0
- package/dist/src/clients/llm-client.js +119 -0
- package/dist/src/clients/rest-client.js +104 -0
- package/dist/src/clients/service-discovery.js +35 -0
- package/dist/src/clients/terraform-client.js +302 -0
- package/dist/src/clients/tools-client.js +1227 -0
- package/dist/src/clients/ws-client.js +93 -0
- package/dist/src/commands/alias.js +91 -0
- package/dist/src/commands/analyze/index.js +313 -0
- package/dist/src/commands/apply/helm.js +375 -0
- package/dist/src/commands/apply/index.js +176 -0
- package/dist/src/commands/apply/k8s.js +350 -0
- package/dist/src/commands/apply/terraform.js +465 -0
- package/dist/src/commands/ask.js +137 -0
- package/dist/src/commands/audit/index.js +322 -0
- package/dist/src/commands/auth-cloud.js +345 -0
- package/dist/src/commands/auth-list.js +112 -0
- package/dist/src/commands/auth-profile.js +104 -0
- package/dist/src/commands/auth-refresh.js +161 -0
- package/dist/src/commands/auth-status.js +122 -0
- package/dist/src/commands/aws/ec2.js +402 -0
- package/dist/src/commands/aws/iam.js +304 -0
- package/dist/src/commands/aws/index.js +108 -0
- package/dist/src/commands/aws/lambda.js +317 -0
- package/dist/src/commands/aws/rds.js +345 -0
- package/dist/src/commands/aws/s3.js +346 -0
- package/dist/src/commands/aws/vpc.js +302 -0
- package/dist/src/commands/aws-discover.js +413 -0
- package/dist/src/commands/aws-terraform.js +618 -0
- package/dist/src/commands/azure/aks.js +305 -0
- package/dist/src/commands/azure/functions.js +200 -0
- package/dist/src/commands/azure/index.js +93 -0
- package/dist/src/commands/azure/storage.js +378 -0
- package/dist/src/commands/azure/vm.js +291 -0
- package/dist/src/commands/billing/index.js +224 -0
- package/dist/src/commands/chat.js +259 -0
- package/dist/src/commands/completions.js +255 -0
- package/dist/src/commands/config.js +291 -0
- package/dist/src/commands/cost/cloud-cost-estimator.js +211 -0
- package/dist/src/commands/cost/estimator.js +73 -0
- package/dist/src/commands/cost/index.js +625 -0
- package/dist/src/commands/cost/parsers/terraform.js +234 -0
- package/dist/src/commands/cost/parsers/types.js +4 -0
- package/dist/src/commands/cost/pricing/aws.js +501 -0
- package/dist/src/commands/cost/pricing/azure.js +462 -0
- package/dist/src/commands/cost/pricing/gcp.js +359 -0
- package/dist/src/commands/cost/pricing/index.js +24 -0
- package/dist/src/commands/demo.js +196 -0
- package/dist/src/commands/deploy.js +215 -0
- package/dist/src/commands/doctor.js +1291 -0
- package/dist/src/commands/drift/index.js +674 -0
- package/dist/src/commands/explain.js +235 -0
- package/dist/src/commands/export.js +120 -0
- package/dist/src/commands/feedback.js +319 -0
- package/dist/src/commands/fix.js +263 -0
- package/dist/src/commands/fs/index.js +338 -0
- package/dist/src/commands/gcp/compute.js +266 -0
- package/dist/src/commands/gcp/functions.js +221 -0
- package/dist/src/commands/gcp/gke.js +357 -0
- package/dist/src/commands/gcp/iam.js +295 -0
- package/dist/src/commands/gcp/index.js +105 -0
- package/dist/src/commands/gcp/storage.js +232 -0
- package/dist/src/commands/generate-helm.js +1026 -0
- package/dist/src/commands/generate-k8s.js +1263 -0
- package/dist/src/commands/generate-terraform.js +1058 -0
- package/dist/src/commands/gh/index.js +663 -0
- package/dist/src/commands/git/index.js +1208 -0
- package/dist/src/commands/helm/index.js +985 -0
- package/dist/src/commands/help.js +639 -0
- package/dist/src/commands/history.js +120 -0
- package/dist/src/commands/import.js +782 -0
- package/dist/src/commands/incident.js +144 -0
- package/dist/src/commands/index.js +109 -0
- package/dist/src/commands/init.js +955 -0
- package/dist/src/commands/k8s/index.js +979 -0
- package/dist/src/commands/login.js +588 -0
- package/dist/src/commands/logout.js +61 -0
- package/dist/src/commands/logs.js +160 -0
- package/dist/src/commands/onboarding.js +382 -0
- package/dist/src/commands/pipeline.js +153 -0
- package/dist/src/commands/plan/display.js +216 -0
- package/dist/src/commands/plan/index.js +525 -0
- package/dist/src/commands/plugin.js +325 -0
- package/dist/src/commands/preview.js +356 -0
- package/dist/src/commands/profile.js +297 -0
- package/dist/src/commands/questionnaire.js +1021 -0
- package/dist/src/commands/resume.js +35 -0
- package/dist/src/commands/rollback.js +259 -0
- package/dist/src/commands/rollout.js +74 -0
- package/dist/src/commands/runbook.js +307 -0
- package/dist/src/commands/schedule.js +202 -0
- package/dist/src/commands/status.js +213 -0
- package/dist/src/commands/team/index.js +309 -0
- package/dist/src/commands/team-context.js +200 -0
- package/dist/src/commands/template.js +204 -0
- package/dist/src/commands/tf/index.js +989 -0
- package/dist/src/commands/upgrade.js +515 -0
- package/dist/src/commands/usage/index.js +118 -0
- package/dist/src/commands/version.js +145 -0
- package/dist/src/commands/watch.js +127 -0
- package/dist/src/compat/index.js +2 -0
- package/dist/src/compat/runtime.js +10 -0
- package/dist/src/compat/sqlite.js +144 -0
- package/dist/src/config/index.js +6 -0
- package/dist/src/config/manager.js +469 -0
- package/dist/src/config/mode-store.js +57 -0
- package/dist/src/config/profiles.js +66 -0
- package/dist/src/config/safety-policy.js +251 -0
- package/dist/src/config/schema.js +107 -0
- package/dist/src/config/types.js +311 -0
- package/dist/src/config/workspace-state.js +38 -0
- package/dist/src/context/context-db.js +138 -0
- package/dist/src/demo/index.js +295 -0
- package/dist/src/demo/scenarios/full-journey.js +226 -0
- package/dist/src/demo/scenarios/getting-started.js +124 -0
- package/dist/src/demo/scenarios/helm-release.js +334 -0
- package/dist/src/demo/scenarios/k8s-deployment.js +190 -0
- package/dist/src/demo/scenarios/terraform-vpc.js +167 -0
- package/dist/src/demo/types.js +6 -0
- package/dist/src/engine/cost-estimator.js +334 -0
- package/dist/src/engine/diagram-generator.js +192 -0
- package/dist/src/engine/drift-detector.js +688 -0
- package/dist/src/engine/executor.js +832 -0
- package/dist/src/engine/index.js +39 -0
- package/dist/src/engine/orchestrator.js +436 -0
- package/dist/src/engine/planner.js +616 -0
- package/dist/src/engine/safety.js +609 -0
- package/dist/src/engine/verifier.js +664 -0
- package/dist/src/enterprise/audit.js +241 -0
- package/dist/src/enterprise/auth.js +189 -0
- package/dist/src/enterprise/billing.js +512 -0
- package/dist/src/enterprise/index.js +16 -0
- package/dist/src/enterprise/teams.js +315 -0
- package/dist/src/generator/best-practices.js +1375 -0
- package/dist/src/generator/helm.js +495 -0
- package/dist/src/generator/index.js +11 -0
- package/dist/src/generator/intent-parser.js +420 -0
- package/dist/src/generator/kubernetes.js +773 -0
- package/dist/src/generator/terraform.js +1472 -0
- package/dist/src/history/index.js +6 -0
- package/dist/src/history/manager.js +199 -0
- package/dist/src/history/types.js +6 -0
- package/dist/src/hooks/config.js +318 -0
- package/dist/src/hooks/engine.js +317 -0
- package/dist/src/hooks/index.js +2 -0
- package/dist/src/llm/auth-bridge.js +157 -0
- package/dist/src/llm/circuit-breaker.js +116 -0
- package/dist/src/llm/config-loader.js +172 -0
- package/dist/src/llm/cost-calculator.js +137 -0
- package/dist/src/llm/index.js +7 -0
- package/dist/src/llm/model-aliases.js +99 -0
- package/dist/src/llm/provider-registry.js +57 -0
- package/dist/src/llm/providers/anthropic.js +430 -0
- package/dist/src/llm/providers/bedrock.js +409 -0
- package/dist/src/llm/providers/google.js +344 -0
- package/dist/src/llm/providers/ollama.js +661 -0
- package/dist/src/llm/providers/openai-compatible.js +289 -0
- package/dist/src/llm/providers/openai.js +284 -0
- package/dist/src/llm/providers/openrouter.js +293 -0
- package/dist/src/llm/router.js +844 -0
- package/dist/src/llm/types.js +69 -0
- package/dist/src/lsp/client.js +239 -0
- package/dist/src/lsp/languages.js +95 -0
- package/dist/src/lsp/manager.js +243 -0
- package/dist/src/mcp/client.js +289 -0
- package/dist/src/mcp/index.js +5 -0
- package/dist/src/mcp/manager.js +113 -0
- package/dist/src/nimbus.js +212 -0
- package/dist/src/plugins/index.js +13 -0
- package/dist/src/plugins/loader.js +280 -0
- package/dist/src/plugins/manager.js +282 -0
- package/dist/src/plugins/types.js +23 -0
- package/dist/src/scanners/cicd-scanner.js +230 -0
- package/dist/src/scanners/cloud-scanner.js +415 -0
- package/dist/src/scanners/framework-scanner.js +430 -0
- package/dist/src/scanners/iac-scanner.js +350 -0
- package/dist/src/scanners/index.js +454 -0
- package/dist/src/scanners/language-scanner.js +258 -0
- package/dist/src/scanners/package-manager-scanner.js +252 -0
- package/dist/src/scanners/types.js +6 -0
- package/dist/src/sessions/manager.js +395 -0
- package/dist/src/sessions/types.js +4 -0
- package/dist/src/sharing/sync.js +238 -0
- package/dist/src/sharing/viewer.js +131 -0
- package/dist/src/snapshots/index.js +1 -0
- package/dist/src/snapshots/manager.js +432 -0
- package/dist/src/state/artifacts.js +94 -0
- package/dist/src/state/audit.js +73 -0
- package/dist/src/state/billing.js +126 -0
- package/dist/src/state/checkpoints.js +81 -0
- package/dist/src/state/config.js +58 -0
- package/dist/src/state/conversations.js +7 -0
- package/dist/src/state/credentials.js +96 -0
- package/dist/src/state/db.js +53 -0
- package/dist/src/state/index.js +23 -0
- package/dist/src/state/messages.js +76 -0
- package/dist/src/state/projects.js +92 -0
- package/dist/src/state/schema.js +233 -0
- package/dist/src/state/sessions.js +79 -0
- package/dist/src/state/teams.js +131 -0
- package/dist/src/telemetry.js +91 -0
- package/dist/src/tools/aws-ops.js +747 -0
- package/dist/src/tools/azure-ops.js +491 -0
- package/dist/src/tools/file-ops.js +451 -0
- package/dist/src/tools/gcp-ops.js +559 -0
- package/dist/src/tools/git-ops.js +557 -0
- package/dist/src/tools/github-ops.js +460 -0
- package/dist/src/tools/helm-ops.js +634 -0
- package/dist/src/tools/index.js +16 -0
- package/dist/src/tools/k8s-ops.js +579 -0
- package/dist/src/tools/schemas/converter.js +129 -0
- package/dist/src/tools/schemas/devops.js +3319 -0
- package/dist/src/tools/schemas/index.js +19 -0
- package/dist/src/tools/schemas/standard.js +966 -0
- package/dist/src/tools/schemas/types.js +409 -0
- package/dist/src/tools/spawn-exec.js +109 -0
- package/dist/src/tools/terraform-ops.js +627 -0
- package/dist/src/types/config.js +1 -0
- package/dist/src/types/drift.js +4 -0
- package/dist/src/types/enterprise.js +5 -0
- package/dist/src/types/index.js +14 -0
- package/dist/src/types/plan.js +1 -0
- package/dist/src/types/request.js +1 -0
- package/dist/src/types/response.js +1 -0
- package/dist/src/types/service.js +1 -0
- package/dist/src/ui/App.js +1672 -0
- package/dist/src/ui/DeployPreview.js +60 -0
- package/dist/src/ui/FileDiffModal.js +108 -0
- package/dist/src/ui/Header.js +46 -0
- package/dist/src/ui/HelpModal.js +9 -0
- package/dist/src/ui/InputBox.js +408 -0
- package/dist/src/ui/MessageList.js +795 -0
- package/dist/src/ui/PermissionPrompt.js +72 -0
- package/dist/src/ui/StatusBar.js +109 -0
- package/dist/src/ui/TerminalPane.js +31 -0
- package/dist/src/ui/ToolCallDisplay.js +303 -0
- package/dist/src/ui/TreePane.js +83 -0
- package/dist/src/ui/chat-ui.js +721 -0
- package/dist/src/ui/index.js +11 -0
- package/dist/src/ui/ink/index.js +1325 -0
- package/dist/src/ui/streaming.js +137 -0
- package/dist/src/ui/theme.js +78 -0
- package/dist/src/ui/types.js +7 -0
- package/dist/src/utils/analytics.js +61 -0
- package/dist/src/utils/cost-warning.js +25 -0
- package/dist/src/utils/env.js +42 -0
- package/dist/src/utils/errors.js +54 -0
- package/dist/src/utils/event-bus.js +22 -0
- package/dist/src/utils/index.js +16 -0
- package/dist/src/utils/logger.js +150 -0
- package/dist/src/utils/rate-limiter.js +90 -0
- package/dist/src/utils/service-auth.js +36 -0
- package/dist/src/utils/validation.js +39 -0
- package/dist/src/version.js +3 -0
- package/dist/src/watcher/index.js +192 -0
- package/dist/src/wizard/approval.js +275 -0
- package/dist/src/wizard/index.js +13 -0
- package/dist/src/wizard/prompts.js +273 -0
- package/dist/src/wizard/types.js +4 -0
- package/dist/src/wizard/ui.js +453 -0
- package/dist/src/wizard/wizard.js +227 -0
- package/package.json +31 -23
- package/src/__tests__/alias.test.ts +133 -0
- package/src/__tests__/app.test.ts +1 -1
- package/src/__tests__/audit.test.ts +1 -1
- package/src/__tests__/circuit-breaker.test.ts +1 -1
- package/src/__tests__/cli-run.test.ts +237 -1
- package/src/__tests__/compat-sqlite.test.ts +68 -0
- package/src/__tests__/context-manager.test.ts +131 -1
- package/src/__tests__/context.test.ts +1 -1
- package/src/__tests__/devops-terminal-gaps.test.ts +718 -0
- package/src/__tests__/doctor.test.ts +48 -0
- package/src/__tests__/enterprise.test.ts +1 -1
- package/src/__tests__/export.test.ts +236 -0
- package/src/__tests__/gap-11-18-20.test.ts +958 -0
- package/src/__tests__/generator.test.ts +1 -1
- package/src/__tests__/helm-streaming.test.ts +127 -0
- package/src/__tests__/hooks.test.ts +1 -1
- package/src/__tests__/incident.test.ts +179 -0
- package/src/__tests__/init.test.ts +55 -4
- package/src/__tests__/intent-parser.test.ts +1 -1
- package/src/__tests__/llm-router.test.ts +1 -1
- package/src/__tests__/logs.test.ts +107 -0
- package/src/__tests__/loop-errors.test.ts +244 -0
- package/src/__tests__/lsp.test.ts +1 -1
- package/src/__tests__/modes.test.ts +1 -1
- package/src/__tests__/perf-optimizations.test.ts +847 -0
- package/src/__tests__/permissions.test.ts +1 -1
- package/src/__tests__/pipeline.test.ts +50 -0
- package/src/__tests__/polish-phase3.test.ts +340 -0
- package/src/__tests__/profile.test.ts +237 -0
- package/src/__tests__/rollback.test.ts +83 -0
- package/src/__tests__/runbook.test.ts +219 -0
- package/src/__tests__/schedule.test.ts +206 -0
- package/src/__tests__/serve.test.ts +1 -1
- package/src/__tests__/sessions.test.ts +96 -1
- package/src/__tests__/sharing.test.ts +53 -1
- package/src/__tests__/snapshots.test.ts +1 -1
- package/src/__tests__/standalone-migration.test.ts +199 -0
- package/src/__tests__/state-db.test.ts +1 -1
- package/src/__tests__/status.test.ts +158 -0
- package/src/__tests__/stream-with-tools.test.ts +71 -25
- package/src/__tests__/subagents.test.ts +1 -1
- package/src/__tests__/system-prompt.test.ts +82 -3
- package/src/__tests__/terminal-gap-v2.test.ts +395 -0
- package/src/__tests__/terminal-parity.test.ts +393 -0
- package/src/__tests__/tf-apply.test.ts +187 -0
- package/src/__tests__/tool-converter.test.ts +1 -1
- package/src/__tests__/tool-schemas.test.ts +209 -4
- package/src/__tests__/tools.test.ts +4 -3
- package/src/__tests__/version-json.test.ts +184 -0
- package/src/__tests__/version.test.ts +1 -1
- package/src/__tests__/watch.test.ts +129 -0
- package/src/agent/compaction-agent.ts +40 -1
- package/src/agent/context-manager.ts +67 -3
- package/src/agent/deploy-preview.ts +62 -1
- package/src/agent/expand-files.ts +108 -0
- package/src/agent/loop.ts +1312 -31
- package/src/agent/permissions.ts +51 -4
- package/src/agent/system-prompt.ts +573 -19
- package/src/app.ts +58 -0
- package/src/audit/security-scanner.ts +45 -0
- package/src/auth/keychain.ts +82 -0
- package/src/auth/oauth.ts +15 -5
- package/src/cli/init.ts +378 -5
- package/src/cli/run.ts +407 -16
- package/src/cli/serve.ts +78 -1
- package/src/cli/web.ts +10 -6
- package/src/cli.ts +312 -1
- package/src/clients/service-discovery.ts +30 -25
- package/src/commands/alias.ts +100 -0
- package/src/commands/audit/index.ts +121 -2
- package/src/commands/auth-cloud.ts +113 -0
- package/src/commands/auth-refresh.ts +187 -0
- package/src/commands/aws-discover.ts +144 -251
- package/src/commands/aws-terraform.ts +68 -118
- package/src/commands/chat.ts +9 -3
- package/src/commands/completions.ts +268 -0
- package/src/commands/config.ts +26 -0
- package/src/commands/cost/index.ts +218 -2
- package/src/commands/deploy.ts +260 -0
- package/src/commands/doctor.ts +744 -152
- package/src/commands/drift/index.ts +371 -23
- package/src/commands/export.ts +146 -0
- package/src/commands/generate-k8s.ts +9 -61
- package/src/commands/generate-terraform.ts +191 -449
- package/src/commands/help.ts +212 -36
- package/src/commands/history.ts +8 -1
- package/src/commands/incident.ts +166 -0
- package/src/commands/init.ts +5 -0
- package/src/commands/login.ts +86 -1
- package/src/commands/logs.ts +167 -0
- package/src/commands/onboarding.ts +211 -34
- package/src/commands/pipeline.ts +186 -0
- package/src/commands/plugin.ts +398 -0
- package/src/commands/profile.ts +342 -0
- package/src/commands/questionnaire.ts +0 -98
- package/src/commands/resume.ts +26 -34
- package/src/commands/rollback.ts +315 -0
- package/src/commands/rollout.ts +88 -0
- package/src/commands/runbook.ts +346 -0
- package/src/commands/schedule.ts +236 -0
- package/src/commands/status.ts +252 -0
- package/src/commands/team-context.ts +220 -0
- package/src/commands/template.ts +58 -57
- package/src/commands/tf/index.ts +70 -11
- package/src/commands/upgrade.ts +57 -0
- package/src/commands/version.ts +54 -50
- package/src/commands/watch.ts +153 -0
- package/src/compat/runtime.ts +1 -1
- package/src/compat/sqlite.ts +75 -5
- package/src/config/mode-store.ts +62 -0
- package/src/config/profiles.ts +84 -0
- package/src/config/types.ts +83 -1
- package/src/config/workspace-state.ts +53 -0
- package/src/engine/cost-estimator.ts +52 -10
- package/src/engine/executor.ts +33 -2
- package/src/engine/planner.ts +68 -1
- package/src/generator/terraform.ts +8 -0
- package/src/history/manager.ts +2 -74
- package/src/hooks/engine.ts +5 -4
- package/src/llm/cost-calculator.ts +2 -2
- package/src/llm/providers/anthropic.ts +50 -21
- package/src/llm/router.ts +76 -7
- package/src/lsp/languages.ts +3 -0
- package/src/lsp/manager.ts +21 -5
- package/src/nimbus.ts +37 -18
- package/src/sessions/manager.ts +108 -1
- package/src/sharing/sync.ts +4 -0
- package/src/sharing/viewer.ts +66 -0
- package/src/tools/file-ops.ts +22 -0
- package/src/tools/schemas/devops.ts +3007 -117
- package/src/tools/schemas/standard.ts +5 -1
- package/src/tools/schemas/types.ts +31 -1
- package/src/tools/spawn-exec.ts +148 -0
- package/src/ui/App.tsx +1183 -66
- package/src/ui/DeployPreview.tsx +62 -57
- package/src/ui/FileDiffModal.tsx +162 -0
- package/src/ui/Header.tsx +87 -24
- package/src/ui/HelpModal.tsx +57 -0
- package/src/ui/InputBox.tsx +163 -10
- package/src/ui/MessageList.tsx +487 -40
- package/src/ui/PermissionPrompt.tsx +17 -5
- package/src/ui/StatusBar.tsx +122 -3
- package/src/ui/TerminalPane.tsx +84 -0
- package/src/ui/ToolCallDisplay.tsx +252 -18
- package/src/ui/TreePane.tsx +132 -0
- package/src/ui/chat-ui.ts +41 -44
- package/src/ui/ink/index.ts +771 -38
- package/src/ui/streaming.ts +1 -1
- package/src/ui/theme.ts +104 -0
- package/src/ui/types.ts +18 -0
- package/src/version.ts +1 -1
- package/src/watcher/index.ts +66 -15
- package/src/wizard/types.ts +1 -0
- package/src/wizard/ui.ts +1 -1
- 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
|
-
//
|
|
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
|
-
|
|
29
|
-
|
|
30
|
-
const
|
|
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
|
-
|
|
33
|
-
|
|
34
|
-
const
|
|
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
|
-
|
|
37
|
-
|
|
38
|
-
const
|
|
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
|
-
|
|
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 =
|
|
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
|
-
|
|
341
|
-
|
|
342
|
-
|
|
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
|
-
//
|
|
390
|
-
|
|
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
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
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
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
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(`
|
|
513
|
-
|
|
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
|
-
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
|
|
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
|
-
*
|
|
725
|
+
* Run synchronous CLI-based infrastructure discovery.
|
|
726
|
+
* Replaces the old REST polling approach.
|
|
766
727
|
*/
|
|
767
|
-
async function
|
|
768
|
-
|
|
769
|
-
|
|
770
|
-
|
|
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
|
-
|
|
781
|
-
|
|
782
|
-
}
|
|
733
|
+
if (ctx.provider === 'aws') {
|
|
734
|
+
const profile = ctx.awsProfile || 'default';
|
|
735
|
+
const env = { ...process.env, AWS_PROFILE: profile };
|
|
783
736
|
|
|
784
|
-
|
|
785
|
-
|
|
786
|
-
|
|
787
|
-
|
|
788
|
-
|
|
789
|
-
|
|
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
|
-
|
|
830
|
-
|
|
831
|
-
|
|
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
|
-
|
|
834
|
-
|
|
835
|
-
|
|
836
|
-
|
|
837
|
-
|
|
838
|
-
|
|
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
|
-
|
|
850
|
-
|
|
851
|
-
|
|
852
|
-
|
|
853
|
-
|
|
854
|
-
|
|
855
|
-
|
|
856
|
-
|
|
857
|
-
|
|
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.
|
|
866
|
-
|
|
867
|
-
|
|
868
|
-
|
|
869
|
-
|
|
870
|
-
|
|
871
|
-
|
|
872
|
-
|
|
873
|
-
|
|
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
|
-
//
|
|
1035
|
-
|
|
1036
|
-
|
|
1037
|
-
|
|
1038
|
-
|
|
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
|
-
|
|
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
|
|
943
|
+
ui.startSpinner({ message: 'Generating Terraform from description...' });
|
|
1089
944
|
|
|
1090
945
|
try {
|
|
1091
|
-
const
|
|
1092
|
-
|
|
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
|
-
|
|
1099
|
-
|
|
1100
|
-
|
|
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
|
-
|
|
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
|
|
1033
|
+
// Run direct CLI discovery
|
|
1190
1034
|
ui.info('Starting infrastructure discovery...');
|
|
1191
1035
|
ui.newLine();
|
|
1192
1036
|
|
|
1193
|
-
|
|
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
|
|
1250
|
-
|
|
1251
|
-
|
|
1252
|
-
|
|
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
|
|
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 =
|
|
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
|
-
|
|
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
|
|
1324
|
-
*
|
|
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
|
-
|
|
1334
|
-
|
|
1335
|
-
|
|
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
|
/**
|