@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
|
@@ -41,6 +41,14 @@ const RISK_COLORS: Record<RiskLevel, string> = {
|
|
|
41
41
|
critical: 'magenta',
|
|
42
42
|
};
|
|
43
43
|
|
|
44
|
+
/** H4: Human-readable descriptions of each risk tier. */
|
|
45
|
+
const RISK_DESCRIPTIONS: Record<RiskLevel, string> = {
|
|
46
|
+
low: 'Read-only — safe to auto-approve',
|
|
47
|
+
medium: 'Modifies local files — review before approving',
|
|
48
|
+
high: 'System or cloud operation — approve with caution',
|
|
49
|
+
critical: 'Destructive or irreversible — explicit confirmation required',
|
|
50
|
+
};
|
|
51
|
+
|
|
44
52
|
/**
|
|
45
53
|
* Format tool input into a list of truncated key=value lines.
|
|
46
54
|
*/
|
|
@@ -112,12 +120,16 @@ export function PermissionPrompt({
|
|
|
112
120
|
</Box>
|
|
113
121
|
|
|
114
122
|
{/* Risk level */}
|
|
115
|
-
<Box
|
|
123
|
+
<Box>
|
|
116
124
|
<Text dimColor>Risk: </Text>
|
|
117
125
|
<Text bold color={riskColor}>
|
|
118
126
|
{riskLevel.toUpperCase()}
|
|
119
127
|
</Text>
|
|
120
128
|
</Box>
|
|
129
|
+
{/* H4: Risk description */}
|
|
130
|
+
<Box marginBottom={1}>
|
|
131
|
+
<Text dimColor>{RISK_DESCRIPTIONS[riskLevel]}</Text>
|
|
132
|
+
</Box>
|
|
121
133
|
|
|
122
134
|
{/* Parameters */}
|
|
123
135
|
<Box flexDirection="column" marginBottom={1}>
|
|
@@ -132,19 +144,19 @@ export function PermissionPrompt({
|
|
|
132
144
|
<Text color="green" bold inverse={pressed === 'a'}>
|
|
133
145
|
[a]
|
|
134
146
|
</Text>
|
|
135
|
-
<Text
|
|
147
|
+
<Text>=approve </Text>
|
|
136
148
|
<Text color="red" bold inverse={pressed === 'r'}>
|
|
137
149
|
[r]
|
|
138
150
|
</Text>
|
|
139
|
-
<Text
|
|
151
|
+
<Text>=reject </Text>
|
|
140
152
|
<Text color="cyan" bold inverse={pressed === 'A'}>
|
|
141
153
|
[A]
|
|
142
154
|
</Text>
|
|
143
|
-
<Text
|
|
155
|
+
<Text>=approve-all-session </Text>
|
|
144
156
|
<Text color="blue" bold inverse={pressed === 's'}>
|
|
145
157
|
[s]
|
|
146
158
|
</Text>
|
|
147
|
-
<Text
|
|
159
|
+
<Text>=approve-this-tool-session</Text>
|
|
148
160
|
</Box>
|
|
149
161
|
</Box>
|
|
150
162
|
);
|
package/src/ui/StatusBar.tsx
CHANGED
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
* percentage bar, estimated cost, snapshot count, and elapsed processing time.
|
|
6
6
|
*
|
|
7
7
|
* Layout:
|
|
8
|
-
* [Plan] [Build] [Deploy] | Tokens: 45.2k/200k (22%) | Cost: $0.03 | Snapshots: 3 | 12s
|
|
8
|
+
* [Plan] [Build] [Deploy] | Tokens: 45.2k/200k (22%) | Cost: $0.03 | Snapshots: 3 (undo) | tf:default | k8s:prod-cluster | delta:+$1.20 | 12s
|
|
9
9
|
*
|
|
10
10
|
* Token percentage colour:
|
|
11
11
|
* green < 50%
|
|
@@ -24,6 +24,20 @@ export interface StatusBarProps {
|
|
|
24
24
|
isProcessing?: boolean;
|
|
25
25
|
/** Timestamp (Date.now()) when processing started. Null when idle. */
|
|
26
26
|
processingStartTime?: number | null;
|
|
27
|
+
/** Number of lines in the current input (Gap 9 — multi-line indicator). */
|
|
28
|
+
inputLineCount?: number;
|
|
29
|
+
/** C1: Show scroll hint when the user has scrolled away from the bottom. */
|
|
30
|
+
showScrollHint?: boolean;
|
|
31
|
+
/** H1: Toast message shown after copying a code block to clipboard. */
|
|
32
|
+
copyToast?: string;
|
|
33
|
+
/** Show "Esc to stop" hint when a log stream is active. */
|
|
34
|
+
showStreamingHint?: boolean;
|
|
35
|
+
/** H5: Mode change toast — shown for 2 seconds after Tab cycle. */
|
|
36
|
+
modeToast?: string;
|
|
37
|
+
/** M1: Active search query (shows "Search: N results" when set). */
|
|
38
|
+
searchQuery?: string;
|
|
39
|
+
/** M1: Number of messages matching the current search. */
|
|
40
|
+
searchResultCount?: number;
|
|
27
41
|
}
|
|
28
42
|
|
|
29
43
|
/** All modes in display order. */
|
|
@@ -55,6 +69,13 @@ function tokenColor(pct: number): string {
|
|
|
55
69
|
return 'green';
|
|
56
70
|
}
|
|
57
71
|
|
|
72
|
+
/**
|
|
73
|
+
* Determine if an environment name is production-like (G1).
|
|
74
|
+
*/
|
|
75
|
+
function isProdEnvironment(name: string): boolean {
|
|
76
|
+
return /prod|production|live/i.test(name);
|
|
77
|
+
}
|
|
78
|
+
|
|
58
79
|
/**
|
|
59
80
|
* A single mode badge. The active mode is rendered with inverse styling.
|
|
60
81
|
*/
|
|
@@ -79,6 +100,13 @@ export function StatusBar({
|
|
|
79
100
|
session,
|
|
80
101
|
isProcessing = false,
|
|
81
102
|
processingStartTime = null,
|
|
103
|
+
inputLineCount = 1,
|
|
104
|
+
showScrollHint = false,
|
|
105
|
+
copyToast = '',
|
|
106
|
+
showStreamingHint = false,
|
|
107
|
+
modeToast,
|
|
108
|
+
searchQuery,
|
|
109
|
+
searchResultCount,
|
|
82
110
|
}: StatusBarProps) {
|
|
83
111
|
const pct =
|
|
84
112
|
session.maxTokens > 0 ? Math.round((session.tokenCount / session.maxTokens) * 100) : 0;
|
|
@@ -115,6 +143,24 @@ export function StatusBar({
|
|
|
115
143
|
}
|
|
116
144
|
}, [isProcessing, processingStartTime]);
|
|
117
145
|
|
|
146
|
+
// Snapshot display: show undo hint when snapshots exist (G19)
|
|
147
|
+
const snapshotDisplay = session.snapshotCount > 0
|
|
148
|
+
? `${session.snapshotCount} (↶ undo)`
|
|
149
|
+
: String(session.snapshotCount);
|
|
150
|
+
|
|
151
|
+
// Infra context colors (G1)
|
|
152
|
+
const tfColor = session.terraformWorkspace && isProdEnvironment(session.terraformWorkspace)
|
|
153
|
+
? 'yellow'
|
|
154
|
+
: 'green';
|
|
155
|
+
const k8sColor = session.kubectlContext && isProdEnvironment(session.kubectlContext)
|
|
156
|
+
? 'yellow'
|
|
157
|
+
: 'green';
|
|
158
|
+
|
|
159
|
+
// L5: Build a compact visual progress bar for context budget
|
|
160
|
+
const BAR_WIDTH = 8;
|
|
161
|
+
const filledBars = Math.round((pct / 100) * BAR_WIDTH);
|
|
162
|
+
const progressBar = '█'.repeat(filledBars) + '░'.repeat(BAR_WIDTH - filledBars);
|
|
163
|
+
|
|
118
164
|
return (
|
|
119
165
|
<Box
|
|
120
166
|
borderStyle="single"
|
|
@@ -132,26 +178,99 @@ export function StatusBar({
|
|
|
132
178
|
|
|
133
179
|
{/* Metrics + keyboard hints */}
|
|
134
180
|
<Box>
|
|
135
|
-
<Text dimColor>
|
|
181
|
+
<Text dimColor>Ctx: </Text>
|
|
182
|
+
<Text color={pctColor}>{progressBar}</Text>
|
|
183
|
+
<Text dimColor> </Text>
|
|
136
184
|
<Text color={pctColor}>
|
|
137
185
|
{formatTokens(session.tokenCount)}/{formatTokens(session.maxTokens)} ({pct}%)
|
|
138
186
|
</Text>
|
|
139
187
|
<Text dimColor> | Cost: </Text>
|
|
140
188
|
<Text>{costStr}</Text>
|
|
141
189
|
<Text dimColor> | Snapshots: </Text>
|
|
142
|
-
<Text>{
|
|
190
|
+
<Text>{snapshotDisplay}</Text>
|
|
191
|
+
{/* G1: Terraform workspace display */}
|
|
192
|
+
{session.terraformWorkspace && (
|
|
193
|
+
<>
|
|
194
|
+
<Text dimColor> | </Text>
|
|
195
|
+
<Text color={tfColor}>tf:{session.terraformWorkspace}</Text>
|
|
196
|
+
</>
|
|
197
|
+
)}
|
|
198
|
+
{/* G1: kubectl context display */}
|
|
199
|
+
{session.kubectlContext && (
|
|
200
|
+
<>
|
|
201
|
+
<Text dimColor> | </Text>
|
|
202
|
+
<Text color={k8sColor}>k8s:{session.kubectlContext}</Text>
|
|
203
|
+
</>
|
|
204
|
+
)}
|
|
205
|
+
{/* G15: Infra cost delta display */}
|
|
206
|
+
{session.infraCostDelta && (
|
|
207
|
+
<>
|
|
208
|
+
<Text dimColor> | </Text>
|
|
209
|
+
<Text color="green">delta:{session.infraCostDelta}</Text>
|
|
210
|
+
</>
|
|
211
|
+
)}
|
|
143
212
|
{isProcessing && elapsedSeconds > 0 && (
|
|
144
213
|
<>
|
|
145
214
|
<Text dimColor> | </Text>
|
|
146
215
|
<Text color="cyan">{elapsedSeconds}s</Text>
|
|
147
216
|
</>
|
|
148
217
|
)}
|
|
218
|
+
{inputLineCount > 1 && !isProcessing && (
|
|
219
|
+
<>
|
|
220
|
+
<Text dimColor> | </Text>
|
|
221
|
+
<Text color="cyan">{inputLineCount} lines</Text>
|
|
222
|
+
</>
|
|
223
|
+
)}
|
|
149
224
|
<Text dimColor> | Tab</Text>
|
|
150
225
|
<Text dimColor>:mode </Text>
|
|
151
226
|
<Text dimColor>Esc</Text>
|
|
152
227
|
<Text dimColor>:cancel </Text>
|
|
153
228
|
<Text dimColor>^C</Text>
|
|
154
229
|
<Text dimColor>:exit</Text>
|
|
230
|
+
{/* C3: Discoverability hints when idle */}
|
|
231
|
+
{!isProcessing && inputLineCount <= 1 && (
|
|
232
|
+
<>
|
|
233
|
+
<Text dimColor> | </Text>
|
|
234
|
+
<Text dimColor>F1:help /tree /terminal</Text>
|
|
235
|
+
<Text dimColor> </Text>
|
|
236
|
+
<Text dimColor>? help</Text>
|
|
237
|
+
</>
|
|
238
|
+
)}
|
|
239
|
+
{/* C1: Scroll hint when user has scrolled away from bottom */}
|
|
240
|
+
{showScrollHint && (
|
|
241
|
+
<>
|
|
242
|
+
<Text dimColor> | </Text>
|
|
243
|
+
<Text color="cyan">↑↓ scroll | G bottom</Text>
|
|
244
|
+
</>
|
|
245
|
+
)}
|
|
246
|
+
{/* H1: Streaming tool indicator — show "Esc to stop" when a log stream is active */}
|
|
247
|
+
{showStreamingHint && (
|
|
248
|
+
<>
|
|
249
|
+
<Text dimColor> | </Text>
|
|
250
|
+
<Text color="cyan">Esc:stop stream</Text>
|
|
251
|
+
</>
|
|
252
|
+
)}
|
|
253
|
+
{/* H1: Copy toast message after copying a code block */}
|
|
254
|
+
{copyToast && (
|
|
255
|
+
<>
|
|
256
|
+
<Text dimColor> | </Text>
|
|
257
|
+
<Text color="green">{copyToast}</Text>
|
|
258
|
+
</>
|
|
259
|
+
)}
|
|
260
|
+
{/* H5: Mode change toast */}
|
|
261
|
+
{modeToast && (
|
|
262
|
+
<>
|
|
263
|
+
<Text dimColor> | </Text>
|
|
264
|
+
<Text color="cyan" italic>{modeToast}</Text>
|
|
265
|
+
</>
|
|
266
|
+
)}
|
|
267
|
+
{/* M1: Search result count */}
|
|
268
|
+
{searchQuery && searchResultCount !== undefined && (
|
|
269
|
+
<>
|
|
270
|
+
<Text dimColor> | </Text>
|
|
271
|
+
<Text color="yellow">Search: "{searchQuery}" — {searchResultCount} results</Text>
|
|
272
|
+
</>
|
|
273
|
+
)}
|
|
155
274
|
</Box>
|
|
156
275
|
</Box>
|
|
157
276
|
);
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* TerminalPane Component (M1)
|
|
3
|
+
*
|
|
4
|
+
* Read-only tool output observation pane showing the last N lines from
|
|
5
|
+
* completed tool calls. Rendered alongside MessageList in a side-by-side
|
|
6
|
+
* layout when active. Toggle via /terminal slash command.
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
import React from 'react';
|
|
10
|
+
import { Box, Text } from 'ink';
|
|
11
|
+
import type { UIToolCall } from './types';
|
|
12
|
+
|
|
13
|
+
export interface TerminalPaneProps {
|
|
14
|
+
/** Tool calls to display output from. */
|
|
15
|
+
toolCalls: UIToolCall[];
|
|
16
|
+
/** Maximum number of output lines to show (default: 20). */
|
|
17
|
+
maxLines?: number;
|
|
18
|
+
/** Width percentage hint for layout (unused — parent controls width). */
|
|
19
|
+
width?: number;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* A scrollable pane showing the last tool outputs as a rolling buffer.
|
|
24
|
+
*/
|
|
25
|
+
export function TerminalPane({ toolCalls, maxLines = 20 }: TerminalPaneProps) {
|
|
26
|
+
// Collect lines from all tool calls (completed + running with streaming output)
|
|
27
|
+
const outputLines: Array<{ text: string; isError: boolean; toolName: string; live?: boolean }> = [];
|
|
28
|
+
|
|
29
|
+
for (const tc of toolCalls) {
|
|
30
|
+
if (tc.status === 'running') {
|
|
31
|
+
// Show live streaming output for in-progress tool calls (Gap 1)
|
|
32
|
+
const liveOutput = tc.streamingOutput ?? '(waiting for output...)';
|
|
33
|
+
const lines = liveOutput.split('\n').filter(l => l.length > 0);
|
|
34
|
+
for (const line of lines) {
|
|
35
|
+
outputLines.push({ text: line, isError: false, toolName: tc.name, live: true });
|
|
36
|
+
}
|
|
37
|
+
continue;
|
|
38
|
+
}
|
|
39
|
+
if (tc.status !== 'completed' && tc.status !== 'failed') continue;
|
|
40
|
+
const output = tc.result?.output ?? '';
|
|
41
|
+
const isError = tc.result?.isError ?? false;
|
|
42
|
+
const lines = output.split('\n').filter(l => l.length > 0);
|
|
43
|
+
for (const line of lines) {
|
|
44
|
+
outputLines.push({ text: line, isError, toolName: tc.name });
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
// Show only the last maxLines lines
|
|
49
|
+
const visible = outputLines.slice(-maxLines);
|
|
50
|
+
|
|
51
|
+
return (
|
|
52
|
+
<Box
|
|
53
|
+
flexDirection="column"
|
|
54
|
+
borderStyle="single"
|
|
55
|
+
borderColor="gray"
|
|
56
|
+
paddingX={1}
|
|
57
|
+
flexGrow={1}
|
|
58
|
+
overflow="hidden"
|
|
59
|
+
>
|
|
60
|
+
{/* Header */}
|
|
61
|
+
<Box marginBottom={1}>
|
|
62
|
+
<Text bold color="cyan">
|
|
63
|
+
Terminal Output
|
|
64
|
+
</Text>
|
|
65
|
+
<Text dimColor> (read-only · /terminal to toggle)</Text>
|
|
66
|
+
</Box>
|
|
67
|
+
|
|
68
|
+
{visible.length === 0 ? (
|
|
69
|
+
<Text dimColor italic>No tool output yet.</Text>
|
|
70
|
+
) : (
|
|
71
|
+
visible.map((line, i) => (
|
|
72
|
+
<Text
|
|
73
|
+
key={i}
|
|
74
|
+
color={line.isError ? 'red' : line.live ? 'cyan' : undefined}
|
|
75
|
+
dimColor={!line.isError && !line.live}
|
|
76
|
+
wrap="truncate"
|
|
77
|
+
>
|
|
78
|
+
{line.live ? '[>] ' : ''}{line.text}
|
|
79
|
+
</Text>
|
|
80
|
+
))
|
|
81
|
+
)}
|
|
82
|
+
</Box>
|
|
83
|
+
);
|
|
84
|
+
}
|
|
@@ -15,9 +15,10 @@
|
|
|
15
15
|
* All other tools fall through to a generic key/value display.
|
|
16
16
|
*/
|
|
17
17
|
|
|
18
|
-
import React, { useState } from 'react';
|
|
18
|
+
import React, { useState, useEffect } from 'react';
|
|
19
19
|
import { Box, Text, useInput } from 'ink';
|
|
20
20
|
import Spinner from 'ink-spinner';
|
|
21
|
+
import { parseTerraformPlanOutput } from '../agent/deploy-preview';
|
|
21
22
|
import type { UIToolCall } from './types';
|
|
22
23
|
|
|
23
24
|
/** Props accepted by the ToolCallDisplay component. */
|
|
@@ -36,7 +37,25 @@ const COLLAPSED_LINES = 20;
|
|
|
36
37
|
* Status badge
|
|
37
38
|
* -------------------------------------------------------------------------*/
|
|
38
39
|
|
|
39
|
-
|
|
40
|
+
/** Long-running tools that get a context hint about expected duration. */
|
|
41
|
+
const LONG_RUNNING_TOOLS = new Set([
|
|
42
|
+
'terraform', 'terraform_plan_analyze', 'deploy_preview', 'drift_detect',
|
|
43
|
+
'helm', 'k8s_rbac', 'cfn', 'gitops',
|
|
44
|
+
]);
|
|
45
|
+
|
|
46
|
+
function StatusBadge({ status, startTime }: { status: UIToolCall['status']; startTime?: number }) {
|
|
47
|
+
const [elapsed, setElapsed] = useState(0);
|
|
48
|
+
|
|
49
|
+
useEffect(() => {
|
|
50
|
+
if (status !== 'running' || !startTime) return;
|
|
51
|
+
const initial = Math.floor((Date.now() - startTime) / 1000);
|
|
52
|
+
setElapsed(initial);
|
|
53
|
+
const id = setInterval(() => {
|
|
54
|
+
setElapsed(Math.floor((Date.now() - startTime) / 1000));
|
|
55
|
+
}, 1000);
|
|
56
|
+
return () => clearInterval(id);
|
|
57
|
+
}, [status, startTime]);
|
|
58
|
+
|
|
40
59
|
switch (status) {
|
|
41
60
|
case 'pending':
|
|
42
61
|
return <Text dimColor>[pending]</Text>;
|
|
@@ -44,6 +63,7 @@ function StatusBadge({ status }: { status: UIToolCall['status'] }) {
|
|
|
44
63
|
return (
|
|
45
64
|
<Text color="cyan">
|
|
46
65
|
<Spinner type="dots" />
|
|
66
|
+
{startTime && elapsed > 0 ? ` ${elapsed}s` : ''}
|
|
47
67
|
</Text>
|
|
48
68
|
);
|
|
49
69
|
case 'completed':
|
|
@@ -258,31 +278,129 @@ function TerraformBody({
|
|
|
258
278
|
input: Record<string, unknown>;
|
|
259
279
|
result?: UIToolCall['result'];
|
|
260
280
|
}) {
|
|
261
|
-
const
|
|
281
|
+
const action = String(input.action ?? input.command ?? input.subcommand ?? 'plan');
|
|
282
|
+
|
|
283
|
+
// Gap 8: Show structured plan summary when output contains plan data
|
|
284
|
+
const isPlan = action === 'plan' || (result?.output ?? '').includes('Plan:');
|
|
285
|
+
const changes = isPlan && result && !result.isError
|
|
286
|
+
? parseTerraformPlanOutput(result.output)
|
|
287
|
+
: [];
|
|
288
|
+
|
|
289
|
+
const creates = changes.filter(c => c.action === 'create').length;
|
|
290
|
+
const updates = changes.filter(c => c.action === 'update').length;
|
|
291
|
+
const destroys = changes.filter(c => c.action === 'destroy').length;
|
|
292
|
+
const replaces = changes.filter(c => c.action === 'replace').length;
|
|
262
293
|
|
|
263
294
|
return (
|
|
264
295
|
<Box flexDirection="column">
|
|
265
296
|
<Text>
|
|
266
297
|
<Text dimColor>terraform </Text>
|
|
267
|
-
<Text bold>{
|
|
298
|
+
<Text bold>{action}</Text>
|
|
268
299
|
</Text>
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
300
|
+
|
|
301
|
+
{/* Gap 8: Structured plan summary panel */}
|
|
302
|
+
{isPlan && changes.length > 0 && result && !result.isError && (
|
|
303
|
+
<Box flexDirection="column" marginTop={1} borderStyle="single" borderColor="gray" paddingX={1}>
|
|
304
|
+
<Text bold>Plan Summary</Text>
|
|
305
|
+
<Box>
|
|
306
|
+
{creates > 0 && <Text color="green">+{creates} create </Text>}
|
|
307
|
+
{updates > 0 && <Text color="yellow">~{updates} change </Text>}
|
|
308
|
+
{destroys > 0 && <Text color="red">-{destroys} destroy </Text>}
|
|
309
|
+
{replaces > 0 && <Text color="magenta">±{replaces} replace </Text>}
|
|
310
|
+
{creates === 0 && updates === 0 && destroys === 0 && replaces === 0 && (
|
|
311
|
+
<Text dimColor>No changes</Text>
|
|
312
|
+
)}
|
|
313
|
+
</Box>
|
|
314
|
+
{changes.slice(0, 10).map((c, i) => {
|
|
315
|
+
const icon = c.action === 'create' ? '+' : c.action === 'destroy' ? '-' : c.action === 'replace' ? '±' : '~';
|
|
316
|
+
const color = c.action === 'create' ? 'green' : c.action === 'destroy' ? 'red' : c.action === 'replace' ? 'magenta' : 'yellow';
|
|
280
317
|
return (
|
|
281
|
-
<Text key={i} color={color}
|
|
282
|
-
{
|
|
318
|
+
<Text key={i} color={color}>
|
|
319
|
+
{icon} {c.resource}
|
|
283
320
|
</Text>
|
|
284
321
|
);
|
|
285
322
|
})}
|
|
323
|
+
{changes.length > 10 && <Text dimColor>... and {changes.length - 10} more</Text>}
|
|
324
|
+
</Box>
|
|
325
|
+
)}
|
|
326
|
+
|
|
327
|
+
{/* H1: Fallback raw line coloring — show up to 200 lines with indicator for truncation */}
|
|
328
|
+
{!(isPlan && changes.length > 0) && result && !result.isError && (
|
|
329
|
+
<Box flexDirection="column" marginTop={1}>
|
|
330
|
+
{(() => {
|
|
331
|
+
const lines = result.output.split('\n');
|
|
332
|
+
const MAX_LINES = 200;
|
|
333
|
+
const displayLines = lines.slice(0, MAX_LINES);
|
|
334
|
+
return (
|
|
335
|
+
<>
|
|
336
|
+
{displayLines.map((line, i) => {
|
|
337
|
+
let color: string | undefined;
|
|
338
|
+
if (line.startsWith('+') || line.includes('will be created')) {
|
|
339
|
+
color = 'green';
|
|
340
|
+
} else if (line.startsWith('-') || line.includes('will be destroyed')) {
|
|
341
|
+
color = 'red';
|
|
342
|
+
} else if (line.startsWith('~') || line.includes('will be updated')) {
|
|
343
|
+
color = 'yellow';
|
|
344
|
+
}
|
|
345
|
+
return (
|
|
346
|
+
<Text key={i} color={color} dimColor={!color}>
|
|
347
|
+
{line}
|
|
348
|
+
</Text>
|
|
349
|
+
);
|
|
350
|
+
})}
|
|
351
|
+
{lines.length > MAX_LINES && (
|
|
352
|
+
<Text dimColor>... {lines.length - MAX_LINES} more lines (full output saved to tool history)</Text>
|
|
353
|
+
)}
|
|
354
|
+
</>
|
|
355
|
+
);
|
|
356
|
+
})()}
|
|
357
|
+
</Box>
|
|
358
|
+
)}
|
|
359
|
+
{result && result.isError && <Text color="red">{result.output}</Text>}
|
|
360
|
+
</Box>
|
|
361
|
+
);
|
|
362
|
+
}
|
|
363
|
+
|
|
364
|
+
/** G12: Kubectl output renderer with status colorization */
|
|
365
|
+
function KubectlBody({
|
|
366
|
+
input,
|
|
367
|
+
result,
|
|
368
|
+
}: {
|
|
369
|
+
input: Record<string, unknown>;
|
|
370
|
+
result?: UIToolCall['result'];
|
|
371
|
+
}) {
|
|
372
|
+
const action = String(input.action ?? input.command ?? 'get');
|
|
373
|
+
|
|
374
|
+
const colorizeKubectlLine = (line: string, i: number): React.ReactNode => {
|
|
375
|
+
// Status coloring for kubectl get output
|
|
376
|
+
if (/\bRunning\b/.test(line)) {
|
|
377
|
+
return <Text key={i} color="green">{line}</Text>;
|
|
378
|
+
}
|
|
379
|
+
if (/\b(Pending|ContainerCreating|Init:|PodInitializing)\b/.test(line)) {
|
|
380
|
+
return <Text key={i} color="yellow">{line}</Text>;
|
|
381
|
+
}
|
|
382
|
+
if (/\b(CrashLoopBackOff|Error|Failed|OOMKilled|ImagePullBackOff|ErrImagePull)\b/.test(line)) {
|
|
383
|
+
return <Text key={i} color="red">{line}</Text>;
|
|
384
|
+
}
|
|
385
|
+
if (/\bCompleted\b/.test(line)) {
|
|
386
|
+
return <Text key={i} color="green" dimColor>{line}</Text>;
|
|
387
|
+
}
|
|
388
|
+
if (/\bTerminating\b/.test(line)) {
|
|
389
|
+
return <Text key={i} color="yellow" dimColor>{line}</Text>;
|
|
390
|
+
}
|
|
391
|
+
return <Text key={i} dimColor>{line}</Text>;
|
|
392
|
+
};
|
|
393
|
+
|
|
394
|
+
return (
|
|
395
|
+
<Box flexDirection="column">
|
|
396
|
+
<Text>
|
|
397
|
+
<Text dimColor>kubectl </Text>
|
|
398
|
+
<Text bold>{action}</Text>
|
|
399
|
+
</Text>
|
|
400
|
+
{result && !result.isError && (
|
|
401
|
+
<Box flexDirection="column" marginTop={1}>
|
|
402
|
+
{/* M1: Increased from 40 to 80 lines for better kubectl output visibility */}
|
|
403
|
+
{result.output.split('\n').slice(0, 80).map((line, i) => colorizeKubectlLine(line, i))}
|
|
286
404
|
</Box>
|
|
287
405
|
)}
|
|
288
406
|
{result && result.isError && <Text color="red">{result.output}</Text>}
|
|
@@ -290,6 +408,66 @@ function TerraformBody({
|
|
|
290
408
|
);
|
|
291
409
|
}
|
|
292
410
|
|
|
411
|
+
/** M2: Docker build progress renderer */
|
|
412
|
+
function DockerBuildBody({
|
|
413
|
+
input,
|
|
414
|
+
result,
|
|
415
|
+
streamingOutput,
|
|
416
|
+
}: {
|
|
417
|
+
input: Record<string, unknown>;
|
|
418
|
+
result?: UIToolCall['result'];
|
|
419
|
+
streamingOutput?: string;
|
|
420
|
+
}) {
|
|
421
|
+
const action = String(input.action ?? '');
|
|
422
|
+
if (action !== 'build') {
|
|
423
|
+
// Non-build actions: show raw output
|
|
424
|
+
if (result && !result.isError) {
|
|
425
|
+
return (
|
|
426
|
+
<Box flexDirection="column" marginTop={1}>
|
|
427
|
+
{result.output.split('\n').slice(0, 20).map((line, i) => (
|
|
428
|
+
<Text key={i} dimColor>{line}</Text>
|
|
429
|
+
))}
|
|
430
|
+
</Box>
|
|
431
|
+
);
|
|
432
|
+
}
|
|
433
|
+
if (result?.isError) return <Text color="red">{result.output}</Text>;
|
|
434
|
+
return null;
|
|
435
|
+
}
|
|
436
|
+
|
|
437
|
+
// Parse step progress from streaming output or result
|
|
438
|
+
const outputText = streamingOutput || result?.output || '';
|
|
439
|
+
const stepMatch = outputText.match(/Step\s+(\d+)\/(\d+)/gi);
|
|
440
|
+
const lastStep = stepMatch ? stepMatch[stepMatch.length - 1] : null;
|
|
441
|
+
const totalSteps = lastStep ? parseInt(lastStep.match(/\/(\d+)/)![1]) : 0;
|
|
442
|
+
const currentStep = lastStep ? parseInt(lastStep.match(/Step\s+(\d+)/i)![1]) : 0;
|
|
443
|
+
const succeeded = outputText.includes('Successfully built') || outputText.includes('Successfully tagged');
|
|
444
|
+
const failed = result?.isError || false;
|
|
445
|
+
|
|
446
|
+
return (
|
|
447
|
+
<Box flexDirection="column" marginTop={1}>
|
|
448
|
+
{totalSteps > 0 && (
|
|
449
|
+
<Box>
|
|
450
|
+
<Text color={succeeded ? 'green' : failed ? 'red' : 'cyan'}>
|
|
451
|
+
{succeeded ? '[ok] ' : failed ? '[xx] ' : '[..] '}
|
|
452
|
+
[{currentStep}/{totalSteps} steps]
|
|
453
|
+
</Text>
|
|
454
|
+
</Box>
|
|
455
|
+
)}
|
|
456
|
+
{outputText.split('\n').filter(l => l.trim()).slice(-5).map((line, i) => {
|
|
457
|
+
const isStep = /^Step\s+\d+\/\d+/i.test(line.trim());
|
|
458
|
+
const isSuccess = /Successfully/i.test(line);
|
|
459
|
+
const isError = /error/i.test(line);
|
|
460
|
+
return (
|
|
461
|
+
<Text key={i} color={isSuccess ? 'green' : isError ? 'red' : isStep ? 'cyan' : undefined} dimColor={!isStep && !isSuccess && !isError}>
|
|
462
|
+
{line}
|
|
463
|
+
</Text>
|
|
464
|
+
);
|
|
465
|
+
})}
|
|
466
|
+
{result?.isError && <Text color="red">{result.output}</Text>}
|
|
467
|
+
</Box>
|
|
468
|
+
);
|
|
469
|
+
}
|
|
470
|
+
|
|
293
471
|
function GenericBody({
|
|
294
472
|
input,
|
|
295
473
|
result,
|
|
@@ -332,6 +510,7 @@ function GenericBody({
|
|
|
332
510
|
|
|
333
511
|
function ToolCallBox({ toolCall, expanded }: { toolCall: UIToolCall; expanded: boolean }) {
|
|
334
512
|
const durationLabel = toolCall.duration != null ? ` (${toolCall.duration}ms)` : '';
|
|
513
|
+
const isLongRunning = LONG_RUNNING_TOOLS.has(toolCall.name.toLowerCase());
|
|
335
514
|
|
|
336
515
|
// Choose specialised body renderer based on tool name
|
|
337
516
|
const renderBody = () => {
|
|
@@ -362,6 +541,12 @@ function ToolCallBox({ toolCall, expanded }: { toolCall: UIToolCall; expanded: b
|
|
|
362
541
|
if (name.startsWith('terraform') || name === 'tf_plan' || name === 'tf_apply') {
|
|
363
542
|
return <TerraformBody {...props} />;
|
|
364
543
|
}
|
|
544
|
+
if (name === 'kubectl' || name === 'k8s') {
|
|
545
|
+
return <KubectlBody {...props} />;
|
|
546
|
+
}
|
|
547
|
+
if (name === 'docker') {
|
|
548
|
+
return <DockerBuildBody input={toolCall.input} result={toolCall.result} streamingOutput={toolCall.streamingOutput} />;
|
|
549
|
+
}
|
|
365
550
|
return <GenericBody {...props} />;
|
|
366
551
|
};
|
|
367
552
|
|
|
@@ -375,11 +560,60 @@ function ToolCallBox({ toolCall, expanded }: { toolCall: UIToolCall; expanded: b
|
|
|
375
560
|
>
|
|
376
561
|
{/* Header */}
|
|
377
562
|
<Box>
|
|
378
|
-
<StatusBadge status={toolCall.status} />
|
|
563
|
+
<StatusBadge status={toolCall.status} startTime={toolCall.startTime} />
|
|
379
564
|
<Text bold> {toolCall.name}</Text>
|
|
380
565
|
<Text dimColor>{durationLabel}</Text>
|
|
566
|
+
{/* M4: Highlight duration prominently when operation took > 5 seconds */}
|
|
567
|
+
{toolCall.status === 'completed' && toolCall.duration != null && toolCall.duration > 5000 && (
|
|
568
|
+
<Text dimColor> [{(toolCall.duration / 1000).toFixed(1)}s]</Text>
|
|
569
|
+
)}
|
|
570
|
+
{toolCall.status === 'running' && toolCall.name === 'logs' && (
|
|
571
|
+
<Text color="cyan"> ● LIVE</Text>
|
|
572
|
+
)}
|
|
381
573
|
</Box>
|
|
382
574
|
|
|
575
|
+
{/* Long-running hint */}
|
|
576
|
+
{toolCall.status === 'running' && isLongRunning && (
|
|
577
|
+
<Text dimColor italic>
|
|
578
|
+
This may take several minutes for large infrastructure changes.
|
|
579
|
+
</Text>
|
|
580
|
+
)}
|
|
581
|
+
|
|
582
|
+
{/* Streaming output — shown while tool is running */}
|
|
583
|
+
{toolCall.status === 'running' && toolCall.streamingOutput && toolCall.streamingOutput.trim() && (
|
|
584
|
+
<Box flexDirection="column" marginTop={1} marginLeft={2}>
|
|
585
|
+
<Box>
|
|
586
|
+
<Text color="green" bold>[LIVE] </Text>
|
|
587
|
+
<Text>{'─'.repeat(34)}</Text>
|
|
588
|
+
</Box>
|
|
589
|
+
{(() => {
|
|
590
|
+
const allLines = toolCall.streamingOutput!.split('\n');
|
|
591
|
+
const isTerraformOrKubectl = toolCall.name === 'terraform' || toolCall.name === 'kubectl' || toolCall.name === 'logs';
|
|
592
|
+
// M1: Increased streaming window — 60 lines for terraform/kubectl/logs, 40 for others
|
|
593
|
+
const windowSize = isTerraformOrKubectl ? 60 : 40;
|
|
594
|
+
const visibleLines = allLines.slice(-windowSize);
|
|
595
|
+
// Pad to minimum 4 lines so the live area is always visible
|
|
596
|
+
while (visibleLines.length < 4) visibleLines.push('');
|
|
597
|
+
const hiddenCount = Math.max(0, allLines.length - windowSize);
|
|
598
|
+
return (
|
|
599
|
+
<>
|
|
600
|
+
{hiddenCount > 0 && (
|
|
601
|
+
<Text dimColor>... {hiddenCount} earlier lines</Text>
|
|
602
|
+
)}
|
|
603
|
+
{visibleLines.map((line, i) => {
|
|
604
|
+
// M2: Color terraform/kubectl streaming output lines
|
|
605
|
+
let lineColor: string | undefined;
|
|
606
|
+
if (line.match(/^\s*\+/) || line.includes('will be created') || line.includes(' created')) lineColor = 'green';
|
|
607
|
+
else if (line.match(/^\s*-/) || line.includes('will be destroyed') || line.includes(' destroyed')) lineColor = 'red';
|
|
608
|
+
else if (line.match(/^\s*~/) || line.includes('will be updated') || line.includes(' modified')) lineColor = 'yellow';
|
|
609
|
+
return <Text key={i} color={lineColor ?? 'gray'} dimColor={!lineColor}>{line}</Text>;
|
|
610
|
+
})}
|
|
611
|
+
</>
|
|
612
|
+
);
|
|
613
|
+
})()}
|
|
614
|
+
</Box>
|
|
615
|
+
)}
|
|
616
|
+
|
|
383
617
|
{/* Body */}
|
|
384
618
|
<Box marginTop={1}>{renderBody()}</Box>
|
|
385
619
|
</Box>
|