@build-astron-co/nimbus 0.3.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.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 +22 -15
- package/src/__tests__/alias.test.ts +133 -0
- 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 +130 -0
- package/src/__tests__/devops-terminal-gaps.test.ts +718 -0
- package/src/__tests__/doctor.test.ts +48 -0
- package/src/__tests__/export.test.ts +236 -0
- package/src/__tests__/gap-11-18-20.test.ts +958 -0
- package/src/__tests__/helm-streaming.test.ts +127 -0
- package/src/__tests__/incident.test.ts +179 -0
- package/src/__tests__/init.test.ts +54 -3
- package/src/__tests__/logs.test.ts +107 -0
- package/src/__tests__/loop-errors.test.ts +244 -0
- package/src/__tests__/perf-optimizations.test.ts +847 -0
- 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__/sessions.test.ts +95 -0
- package/src/__tests__/standalone-migration.test.ts +199 -0
- package/src/__tests__/status.test.ts +158 -0
- package/src/__tests__/stream-with-tools.test.ts +49 -1
- package/src/__tests__/system-prompt.test.ts +81 -2
- 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-schemas.test.ts +209 -4
- package/src/__tests__/version-json.test.ts +184 -0
- 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/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/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/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 +20 -4
- package/src/nimbus.ts +34 -1
- package/src/sessions/manager.ts +108 -1
- 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/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/app.ts
CHANGED
|
@@ -27,12 +27,19 @@ export interface AppContext {
|
|
|
27
27
|
readonly nimbusDir: string;
|
|
28
28
|
}
|
|
29
29
|
|
|
30
|
+
/**
|
|
31
|
+
* Startup warnings collected during initApp() — surfaced as first system
|
|
32
|
+
* message when the TUI starts (Gap 19).
|
|
33
|
+
*/
|
|
34
|
+
export let startupWarnings: string[] = [];
|
|
35
|
+
|
|
30
36
|
/**
|
|
31
37
|
* Initialize the Nimbus application.
|
|
32
38
|
*
|
|
33
39
|
* - Ensures the ~/.nimbus directory exists.
|
|
34
40
|
* - Opens (or creates) the local SQLite database via the state module.
|
|
35
41
|
* - Loads config and creates the LLM router instance.
|
|
42
|
+
* - Runs pre-flight health checks and exits on critical failures (Gap 19).
|
|
36
43
|
*
|
|
37
44
|
* This function is lazy: calling it multiple times returns the same context.
|
|
38
45
|
*/
|
|
@@ -41,6 +48,49 @@ export async function initApp(): Promise<AppContext> {
|
|
|
41
48
|
return appContext;
|
|
42
49
|
}
|
|
43
50
|
|
|
51
|
+
// Gap 19: Pre-flight startup checks (fast, no network calls)
|
|
52
|
+
try {
|
|
53
|
+
const { runStartupChecks } = await import('./commands/doctor');
|
|
54
|
+
const issues = await runStartupChecks();
|
|
55
|
+
if (issues.critical.length > 0) {
|
|
56
|
+
const lines = [
|
|
57
|
+
'\x1b[31m\x1b[1mNimbus cannot start:\x1b[0m',
|
|
58
|
+
...issues.critical.map(i => ` \x1b[31m✗ ${i}\x1b[0m`),
|
|
59
|
+
'',
|
|
60
|
+
'\x1b[2mRun `nimbus doctor` for detailed diagnosis.\x1b[0m',
|
|
61
|
+
];
|
|
62
|
+
process.stderr.write(lines.join('\n') + '\n');
|
|
63
|
+
process.exit(1);
|
|
64
|
+
}
|
|
65
|
+
startupWarnings = issues.warnings;
|
|
66
|
+
} catch {
|
|
67
|
+
// Startup checks are non-critical — never let them block startup
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
// H2: Warn if no NIMBUS.md found in the current project directory
|
|
71
|
+
try {
|
|
72
|
+
const cwd = process.cwd();
|
|
73
|
+
const nimbusMdPaths = [join(cwd, 'NIMBUS.md'), join(cwd, '.nimbus', 'NIMBUS.md')];
|
|
74
|
+
if (!nimbusMdPaths.some(p => existsSync(p))) {
|
|
75
|
+
startupWarnings.push('No NIMBUS.md found — run /init to set up project context for better results.');
|
|
76
|
+
}
|
|
77
|
+
} catch {
|
|
78
|
+
// Non-critical
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
// M3: Load per-project config and surface protected environments as warnings
|
|
82
|
+
try {
|
|
83
|
+
const { loadProjectConfig } = await import('./config/types');
|
|
84
|
+
const projectConfig = loadProjectConfig(process.cwd());
|
|
85
|
+
if (projectConfig?.protectedEnvironments && projectConfig.protectedEnvironments.length > 0) {
|
|
86
|
+
startupWarnings.push(
|
|
87
|
+
`Protected environments: ${projectConfig.protectedEnvironments.join(', ')} — destructive operations require confirmation.`
|
|
88
|
+
);
|
|
89
|
+
}
|
|
90
|
+
} catch {
|
|
91
|
+
// Non-critical
|
|
92
|
+
}
|
|
93
|
+
|
|
44
94
|
// Ensure ~/.nimbus directory exists
|
|
45
95
|
if (!existsSync(NIMBUS_DIR)) {
|
|
46
96
|
mkdirSync(NIMBUS_DIR, { recursive: true });
|
|
@@ -112,6 +162,14 @@ export async function shutdownApp(): Promise<void> {
|
|
|
112
162
|
return;
|
|
113
163
|
}
|
|
114
164
|
|
|
165
|
+
// Flush any pending debounced SQLite writes before closing the DB
|
|
166
|
+
try {
|
|
167
|
+
const { SessionManager } = await import('./sessions/manager');
|
|
168
|
+
SessionManager.getInstance().flushAll();
|
|
169
|
+
} catch {
|
|
170
|
+
// SessionManager may not have been used — ignore
|
|
171
|
+
}
|
|
172
|
+
|
|
115
173
|
try {
|
|
116
174
|
appContext.db.close();
|
|
117
175
|
} catch {
|
|
@@ -594,3 +594,48 @@ export function formatFindings(findings: SecurityFinding[]): string {
|
|
|
594
594
|
|
|
595
595
|
return lines.join('\n');
|
|
596
596
|
}
|
|
597
|
+
|
|
598
|
+
// ---------------------------------------------------------------------------
|
|
599
|
+
// Gap 12: Secret masking for tool output
|
|
600
|
+
// ---------------------------------------------------------------------------
|
|
601
|
+
|
|
602
|
+
interface SecretPattern {
|
|
603
|
+
pattern: RegExp;
|
|
604
|
+
label: string;
|
|
605
|
+
}
|
|
606
|
+
|
|
607
|
+
/**
|
|
608
|
+
* Patterns used to detect and redact secrets in tool output.
|
|
609
|
+
* Applied in order; earlier patterns take precedence.
|
|
610
|
+
*/
|
|
611
|
+
const SECRET_MASK_PATTERNS: SecretPattern[] = [
|
|
612
|
+
{ pattern: /AKIA[0-9A-Z]{16}/g, label: '[AWS_ACCESS_KEY]' },
|
|
613
|
+
{ pattern: /sk-ant-[a-zA-Z0-9\-]{40,}/g, label: '[ANTHROPIC_KEY]' },
|
|
614
|
+
{ pattern: /ghp_[a-zA-Z0-9]{36}/g, label: '[GITHUB_TOKEN]' },
|
|
615
|
+
{ pattern: /gho_[a-zA-Z0-9]{36}/g, label: '[GITHUB_OAUTH]' },
|
|
616
|
+
{ pattern: /sk-[a-zA-Z0-9]{40,}/g, label: '[OPENAI_KEY]' },
|
|
617
|
+
{ pattern: /AIza[0-9A-Za-z\-_]{35}/g, label: '[GOOGLE_API_KEY]' },
|
|
618
|
+
{ pattern: /Bearer [a-zA-Z0-9._\-]{20,}/g, label: 'Bearer [BEARER_TOKEN]' },
|
|
619
|
+
{ pattern: /password[=:\s]["']?[^\s"']{8,}/gi, label: 'password=[REDACTED]' },
|
|
620
|
+
{ pattern: /passwd[=:\s]["']?[^\s"']{8,}/gi, label: 'passwd=[REDACTED]' },
|
|
621
|
+
{ pattern: /token[=:\s]["']?[a-zA-Z0-9._\-]{20,}/gi, label: 'token=[REDACTED]' },
|
|
622
|
+
{ pattern: /secret[=:\s]["']?[a-zA-Z0-9._\-]{16,}/gi, label: 'secret=[REDACTED]' },
|
|
623
|
+
{ pattern: /-----BEGIN (?:RSA |EC |DSA )?PRIVATE KEY-----[\s\S]{0,2048}?-----END (?:RSA |EC |DSA )?PRIVATE KEY-----/g, label: '[PRIVATE_KEY_REDACTED]' },
|
|
624
|
+
];
|
|
625
|
+
|
|
626
|
+
/**
|
|
627
|
+
* Replace recognized secret patterns in a text string with safe placeholders.
|
|
628
|
+
*
|
|
629
|
+
* Applied to all tool output before it is shown in the TUI or stored in chat
|
|
630
|
+
* history, preventing accidental leakage of credentials through the UI.
|
|
631
|
+
*
|
|
632
|
+
* @param text - Raw tool output string.
|
|
633
|
+
* @returns The text with known secret patterns replaced by `[REDACTED]` labels.
|
|
634
|
+
*/
|
|
635
|
+
export function maskSecrets(text: string): string {
|
|
636
|
+
let masked = text;
|
|
637
|
+
for (const { pattern, label } of SECRET_MASK_PATTERNS) {
|
|
638
|
+
masked = masked.replace(pattern, label);
|
|
639
|
+
}
|
|
640
|
+
return masked;
|
|
641
|
+
}
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* OS Keychain Abstraction
|
|
3
|
+
*
|
|
4
|
+
* Wraps the optional `keytar` package (native Node addon) to provide
|
|
5
|
+
* secure OS-level secret storage. If keytar is unavailable the module
|
|
6
|
+
* silently falls back to the existing machine-fingerprint key derivation.
|
|
7
|
+
*
|
|
8
|
+
* On macOS: credentials are stored in the macOS Keychain.
|
|
9
|
+
* On Linux: credentials are stored via libsecret (GNOME Keyring / KDE Wallet).
|
|
10
|
+
* On Windows: credentials are stored in Windows Credential Manager.
|
|
11
|
+
*/
|
|
12
|
+
|
|
13
|
+
const SERVICE = 'nimbus-ai';
|
|
14
|
+
|
|
15
|
+
/** Lazy-loaded keytar module. null = checked and unavailable. */
|
|
16
|
+
let keytarModule: {
|
|
17
|
+
getPassword: (service: string, account: string) => Promise<string | null>;
|
|
18
|
+
setPassword: (service: string, account: string, password: string) => Promise<void>;
|
|
19
|
+
deletePassword: (service: string, account: string) => Promise<boolean>;
|
|
20
|
+
} | null | undefined = undefined; // undefined = not yet checked
|
|
21
|
+
|
|
22
|
+
async function loadKeytar() {
|
|
23
|
+
if (keytarModule !== undefined) return keytarModule;
|
|
24
|
+
try {
|
|
25
|
+
// Dynamic import — keytar is an optional peer dependency
|
|
26
|
+
// eslint-disable-next-line @typescript-eslint/no-var-requires
|
|
27
|
+
keytarModule = require('keytar') as typeof keytarModule;
|
|
28
|
+
} catch {
|
|
29
|
+
keytarModule = null;
|
|
30
|
+
}
|
|
31
|
+
return keytarModule;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* Check whether OS keychain integration is available.
|
|
36
|
+
*/
|
|
37
|
+
export async function isKeychainAvailable(): Promise<boolean> {
|
|
38
|
+
const kt = await loadKeytar();
|
|
39
|
+
return kt !== null;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
/**
|
|
43
|
+
* Retrieve a secret from the OS keychain.
|
|
44
|
+
* Returns null if not found or keychain unavailable.
|
|
45
|
+
*/
|
|
46
|
+
export async function keychainGet(account: string): Promise<string | null> {
|
|
47
|
+
const kt = await loadKeytar();
|
|
48
|
+
if (!kt) return null;
|
|
49
|
+
try {
|
|
50
|
+
return await kt.getPassword(SERVICE, account);
|
|
51
|
+
} catch {
|
|
52
|
+
return null;
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
/**
|
|
57
|
+
* Store a secret in the OS keychain.
|
|
58
|
+
* No-op if keychain unavailable.
|
|
59
|
+
*/
|
|
60
|
+
export async function keychainSet(account: string, secret: string): Promise<void> {
|
|
61
|
+
const kt = await loadKeytar();
|
|
62
|
+
if (!kt) return;
|
|
63
|
+
try {
|
|
64
|
+
await kt.setPassword(SERVICE, account, secret);
|
|
65
|
+
} catch {
|
|
66
|
+
// Silently ignore keychain errors — caller falls back to file-based storage
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
/**
|
|
71
|
+
* Delete a secret from the OS keychain.
|
|
72
|
+
* No-op if keychain unavailable.
|
|
73
|
+
*/
|
|
74
|
+
export async function keychainDelete(account: string): Promise<void> {
|
|
75
|
+
const kt = await loadKeytar();
|
|
76
|
+
if (!kt) return;
|
|
77
|
+
try {
|
|
78
|
+
await kt.deletePassword(SERVICE, account);
|
|
79
|
+
} catch {
|
|
80
|
+
/* ignore */
|
|
81
|
+
}
|
|
82
|
+
}
|
package/src/cli/init.ts
CHANGED
|
@@ -68,6 +68,11 @@ export interface InitOptions {
|
|
|
68
68
|
force?: boolean;
|
|
69
69
|
/** Suppress all console output */
|
|
70
70
|
quiet?: boolean;
|
|
71
|
+
/**
|
|
72
|
+
* Merge mode (M2): append a ## Local Overrides section to an existing
|
|
73
|
+
* NIMBUS.md and create .nimbus/local.md instead of overwriting the file.
|
|
74
|
+
*/
|
|
75
|
+
merge?: boolean;
|
|
71
76
|
}
|
|
72
77
|
|
|
73
78
|
/** Value returned by {@link runInit} on success */
|
|
@@ -345,6 +350,167 @@ export function detectCloudProviders(dir: string): CloudProvider[] {
|
|
|
345
350
|
return Array.from(found);
|
|
346
351
|
}
|
|
347
352
|
|
|
353
|
+
/** Live infrastructure context gathered by querying actual CLI tools */
|
|
354
|
+
export interface InfraContext {
|
|
355
|
+
terraformWorkspace?: string;
|
|
356
|
+
terraformBackend?: string;
|
|
357
|
+
kubectlContext?: string;
|
|
358
|
+
kubectlClusters?: string[];
|
|
359
|
+
helmReleases?: string[];
|
|
360
|
+
awsAccount?: string;
|
|
361
|
+
awsRegion?: string;
|
|
362
|
+
/** G13: Named AWS profiles from ~/.aws/config (max 10) */
|
|
363
|
+
awsProfiles?: Array<{ profile: string; account?: string; region?: string }>;
|
|
364
|
+
gcpProject?: string;
|
|
365
|
+
/** G16: Number of Kubernetes namespaces in the current cluster */
|
|
366
|
+
k8sNamespaceCount?: number;
|
|
367
|
+
/** G16: Number of Kubernetes deployments across all namespaces */
|
|
368
|
+
k8sDeploymentCount?: number;
|
|
369
|
+
/** CI/CD pipeline system detected in the project directory (G9). */
|
|
370
|
+
cicdPipeline?: string;
|
|
371
|
+
}
|
|
372
|
+
|
|
373
|
+
/**
|
|
374
|
+
* Discover live infrastructure context by querying CLI tools.
|
|
375
|
+
* All queries are best-effort with short timeouts.
|
|
376
|
+
*/
|
|
377
|
+
export async function discoverInfraContext(dir: string): Promise<InfraContext> {
|
|
378
|
+
const { execFileSync } = await import('node:child_process');
|
|
379
|
+
const ctx: InfraContext = {};
|
|
380
|
+
|
|
381
|
+
// Terraform workspace (only if .terraform exists)
|
|
382
|
+
if (exists(path.join(dir, '.terraform'))) {
|
|
383
|
+
try {
|
|
384
|
+
ctx.terraformWorkspace = execFileSync('terraform', ['workspace', 'show'], {
|
|
385
|
+
cwd: dir, encoding: 'utf-8', timeout: 5000, stdio: ['pipe', 'pipe', 'pipe'],
|
|
386
|
+
}).trim();
|
|
387
|
+
} catch { /* ignore */ }
|
|
388
|
+
}
|
|
389
|
+
|
|
390
|
+
// kubectl context
|
|
391
|
+
try {
|
|
392
|
+
ctx.kubectlContext = execFileSync('kubectl', ['config', 'current-context'], {
|
|
393
|
+
encoding: 'utf-8', timeout: 3000, stdio: ['pipe', 'pipe', 'pipe'],
|
|
394
|
+
}).trim();
|
|
395
|
+
const clustersOut = execFileSync('kubectl', ['config', 'get-clusters'], {
|
|
396
|
+
encoding: 'utf-8', timeout: 3000, stdio: ['pipe', 'pipe', 'pipe'],
|
|
397
|
+
});
|
|
398
|
+
ctx.kubectlClusters = clustersOut.split('\n').slice(1).map(s => s.trim()).filter(Boolean);
|
|
399
|
+
} catch { /* ignore */ }
|
|
400
|
+
|
|
401
|
+
// Helm releases
|
|
402
|
+
try {
|
|
403
|
+
const helmOut = execFileSync('helm', ['list', '-A', '--output=json'], {
|
|
404
|
+
encoding: 'utf-8', timeout: 10000, stdio: ['pipe', 'pipe', 'pipe'],
|
|
405
|
+
});
|
|
406
|
+
const releases = JSON.parse(helmOut || '[]') as Array<{ name: string; namespace: string }>;
|
|
407
|
+
ctx.helmReleases = releases.map(r => `${r.name} (${r.namespace})`);
|
|
408
|
+
} catch { /* ignore */ }
|
|
409
|
+
|
|
410
|
+
// AWS account + region
|
|
411
|
+
try {
|
|
412
|
+
const awsIdOut = execFileSync('aws', ['sts', 'get-caller-identity', '--output=json'], {
|
|
413
|
+
encoding: 'utf-8', timeout: 8000, stdio: ['pipe', 'pipe', 'pipe'],
|
|
414
|
+
});
|
|
415
|
+
const awsId = JSON.parse(awsIdOut);
|
|
416
|
+
ctx.awsAccount = awsId.Account;
|
|
417
|
+
} catch { /* ignore */ }
|
|
418
|
+
|
|
419
|
+
try {
|
|
420
|
+
ctx.awsRegion = execFileSync('aws', ['configure', 'get', 'region'], {
|
|
421
|
+
encoding: 'utf-8', timeout: 3000, stdio: ['pipe', 'pipe', 'pipe'],
|
|
422
|
+
}).trim() || process.env.AWS_DEFAULT_REGION;
|
|
423
|
+
} catch {
|
|
424
|
+
ctx.awsRegion = process.env.AWS_DEFAULT_REGION;
|
|
425
|
+
}
|
|
426
|
+
|
|
427
|
+
// G13: Discover all AWS profiles from ~/.aws/config
|
|
428
|
+
try {
|
|
429
|
+
const { readFileSync } = await import('node:fs');
|
|
430
|
+
const { homedir } = await import('node:os');
|
|
431
|
+
const awsConfigPath = path.join(homedir(), '.aws', 'config');
|
|
432
|
+
if (exists(awsConfigPath)) {
|
|
433
|
+
const awsConfig = readFileSync(awsConfigPath, 'utf-8');
|
|
434
|
+
const profiles: Array<{ profile: string; account?: string; region?: string }> = [];
|
|
435
|
+
const profileRegex = /^\[(?:profile\s+)?(\S+)\]/gm;
|
|
436
|
+
let match;
|
|
437
|
+
while ((match = profileRegex.exec(awsConfig)) !== null) {
|
|
438
|
+
const profileName = match[1];
|
|
439
|
+
if (profileName === 'default') continue; // handled separately
|
|
440
|
+
// Extract region from the profile block
|
|
441
|
+
const blockStart = match.index + match[0].length;
|
|
442
|
+
const nextBlock = awsConfig.indexOf('\n[', blockStart);
|
|
443
|
+
const block = awsConfig.slice(blockStart, nextBlock === -1 ? undefined : nextBlock);
|
|
444
|
+
const regionMatch = block.match(/^\s*region\s*=\s*(.+)$/m);
|
|
445
|
+
profiles.push({ profile: profileName, region: regionMatch?.[1]?.trim() });
|
|
446
|
+
}
|
|
447
|
+
if (profiles.length > 0) ctx.awsProfiles = profiles.slice(0, 10); // max 10 profiles
|
|
448
|
+
}
|
|
449
|
+
} catch { /* ignore */ }
|
|
450
|
+
|
|
451
|
+
// GCP project
|
|
452
|
+
try {
|
|
453
|
+
const proj = execFileSync('gcloud', ['config', 'get-value', 'project'], {
|
|
454
|
+
encoding: 'utf-8', timeout: 3000, stdio: ['pipe', 'pipe', 'pipe'],
|
|
455
|
+
}).trim();
|
|
456
|
+
if (proj && proj !== '(unset)') ctx.gcpProject = proj;
|
|
457
|
+
} catch { /* ignore */ }
|
|
458
|
+
|
|
459
|
+
// G16: Count K8s namespaces and deployments
|
|
460
|
+
if (ctx.kubectlContext) {
|
|
461
|
+
try {
|
|
462
|
+
const nsOut = execFileSync('kubectl', ['get', 'namespaces', '--no-headers'], {
|
|
463
|
+
encoding: 'utf-8', timeout: 3000, stdio: ['pipe', 'pipe', 'pipe'],
|
|
464
|
+
});
|
|
465
|
+
const nsCount = nsOut.trim().split('\n').filter(Boolean).length;
|
|
466
|
+
ctx.k8sNamespaceCount = nsCount;
|
|
467
|
+
} catch { /* ignore */ }
|
|
468
|
+
try {
|
|
469
|
+
const deplOut = execFileSync('kubectl', ['get', 'deployments', '-A', '--no-headers'], {
|
|
470
|
+
encoding: 'utf-8', timeout: 3000, stdio: ['pipe', 'pipe', 'pipe'],
|
|
471
|
+
});
|
|
472
|
+
const deplCount = deplOut.trim().split('\n').filter(Boolean).length;
|
|
473
|
+
ctx.k8sDeploymentCount = deplCount;
|
|
474
|
+
} catch { /* ignore */ }
|
|
475
|
+
}
|
|
476
|
+
|
|
477
|
+
// CI/CD pipeline detection (G9)
|
|
478
|
+
const cicdFiles: Array<[string, string]> = [
|
|
479
|
+
['.github/workflows', 'GitHub Actions'],
|
|
480
|
+
['.gitlab-ci.yml', 'GitLab CI'],
|
|
481
|
+
['.circleci', 'CircleCI'],
|
|
482
|
+
['Jenkinsfile', 'Jenkins'],
|
|
483
|
+
['.buildkite', 'Buildkite'],
|
|
484
|
+
['azure-pipelines.yml', 'Azure Pipelines'],
|
|
485
|
+
];
|
|
486
|
+
for (const [file, name] of cicdFiles) {
|
|
487
|
+
if (exists(path.join(dir, file))) {
|
|
488
|
+
ctx.cicdPipeline = name;
|
|
489
|
+
break;
|
|
490
|
+
}
|
|
491
|
+
}
|
|
492
|
+
|
|
493
|
+
return ctx;
|
|
494
|
+
}
|
|
495
|
+
|
|
496
|
+
/**
|
|
497
|
+
* Format an InfraContext into a compact single-line summary for injection
|
|
498
|
+
* into agent messages (Gaps 7 & 10).
|
|
499
|
+
*/
|
|
500
|
+
export function formatInfraContext(ctx: InfraContext): string {
|
|
501
|
+
const parts: string[] = [];
|
|
502
|
+
if (ctx.terraformWorkspace) parts.push(`tf-workspace: ${ctx.terraformWorkspace}`);
|
|
503
|
+
if (ctx.kubectlContext) parts.push(`k8s-context: ${ctx.kubectlContext}`);
|
|
504
|
+
if (ctx.helmReleases && ctx.helmReleases.length > 0) {
|
|
505
|
+
parts.push(`helm-releases: ${ctx.helmReleases.slice(0, 3).join(', ')}${ctx.helmReleases.length > 3 ? ` +${ctx.helmReleases.length - 3} more` : ''}`);
|
|
506
|
+
}
|
|
507
|
+
if (ctx.awsAccount) parts.push(`aws-account: ${ctx.awsAccount}`);
|
|
508
|
+
if (ctx.awsRegion) parts.push(`aws-region: ${ctx.awsRegion}`);
|
|
509
|
+
if (ctx.gcpProject) parts.push(`gcp-project: ${ctx.gcpProject}`);
|
|
510
|
+
if (ctx.cicdPipeline) parts.push(`cicd: ${ctx.cicdPipeline}`);
|
|
511
|
+
return parts.length > 0 ? parts.join(' | ') : 'no infra context detected';
|
|
512
|
+
}
|
|
513
|
+
|
|
348
514
|
/**
|
|
349
515
|
* Detect which Node.js package manager is used in `dir`.
|
|
350
516
|
*
|
|
@@ -563,7 +729,7 @@ export function detectProject(dir: string): ProjectDetection {
|
|
|
563
729
|
* The generated markdown serves as both human-readable documentation
|
|
564
730
|
* and machine-readable project metadata for the Nimbus agent.
|
|
565
731
|
*/
|
|
566
|
-
export function generateNimbusMd(detection: ProjectDetection, _dir: string): string {
|
|
732
|
+
export function generateNimbusMd(detection: ProjectDetection, _dir: string, infraCtx?: InfraContext): string {
|
|
567
733
|
const lines: string[] = [];
|
|
568
734
|
|
|
569
735
|
// --- Header ---
|
|
@@ -597,6 +763,57 @@ export function generateNimbusMd(detection: ProjectDetection, _dir: string): str
|
|
|
597
763
|
if (detection.cloudProviders.length > 0) {
|
|
598
764
|
lines.push(`- **Cloud Providers:** ${detection.cloudProviders.join(', ')}`);
|
|
599
765
|
}
|
|
766
|
+
if (infraCtx) {
|
|
767
|
+
if (infraCtx.terraformWorkspace) {
|
|
768
|
+
lines.push(`- **Terraform Workspace:** ${infraCtx.terraformWorkspace}`);
|
|
769
|
+
}
|
|
770
|
+
if (infraCtx.kubectlContext) {
|
|
771
|
+
lines.push(`- **Kubernetes Context:** ${infraCtx.kubectlContext}`);
|
|
772
|
+
}
|
|
773
|
+
if (infraCtx.kubectlClusters && infraCtx.kubectlClusters.length > 0) {
|
|
774
|
+
lines.push(`- **Kubernetes Clusters:** ${infraCtx.kubectlClusters.join(', ')}`);
|
|
775
|
+
}
|
|
776
|
+
if (infraCtx.helmReleases && infraCtx.helmReleases.length > 0) {
|
|
777
|
+
lines.push(`- **Helm Releases:** ${infraCtx.helmReleases.slice(0, 5).join(', ')}${infraCtx.helmReleases.length > 5 ? ` (+${infraCtx.helmReleases.length - 5} more)` : ''}`);
|
|
778
|
+
}
|
|
779
|
+
if (infraCtx.awsAccount) {
|
|
780
|
+
lines.push(`- **AWS Account:** ${infraCtx.awsAccount}${infraCtx.awsRegion ? ` (${infraCtx.awsRegion})` : ''}`);
|
|
781
|
+
}
|
|
782
|
+
// G13: List named AWS profiles
|
|
783
|
+
if (infraCtx.awsProfiles && infraCtx.awsProfiles.length > 0) {
|
|
784
|
+
lines.push(`- **AWS Profiles:** ${infraCtx.awsProfiles.map(p => p.profile).join(', ')}`);
|
|
785
|
+
}
|
|
786
|
+
if (infraCtx.gcpProject) {
|
|
787
|
+
lines.push(`- **GCP Project:** ${infraCtx.gcpProject}`);
|
|
788
|
+
}
|
|
789
|
+
// G16: K8s namespace and deployment counts
|
|
790
|
+
if (infraCtx.k8sNamespaceCount !== undefined) {
|
|
791
|
+
lines.push(`- **K8s Namespaces:** ${infraCtx.k8sNamespaceCount}`);
|
|
792
|
+
}
|
|
793
|
+
if (infraCtx.k8sDeploymentCount !== undefined) {
|
|
794
|
+
lines.push(`- **K8s Deployments:** ${infraCtx.k8sDeploymentCount}`);
|
|
795
|
+
}
|
|
796
|
+
// G9: CI/CD pipeline
|
|
797
|
+
if (infraCtx.cicdPipeline) {
|
|
798
|
+
lines.push(`- **CI/CD:** ${infraCtx.cicdPipeline}`);
|
|
799
|
+
}
|
|
800
|
+
}
|
|
801
|
+
lines.push('');
|
|
802
|
+
}
|
|
803
|
+
|
|
804
|
+
// --- CI/CD (M4) ---
|
|
805
|
+
if (infraCtx?.cicdPipeline) {
|
|
806
|
+
lines.push('## CI/CD');
|
|
807
|
+
lines.push('');
|
|
808
|
+
const pipelineDir: Record<string, string> = {
|
|
809
|
+
'GitHub Actions': '.github/workflows/',
|
|
810
|
+
'GitLab CI': '.gitlab-ci.yml',
|
|
811
|
+
'CircleCI': '.circleci/',
|
|
812
|
+
'Jenkins': 'Jenkinsfile',
|
|
813
|
+
};
|
|
814
|
+
const pipelinePath = pipelineDir[infraCtx.cicdPipeline] ?? '';
|
|
815
|
+
lines.push(`Pipeline: ${infraCtx.cicdPipeline}${pipelinePath ? ` (${pipelinePath})` : ''}`);
|
|
816
|
+
lines.push('Convention: Always run `terraform plan` in CI before apply. Apply only on main branch merge.');
|
|
600
817
|
lines.push('');
|
|
601
818
|
}
|
|
602
819
|
|
|
@@ -613,6 +830,16 @@ export function generateNimbusMd(detection: ProjectDetection, _dir: string): str
|
|
|
613
830
|
lines.push('');
|
|
614
831
|
}
|
|
615
832
|
|
|
833
|
+
// --- Environments (GAP-22) ---
|
|
834
|
+
lines.push('## Environments');
|
|
835
|
+
lines.push('');
|
|
836
|
+
lines.push('| Name | Terraform Workspace | Kubernetes Context | Protected |');
|
|
837
|
+
lines.push('|------|--------------------|--------------------|-----------|');
|
|
838
|
+
lines.push('| dev | dev | | false |');
|
|
839
|
+
lines.push('| staging | staging | | false |');
|
|
840
|
+
lines.push('| prod | prod | | true |');
|
|
841
|
+
lines.push('');
|
|
842
|
+
|
|
616
843
|
// --- Safety Rules ---
|
|
617
844
|
lines.push('## Safety Rules');
|
|
618
845
|
lines.push('');
|
|
@@ -623,12 +850,52 @@ export function generateNimbusMd(detection: ProjectDetection, _dir: string): str
|
|
|
623
850
|
lines.push('- Never store secrets in source control');
|
|
624
851
|
lines.push('');
|
|
625
852
|
|
|
853
|
+
// --- Guardrails (G5): DevOps-specific rules when infra is detected ---
|
|
854
|
+
const hasInfra = detection.infraTypes.length > 0 || detection.cloudProviders.length > 0;
|
|
855
|
+
if (hasInfra) {
|
|
856
|
+
lines.push('## Guardrails');
|
|
857
|
+
lines.push('');
|
|
858
|
+
lines.push('- Never run `terraform destroy` without explicit confirmation');
|
|
859
|
+
lines.push('- Protected Kubernetes namespaces: `production`, `kube-system`, `monitoring`');
|
|
860
|
+
lines.push('- Always show terraform plan before apply');
|
|
861
|
+
lines.push('- Confirm target cloud account and region before resource creation');
|
|
862
|
+
lines.push('- Protected environments: any workspace/namespace containing `prod`, `prd`, `production`');
|
|
863
|
+
lines.push('');
|
|
864
|
+
}
|
|
865
|
+
|
|
866
|
+
// --- Forbidden placeholder (G5) ---
|
|
867
|
+
lines.push('## Forbidden');
|
|
868
|
+
lines.push('');
|
|
869
|
+
lines.push('<!-- List operations Nimbus must never perform in this project -->');
|
|
870
|
+
lines.push('<!-- Example: - Never destroy the production database -->');
|
|
871
|
+
lines.push('');
|
|
872
|
+
|
|
626
873
|
// --- Custom Instructions ---
|
|
627
874
|
lines.push('## Custom Instructions');
|
|
628
875
|
lines.push('');
|
|
629
876
|
lines.push('<!-- Add project-specific instructions for the Nimbus agent here -->');
|
|
630
877
|
lines.push('');
|
|
631
878
|
|
|
879
|
+
// --- G18: Runbooks ---
|
|
880
|
+
const runbookDirs = ['docs/runbooks', 'runbooks', '.github/runbooks', '.nimbus/runbooks'];
|
|
881
|
+
const foundRunbooks: string[] = [];
|
|
882
|
+
for (const dir of runbookDirs) {
|
|
883
|
+
const fullPath = path.join(_dir, dir);
|
|
884
|
+
if (fs.existsSync(fullPath)) {
|
|
885
|
+
try {
|
|
886
|
+
const files = fs.readdirSync(fullPath).filter(f => /\.(md|yaml|yml)$/.test(f));
|
|
887
|
+
foundRunbooks.push(...files.map(f => `- ${dir}/${f}`));
|
|
888
|
+
} catch { /* non-critical */ }
|
|
889
|
+
}
|
|
890
|
+
}
|
|
891
|
+
const runbookSection = foundRunbooks.length > 0
|
|
892
|
+
? foundRunbooks.join('\n') + '\n\nRefer to these runbooks for incident response and operational procedures.'
|
|
893
|
+
: '<!-- Add runbook references, e.g.:\n- docs/runbooks/cert-rotation.md -->';
|
|
894
|
+
lines.push('## Runbooks');
|
|
895
|
+
lines.push('');
|
|
896
|
+
lines.push(runbookSection);
|
|
897
|
+
lines.push('');
|
|
898
|
+
|
|
632
899
|
return lines.join('\n');
|
|
633
900
|
}
|
|
634
901
|
|
|
@@ -721,6 +988,7 @@ export async function runInit(options?: InitOptions): Promise<InitResult> {
|
|
|
721
988
|
const dir = path.resolve(options?.cwd ?? process.cwd());
|
|
722
989
|
const force = options?.force ?? false;
|
|
723
990
|
const quiet = options?.quiet ?? false;
|
|
991
|
+
const merge = options?.merge ?? false;
|
|
724
992
|
|
|
725
993
|
const log = (msg: string): void => {
|
|
726
994
|
if (!quiet) {
|
|
@@ -728,6 +996,31 @@ export async function runInit(options?: InitOptions): Promise<InitResult> {
|
|
|
728
996
|
}
|
|
729
997
|
};
|
|
730
998
|
|
|
999
|
+
// ---- M2: --merge mode — append Local Overrides section, don't overwrite ----
|
|
1000
|
+
const nimbusmdPathEarly = path.join(dir, 'NIMBUS.md');
|
|
1001
|
+
if (merge && exists(nimbusmdPathEarly)) {
|
|
1002
|
+
const content = fs.readFileSync(nimbusmdPathEarly, 'utf-8');
|
|
1003
|
+
const filesCreated: string[] = [];
|
|
1004
|
+
|
|
1005
|
+
if (!content.includes('## Local Overrides')) {
|
|
1006
|
+
fs.appendFileSync(nimbusmdPathEarly, '\n\n## Local Overrides\n\n<!-- Personal additions -->\n', 'utf-8');
|
|
1007
|
+
log(' Appended ## Local Overrides section to NIMBUS.md');
|
|
1008
|
+
} else {
|
|
1009
|
+
log(' NIMBUS.md already has a ## Local Overrides section');
|
|
1010
|
+
}
|
|
1011
|
+
|
|
1012
|
+
const localMdPath = path.join(dir, '.nimbus', 'local.md');
|
|
1013
|
+
if (!exists(localMdPath)) {
|
|
1014
|
+
fs.mkdirSync(path.join(dir, '.nimbus'), { recursive: true });
|
|
1015
|
+
fs.writeFileSync(localMdPath, '# Local Overrides\n\n<!-- Personal notes and overrides that are not committed -->\n', 'utf-8');
|
|
1016
|
+
log(' Created .nimbus/local.md for personal overrides');
|
|
1017
|
+
filesCreated.push(localMdPath);
|
|
1018
|
+
}
|
|
1019
|
+
|
|
1020
|
+
const detection = detectProject(dir);
|
|
1021
|
+
return { detection, filesCreated, nimbusmdPath: nimbusmdPathEarly };
|
|
1022
|
+
}
|
|
1023
|
+
|
|
731
1024
|
// ---- Step 1: Detect project characteristics ----
|
|
732
1025
|
log('Detecting project...');
|
|
733
1026
|
const detection = detectProject(dir);
|
|
@@ -746,10 +1039,62 @@ export async function runInit(options?: InitOptions): Promise<InitResult> {
|
|
|
746
1039
|
log(` Test framework: ${detection.testFramework}`);
|
|
747
1040
|
}
|
|
748
1041
|
|
|
749
|
-
// ---- Step 2: Check for existing NIMBUS.md ----
|
|
1042
|
+
// ---- Step 2: Check for existing NIMBUS.md and show diff (L8) ----
|
|
750
1043
|
const nimbusmdPath = path.join(dir, 'NIMBUS.md');
|
|
751
|
-
if (exists(nimbusmdPath) && !force) {
|
|
752
|
-
|
|
1044
|
+
if (exists(nimbusmdPath) && !force && !quiet) {
|
|
1045
|
+
// Generate the new content first for diffing
|
|
1046
|
+
const infraCtxForDiff = await discoverInfraContext(dir).catch(() => undefined);
|
|
1047
|
+
const newContent = generateNimbusMd(detection, dir, infraCtxForDiff);
|
|
1048
|
+
const existingContent = readText(nimbusmdPath);
|
|
1049
|
+
|
|
1050
|
+
if (newContent === existingContent) {
|
|
1051
|
+
log('NIMBUS.md is already up to date.');
|
|
1052
|
+
return { detection, filesCreated: [], nimbusmdPath };
|
|
1053
|
+
}
|
|
1054
|
+
|
|
1055
|
+
// Show line-level diff
|
|
1056
|
+
const oldLines = existingContent.split('\n');
|
|
1057
|
+
const newLines = newContent.split('\n');
|
|
1058
|
+
const diffLines: string[] = [];
|
|
1059
|
+
const maxLen = Math.max(oldLines.length, newLines.length);
|
|
1060
|
+
for (let idx = 0; idx < maxLen; idx++) {
|
|
1061
|
+
const o = oldLines[idx];
|
|
1062
|
+
const n = newLines[idx];
|
|
1063
|
+
if (o === undefined) diffLines.push(`+ ${n}`);
|
|
1064
|
+
else if (n === undefined) diffLines.push(`- ${o}`);
|
|
1065
|
+
else if (o !== n) { diffLines.push(`- ${o}`); diffLines.push(`+ ${n}`); }
|
|
1066
|
+
}
|
|
1067
|
+
|
|
1068
|
+
if (diffLines.length > 0) {
|
|
1069
|
+
log('\nNIMBUS.md changes:');
|
|
1070
|
+
for (const dl of diffLines.slice(0, 50)) {
|
|
1071
|
+
if (dl.startsWith('+')) log(` \x1b[32m${dl}\x1b[0m`);
|
|
1072
|
+
else if (dl.startsWith('-')) log(` \x1b[31m${dl}\x1b[0m`);
|
|
1073
|
+
else log(` ${dl}`);
|
|
1074
|
+
}
|
|
1075
|
+
if (diffLines.length > 50) log(` ... and ${diffLines.length - 50} more changes`);
|
|
1076
|
+
log('');
|
|
1077
|
+
log('Apply these changes? [y/N]');
|
|
1078
|
+
|
|
1079
|
+
// Synchronous readline for non-quiet mode
|
|
1080
|
+
const answer = await new Promise<string>(resolve => {
|
|
1081
|
+
const { createInterface } = require('readline') as typeof import('readline');
|
|
1082
|
+
const rl = createInterface({ input: process.stdin, output: process.stdout });
|
|
1083
|
+
rl.question('', (ans: string) => { rl.close(); resolve(ans.trim()); });
|
|
1084
|
+
});
|
|
1085
|
+
|
|
1086
|
+
if (answer.toLowerCase() !== 'y') {
|
|
1087
|
+
log('Aborted — NIMBUS.md not changed.');
|
|
1088
|
+
return { detection, filesCreated: [], nimbusmdPath };
|
|
1089
|
+
}
|
|
1090
|
+
|
|
1091
|
+
fs.writeFileSync(nimbusmdPath, newContent, 'utf-8');
|
|
1092
|
+
log(' Updated NIMBUS.md');
|
|
1093
|
+
return { detection, filesCreated: [nimbusmdPath], nimbusmdPath };
|
|
1094
|
+
}
|
|
1095
|
+
} else if (exists(nimbusmdPath) && !force) {
|
|
1096
|
+
// quiet mode — skip without prompting
|
|
1097
|
+
return { detection, filesCreated: [], nimbusmdPath };
|
|
753
1098
|
}
|
|
754
1099
|
|
|
755
1100
|
// ---- Step 3: Create .nimbus/ directory structure ----
|
|
@@ -827,7 +1172,25 @@ export async function runInit(options?: InitOptions): Promise<InitResult> {
|
|
|
827
1172
|
}
|
|
828
1173
|
|
|
829
1174
|
// ---- Step 7: Generate and write NIMBUS.md ----
|
|
830
|
-
|
|
1175
|
+
// L8: Monorepo detection — scan immediate subdirs for terraform roots
|
|
1176
|
+
let monorepoSection = '';
|
|
1177
|
+
try {
|
|
1178
|
+
const subdirs = fs.readdirSync(dir, { withFileTypes: true })
|
|
1179
|
+
.filter(e => e.isDirectory() && !e.name.startsWith('.') && !['node_modules', '.git'].includes(e.name))
|
|
1180
|
+
.map(e => e.name);
|
|
1181
|
+
const tfRoots: string[] = [];
|
|
1182
|
+
for (const sub of subdirs.slice(0, 20)) {
|
|
1183
|
+
const subPath = path.join(dir, sub);
|
|
1184
|
+
const hasTf = fs.readdirSync(subPath).some(f => f.endsWith('.tf'));
|
|
1185
|
+
if (hasTf) tfRoots.push(sub);
|
|
1186
|
+
}
|
|
1187
|
+
if (tfRoots.length > 1) {
|
|
1188
|
+
monorepoSection = `\n## Terraform Modules (Monorepo)\n\nThis is a monorepo with multiple Terraform roots:\n${tfRoots.map(r => `- \`./${r}/\``).join('\n')}\n\nTo target a specific root, \`cd\` into the directory or specify the path.\n`;
|
|
1189
|
+
log(` Detected ${tfRoots.length} Terraform roots (monorepo)`);
|
|
1190
|
+
}
|
|
1191
|
+
} catch { /* non-critical */ }
|
|
1192
|
+
|
|
1193
|
+
const nimbusmdContent = generateNimbusMd(detection, dir) + monorepoSection;
|
|
831
1194
|
fs.writeFileSync(nimbusmdPath, nimbusmdContent, 'utf-8');
|
|
832
1195
|
filesCreated.push(nimbusmdPath);
|
|
833
1196
|
log(' Created NIMBUS.md');
|
|
@@ -846,6 +1209,16 @@ export async function runInit(options?: InitOptions): Promise<InitResult> {
|
|
|
846
1209
|
log('Nimbus project initialized successfully.');
|
|
847
1210
|
log('Edit NIMBUS.md to customise agent behaviour.');
|
|
848
1211
|
|
|
1212
|
+
// M5: Print next steps after generating NIMBUS.md
|
|
1213
|
+
if (!quiet) {
|
|
1214
|
+
log('');
|
|
1215
|
+
log('\x1b[36mNext steps:\x1b[0m');
|
|
1216
|
+
log(' nimbus plan Preview infrastructure changes');
|
|
1217
|
+
log(' nimbus doctor Check your DevOps toolchain');
|
|
1218
|
+
log(' nimbus status Live infrastructure health dashboard');
|
|
1219
|
+
log(' nimbus Open the interactive DevOps agent');
|
|
1220
|
+
}
|
|
1221
|
+
|
|
849
1222
|
return {
|
|
850
1223
|
detection,
|
|
851
1224
|
filesCreated,
|