@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
package/src/cli/run.ts
CHANGED
|
@@ -9,6 +9,7 @@
|
|
|
9
9
|
* nimbus run "fix the failing tests" --auto-approve
|
|
10
10
|
* echo "analyze this repo" | nimbus run --stdin
|
|
11
11
|
* nimbus run "estimate costs" --format json --model anthropic/claude-haiku-4-5
|
|
12
|
+
* nimbus run --schema # print the JSON output schema and exit
|
|
12
13
|
*/
|
|
13
14
|
|
|
14
15
|
import { runAgentLoop } from '../agent/loop';
|
|
@@ -18,23 +19,83 @@ import { standardTools } from '../tools/schemas/standard';
|
|
|
18
19
|
import { devopsTools } from '../tools/schemas/devops';
|
|
19
20
|
import type { AgentMode } from '../agent/system-prompt';
|
|
20
21
|
import type { LLMRouter } from '../llm/router';
|
|
22
|
+
import { expandFileReferences } from '../agent/expand-files';
|
|
23
|
+
|
|
24
|
+
/** JSON output schema for `nimbus run --format json` */
|
|
25
|
+
export interface RunJsonOutput {
|
|
26
|
+
success: boolean; // whether the agent completed without error
|
|
27
|
+
output: string; // final text response from the agent
|
|
28
|
+
cost: number; // total cost in USD
|
|
29
|
+
turns: number; // number of LLM turns taken
|
|
30
|
+
toolCalls: Array<{ // all tool calls made during the run
|
|
31
|
+
name: string;
|
|
32
|
+
success: boolean;
|
|
33
|
+
durationMs: number;
|
|
34
|
+
}>;
|
|
35
|
+
errors: string[]; // any error messages encountered
|
|
36
|
+
/** L2: Terraform plan summary extracted from plan output (CI-friendly) */
|
|
37
|
+
planSummary?: {
|
|
38
|
+
toAdd: number;
|
|
39
|
+
toChange: number;
|
|
40
|
+
toDestroy: number;
|
|
41
|
+
raw: string;
|
|
42
|
+
};
|
|
43
|
+
/** M3: Summary of DevOps tools invoked during the run */
|
|
44
|
+
devops_summary?: {
|
|
45
|
+
tools_used: string[];
|
|
46
|
+
tool_call_count: number;
|
|
47
|
+
devops_tool_count: number;
|
|
48
|
+
};
|
|
49
|
+
/** M1: Infrastructure context active during the run */
|
|
50
|
+
infraContext?: {
|
|
51
|
+
terraformWorkspace?: string;
|
|
52
|
+
kubectlContext?: string;
|
|
53
|
+
awsAccount?: string;
|
|
54
|
+
};
|
|
55
|
+
/** M1: Unique DevOps tool names invoked */
|
|
56
|
+
toolsUsed?: string[];
|
|
57
|
+
}
|
|
21
58
|
|
|
22
59
|
/** Options parsed from command-line arguments */
|
|
23
60
|
export interface RunOptions {
|
|
24
61
|
/** The prompt to execute */
|
|
25
62
|
prompt: string;
|
|
26
63
|
/** Output format */
|
|
27
|
-
format: 'text' | 'json';
|
|
28
|
-
/** Skip permission prompts — auto-approve everything */
|
|
64
|
+
format: 'text' | 'json' | 'table';
|
|
65
|
+
/** Skip permission prompts — auto-approve everything (--non-interactive is an alias) */
|
|
29
66
|
autoApprove: boolean;
|
|
30
67
|
/** Read prompt from stdin */
|
|
31
68
|
stdin: boolean;
|
|
69
|
+
/** Parse stdin as JSON config object { prompt, mode, model, autoApprove, maxTurns } */
|
|
70
|
+
stdinJson: boolean;
|
|
32
71
|
/** Model override */
|
|
33
72
|
model?: string;
|
|
34
73
|
/** Agent mode override */
|
|
35
74
|
mode: AgentMode;
|
|
36
75
|
/** Maximum turns */
|
|
37
76
|
maxTurns: number;
|
|
77
|
+
/** G13: Abort agent loop after this many milliseconds */
|
|
78
|
+
timeout?: number;
|
|
79
|
+
/** G15: Print last tool output as JSON instead of prose */
|
|
80
|
+
rawToolOutput?: boolean;
|
|
81
|
+
/** G23: Print the JSON output schema and exit */
|
|
82
|
+
schema?: boolean;
|
|
83
|
+
/** M1: Dry-run mode — forces plan mode and instructs agent not to mutate anything */
|
|
84
|
+
dryRun?: boolean;
|
|
85
|
+
/** H3: Exit with code 1 on agent failure */
|
|
86
|
+
exitOnError?: boolean;
|
|
87
|
+
/** H3: kubectl context to inject before running */
|
|
88
|
+
context?: string;
|
|
89
|
+
/** H3: Terraform workspace to inject before running */
|
|
90
|
+
workspace?: string;
|
|
91
|
+
/** H3: Kubernetes namespace to inject before running */
|
|
92
|
+
namespace?: string;
|
|
93
|
+
/** H3: Webhook URL to POST result to after completion */
|
|
94
|
+
notify?: string;
|
|
95
|
+
/** H3: Slack webhook URL to POST formatted result to */
|
|
96
|
+
notifySlack?: string;
|
|
97
|
+
/** G16: Maximum cost in USD per session */
|
|
98
|
+
budget?: number;
|
|
38
99
|
}
|
|
39
100
|
|
|
40
101
|
/** Result of a non-interactive run */
|
|
@@ -62,12 +123,24 @@ export interface RunResult {
|
|
|
62
123
|
*/
|
|
63
124
|
export function parseRunArgs(args: string[]): RunOptions {
|
|
64
125
|
let prompt = '';
|
|
65
|
-
let format: 'text' | 'json' = 'text';
|
|
126
|
+
let format: 'text' | 'json' | 'table' = 'text';
|
|
66
127
|
let autoApprove = false;
|
|
67
128
|
let stdin = false;
|
|
129
|
+
let stdinJson = false;
|
|
68
130
|
let model: string | undefined;
|
|
69
131
|
let mode: AgentMode = 'build';
|
|
70
132
|
let maxTurns = 50;
|
|
133
|
+
let timeout: number | undefined;
|
|
134
|
+
let rawToolOutput = false;
|
|
135
|
+
let schema = false;
|
|
136
|
+
let dryRun = false;
|
|
137
|
+
let exitOnError = true; // C5: default true for CI/CD POSIX convention
|
|
138
|
+
let context: string | undefined;
|
|
139
|
+
let workspace: string | undefined;
|
|
140
|
+
let namespace: string | undefined;
|
|
141
|
+
let notify: string | undefined;
|
|
142
|
+
let notifySlack: string | undefined;
|
|
143
|
+
let budget: number | undefined;
|
|
71
144
|
|
|
72
145
|
const positional: string[] = [];
|
|
73
146
|
|
|
@@ -76,18 +149,23 @@ export function parseRunArgs(args: string[]): RunOptions {
|
|
|
76
149
|
|
|
77
150
|
switch (arg) {
|
|
78
151
|
case '--format':
|
|
79
|
-
format = (args[++i] ?? 'text') as 'text' | 'json';
|
|
152
|
+
format = (args[++i] ?? 'text') as 'text' | 'json' | 'table';
|
|
80
153
|
break;
|
|
81
154
|
case '--json':
|
|
82
155
|
format = 'json';
|
|
83
156
|
break;
|
|
84
157
|
case '--auto-approve':
|
|
85
158
|
case '-y':
|
|
159
|
+
case '--non-interactive':
|
|
86
160
|
autoApprove = true;
|
|
87
161
|
break;
|
|
88
162
|
case '--stdin':
|
|
89
163
|
stdin = true;
|
|
90
164
|
break;
|
|
165
|
+
case '--stdin-json':
|
|
166
|
+
stdinJson = true;
|
|
167
|
+
stdin = true; // also read stdin
|
|
168
|
+
break;
|
|
91
169
|
case '--model':
|
|
92
170
|
model = args[++i];
|
|
93
171
|
break;
|
|
@@ -97,6 +175,60 @@ export function parseRunArgs(args: string[]): RunOptions {
|
|
|
97
175
|
case '--max-turns':
|
|
98
176
|
maxTurns = parseInt(args[++i] ?? '50', 10);
|
|
99
177
|
break;
|
|
178
|
+
case '--timeout':
|
|
179
|
+
// G13: timeout in seconds, converted to ms
|
|
180
|
+
timeout = parseInt(args[++i] ?? '0', 10) * 1000;
|
|
181
|
+
break;
|
|
182
|
+
case '--raw-tool-output':
|
|
183
|
+
// G15: print last tool output as JSON
|
|
184
|
+
rawToolOutput = true;
|
|
185
|
+
break;
|
|
186
|
+
case '--schema':
|
|
187
|
+
// G23: print the JSON output schema and exit
|
|
188
|
+
schema = true;
|
|
189
|
+
break;
|
|
190
|
+
case '--json-schema':
|
|
191
|
+
// GAP-16: print the stable JSON output schema and exit
|
|
192
|
+
schema = true;
|
|
193
|
+
break;
|
|
194
|
+
case '--dry-run':
|
|
195
|
+
// M1: dry-run forces plan mode — no mutations allowed
|
|
196
|
+
dryRun = true;
|
|
197
|
+
mode = 'plan';
|
|
198
|
+
break;
|
|
199
|
+
case '--exit-code-on-error':
|
|
200
|
+
// H3: exit with code 1 on failure
|
|
201
|
+
exitOnError = true;
|
|
202
|
+
break;
|
|
203
|
+
case '--no-exit-on-error':
|
|
204
|
+
// C5: legacy scripts can opt out of exit-on-error
|
|
205
|
+
exitOnError = false;
|
|
206
|
+
break;
|
|
207
|
+
case '--context':
|
|
208
|
+
// H3: kubectl context
|
|
209
|
+
context = args[++i];
|
|
210
|
+
break;
|
|
211
|
+
case '--workspace':
|
|
212
|
+
// H3: terraform workspace
|
|
213
|
+
workspace = args[++i];
|
|
214
|
+
break;
|
|
215
|
+
case '--namespace':
|
|
216
|
+
case '-n':
|
|
217
|
+
// H3: kubernetes namespace
|
|
218
|
+
namespace = args[++i];
|
|
219
|
+
break;
|
|
220
|
+
case '--notify':
|
|
221
|
+
// H3: webhook URL
|
|
222
|
+
notify = args[++i];
|
|
223
|
+
break;
|
|
224
|
+
case '--notify-slack':
|
|
225
|
+
// H3: slack webhook
|
|
226
|
+
notifySlack = args[++i];
|
|
227
|
+
break;
|
|
228
|
+
case '--budget':
|
|
229
|
+
// G16: cost budget in USD
|
|
230
|
+
budget = parseFloat(args[++i] ?? '0');
|
|
231
|
+
break;
|
|
100
232
|
default:
|
|
101
233
|
if (!arg.startsWith('-')) {
|
|
102
234
|
positional.push(arg);
|
|
@@ -107,19 +239,71 @@ export function parseRunArgs(args: string[]): RunOptions {
|
|
|
107
239
|
|
|
108
240
|
prompt = positional.join(' ');
|
|
109
241
|
|
|
110
|
-
return { prompt, format, autoApprove, stdin, model, mode, maxTurns };
|
|
242
|
+
return { prompt, format, autoApprove, stdin, stdinJson, model, mode, maxTurns, timeout, rawToolOutput, schema, dryRun, exitOnError, context, workspace, namespace, notify, notifySlack, budget };
|
|
111
243
|
}
|
|
112
244
|
|
|
113
245
|
/**
|
|
114
246
|
* Execute a non-interactive run.
|
|
115
247
|
*/
|
|
116
248
|
export async function executeRun(router: LLMRouter, options: RunOptions): Promise<RunResult> {
|
|
249
|
+
// G23 / GAP-16: --schema / --json-schema flag: print the JSON output schema and exit immediately
|
|
250
|
+
if (options.schema) {
|
|
251
|
+
const schema = {
|
|
252
|
+
type: 'object',
|
|
253
|
+
properties: {
|
|
254
|
+
success: { type: 'boolean', description: 'Whether the agent completed without error' },
|
|
255
|
+
output: { type: 'string', description: 'Final text response from the agent' },
|
|
256
|
+
cost: { type: 'number', description: 'Total cost in USD' },
|
|
257
|
+
turns: { type: 'number', description: 'Number of LLM turns taken' },
|
|
258
|
+
toolCalls: { type: 'array', items: { type: 'object', properties: { name: { type: 'string' }, success: { type: 'boolean' }, durationMs: { type: 'number' } } } },
|
|
259
|
+
errors: { type: 'array', items: { type: 'string' }, description: 'Any error messages encountered' },
|
|
260
|
+
},
|
|
261
|
+
required: ['success', 'output', 'cost', 'turns', 'toolCalls', 'errors'],
|
|
262
|
+
};
|
|
263
|
+
process.stdout.write(JSON.stringify(schema, null, 2) + '\n');
|
|
264
|
+
return {
|
|
265
|
+
success: true,
|
|
266
|
+
output: '',
|
|
267
|
+
turns: 0,
|
|
268
|
+
usage: { promptTokens: 0, completionTokens: 0, totalTokens: 0 },
|
|
269
|
+
cost: 0,
|
|
270
|
+
interrupted: false,
|
|
271
|
+
};
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
// H3: Inject context/workspace/namespace into environment before agent loop
|
|
275
|
+
if (options.context) process.env.KUBECTL_CONTEXT = options.context;
|
|
276
|
+
if (options.workspace) process.env.TF_WORKSPACE = options.workspace;
|
|
277
|
+
if (options.namespace) process.env.K8S_NAMESPACE = options.namespace;
|
|
278
|
+
|
|
117
279
|
// Get prompt from stdin if requested
|
|
118
280
|
let prompt = options.prompt;
|
|
119
281
|
if (options.stdin && !prompt) {
|
|
120
|
-
|
|
282
|
+
const stdinContent = await readStdin();
|
|
283
|
+
|
|
284
|
+
// L5: --stdin-json support: parse stdin as { prompt, mode, model, autoApprove, maxTurns }
|
|
285
|
+
if (options.stdinJson && stdinContent) {
|
|
286
|
+
try {
|
|
287
|
+
const config = JSON.parse(stdinContent) as Record<string, unknown>;
|
|
288
|
+
if (typeof config.prompt === 'string') prompt = config.prompt;
|
|
289
|
+
if (config.mode === 'plan' || config.mode === 'build' || config.mode === 'deploy') {
|
|
290
|
+
options = { ...options, mode: config.mode };
|
|
291
|
+
}
|
|
292
|
+
if (typeof config.model === 'string') options = { ...options, model: config.model };
|
|
293
|
+
if (typeof config.autoApprove === 'boolean') options = { ...options, autoApprove: config.autoApprove };
|
|
294
|
+
if (typeof config.maxTurns === 'number') options = { ...options, maxTurns: config.maxTurns };
|
|
295
|
+
} catch {
|
|
296
|
+
// If JSON parse fails, treat stdin as raw prompt text
|
|
297
|
+
prompt = stdinContent;
|
|
298
|
+
}
|
|
299
|
+
} else {
|
|
300
|
+
prompt = stdinContent;
|
|
301
|
+
}
|
|
121
302
|
}
|
|
122
303
|
|
|
304
|
+
// Expand @file references in the prompt
|
|
305
|
+
prompt = expandFileReferences(prompt, process.cwd());
|
|
306
|
+
|
|
123
307
|
if (!prompt) {
|
|
124
308
|
return {
|
|
125
309
|
success: false,
|
|
@@ -149,6 +333,34 @@ export async function executeRun(router: LLMRouter, options: RunOptions): Promis
|
|
|
149
333
|
|
|
150
334
|
// Collect output
|
|
151
335
|
const outputParts: string[] = [];
|
|
336
|
+
const tableRows: Array<{ tool: string; status: string; output: string }> = [];
|
|
337
|
+
|
|
338
|
+
// G13: Set up timeout AbortController if --timeout was specified
|
|
339
|
+
let timeoutAbortController: AbortController | undefined;
|
|
340
|
+
let timeoutHandle: ReturnType<typeof setTimeout> | undefined;
|
|
341
|
+
if (options.timeout && options.timeout > 0) {
|
|
342
|
+
timeoutAbortController = new AbortController();
|
|
343
|
+
timeoutHandle = setTimeout(() => {
|
|
344
|
+
timeoutAbortController!.abort();
|
|
345
|
+
if (options.format === 'text') {
|
|
346
|
+
process.stderr.write(`\n[Timeout: agent loop aborted after ${options.timeout! / 1000}s]\n`);
|
|
347
|
+
}
|
|
348
|
+
}, options.timeout);
|
|
349
|
+
}
|
|
350
|
+
|
|
351
|
+
// G15: Track last tool output for --raw-tool-output
|
|
352
|
+
let lastToolName = '';
|
|
353
|
+
let lastToolOutput = '';
|
|
354
|
+
|
|
355
|
+
// G23: Track all tool calls for structured JSON output
|
|
356
|
+
const allToolCalls: Array<{ name: string; input: Record<string, unknown>; output: string; isError: boolean }> = [];
|
|
357
|
+
|
|
358
|
+
// H1: Discover infra context for CI/CD pipelines (best-effort, non-blocking)
|
|
359
|
+
let infraContext: import('../cli/init').InfraContext | undefined;
|
|
360
|
+
try {
|
|
361
|
+
const { discoverInfraContext } = await import('../cli/init');
|
|
362
|
+
infraContext = await discoverInfraContext(process.cwd());
|
|
363
|
+
} catch { /* non-critical */ }
|
|
152
364
|
|
|
153
365
|
// Run the agent loop
|
|
154
366
|
const result = await runAgentLoop(prompt, [], {
|
|
@@ -158,6 +370,10 @@ export async function executeRun(router: LLMRouter, options: RunOptions): Promis
|
|
|
158
370
|
maxTurns: options.maxTurns,
|
|
159
371
|
model: options.model,
|
|
160
372
|
cwd: process.cwd(),
|
|
373
|
+
signal: timeoutAbortController?.signal,
|
|
374
|
+
dryRun: options.dryRun,
|
|
375
|
+
costBudgetUSD: options.budget,
|
|
376
|
+
infraContext,
|
|
161
377
|
|
|
162
378
|
onText: text => {
|
|
163
379
|
outputParts.push(text);
|
|
@@ -176,6 +392,25 @@ export async function executeRun(router: LLMRouter, options: RunOptions): Promis
|
|
|
176
392
|
if (options.format === 'text' && result.isError) {
|
|
177
393
|
process.stderr.write(`[Error: ${result.error}]\n`);
|
|
178
394
|
}
|
|
395
|
+
if (options.format === 'table') {
|
|
396
|
+
tableRows.push({
|
|
397
|
+
tool: toolCall.name,
|
|
398
|
+
status: result.isError ? 'error' : 'ok',
|
|
399
|
+
output: (result.output ?? result.error ?? '').slice(0, 80),
|
|
400
|
+
});
|
|
401
|
+
}
|
|
402
|
+
// G15: track last tool output
|
|
403
|
+
lastToolName = toolCall.name;
|
|
404
|
+
lastToolOutput = result.isError ? (result.error ?? '') : (result.output ?? '');
|
|
405
|
+
// G23: accumulate all tool calls for structured JSON output
|
|
406
|
+
allToolCalls.push({
|
|
407
|
+
name: toolCall.name,
|
|
408
|
+
input: toolCall.input && typeof toolCall.input === 'object'
|
|
409
|
+
? (toolCall.input as Record<string, unknown>)
|
|
410
|
+
: {},
|
|
411
|
+
output: result.isError ? (result.error ?? '') : (result.output ?? ''),
|
|
412
|
+
isError: result.isError ?? false,
|
|
413
|
+
});
|
|
179
414
|
},
|
|
180
415
|
|
|
181
416
|
checkPermission: async (tool, input) => {
|
|
@@ -191,11 +426,17 @@ export async function executeRun(router: LLMRouter, options: RunOptions): Promis
|
|
|
191
426
|
},
|
|
192
427
|
});
|
|
193
428
|
|
|
429
|
+
// G13: Clear the timeout timer if we finished before it fired
|
|
430
|
+
if (timeoutHandle) {
|
|
431
|
+
clearTimeout(timeoutHandle);
|
|
432
|
+
}
|
|
433
|
+
|
|
194
434
|
const output = outputParts.join('');
|
|
195
435
|
|
|
196
|
-
//
|
|
197
|
-
if (options.
|
|
198
|
-
|
|
436
|
+
// G15: --raw-tool-output: print last tool call as JSON to stdout
|
|
437
|
+
if (options.rawToolOutput && lastToolName) {
|
|
438
|
+
console.log(JSON.stringify({ tool: lastToolName, output: lastToolOutput }));
|
|
439
|
+
return {
|
|
199
440
|
success: !result.interrupted,
|
|
200
441
|
output,
|
|
201
442
|
turns: result.turns,
|
|
@@ -203,13 +444,87 @@ export async function executeRun(router: LLMRouter, options: RunOptions): Promis
|
|
|
203
444
|
cost: result.totalCost,
|
|
204
445
|
interrupted: result.interrupted,
|
|
205
446
|
};
|
|
447
|
+
}
|
|
448
|
+
|
|
449
|
+
// Format output
|
|
450
|
+
if (options.format === 'json') {
|
|
451
|
+
// GAP-16 / G23: structured JSON output matching the stable RunJsonOutput schema
|
|
452
|
+
// L2: Extract terraform plan summary from tool outputs for CI-friendly output
|
|
453
|
+
let planSummary: RunJsonOutput['planSummary'];
|
|
454
|
+
const tfPlanCall = allToolCalls.find(tc => tc.name === 'terraform' && tc.output && /Plan:/.test(tc.output));
|
|
455
|
+
if (tfPlanCall?.output) {
|
|
456
|
+
const planLine = tfPlanCall.output.match(/Plan:\s*(\d+)\s*to add,\s*(\d+)\s*to change,\s*(\d+)\s*to destroy/i);
|
|
457
|
+
if (planLine) {
|
|
458
|
+
planSummary = {
|
|
459
|
+
toAdd: parseInt(planLine[1]),
|
|
460
|
+
toChange: parseInt(planLine[2]),
|
|
461
|
+
toDestroy: parseInt(planLine[3]),
|
|
462
|
+
raw: planLine[0],
|
|
463
|
+
};
|
|
464
|
+
}
|
|
465
|
+
}
|
|
466
|
+
const jsonResult: RunJsonOutput = {
|
|
467
|
+
success: !result.interrupted,
|
|
468
|
+
output,
|
|
469
|
+
cost: result.totalCost ?? 0,
|
|
470
|
+
turns: result.turns ?? 0,
|
|
471
|
+
toolCalls: allToolCalls.map(tc => ({
|
|
472
|
+
name: tc.name,
|
|
473
|
+
success: !tc.isError,
|
|
474
|
+
durationMs: 0,
|
|
475
|
+
})),
|
|
476
|
+
errors: allToolCalls.filter(tc => tc.isError).map(tc => tc.output).filter(Boolean),
|
|
477
|
+
...(planSummary ? { planSummary } : {}),
|
|
478
|
+
};
|
|
479
|
+
// M3: Build devops_summary from tool calls
|
|
480
|
+
const DEVOPS_TOOL_NAMES = new Set([
|
|
481
|
+
'terraform', 'kubectl', 'helm', 'aws', 'gcloud', 'az',
|
|
482
|
+
'docker', 'secrets', 'cicd', 'monitor', 'gitops', 'cloud_action',
|
|
483
|
+
'logs', 'certs', 'mesh', 'cfn', 'k8s_rbac', 'generate_infra',
|
|
484
|
+
'kubectl_context', 'helm_values', 'cost_estimate', 'cloud_discover',
|
|
485
|
+
]);
|
|
486
|
+
const toolsUsed = [...new Set(allToolCalls.map(tc => tc.name))];
|
|
487
|
+
const devopsToolsUsed = toolsUsed.filter(t => DEVOPS_TOOL_NAMES.has(t));
|
|
488
|
+
if (devopsToolsUsed.length > 0) {
|
|
489
|
+
jsonResult.devops_summary = {
|
|
490
|
+
tools_used: devopsToolsUsed,
|
|
491
|
+
tool_call_count: allToolCalls.length,
|
|
492
|
+
devops_tool_count: allToolCalls.filter(tc => DEVOPS_TOOL_NAMES.has(tc.name)).length,
|
|
493
|
+
};
|
|
494
|
+
}
|
|
495
|
+
// M1: Add infraContext and toolsUsed fields
|
|
496
|
+
jsonResult.toolsUsed = toolsUsed;
|
|
497
|
+
if (infraContext) {
|
|
498
|
+
jsonResult.infraContext = {
|
|
499
|
+
terraformWorkspace: infraContext.terraformWorkspace,
|
|
500
|
+
kubectlContext: infraContext.kubectlContext,
|
|
501
|
+
awsAccount: infraContext.awsAccount,
|
|
502
|
+
};
|
|
503
|
+
}
|
|
206
504
|
console.log(JSON.stringify(jsonResult, null, 2));
|
|
207
505
|
} else if (options.format === 'text') {
|
|
208
506
|
// Text was already streamed above
|
|
209
507
|
console.log(''); // Final newline
|
|
508
|
+
} else if (options.format === 'table') {
|
|
509
|
+
// ASCII table of tool calls
|
|
510
|
+
const COL_TOOL = 30;
|
|
511
|
+
const COL_STATUS = 6;
|
|
512
|
+
const COL_OUTPUT = 80;
|
|
513
|
+
const divider = `${'-'.repeat(COL_TOOL + 2)}+${'-'.repeat(COL_STATUS + 2)}+${'-'.repeat(COL_OUTPUT + 2)}`;
|
|
514
|
+
const pad = (s: string, n: number) => s.slice(0, n).padEnd(n);
|
|
515
|
+
console.log(divider);
|
|
516
|
+
console.log(`| ${pad('Tool', COL_TOOL)} | ${pad('Status', COL_STATUS)} | ${pad('Output', COL_OUTPUT)} |`);
|
|
517
|
+
console.log(divider);
|
|
518
|
+
for (const row of tableRows) {
|
|
519
|
+
console.log(`| ${pad(row.tool, COL_TOOL)} | ${pad(row.status, COL_STATUS)} | ${pad(row.output, COL_OUTPUT)} |`);
|
|
520
|
+
}
|
|
521
|
+
console.log(divider);
|
|
522
|
+
console.log('');
|
|
523
|
+
// Also print final text output
|
|
524
|
+
if (output) process.stdout.write(output + '\n');
|
|
210
525
|
}
|
|
211
526
|
|
|
212
|
-
|
|
527
|
+
const runResult: RunResult = {
|
|
213
528
|
success: !result.interrupted,
|
|
214
529
|
output,
|
|
215
530
|
turns: result.turns,
|
|
@@ -217,21 +532,97 @@ export async function executeRun(router: LLMRouter, options: RunOptions): Promis
|
|
|
217
532
|
cost: result.totalCost,
|
|
218
533
|
interrupted: result.interrupted,
|
|
219
534
|
};
|
|
535
|
+
|
|
536
|
+
// H3: Fire webhook notifications after run completes
|
|
537
|
+
const duration = Date.now(); // approximate; real duration would need a start time
|
|
538
|
+
const notifyPayload = {
|
|
539
|
+
success: runResult.success,
|
|
540
|
+
output: runResult.output.slice(0, 2000), // truncate for webhook
|
|
541
|
+
cost: runResult.cost,
|
|
542
|
+
duration,
|
|
543
|
+
};
|
|
544
|
+
|
|
545
|
+
if (options.notify) {
|
|
546
|
+
try {
|
|
547
|
+
await fetch(options.notify, {
|
|
548
|
+
method: 'POST',
|
|
549
|
+
headers: { 'Content-Type': 'application/json' },
|
|
550
|
+
body: JSON.stringify(notifyPayload),
|
|
551
|
+
signal: AbortSignal.timeout(10_000),
|
|
552
|
+
});
|
|
553
|
+
} catch {
|
|
554
|
+
// Webhook failure is non-fatal
|
|
555
|
+
process.stderr.write(`[Warning: notification webhook failed]\n`);
|
|
556
|
+
}
|
|
557
|
+
}
|
|
558
|
+
|
|
559
|
+
if (options.notifySlack) {
|
|
560
|
+
try {
|
|
561
|
+
const slackPayload = {
|
|
562
|
+
text: runResult.success
|
|
563
|
+
? `:white_check_mark: *Nimbus run succeeded*\n${runResult.output.slice(0, 500)}`
|
|
564
|
+
: `:x: *Nimbus run failed*\n${runResult.output.slice(0, 500)}`,
|
|
565
|
+
attachments: [
|
|
566
|
+
{
|
|
567
|
+
fields: [
|
|
568
|
+
{ title: 'Cost', value: `$${runResult.cost.toFixed(4)}`, short: true },
|
|
569
|
+
{ title: 'Turns', value: String(runResult.turns), short: true },
|
|
570
|
+
],
|
|
571
|
+
},
|
|
572
|
+
],
|
|
573
|
+
};
|
|
574
|
+
await fetch(options.notifySlack, {
|
|
575
|
+
method: 'POST',
|
|
576
|
+
headers: { 'Content-Type': 'application/json' },
|
|
577
|
+
body: JSON.stringify(slackPayload),
|
|
578
|
+
signal: AbortSignal.timeout(10_000),
|
|
579
|
+
});
|
|
580
|
+
} catch {
|
|
581
|
+
process.stderr.write(`[Warning: Slack notification failed]\n`);
|
|
582
|
+
}
|
|
583
|
+
}
|
|
584
|
+
|
|
585
|
+
// H3: Exit with code 1 if the run failed and --exit-code-on-error is set
|
|
586
|
+
if (options.exitOnError && !runResult.success) {
|
|
587
|
+
process.exit(1);
|
|
588
|
+
}
|
|
589
|
+
|
|
590
|
+
return runResult;
|
|
220
591
|
}
|
|
221
592
|
|
|
222
593
|
/**
|
|
223
594
|
* Read all input from stdin.
|
|
224
595
|
*/
|
|
225
596
|
async function readStdin(): Promise<string> {
|
|
597
|
+
// If stdin is a TTY (no pipe), resolve immediately with empty string
|
|
598
|
+
if (process.stdin.isTTY) {
|
|
599
|
+
return '';
|
|
600
|
+
}
|
|
601
|
+
|
|
226
602
|
const chunks: Buffer[] = [];
|
|
227
603
|
|
|
228
|
-
return new Promise(resolve => {
|
|
604
|
+
return new Promise((resolve, reject) => {
|
|
605
|
+
// 30-second timeout for stdin reads to prevent hanging
|
|
606
|
+
const timeout = setTimeout(() => {
|
|
607
|
+
process.stdin.removeAllListeners();
|
|
608
|
+
resolve(Buffer.concat(chunks).toString('utf-8').trim());
|
|
609
|
+
}, 30_000);
|
|
610
|
+
|
|
229
611
|
process.stdin.on('data', chunk => chunks.push(Buffer.from(chunk)));
|
|
230
|
-
process.stdin.on('end', () => resolve(Buffer.concat(chunks).toString('utf-8').trim()));
|
|
231
612
|
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
resolve('');
|
|
235
|
-
}
|
|
613
|
+
process.stdin.on('end', () => {
|
|
614
|
+
clearTimeout(timeout);
|
|
615
|
+
resolve(Buffer.concat(chunks).toString('utf-8').trim());
|
|
616
|
+
});
|
|
617
|
+
|
|
618
|
+
process.stdin.on('error', err => {
|
|
619
|
+
clearTimeout(timeout);
|
|
620
|
+
// On error, use whatever we've collected so far
|
|
621
|
+
if (chunks.length > 0) {
|
|
622
|
+
resolve(Buffer.concat(chunks).toString('utf-8').trim());
|
|
623
|
+
} else {
|
|
624
|
+
reject(err);
|
|
625
|
+
}
|
|
626
|
+
});
|
|
236
627
|
});
|
|
237
628
|
}
|
package/src/cli/serve.ts
CHANGED
|
@@ -50,6 +50,10 @@ export interface ServeOptions {
|
|
|
50
50
|
host?: string;
|
|
51
51
|
/** HTTP Basic Auth credentials in "user:pass" format. */
|
|
52
52
|
auth?: string;
|
|
53
|
+
/** Run server as a background daemon (M1). */
|
|
54
|
+
background?: boolean;
|
|
55
|
+
/** Stop a running background server (M1). */
|
|
56
|
+
stop?: boolean;
|
|
53
57
|
}
|
|
54
58
|
|
|
55
59
|
// ---------------------------------------------------------------------------
|
|
@@ -198,6 +202,69 @@ export async function serveCommand(options: ServeOptions): Promise<void> {
|
|
|
198
202
|
const port = options.port ?? 4200;
|
|
199
203
|
const host = options.host ?? 'localhost';
|
|
200
204
|
|
|
205
|
+
// M1: stop a running background server
|
|
206
|
+
if (options.stop) {
|
|
207
|
+
const { join } = await import('node:path');
|
|
208
|
+
const { homedir } = await import('node:os');
|
|
209
|
+
const { readFileSync, unlinkSync, existsSync } = await import('node:fs');
|
|
210
|
+
const pidFile = join(homedir(), '.nimbus', 'serve.pid');
|
|
211
|
+
if (!existsSync(pidFile)) {
|
|
212
|
+
console.log('No background nimbus serve process found.');
|
|
213
|
+
return;
|
|
214
|
+
}
|
|
215
|
+
const pid = parseInt(readFileSync(pidFile, 'utf-8').trim(), 10);
|
|
216
|
+
try {
|
|
217
|
+
process.kill(pid, 'SIGTERM');
|
|
218
|
+
unlinkSync(pidFile);
|
|
219
|
+
console.log(`Stopped nimbus serve (PID: ${pid})`);
|
|
220
|
+
} catch (e: any) {
|
|
221
|
+
console.error(`Failed to stop process ${pid}: ${e.message}`);
|
|
222
|
+
unlinkSync(pidFile);
|
|
223
|
+
}
|
|
224
|
+
return;
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
// M1: daemonize — spawn detached child and exit
|
|
228
|
+
if (options.background) {
|
|
229
|
+
const { spawn } = await import('node:child_process');
|
|
230
|
+
const { join } = await import('node:path');
|
|
231
|
+
const { homedir } = await import('node:os');
|
|
232
|
+
const { writeFileSync, mkdirSync } = await import('node:fs');
|
|
233
|
+
const nimbusDir = join(homedir(), '.nimbus');
|
|
234
|
+
mkdirSync(nimbusDir, { recursive: true });
|
|
235
|
+
const childArgs = [process.argv[1], 'serve', '--port', String(port)];
|
|
236
|
+
if (options.host) childArgs.push('--host', options.host);
|
|
237
|
+
if (options.auth) childArgs.push('--auth', options.auth);
|
|
238
|
+
const child = spawn(process.execPath, childArgs, {
|
|
239
|
+
detached: true,
|
|
240
|
+
stdio: 'ignore',
|
|
241
|
+
env: process.env,
|
|
242
|
+
});
|
|
243
|
+
child.unref();
|
|
244
|
+
writeFileSync(join(nimbusDir, 'serve.pid'), String(child.pid), 'utf-8');
|
|
245
|
+
console.log(`nimbus serve started in background (PID: ${child.pid})`);
|
|
246
|
+
console.log(`Stop with: nimbus serve stop`);
|
|
247
|
+
return;
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
// G19: Security guard — non-localhost binding requires explicit auth
|
|
251
|
+
const isLocalhost = !host || host === 'localhost' || host === '127.0.0.1' || host === '::1';
|
|
252
|
+
if (!isLocalhost && !options.auth) {
|
|
253
|
+
throw new Error(
|
|
254
|
+
`Cannot bind nimbus serve to ${host} without authentication.\n` +
|
|
255
|
+
'Use --auth user:pass to enable HTTP Basic Auth, or bind to localhost only.'
|
|
256
|
+
);
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
// G19: Auto-generate token for localhost when no auth provided
|
|
260
|
+
let autoToken: string | undefined;
|
|
261
|
+
if (isLocalhost && !options.auth) {
|
|
262
|
+
const { randomBytes } = await import('node:crypto');
|
|
263
|
+
autoToken = randomBytes(16).toString('hex');
|
|
264
|
+
process.stderr.write(`\nNimbus API token: ${autoToken}\n`);
|
|
265
|
+
process.stderr.write('Pass as: Authorization: Bearer <token>\n\n');
|
|
266
|
+
}
|
|
267
|
+
|
|
201
268
|
// ------------------------------------------------------------------
|
|
202
269
|
// Initialize core systems
|
|
203
270
|
// ------------------------------------------------------------------
|
|
@@ -220,7 +287,7 @@ export async function serveCommand(options: ServeOptions): Promise<void> {
|
|
|
220
287
|
})
|
|
221
288
|
);
|
|
222
289
|
|
|
223
|
-
//
|
|
290
|
+
// HTTP Basic Auth (explicit or auto-generated token)
|
|
224
291
|
if (options.auth) {
|
|
225
292
|
const colonIdx = options.auth.indexOf(':');
|
|
226
293
|
if (colonIdx > 0) {
|
|
@@ -228,6 +295,16 @@ export async function serveCommand(options: ServeOptions): Promise<void> {
|
|
|
228
295
|
const pass = options.auth.slice(colonIdx + 1);
|
|
229
296
|
app.onBeforeHandle(createAuthMiddleware({ username: user, password: pass }));
|
|
230
297
|
}
|
|
298
|
+
} else if (autoToken) {
|
|
299
|
+
// G19: Bearer token auth for localhost when no explicit auth provided
|
|
300
|
+
const capturedToken = autoToken;
|
|
301
|
+
app.onBeforeHandle((ctx: any) => {
|
|
302
|
+
const authHeader = (ctx.request as Request).headers.get('Authorization') ?? '';
|
|
303
|
+
if (authHeader !== `Bearer ${capturedToken}`) {
|
|
304
|
+
ctx.set.status = 401;
|
|
305
|
+
return { error: 'Unauthorized. Pass Authorization: Bearer <token>' };
|
|
306
|
+
}
|
|
307
|
+
});
|
|
231
308
|
}
|
|
232
309
|
|
|
233
310
|
// ------------------------------------------------------------------
|
package/src/cli/web.ts
CHANGED
|
@@ -23,6 +23,8 @@ export interface WebOptions {
|
|
|
23
23
|
auth?: string;
|
|
24
24
|
/** URL of the Web UI (default: http://localhost:6001/nimbus). */
|
|
25
25
|
uiUrl?: string;
|
|
26
|
+
/** Skip opening browser (L3). */
|
|
27
|
+
noOpen?: boolean;
|
|
26
28
|
}
|
|
27
29
|
|
|
28
30
|
/**
|
|
@@ -51,12 +53,14 @@ export async function webCommand(options: WebOptions): Promise<void> {
|
|
|
51
53
|
console.log(`Starting Nimbus API server on port ${port}...`);
|
|
52
54
|
console.log(`Opening Web UI at ${uiUrl}\n`);
|
|
53
55
|
|
|
54
|
-
// Open browser after a short delay to let the server start
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
56
|
+
// Open browser after a short delay to let the server start (skipped with --no-open)
|
|
57
|
+
if (!options.noOpen) {
|
|
58
|
+
setTimeout(() => {
|
|
59
|
+
openBrowser(uiUrl).catch(() => {
|
|
60
|
+
console.log(`Could not open browser. Please visit: ${uiUrl}`);
|
|
61
|
+
});
|
|
62
|
+
}, 1500);
|
|
63
|
+
}
|
|
60
64
|
|
|
61
65
|
// Start the server (this blocks)
|
|
62
66
|
await serveCommand({
|