@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
|
@@ -6,7 +6,6 @@
|
|
|
6
6
|
|
|
7
7
|
import { ui } from '../../wizard/ui';
|
|
8
8
|
import { select, confirm } from '../../wizard/prompts';
|
|
9
|
-
import { CoreEngineClient } from '../../clients/core-engine-client';
|
|
10
9
|
import type { DriftReport, DriftRemediationResult, DriftProvider } from '../../types';
|
|
11
10
|
|
|
12
11
|
// ==========================================
|
|
@@ -236,6 +235,318 @@ function displayRemediationResult(result: DriftRemediationResult): void {
|
|
|
236
235
|
// Commands
|
|
237
236
|
// ==========================================
|
|
238
237
|
|
|
238
|
+
/**
|
|
239
|
+
* Detect drift directly using CLI tools (no CoreEngineClient).
|
|
240
|
+
* For terraform: uses terraform plan -detailed-exitcode.
|
|
241
|
+
* For kubernetes/helm: returns a minimal "no API" report.
|
|
242
|
+
*/
|
|
243
|
+
async function detectDriftDirect(provider: DriftProvider, directory: string): Promise<DriftReport> {
|
|
244
|
+
const { execFileSync } = await import('child_process');
|
|
245
|
+
|
|
246
|
+
if (provider === 'terraform') {
|
|
247
|
+
try {
|
|
248
|
+
execFileSync('terraform', ['plan', '-no-color', '-detailed-exitcode'], {
|
|
249
|
+
cwd: directory,
|
|
250
|
+
encoding: 'utf-8',
|
|
251
|
+
timeout: 120_000,
|
|
252
|
+
stdio: ['pipe', 'pipe', 'pipe'],
|
|
253
|
+
});
|
|
254
|
+
// exit 0 = no drift
|
|
255
|
+
return { hasDrift: false, provider, directory, detectedAt: new Date().toISOString(), resources: [], summary: { total: 0, added: 0, removed: 0, modified: 0, bySeverity: {} } };
|
|
256
|
+
} catch (e: any) {
|
|
257
|
+
if (e.status === 2) {
|
|
258
|
+
// exit 2 = changes present
|
|
259
|
+
const planOutput: string = e.stdout ?? '';
|
|
260
|
+
const planLine = planOutput.split('\n').find((l: string) => l.startsWith('Plan:')) ?? 'Changes detected';
|
|
261
|
+
return {
|
|
262
|
+
hasDrift: true,
|
|
263
|
+
provider,
|
|
264
|
+
directory,
|
|
265
|
+
detectedAt: new Date().toISOString(),
|
|
266
|
+
resources: [{ resourceId: planLine.trim(), resourceType: 'terraform', driftType: 'modified', severity: 'medium', changes: [] }],
|
|
267
|
+
summary: { total: 1, added: 0, removed: 0, modified: 1, bySeverity: { medium: 1 } },
|
|
268
|
+
};
|
|
269
|
+
}
|
|
270
|
+
throw new Error(`terraform plan failed: ${String(e.message ?? e).slice(0, 200)}`);
|
|
271
|
+
}
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
if (provider === 'kubernetes') {
|
|
275
|
+
try {
|
|
276
|
+
const out = execFileSync('kubectl', ['diff', '-R', '-f', directory], {
|
|
277
|
+
encoding: 'utf-8', timeout: 30_000, stdio: ['pipe', 'pipe', 'pipe'],
|
|
278
|
+
});
|
|
279
|
+
const hasDiff = out.trim().length > 0;
|
|
280
|
+
return { hasDrift: hasDiff, provider, directory, detectedAt: new Date().toISOString(), resources: [], summary: { total: hasDiff ? 1 : 0, added: 0, removed: 0, modified: hasDiff ? 1 : 0, bySeverity: {} } };
|
|
281
|
+
} catch (e: any) {
|
|
282
|
+
// kubectl diff exits 1 when there are differences
|
|
283
|
+
if (e.status === 1) {
|
|
284
|
+
return { hasDrift: true, provider, directory, detectedAt: new Date().toISOString(), resources: [], summary: { total: 1, added: 0, removed: 0, modified: 1, bySeverity: { medium: 1 } } };
|
|
285
|
+
}
|
|
286
|
+
throw new Error(`kubectl diff failed: ${String(e.message ?? e).slice(0, 200)}`);
|
|
287
|
+
}
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
if (provider === 'helm') {
|
|
291
|
+
try {
|
|
292
|
+
const out = execFileSync('helm', ['list', '--all-namespaces', '--output', 'json'], {
|
|
293
|
+
encoding: 'utf-8', timeout: 15_000, stdio: ['pipe', 'pipe', 'pipe'],
|
|
294
|
+
});
|
|
295
|
+
const releases: Array<{ status: string; name: string }> = JSON.parse(out || '[]');
|
|
296
|
+
const drifted = releases.filter(r => r.status !== 'deployed');
|
|
297
|
+
return {
|
|
298
|
+
hasDrift: drifted.length > 0,
|
|
299
|
+
provider,
|
|
300
|
+
directory,
|
|
301
|
+
detectedAt: new Date().toISOString(),
|
|
302
|
+
resources: drifted.map(r => ({ resourceId: r.name, resourceType: 'helm', driftType: 'modified' as const, severity: 'medium' as const, changes: [{ attribute: 'status', expected: 'deployed', actual: r.status }] })),
|
|
303
|
+
summary: { total: drifted.length, added: 0, removed: 0, modified: drifted.length, bySeverity: { medium: drifted.length } },
|
|
304
|
+
};
|
|
305
|
+
} catch (e: any) {
|
|
306
|
+
throw new Error(`helm list failed: ${String(e.message ?? e).slice(0, 200)}`);
|
|
307
|
+
}
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
throw new Error(`Unknown provider: ${provider}`);
|
|
311
|
+
}
|
|
312
|
+
|
|
313
|
+
/**
|
|
314
|
+
* Fix drift directly using CLI tools (no CoreEngineClient).
|
|
315
|
+
* For terraform: runs terraform apply -auto-approve.
|
|
316
|
+
* For kubernetes: runs kubectl apply -f <dir>.
|
|
317
|
+
* For helm: no automated fix; returns guidance.
|
|
318
|
+
*/
|
|
319
|
+
async function fixDriftDirect(provider: DriftProvider, directory: string): Promise<DriftRemediationResult> {
|
|
320
|
+
const { execFileSync } = await import('child_process');
|
|
321
|
+
|
|
322
|
+
if (provider === 'terraform') {
|
|
323
|
+
try {
|
|
324
|
+
const output = execFileSync('terraform', ['apply', '-auto-approve', '-no-color'], {
|
|
325
|
+
cwd: directory,
|
|
326
|
+
encoding: 'utf-8',
|
|
327
|
+
timeout: 300_000,
|
|
328
|
+
stdio: ['pipe', 'pipe', 'pipe'],
|
|
329
|
+
});
|
|
330
|
+
return { success: true, appliedCount: 1, failedCount: 0, skippedCount: 0, actions: [{ id: '1', type: 'apply' as const, resourceId: directory, description: 'terraform apply', status: 'applied' as const }], report: output.slice(0, 500) };
|
|
331
|
+
} catch (e: any) {
|
|
332
|
+
return { success: false, appliedCount: 0, failedCount: 1, skippedCount: 0, actions: [{ id: '1', type: 'apply' as const, resourceId: directory, description: 'terraform apply', status: 'failed' as const, error: String(e.message ?? e).slice(0, 200) }] };
|
|
333
|
+
}
|
|
334
|
+
}
|
|
335
|
+
|
|
336
|
+
if (provider === 'kubernetes') {
|
|
337
|
+
try {
|
|
338
|
+
const output = execFileSync('kubectl', ['apply', '-R', '-f', directory], {
|
|
339
|
+
encoding: 'utf-8', timeout: 60_000, stdio: ['pipe', 'pipe', 'pipe'],
|
|
340
|
+
});
|
|
341
|
+
return { success: true, appliedCount: 1, failedCount: 0, skippedCount: 0, actions: [{ id: '1', type: 'apply' as const, resourceId: directory, description: 'kubectl apply', status: 'applied' as const }], report: output.slice(0, 500) };
|
|
342
|
+
} catch (e: any) {
|
|
343
|
+
return { success: false, appliedCount: 0, failedCount: 1, skippedCount: 0, actions: [{ id: '1', type: 'apply' as const, resourceId: directory, description: 'kubectl apply', status: 'failed' as const, error: String(e.message ?? e).slice(0, 200) }] };
|
|
344
|
+
}
|
|
345
|
+
}
|
|
346
|
+
|
|
347
|
+
// Helm: no automated fix
|
|
348
|
+
return { success: false, appliedCount: 0, failedCount: 0, skippedCount: 1, actions: [{ id: '1', type: 'manual' as const, resourceId: directory, description: 'helm fix', status: 'skipped' as const, error: 'Helm drift fix requires manual intervention. Run "helm upgrade <release> <chart>" to remediate.' }] };
|
|
349
|
+
}
|
|
350
|
+
|
|
351
|
+
/**
|
|
352
|
+
* H3: Direct drift scan — runs terraform plan -detailed-exitcode in all
|
|
353
|
+
* subdirectories that contain Terraform configs, without needing CoreEngineClient.
|
|
354
|
+
*/
|
|
355
|
+
export async function driftScanCommand(opts: { workdir?: string; format?: 'table' | 'json' } = {}): Promise<void> {
|
|
356
|
+
const { execFileSync } = await import('child_process');
|
|
357
|
+
const fsSync = await import('fs');
|
|
358
|
+
const pathMod = await import('path');
|
|
359
|
+
|
|
360
|
+
const rootDir = pathMod.resolve(opts.workdir ?? process.cwd());
|
|
361
|
+
|
|
362
|
+
interface ScanResult {
|
|
363
|
+
directory: string;
|
|
364
|
+
status: 'clean' | 'drift' | 'error';
|
|
365
|
+
summary: string;
|
|
366
|
+
}
|
|
367
|
+
|
|
368
|
+
// Find terraform directories up to depth 3
|
|
369
|
+
const tfDirs: string[] = [];
|
|
370
|
+
|
|
371
|
+
function walk(dir: string, depth: number): void {
|
|
372
|
+
if (depth > 3) return;
|
|
373
|
+
try {
|
|
374
|
+
if (fsSync.existsSync(pathMod.join(dir, '.terraform')) || fsSync.readdirSync(dir).some(f => f.endsWith('.tf'))) {
|
|
375
|
+
tfDirs.push(dir);
|
|
376
|
+
}
|
|
377
|
+
for (const entry of fsSync.readdirSync(dir, { withFileTypes: true })) {
|
|
378
|
+
if (entry.isDirectory() && !entry.name.startsWith('.') && entry.name !== 'node_modules') {
|
|
379
|
+
walk(pathMod.join(dir, entry.name), depth + 1);
|
|
380
|
+
}
|
|
381
|
+
}
|
|
382
|
+
} catch { /* skip unreadable dirs */ }
|
|
383
|
+
}
|
|
384
|
+
|
|
385
|
+
walk(rootDir, 0);
|
|
386
|
+
|
|
387
|
+
if (tfDirs.length === 0) {
|
|
388
|
+
ui.info('No Terraform directories found.');
|
|
389
|
+
return;
|
|
390
|
+
}
|
|
391
|
+
|
|
392
|
+
ui.header('Terraform Drift Scan');
|
|
393
|
+
const results: ScanResult[] = [];
|
|
394
|
+
|
|
395
|
+
for (const dir of tfDirs) {
|
|
396
|
+
const relDir = pathMod.relative(rootDir, dir) || '.';
|
|
397
|
+
try {
|
|
398
|
+
execFileSync('terraform', ['plan', '-no-color', '-detailed-exitcode'], {
|
|
399
|
+
cwd: dir,
|
|
400
|
+
encoding: 'utf-8',
|
|
401
|
+
timeout: 120_000,
|
|
402
|
+
stdio: ['pipe', 'pipe', 'pipe'],
|
|
403
|
+
});
|
|
404
|
+
// exit 0 = no changes
|
|
405
|
+
results.push({ directory: relDir, status: 'clean', summary: 'No changes' });
|
|
406
|
+
} catch (e: any) {
|
|
407
|
+
if (e.status === 2) {
|
|
408
|
+
// exit 2 = changes present
|
|
409
|
+
const planOutput: string = e.stdout ?? '';
|
|
410
|
+
const planLine = planOutput.split('\n').find((l: string) => l.startsWith('Plan:')) ?? 'Changes detected';
|
|
411
|
+
results.push({ directory: relDir, status: 'drift', summary: planLine.trim() });
|
|
412
|
+
} else {
|
|
413
|
+
results.push({ directory: relDir, status: 'error', summary: String(e.message ?? 'error').slice(0, 80) });
|
|
414
|
+
}
|
|
415
|
+
}
|
|
416
|
+
}
|
|
417
|
+
|
|
418
|
+
if (opts.format === 'json') {
|
|
419
|
+
console.log(JSON.stringify(results, null, 2));
|
|
420
|
+
return;
|
|
421
|
+
}
|
|
422
|
+
|
|
423
|
+
// Print table
|
|
424
|
+
const COL = { dir: 40, status: 8, summary: 60 };
|
|
425
|
+
const pad = (s: string, n: number) => s.slice(0, n).padEnd(n);
|
|
426
|
+
const divider = `${'-'.repeat(COL.dir + 2)}+${'-'.repeat(COL.status + 2)}+${'-'.repeat(COL.summary + 2)}`;
|
|
427
|
+
console.log(divider);
|
|
428
|
+
console.log(`| ${pad('Directory', COL.dir)} | ${pad('Status', COL.status)} | ${pad('Summary', COL.summary)} |`);
|
|
429
|
+
console.log(divider);
|
|
430
|
+
for (const r of results) {
|
|
431
|
+
console.log(`| ${pad(r.directory, COL.dir)} | ${pad(r.status, COL.status)} | ${pad(r.summary, COL.summary)} |`);
|
|
432
|
+
}
|
|
433
|
+
console.log(divider);
|
|
434
|
+
}
|
|
435
|
+
|
|
436
|
+
// ==========================================
|
|
437
|
+
// K8s Drift Detection (H3)
|
|
438
|
+
// ==========================================
|
|
439
|
+
|
|
440
|
+
export interface K8sDriftOptions {
|
|
441
|
+
/** Update baseline with current resource counts. */
|
|
442
|
+
updateBaseline?: boolean;
|
|
443
|
+
/** Output format. */
|
|
444
|
+
format?: 'table' | 'json';
|
|
445
|
+
}
|
|
446
|
+
|
|
447
|
+
/** Stored baseline: map of namespace → resource count. */
|
|
448
|
+
interface K8sBaseline {
|
|
449
|
+
capturedAt: string;
|
|
450
|
+
namespaceCounts: Record<string, number>;
|
|
451
|
+
}
|
|
452
|
+
|
|
453
|
+
/**
|
|
454
|
+
* H3: Check ConfigMap/Secret drift versus a stored baseline.
|
|
455
|
+
* Runs `kubectl get configmap,secret -A` to get current counts per namespace,
|
|
456
|
+
* compares against ~/.nimbus/drift-baseline.json, and reports new/missing resources.
|
|
457
|
+
*/
|
|
458
|
+
export async function checkK8sDrift(opts: K8sDriftOptions = {}): Promise<void> {
|
|
459
|
+
const { execFile } = await import('child_process');
|
|
460
|
+
const { promisify } = await import('util');
|
|
461
|
+
const { existsSync, readFileSync, writeFileSync, mkdirSync } = await import('fs');
|
|
462
|
+
const { join } = await import('path');
|
|
463
|
+
const { homedir } = await import('os');
|
|
464
|
+
const execFileAsync = promisify(execFile);
|
|
465
|
+
|
|
466
|
+
const baselinePath = join(homedir(), '.nimbus', 'drift-baseline.json');
|
|
467
|
+
|
|
468
|
+
// Get current resource counts per namespace
|
|
469
|
+
let rawOutput = '';
|
|
470
|
+
try {
|
|
471
|
+
const { stdout } = await execFileAsync('kubectl', [
|
|
472
|
+
'get', 'configmap,secret', '-A', '--no-headers', '-o',
|
|
473
|
+
'custom-columns=NAMESPACE:.metadata.namespace,NAME:.metadata.name',
|
|
474
|
+
], { timeout: 15000 });
|
|
475
|
+
rawOutput = stdout;
|
|
476
|
+
} catch {
|
|
477
|
+
ui.error('kubectl not available or cluster unreachable.');
|
|
478
|
+
return;
|
|
479
|
+
}
|
|
480
|
+
|
|
481
|
+
const lines = rawOutput.trim().split('\n').filter(Boolean);
|
|
482
|
+
const namespaceCounts: Record<string, number> = {};
|
|
483
|
+
for (const line of lines) {
|
|
484
|
+
const parts = line.trim().split(/\s+/);
|
|
485
|
+
const ns = parts[0] ?? 'default';
|
|
486
|
+
namespaceCounts[ns] = (namespaceCounts[ns] ?? 0) + 1;
|
|
487
|
+
}
|
|
488
|
+
|
|
489
|
+
if (opts.updateBaseline) {
|
|
490
|
+
const baseline: K8sBaseline = { capturedAt: new Date().toISOString(), namespaceCounts };
|
|
491
|
+
mkdirSync(join(homedir(), '.nimbus'), { recursive: true });
|
|
492
|
+
writeFileSync(baselinePath, JSON.stringify(baseline, null, 2), 'utf-8');
|
|
493
|
+
ui.success(`Baseline saved: ${Object.keys(namespaceCounts).length} namespaces, ${lines.length} resources total.`);
|
|
494
|
+
return;
|
|
495
|
+
}
|
|
496
|
+
|
|
497
|
+
// Load baseline
|
|
498
|
+
if (!existsSync(baselinePath)) {
|
|
499
|
+
ui.warning('No K8s drift baseline found. Run with --update-baseline to capture current state.');
|
|
500
|
+
ui.newLine();
|
|
501
|
+
ui.print(`Current state: ${lines.length} ConfigMaps/Secrets across ${Object.keys(namespaceCounts).length} namespaces.`);
|
|
502
|
+
return;
|
|
503
|
+
}
|
|
504
|
+
|
|
505
|
+
let baseline: K8sBaseline;
|
|
506
|
+
try {
|
|
507
|
+
baseline = JSON.parse(readFileSync(baselinePath, 'utf-8')) as K8sBaseline;
|
|
508
|
+
} catch {
|
|
509
|
+
ui.error('Failed to parse drift baseline file. Re-capture with --update-baseline.');
|
|
510
|
+
return;
|
|
511
|
+
}
|
|
512
|
+
|
|
513
|
+
// Compare counts
|
|
514
|
+
const driftEntries: Array<{ namespace: string; baseline: number; current: number; delta: number }> = [];
|
|
515
|
+
const allNamespaces = new Set([...Object.keys(baseline.namespaceCounts), ...Object.keys(namespaceCounts)]);
|
|
516
|
+
|
|
517
|
+
for (const ns of allNamespaces) {
|
|
518
|
+
const baseCount = baseline.namespaceCounts[ns] ?? 0;
|
|
519
|
+
const currCount = namespaceCounts[ns] ?? 0;
|
|
520
|
+
if (baseCount !== currCount) {
|
|
521
|
+
driftEntries.push({ namespace: ns, baseline: baseCount, current: currCount, delta: currCount - baseCount });
|
|
522
|
+
}
|
|
523
|
+
}
|
|
524
|
+
|
|
525
|
+
if (opts.format === 'json') {
|
|
526
|
+
console.log(JSON.stringify({ capturedAt: baseline.capturedAt, drift: driftEntries }, null, 2));
|
|
527
|
+
return;
|
|
528
|
+
}
|
|
529
|
+
|
|
530
|
+
ui.header('K8s ConfigMap/Secret Drift');
|
|
531
|
+
ui.print(` Baseline captured: ${new Date(baseline.capturedAt).toLocaleString()}`);
|
|
532
|
+
ui.newLine();
|
|
533
|
+
|
|
534
|
+
if (driftEntries.length === 0) {
|
|
535
|
+
ui.success('No drift detected — ConfigMap/Secret counts match baseline.');
|
|
536
|
+
return;
|
|
537
|
+
}
|
|
538
|
+
|
|
539
|
+
ui.warning(`${driftEntries.length} namespace(s) have drifted from baseline:`);
|
|
540
|
+
ui.newLine();
|
|
541
|
+
for (const entry of driftEntries) {
|
|
542
|
+
const sign = entry.delta > 0 ? '+' : '';
|
|
543
|
+
const color = entry.delta > 0 ? 'yellow' : 'red';
|
|
544
|
+
ui.print(` ${ui.bold(entry.namespace.padEnd(30))} baseline: ${String(entry.baseline).padStart(4)} current: ${String(entry.current).padStart(4)} delta: ${ui.color(sign + entry.delta, color)}`);
|
|
545
|
+
}
|
|
546
|
+
ui.newLine();
|
|
547
|
+
ui.print(ui.dim('Run "nimbus drift k8s --update-baseline" to update the baseline.'));
|
|
548
|
+
}
|
|
549
|
+
|
|
239
550
|
/**
|
|
240
551
|
* Drift parent command
|
|
241
552
|
*/
|
|
@@ -246,10 +557,16 @@ export async function driftCommand(args: string[]): Promise<void> {
|
|
|
246
557
|
ui.print('Usage: nimbus drift <command> [options]');
|
|
247
558
|
ui.newLine();
|
|
248
559
|
ui.print('Commands:');
|
|
560
|
+
ui.print(` ${ui.bold('scan')} Direct terraform drift scan (no service dependency)`);
|
|
561
|
+
ui.print(` ${ui.bold('k8s')} K8s ConfigMap/Secret drift vs baseline`);
|
|
249
562
|
ui.print(` ${ui.bold('detect')} Detect infrastructure drift`);
|
|
250
563
|
ui.print(` ${ui.bold('fix')} Fix detected drift`);
|
|
251
564
|
ui.newLine();
|
|
252
565
|
ui.print('Examples:');
|
|
566
|
+
ui.print(' nimbus drift scan');
|
|
567
|
+
ui.print(' nimbus drift scan --format json');
|
|
568
|
+
ui.print(' nimbus drift k8s');
|
|
569
|
+
ui.print(' nimbus drift k8s --update-baseline');
|
|
253
570
|
ui.print(' nimbus drift detect --provider terraform');
|
|
254
571
|
ui.print(' nimbus drift detect kubernetes -d ./manifests');
|
|
255
572
|
ui.print(' nimbus drift fix terraform --auto-approve');
|
|
@@ -261,11 +578,24 @@ export async function driftCommand(args: string[]): Promise<void> {
|
|
|
261
578
|
const subArgs = args.slice(1);
|
|
262
579
|
|
|
263
580
|
switch (subcommand) {
|
|
581
|
+
case 'scan': {
|
|
582
|
+
const format = subArgs.includes('--json') ? 'json' : 'table';
|
|
583
|
+
const workdirIdx = subArgs.indexOf('--workdir');
|
|
584
|
+
const workdir = workdirIdx !== -1 ? subArgs[workdirIdx + 1] : undefined;
|
|
585
|
+
await driftScanCommand({ workdir, format });
|
|
586
|
+
break;
|
|
587
|
+
}
|
|
588
|
+
case 'k8s': {
|
|
589
|
+
const updateBaseline = subArgs.includes('--update-baseline');
|
|
590
|
+
const format = subArgs.includes('--json') ? 'json' : 'table';
|
|
591
|
+
await checkK8sDrift({ updateBaseline, format });
|
|
592
|
+
break;
|
|
593
|
+
}
|
|
264
594
|
case 'detect':
|
|
265
595
|
await driftDetectCommand(parseDriftDetectOptions(subArgs));
|
|
266
596
|
break;
|
|
267
597
|
case 'fix':
|
|
268
|
-
await driftFixCommand(parseDriftFixOptions(subArgs));
|
|
598
|
+
await driftFixCommand(parseDriftFixOptions(subArgs), subArgs);
|
|
269
599
|
break;
|
|
270
600
|
default:
|
|
271
601
|
ui.error(`Unknown drift command: ${subcommand}`);
|
|
@@ -302,12 +632,7 @@ export async function driftDetectCommand(options: DriftDetectOptions): Promise<v
|
|
|
302
632
|
ui.startSpinner({ message: `Detecting ${provider} drift...` });
|
|
303
633
|
|
|
304
634
|
try {
|
|
305
|
-
const
|
|
306
|
-
const report = await client.detectDrift({
|
|
307
|
-
provider,
|
|
308
|
-
directory,
|
|
309
|
-
});
|
|
310
|
-
|
|
635
|
+
const report = await detectDriftDirect(provider, directory);
|
|
311
636
|
ui.stopSpinnerSuccess('Drift detection complete');
|
|
312
637
|
|
|
313
638
|
if (options.json) {
|
|
@@ -330,7 +655,7 @@ export async function driftDetectCommand(options: DriftDetectOptions): Promise<v
|
|
|
330
655
|
/**
|
|
331
656
|
* Fix drift command
|
|
332
657
|
*/
|
|
333
|
-
export async function driftFixCommand(options: DriftFixOptions): Promise<void> {
|
|
658
|
+
export async function driftFixCommand(options: DriftFixOptions, args: string[] = []): Promise<void> {
|
|
334
659
|
const directory = options.directory || process.cwd();
|
|
335
660
|
let provider = options.provider;
|
|
336
661
|
|
|
@@ -354,11 +679,7 @@ export async function driftFixCommand(options: DriftFixOptions): Promise<void> {
|
|
|
354
679
|
|
|
355
680
|
let report: DriftReport;
|
|
356
681
|
try {
|
|
357
|
-
|
|
358
|
-
report = await client.detectDrift({
|
|
359
|
-
provider,
|
|
360
|
-
directory,
|
|
361
|
-
});
|
|
682
|
+
report = await detectDriftDirect(provider, directory);
|
|
362
683
|
ui.stopSpinnerSuccess('Drift detection complete');
|
|
363
684
|
} catch (error) {
|
|
364
685
|
ui.stopSpinnerFail('Drift detection failed');
|
|
@@ -412,13 +733,11 @@ export async function driftFixCommand(options: DriftFixOptions): Promise<void> {
|
|
|
412
733
|
// Apply fixes
|
|
413
734
|
ui.startSpinner({ message: 'Applying remediation...' });
|
|
414
735
|
|
|
736
|
+
let driftReport: DriftReport | undefined;
|
|
415
737
|
try {
|
|
416
|
-
const
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
directory,
|
|
420
|
-
dryRun: false,
|
|
421
|
-
});
|
|
738
|
+
const result = await fixDriftDirect(provider, directory);
|
|
739
|
+
|
|
740
|
+
driftReport = report;
|
|
422
741
|
|
|
423
742
|
if (result.success) {
|
|
424
743
|
ui.stopSpinnerSuccess('Remediation complete');
|
|
@@ -428,12 +747,41 @@ export async function driftFixCommand(options: DriftFixOptions): Promise<void> {
|
|
|
428
747
|
|
|
429
748
|
if (options.json) {
|
|
430
749
|
console.log(JSON.stringify(result, null, 2));
|
|
431
|
-
|
|
750
|
+
} else {
|
|
751
|
+
displayRemediationResult(result);
|
|
432
752
|
}
|
|
433
|
-
|
|
434
|
-
displayRemediationResult(result);
|
|
435
753
|
} catch (error) {
|
|
436
754
|
ui.stopSpinnerFail('Remediation failed');
|
|
437
755
|
ui.error((error as Error).message);
|
|
438
756
|
}
|
|
757
|
+
|
|
758
|
+
// GAP-23: --notify support for Slack/email
|
|
759
|
+
const notifyFlag = args.find((a: string) => a.startsWith('--notify='))?.split('=')[1]
|
|
760
|
+
?? (args.includes('--notify') ? args[args.indexOf('--notify') + 1] : undefined);
|
|
761
|
+
|
|
762
|
+
if (notifyFlag === 'slack') {
|
|
763
|
+
const webhookUrl = process.env.NIMBUS_SLACK_WEBHOOK;
|
|
764
|
+
if (!webhookUrl) {
|
|
765
|
+
ui.warning('Set NIMBUS_SLACK_WEBHOOK env var to enable Slack notifications');
|
|
766
|
+
} else {
|
|
767
|
+
try {
|
|
768
|
+
const summary = driftReport ? JSON.stringify(driftReport).slice(0, 2000) : 'Drift check completed';
|
|
769
|
+
await fetch(webhookUrl, {
|
|
770
|
+
method: 'POST',
|
|
771
|
+
headers: { 'Content-Type': 'application/json' },
|
|
772
|
+
body: JSON.stringify({ text: `*Nimbus Drift Report*\n${summary}` }),
|
|
773
|
+
});
|
|
774
|
+
ui.success('Slack notification sent');
|
|
775
|
+
} catch (e) {
|
|
776
|
+
ui.warning(`Slack notification failed: ${e instanceof Error ? e.message : String(e)}`);
|
|
777
|
+
}
|
|
778
|
+
}
|
|
779
|
+
} else if (notifyFlag === 'email') {
|
|
780
|
+
// Generate curl command for email via SMTP relay / webhook
|
|
781
|
+
ui.print('To send drift report via email, run:');
|
|
782
|
+
ui.print(` curl -X POST https://api.mailersend.com/v1/email \\
|
|
783
|
+
-H "Authorization: Bearer $MAILERSEND_API_KEY" \\
|
|
784
|
+
-H "Content-Type: application/json" \\
|
|
785
|
+
-d '{"from":{"email":"nimbus@yourdomain.com"},"to":[{"email":"team@yourdomain.com"}],"subject":"Nimbus Drift Report","text":"${JSON.stringify(driftReport ?? 'completed').slice(0, 500)}"}'`);
|
|
786
|
+
}
|
|
439
787
|
}
|
|
@@ -0,0 +1,146 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Export Command (G19)
|
|
3
|
+
*
|
|
4
|
+
* Export session conversations to markdown, HTML, or JSON.
|
|
5
|
+
*
|
|
6
|
+
* Usage:
|
|
7
|
+
* nimbus export # most recent session → stdout (md)
|
|
8
|
+
* nimbus export <session-id> # specific session
|
|
9
|
+
* nimbus export --format html # HTML output
|
|
10
|
+
* nimbus export --format json # JSON output
|
|
11
|
+
* nimbus export --output report.md # save to file
|
|
12
|
+
*/
|
|
13
|
+
|
|
14
|
+
import { writeFileSync } from 'node:fs';
|
|
15
|
+
|
|
16
|
+
export interface ExportOptions {
|
|
17
|
+
format?: 'md' | 'html' | 'json';
|
|
18
|
+
output?: string;
|
|
19
|
+
sessionId?: string;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
// ---------------------------------------------------------------------------
|
|
23
|
+
// Helpers
|
|
24
|
+
// ---------------------------------------------------------------------------
|
|
25
|
+
|
|
26
|
+
function messageToMarkdown(role: string, content: string): string {
|
|
27
|
+
const label = role === 'user' ? '**User**' : role === 'assistant' ? '**Agent**' : `*[${role}]*`;
|
|
28
|
+
return `${label}:\n${content}\n`;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
// ---------------------------------------------------------------------------
|
|
32
|
+
// Main export
|
|
33
|
+
// ---------------------------------------------------------------------------
|
|
34
|
+
|
|
35
|
+
export async function exportCommand(options: ExportOptions = {}): Promise<void> {
|
|
36
|
+
const format = options.format ?? 'md';
|
|
37
|
+
|
|
38
|
+
// Load session manager + conversation
|
|
39
|
+
let conversation: Array<{ role: string; content: string; timestamp?: Date }> = [];
|
|
40
|
+
let sessionMeta: { id: string; name?: string; model?: string; mode?: string; createdAt?: string } = { id: 'unknown' };
|
|
41
|
+
|
|
42
|
+
try {
|
|
43
|
+
const { SessionManager } = await import('../sessions/manager');
|
|
44
|
+
const sm = SessionManager.getInstance();
|
|
45
|
+
const sessions = sm.list();
|
|
46
|
+
|
|
47
|
+
// Find the target session
|
|
48
|
+
let targetSession = sessions.find(s =>
|
|
49
|
+
options.sessionId && (s.id === options.sessionId || s.id.startsWith(options.sessionId))
|
|
50
|
+
) ?? sessions[0];
|
|
51
|
+
|
|
52
|
+
if (!targetSession) {
|
|
53
|
+
console.error('No sessions found. Run "nimbus chat" first to create a session.');
|
|
54
|
+
process.exit(1);
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
sessionMeta = {
|
|
58
|
+
id: targetSession.id,
|
|
59
|
+
name: targetSession.name,
|
|
60
|
+
model: targetSession.model,
|
|
61
|
+
mode: targetSession.mode,
|
|
62
|
+
createdAt: targetSession.createdAt,
|
|
63
|
+
};
|
|
64
|
+
|
|
65
|
+
const messages = sm.loadConversation(targetSession.id);
|
|
66
|
+
conversation = messages
|
|
67
|
+
.filter(m => m.role === 'user' || m.role === 'assistant')
|
|
68
|
+
.map(m => ({
|
|
69
|
+
role: m.role,
|
|
70
|
+
content: Array.isArray(m.content)
|
|
71
|
+
? m.content.map((b: unknown) => {
|
|
72
|
+
if (typeof b === 'string') return b;
|
|
73
|
+
if (b && typeof b === 'object' && 'text' in b) return (b as { text: string }).text;
|
|
74
|
+
return '';
|
|
75
|
+
}).join('')
|
|
76
|
+
: String(m.content ?? ''),
|
|
77
|
+
}));
|
|
78
|
+
} catch (err) {
|
|
79
|
+
console.error(`Failed to load session: ${err instanceof Error ? err.message : String(err)}`);
|
|
80
|
+
process.exit(1);
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
// Generate output
|
|
84
|
+
let output = '';
|
|
85
|
+
|
|
86
|
+
if (format === 'json') {
|
|
87
|
+
output = JSON.stringify({ session: sessionMeta, messages: conversation }, null, 2);
|
|
88
|
+
} else if (format === 'html') {
|
|
89
|
+
output = buildHtml(sessionMeta, conversation);
|
|
90
|
+
} else {
|
|
91
|
+
// Default: markdown
|
|
92
|
+
output = buildMarkdown(sessionMeta, conversation);
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
// Output to file or stdout
|
|
96
|
+
if (options.output) {
|
|
97
|
+
writeFileSync(options.output, output, 'utf-8');
|
|
98
|
+
console.log(`Exported to: ${options.output}`);
|
|
99
|
+
} else {
|
|
100
|
+
process.stdout.write(output);
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
function buildMarkdown(
|
|
105
|
+
meta: { id: string; name?: string; model?: string; mode?: string; createdAt?: string },
|
|
106
|
+
conversation: Array<{ role: string; content: string }>
|
|
107
|
+
): string {
|
|
108
|
+
const lines: string[] = [
|
|
109
|
+
`# Nimbus Session Export`,
|
|
110
|
+
``,
|
|
111
|
+
`| Field | Value |`,
|
|
112
|
+
`|-------|-------|`,
|
|
113
|
+
`| Session ID | ${meta.id} |`,
|
|
114
|
+
`| Name | ${meta.name ?? 'N/A'} |`,
|
|
115
|
+
`| Model | ${meta.model ?? 'N/A'} |`,
|
|
116
|
+
`| Mode | ${meta.mode ?? 'N/A'} |`,
|
|
117
|
+
`| Created | ${meta.createdAt ?? new Date().toISOString()} |`,
|
|
118
|
+
``,
|
|
119
|
+
`---`,
|
|
120
|
+
``,
|
|
121
|
+
`## Conversation`,
|
|
122
|
+
``,
|
|
123
|
+
];
|
|
124
|
+
|
|
125
|
+
for (const msg of conversation) {
|
|
126
|
+
lines.push(messageToMarkdown(msg.role, msg.content));
|
|
127
|
+
lines.push('---');
|
|
128
|
+
lines.push('');
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
return lines.join('\n');
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
function buildHtml(
|
|
135
|
+
meta: { id: string; name?: string; model?: string; mode?: string; createdAt?: string },
|
|
136
|
+
conversation: Array<{ role: string; content: string }>
|
|
137
|
+
): string {
|
|
138
|
+
const escape = (s: string) => s.replace(/&/g, '&').replace(/</g, '<').replace(/>/g, '>');
|
|
139
|
+
const msgs = conversation.map(m => {
|
|
140
|
+
const isUser = m.role === 'user';
|
|
141
|
+
const bg = isUser ? '#3b82f6' : '#1e293b';
|
|
142
|
+
const align = isUser ? 'flex-end' : 'flex-start';
|
|
143
|
+
return `<div style="display:flex;justify-content:${align};margin:8px 0"><div style="max-width:75%;padding:12px 16px;border-radius:16px;background:${bg};color:#e2e8f0;font-size:14px;line-height:1.6;white-space:pre-wrap">${escape(m.content)}</div></div>`;
|
|
144
|
+
}).join('\n');
|
|
145
|
+
return `<!DOCTYPE html><html><head><meta charset="utf-8"><title>Nimbus Session Export — ${escape(meta.id)}</title></head><body style="background:#0f172a;color:#e2e8f0;font-family:monospace;padding:24px"><h2>Nimbus Session: ${escape(meta.name ?? meta.id)}</h2><p>Model: ${escape(meta.model ?? 'N/A')} | Mode: ${escape(meta.mode ?? 'N/A')} | Created: ${escape(meta.createdAt ?? '')}</p><div>${msgs}</div></body></html>`;
|
|
146
|
+
}
|