@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
|
@@ -0,0 +1,325 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Plugin / Extension System (L3)
|
|
3
|
+
*
|
|
4
|
+
* Manages MCP server plugins that extend Nimbus's tool capabilities.
|
|
5
|
+
* Registry stored at ~/.nimbus/plugins.json
|
|
6
|
+
*
|
|
7
|
+
* Commands:
|
|
8
|
+
* nimbus plugin list — list installed plugins
|
|
9
|
+
* nimbus plugin add <name> — add a plugin from npm or git URL
|
|
10
|
+
* nimbus plugin remove <name> — remove a plugin
|
|
11
|
+
* nimbus plugin init — scaffold a minimal MCP server in CWD
|
|
12
|
+
*/
|
|
13
|
+
import * as fs from 'node:fs';
|
|
14
|
+
import * as path from 'node:path';
|
|
15
|
+
import * as os from 'node:os';
|
|
16
|
+
import { execFileSync } from 'node:child_process';
|
|
17
|
+
import { ui } from '../wizard/ui';
|
|
18
|
+
const PLUGINS_FILE = path.join(os.homedir(), '.nimbus', 'plugins.json');
|
|
19
|
+
const MCP_FILE = path.join(os.homedir(), '.nimbus', 'mcp.json');
|
|
20
|
+
function loadMCPConfig() {
|
|
21
|
+
try {
|
|
22
|
+
const content = fs.readFileSync(MCP_FILE, 'utf-8');
|
|
23
|
+
return JSON.parse(content);
|
|
24
|
+
}
|
|
25
|
+
catch {
|
|
26
|
+
return { servers: [] };
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
function saveMCPConfig(config) {
|
|
30
|
+
const dir = path.dirname(MCP_FILE);
|
|
31
|
+
fs.mkdirSync(dir, { recursive: true });
|
|
32
|
+
fs.writeFileSync(MCP_FILE, JSON.stringify(config, null, 2), 'utf-8');
|
|
33
|
+
}
|
|
34
|
+
/**
|
|
35
|
+
* M5: MCP server management command.
|
|
36
|
+
* Subcommands: add, list, remove, test
|
|
37
|
+
*/
|
|
38
|
+
export async function mcpCommand(subcommand, args) {
|
|
39
|
+
switch (subcommand) {
|
|
40
|
+
case 'list': {
|
|
41
|
+
const config = loadMCPConfig();
|
|
42
|
+
if (config.servers.length === 0) {
|
|
43
|
+
ui.info('No MCP servers configured. Add one with: nimbus mcp add <command-or-url>');
|
|
44
|
+
ui.info(`Config file: ${MCP_FILE}`);
|
|
45
|
+
return;
|
|
46
|
+
}
|
|
47
|
+
ui.header('Configured MCP Servers');
|
|
48
|
+
for (const s of config.servers) {
|
|
49
|
+
const cmd = s.type === 'http' ? `${s.url ?? ''}` : `${s.command} ${(s.args ?? []).join(' ')}`;
|
|
50
|
+
ui.print(` ${ui.color(s.name, 'cyan')} [${s.type}] ${cmd}`);
|
|
51
|
+
}
|
|
52
|
+
ui.newLine();
|
|
53
|
+
ui.info(`Config: ${MCP_FILE}`);
|
|
54
|
+
break;
|
|
55
|
+
}
|
|
56
|
+
case 'add': {
|
|
57
|
+
const commandOrUrl = args[0];
|
|
58
|
+
if (!commandOrUrl) {
|
|
59
|
+
ui.print('Usage: nimbus mcp add <command-or-url> [--name <name>]');
|
|
60
|
+
ui.print('');
|
|
61
|
+
ui.print('Examples:');
|
|
62
|
+
ui.print(' nimbus mcp add "npx -y @my/mcp-server" (npm package)');
|
|
63
|
+
ui.print(' nimbus mcp add "https://my-server.example.com" (HTTP)');
|
|
64
|
+
ui.print(' nimbus mcp add "./my-server.js" (local script)');
|
|
65
|
+
return;
|
|
66
|
+
}
|
|
67
|
+
// Parse optional --name flag
|
|
68
|
+
let serverName = commandOrUrl;
|
|
69
|
+
for (let i = 1; i < args.length; i++) {
|
|
70
|
+
if (args[i] === '--name' && args[i + 1]) {
|
|
71
|
+
serverName = args[++i];
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
const config = loadMCPConfig();
|
|
75
|
+
if (config.servers.find(s => s.name === serverName)) {
|
|
76
|
+
ui.warning(`MCP server "${serverName}" is already configured.`);
|
|
77
|
+
return;
|
|
78
|
+
}
|
|
79
|
+
// Determine transport type and build entry
|
|
80
|
+
const isHttp = commandOrUrl.startsWith('http://') || commandOrUrl.startsWith('https://');
|
|
81
|
+
let entry;
|
|
82
|
+
if (isHttp) {
|
|
83
|
+
entry = {
|
|
84
|
+
name: serverName,
|
|
85
|
+
command: '',
|
|
86
|
+
type: 'http',
|
|
87
|
+
url: commandOrUrl,
|
|
88
|
+
};
|
|
89
|
+
}
|
|
90
|
+
else {
|
|
91
|
+
// Parse "npx -y @my/mcp-server" into command + args
|
|
92
|
+
const parts = commandOrUrl.split(/\s+/);
|
|
93
|
+
entry = {
|
|
94
|
+
name: serverName,
|
|
95
|
+
command: parts[0] ?? commandOrUrl,
|
|
96
|
+
args: parts.slice(1),
|
|
97
|
+
type: 'stdio',
|
|
98
|
+
};
|
|
99
|
+
}
|
|
100
|
+
config.servers.push(entry);
|
|
101
|
+
saveMCPConfig(config);
|
|
102
|
+
ui.print(`${ui.color('✓', 'green')} MCP server "${serverName}" added.`);
|
|
103
|
+
ui.info(`Config saved to: ${MCP_FILE}`);
|
|
104
|
+
ui.info('Restart Nimbus for the server to take effect.');
|
|
105
|
+
break;
|
|
106
|
+
}
|
|
107
|
+
case 'remove': {
|
|
108
|
+
const name = args[0];
|
|
109
|
+
if (!name) {
|
|
110
|
+
ui.print('Usage: nimbus mcp remove <name>');
|
|
111
|
+
return;
|
|
112
|
+
}
|
|
113
|
+
const config = loadMCPConfig();
|
|
114
|
+
const idx = config.servers.findIndex(s => s.name === name);
|
|
115
|
+
if (idx === -1) {
|
|
116
|
+
ui.warning(`MCP server "${name}" not found.`);
|
|
117
|
+
ui.info('Run "nimbus mcp list" to see configured servers.');
|
|
118
|
+
return;
|
|
119
|
+
}
|
|
120
|
+
config.servers.splice(idx, 1);
|
|
121
|
+
saveMCPConfig(config);
|
|
122
|
+
ui.print(`${ui.color('✓', 'green')} MCP server "${name}" removed.`);
|
|
123
|
+
break;
|
|
124
|
+
}
|
|
125
|
+
case 'test': {
|
|
126
|
+
const name = args[0];
|
|
127
|
+
if (!name) {
|
|
128
|
+
ui.print('Usage: nimbus mcp test <name>');
|
|
129
|
+
return;
|
|
130
|
+
}
|
|
131
|
+
const config = loadMCPConfig();
|
|
132
|
+
const server = config.servers.find(s => s.name === name);
|
|
133
|
+
if (!server) {
|
|
134
|
+
ui.warning(`MCP server "${name}" not found.`);
|
|
135
|
+
ui.info('Run "nimbus mcp list" to see configured servers.');
|
|
136
|
+
return;
|
|
137
|
+
}
|
|
138
|
+
if (server.type === 'http') {
|
|
139
|
+
// HTTP: send a tools/list request via curl if available
|
|
140
|
+
ui.startSpinner({ message: `Testing MCP server "${name}" at ${server.url ?? ''}...` });
|
|
141
|
+
try {
|
|
142
|
+
const resp = execFileSync('curl', [
|
|
143
|
+
'-s', '--max-time', '5',
|
|
144
|
+
'-X', 'POST',
|
|
145
|
+
'-H', 'Content-Type: application/json',
|
|
146
|
+
'-d', JSON.stringify({ jsonrpc: '2.0', id: 1, method: 'tools/list', params: {} }),
|
|
147
|
+
server.url ?? '',
|
|
148
|
+
], { encoding: 'utf-8', stdio: ['pipe', 'pipe', 'pipe'] });
|
|
149
|
+
const data = JSON.parse(resp);
|
|
150
|
+
ui.stopSpinnerSuccess(`Server responded. Result: ${JSON.stringify(data).slice(0, 200)}`);
|
|
151
|
+
}
|
|
152
|
+
catch (err) {
|
|
153
|
+
ui.stopSpinnerFail(`Could not reach server: ${err instanceof Error ? err.message : String(err)}`);
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
else {
|
|
157
|
+
// stdio: spawn briefly and send a tools/list JSON-RPC message
|
|
158
|
+
ui.startSpinner({ message: `Testing MCP server "${name}" (stdio)...` });
|
|
159
|
+
try {
|
|
160
|
+
const testPayload = JSON.stringify({ jsonrpc: '2.0', id: 1, method: 'tools/list', params: {} }) + '\n';
|
|
161
|
+
const result = execFileSync(server.command, server.args ?? [], {
|
|
162
|
+
input: testPayload,
|
|
163
|
+
encoding: 'utf-8',
|
|
164
|
+
timeout: 5000,
|
|
165
|
+
stdio: ['pipe', 'pipe', 'pipe'],
|
|
166
|
+
});
|
|
167
|
+
const parsed = JSON.parse(result);
|
|
168
|
+
const tools = parsed.result?.tools;
|
|
169
|
+
const toolCount = Array.isArray(tools) ? tools.length : '?';
|
|
170
|
+
ui.stopSpinnerSuccess(`Server responded with ${toolCount} tools.`);
|
|
171
|
+
}
|
|
172
|
+
catch (err) {
|
|
173
|
+
ui.stopSpinnerFail(`Server test failed: ${err instanceof Error ? err.message : String(err)}`);
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
break;
|
|
177
|
+
}
|
|
178
|
+
default:
|
|
179
|
+
ui.print('Usage: nimbus mcp <list|add|remove|test>');
|
|
180
|
+
ui.print('');
|
|
181
|
+
ui.print('Commands:');
|
|
182
|
+
ui.print(' list List configured MCP servers');
|
|
183
|
+
ui.print(' add <cmd-or-url> Add a new MCP server');
|
|
184
|
+
ui.print(' remove <name> Remove an MCP server');
|
|
185
|
+
ui.print(' test <name> Test that a server responds');
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
function loadRegistry() {
|
|
189
|
+
try {
|
|
190
|
+
const content = fs.readFileSync(PLUGINS_FILE, 'utf-8');
|
|
191
|
+
return JSON.parse(content);
|
|
192
|
+
}
|
|
193
|
+
catch {
|
|
194
|
+
return { plugins: [] };
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
function saveRegistry(registry) {
|
|
198
|
+
const dir = path.dirname(PLUGINS_FILE);
|
|
199
|
+
fs.mkdirSync(dir, { recursive: true });
|
|
200
|
+
fs.writeFileSync(PLUGINS_FILE, JSON.stringify(registry, null, 2), 'utf-8');
|
|
201
|
+
}
|
|
202
|
+
export async function pluginCommand(subcommand, args) {
|
|
203
|
+
switch (subcommand) {
|
|
204
|
+
case 'list': {
|
|
205
|
+
const registry = loadRegistry();
|
|
206
|
+
if (registry.plugins.length === 0) {
|
|
207
|
+
ui.info('No plugins installed. Add one with: nimbus plugin add <name>');
|
|
208
|
+
return;
|
|
209
|
+
}
|
|
210
|
+
ui.header('Installed Plugins');
|
|
211
|
+
for (const p of registry.plugins) {
|
|
212
|
+
ui.print(` ${ui.color(p.name, 'cyan')} — ${p.description ?? p.command}`);
|
|
213
|
+
if (p.args)
|
|
214
|
+
ui.print(` args: ${p.args.join(' ')}`);
|
|
215
|
+
}
|
|
216
|
+
break;
|
|
217
|
+
}
|
|
218
|
+
case 'add': {
|
|
219
|
+
const name = args[0];
|
|
220
|
+
if (!name) {
|
|
221
|
+
ui.print('Usage: nimbus plugin add <package-name-or-url>');
|
|
222
|
+
return;
|
|
223
|
+
}
|
|
224
|
+
const registry = loadRegistry();
|
|
225
|
+
if (registry.plugins.find(p => p.name === name)) {
|
|
226
|
+
ui.warning(`Plugin "${name}" is already installed.`);
|
|
227
|
+
return;
|
|
228
|
+
}
|
|
229
|
+
// Determine command: npm package → `npx <name>`, git URL → clone first
|
|
230
|
+
const isUrl = name.startsWith('http') || name.startsWith('git@');
|
|
231
|
+
const command = isUrl ? `node` : `npx`;
|
|
232
|
+
const pluginArgs = isUrl ? [name] : [name];
|
|
233
|
+
const entry = {
|
|
234
|
+
name,
|
|
235
|
+
command,
|
|
236
|
+
args: pluginArgs,
|
|
237
|
+
installedAt: new Date().toISOString(),
|
|
238
|
+
};
|
|
239
|
+
registry.plugins.push(entry);
|
|
240
|
+
saveRegistry(registry);
|
|
241
|
+
ui.print(`${ui.color('✓', 'green')} Plugin "${name}" added.`);
|
|
242
|
+
ui.info('Restart Nimbus for the plugin to take effect.');
|
|
243
|
+
break;
|
|
244
|
+
}
|
|
245
|
+
case 'remove': {
|
|
246
|
+
const name = args[0];
|
|
247
|
+
if (!name) {
|
|
248
|
+
ui.print('Usage: nimbus plugin remove <name>');
|
|
249
|
+
return;
|
|
250
|
+
}
|
|
251
|
+
const registry = loadRegistry();
|
|
252
|
+
const idx = registry.plugins.findIndex(p => p.name === name);
|
|
253
|
+
if (idx === -1) {
|
|
254
|
+
ui.warning(`Plugin "${name}" not found.`);
|
|
255
|
+
return;
|
|
256
|
+
}
|
|
257
|
+
registry.plugins.splice(idx, 1);
|
|
258
|
+
saveRegistry(registry);
|
|
259
|
+
ui.print(`${ui.color('✓', 'green')} Plugin "${name}" removed.`);
|
|
260
|
+
break;
|
|
261
|
+
}
|
|
262
|
+
case 'init': {
|
|
263
|
+
const cwd = process.cwd();
|
|
264
|
+
const serverFile = path.join(cwd, 'nimbus-plugin.js');
|
|
265
|
+
if (fs.existsSync(serverFile)) {
|
|
266
|
+
ui.warning(`${serverFile} already exists.`);
|
|
267
|
+
return;
|
|
268
|
+
}
|
|
269
|
+
const template = `#!/usr/bin/env node
|
|
270
|
+
/**
|
|
271
|
+
* Nimbus MCP Plugin Template
|
|
272
|
+
*
|
|
273
|
+
* Implement tools that extend Nimbus's capabilities.
|
|
274
|
+
* See: https://github.com/the-ai-project-co/nimbus
|
|
275
|
+
*/
|
|
276
|
+
|
|
277
|
+
const readline = require('readline');
|
|
278
|
+
|
|
279
|
+
const tools = [
|
|
280
|
+
{
|
|
281
|
+
name: 'my_tool',
|
|
282
|
+
description: 'A custom tool for Nimbus',
|
|
283
|
+
inputSchema: {
|
|
284
|
+
type: 'object',
|
|
285
|
+
properties: {
|
|
286
|
+
input: { type: 'string', description: 'Input for the tool' },
|
|
287
|
+
},
|
|
288
|
+
required: ['input'],
|
|
289
|
+
},
|
|
290
|
+
},
|
|
291
|
+
];
|
|
292
|
+
|
|
293
|
+
// MCP server — reads JSON-RPC requests from stdin, writes responses to stdout
|
|
294
|
+
const rl = readline.createInterface({ input: process.stdin, terminal: false });
|
|
295
|
+
|
|
296
|
+
rl.on('line', (line) => {
|
|
297
|
+
try {
|
|
298
|
+
const request = JSON.parse(line);
|
|
299
|
+
if (request.method === 'tools/list') {
|
|
300
|
+
respond(request.id, { tools });
|
|
301
|
+
} else if (request.method === 'tools/call') {
|
|
302
|
+
const { name, arguments: args } = request.params;
|
|
303
|
+
if (name === 'my_tool') {
|
|
304
|
+
respond(request.id, { content: [{ type: 'text', text: \`You called my_tool with: \${args.input}\` }] });
|
|
305
|
+
}
|
|
306
|
+
}
|
|
307
|
+
} catch (e) {
|
|
308
|
+
process.stderr.write(\`Error: \${e}\\n\`);
|
|
309
|
+
}
|
|
310
|
+
});
|
|
311
|
+
|
|
312
|
+
function respond(id, result) {
|
|
313
|
+
process.stdout.write(JSON.stringify({ jsonrpc: '2.0', id, result }) + '\\n');
|
|
314
|
+
}
|
|
315
|
+
`;
|
|
316
|
+
fs.writeFileSync(serverFile, template, 'utf-8');
|
|
317
|
+
fs.chmodSync(serverFile, 0o755);
|
|
318
|
+
ui.print(`${ui.color('✓', 'green')} Created ${serverFile}`);
|
|
319
|
+
ui.info('Register with: nimbus plugin add ./nimbus-plugin.js');
|
|
320
|
+
break;
|
|
321
|
+
}
|
|
322
|
+
default:
|
|
323
|
+
ui.print('Usage: nimbus plugin <list|add|remove|init>');
|
|
324
|
+
}
|
|
325
|
+
}
|
|
@@ -0,0 +1,356 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Preview Command
|
|
3
|
+
*
|
|
4
|
+
* Preview infrastructure changes without applying them
|
|
5
|
+
*
|
|
6
|
+
* Usage:
|
|
7
|
+
* nimbus preview terraform [directory]
|
|
8
|
+
* nimbus preview k8s [directory]
|
|
9
|
+
* nimbus preview helm [chart]
|
|
10
|
+
*/
|
|
11
|
+
import { logger } from '../utils';
|
|
12
|
+
import { ui } from '../wizard/ui';
|
|
13
|
+
import { terraformClient, k8sClient, helmClient } from '../clients';
|
|
14
|
+
import { loadSafetyPolicy, evaluateSafety } from '../config/safety-policy';
|
|
15
|
+
import { displaySafetySummary } from '../wizard/approval';
|
|
16
|
+
/**
|
|
17
|
+
* Preview command handler
|
|
18
|
+
*/
|
|
19
|
+
export async function previewCommand(options) {
|
|
20
|
+
logger.info('Running preview', { type: options.type });
|
|
21
|
+
ui.newLine();
|
|
22
|
+
ui.header(`Preview ${capitalize(options.type)} Changes`);
|
|
23
|
+
switch (options.type) {
|
|
24
|
+
case 'terraform':
|
|
25
|
+
await previewTerraform(options);
|
|
26
|
+
break;
|
|
27
|
+
case 'k8s':
|
|
28
|
+
await previewKubernetes(options);
|
|
29
|
+
break;
|
|
30
|
+
case 'helm':
|
|
31
|
+
await previewHelm(options);
|
|
32
|
+
break;
|
|
33
|
+
default:
|
|
34
|
+
ui.error(`Unknown preview type: ${options.type}`);
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
/**
|
|
38
|
+
* Preview Terraform changes
|
|
39
|
+
*/
|
|
40
|
+
async function previewTerraform(options) {
|
|
41
|
+
const directory = options.directory || '.';
|
|
42
|
+
ui.info(`Directory: ${directory}`);
|
|
43
|
+
ui.newLine();
|
|
44
|
+
// Check if terraform client is available
|
|
45
|
+
const clientAvailable = await terraformClient.isAvailable();
|
|
46
|
+
if (clientAvailable) {
|
|
47
|
+
await previewTerraformWithService(options);
|
|
48
|
+
}
|
|
49
|
+
else {
|
|
50
|
+
await previewTerraformWithCLI(options);
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
/**
|
|
54
|
+
* Preview Terraform using service
|
|
55
|
+
*/
|
|
56
|
+
async function previewTerraformWithService(options) {
|
|
57
|
+
const directory = options.directory || '.';
|
|
58
|
+
ui.startSpinner({ message: 'Creating execution plan...' });
|
|
59
|
+
try {
|
|
60
|
+
const result = await terraformClient.plan(directory, {});
|
|
61
|
+
ui.stopSpinnerSuccess('Plan created');
|
|
62
|
+
ui.newLine();
|
|
63
|
+
if (!result.success) {
|
|
64
|
+
ui.error(`Plan failed: ${result.error}`);
|
|
65
|
+
return;
|
|
66
|
+
}
|
|
67
|
+
// Display plan
|
|
68
|
+
displayTerraformPlan(result, options);
|
|
69
|
+
// Run safety checks if not skipped
|
|
70
|
+
if (!options.skipSafety) {
|
|
71
|
+
await runSafetyChecks('plan', 'terraform', result.output, options);
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
catch (error) {
|
|
75
|
+
ui.stopSpinnerFail('Plan failed');
|
|
76
|
+
ui.error(error.message);
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
/**
|
|
80
|
+
* Preview Terraform using local CLI
|
|
81
|
+
*/
|
|
82
|
+
async function previewTerraformWithCLI(options) {
|
|
83
|
+
const { spawn } = await import('child_process');
|
|
84
|
+
const directory = options.directory || '.';
|
|
85
|
+
const args = ['plan', '-no-color'];
|
|
86
|
+
if (options.target) {
|
|
87
|
+
args.push('-target', options.target);
|
|
88
|
+
}
|
|
89
|
+
ui.info(`Running: terraform ${args.join(' ')}`);
|
|
90
|
+
ui.newLine();
|
|
91
|
+
return new Promise(resolve => {
|
|
92
|
+
let output = '';
|
|
93
|
+
const proc = spawn('terraform', args, {
|
|
94
|
+
cwd: directory,
|
|
95
|
+
stdio: ['inherit', 'pipe', 'pipe'],
|
|
96
|
+
});
|
|
97
|
+
proc.stdout?.on('data', data => {
|
|
98
|
+
const text = data.toString();
|
|
99
|
+
output += text;
|
|
100
|
+
process.stdout.write(text);
|
|
101
|
+
});
|
|
102
|
+
proc.stderr?.on('data', data => {
|
|
103
|
+
process.stderr.write(data);
|
|
104
|
+
});
|
|
105
|
+
proc.on('error', error => {
|
|
106
|
+
ui.error(`Failed to run terraform: ${error.message}`);
|
|
107
|
+
ui.info('Make sure terraform is installed and in your PATH');
|
|
108
|
+
resolve();
|
|
109
|
+
});
|
|
110
|
+
proc.on('close', async (code) => {
|
|
111
|
+
if (code === 0) {
|
|
112
|
+
ui.newLine();
|
|
113
|
+
ui.success('Plan preview complete');
|
|
114
|
+
// Run safety checks if not skipped
|
|
115
|
+
if (!options.skipSafety) {
|
|
116
|
+
await runSafetyChecks('plan', 'terraform', output, options);
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
else {
|
|
120
|
+
ui.newLine();
|
|
121
|
+
ui.error(`Terraform plan failed with exit code ${code}`);
|
|
122
|
+
}
|
|
123
|
+
resolve();
|
|
124
|
+
});
|
|
125
|
+
});
|
|
126
|
+
}
|
|
127
|
+
/**
|
|
128
|
+
* Display Terraform plan results
|
|
129
|
+
*/
|
|
130
|
+
function displayTerraformPlan(result, options) {
|
|
131
|
+
if (!result.hasChanges) {
|
|
132
|
+
ui.success('No changes. Infrastructure is up to date.');
|
|
133
|
+
return;
|
|
134
|
+
}
|
|
135
|
+
if (options.format === 'json') {
|
|
136
|
+
console.log(JSON.stringify({ hasChanges: result.hasChanges, output: result.output }, null, 2));
|
|
137
|
+
return;
|
|
138
|
+
}
|
|
139
|
+
// Parse changes from output
|
|
140
|
+
const addMatch = result.output.match(/(\d+) to add/);
|
|
141
|
+
const changeMatch = result.output.match(/(\d+) to change/);
|
|
142
|
+
const destroyMatch = result.output.match(/(\d+) to destroy/);
|
|
143
|
+
const add = parseInt(addMatch?.[1] || '0', 10);
|
|
144
|
+
const change = parseInt(changeMatch?.[1] || '0', 10);
|
|
145
|
+
const destroy = parseInt(destroyMatch?.[1] || '0', 10);
|
|
146
|
+
// Display summary table
|
|
147
|
+
ui.print(ui.bold('Plan Summary:'));
|
|
148
|
+
ui.newLine();
|
|
149
|
+
if (add > 0) {
|
|
150
|
+
ui.print(` ${ui.color(`+ ${add} to add`, 'green')}`);
|
|
151
|
+
}
|
|
152
|
+
if (change > 0) {
|
|
153
|
+
ui.print(` ${ui.color(`~ ${change} to change`, 'yellow')}`);
|
|
154
|
+
}
|
|
155
|
+
if (destroy > 0) {
|
|
156
|
+
ui.print(` ${ui.color(`- ${destroy} to destroy`, 'red')}`);
|
|
157
|
+
}
|
|
158
|
+
// Show detailed output if verbose
|
|
159
|
+
if (options.verbose) {
|
|
160
|
+
ui.newLine();
|
|
161
|
+
ui.print(ui.bold('Detailed Changes:'));
|
|
162
|
+
ui.newLine();
|
|
163
|
+
ui.print(result.output);
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
/**
|
|
167
|
+
* Preview Kubernetes changes
|
|
168
|
+
*/
|
|
169
|
+
async function previewKubernetes(options) {
|
|
170
|
+
const directory = options.directory || '.';
|
|
171
|
+
const namespace = options.namespace || 'default';
|
|
172
|
+
ui.info(`Directory: ${directory}`);
|
|
173
|
+
ui.info(`Namespace: ${namespace}`);
|
|
174
|
+
ui.newLine();
|
|
175
|
+
// Check if k8s client is available
|
|
176
|
+
const clientAvailable = await k8sClient.isAvailable();
|
|
177
|
+
// K8s client doesn't have a diff method, always use CLI
|
|
178
|
+
// If client is available, we could use dry-run apply in the future
|
|
179
|
+
if (clientAvailable) {
|
|
180
|
+
ui.info('Using kubectl diff for preview...');
|
|
181
|
+
}
|
|
182
|
+
// Use kubectl diff CLI
|
|
183
|
+
await previewKubernetesWithCLI(options);
|
|
184
|
+
}
|
|
185
|
+
/**
|
|
186
|
+
* Preview Kubernetes using kubectl
|
|
187
|
+
*/
|
|
188
|
+
async function previewKubernetesWithCLI(options) {
|
|
189
|
+
const { spawn } = await import('child_process');
|
|
190
|
+
const directory = options.directory || '.';
|
|
191
|
+
const namespace = options.namespace || 'default';
|
|
192
|
+
const args = ['diff', '-f', directory, '-n', namespace];
|
|
193
|
+
ui.info(`Running: kubectl ${args.join(' ')}`);
|
|
194
|
+
ui.newLine();
|
|
195
|
+
return new Promise(resolve => {
|
|
196
|
+
const proc = spawn('kubectl', args, {
|
|
197
|
+
stdio: 'inherit',
|
|
198
|
+
});
|
|
199
|
+
proc.on('error', error => {
|
|
200
|
+
ui.error(`Failed to run kubectl: ${error.message}`);
|
|
201
|
+
ui.info('Make sure kubectl is installed and configured');
|
|
202
|
+
resolve();
|
|
203
|
+
});
|
|
204
|
+
proc.on('close', code => {
|
|
205
|
+
ui.newLine();
|
|
206
|
+
if (code === 0) {
|
|
207
|
+
ui.success('No changes detected');
|
|
208
|
+
}
|
|
209
|
+
else if (code === 1) {
|
|
210
|
+
ui.info('Changes detected (see diff above)');
|
|
211
|
+
}
|
|
212
|
+
else {
|
|
213
|
+
ui.error(`kubectl diff failed with exit code ${code}`);
|
|
214
|
+
}
|
|
215
|
+
resolve();
|
|
216
|
+
});
|
|
217
|
+
});
|
|
218
|
+
}
|
|
219
|
+
/**
|
|
220
|
+
* Display Kubernetes diff
|
|
221
|
+
*/
|
|
222
|
+
function _displayK8sDiff(result, options) {
|
|
223
|
+
if (options.format === 'json') {
|
|
224
|
+
console.log(JSON.stringify(result, null, 2));
|
|
225
|
+
return;
|
|
226
|
+
}
|
|
227
|
+
ui.print(ui.bold('Kubernetes Diff:'));
|
|
228
|
+
ui.newLine();
|
|
229
|
+
if (result.output) {
|
|
230
|
+
// Color the diff output
|
|
231
|
+
const lines = result.output.split('\n');
|
|
232
|
+
for (const line of lines) {
|
|
233
|
+
if (line.startsWith('+')) {
|
|
234
|
+
ui.print(ui.color(line, 'green'));
|
|
235
|
+
}
|
|
236
|
+
else if (line.startsWith('-')) {
|
|
237
|
+
ui.print(ui.color(line, 'red'));
|
|
238
|
+
}
|
|
239
|
+
else if (line.startsWith('@')) {
|
|
240
|
+
ui.print(ui.color(line, 'cyan'));
|
|
241
|
+
}
|
|
242
|
+
else {
|
|
243
|
+
ui.print(line);
|
|
244
|
+
}
|
|
245
|
+
}
|
|
246
|
+
}
|
|
247
|
+
}
|
|
248
|
+
/**
|
|
249
|
+
* Preview Helm changes
|
|
250
|
+
*/
|
|
251
|
+
async function previewHelm(options) {
|
|
252
|
+
const chart = options.directory || '.';
|
|
253
|
+
const release = options.release || 'preview';
|
|
254
|
+
const namespace = options.namespace || 'default';
|
|
255
|
+
ui.info(`Chart: ${chart}`);
|
|
256
|
+
ui.info(`Release: ${release}`);
|
|
257
|
+
ui.info(`Namespace: ${namespace}`);
|
|
258
|
+
ui.newLine();
|
|
259
|
+
// Check if helm client is available
|
|
260
|
+
const clientAvailable = await helmClient.isAvailable();
|
|
261
|
+
// Helm client doesn't have a diff method, always use CLI
|
|
262
|
+
// If client is available, we could use template comparison in the future
|
|
263
|
+
if (clientAvailable) {
|
|
264
|
+
ui.info('Using helm template for preview...');
|
|
265
|
+
}
|
|
266
|
+
// Use helm template CLI
|
|
267
|
+
await previewHelmWithCLI(options);
|
|
268
|
+
}
|
|
269
|
+
/**
|
|
270
|
+
* Preview Helm using helm CLI
|
|
271
|
+
*/
|
|
272
|
+
async function previewHelmWithCLI(options) {
|
|
273
|
+
const { spawn } = await import('child_process');
|
|
274
|
+
const chart = options.directory || '.';
|
|
275
|
+
const release = options.release || 'preview';
|
|
276
|
+
const namespace = options.namespace || 'default';
|
|
277
|
+
// Use helm template to preview what would be generated
|
|
278
|
+
const args = ['template', release, chart, '-n', namespace];
|
|
279
|
+
if (options.valuesFile) {
|
|
280
|
+
args.push('-f', options.valuesFile);
|
|
281
|
+
}
|
|
282
|
+
ui.info(`Running: helm ${args.join(' ')}`);
|
|
283
|
+
ui.newLine();
|
|
284
|
+
return new Promise(resolve => {
|
|
285
|
+
const proc = spawn('helm', args, {
|
|
286
|
+
stdio: 'inherit',
|
|
287
|
+
});
|
|
288
|
+
proc.on('error', error => {
|
|
289
|
+
ui.error(`Failed to run helm: ${error.message}`);
|
|
290
|
+
ui.info('Make sure helm is installed and in your PATH');
|
|
291
|
+
resolve();
|
|
292
|
+
});
|
|
293
|
+
proc.on('close', code => {
|
|
294
|
+
ui.newLine();
|
|
295
|
+
if (code === 0) {
|
|
296
|
+
ui.success('Template preview complete');
|
|
297
|
+
}
|
|
298
|
+
else {
|
|
299
|
+
ui.error(`helm template failed with exit code ${code}`);
|
|
300
|
+
}
|
|
301
|
+
resolve();
|
|
302
|
+
});
|
|
303
|
+
});
|
|
304
|
+
}
|
|
305
|
+
/**
|
|
306
|
+
* Display Helm diff
|
|
307
|
+
*/
|
|
308
|
+
function _displayHelmDiff(result, options) {
|
|
309
|
+
if (options.format === 'json') {
|
|
310
|
+
console.log(JSON.stringify(result, null, 2));
|
|
311
|
+
return;
|
|
312
|
+
}
|
|
313
|
+
if (!result.hasDiff) {
|
|
314
|
+
ui.success('No changes. Helm release is up to date.');
|
|
315
|
+
return;
|
|
316
|
+
}
|
|
317
|
+
ui.print(ui.bold('Helm Diff:'));
|
|
318
|
+
ui.newLine();
|
|
319
|
+
if (result.output) {
|
|
320
|
+
ui.print(result.output);
|
|
321
|
+
}
|
|
322
|
+
}
|
|
323
|
+
/**
|
|
324
|
+
* Run safety checks on the preview
|
|
325
|
+
*/
|
|
326
|
+
async function runSafetyChecks(operation, type, output, options) {
|
|
327
|
+
const policy = loadSafetyPolicy();
|
|
328
|
+
const context = {
|
|
329
|
+
operation,
|
|
330
|
+
type,
|
|
331
|
+
planOutput: output,
|
|
332
|
+
metadata: {
|
|
333
|
+
directory: options.directory,
|
|
334
|
+
namespace: options.namespace,
|
|
335
|
+
},
|
|
336
|
+
};
|
|
337
|
+
const result = evaluateSafety(context, policy);
|
|
338
|
+
ui.newLine();
|
|
339
|
+
displaySafetySummary({
|
|
340
|
+
operation: `${type} ${operation}`,
|
|
341
|
+
risks: result.risks,
|
|
342
|
+
passed: result.passed,
|
|
343
|
+
});
|
|
344
|
+
if (result.requiresApproval) {
|
|
345
|
+
ui.newLine();
|
|
346
|
+
ui.warning('This operation will require approval when applied');
|
|
347
|
+
}
|
|
348
|
+
}
|
|
349
|
+
/**
|
|
350
|
+
* Capitalize first letter
|
|
351
|
+
*/
|
|
352
|
+
function capitalize(str) {
|
|
353
|
+
return str.charAt(0).toUpperCase() + str.slice(1);
|
|
354
|
+
}
|
|
355
|
+
// Export as default
|
|
356
|
+
export default previewCommand;
|