@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
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Resume Command
|
|
3
|
+
* Resume a Nimbus session by ID (or last session if omitted).
|
|
4
|
+
* Looks up sessions from SQLite, then launches chat with that session.
|
|
5
|
+
*/
|
|
6
|
+
import { ui } from '../wizard/ui';
|
|
7
|
+
import { SessionManager } from '../sessions/manager';
|
|
8
|
+
export async function resumeCommand(taskIdOrOptions = {}) {
|
|
9
|
+
const taskId = typeof taskIdOrOptions === 'string' ? taskIdOrOptions : taskIdOrOptions.taskId;
|
|
10
|
+
ui.header('Resume Session');
|
|
11
|
+
const sessionManager = SessionManager.getInstance();
|
|
12
|
+
// If no taskId given, try to resume the most recent session
|
|
13
|
+
if (!taskId) {
|
|
14
|
+
const sessions = sessionManager.list();
|
|
15
|
+
if (sessions.length === 0) {
|
|
16
|
+
ui.error('No sessions found. Start a new session with "nimbus chat".');
|
|
17
|
+
process.exit(1);
|
|
18
|
+
}
|
|
19
|
+
const lastSession = sessions[0];
|
|
20
|
+
ui.info(`Resuming last session: ${lastSession.id.slice(0, 8)}`);
|
|
21
|
+
const { chatCommand } = await import('./chat');
|
|
22
|
+
await chatCommand({ continue: true });
|
|
23
|
+
return;
|
|
24
|
+
}
|
|
25
|
+
// Look up specific session by ID (or prefix)
|
|
26
|
+
const sessions = sessionManager.list();
|
|
27
|
+
const found = sessions.find((s) => s.id === taskId || s.id.startsWith(taskId));
|
|
28
|
+
if (!found) {
|
|
29
|
+
ui.error(`Session not found. Use "nimbus sessions" to list available sessions.`);
|
|
30
|
+
process.exit(1);
|
|
31
|
+
}
|
|
32
|
+
ui.info(`Resuming session: ${found.id.slice(0, 8)}`);
|
|
33
|
+
const { chatCommand } = await import('./chat');
|
|
34
|
+
await chatCommand({ continue: true });
|
|
35
|
+
}
|
|
@@ -0,0 +1,259 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* nimbus rollback — Infrastructure Rollback Assistant
|
|
3
|
+
*
|
|
4
|
+
* Provides guided rollback for Helm releases and Kubernetes deployments.
|
|
5
|
+
* For Terraform, explains the rollback approach via state management.
|
|
6
|
+
*
|
|
7
|
+
* G20: New command added to the gap fix plan.
|
|
8
|
+
*
|
|
9
|
+
* Usage:
|
|
10
|
+
* nimbus rollback --helm <release>
|
|
11
|
+
* nimbus rollback --helm <release> --namespace <ns>
|
|
12
|
+
* nimbus rollback --k8s <deployment>
|
|
13
|
+
* nimbus rollback --k8s <deployment> --namespace <ns>
|
|
14
|
+
* nimbus rollback --tf
|
|
15
|
+
*/
|
|
16
|
+
import { ui, confirm, select } from '../wizard';
|
|
17
|
+
/**
|
|
18
|
+
* Run the nimbus rollback command.
|
|
19
|
+
*/
|
|
20
|
+
export async function rollbackCommand(options) {
|
|
21
|
+
const { execFileSync } = await import('node:child_process');
|
|
22
|
+
const run = (cmd, args) => {
|
|
23
|
+
return execFileSync(cmd, args, {
|
|
24
|
+
encoding: 'utf-8',
|
|
25
|
+
timeout: 30000,
|
|
26
|
+
stdio: ['pipe', 'pipe', 'pipe'],
|
|
27
|
+
});
|
|
28
|
+
};
|
|
29
|
+
// Helm rollback
|
|
30
|
+
if (options.helm) {
|
|
31
|
+
await rollbackHelm(options.helm, options.namespace, run);
|
|
32
|
+
return;
|
|
33
|
+
}
|
|
34
|
+
// Kubernetes deployment rollback
|
|
35
|
+
if (options.k8s) {
|
|
36
|
+
await rollbackK8s(options.k8s, options.namespace, run);
|
|
37
|
+
return;
|
|
38
|
+
}
|
|
39
|
+
// Terraform state rollback (--terraform flag)
|
|
40
|
+
if (options.terraform) {
|
|
41
|
+
await rollbackTerraformState(options.tfDir ?? process.cwd(), run);
|
|
42
|
+
return;
|
|
43
|
+
}
|
|
44
|
+
// Terraform guidance (--tf flag)
|
|
45
|
+
if (options.tf) {
|
|
46
|
+
rollbackTerraformGuidance();
|
|
47
|
+
return;
|
|
48
|
+
}
|
|
49
|
+
ui.error('Specify a rollback target: --helm <release>, --k8s <deployment>, --tf, or --terraform');
|
|
50
|
+
ui.print('Usage:');
|
|
51
|
+
ui.print(' nimbus rollback --helm <release> [--namespace <ns>]');
|
|
52
|
+
ui.print(' nimbus rollback --k8s <deployment> [--namespace <ns>]');
|
|
53
|
+
ui.print(' nimbus rollback --tf');
|
|
54
|
+
ui.print(' nimbus rollback --terraform [--tf-dir <path>]');
|
|
55
|
+
}
|
|
56
|
+
async function rollbackHelm(release, namespace, run) {
|
|
57
|
+
const nsArgs = namespace ? ['--namespace', namespace] : [];
|
|
58
|
+
ui.info(`Fetching history for Helm release: ${release}`);
|
|
59
|
+
let historyOutput;
|
|
60
|
+
try {
|
|
61
|
+
historyOutput = run('helm', ['history', release, '--output', 'table', ...nsArgs]);
|
|
62
|
+
}
|
|
63
|
+
catch (e) {
|
|
64
|
+
const msg = e instanceof Error ? e.message : String(e);
|
|
65
|
+
ui.error(`Failed to fetch Helm history: ${msg}`);
|
|
66
|
+
return;
|
|
67
|
+
}
|
|
68
|
+
ui.newLine();
|
|
69
|
+
ui.print(historyOutput);
|
|
70
|
+
ui.newLine();
|
|
71
|
+
// Parse revision numbers from history output
|
|
72
|
+
const revisionLines = historyOutput
|
|
73
|
+
.split('\n')
|
|
74
|
+
.slice(1) // skip header
|
|
75
|
+
.filter(line => /^\d+/.test(line.trim()));
|
|
76
|
+
if (revisionLines.length === 0) {
|
|
77
|
+
ui.warning('No revision history found.');
|
|
78
|
+
return;
|
|
79
|
+
}
|
|
80
|
+
const revisionOptions = revisionLines.map(line => {
|
|
81
|
+
const parts = line.trim().split(/\s+/);
|
|
82
|
+
const rev = parts[0];
|
|
83
|
+
const status = parts[2] ?? '';
|
|
84
|
+
const description = parts.slice(4).join(' ') ?? '';
|
|
85
|
+
return {
|
|
86
|
+
value: rev,
|
|
87
|
+
label: `Revision ${rev} — ${status} ${description}`.trim(),
|
|
88
|
+
};
|
|
89
|
+
});
|
|
90
|
+
const selectedRevision = await select({
|
|
91
|
+
message: `Roll back ${release} to which revision?`,
|
|
92
|
+
options: revisionOptions,
|
|
93
|
+
});
|
|
94
|
+
if (!selectedRevision) {
|
|
95
|
+
ui.info('Rollback cancelled.');
|
|
96
|
+
return;
|
|
97
|
+
}
|
|
98
|
+
const confirmed = await confirm({
|
|
99
|
+
message: `Roll back ${release} to revision ${selectedRevision}? This will update the release.`,
|
|
100
|
+
defaultValue: false,
|
|
101
|
+
});
|
|
102
|
+
if (!confirmed) {
|
|
103
|
+
ui.info('Rollback cancelled.');
|
|
104
|
+
return;
|
|
105
|
+
}
|
|
106
|
+
ui.startSpinner({ message: `Rolling back ${release} to revision ${selectedRevision}...` });
|
|
107
|
+
try {
|
|
108
|
+
run('helm', ['rollback', release, selectedRevision, ...nsArgs]);
|
|
109
|
+
ui.stopSpinnerSuccess(`${release} rolled back to revision ${selectedRevision}`);
|
|
110
|
+
}
|
|
111
|
+
catch (e) {
|
|
112
|
+
const msg = e instanceof Error ? e.message : String(e);
|
|
113
|
+
ui.stopSpinnerFail(`Rollback failed: ${msg}`);
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
async function rollbackK8s(deployment, namespace, run) {
|
|
117
|
+
const nsArgs = namespace ? ['-n', namespace] : [];
|
|
118
|
+
const deployTarget = `deployment/${deployment}`;
|
|
119
|
+
ui.info(`Fetching rollout history for ${deployTarget}`);
|
|
120
|
+
let historyOutput;
|
|
121
|
+
try {
|
|
122
|
+
historyOutput = run('kubectl', ['rollout', 'history', deployTarget, ...nsArgs]);
|
|
123
|
+
}
|
|
124
|
+
catch (e) {
|
|
125
|
+
const msg = e instanceof Error ? e.message : String(e);
|
|
126
|
+
ui.error(`Failed to fetch rollout history: ${msg}`);
|
|
127
|
+
return;
|
|
128
|
+
}
|
|
129
|
+
ui.newLine();
|
|
130
|
+
ui.print(historyOutput);
|
|
131
|
+
ui.newLine();
|
|
132
|
+
const confirmed = await confirm({
|
|
133
|
+
message: `Undo the last rollout for ${deployTarget}? This will revert to the previous revision.`,
|
|
134
|
+
defaultValue: false,
|
|
135
|
+
});
|
|
136
|
+
if (!confirmed) {
|
|
137
|
+
ui.info('Rollback cancelled.');
|
|
138
|
+
return;
|
|
139
|
+
}
|
|
140
|
+
ui.startSpinner({ message: `Rolling back ${deployTarget}...` });
|
|
141
|
+
try {
|
|
142
|
+
run('kubectl', ['rollout', 'undo', deployTarget, ...nsArgs]);
|
|
143
|
+
ui.stopSpinnerSuccess(`${deployTarget} rolled back`);
|
|
144
|
+
// Show status
|
|
145
|
+
const status = run('kubectl', ['rollout', 'status', deployTarget, ...nsArgs]);
|
|
146
|
+
ui.print(status);
|
|
147
|
+
}
|
|
148
|
+
catch (e) {
|
|
149
|
+
const msg = e instanceof Error ? e.message : String(e);
|
|
150
|
+
ui.stopSpinnerFail(`Rollback failed: ${msg}`);
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
async function rollbackTerraformState(dir, run) {
|
|
154
|
+
const { existsSync, readdirSync } = await import('node:fs');
|
|
155
|
+
// Detect if terraform directory exists (look for *.tf files)
|
|
156
|
+
let hasTfFiles = false;
|
|
157
|
+
try {
|
|
158
|
+
hasTfFiles = existsSync(dir) && readdirSync(dir).some(f => f.endsWith('.tf'));
|
|
159
|
+
}
|
|
160
|
+
catch {
|
|
161
|
+
/* ignore */
|
|
162
|
+
}
|
|
163
|
+
if (!hasTfFiles) {
|
|
164
|
+
ui.warning(`No *.tf files found in ${dir}. Is this a Terraform directory?`);
|
|
165
|
+
ui.dim('Use --tf-dir <path> to specify a different directory.');
|
|
166
|
+
return;
|
|
167
|
+
}
|
|
168
|
+
// Show current workspace
|
|
169
|
+
let currentWorkspace = 'default';
|
|
170
|
+
try {
|
|
171
|
+
currentWorkspace = run('terraform', ['workspace', 'show']).trim();
|
|
172
|
+
ui.info(`Current Terraform workspace: ${currentWorkspace}`);
|
|
173
|
+
}
|
|
174
|
+
catch {
|
|
175
|
+
ui.dim('Could not determine current workspace (terraform not in PATH?)');
|
|
176
|
+
}
|
|
177
|
+
ui.newLine();
|
|
178
|
+
// Show state list
|
|
179
|
+
let stateList = '';
|
|
180
|
+
try {
|
|
181
|
+
stateList = run('terraform', ['state', 'list']).trim();
|
|
182
|
+
}
|
|
183
|
+
catch (e) {
|
|
184
|
+
const msg = e instanceof Error ? e.message : String(e);
|
|
185
|
+
ui.warning(`Could not list terraform state: ${msg}`);
|
|
186
|
+
}
|
|
187
|
+
if (stateList) {
|
|
188
|
+
ui.info('Current state resources:');
|
|
189
|
+
for (const line of stateList.split('\n').slice(0, 20)) {
|
|
190
|
+
ui.print(` ${line}`);
|
|
191
|
+
}
|
|
192
|
+
const total = stateList.split('\n').length;
|
|
193
|
+
if (total > 20)
|
|
194
|
+
ui.dim(` ... and ${total - 20} more`);
|
|
195
|
+
ui.newLine();
|
|
196
|
+
}
|
|
197
|
+
// Rollback guidance specific to state
|
|
198
|
+
ui.box({
|
|
199
|
+
title: `Terraform State Rollback — workspace: ${currentWorkspace}`,
|
|
200
|
+
content: [
|
|
201
|
+
'',
|
|
202
|
+
'To roll back Terraform state in workspace "' + currentWorkspace + '":',
|
|
203
|
+
'',
|
|
204
|
+
'1. Pull current state:',
|
|
205
|
+
' terraform state pull > state-backup.tfstate',
|
|
206
|
+
'',
|
|
207
|
+
'2. Push a previous state backup:',
|
|
208
|
+
' terraform state push <backup>.tfstate',
|
|
209
|
+
'',
|
|
210
|
+
'3. Or revert IaC code and re-apply:',
|
|
211
|
+
' git checkout <commit> -- *.tf',
|
|
212
|
+
' terraform plan && terraform apply',
|
|
213
|
+
'',
|
|
214
|
+
'4. For targeted resource rollback:',
|
|
215
|
+
' terraform apply -target=<resource>',
|
|
216
|
+
'',
|
|
217
|
+
'Run `nimbus chat` for AI-assisted rollback guidance.',
|
|
218
|
+
'',
|
|
219
|
+
],
|
|
220
|
+
style: 'rounded',
|
|
221
|
+
borderColor: 'yellow',
|
|
222
|
+
padding: 0,
|
|
223
|
+
});
|
|
224
|
+
ui.newLine();
|
|
225
|
+
}
|
|
226
|
+
function rollbackTerraformGuidance() {
|
|
227
|
+
ui.newLine();
|
|
228
|
+
ui.box({
|
|
229
|
+
title: 'Terraform Rollback Guidance',
|
|
230
|
+
content: [
|
|
231
|
+
'',
|
|
232
|
+
'Terraform does not have a built-in rollback command.',
|
|
233
|
+
'To restore previous infrastructure state, use one of these approaches:',
|
|
234
|
+
'',
|
|
235
|
+
'1. Revert IaC code to a previous git commit:',
|
|
236
|
+
' git checkout <commit> -- *.tf',
|
|
237
|
+
' terraform plan && terraform apply',
|
|
238
|
+
'',
|
|
239
|
+
'2. Restore state from a backup:',
|
|
240
|
+
' terraform state pull > current.tfstate',
|
|
241
|
+
' terraform state push <backup>.tfstate',
|
|
242
|
+
'',
|
|
243
|
+
'3. Use workspace-specific state:',
|
|
244
|
+
' terraform workspace select <env>',
|
|
245
|
+
' terraform state list',
|
|
246
|
+
'',
|
|
247
|
+
'4. Target a specific resource for rollback:',
|
|
248
|
+
' terraform apply -target=<resource>',
|
|
249
|
+
'',
|
|
250
|
+
'Run `nimbus chat` to get AI-assisted rollback guidance.',
|
|
251
|
+
'',
|
|
252
|
+
],
|
|
253
|
+
style: 'rounded',
|
|
254
|
+
borderColor: 'yellow',
|
|
255
|
+
padding: 0,
|
|
256
|
+
});
|
|
257
|
+
ui.newLine();
|
|
258
|
+
}
|
|
259
|
+
export default rollbackCommand;
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* nimbus rollout — Watch Kubernetes Deployment Rollouts
|
|
3
|
+
*
|
|
4
|
+
* Streams real-time rollout status for a Kubernetes deployment.
|
|
5
|
+
* Wraps `kubectl rollout status deployment/<name> --watch`.
|
|
6
|
+
*
|
|
7
|
+
* L1: New command for DevOps parity.
|
|
8
|
+
*
|
|
9
|
+
* Usage:
|
|
10
|
+
* nimbus rollout <deployment>
|
|
11
|
+
* nimbus rollout <deployment> --namespace <ns>
|
|
12
|
+
* nimbus rollout <deployment> --timeout 10m
|
|
13
|
+
*/
|
|
14
|
+
import { spawnExec } from '../tools/spawn-exec';
|
|
15
|
+
/**
|
|
16
|
+
* Run the nimbus rollout command.
|
|
17
|
+
* Streams kubectl rollout status with live output.
|
|
18
|
+
*/
|
|
19
|
+
export async function rolloutCommand(options) {
|
|
20
|
+
const { deployment, namespace, timeout = '5m' } = options;
|
|
21
|
+
const nsFlag = namespace ? `-n ${namespace}` : '';
|
|
22
|
+
const timeoutFlag = `--timeout=${timeout}`;
|
|
23
|
+
const command = `kubectl rollout status deployment/${deployment} ${nsFlag} ${timeoutFlag} --watch`.trim().replace(/\s+/g, ' ');
|
|
24
|
+
console.log(`Watching rollout: deployment/${deployment}${namespace ? ` (namespace: ${namespace})` : ''}`);
|
|
25
|
+
console.log(`Timeout: ${timeout}\n`);
|
|
26
|
+
const ac = new AbortController();
|
|
27
|
+
// Allow Ctrl+C to gracefully abort the rollout watch
|
|
28
|
+
process.on('SIGINT', () => {
|
|
29
|
+
console.log('\nRollout watch interrupted.');
|
|
30
|
+
ac.abort();
|
|
31
|
+
});
|
|
32
|
+
try {
|
|
33
|
+
const result = await spawnExec(command, {
|
|
34
|
+
onChunk: (chunk) => {
|
|
35
|
+
process.stdout.write(chunk);
|
|
36
|
+
},
|
|
37
|
+
timeout: parseTimeoutToMs(timeout),
|
|
38
|
+
});
|
|
39
|
+
const combined = [result.stdout, result.stderr].filter(Boolean).join('\n');
|
|
40
|
+
if (result.exitCode !== 0) {
|
|
41
|
+
console.error(`\nRollout failed (exit code ${result.exitCode}):`);
|
|
42
|
+
if (combined)
|
|
43
|
+
console.error(combined);
|
|
44
|
+
process.exitCode = 1;
|
|
45
|
+
}
|
|
46
|
+
else {
|
|
47
|
+
console.log('\nRollout complete.');
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
catch (err) {
|
|
51
|
+
if (err.message?.includes('aborted')) {
|
|
52
|
+
// Already printed abort message
|
|
53
|
+
}
|
|
54
|
+
else {
|
|
55
|
+
console.error(`Rollout watch error: ${err instanceof Error ? err.message : String(err)}`);
|
|
56
|
+
process.exitCode = 1;
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
/**
|
|
61
|
+
* Parse a kubectl-style timeout string (e.g. "5m", "30s", "2h") to milliseconds.
|
|
62
|
+
*/
|
|
63
|
+
export function parseTimeoutToMs(timeout) {
|
|
64
|
+
const match = timeout.match(/^(\d+)(s|m|h)$/);
|
|
65
|
+
if (!match)
|
|
66
|
+
return 300_000; // default 5 min
|
|
67
|
+
const value = parseInt(match[1]);
|
|
68
|
+
switch (match[2]) {
|
|
69
|
+
case 's': return value * 1000;
|
|
70
|
+
case 'm': return value * 60 * 1000;
|
|
71
|
+
case 'h': return value * 3600 * 1000;
|
|
72
|
+
default: return 300_000;
|
|
73
|
+
}
|
|
74
|
+
}
|
|
@@ -0,0 +1,307 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Runbook Command (G15)
|
|
3
|
+
*
|
|
4
|
+
* Load and execute operational runbooks as agent prompts.
|
|
5
|
+
*
|
|
6
|
+
* Runbook YAML format:
|
|
7
|
+
* name: rotate-certs
|
|
8
|
+
* description: Rotate TLS certs in prod namespace
|
|
9
|
+
* context: prod # profile to activate
|
|
10
|
+
* steps:
|
|
11
|
+
* - Check for expiring certs in all namespaces
|
|
12
|
+
* - Rotate each cert using cert-manager annotate
|
|
13
|
+
* - Verify new certs are valid and pods restarted
|
|
14
|
+
*
|
|
15
|
+
* Usage:
|
|
16
|
+
* nimbus runbook list
|
|
17
|
+
* nimbus runbook run <name> [--auto]
|
|
18
|
+
* nimbus runbook create <name>
|
|
19
|
+
*/
|
|
20
|
+
import { existsSync, readdirSync, readFileSync, writeFileSync, mkdirSync } from 'node:fs';
|
|
21
|
+
import { join, basename } from 'node:path';
|
|
22
|
+
import { homedir } from 'node:os';
|
|
23
|
+
// ---------------------------------------------------------------------------
|
|
24
|
+
// Helpers
|
|
25
|
+
// ---------------------------------------------------------------------------
|
|
26
|
+
/** Directories to scan for runbooks. */
|
|
27
|
+
const RUNBOOK_DIRS = [
|
|
28
|
+
join(homedir(), '.nimbus', 'runbooks'),
|
|
29
|
+
join(process.cwd(), 'runbooks'),
|
|
30
|
+
join(process.cwd(), 'docs', 'runbooks'),
|
|
31
|
+
join(process.cwd(), '.github', 'runbooks'),
|
|
32
|
+
];
|
|
33
|
+
/**
|
|
34
|
+
* Find all runbook YAML files in the standard directories.
|
|
35
|
+
*/
|
|
36
|
+
function findRunbooks() {
|
|
37
|
+
const results = [];
|
|
38
|
+
for (const dir of RUNBOOK_DIRS) {
|
|
39
|
+
if (!existsSync(dir))
|
|
40
|
+
continue;
|
|
41
|
+
try {
|
|
42
|
+
const files = readdirSync(dir).filter(f => /\.(yaml|yml)$/.test(f));
|
|
43
|
+
for (const file of files) {
|
|
44
|
+
results.push({ path: join(dir, file), dir, file });
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
catch { /* non-critical */ }
|
|
48
|
+
}
|
|
49
|
+
return results;
|
|
50
|
+
}
|
|
51
|
+
/**
|
|
52
|
+
* Minimal YAML parser for simple runbook format (no external dep).
|
|
53
|
+
* Supports both plain string steps and structured steps with if:/require_approval: fields.
|
|
54
|
+
*
|
|
55
|
+
* Structured step example:
|
|
56
|
+
* steps:
|
|
57
|
+
* - name: Check certs
|
|
58
|
+
* run: Check for expiring certs
|
|
59
|
+
* if: cert_count > 0
|
|
60
|
+
* require_approval: true
|
|
61
|
+
*/
|
|
62
|
+
function parseRunbookYaml(content) {
|
|
63
|
+
const lines = content.split('\n');
|
|
64
|
+
const def = { name: '', steps: [] };
|
|
65
|
+
let inSteps = false;
|
|
66
|
+
// We parse in two modes: simple (step is a plain `- text` line) and
|
|
67
|
+
// structured (step is a YAML mapping started by `- name:` or `- run:`).
|
|
68
|
+
let currentStep = null;
|
|
69
|
+
const flushStep = () => {
|
|
70
|
+
if (currentStep) {
|
|
71
|
+
def.steps.push(currentStep);
|
|
72
|
+
currentStep = null;
|
|
73
|
+
}
|
|
74
|
+
};
|
|
75
|
+
for (const raw of lines) {
|
|
76
|
+
const line = raw.trimEnd();
|
|
77
|
+
if (line.startsWith('#') || !line.trim())
|
|
78
|
+
continue;
|
|
79
|
+
if (line.startsWith('name:') && !inSteps) {
|
|
80
|
+
def.name = line.slice(5).trim().replace(/^['"]|['"]$/g, '');
|
|
81
|
+
}
|
|
82
|
+
else if (line.startsWith('description:') && !inSteps) {
|
|
83
|
+
def.description = line.slice(12).trim().replace(/^['"]|['"]$/g, '');
|
|
84
|
+
}
|
|
85
|
+
else if (line.startsWith('context:') && !inSteps) {
|
|
86
|
+
def.context = line.slice(8).trim().replace(/^['"]|['"]$/g, '');
|
|
87
|
+
}
|
|
88
|
+
else if (line.trim() === 'steps:') {
|
|
89
|
+
inSteps = true;
|
|
90
|
+
}
|
|
91
|
+
else if (inSteps) {
|
|
92
|
+
// Top-level step item: starts with exactly two spaces + "- "
|
|
93
|
+
if (/^ {0,2}- /.test(line)) {
|
|
94
|
+
flushStep();
|
|
95
|
+
const stepText = line.replace(/^\s*-\s*/, '').trim();
|
|
96
|
+
// Detect structured step: starts with "name:", "run:", "action:"
|
|
97
|
+
if (/^(name|run|action):/.test(stepText)) {
|
|
98
|
+
const val = stepText.replace(/^(name|run|action):\s*/, '').replace(/^['"]|['"]$/g, '');
|
|
99
|
+
currentStep = { text: val };
|
|
100
|
+
}
|
|
101
|
+
else if (stepText === '') {
|
|
102
|
+
// blank mapping start — next indented lines fill it in
|
|
103
|
+
currentStep = { text: '' };
|
|
104
|
+
}
|
|
105
|
+
else {
|
|
106
|
+
// Plain string step
|
|
107
|
+
def.steps.push({ text: stepText.replace(/^['"]|['"]$/g, '') });
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
else if (currentStep && /^\s+(name|run|action):/.test(line)) {
|
|
111
|
+
// Structured field continuation inside a step block
|
|
112
|
+
const val = line.replace(/^\s+(name|run|action):\s*/, '').replace(/^['"]|['"]$/g, '');
|
|
113
|
+
if (!currentStep.text)
|
|
114
|
+
currentStep.text = val;
|
|
115
|
+
}
|
|
116
|
+
else if (currentStep && /^\s+if:/.test(line)) {
|
|
117
|
+
const val = line.replace(/^\s+if:\s*/, '').replace(/^['"]|['"]$/g, '');
|
|
118
|
+
currentStep.if = val;
|
|
119
|
+
}
|
|
120
|
+
else if (currentStep && /^\s+require_approval:/.test(line)) {
|
|
121
|
+
const val = line.replace(/^\s+require_approval:\s*/, '').trim();
|
|
122
|
+
currentStep.require_approval = val === 'true';
|
|
123
|
+
}
|
|
124
|
+
else if (!/^\s/.test(line)) {
|
|
125
|
+
// Non-indented non-step line — exit step parsing
|
|
126
|
+
flushStep();
|
|
127
|
+
inSteps = false;
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
flushStep();
|
|
132
|
+
return def;
|
|
133
|
+
}
|
|
134
|
+
/**
|
|
135
|
+
* Build a multi-step agent prompt from a runbook definition.
|
|
136
|
+
* Supports GAP-24 conditional steps (if:) and approval gates (require_approval:).
|
|
137
|
+
*/
|
|
138
|
+
function buildRunbookPrompt(def) {
|
|
139
|
+
const parts = [
|
|
140
|
+
`# Runbook: ${def.name}`,
|
|
141
|
+
];
|
|
142
|
+
if (def.description)
|
|
143
|
+
parts.push(`\n${def.description}`);
|
|
144
|
+
if (def.context)
|
|
145
|
+
parts.push(`\nContext/profile: ${def.context}`);
|
|
146
|
+
parts.push('\n## Steps to execute in order:');
|
|
147
|
+
def.steps.forEach((step, i) => {
|
|
148
|
+
// GAP-24: In step parsing loop
|
|
149
|
+
const ifCondition = step.if;
|
|
150
|
+
const requireApproval = step.require_approval;
|
|
151
|
+
let stepText = `Step ${i + 1}: ${step.text}`;
|
|
152
|
+
if (ifCondition) {
|
|
153
|
+
stepText += `\n [CONDITIONAL: Only proceed if: ${ifCondition}]`;
|
|
154
|
+
stepText += `\nAfter completing this step, check: ${ifCondition}. If the condition evaluates to false, stop and report the status without continuing to the next step.`;
|
|
155
|
+
}
|
|
156
|
+
if (requireApproval) {
|
|
157
|
+
stepText += `\n [REQUIRES APPROVAL: State this step's plan and wait for explicit user approval before executing]`;
|
|
158
|
+
stepText = `IMPORTANT: Before executing this step, explicitly state what you are about to do and wait for the user to say "approve" or "yes" before proceeding.\n` + stepText;
|
|
159
|
+
}
|
|
160
|
+
parts.push(stepText);
|
|
161
|
+
});
|
|
162
|
+
parts.push('\nExecute each step in sequence. Check for errors after each step before proceeding. Report progress clearly.');
|
|
163
|
+
return parts.join('\n');
|
|
164
|
+
}
|
|
165
|
+
// ---------------------------------------------------------------------------
|
|
166
|
+
// Subcommands
|
|
167
|
+
// ---------------------------------------------------------------------------
|
|
168
|
+
async function runbookList() {
|
|
169
|
+
const runbooks = findRunbooks();
|
|
170
|
+
if (runbooks.length === 0) {
|
|
171
|
+
console.log('No runbooks found.');
|
|
172
|
+
console.log('');
|
|
173
|
+
console.log('Create one at:');
|
|
174
|
+
for (const dir of RUNBOOK_DIRS.slice(0, 2)) {
|
|
175
|
+
console.log(` ${dir}/<name>.yaml`);
|
|
176
|
+
}
|
|
177
|
+
return;
|
|
178
|
+
}
|
|
179
|
+
console.log('Available runbooks:\n');
|
|
180
|
+
for (const rb of runbooks) {
|
|
181
|
+
try {
|
|
182
|
+
const content = readFileSync(rb.path, 'utf-8');
|
|
183
|
+
const def = parseRunbookYaml(content);
|
|
184
|
+
console.log(` ${def.name.padEnd(24)} ${def.description ?? ''}`);
|
|
185
|
+
console.log(` ${''.padEnd(24)} (${rb.path})`);
|
|
186
|
+
}
|
|
187
|
+
catch {
|
|
188
|
+
console.log(` ${basename(rb.path)} (parse error)`);
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
async function runbookRun(name, options) {
|
|
193
|
+
const runbooks = findRunbooks();
|
|
194
|
+
const match = runbooks.find(rb => {
|
|
195
|
+
try {
|
|
196
|
+
const def = parseRunbookYaml(readFileSync(rb.path, 'utf-8'));
|
|
197
|
+
return def.name === name || basename(rb.file, '.yaml') === name || basename(rb.file, '.yml') === name;
|
|
198
|
+
}
|
|
199
|
+
catch {
|
|
200
|
+
return false;
|
|
201
|
+
}
|
|
202
|
+
});
|
|
203
|
+
if (!match) {
|
|
204
|
+
console.error(`Runbook not found: ${name}`);
|
|
205
|
+
console.log('Run "nimbus runbook list" to see available runbooks.');
|
|
206
|
+
process.exit(1);
|
|
207
|
+
}
|
|
208
|
+
const def = parseRunbookYaml(readFileSync(match.path, 'utf-8'));
|
|
209
|
+
const prompt = buildRunbookPrompt(def);
|
|
210
|
+
console.log(`Executing runbook: ${def.name}`);
|
|
211
|
+
if (def.description)
|
|
212
|
+
console.log(`Description: ${def.description}`);
|
|
213
|
+
console.log(`Steps: ${def.steps.length}`);
|
|
214
|
+
console.log('');
|
|
215
|
+
if (!options.auto) {
|
|
216
|
+
// Prompt for confirmation in interactive mode
|
|
217
|
+
const readline = await import('node:readline');
|
|
218
|
+
const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
|
|
219
|
+
const answer = await new Promise(resolve => {
|
|
220
|
+
rl.question('Proceed with runbook execution? (y/N) ', resolve);
|
|
221
|
+
});
|
|
222
|
+
rl.close();
|
|
223
|
+
if (!answer.toLowerCase().startsWith('y')) {
|
|
224
|
+
console.log('Runbook execution cancelled.');
|
|
225
|
+
return;
|
|
226
|
+
}
|
|
227
|
+
}
|
|
228
|
+
const { chatCommand } = await import('./chat');
|
|
229
|
+
await chatCommand({ initialPrompt: prompt, mode: 'deploy' });
|
|
230
|
+
}
|
|
231
|
+
async function runbookCreate(name) {
|
|
232
|
+
const targetDir = join(homedir(), '.nimbus', 'runbooks');
|
|
233
|
+
mkdirSync(targetDir, { recursive: true });
|
|
234
|
+
const targetPath = join(targetDir, `${name}.yaml`);
|
|
235
|
+
if (existsSync(targetPath)) {
|
|
236
|
+
console.error(`Runbook already exists: ${targetPath}`);
|
|
237
|
+
process.exit(1);
|
|
238
|
+
}
|
|
239
|
+
const readline = await import('node:readline');
|
|
240
|
+
const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
|
|
241
|
+
const question = (q) => new Promise(resolve => rl.question(q, resolve));
|
|
242
|
+
console.log(`Creating runbook: ${name}`);
|
|
243
|
+
const description = await question('Description: ');
|
|
244
|
+
const context = await question('Context/profile (optional, e.g. "prod"): ');
|
|
245
|
+
console.log('Enter steps (empty line to finish):');
|
|
246
|
+
const steps = [];
|
|
247
|
+
let stepNum = 1;
|
|
248
|
+
while (true) {
|
|
249
|
+
const step = await question(`Step ${stepNum}: `);
|
|
250
|
+
if (!step.trim())
|
|
251
|
+
break;
|
|
252
|
+
steps.push(step.trim());
|
|
253
|
+
stepNum++;
|
|
254
|
+
}
|
|
255
|
+
rl.close();
|
|
256
|
+
if (steps.length === 0) {
|
|
257
|
+
console.error('Runbook must have at least one step.');
|
|
258
|
+
process.exit(1);
|
|
259
|
+
}
|
|
260
|
+
const yaml = [
|
|
261
|
+
`name: ${name}`,
|
|
262
|
+
`description: ${description}`,
|
|
263
|
+
context ? `context: ${context}` : '# context: prod',
|
|
264
|
+
'steps:',
|
|
265
|
+
...steps.map(s => ` - ${s}`),
|
|
266
|
+
].join('\n') + '\n';
|
|
267
|
+
writeFileSync(targetPath, yaml, 'utf-8');
|
|
268
|
+
console.log(`\nRunbook saved to: ${targetPath}`);
|
|
269
|
+
console.log(`Run with: nimbus runbook run ${name}`);
|
|
270
|
+
}
|
|
271
|
+
// ---------------------------------------------------------------------------
|
|
272
|
+
// Main export
|
|
273
|
+
// ---------------------------------------------------------------------------
|
|
274
|
+
export async function runbookCommand(subcommand, args) {
|
|
275
|
+
switch (subcommand) {
|
|
276
|
+
case 'list':
|
|
277
|
+
case 'ls':
|
|
278
|
+
await runbookList();
|
|
279
|
+
break;
|
|
280
|
+
case 'run': {
|
|
281
|
+
const name = args[0];
|
|
282
|
+
if (!name) {
|
|
283
|
+
console.error('Usage: nimbus runbook run <name> [--auto]');
|
|
284
|
+
process.exit(1);
|
|
285
|
+
}
|
|
286
|
+
await runbookRun(name, { auto: args.includes('--auto') });
|
|
287
|
+
break;
|
|
288
|
+
}
|
|
289
|
+
case 'create':
|
|
290
|
+
case 'new': {
|
|
291
|
+
const name = args[0];
|
|
292
|
+
if (!name) {
|
|
293
|
+
console.error('Usage: nimbus runbook create <name>');
|
|
294
|
+
process.exit(1);
|
|
295
|
+
}
|
|
296
|
+
await runbookCreate(name);
|
|
297
|
+
break;
|
|
298
|
+
}
|
|
299
|
+
default:
|
|
300
|
+
console.log('Usage: nimbus runbook <list|run|create>');
|
|
301
|
+
console.log('');
|
|
302
|
+
console.log(' list List available runbooks');
|
|
303
|
+
console.log(' run <name> Execute a runbook as an agent prompt');
|
|
304
|
+
console.log(' create <name> Create a new runbook interactively');
|
|
305
|
+
break;
|
|
306
|
+
}
|
|
307
|
+
}
|