@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
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
* structures for both standard and DevOps tools.
|
|
6
6
|
*/
|
|
7
7
|
|
|
8
|
-
import { describe, test, expect, beforeEach } from '
|
|
8
|
+
import { describe, test, it, expect, beforeEach } from 'vitest';
|
|
9
9
|
import { z } from 'zod';
|
|
10
10
|
import {
|
|
11
11
|
ToolRegistry,
|
|
@@ -14,7 +14,7 @@ import {
|
|
|
14
14
|
type ToolDefinition,
|
|
15
15
|
} from '../tools/schemas/types';
|
|
16
16
|
import { standardTools } from '../tools/schemas/standard';
|
|
17
|
-
import { devopsTools } from '../tools/schemas/devops';
|
|
17
|
+
import { devopsTools, formatKubectlPodsOutput, formatHelmListOutput } from '../tools/schemas/devops';
|
|
18
18
|
|
|
19
19
|
// ---------------------------------------------------------------------------
|
|
20
20
|
// Helpers
|
|
@@ -353,8 +353,8 @@ describe('Tool counts and metadata', () => {
|
|
|
353
353
|
expect(standardTools).toHaveLength(12);
|
|
354
354
|
});
|
|
355
355
|
|
|
356
|
-
test('devopsTools has exactly
|
|
357
|
-
expect(devopsTools).toHaveLength(
|
|
356
|
+
test('devopsTools has exactly 28 tools', () => {
|
|
357
|
+
expect(devopsTools).toHaveLength(28);
|
|
358
358
|
});
|
|
359
359
|
|
|
360
360
|
test('all standard tools have category "standard"', () => {
|
|
@@ -395,3 +395,208 @@ describe('Tool counts and metadata', () => {
|
|
|
395
395
|
}
|
|
396
396
|
});
|
|
397
397
|
});
|
|
398
|
+
|
|
399
|
+
// ---------------------------------------------------------------------------
|
|
400
|
+
// C2: infraContext in ToolExecuteContext
|
|
401
|
+
// ---------------------------------------------------------------------------
|
|
402
|
+
|
|
403
|
+
describe('infraContext in ToolExecuteContext (C2)', () => {
|
|
404
|
+
it('ToolExecuteContext type has infraContext field', async () => {
|
|
405
|
+
const { readFileSync } = await import('node:fs');
|
|
406
|
+
const { join } = await import('node:path');
|
|
407
|
+
const src = readFileSync(join(process.cwd(), 'src/tools/schemas/types.ts'), 'utf-8');
|
|
408
|
+
expect(src).toContain('infraContext?:');
|
|
409
|
+
});
|
|
410
|
+
|
|
411
|
+
it('kubectl tool uses contextFlag from infraContext', async () => {
|
|
412
|
+
const { readFileSync } = await import('node:fs');
|
|
413
|
+
const { join } = await import('node:path');
|
|
414
|
+
const src = readFileSync(join(process.cwd(), 'src/tools/schemas/devops.ts'), 'utf-8');
|
|
415
|
+
expect(src).toContain('ctx?.infraContext?.kubectlContext');
|
|
416
|
+
});
|
|
417
|
+
|
|
418
|
+
it('terraform tool reads sessionWorkspace from infraContext', async () => {
|
|
419
|
+
const { readFileSync } = await import('node:fs');
|
|
420
|
+
const { join } = await import('node:path');
|
|
421
|
+
const src = readFileSync(join(process.cwd(), 'src/tools/schemas/devops.ts'), 'utf-8');
|
|
422
|
+
expect(src).toContain('ctx?.infraContext?.terraformWorkspace');
|
|
423
|
+
});
|
|
424
|
+
|
|
425
|
+
it('generateInfraTool is in devopsTools array', async () => {
|
|
426
|
+
const { devopsTools } = await import('../tools/schemas/devops');
|
|
427
|
+
const names = devopsTools.map(t => t.name);
|
|
428
|
+
expect(names).toContain('generate_infra');
|
|
429
|
+
});
|
|
430
|
+
|
|
431
|
+
it('generateInfraTool has ask_once permissionTier', async () => {
|
|
432
|
+
const { devopsTools } = await import('../tools/schemas/devops');
|
|
433
|
+
const tool = devopsTools.find(t => t.name === 'generate_infra');
|
|
434
|
+
expect(tool?.permissionTier).toBe('ask_once');
|
|
435
|
+
});
|
|
436
|
+
|
|
437
|
+
it('generateInfraTool schema accepts terraform/kubernetes/helm types', async () => {
|
|
438
|
+
const { devopsTools } = await import('../tools/schemas/devops');
|
|
439
|
+
const tool = devopsTools.find(t => t.name === 'generate_infra');
|
|
440
|
+
expect(tool).toBeDefined();
|
|
441
|
+
// The schema should parse valid input without throwing
|
|
442
|
+
const { z } = await import('zod');
|
|
443
|
+
// Just verify the tool exists and has a schema
|
|
444
|
+
expect(tool?.inputSchema).toBeDefined();
|
|
445
|
+
});
|
|
446
|
+
});
|
|
447
|
+
|
|
448
|
+
// ---------------------------------------------------------------------------
|
|
449
|
+
// M2: Docker build streaming
|
|
450
|
+
// ---------------------------------------------------------------------------
|
|
451
|
+
|
|
452
|
+
describe('docker build streaming (M2)', () => {
|
|
453
|
+
it('dockerTool execute accepts ctx parameter', async () => {
|
|
454
|
+
const { readFileSync } = await import('node:fs');
|
|
455
|
+
const { join } = await import('node:path');
|
|
456
|
+
const src = readFileSync(join(process.cwd(), 'src/tools/schemas/devops.ts'), 'utf-8');
|
|
457
|
+
expect(src).toContain("input.action === 'build' && ctx?.onProgress");
|
|
458
|
+
});
|
|
459
|
+
|
|
460
|
+
it('docker build output filter keeps Step N/M lines', () => {
|
|
461
|
+
// Inline the filter logic to unit test it
|
|
462
|
+
const filterDockerBuildLine = (line: string): boolean => {
|
|
463
|
+
const trimmed = line.trim();
|
|
464
|
+
if (!trimmed) return false;
|
|
465
|
+
return /^Step\s+\d+\/\d+/i.test(trimmed) ||
|
|
466
|
+
/---> Using cache/i.test(trimmed) ||
|
|
467
|
+
/Successfully built/i.test(trimmed) ||
|
|
468
|
+
/Successfully tagged/i.test(trimmed) ||
|
|
469
|
+
/error/i.test(trimmed) ||
|
|
470
|
+
/warning/i.test(trimmed);
|
|
471
|
+
};
|
|
472
|
+
|
|
473
|
+
expect(filterDockerBuildLine('Step 3/12: RUN npm install')).toBe(true);
|
|
474
|
+
expect(filterDockerBuildLine(' ---> Using cache')).toBe(true);
|
|
475
|
+
expect(filterDockerBuildLine('Successfully built abc123')).toBe(true);
|
|
476
|
+
expect(filterDockerBuildLine('Successfully tagged myapp:latest')).toBe(true);
|
|
477
|
+
expect(filterDockerBuildLine(' ---> sha256:abc123def456')).toBe(false);
|
|
478
|
+
expect(filterDockerBuildLine('Removing intermediate container abc123')).toBe(false);
|
|
479
|
+
});
|
|
480
|
+
});
|
|
481
|
+
|
|
482
|
+
// ===========================================================================
|
|
483
|
+
// H6: kubectl pods output formatting
|
|
484
|
+
// ===========================================================================
|
|
485
|
+
|
|
486
|
+
describe('H6 — formatKubectlPodsOutput', () => {
|
|
487
|
+
const sampleOutput = [
|
|
488
|
+
'NAME READY STATUS RESTARTS AGE',
|
|
489
|
+
'web-7d4f6b9c5-abc12 1/1 Running 0 2d',
|
|
490
|
+
'worker-6b5c8d7f4-xyz99 0/1 CrashLoopBackOff 5 1h',
|
|
491
|
+
'db-5f4e3d2c1-pqr55 0/1 Pending 0 30m',
|
|
492
|
+
'init-job-abc 0/1 Init:0/1 0 5m',
|
|
493
|
+
'old-pod-done 0/1 Completed 0 7d',
|
|
494
|
+
'failing-app-xyz 0/1 Error 3 2h',
|
|
495
|
+
'evicted-pod 0/1 Evicted 0 1d',
|
|
496
|
+
].join('\n');
|
|
497
|
+
|
|
498
|
+
it('prefixes Running pods with [OK]', () => {
|
|
499
|
+
const result = formatKubectlPodsOutput(sampleOutput);
|
|
500
|
+
expect(result).toContain('[OK]');
|
|
501
|
+
const lines = result.split('\n');
|
|
502
|
+
const runningLine = lines.find(l => l.includes('Running'));
|
|
503
|
+
expect(runningLine).toMatch(/^\[OK\]/);
|
|
504
|
+
});
|
|
505
|
+
|
|
506
|
+
it('prefixes CrashLoopBackOff pods with [XX]', () => {
|
|
507
|
+
const result = formatKubectlPodsOutput(sampleOutput);
|
|
508
|
+
const lines = result.split('\n');
|
|
509
|
+
const crashLine = lines.find(l => l.includes('CrashLoopBackOff'));
|
|
510
|
+
expect(crashLine).toMatch(/^\[XX\]/);
|
|
511
|
+
});
|
|
512
|
+
|
|
513
|
+
it('prefixes Pending pods with [!!]', () => {
|
|
514
|
+
const result = formatKubectlPodsOutput(sampleOutput);
|
|
515
|
+
const lines = result.split('\n');
|
|
516
|
+
const pendingLine = lines.find(l => l.includes('Pending'));
|
|
517
|
+
expect(pendingLine).toMatch(/^\[!!\]/);
|
|
518
|
+
});
|
|
519
|
+
|
|
520
|
+
it('prefixes Error pods with [XX]', () => {
|
|
521
|
+
const result = formatKubectlPodsOutput(sampleOutput);
|
|
522
|
+
const lines = result.split('\n');
|
|
523
|
+
const errorLine = lines.find(l => l.includes('Error') && !l.includes('CrashLoop'));
|
|
524
|
+
expect(errorLine).toMatch(/^\[XX\]/);
|
|
525
|
+
});
|
|
526
|
+
|
|
527
|
+
it('prefixes Completed pods with [OK]', () => {
|
|
528
|
+
const result = formatKubectlPodsOutput(sampleOutput);
|
|
529
|
+
const lines = result.split('\n');
|
|
530
|
+
const completedLine = lines.find(l => l.includes('Completed'));
|
|
531
|
+
expect(completedLine).toMatch(/^\[OK\]/);
|
|
532
|
+
});
|
|
533
|
+
|
|
534
|
+
it('preserves header line without emoji prefix', () => {
|
|
535
|
+
const result = formatKubectlPodsOutput(sampleOutput);
|
|
536
|
+
const headerLine = result.split('\n')[0];
|
|
537
|
+
expect(headerLine).toBe('NAME READY STATUS RESTARTS AGE');
|
|
538
|
+
});
|
|
539
|
+
|
|
540
|
+
it('returns original line for empty input', () => {
|
|
541
|
+
const result = formatKubectlPodsOutput('');
|
|
542
|
+
expect(result).toBe('');
|
|
543
|
+
});
|
|
544
|
+
|
|
545
|
+
it('handles single Running pod', () => {
|
|
546
|
+
const input = 'NAME READY STATUS RESTARTS AGE\nmypod 1/1 Running 0 1m';
|
|
547
|
+
const result = formatKubectlPodsOutput(input);
|
|
548
|
+
const lines = result.split('\n');
|
|
549
|
+
expect(lines[1]).toMatch(/^\[OK\]/);
|
|
550
|
+
});
|
|
551
|
+
});
|
|
552
|
+
|
|
553
|
+
// ===========================================================================
|
|
554
|
+
// H6: helm list output formatting
|
|
555
|
+
// ===========================================================================
|
|
556
|
+
|
|
557
|
+
describe('H6 — formatHelmListOutput', () => {
|
|
558
|
+
const sampleJson = JSON.stringify([
|
|
559
|
+
{ name: 'nginx', namespace: 'default', revision: '3', status: 'deployed', chart: 'nginx-1.2.0', app_version: '1.21', updated: '2024-01-01' },
|
|
560
|
+
{ name: 'redis', namespace: 'cache', revision: '1', status: 'failed', chart: 'redis-7.0.0', app_version: '7.0', updated: '2024-01-02' },
|
|
561
|
+
{ name: 'postgres', namespace: 'db', revision: '2', status: 'pending-upgrade', chart: 'postgresql-12.1.0', app_version: '15', updated: '2024-01-03' },
|
|
562
|
+
]);
|
|
563
|
+
|
|
564
|
+
it('prefixes deployed releases with [OK]', () => {
|
|
565
|
+
const result = formatHelmListOutput(sampleJson);
|
|
566
|
+
expect(result).toContain('[OK]');
|
|
567
|
+
const lines = result.split('\n');
|
|
568
|
+
const deployedLine = lines.find(l => l.includes('deployed'));
|
|
569
|
+
expect(deployedLine).toMatch(/^\[OK\]/);
|
|
570
|
+
});
|
|
571
|
+
|
|
572
|
+
it('prefixes failed releases with [XX]', () => {
|
|
573
|
+
const result = formatHelmListOutput(sampleJson);
|
|
574
|
+
const lines = result.split('\n');
|
|
575
|
+
const failedLine = lines.find(l => l.includes('failed'));
|
|
576
|
+
expect(failedLine).toMatch(/^\[XX\]/);
|
|
577
|
+
});
|
|
578
|
+
|
|
579
|
+
it('prefixes pending releases with [!!]', () => {
|
|
580
|
+
const result = formatHelmListOutput(sampleJson);
|
|
581
|
+
const lines = result.split('\n');
|
|
582
|
+
const pendingLine = lines.find(l => l.includes('pending'));
|
|
583
|
+
expect(pendingLine).toMatch(/^\[!!\]/);
|
|
584
|
+
});
|
|
585
|
+
|
|
586
|
+
it('returns "No Helm releases found." for empty array', () => {
|
|
587
|
+
const result = formatHelmListOutput('[]');
|
|
588
|
+
expect(result).toBe('No Helm releases found.');
|
|
589
|
+
});
|
|
590
|
+
|
|
591
|
+
it('falls back to raw string for invalid JSON', () => {
|
|
592
|
+
const result = formatHelmListOutput('not json');
|
|
593
|
+
expect(result).toBe('not json');
|
|
594
|
+
});
|
|
595
|
+
|
|
596
|
+
it('includes release name and namespace in output', () => {
|
|
597
|
+
const result = formatHelmListOutput(sampleJson);
|
|
598
|
+
expect(result).toContain('nginx');
|
|
599
|
+
expect(result).toContain('default');
|
|
600
|
+
});
|
|
601
|
+
});
|
|
602
|
+
|
|
@@ -9,13 +9,14 @@
|
|
|
9
9
|
* tests fast and hermetic.
|
|
10
10
|
*/
|
|
11
11
|
|
|
12
|
-
import { describe, it, expect } from '
|
|
13
|
-
import {
|
|
12
|
+
import { describe, it, expect } from 'vitest';
|
|
13
|
+
import { fileURLToPath } from 'node:url';
|
|
14
|
+
import { join, dirname } from 'node:path';
|
|
14
15
|
import { FileSystemOperations } from '../tools/file-ops';
|
|
15
16
|
import { GitOperations } from '../tools/git-ops';
|
|
16
17
|
|
|
17
18
|
// The repository root is two levels above this test file: src/__tests__/ -> src/ -> repo-root
|
|
18
|
-
const REPO_ROOT = join(import.meta.
|
|
19
|
+
const REPO_ROOT = join(dirname(fileURLToPath(import.meta.url)), '..', '..');
|
|
19
20
|
|
|
20
21
|
// ---------------------------------------------------------------------------
|
|
21
22
|
// FileSystemOperations
|
|
@@ -0,0 +1,184 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Version Command Tests — L3
|
|
3
|
+
*
|
|
4
|
+
* Validates that `nimbus version --json` returns valid JSON with
|
|
5
|
+
* required fields: version, node, platform, arch.
|
|
6
|
+
*
|
|
7
|
+
* Also validates that `nimbus update` is an alias for `upgrade` in cli.ts.
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
import { describe, test, expect, vi, beforeEach, afterEach } from 'vitest';
|
|
11
|
+
|
|
12
|
+
// ---------------------------------------------------------------------------
|
|
13
|
+
// L3: version --json
|
|
14
|
+
// ---------------------------------------------------------------------------
|
|
15
|
+
|
|
16
|
+
describe('versionCommand --json (L3)', () => {
|
|
17
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
18
|
+
let consoleSpy: any;
|
|
19
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
20
|
+
let exitSpy: any;
|
|
21
|
+
|
|
22
|
+
beforeEach(() => {
|
|
23
|
+
consoleSpy = vi.spyOn(console, 'log').mockImplementation(() => {});
|
|
24
|
+
exitSpy = vi.spyOn(process, 'exit').mockImplementation((() => {}) as never);
|
|
25
|
+
});
|
|
26
|
+
|
|
27
|
+
afterEach(() => {
|
|
28
|
+
vi.restoreAllMocks();
|
|
29
|
+
});
|
|
30
|
+
|
|
31
|
+
test('outputs valid JSON to stdout when json option is true', async () => {
|
|
32
|
+
const { versionCommand } = await import('../commands/version');
|
|
33
|
+
await versionCommand({ json: true });
|
|
34
|
+
|
|
35
|
+
// process.exit(0) should have been called
|
|
36
|
+
expect(exitSpy).toHaveBeenCalledWith(0);
|
|
37
|
+
|
|
38
|
+
// console.log should have been called with JSON
|
|
39
|
+
expect(consoleSpy).toHaveBeenCalled();
|
|
40
|
+
|
|
41
|
+
const jsonArg = consoleSpy.mock.calls[0][0] as string;
|
|
42
|
+
expect(() => JSON.parse(jsonArg)).not.toThrow();
|
|
43
|
+
});
|
|
44
|
+
|
|
45
|
+
test('JSON output contains required fields: version, node, platform, arch', async () => {
|
|
46
|
+
const { versionCommand } = await import('../commands/version');
|
|
47
|
+
await versionCommand({ json: true });
|
|
48
|
+
|
|
49
|
+
const jsonArg = consoleSpy.mock.calls[0][0] as string;
|
|
50
|
+
const parsed = JSON.parse(jsonArg);
|
|
51
|
+
|
|
52
|
+
expect(parsed).toHaveProperty('version');
|
|
53
|
+
expect(parsed).toHaveProperty('node');
|
|
54
|
+
expect(parsed).toHaveProperty('platform');
|
|
55
|
+
expect(parsed).toHaveProperty('arch');
|
|
56
|
+
});
|
|
57
|
+
|
|
58
|
+
test('JSON version field is a non-empty string', async () => {
|
|
59
|
+
const { versionCommand } = await import('../commands/version');
|
|
60
|
+
await versionCommand({ json: true });
|
|
61
|
+
|
|
62
|
+
const jsonArg = consoleSpy.mock.calls[0][0] as string;
|
|
63
|
+
const parsed = JSON.parse(jsonArg);
|
|
64
|
+
|
|
65
|
+
expect(typeof parsed.version).toBe('string');
|
|
66
|
+
expect(parsed.version.length).toBeGreaterThan(0);
|
|
67
|
+
});
|
|
68
|
+
|
|
69
|
+
test('JSON node field matches process.version', async () => {
|
|
70
|
+
const { versionCommand } = await import('../commands/version');
|
|
71
|
+
await versionCommand({ json: true });
|
|
72
|
+
|
|
73
|
+
const jsonArg = consoleSpy.mock.calls[0][0] as string;
|
|
74
|
+
const parsed = JSON.parse(jsonArg);
|
|
75
|
+
|
|
76
|
+
expect(parsed.node).toBe(process.version);
|
|
77
|
+
});
|
|
78
|
+
|
|
79
|
+
test('JSON platform field matches process.platform', async () => {
|
|
80
|
+
const { versionCommand } = await import('../commands/version');
|
|
81
|
+
await versionCommand({ json: true });
|
|
82
|
+
|
|
83
|
+
const jsonArg = consoleSpy.mock.calls[0][0] as string;
|
|
84
|
+
const parsed = JSON.parse(jsonArg);
|
|
85
|
+
|
|
86
|
+
expect(parsed.platform).toBe(process.platform);
|
|
87
|
+
});
|
|
88
|
+
|
|
89
|
+
test('JSON arch field matches process.arch', async () => {
|
|
90
|
+
const { versionCommand } = await import('../commands/version');
|
|
91
|
+
await versionCommand({ json: true });
|
|
92
|
+
|
|
93
|
+
const jsonArg = consoleSpy.mock.calls[0][0] as string;
|
|
94
|
+
const parsed = JSON.parse(jsonArg);
|
|
95
|
+
|
|
96
|
+
expect(parsed.arch).toBe(process.arch);
|
|
97
|
+
});
|
|
98
|
+
|
|
99
|
+
test('without --json flag, does not call process.exit(0)', async () => {
|
|
100
|
+
const { versionCommand } = await import('../commands/version');
|
|
101
|
+
await versionCommand({ json: false });
|
|
102
|
+
|
|
103
|
+
expect(exitSpy).not.toHaveBeenCalledWith(0);
|
|
104
|
+
});
|
|
105
|
+
});
|
|
106
|
+
|
|
107
|
+
// ---------------------------------------------------------------------------
|
|
108
|
+
// L1: `nimbus update` alias for `upgrade`
|
|
109
|
+
// ---------------------------------------------------------------------------
|
|
110
|
+
|
|
111
|
+
describe('nimbus update alias (L1)', () => {
|
|
112
|
+
test('runCommand treats "update" the same as "upgrade"', async () => {
|
|
113
|
+
// Verify that both 'upgrade' and 'update' route to the upgrade handler
|
|
114
|
+
// by checking the COMMAND_ALIASES or the if-condition in cli.ts.
|
|
115
|
+
// We test this by importing runCommand and stubbing upgradeCommand.
|
|
116
|
+
|
|
117
|
+
const upgradeMod = await import('../commands/upgrade');
|
|
118
|
+
const upgradeSpy = vi.spyOn(upgradeMod, 'upgradeCommand').mockResolvedValue(undefined);
|
|
119
|
+
|
|
120
|
+
const { runCommand } = await import('../cli');
|
|
121
|
+
await runCommand(['update', '--check']);
|
|
122
|
+
|
|
123
|
+
expect(upgradeSpy).toHaveBeenCalledWith(expect.objectContaining({ check: true }));
|
|
124
|
+
|
|
125
|
+
upgradeSpy.mockRestore();
|
|
126
|
+
});
|
|
127
|
+
|
|
128
|
+
test('runCommand treats "upgrade" the same as "update"', async () => {
|
|
129
|
+
const upgradeMod = await import('../commands/upgrade');
|
|
130
|
+
const upgradeSpy = vi.spyOn(upgradeMod, 'upgradeCommand').mockResolvedValue(undefined);
|
|
131
|
+
|
|
132
|
+
const { runCommand } = await import('../cli');
|
|
133
|
+
await runCommand(['upgrade', '--check']);
|
|
134
|
+
|
|
135
|
+
expect(upgradeSpy).toHaveBeenCalledWith(expect.objectContaining({ check: true }));
|
|
136
|
+
|
|
137
|
+
upgradeSpy.mockRestore();
|
|
138
|
+
});
|
|
139
|
+
});
|
|
140
|
+
|
|
141
|
+
// ---------------------------------------------------------------------------
|
|
142
|
+
// L2: --quiet flag for doctor
|
|
143
|
+
// ---------------------------------------------------------------------------
|
|
144
|
+
|
|
145
|
+
describe('doctorCommand --quiet (L2)', () => {
|
|
146
|
+
test('DoctorOptions interface accepts quiet field', async () => {
|
|
147
|
+
const { doctorCommand } = await import('../commands/doctor');
|
|
148
|
+
// Just verifying the function can be called with quiet option without type errors
|
|
149
|
+
expect(typeof doctorCommand).toBe('function');
|
|
150
|
+
});
|
|
151
|
+
});
|
|
152
|
+
|
|
153
|
+
// ---------------------------------------------------------------------------
|
|
154
|
+
// L2: --quiet flag for logs
|
|
155
|
+
// ---------------------------------------------------------------------------
|
|
156
|
+
|
|
157
|
+
describe('parseLogsArgs --quiet (L2)', () => {
|
|
158
|
+
test('parses -q as quiet option', async () => {
|
|
159
|
+
const { parseLogsArgs } = await import('../commands/logs');
|
|
160
|
+
const { options } = parseLogsArgs(['my-pod', '-q']);
|
|
161
|
+
expect(options.quiet).toBe(true);
|
|
162
|
+
});
|
|
163
|
+
|
|
164
|
+
test('parses --quiet as quiet option', async () => {
|
|
165
|
+
const { parseLogsArgs } = await import('../commands/logs');
|
|
166
|
+
const { options } = parseLogsArgs(['my-pod', '--quiet']);
|
|
167
|
+
expect(options.quiet).toBe(true);
|
|
168
|
+
});
|
|
169
|
+
|
|
170
|
+
test('quiet is false when not provided', async () => {
|
|
171
|
+
const { parseLogsArgs } = await import('../commands/logs');
|
|
172
|
+
const { options } = parseLogsArgs(['my-pod']);
|
|
173
|
+
expect(options.quiet).toBeUndefined();
|
|
174
|
+
});
|
|
175
|
+
|
|
176
|
+
test('parses --quiet along with other flags', async () => {
|
|
177
|
+
const { parseLogsArgs } = await import('../commands/logs');
|
|
178
|
+
const { pod, options } = parseLogsArgs(['my-pod', '-n', 'default', '--quiet', '-f']);
|
|
179
|
+
expect(pod).toBe('my-pod');
|
|
180
|
+
expect(options.namespace).toBe('default');
|
|
181
|
+
expect(options.quiet).toBe(true);
|
|
182
|
+
expect(options.follow).toBe(true);
|
|
183
|
+
});
|
|
184
|
+
});
|
|
@@ -0,0 +1,129 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Watch Command Tests (M3)
|
|
3
|
+
*
|
|
4
|
+
* Tests the nimbus watch command which watches files and triggers agent runs.
|
|
5
|
+
* Validates glob matching, option parsing, and command export.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import { describe, it, expect } from 'vitest';
|
|
9
|
+
import { readFileSync } from 'node:fs';
|
|
10
|
+
import { join } from 'node:path';
|
|
11
|
+
|
|
12
|
+
// ---------------------------------------------------------------------------
|
|
13
|
+
// Import and verify exports
|
|
14
|
+
// ---------------------------------------------------------------------------
|
|
15
|
+
|
|
16
|
+
describe('watchCommand exports', () => {
|
|
17
|
+
it('watchCommand is exported from commands/watch.ts', async () => {
|
|
18
|
+
const { watchCommand } = await import('../commands/watch');
|
|
19
|
+
expect(typeof watchCommand).toBe('function');
|
|
20
|
+
});
|
|
21
|
+
|
|
22
|
+
it('WatchOptions interface supports required fields', () => {
|
|
23
|
+
// Type-level check via source inspection
|
|
24
|
+
const src = readFileSync(join(process.cwd(), 'src/commands/watch.ts'), 'utf-8');
|
|
25
|
+
expect(src).toContain('glob: string');
|
|
26
|
+
expect(src).toContain('run?:');
|
|
27
|
+
expect(src).toContain('debounce?:');
|
|
28
|
+
expect(src).toContain('autoApprove?:');
|
|
29
|
+
expect(src).toContain('maxRuns?:');
|
|
30
|
+
});
|
|
31
|
+
});
|
|
32
|
+
|
|
33
|
+
// ---------------------------------------------------------------------------
|
|
34
|
+
// Glob matching logic (inline reproduction)
|
|
35
|
+
// ---------------------------------------------------------------------------
|
|
36
|
+
|
|
37
|
+
function matchGlob(filename: string, pattern: string): boolean {
|
|
38
|
+
const f = filename.replace(/\\/g, '/');
|
|
39
|
+
const p = pattern.replace(/\\/g, '/');
|
|
40
|
+
|
|
41
|
+
const regexStr = p
|
|
42
|
+
.replace(/[.+^${}()|[\]\\]/g, '\\$&')
|
|
43
|
+
.replace(/\*\*/g, '__GLOBSTAR__')
|
|
44
|
+
.replace(/\*/g, '[^/]*')
|
|
45
|
+
.replace(/\?/g, '[^/]')
|
|
46
|
+
.replace(/__GLOBSTAR__/g, '.*');
|
|
47
|
+
|
|
48
|
+
const re = new RegExp(`^${regexStr}$`);
|
|
49
|
+
const { basename } = require('node:path');
|
|
50
|
+
const base = basename(f);
|
|
51
|
+
return re.test(f) || (!/\//.test(p) && re.test(base));
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
describe('matchGlob (M3 — watch command glob matching)', () => {
|
|
55
|
+
it('matches *.tf against main.tf', () => {
|
|
56
|
+
expect(matchGlob('main.tf', '*.tf')).toBe(true);
|
|
57
|
+
});
|
|
58
|
+
|
|
59
|
+
it('matches *.tf against variables.tf', () => {
|
|
60
|
+
expect(matchGlob('variables.tf', '*.tf')).toBe(true);
|
|
61
|
+
});
|
|
62
|
+
|
|
63
|
+
it('does not match *.tf against main.ts', () => {
|
|
64
|
+
expect(matchGlob('main.ts', '*.tf')).toBe(false);
|
|
65
|
+
});
|
|
66
|
+
|
|
67
|
+
it('matches *.yaml against deploy.yaml', () => {
|
|
68
|
+
expect(matchGlob('deploy.yaml', '*.yaml')).toBe(true);
|
|
69
|
+
});
|
|
70
|
+
|
|
71
|
+
it('matches *.yaml against deploy.yml', () => {
|
|
72
|
+
expect(matchGlob('deploy.yml', '*.yaml')).toBe(false);
|
|
73
|
+
});
|
|
74
|
+
|
|
75
|
+
it('matches *.yml against deploy.yml', () => {
|
|
76
|
+
expect(matchGlob('deploy.yml', '*.yml')).toBe(true);
|
|
77
|
+
});
|
|
78
|
+
|
|
79
|
+
it('matches src/** against src/index.ts', () => {
|
|
80
|
+
expect(matchGlob('src/index.ts', 'src/**')).toBe(true);
|
|
81
|
+
});
|
|
82
|
+
|
|
83
|
+
it('matches src/** against src/utils/helper.ts', () => {
|
|
84
|
+
expect(matchGlob('src/utils/helper.ts', 'src/**')).toBe(true);
|
|
85
|
+
});
|
|
86
|
+
|
|
87
|
+
it('does not match src/** against lib/index.ts', () => {
|
|
88
|
+
expect(matchGlob('lib/index.ts', 'src/**')).toBe(false);
|
|
89
|
+
});
|
|
90
|
+
|
|
91
|
+
it('matches Dockerfile exactly', () => {
|
|
92
|
+
expect(matchGlob('Dockerfile', 'Dockerfile')).toBe(true);
|
|
93
|
+
});
|
|
94
|
+
|
|
95
|
+
it('matches nested *.tf in subdir', () => {
|
|
96
|
+
expect(matchGlob('modules/vpc/main.tf', '*.tf')).toBe(true);
|
|
97
|
+
});
|
|
98
|
+
});
|
|
99
|
+
|
|
100
|
+
// ---------------------------------------------------------------------------
|
|
101
|
+
// CLI wiring — watch is registered in cli.ts
|
|
102
|
+
// ---------------------------------------------------------------------------
|
|
103
|
+
|
|
104
|
+
describe('watch command wired in CLI (M3)', () => {
|
|
105
|
+
it('cli.ts contains nimbus watch handler', () => {
|
|
106
|
+
const src = readFileSync(join(process.cwd(), 'src/cli.ts'), 'utf-8');
|
|
107
|
+
expect(src).toContain("command === 'watch'");
|
|
108
|
+
});
|
|
109
|
+
|
|
110
|
+
it('cli.ts imports watchCommand from commands/watch', () => {
|
|
111
|
+
const src = readFileSync(join(process.cwd(), 'src/cli.ts'), 'utf-8');
|
|
112
|
+
expect(src).toContain("import('./commands/watch')");
|
|
113
|
+
});
|
|
114
|
+
|
|
115
|
+
it('cli.ts parses --run flag for watch', () => {
|
|
116
|
+
const src = readFileSync(join(process.cwd(), 'src/cli.ts'), 'utf-8');
|
|
117
|
+
expect(src).toContain("watchOptions.run = args[++i]");
|
|
118
|
+
});
|
|
119
|
+
|
|
120
|
+
it('cli.ts parses --debounce flag for watch', () => {
|
|
121
|
+
const src = readFileSync(join(process.cwd(), 'src/cli.ts'), 'utf-8');
|
|
122
|
+
expect(src).toContain("watchOptions.debounce");
|
|
123
|
+
});
|
|
124
|
+
|
|
125
|
+
it('cli.ts parses --auto-approve flag for watch', () => {
|
|
126
|
+
const src = readFileSync(join(process.cwd(), 'src/cli.ts'), 'utf-8');
|
|
127
|
+
expect(src).toContain("watchOptions.autoApprove = true");
|
|
128
|
+
});
|
|
129
|
+
});
|
|
@@ -33,6 +33,15 @@ export interface CompactionOptions {
|
|
|
33
33
|
focusArea?: string;
|
|
34
34
|
/** Model to use for compaction (default: haiku). */
|
|
35
35
|
model?: string;
|
|
36
|
+
/** H3: Infrastructure context to preserve during compaction. */
|
|
37
|
+
infraContext?: {
|
|
38
|
+
terraformWorkspace?: string;
|
|
39
|
+
kubectlContext?: string;
|
|
40
|
+
awsProfile?: string;
|
|
41
|
+
awsRegion?: string;
|
|
42
|
+
gcpProject?: string;
|
|
43
|
+
azureSubscription?: string;
|
|
44
|
+
};
|
|
36
45
|
}
|
|
37
46
|
|
|
38
47
|
// ---------------------------------------------------------------------------
|
|
@@ -107,6 +116,21 @@ export async function runCompaction(
|
|
|
107
116
|
userPrompt += `\n\nPay special attention to: ${options.focusArea}`;
|
|
108
117
|
}
|
|
109
118
|
|
|
119
|
+
// H3: Inject current infraContext into compaction prompt so it's never omitted
|
|
120
|
+
if (options.infraContext) {
|
|
121
|
+
const ic = options.infraContext;
|
|
122
|
+
const infraLines: string[] = [];
|
|
123
|
+
if (ic.terraformWorkspace) infraLines.push(`- Terraform workspace: ${ic.terraformWorkspace}`);
|
|
124
|
+
if (ic.kubectlContext) infraLines.push(`- kubectl context: ${ic.kubectlContext}`);
|
|
125
|
+
if (ic.awsProfile) infraLines.push(`- AWS profile: ${ic.awsProfile}`);
|
|
126
|
+
if (ic.awsRegion) infraLines.push(`- AWS region: ${ic.awsRegion}`);
|
|
127
|
+
if (ic.gcpProject) infraLines.push(`- GCP project: ${ic.gcpProject}`);
|
|
128
|
+
if (ic.azureSubscription) infraLines.push(`- Azure subscription: ${ic.azureSubscription}`);
|
|
129
|
+
if (infraLines.length > 0) {
|
|
130
|
+
userPrompt += `\n\n## ALWAYS PRESERVE IN SUMMARY (do not omit):\n${infraLines.join('\n')}`;
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
|
|
110
134
|
// Call the LLM for summarization using a fast, cheap model
|
|
111
135
|
const model = options.model ?? 'haiku';
|
|
112
136
|
let summary: string;
|
|
@@ -126,8 +150,23 @@ export async function runCompaction(
|
|
|
126
150
|
summary = fallbackSummary(toSummarize);
|
|
127
151
|
}
|
|
128
152
|
|
|
153
|
+
// H3: Prepend infraContext block to summary so it survives compaction
|
|
154
|
+
let finalSummary = summary;
|
|
155
|
+
if (options.infraContext) {
|
|
156
|
+
const ic = options.infraContext;
|
|
157
|
+
const infraLines: string[] = [];
|
|
158
|
+
if (ic.terraformWorkspace) infraLines.push(`- Terraform workspace: ${ic.terraformWorkspace}`);
|
|
159
|
+
if (ic.kubectlContext) infraLines.push(`- kubectl context: ${ic.kubectlContext}`);
|
|
160
|
+
if (ic.awsProfile) infraLines.push(`- AWS profile: ${ic.awsProfile}`);
|
|
161
|
+
if (ic.awsRegion) infraLines.push(`- AWS region: ${ic.awsRegion}`);
|
|
162
|
+
if (ic.gcpProject) infraLines.push(`- GCP project: ${ic.gcpProject}`);
|
|
163
|
+
if (ic.azureSubscription) infraLines.push(`- Azure subscription: ${ic.azureSubscription}`);
|
|
164
|
+
if (infraLines.length > 0) {
|
|
165
|
+
finalSummary = `## Infrastructure Context\n${infraLines.join('\n')}\n\n${summary}`;
|
|
166
|
+
}
|
|
167
|
+
}
|
|
129
168
|
// Reassemble the compacted message array
|
|
130
|
-
const compactedMessages = contextManager.buildCompactedMessages(preserved,
|
|
169
|
+
const compactedMessages = contextManager.buildCompactedMessages(preserved, finalSummary);
|
|
131
170
|
const compactedTokens = compactedMessages.reduce(
|
|
132
171
|
(sum, m) => sum + estimateTokens(getTextContent(m.content)),
|
|
133
172
|
0
|