@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,625 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Cost Commands
|
|
3
|
+
*
|
|
4
|
+
* Commands for infrastructure cost estimation and tracking
|
|
5
|
+
*/
|
|
6
|
+
import { ui } from '../../wizard/ui';
|
|
7
|
+
import { select } from '../../wizard/prompts';
|
|
8
|
+
import * as fs from 'node:fs';
|
|
9
|
+
import * as path from 'node:path';
|
|
10
|
+
import { execSync } from 'node:child_process';
|
|
11
|
+
import { CostEstimator } from './estimator';
|
|
12
|
+
// ==========================================
|
|
13
|
+
// Parsers
|
|
14
|
+
// ==========================================
|
|
15
|
+
/**
|
|
16
|
+
* Parse cost estimate options
|
|
17
|
+
*/
|
|
18
|
+
export function parseCostEstimateOptions(args) {
|
|
19
|
+
const options = {};
|
|
20
|
+
for (let i = 0; i < args.length; i++) {
|
|
21
|
+
const arg = args[i];
|
|
22
|
+
if (arg === '--directory' && args[i + 1]) {
|
|
23
|
+
options.directory = args[++i];
|
|
24
|
+
}
|
|
25
|
+
else if (arg === '-d' && args[i + 1]) {
|
|
26
|
+
options.directory = args[++i];
|
|
27
|
+
}
|
|
28
|
+
else if (arg === '--format' && args[i + 1]) {
|
|
29
|
+
options.format = args[++i];
|
|
30
|
+
}
|
|
31
|
+
else if (arg === '--detailed') {
|
|
32
|
+
options.detailed = true;
|
|
33
|
+
}
|
|
34
|
+
else if (arg === '--compare' && args[i + 1]) {
|
|
35
|
+
options.compare = args[++i];
|
|
36
|
+
}
|
|
37
|
+
else if (!arg.startsWith('-') && !options.directory) {
|
|
38
|
+
options.directory = arg;
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
return options;
|
|
42
|
+
}
|
|
43
|
+
/**
|
|
44
|
+
* Parse cost history options
|
|
45
|
+
*/
|
|
46
|
+
export function parseCostHistoryOptions(args) {
|
|
47
|
+
const options = {
|
|
48
|
+
days: 30,
|
|
49
|
+
groupBy: 'service',
|
|
50
|
+
};
|
|
51
|
+
for (let i = 0; i < args.length; i++) {
|
|
52
|
+
const arg = args[i];
|
|
53
|
+
if (arg === '--days' && args[i + 1]) {
|
|
54
|
+
options.days = parseInt(args[++i], 10);
|
|
55
|
+
}
|
|
56
|
+
else if (arg === '--group-by' && args[i + 1]) {
|
|
57
|
+
options.groupBy = args[++i];
|
|
58
|
+
}
|
|
59
|
+
else if (arg === '--provider' && args[i + 1]) {
|
|
60
|
+
options.provider = args[++i];
|
|
61
|
+
}
|
|
62
|
+
else if (arg === '--format' && args[i + 1]) {
|
|
63
|
+
options.format = args[++i];
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
return options;
|
|
67
|
+
}
|
|
68
|
+
// ==========================================
|
|
69
|
+
// Helpers
|
|
70
|
+
// ==========================================
|
|
71
|
+
/**
|
|
72
|
+
* Check if infracost is installed
|
|
73
|
+
*/
|
|
74
|
+
function checkInfracost() {
|
|
75
|
+
try {
|
|
76
|
+
execSync('infracost --version', { stdio: 'pipe' });
|
|
77
|
+
return true;
|
|
78
|
+
}
|
|
79
|
+
catch {
|
|
80
|
+
return false;
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
/**
|
|
84
|
+
* Run infracost breakdown
|
|
85
|
+
*/
|
|
86
|
+
function runInfracostBreakdown(directory) {
|
|
87
|
+
try {
|
|
88
|
+
const result = execSync(`infracost breakdown --path "${directory}" --format json`, {
|
|
89
|
+
stdio: 'pipe',
|
|
90
|
+
encoding: 'utf-8',
|
|
91
|
+
maxBuffer: 10 * 1024 * 1024,
|
|
92
|
+
});
|
|
93
|
+
return JSON.parse(result);
|
|
94
|
+
}
|
|
95
|
+
catch (error) {
|
|
96
|
+
return null;
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
/**
|
|
100
|
+
* Format currency
|
|
101
|
+
*/
|
|
102
|
+
function formatCurrency(amount, currency = 'USD') {
|
|
103
|
+
return new Intl.NumberFormat('en-US', {
|
|
104
|
+
style: 'currency',
|
|
105
|
+
currency,
|
|
106
|
+
minimumFractionDigits: 2,
|
|
107
|
+
maximumFractionDigits: 2,
|
|
108
|
+
}).format(amount);
|
|
109
|
+
}
|
|
110
|
+
/**
|
|
111
|
+
* Format change with color
|
|
112
|
+
*/
|
|
113
|
+
function formatChange(amount, currency = 'USD') {
|
|
114
|
+
if (amount === 0) {
|
|
115
|
+
return ui.color('$0.00', 'dim');
|
|
116
|
+
}
|
|
117
|
+
else if (amount > 0) {
|
|
118
|
+
return ui.color(`+${formatCurrency(amount, currency)}`, 'red');
|
|
119
|
+
}
|
|
120
|
+
else {
|
|
121
|
+
return ui.color(formatCurrency(amount, currency), 'green');
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
/**
|
|
125
|
+
* Get mock cost history (in real implementation, would query cloud provider)
|
|
126
|
+
*/
|
|
127
|
+
function getMockCostHistory(options) {
|
|
128
|
+
const entries = [];
|
|
129
|
+
const now = new Date();
|
|
130
|
+
const days = options.days || 30;
|
|
131
|
+
// Generate mock data for demonstration
|
|
132
|
+
const services = ['EC2', 'RDS', 'S3', 'Lambda', 'CloudWatch'];
|
|
133
|
+
for (let i = days - 1; i >= 0; i--) {
|
|
134
|
+
const date = new Date(now);
|
|
135
|
+
date.setDate(date.getDate() - i);
|
|
136
|
+
const dateStr = date.toISOString().split('T')[0];
|
|
137
|
+
for (const service of services) {
|
|
138
|
+
const baseCost = {
|
|
139
|
+
EC2: 45,
|
|
140
|
+
RDS: 35,
|
|
141
|
+
S3: 5,
|
|
142
|
+
Lambda: 10,
|
|
143
|
+
CloudWatch: 3,
|
|
144
|
+
}[service] || 10;
|
|
145
|
+
// Add some variance
|
|
146
|
+
const variance = (Math.random() - 0.5) * baseCost * 0.2;
|
|
147
|
+
const cost = baseCost + variance;
|
|
148
|
+
entries.push({
|
|
149
|
+
date: dateStr,
|
|
150
|
+
service,
|
|
151
|
+
cost: parseFloat(cost.toFixed(2)),
|
|
152
|
+
change: parseFloat(variance.toFixed(2)),
|
|
153
|
+
});
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
return entries;
|
|
157
|
+
}
|
|
158
|
+
// ==========================================
|
|
159
|
+
// Display Functions
|
|
160
|
+
// ==========================================
|
|
161
|
+
/**
|
|
162
|
+
* Display cost estimate
|
|
163
|
+
*/
|
|
164
|
+
function displayCostEstimate(estimate, detailed = false) {
|
|
165
|
+
ui.newLine();
|
|
166
|
+
ui.section('Cost Estimate');
|
|
167
|
+
ui.print(` ${ui.dim('Generated:')} ${new Date(estimate.timeGenerated).toLocaleString()}`);
|
|
168
|
+
ui.print(` ${ui.dim('Currency:')} ${estimate.currency}`);
|
|
169
|
+
ui.newLine();
|
|
170
|
+
// Summary
|
|
171
|
+
ui.print(` ${ui.bold('Monthly Cost:')} ${ui.color(formatCurrency(estimate.totalMonthlyCost), 'cyan')}`);
|
|
172
|
+
ui.print(` ${ui.bold('Hourly Cost:')} ${formatCurrency(estimate.totalHourlyCost)}`);
|
|
173
|
+
if (estimate.diffTotalMonthlyCost !== 0) {
|
|
174
|
+
ui.print(` ${ui.bold('Change:')} ${formatChange(estimate.diffTotalMonthlyCost)}`);
|
|
175
|
+
}
|
|
176
|
+
ui.newLine();
|
|
177
|
+
// Resource summary
|
|
178
|
+
ui.print(` ${ui.dim('Resources detected:')} ${estimate.summary.totalDetectedResources}`);
|
|
179
|
+
ui.print(` ${ui.dim('Resources supported:')} ${estimate.summary.totalSupportedResources}`);
|
|
180
|
+
ui.print(` ${ui.dim('Resources unsupported:')} ${estimate.summary.totalUnsupportedResources}`);
|
|
181
|
+
ui.newLine();
|
|
182
|
+
// Project breakdown
|
|
183
|
+
for (const project of estimate.projects) {
|
|
184
|
+
ui.section(`Project: ${project.name}`);
|
|
185
|
+
ui.print(` ${ui.dim('Monthly:')} ${formatCurrency(project.totalMonthlyCost)}`);
|
|
186
|
+
if (project.diffTotalMonthlyCost !== 0) {
|
|
187
|
+
ui.print(` ${ui.dim('Change:')} ${formatChange(project.diffTotalMonthlyCost)}`);
|
|
188
|
+
}
|
|
189
|
+
if (detailed && project.resources.length > 0) {
|
|
190
|
+
ui.newLine();
|
|
191
|
+
ui.print(' Resources:');
|
|
192
|
+
// Group by type
|
|
193
|
+
const byType = {};
|
|
194
|
+
for (const resource of project.resources) {
|
|
195
|
+
const type = resource.resourceType;
|
|
196
|
+
if (!byType[type]) {
|
|
197
|
+
byType[type] = [];
|
|
198
|
+
}
|
|
199
|
+
byType[type].push(resource);
|
|
200
|
+
}
|
|
201
|
+
for (const [type, resources] of Object.entries(byType)) {
|
|
202
|
+
const typeCost = resources.reduce((sum, r) => sum + r.monthlyCost, 0);
|
|
203
|
+
ui.newLine();
|
|
204
|
+
ui.print(` ${ui.bold(type)} (${resources.length}) - ${formatCurrency(typeCost)}/mo`);
|
|
205
|
+
for (const resource of resources.slice(0, 5)) {
|
|
206
|
+
const cost = formatCurrency(resource.monthlyCost);
|
|
207
|
+
ui.print(` ${resource.name}: ${cost}/mo`);
|
|
208
|
+
}
|
|
209
|
+
if (resources.length > 5) {
|
|
210
|
+
ui.print(ui.dim(` ... and ${resources.length - 5} more`));
|
|
211
|
+
}
|
|
212
|
+
}
|
|
213
|
+
}
|
|
214
|
+
}
|
|
215
|
+
// Unsupported resources warning
|
|
216
|
+
if (Object.keys(estimate.summary.unsupportedResourceCounts).length > 0) {
|
|
217
|
+
ui.newLine();
|
|
218
|
+
ui.warning('Some resources could not be priced:');
|
|
219
|
+
for (const [type, count] of Object.entries(estimate.summary.unsupportedResourceCounts)) {
|
|
220
|
+
ui.print(` ${ui.dim('•')} ${type}: ${count}`);
|
|
221
|
+
}
|
|
222
|
+
}
|
|
223
|
+
}
|
|
224
|
+
/**
|
|
225
|
+
* Display cost history
|
|
226
|
+
*/
|
|
227
|
+
function displayCostHistory(entries, groupBy) {
|
|
228
|
+
ui.newLine();
|
|
229
|
+
ui.section('Cost History');
|
|
230
|
+
if (entries.length === 0) {
|
|
231
|
+
ui.info('No cost data available for the specified period.');
|
|
232
|
+
return;
|
|
233
|
+
}
|
|
234
|
+
// Group entries by the specified field
|
|
235
|
+
const groups = {};
|
|
236
|
+
for (const entry of entries) {
|
|
237
|
+
const key = groupBy === 'service' ? entry.service : entry.resource || entry.service;
|
|
238
|
+
if (!groups[key]) {
|
|
239
|
+
groups[key] = [];
|
|
240
|
+
}
|
|
241
|
+
groups[key].push(entry);
|
|
242
|
+
}
|
|
243
|
+
// Calculate totals per group
|
|
244
|
+
const totals = [];
|
|
245
|
+
for (const [group, groupEntries] of Object.entries(groups)) {
|
|
246
|
+
const total = groupEntries.reduce((sum, e) => sum + e.cost, 0);
|
|
247
|
+
const avg = total / groupEntries.length;
|
|
248
|
+
// Calculate trend (last 7 days vs previous 7 days)
|
|
249
|
+
const recent = groupEntries.slice(-7).reduce((sum, e) => sum + e.cost, 0);
|
|
250
|
+
const previous = groupEntries.slice(-14, -7).reduce((sum, e) => sum + e.cost, 0);
|
|
251
|
+
const trend = previous > 0 ? ((recent - previous) / previous) * 100 : 0;
|
|
252
|
+
totals.push({ group, total, avg, trend });
|
|
253
|
+
}
|
|
254
|
+
// Sort by total cost
|
|
255
|
+
totals.sort((a, b) => b.total - a.total);
|
|
256
|
+
// Display table
|
|
257
|
+
const grandTotal = totals.reduce((sum, t) => sum + t.total, 0);
|
|
258
|
+
ui.print(` ${ui.dim('Total period cost:')} ${ui.color(formatCurrency(grandTotal), 'cyan')}`);
|
|
259
|
+
ui.newLine();
|
|
260
|
+
// Table header
|
|
261
|
+
const colWidths = { group: 20, total: 15, avg: 15, trend: 15 };
|
|
262
|
+
const header = [
|
|
263
|
+
groupBy.charAt(0).toUpperCase() + groupBy.slice(1).padEnd(colWidths.group),
|
|
264
|
+
'Total'.padStart(colWidths.total),
|
|
265
|
+
'Daily Avg'.padStart(colWidths.avg),
|
|
266
|
+
'Trend (7d)'.padStart(colWidths.trend),
|
|
267
|
+
].join(' ');
|
|
268
|
+
ui.print(` ${ui.dim(header)}`);
|
|
269
|
+
ui.print(` ${ui.dim('-'.repeat(header.length))}`);
|
|
270
|
+
for (const { group, total, avg, trend } of totals.slice(0, 10)) {
|
|
271
|
+
const trendStr = trend > 0
|
|
272
|
+
? ui.color(`+${trend.toFixed(1)}%`, 'red')
|
|
273
|
+
: trend < 0
|
|
274
|
+
? ui.color(`${trend.toFixed(1)}%`, 'green')
|
|
275
|
+
: ui.color('0.0%', 'dim');
|
|
276
|
+
const row = [
|
|
277
|
+
group.substring(0, colWidths.group).padEnd(colWidths.group),
|
|
278
|
+
formatCurrency(total).padStart(colWidths.total),
|
|
279
|
+
formatCurrency(avg).padStart(colWidths.avg),
|
|
280
|
+
trendStr.padStart(colWidths.trend + 10), // Extra for ANSI codes
|
|
281
|
+
].join(' ');
|
|
282
|
+
ui.print(` ${row}`);
|
|
283
|
+
}
|
|
284
|
+
if (totals.length > 10) {
|
|
285
|
+
ui.newLine();
|
|
286
|
+
ui.print(ui.dim(` ... and ${totals.length - 10} more ${groupBy}s`));
|
|
287
|
+
}
|
|
288
|
+
}
|
|
289
|
+
// ==========================================
|
|
290
|
+
// Commands
|
|
291
|
+
// ==========================================
|
|
292
|
+
/**
|
|
293
|
+
* Cost parent command
|
|
294
|
+
*/
|
|
295
|
+
export async function costCommand(args) {
|
|
296
|
+
if (args.length === 0) {
|
|
297
|
+
ui.header('Nimbus Cost', 'Infrastructure cost estimation and tracking');
|
|
298
|
+
ui.newLine();
|
|
299
|
+
ui.print('Usage: nimbus cost <command> [options]');
|
|
300
|
+
ui.newLine();
|
|
301
|
+
ui.print('Commands:');
|
|
302
|
+
ui.print(` ${ui.bold('estimate')} Estimate infrastructure costs from Terraform`);
|
|
303
|
+
ui.print(` ${ui.bold('compare')} Compare cost between two workspaces or directories`);
|
|
304
|
+
ui.print(` ${ui.bold('report')} Export session cost summary`);
|
|
305
|
+
ui.print(` ${ui.bold('history')} View historical cost data`);
|
|
306
|
+
ui.print(` ${ui.bold('diff')} Diff cost between two paths`);
|
|
307
|
+
ui.newLine();
|
|
308
|
+
ui.print('Examples:');
|
|
309
|
+
ui.print(' nimbus cost estimate');
|
|
310
|
+
ui.print(' nimbus cost estimate --workspace staging --provider aws');
|
|
311
|
+
ui.print(' nimbus cost estimate -d ./terraform --detailed');
|
|
312
|
+
ui.print(' nimbus cost compare ./workspace1 ./workspace2');
|
|
313
|
+
ui.print(' nimbus cost report --output csv');
|
|
314
|
+
ui.print(' nimbus cost history --days 30 --group-by service');
|
|
315
|
+
return;
|
|
316
|
+
}
|
|
317
|
+
const subcommand = args[0];
|
|
318
|
+
const subArgs = args.slice(1);
|
|
319
|
+
switch (subcommand) {
|
|
320
|
+
case 'estimate': {
|
|
321
|
+
const opts = parseCostEstimateOptions(subArgs);
|
|
322
|
+
// M3: handle --workspace and --provider flags for standalone estimate
|
|
323
|
+
for (let i = 0; i < subArgs.length; i++) {
|
|
324
|
+
if (subArgs[i] === '--workspace' && subArgs[i + 1]) {
|
|
325
|
+
opts.directory = opts.directory ?? subArgs[i + 1];
|
|
326
|
+
}
|
|
327
|
+
if (subArgs[i] === '--provider' && subArgs[i + 1]) {
|
|
328
|
+
// Provider hint is informational — store in compare for future use
|
|
329
|
+
}
|
|
330
|
+
}
|
|
331
|
+
await costEstimateCommand(opts);
|
|
332
|
+
break;
|
|
333
|
+
}
|
|
334
|
+
case 'compare': {
|
|
335
|
+
// M3: nimbus cost compare <workspace1> <workspace2>
|
|
336
|
+
const path1 = subArgs[0];
|
|
337
|
+
const path2 = subArgs[1];
|
|
338
|
+
if (!path1 || !path2) {
|
|
339
|
+
ui.error('Usage: nimbus cost compare <workspace1> <workspace2>');
|
|
340
|
+
ui.info('Compares infracost estimates between two directories.');
|
|
341
|
+
process.exit(1);
|
|
342
|
+
}
|
|
343
|
+
const format = subArgs.includes('--json') ? 'json' : 'table';
|
|
344
|
+
await costCompareCommand(path1, path2, { format });
|
|
345
|
+
break;
|
|
346
|
+
}
|
|
347
|
+
case 'report': {
|
|
348
|
+
// M3: nimbus cost report [--output csv|json|text]
|
|
349
|
+
let outputFormat = 'text';
|
|
350
|
+
for (let i = 0; i < subArgs.length; i++) {
|
|
351
|
+
if (subArgs[i] === '--output' && subArgs[i + 1]) {
|
|
352
|
+
const fmt = subArgs[i + 1];
|
|
353
|
+
if (fmt === 'csv' || fmt === 'json' || fmt === 'text') {
|
|
354
|
+
outputFormat = fmt;
|
|
355
|
+
}
|
|
356
|
+
}
|
|
357
|
+
}
|
|
358
|
+
await costReportCommand(outputFormat);
|
|
359
|
+
break;
|
|
360
|
+
}
|
|
361
|
+
case 'history':
|
|
362
|
+
await costHistoryCommand(parseCostHistoryOptions(subArgs));
|
|
363
|
+
break;
|
|
364
|
+
case 'diff': {
|
|
365
|
+
const format = subArgs.includes('--json') ? 'json' : 'table';
|
|
366
|
+
await costDiffCommand(subArgs[0], subArgs[1], { format });
|
|
367
|
+
break;
|
|
368
|
+
}
|
|
369
|
+
default:
|
|
370
|
+
ui.error(`Unknown cost command: ${subcommand}`);
|
|
371
|
+
ui.info('Run "nimbus cost" for usage');
|
|
372
|
+
}
|
|
373
|
+
}
|
|
374
|
+
/**
|
|
375
|
+
* Cost estimate command
|
|
376
|
+
*/
|
|
377
|
+
export async function costEstimateCommand(options) {
|
|
378
|
+
const directory = options.directory || process.cwd();
|
|
379
|
+
ui.header('Nimbus Cost Estimate', directory);
|
|
380
|
+
// Check for Terraform files
|
|
381
|
+
const hasTerraform = fs.existsSync(path.join(directory, 'main.tf')) ||
|
|
382
|
+
fs.existsSync(path.join(directory, 'terraform.tf')) ||
|
|
383
|
+
fs.readdirSync(directory).some(f => f.endsWith('.tf'));
|
|
384
|
+
if (!hasTerraform) {
|
|
385
|
+
ui.warning('No Terraform files found in the specified directory.');
|
|
386
|
+
ui.info('Cost estimation requires Terraform configuration files.');
|
|
387
|
+
return;
|
|
388
|
+
}
|
|
389
|
+
// Check for infracost
|
|
390
|
+
if (!checkInfracost()) {
|
|
391
|
+
ui.info('Infracost is not installed. Using built-in cost estimator.');
|
|
392
|
+
ui.newLine();
|
|
393
|
+
ui.startSpinner({ message: 'Running built-in cost estimation...' });
|
|
394
|
+
try {
|
|
395
|
+
const result = await CostEstimator.estimateDirectory(directory);
|
|
396
|
+
ui.stopSpinnerSuccess('Cost estimation complete');
|
|
397
|
+
if (options.format === 'json') {
|
|
398
|
+
console.log(JSON.stringify(result, null, 2));
|
|
399
|
+
return;
|
|
400
|
+
}
|
|
401
|
+
displayCostEstimate(result, options.detailed || false);
|
|
402
|
+
ui.newLine();
|
|
403
|
+
ui.info('For more accurate pricing, install Infracost:');
|
|
404
|
+
ui.print(` ${ui.dim('brew install infracost')} (macOS)`);
|
|
405
|
+
ui.print(` ${ui.dim('curl -fsSL https://raw.githubusercontent.com/infracost/infracost/master/scripts/install.sh | sh')} (Linux)`);
|
|
406
|
+
}
|
|
407
|
+
catch (error) {
|
|
408
|
+
ui.stopSpinnerFail('Cost estimation failed');
|
|
409
|
+
ui.error(`Built-in estimator error: ${error instanceof Error ? error.message : String(error)}`);
|
|
410
|
+
}
|
|
411
|
+
return;
|
|
412
|
+
}
|
|
413
|
+
ui.startSpinner({ message: 'Running cost estimation...' });
|
|
414
|
+
const estimate = runInfracostBreakdown(directory);
|
|
415
|
+
if (!estimate) {
|
|
416
|
+
ui.stopSpinnerFail('Cost estimation failed');
|
|
417
|
+
ui.error('Failed to run infracost. Make sure you have authenticated with "infracost auth login"');
|
|
418
|
+
return;
|
|
419
|
+
}
|
|
420
|
+
ui.stopSpinnerSuccess('Cost estimation complete');
|
|
421
|
+
if (options.format === 'json') {
|
|
422
|
+
console.log(JSON.stringify(estimate, null, 2));
|
|
423
|
+
return;
|
|
424
|
+
}
|
|
425
|
+
displayCostEstimate(estimate, options.detailed || false);
|
|
426
|
+
}
|
|
427
|
+
/**
|
|
428
|
+
* Cost history command
|
|
429
|
+
*/
|
|
430
|
+
export async function costHistoryCommand(options) {
|
|
431
|
+
ui.header('Nimbus Cost History', `Last ${options.days} days`);
|
|
432
|
+
// In a real implementation, this would query cloud provider cost APIs
|
|
433
|
+
// For now, we'll use mock data or show instructions
|
|
434
|
+
if (!options.provider) {
|
|
435
|
+
const providerChoice = await select({
|
|
436
|
+
message: 'Select cloud provider:',
|
|
437
|
+
options: [
|
|
438
|
+
{ label: 'AWS', value: 'aws', description: 'Amazon Web Services Cost Explorer' },
|
|
439
|
+
{ label: 'GCP', value: 'gcp', description: 'Google Cloud Billing' },
|
|
440
|
+
{ label: 'Azure', value: 'azure', description: 'Azure Cost Management' },
|
|
441
|
+
{ label: 'Demo', value: 'demo', description: 'Show demo data' },
|
|
442
|
+
],
|
|
443
|
+
});
|
|
444
|
+
options.provider = providerChoice;
|
|
445
|
+
}
|
|
446
|
+
if (options.provider === 'demo') {
|
|
447
|
+
ui.startSpinner({ message: 'Loading cost history...' });
|
|
448
|
+
// Simulate API call delay
|
|
449
|
+
await new Promise(resolve => setTimeout(resolve, 1000));
|
|
450
|
+
const entries = getMockCostHistory(options);
|
|
451
|
+
ui.stopSpinnerSuccess(`Loaded ${entries.length} entries`);
|
|
452
|
+
if (options.format === 'json') {
|
|
453
|
+
console.log(JSON.stringify(entries, null, 2));
|
|
454
|
+
return;
|
|
455
|
+
}
|
|
456
|
+
displayCostHistory(entries, options.groupBy || 'service');
|
|
457
|
+
ui.newLine();
|
|
458
|
+
ui.warning('This is demo data. Connect to a real cloud provider for actual costs.');
|
|
459
|
+
return;
|
|
460
|
+
}
|
|
461
|
+
// Real provider - show instructions
|
|
462
|
+
ui.newLine();
|
|
463
|
+
ui.info(`To view ${options.provider?.toUpperCase()} cost history, you need to configure credentials.`);
|
|
464
|
+
ui.newLine();
|
|
465
|
+
switch (options.provider) {
|
|
466
|
+
case 'aws':
|
|
467
|
+
ui.print('AWS Cost Explorer API requires:');
|
|
468
|
+
ui.print(' 1. AWS credentials configured (aws configure)');
|
|
469
|
+
ui.print(' 2. Cost Explorer enabled in your AWS account');
|
|
470
|
+
ui.print(' 3. IAM permissions for ce:GetCostAndUsage');
|
|
471
|
+
break;
|
|
472
|
+
case 'gcp':
|
|
473
|
+
ui.print('GCP Billing API requires:');
|
|
474
|
+
ui.print(' 1. GCP credentials configured (gcloud auth)');
|
|
475
|
+
ui.print(' 2. Billing export enabled to BigQuery');
|
|
476
|
+
ui.print(' 3. IAM permissions for bigquery.jobs.create');
|
|
477
|
+
break;
|
|
478
|
+
case 'azure':
|
|
479
|
+
ui.print('Azure Cost Management API requires:');
|
|
480
|
+
ui.print(' 1. Azure CLI authenticated (az login)');
|
|
481
|
+
ui.print(' 2. Reader role on the subscription');
|
|
482
|
+
ui.print(' 3. Cost Management permissions');
|
|
483
|
+
break;
|
|
484
|
+
}
|
|
485
|
+
ui.newLine();
|
|
486
|
+
ui.print('Run with --provider demo to see sample data.');
|
|
487
|
+
}
|
|
488
|
+
/**
|
|
489
|
+
* H5: Cost diff — compare infracost estimates between two directories or branches.
|
|
490
|
+
*/
|
|
491
|
+
export async function costDiffCommand(path1, path2, opts = {}) {
|
|
492
|
+
if (!path1 || !path2) {
|
|
493
|
+
ui.error('Usage: nimbus cost diff <path1> <path2> [--json]');
|
|
494
|
+
process.exit(1);
|
|
495
|
+
}
|
|
496
|
+
// Check if infracost is available
|
|
497
|
+
try {
|
|
498
|
+
execSync('infracost --version', { stdio: 'pipe' });
|
|
499
|
+
}
|
|
500
|
+
catch {
|
|
501
|
+
ui.error('infracost is not installed.');
|
|
502
|
+
ui.info('Install it from: https://www.infracost.io/docs/');
|
|
503
|
+
ui.info(' brew install infracost (macOS)');
|
|
504
|
+
ui.info(' curl -fsSL https://raw.githubusercontent.com/infracost/infracost/master/scripts/install.sh | sh (Linux)');
|
|
505
|
+
process.exit(1);
|
|
506
|
+
}
|
|
507
|
+
ui.startSpinner({ message: `Comparing costs: ${path1} vs ${path2}` });
|
|
508
|
+
try {
|
|
509
|
+
const rawOutput = execSync(`infracost diff --path "${path1}" --compare-to "${path2}" --format json`, { encoding: 'utf-8', stdio: 'pipe', maxBuffer: 10 * 1024 * 1024 });
|
|
510
|
+
const data = JSON.parse(rawOutput);
|
|
511
|
+
ui.stopSpinnerSuccess('Cost diff complete');
|
|
512
|
+
if (opts.format === 'json') {
|
|
513
|
+
console.log(JSON.stringify(data, null, 2));
|
|
514
|
+
return;
|
|
515
|
+
}
|
|
516
|
+
// Table output
|
|
517
|
+
ui.header('Cost Diff');
|
|
518
|
+
ui.print(` Comparing: ${path1} (current) → ${path2} (baseline)`);
|
|
519
|
+
ui.newLine();
|
|
520
|
+
const monthly = data.diffTotalMonthlyCost ?? 0;
|
|
521
|
+
ui.print(` ${ui.bold('Monthly delta:')} ${formatChange(monthly, data.currency)}`);
|
|
522
|
+
ui.print(` ${ui.bold('New total: ')} ${formatCurrency(data.totalMonthlyCost, data.currency)}/mo`);
|
|
523
|
+
ui.newLine();
|
|
524
|
+
// Per-project breakdown
|
|
525
|
+
for (const project of data.projects ?? []) {
|
|
526
|
+
if ((project.diffTotalMonthlyCost ?? 0) === 0)
|
|
527
|
+
continue;
|
|
528
|
+
ui.print(` ${ui.bold(project.name)}: ${formatChange(project.diffTotalMonthlyCost, data.currency)}/mo`);
|
|
529
|
+
for (const resource of (project.resources ?? []).slice(0, 10)) {
|
|
530
|
+
ui.print(` ${resource.name.slice(0, 50).padEnd(50)} ${formatCurrency(resource.monthlyCost, data.currency)}/mo`);
|
|
531
|
+
}
|
|
532
|
+
}
|
|
533
|
+
}
|
|
534
|
+
catch (error) {
|
|
535
|
+
ui.stopSpinnerFail('Cost diff failed');
|
|
536
|
+
ui.error(error instanceof Error ? error.message : String(error));
|
|
537
|
+
process.exit(1);
|
|
538
|
+
}
|
|
539
|
+
}
|
|
540
|
+
/**
|
|
541
|
+
* M3: Cost compare — compare infracost estimates between two workspaces.
|
|
542
|
+
* Runs infracost breakdown for each path and shows the delta.
|
|
543
|
+
*/
|
|
544
|
+
export async function costCompareCommand(workspace1, workspace2, opts = {}) {
|
|
545
|
+
if (!workspace1 || !workspace2) {
|
|
546
|
+
ui.error('Usage: nimbus cost compare <workspace1> <workspace2>');
|
|
547
|
+
process.exit(1);
|
|
548
|
+
}
|
|
549
|
+
// Try infracost first
|
|
550
|
+
const hasInfracost = checkInfracost();
|
|
551
|
+
if (!hasInfracost) {
|
|
552
|
+
ui.info('infracost is not installed — showing placeholder comparison.');
|
|
553
|
+
ui.info('Install infracost for real cost estimates: https://infracost.io');
|
|
554
|
+
ui.newLine();
|
|
555
|
+
ui.print(` Workspace 1: ${workspace1}`);
|
|
556
|
+
ui.print(` Workspace 2: ${workspace2}`);
|
|
557
|
+
ui.print(' Cost delta: N/A (install infracost for actual data)');
|
|
558
|
+
ui.newLine();
|
|
559
|
+
ui.info(' brew install infracost (macOS)');
|
|
560
|
+
ui.info(' curl -fsSL https://raw.githubusercontent.com/infracost/infracost/master/scripts/install.sh | sh (Linux)');
|
|
561
|
+
return;
|
|
562
|
+
}
|
|
563
|
+
ui.startSpinner({ message: `Comparing costs: ${workspace1} vs ${workspace2}` });
|
|
564
|
+
try {
|
|
565
|
+
// Run infracost breakdown for each workspace
|
|
566
|
+
const est1 = runInfracostBreakdown(workspace1);
|
|
567
|
+
const est2 = runInfracostBreakdown(workspace2);
|
|
568
|
+
ui.stopSpinnerSuccess('Cost comparison complete');
|
|
569
|
+
if (!est1 && !est2) {
|
|
570
|
+
ui.warning('Could not obtain infracost estimates for either workspace.');
|
|
571
|
+
return;
|
|
572
|
+
}
|
|
573
|
+
const cost1 = est1?.totalMonthlyCost ?? 0;
|
|
574
|
+
const cost2 = est2?.totalMonthlyCost ?? 0;
|
|
575
|
+
const delta = cost1 - cost2;
|
|
576
|
+
const currency = est1?.currency ?? est2?.currency ?? 'USD';
|
|
577
|
+
if (opts.format === 'json') {
|
|
578
|
+
console.log(JSON.stringify({ workspace1, workspace2, cost1, cost2, delta, currency }, null, 2));
|
|
579
|
+
return;
|
|
580
|
+
}
|
|
581
|
+
ui.header('Cost Comparison');
|
|
582
|
+
ui.print(` ${ui.bold(workspace1)}: ${formatCurrency(cost1, currency)}/mo`);
|
|
583
|
+
ui.print(` ${ui.bold(workspace2)}: ${formatCurrency(cost2, currency)}/mo`);
|
|
584
|
+
ui.newLine();
|
|
585
|
+
ui.print(` ${ui.bold('Delta (1 vs 2):')} ${formatChange(delta, currency)}/mo`);
|
|
586
|
+
}
|
|
587
|
+
catch (err) {
|
|
588
|
+
ui.stopSpinnerFail('Cost comparison failed');
|
|
589
|
+
ui.error(err instanceof Error ? err.message : String(err));
|
|
590
|
+
process.exit(1);
|
|
591
|
+
}
|
|
592
|
+
}
|
|
593
|
+
/**
|
|
594
|
+
* M3: Cost report — export the session cost summary.
|
|
595
|
+
* Reads session tool-call cost data and exports it in the requested format.
|
|
596
|
+
*/
|
|
597
|
+
export async function costReportCommand(outputFormat = 'text') {
|
|
598
|
+
ui.header('Nimbus Cost Report', `Session cost summary (format: ${outputFormat})`);
|
|
599
|
+
// Attempt to read cost data from ~/.nimbus/nimbus.db via the state DB
|
|
600
|
+
// For now, expose session-level token cost data as a report placeholder.
|
|
601
|
+
// In a full implementation this would query the cost_tracker table in SQLite.
|
|
602
|
+
const report = {
|
|
603
|
+
generatedAt: new Date().toISOString(),
|
|
604
|
+
note: 'Session cost data is tracked in ~/.nimbus/nimbus.db (cost_tracker table).',
|
|
605
|
+
hint: 'Run `nimbus cost estimate` to estimate infrastructure costs for the current directory.',
|
|
606
|
+
format: outputFormat,
|
|
607
|
+
};
|
|
608
|
+
if (outputFormat === 'json') {
|
|
609
|
+
console.log(JSON.stringify(report, null, 2));
|
|
610
|
+
return;
|
|
611
|
+
}
|
|
612
|
+
if (outputFormat === 'csv') {
|
|
613
|
+
console.log('timestamp,description,cost_usd');
|
|
614
|
+
console.log(`${report.generatedAt},session_summary,0.00`);
|
|
615
|
+
ui.newLine();
|
|
616
|
+
ui.info(report.note);
|
|
617
|
+
return;
|
|
618
|
+
}
|
|
619
|
+
// text
|
|
620
|
+
ui.newLine();
|
|
621
|
+
ui.print(` ${ui.dim('Generated:')} ${report.generatedAt}`);
|
|
622
|
+
ui.newLine();
|
|
623
|
+
ui.info(report.note);
|
|
624
|
+
ui.info(report.hint);
|
|
625
|
+
}
|