@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,167 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Logs Command — H1
|
|
3
|
+
*
|
|
4
|
+
* Shorthand for streaming Kubernetes pod logs directly.
|
|
5
|
+
*
|
|
6
|
+
* Usage:
|
|
7
|
+
* nimbus logs <pod> [-n namespace] [-f] [--previous] [--tail N] [--analyze]
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
import { execFileSync, spawn } from 'node:child_process';
|
|
11
|
+
import { ui } from '../wizard/ui';
|
|
12
|
+
|
|
13
|
+
export interface LogsOptions {
|
|
14
|
+
namespace?: string;
|
|
15
|
+
follow?: boolean;
|
|
16
|
+
previous?: boolean;
|
|
17
|
+
tail?: number;
|
|
18
|
+
analyze?: boolean;
|
|
19
|
+
container?: string;
|
|
20
|
+
context?: string;
|
|
21
|
+
quiet?: boolean;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* Build the kubectl logs argument array.
|
|
26
|
+
*/
|
|
27
|
+
function buildArgs(pod: string, options: LogsOptions): string[] {
|
|
28
|
+
const args = ['logs', pod];
|
|
29
|
+
if (options.namespace) args.push('-n', options.namespace);
|
|
30
|
+
if (options.follow) args.push('--follow');
|
|
31
|
+
if (options.previous) args.push('--previous');
|
|
32
|
+
if (options.tail !== undefined) args.push('--tail', String(options.tail));
|
|
33
|
+
if (options.container) args.push('-c', options.container);
|
|
34
|
+
if (options.context) args.push('--context', options.context);
|
|
35
|
+
return args;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* Stream or fetch Kubernetes pod logs.
|
|
40
|
+
*/
|
|
41
|
+
export async function logsCommand(pod: string, options: LogsOptions = {}): Promise<void> {
|
|
42
|
+
if (!pod) {
|
|
43
|
+
ui.error('Usage: nimbus logs <pod> [-n namespace] [-f] [--tail N]');
|
|
44
|
+
process.exit(1);
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
const args = buildArgs(pod, options);
|
|
48
|
+
|
|
49
|
+
// --analyze: capture output and pass to agent for analysis
|
|
50
|
+
if (options.analyze) {
|
|
51
|
+
if (!options.quiet) {
|
|
52
|
+
ui.startSpinner({ message: `Fetching logs for ${pod}...` });
|
|
53
|
+
}
|
|
54
|
+
try {
|
|
55
|
+
const output = execFileSync('kubectl', args, {
|
|
56
|
+
encoding: 'utf-8',
|
|
57
|
+
timeout: 30_000,
|
|
58
|
+
stdio: ['pipe', 'pipe', 'pipe'],
|
|
59
|
+
});
|
|
60
|
+
if (!options.quiet) {
|
|
61
|
+
ui.stopSpinnerSuccess('Logs fetched');
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
// Lazy-import agent loop to avoid circular deps at startup
|
|
65
|
+
const { runAgentLoop } = await import('../agent/loop');
|
|
66
|
+
const { getAppContext } = await import('../app');
|
|
67
|
+
const { defaultToolRegistry } = await import('../tools/schemas/types');
|
|
68
|
+
const { standardTools } = await import('../tools/schemas/standard');
|
|
69
|
+
const ctx = getAppContext();
|
|
70
|
+
if (!ctx) {
|
|
71
|
+
ui.error('App not initialized. Run `nimbus login` first.');
|
|
72
|
+
process.exit(1);
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
// Ensure tools are registered for the agent loop
|
|
76
|
+
if (defaultToolRegistry.size === 0) {
|
|
77
|
+
for (const tool of standardTools) {
|
|
78
|
+
try { defaultToolRegistry.register(tool); } catch { /* skip duplicates */ }
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
if (!options.quiet) {
|
|
83
|
+
ui.info('Analyzing logs...');
|
|
84
|
+
}
|
|
85
|
+
await runAgentLoop(
|
|
86
|
+
`Analyze these Kubernetes logs for pod "${pod}". Identify errors, anomalies, and crash patterns:\n\n${output}`,
|
|
87
|
+
[],
|
|
88
|
+
{
|
|
89
|
+
router: ctx.router,
|
|
90
|
+
toolRegistry: defaultToolRegistry,
|
|
91
|
+
mode: 'plan',
|
|
92
|
+
maxTurns: 5,
|
|
93
|
+
onText: text => { process.stdout.write(text); },
|
|
94
|
+
}
|
|
95
|
+
);
|
|
96
|
+
console.log('');
|
|
97
|
+
} catch (error: any) {
|
|
98
|
+
if (!options.quiet) {
|
|
99
|
+
ui.stopSpinnerFail('Failed to fetch logs');
|
|
100
|
+
}
|
|
101
|
+
ui.error(error.message);
|
|
102
|
+
process.exit(1);
|
|
103
|
+
}
|
|
104
|
+
return;
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
// --follow: stream via spawn
|
|
108
|
+
if (options.follow) {
|
|
109
|
+
const child = spawn('kubectl', args, {
|
|
110
|
+
stdio: ['ignore', 'inherit', 'inherit'],
|
|
111
|
+
});
|
|
112
|
+
await new Promise<void>((resolve, reject) => {
|
|
113
|
+
child.on('close', code => {
|
|
114
|
+
if (code !== 0) reject(new Error(`kubectl logs exited with code ${code}`));
|
|
115
|
+
else resolve();
|
|
116
|
+
});
|
|
117
|
+
child.on('error', reject);
|
|
118
|
+
});
|
|
119
|
+
return;
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
// Non-follow: capture and print
|
|
123
|
+
try {
|
|
124
|
+
const output = execFileSync('kubectl', args, {
|
|
125
|
+
encoding: 'utf-8',
|
|
126
|
+
timeout: 60_000,
|
|
127
|
+
stdio: ['pipe', 'pipe', 'pipe'],
|
|
128
|
+
});
|
|
129
|
+
process.stdout.write(output);
|
|
130
|
+
} catch (error: any) {
|
|
131
|
+
ui.error(`kubectl logs failed: ${error.message}`);
|
|
132
|
+
process.exit(1);
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
/**
|
|
137
|
+
* Parse `nimbus logs <pod> [options]` arguments.
|
|
138
|
+
*/
|
|
139
|
+
export function parseLogsArgs(args: string[]): { pod: string; options: LogsOptions } {
|
|
140
|
+
let pod = '';
|
|
141
|
+
const options: LogsOptions = {};
|
|
142
|
+
|
|
143
|
+
for (let i = 0; i < args.length; i++) {
|
|
144
|
+
const arg = args[i];
|
|
145
|
+
if (arg === '-n' || arg === '--namespace') {
|
|
146
|
+
options.namespace = args[++i];
|
|
147
|
+
} else if (arg === '-f' || arg === '--follow') {
|
|
148
|
+
options.follow = true;
|
|
149
|
+
} else if (arg === '-p' || arg === '--previous') {
|
|
150
|
+
options.previous = true;
|
|
151
|
+
} else if (arg === '--tail') {
|
|
152
|
+
options.tail = parseInt(args[++i] ?? '100', 10);
|
|
153
|
+
} else if (arg === '--analyze') {
|
|
154
|
+
options.analyze = true;
|
|
155
|
+
} else if (arg === '-c' || arg === '--container') {
|
|
156
|
+
options.container = args[++i];
|
|
157
|
+
} else if (arg === '--context') {
|
|
158
|
+
options.context = args[++i];
|
|
159
|
+
} else if (arg === '-q' || arg === '--quiet') {
|
|
160
|
+
options.quiet = true;
|
|
161
|
+
} else if (!arg.startsWith('-') && !pod) {
|
|
162
|
+
pod = arg;
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
return { pod, options };
|
|
167
|
+
}
|
|
@@ -79,15 +79,20 @@ export async function onboardingCommand(options: OnboardingOptions = {}): Promis
|
|
|
79
79
|
}
|
|
80
80
|
|
|
81
81
|
try {
|
|
82
|
-
// Welcome banner
|
|
82
|
+
// Welcome banner — DevOps-first identity
|
|
83
83
|
ui.newLine();
|
|
84
84
|
ui.box({
|
|
85
|
-
title: 'Welcome to Nimbus',
|
|
85
|
+
title: 'Welcome to Nimbus — AI-Powered DevOps Terminal',
|
|
86
86
|
content: [
|
|
87
|
-
'
|
|
87
|
+
'Plan, apply, and manage Terraform, Kubernetes, Helm,',
|
|
88
|
+
'AWS, GCP, and Azure using natural language.',
|
|
88
89
|
'',
|
|
89
|
-
"Let's get you set up
|
|
90
|
-
'
|
|
90
|
+
"Let's get you set up. This takes about 30 seconds.",
|
|
91
|
+
'',
|
|
92
|
+
'After setup you can:',
|
|
93
|
+
' nimbus Open interactive DevOps terminal',
|
|
94
|
+
' nimbus run "tf plan --explain" Run agent non-interactively',
|
|
95
|
+
' nimbus status Live infra health dashboard',
|
|
91
96
|
],
|
|
92
97
|
style: 'rounded',
|
|
93
98
|
borderColor: 'cyan',
|
|
@@ -96,6 +101,15 @@ export async function onboardingCommand(options: OnboardingOptions = {}): Promis
|
|
|
96
101
|
});
|
|
97
102
|
ui.newLine();
|
|
98
103
|
|
|
104
|
+
// Gap 12: Show env var shortcut for power users before the wizard
|
|
105
|
+
ui.print(ui.dim('─────────────────────────────────────────────────────'));
|
|
106
|
+
ui.print(ui.dim(' Quick setup: export an API key and restart nimbus.'));
|
|
107
|
+
ui.print(ui.dim(' export ANTHROPIC_API_KEY=sk-ant-...'));
|
|
108
|
+
ui.print(ui.dim(' export OPENAI_API_KEY=sk-...'));
|
|
109
|
+
ui.print(ui.dim(' export GOOGLE_API_KEY=...'));
|
|
110
|
+
ui.print(ui.dim('─────────────────────────────────────────────────────'));
|
|
111
|
+
ui.newLine();
|
|
112
|
+
|
|
99
113
|
// Provider selection
|
|
100
114
|
const providerOptions = Object.values(PROVIDER_REGISTRY).map(p => ({
|
|
101
115
|
label: p.displayName,
|
|
@@ -129,14 +143,71 @@ export async function onboardingCommand(options: OnboardingOptions = {}): Promis
|
|
|
129
143
|
}
|
|
130
144
|
ui.newLine();
|
|
131
145
|
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
146
|
+
// Retry loop: re-prompt on invalid key (up to 3 attempts)
|
|
147
|
+
const MAX_ATTEMPTS = 3;
|
|
148
|
+
let attempt = 0;
|
|
149
|
+
let validated = false;
|
|
135
150
|
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
151
|
+
while (attempt < MAX_ATTEMPTS) {
|
|
152
|
+
attempt++;
|
|
153
|
+
|
|
154
|
+
apiKey = await input({
|
|
155
|
+
message:
|
|
156
|
+
attempt === 1
|
|
157
|
+
? `Enter your ${providerInfo.displayName} API key`
|
|
158
|
+
: `Enter your ${providerInfo.displayName} API key (attempt ${attempt}/${MAX_ATTEMPTS})`,
|
|
159
|
+
});
|
|
160
|
+
|
|
161
|
+
if (!apiKey) {
|
|
162
|
+
ui.newLine();
|
|
163
|
+
ui.warning('No API key entered. You can run "nimbus login" later to set up.');
|
|
164
|
+
ui.newLine();
|
|
165
|
+
return;
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
// Validate credentials
|
|
139
169
|
ui.newLine();
|
|
170
|
+
ui.startSpinner({ message: `Validating ${providerInfo.displayName} credentials...` });
|
|
171
|
+
|
|
172
|
+
let validation: { valid: boolean; error?: string };
|
|
173
|
+
try {
|
|
174
|
+
validation = await validateProviderApiKey(provider, apiKey, baseUrl);
|
|
175
|
+
} catch (err: unknown) {
|
|
176
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
177
|
+
ui.stopSpinnerFail(`Validation error: ${msg}`);
|
|
178
|
+
if (attempt < MAX_ATTEMPTS) {
|
|
179
|
+
ui.warning('Please check your key and try again.');
|
|
180
|
+
ui.newLine();
|
|
181
|
+
continue;
|
|
182
|
+
}
|
|
183
|
+
ui.newLine();
|
|
184
|
+
ui.info('You can retry with "nimbus login" or set the API key via environment variable.');
|
|
185
|
+
ui.newLine();
|
|
186
|
+
return;
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
if (!validation.valid) {
|
|
190
|
+
ui.stopSpinnerFail(`Invalid key: ${validation.error}`);
|
|
191
|
+
if (attempt < MAX_ATTEMPTS) {
|
|
192
|
+
ui.warning('Please check your key and try again.');
|
|
193
|
+
ui.newLine();
|
|
194
|
+
continue;
|
|
195
|
+
}
|
|
196
|
+
ui.newLine();
|
|
197
|
+
ui.info('You can retry with "nimbus login" or set the API key via environment variable:');
|
|
198
|
+
if (providerInfo.envVarName) {
|
|
199
|
+
ui.info(` export ${providerInfo.envVarName}=your-key`);
|
|
200
|
+
}
|
|
201
|
+
ui.newLine();
|
|
202
|
+
return;
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
ui.stopSpinnerSuccess(`${providerInfo.displayName} credentials verified!`);
|
|
206
|
+
validated = true;
|
|
207
|
+
break;
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
if (!validated) {
|
|
140
211
|
return;
|
|
141
212
|
}
|
|
142
213
|
} else if (providerInfo.supportsBaseUrl) {
|
|
@@ -148,34 +219,49 @@ export async function onboardingCommand(options: OnboardingOptions = {}): Promis
|
|
|
148
219
|
});
|
|
149
220
|
}
|
|
150
221
|
|
|
151
|
-
//
|
|
152
|
-
|
|
153
|
-
ui.startSpinner({ message: `Validating ${providerInfo.displayName} credentials...` });
|
|
154
|
-
|
|
155
|
-
let validation: { valid: boolean; error?: string };
|
|
222
|
+
// Detect live infra stack from current directory (Terraform, K8s, Helm, etc.)
|
|
223
|
+
let detectedInfraStack: string[] = [];
|
|
156
224
|
try {
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
const
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
225
|
+
const { existsSync, readdirSync } = await import('node:fs');
|
|
226
|
+
const cwd = process.cwd();
|
|
227
|
+
const files = readdirSync(cwd);
|
|
228
|
+
if (files.some(f => f.endsWith('.tf') || f === 'terraform')) detectedInfraStack.push('terraform');
|
|
229
|
+
if (files.some(f => f === 'Chart.yaml' || f === 'helmfile.yaml')) detectedInfraStack.push('helm');
|
|
230
|
+
if (files.some(f => f.endsWith('.yaml') || f.endsWith('.yml'))) {
|
|
231
|
+
// Check if any yaml looks like K8s
|
|
232
|
+
const yamls = files.filter(f => f.endsWith('.yaml') || f.endsWith('.yml')).slice(0, 5);
|
|
233
|
+
for (const y of yamls) {
|
|
234
|
+
try {
|
|
235
|
+
const txt = require('node:fs').readFileSync(require('node:path').join(cwd, y), 'utf-8');
|
|
236
|
+
if (txt.includes('apiVersion:') && txt.includes('kind:')) { detectedInfraStack.push('kubernetes'); break; }
|
|
237
|
+
} catch { /* skip */ }
|
|
238
|
+
}
|
|
239
|
+
}
|
|
240
|
+
if (existsSync(require('node:path').join(cwd, 'docker-compose.yaml')) || existsSync(require('node:path').join(cwd, 'docker-compose.yml')) || existsSync(require('node:path').join(cwd, 'Dockerfile'))) {
|
|
241
|
+
detectedInfraStack.push('docker');
|
|
173
242
|
}
|
|
243
|
+
} catch { /* non-critical */ }
|
|
244
|
+
|
|
245
|
+
if (detectedInfraStack.length > 0) {
|
|
174
246
|
ui.newLine();
|
|
175
|
-
|
|
247
|
+
ui.print(ui.color(`Detected infrastructure: ${detectedInfraStack.join(', ')}`, 'green'));
|
|
248
|
+
ui.print(ui.dim('Nimbus will use this context to give better DevOps assistance.'));
|
|
176
249
|
}
|
|
177
250
|
|
|
178
|
-
|
|
251
|
+
// C5: Cloud provider selection step
|
|
252
|
+
let primaryClouds: string[] = [];
|
|
253
|
+
try {
|
|
254
|
+
const cloudInput = await input({
|
|
255
|
+
message: 'Which cloud providers do you primarily use? (comma-separated: aws, gcp, azure, or none)',
|
|
256
|
+
defaultValue: 'none',
|
|
257
|
+
});
|
|
258
|
+
if (cloudInput && cloudInput.trim().toLowerCase() !== 'none') {
|
|
259
|
+
primaryClouds = cloudInput
|
|
260
|
+
.split(',')
|
|
261
|
+
.map((s: string) => s.trim().toLowerCase())
|
|
262
|
+
.filter((s: string) => ['aws', 'gcp', 'azure'].includes(s));
|
|
263
|
+
}
|
|
264
|
+
} catch { /* non-critical — prompt may fail in non-interactive environments */ }
|
|
179
265
|
|
|
180
266
|
// Save credentials
|
|
181
267
|
const defaultModel = getDefaultModel(provider);
|
|
@@ -198,6 +284,25 @@ export async function onboardingCommand(options: OnboardingOptions = {}): Promis
|
|
|
198
284
|
/* non-critical */
|
|
199
285
|
}
|
|
200
286
|
|
|
287
|
+
// C5: Save primaryClouds to ~/.nimbus/config.json
|
|
288
|
+
if (primaryClouds.length > 0) {
|
|
289
|
+
try {
|
|
290
|
+
const { existsSync, readFileSync, writeFileSync, mkdirSync } = await import('node:fs');
|
|
291
|
+
const { join } = await import('node:path');
|
|
292
|
+
const { homedir } = await import('node:os');
|
|
293
|
+
const configJsonPath = join(homedir(), '.nimbus', 'config.json');
|
|
294
|
+
mkdirSync(join(homedir(), '.nimbus'), { recursive: true });
|
|
295
|
+
let configData: Record<string, unknown> = {};
|
|
296
|
+
if (existsSync(configJsonPath)) {
|
|
297
|
+
try {
|
|
298
|
+
configData = JSON.parse(readFileSync(configJsonPath, 'utf-8')) as Record<string, unknown>;
|
|
299
|
+
} catch { /* start fresh */ }
|
|
300
|
+
}
|
|
301
|
+
configData.primaryClouds = primaryClouds;
|
|
302
|
+
writeFileSync(configJsonPath, JSON.stringify(configData, null, 2), 'utf-8');
|
|
303
|
+
} catch { /* non-critical */ }
|
|
304
|
+
}
|
|
305
|
+
|
|
201
306
|
// Success message
|
|
202
307
|
ui.newLine();
|
|
203
308
|
ui.box({
|
|
@@ -213,6 +318,78 @@ export async function onboardingCommand(options: OnboardingOptions = {}): Promis
|
|
|
213
318
|
titleColor: 'brightGreen',
|
|
214
319
|
padding: 1,
|
|
215
320
|
});
|
|
321
|
+
// G3: DevOps context wizard — offer to run nimbus init after LLM setup
|
|
322
|
+
try {
|
|
323
|
+
const devopsSetup = await select<string>({
|
|
324
|
+
message: 'Would you like to set up your DevOps context now? (recommended)',
|
|
325
|
+
options: [
|
|
326
|
+
{ label: 'Yes, detect my infrastructure', value: 'yes' },
|
|
327
|
+
{ label: 'No, I\'ll run nimbus init later', value: 'no' },
|
|
328
|
+
],
|
|
329
|
+
});
|
|
330
|
+
if (devopsSetup === 'yes') {
|
|
331
|
+
ui.newLine();
|
|
332
|
+
ui.startSpinner({ message: 'Detecting infrastructure context...' });
|
|
333
|
+
try {
|
|
334
|
+
const { runInit } = await import('../cli/init');
|
|
335
|
+
await runInit({ cwd: process.cwd(), quiet: false });
|
|
336
|
+
ui.stopSpinnerSuccess('NIMBUS.md generated — your infra context is ready');
|
|
337
|
+
} catch (initErr) {
|
|
338
|
+
ui.stopSpinnerFail('Could not generate NIMBUS.md (run `nimbus init` manually)');
|
|
339
|
+
}
|
|
340
|
+
}
|
|
341
|
+
} catch { /* non-critical — prompt may fail in non-interactive environments */ }
|
|
342
|
+
|
|
343
|
+
// GAP-4: Offer to install shell completions with clear success/failure feedback
|
|
344
|
+
try {
|
|
345
|
+
ui.newLine();
|
|
346
|
+
const installCompletions = await select<string>({
|
|
347
|
+
message: 'Install shell completions for nimbus? (adds tab completion)',
|
|
348
|
+
options: [
|
|
349
|
+
{ label: 'Yes, install completions', value: 'yes' },
|
|
350
|
+
{ label: 'No, skip', value: 'no' },
|
|
351
|
+
],
|
|
352
|
+
});
|
|
353
|
+
if (installCompletions === 'yes') {
|
|
354
|
+
try {
|
|
355
|
+
const { completionsCommand } = await import('./completions');
|
|
356
|
+
await completionsCommand('install');
|
|
357
|
+
ui.success('Shell completions installed successfully!');
|
|
358
|
+
// Write marker file so nimbus can detect first-run tip
|
|
359
|
+
try {
|
|
360
|
+
const { mkdirSync, writeFileSync } = await import('node:fs');
|
|
361
|
+
const { join } = await import('node:path');
|
|
362
|
+
const { homedir } = await import('node:os');
|
|
363
|
+
mkdirSync(join(homedir(), '.nimbus'), { recursive: true });
|
|
364
|
+
writeFileSync(join(homedir(), '.nimbus', 'completions-installed'), '1', 'utf-8');
|
|
365
|
+
} catch { /* non-critical */ }
|
|
366
|
+
} catch (completionErr) {
|
|
367
|
+
const shell = process.env.SHELL ?? '';
|
|
368
|
+
ui.warning(`Could not auto-install completions: ${completionErr instanceof Error ? completionErr.message : String(completionErr)}`);
|
|
369
|
+
if (/zsh/.test(shell)) {
|
|
370
|
+
ui.print(ui.dim(' Manual install: nimbus completions install'));
|
|
371
|
+
ui.print(ui.dim(' Then add to ~/.zshrc: fpath=(~/.zsh/completions $fpath) && autoload -U compinit && compinit'));
|
|
372
|
+
} else if (/bash/.test(shell)) {
|
|
373
|
+
ui.print(ui.dim(' Manual install: nimbus completions install'));
|
|
374
|
+
} else {
|
|
375
|
+
ui.print(ui.dim(' Manual install: nimbus completions install'));
|
|
376
|
+
}
|
|
377
|
+
}
|
|
378
|
+
}
|
|
379
|
+
} catch { /* non-critical — prompt may fail in non-interactive environments */ }
|
|
380
|
+
|
|
381
|
+
// C3/H1: Optional CI/CD and monitoring env vars
|
|
382
|
+
ui.newLine();
|
|
383
|
+
ui.print(ui.dim('Optional: Set these env vars for full DevOps integration:'));
|
|
384
|
+
ui.print(ui.dim(' GITLAB_TOKEN=<token> # GitLab CI pipeline access'));
|
|
385
|
+
ui.print(ui.dim(' CIRCLECI_TOKEN=<token> # CircleCI pipeline access'));
|
|
386
|
+
ui.print(ui.dim(' PROMETHEUS_URL=<url> # Prometheus metrics queries'));
|
|
387
|
+
ui.print(ui.dim(' GRAFANA_URL=<url> # Grafana dashboard access'));
|
|
388
|
+
ui.print(ui.dim(' GRAFANA_TOKEN=<token> # Grafana API token'));
|
|
389
|
+
ui.print(ui.dim(' DD_API_KEY=<key> # Datadog metrics/alerts'));
|
|
390
|
+
ui.print(ui.dim(' PD_API_KEY=<key> # PagerDuty incident management (Gap 5)'));
|
|
391
|
+
ui.print(ui.dim(' OPSGENIE_API_KEY=<key> # Opsgenie alert management (Gap 5)'));
|
|
392
|
+
ui.print(ui.dim(' BRAVE_API_KEY=<key> # Enhanced web search'));
|
|
216
393
|
ui.newLine();
|
|
217
394
|
} catch (err: unknown) {
|
|
218
395
|
// Catch-all for prompt failures (stdin closed, terminal not supported, etc.)
|
|
@@ -0,0 +1,186 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Pipeline Command — H2
|
|
3
|
+
*
|
|
4
|
+
* View CI/CD pipeline status from GitHub Actions, GitLab CI, or CircleCI.
|
|
5
|
+
*
|
|
6
|
+
* Usage:
|
|
7
|
+
* nimbus pipeline status [run-id]
|
|
8
|
+
* nimbus pipeline status --provider github --limit 10
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
import { execFileSync } from 'node:child_process';
|
|
12
|
+
import * as fs from 'node:fs';
|
|
13
|
+
import * as path from 'node:path';
|
|
14
|
+
import { ui } from '../wizard/ui';
|
|
15
|
+
|
|
16
|
+
export interface PipelineOptions {
|
|
17
|
+
provider?: 'github' | 'gitlab' | 'circleci' | 'auto';
|
|
18
|
+
format?: 'table' | 'json';
|
|
19
|
+
limit?: number;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* Auto-detect the CI/CD provider by scanning the cwd for config files.
|
|
24
|
+
*/
|
|
25
|
+
export function detectProvider(cwd: string = process.cwd()): 'github' | 'gitlab' | 'circleci' | null {
|
|
26
|
+
if (fs.existsSync(path.join(cwd, '.github', 'workflows'))) return 'github';
|
|
27
|
+
if (fs.existsSync(path.join(cwd, '.gitlab-ci.yml'))) return 'gitlab';
|
|
28
|
+
if (fs.existsSync(path.join(cwd, '.circleci', 'config.yml'))) return 'circleci';
|
|
29
|
+
return null;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
interface PipelineRun {
|
|
33
|
+
id: string;
|
|
34
|
+
workflow: string;
|
|
35
|
+
status: string;
|
|
36
|
+
result: string;
|
|
37
|
+
started: string;
|
|
38
|
+
url?: string;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
function fetchGitHubRuns(limit: number, runId?: string): PipelineRun[] {
|
|
42
|
+
if (runId) {
|
|
43
|
+
const raw = execFileSync(
|
|
44
|
+
'gh',
|
|
45
|
+
['run', 'view', runId, '--json', 'status,conclusion,name,createdAt,url,databaseId'],
|
|
46
|
+
{ encoding: 'utf-8', stdio: ['pipe', 'pipe', 'pipe'] }
|
|
47
|
+
);
|
|
48
|
+
const r = JSON.parse(raw);
|
|
49
|
+
return [{
|
|
50
|
+
id: String(r.databaseId ?? runId),
|
|
51
|
+
workflow: r.name ?? '',
|
|
52
|
+
status: r.status ?? '',
|
|
53
|
+
result: r.conclusion ?? '-',
|
|
54
|
+
started: r.createdAt ?? '',
|
|
55
|
+
url: r.url,
|
|
56
|
+
}];
|
|
57
|
+
}
|
|
58
|
+
const raw = execFileSync(
|
|
59
|
+
'gh',
|
|
60
|
+
['run', 'list', '--limit', String(limit), '--json', 'status,conclusion,name,createdAt,databaseId'],
|
|
61
|
+
{ encoding: 'utf-8', stdio: ['pipe', 'pipe', 'pipe'] }
|
|
62
|
+
);
|
|
63
|
+
const runs = JSON.parse(raw) as Array<Record<string, unknown>>;
|
|
64
|
+
return runs.map(r => ({
|
|
65
|
+
id: String(r['databaseId'] ?? ''),
|
|
66
|
+
workflow: String(r['name'] ?? ''),
|
|
67
|
+
status: String(r['status'] ?? ''),
|
|
68
|
+
result: String(r['conclusion'] ?? '-'),
|
|
69
|
+
started: String(r['createdAt'] ?? ''),
|
|
70
|
+
}));
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
function fetchGitLabRuns(limit: number): PipelineRun[] {
|
|
74
|
+
const raw = execFileSync(
|
|
75
|
+
'glab',
|
|
76
|
+
['ci', 'list', '--format', 'json'],
|
|
77
|
+
{ encoding: 'utf-8', stdio: ['pipe', 'pipe', 'pipe'] }
|
|
78
|
+
);
|
|
79
|
+
const pipelines = JSON.parse(raw) as Array<Record<string, unknown>>;
|
|
80
|
+
return pipelines.slice(0, limit).map(p => ({
|
|
81
|
+
id: String(p['id'] ?? ''),
|
|
82
|
+
workflow: String(p['ref'] ?? ''),
|
|
83
|
+
status: String(p['status'] ?? ''),
|
|
84
|
+
result: String(p['status'] ?? '-'),
|
|
85
|
+
started: String(p['created_at'] ?? ''),
|
|
86
|
+
url: String(p['web_url'] ?? ''),
|
|
87
|
+
}));
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
function fetchCircleCIRuns(): PipelineRun[] {
|
|
91
|
+
const raw = execFileSync(
|
|
92
|
+
'circleci',
|
|
93
|
+
['pipeline', 'list'],
|
|
94
|
+
{ encoding: 'utf-8', stdio: ['pipe', 'pipe', 'pipe'] }
|
|
95
|
+
);
|
|
96
|
+
// CircleCI CLI output is not always JSON; do best-effort parse
|
|
97
|
+
try {
|
|
98
|
+
const pipelines = JSON.parse(raw) as Array<Record<string, unknown>>;
|
|
99
|
+
return pipelines.map(p => ({
|
|
100
|
+
id: String(p['id'] ?? ''),
|
|
101
|
+
workflow: String(p['vcs'] ?? ''),
|
|
102
|
+
status: String(p['state'] ?? ''),
|
|
103
|
+
result: String(p['state'] ?? '-'),
|
|
104
|
+
started: String(p['created_at'] ?? ''),
|
|
105
|
+
}));
|
|
106
|
+
} catch {
|
|
107
|
+
return [{ id: '-', workflow: '-', status: raw.trim().slice(0, 40), result: '-', started: '-' }];
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
function printTable(runs: PipelineRun[]): void {
|
|
112
|
+
const COL = { id: 12, workflow: 30, status: 12, result: 12, started: 22 };
|
|
113
|
+
const pad = (s: string, n: number) => s.slice(0, n).padEnd(n);
|
|
114
|
+
const divider = `${'-'.repeat(COL.id + 2)}+${'-'.repeat(COL.workflow + 2)}+${'-'.repeat(COL.status + 2)}+${'-'.repeat(COL.result + 2)}+${'-'.repeat(COL.started + 2)}`;
|
|
115
|
+
console.log(divider);
|
|
116
|
+
console.log(`| ${pad('ID', COL.id)} | ${pad('Workflow', COL.workflow)} | ${pad('Status', COL.status)} | ${pad('Result', COL.result)} | ${pad('Started', COL.started)} |`);
|
|
117
|
+
console.log(divider);
|
|
118
|
+
for (const r of runs) {
|
|
119
|
+
console.log(`| ${pad(r.id, COL.id)} | ${pad(r.workflow, COL.workflow)} | ${pad(r.status, COL.status)} | ${pad(r.result, COL.result)} | ${pad(r.started, COL.started)} |`);
|
|
120
|
+
}
|
|
121
|
+
console.log(divider);
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
/**
|
|
125
|
+
* Main pipeline command dispatcher.
|
|
126
|
+
*/
|
|
127
|
+
export async function pipelineCommand(subcommand: string, args: string[]): Promise<void> {
|
|
128
|
+
const options: PipelineOptions = { format: 'table', limit: 10 };
|
|
129
|
+
let runId: string | undefined;
|
|
130
|
+
|
|
131
|
+
for (let i = 0; i < args.length; i++) {
|
|
132
|
+
const arg = args[i];
|
|
133
|
+
if (arg === '--provider' && args[i + 1]) {
|
|
134
|
+
options.provider = args[++i] as PipelineOptions['provider'];
|
|
135
|
+
} else if (arg === '--format' && args[i + 1]) {
|
|
136
|
+
options.format = args[++i] as 'table' | 'json';
|
|
137
|
+
} else if (arg === '--limit' && args[i + 1]) {
|
|
138
|
+
options.limit = parseInt(args[++i], 10);
|
|
139
|
+
} else if (arg === '--json') {
|
|
140
|
+
options.format = 'json';
|
|
141
|
+
} else if (!arg.startsWith('-') && !runId && subcommand === 'status') {
|
|
142
|
+
runId = arg;
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
const provider =
|
|
147
|
+
options.provider === 'auto' || !options.provider
|
|
148
|
+
? detectProvider()
|
|
149
|
+
: options.provider;
|
|
150
|
+
|
|
151
|
+
if (!provider) {
|
|
152
|
+
ui.error('Could not detect CI/CD provider. Use --provider github|gitlab|circleci or run in a repo with CI config.');
|
|
153
|
+
process.exit(1);
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
let runs: PipelineRun[];
|
|
157
|
+
try {
|
|
158
|
+
if (provider === 'github') {
|
|
159
|
+
runs = fetchGitHubRuns(options.limit ?? 10, runId);
|
|
160
|
+
} else if (provider === 'gitlab') {
|
|
161
|
+
runs = fetchGitLabRuns(options.limit ?? 10);
|
|
162
|
+
} else {
|
|
163
|
+
runs = fetchCircleCIRuns();
|
|
164
|
+
}
|
|
165
|
+
} catch (error: any) {
|
|
166
|
+
if (error.code === 'ENOENT') {
|
|
167
|
+
const installHints: Record<string, string> = {
|
|
168
|
+
github: 'Install GitHub CLI: https://cli.github.com',
|
|
169
|
+
gitlab: 'Install GitLab CLI: https://gitlab.com/gitlab-org/cli',
|
|
170
|
+
circleci: 'Install CircleCI CLI: https://circleci.com/docs/local-cli/',
|
|
171
|
+
};
|
|
172
|
+
ui.error(`CLI not found for provider "${provider}". ${installHints[provider] ?? ''}`);
|
|
173
|
+
} else {
|
|
174
|
+
ui.error(`Failed to fetch pipeline status: ${error.message}`);
|
|
175
|
+
}
|
|
176
|
+
process.exit(1);
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
if (options.format === 'json') {
|
|
180
|
+
console.log(JSON.stringify(runs, null, 2));
|
|
181
|
+
return;
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
ui.header(`Pipeline Status (${provider})`);
|
|
185
|
+
printTable(runs);
|
|
186
|
+
}
|