@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,199 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* History Manager
|
|
3
|
+
*
|
|
4
|
+
* Manages command history persistence at ~/.nimbus/history.json
|
|
5
|
+
* with remote sync to the State Service for cross-session persistence.
|
|
6
|
+
*/
|
|
7
|
+
import * as fs from 'fs';
|
|
8
|
+
import * as path from 'path';
|
|
9
|
+
import * as os from 'os';
|
|
10
|
+
const HISTORY_FILE_VERSION = 1;
|
|
11
|
+
const MAX_HISTORY_ENTRIES = 1000;
|
|
12
|
+
/**
|
|
13
|
+
* Generate a unique ID for history entries
|
|
14
|
+
*/
|
|
15
|
+
function generateId() {
|
|
16
|
+
return `${Date.now()}-${Math.random().toString(36).substr(2, 9)}`;
|
|
17
|
+
}
|
|
18
|
+
/**
|
|
19
|
+
* Create an empty history file structure
|
|
20
|
+
*/
|
|
21
|
+
function createEmptyHistoryFile() {
|
|
22
|
+
const now = new Date().toISOString();
|
|
23
|
+
return {
|
|
24
|
+
version: HISTORY_FILE_VERSION,
|
|
25
|
+
entries: [],
|
|
26
|
+
createdAt: now,
|
|
27
|
+
updatedAt: now,
|
|
28
|
+
};
|
|
29
|
+
}
|
|
30
|
+
/**
|
|
31
|
+
* HistoryManager class for command history persistence
|
|
32
|
+
* with optional remote sync to State Service.
|
|
33
|
+
*/
|
|
34
|
+
export class HistoryManager {
|
|
35
|
+
historyPath;
|
|
36
|
+
historyFile = null;
|
|
37
|
+
constructor(historyPath) {
|
|
38
|
+
this.historyPath = historyPath || path.join(os.homedir(), '.nimbus', 'history.json');
|
|
39
|
+
}
|
|
40
|
+
/**
|
|
41
|
+
* Get the path to the history file
|
|
42
|
+
*/
|
|
43
|
+
getHistoryPath() {
|
|
44
|
+
return this.historyPath;
|
|
45
|
+
}
|
|
46
|
+
/**
|
|
47
|
+
* Ensure the history directory exists
|
|
48
|
+
*/
|
|
49
|
+
ensureDirectory() {
|
|
50
|
+
const dir = path.dirname(this.historyPath);
|
|
51
|
+
if (!fs.existsSync(dir)) {
|
|
52
|
+
fs.mkdirSync(dir, { recursive: true, mode: 0o700 });
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
/**
|
|
56
|
+
* Load history file from disk, creating if necessary
|
|
57
|
+
*/
|
|
58
|
+
load() {
|
|
59
|
+
if (this.historyFile) {
|
|
60
|
+
return this.historyFile;
|
|
61
|
+
}
|
|
62
|
+
this.ensureDirectory();
|
|
63
|
+
if (!fs.existsSync(this.historyPath)) {
|
|
64
|
+
this.historyFile = createEmptyHistoryFile();
|
|
65
|
+
return this.historyFile;
|
|
66
|
+
}
|
|
67
|
+
try {
|
|
68
|
+
const content = fs.readFileSync(this.historyPath, 'utf-8');
|
|
69
|
+
const parsed = JSON.parse(content);
|
|
70
|
+
// Validate version and migrate if needed
|
|
71
|
+
if (parsed.version !== HISTORY_FILE_VERSION) {
|
|
72
|
+
parsed.version = HISTORY_FILE_VERSION;
|
|
73
|
+
}
|
|
74
|
+
// Ensure required fields exist
|
|
75
|
+
parsed.entries = parsed.entries || [];
|
|
76
|
+
this.historyFile = parsed;
|
|
77
|
+
return this.historyFile;
|
|
78
|
+
}
|
|
79
|
+
catch {
|
|
80
|
+
// If file is corrupted, start fresh
|
|
81
|
+
this.historyFile = createEmptyHistoryFile();
|
|
82
|
+
return this.historyFile;
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
/**
|
|
86
|
+
* Save history file to disk
|
|
87
|
+
*/
|
|
88
|
+
save(historyFile) {
|
|
89
|
+
this.ensureDirectory();
|
|
90
|
+
const fileToSave = historyFile || this.historyFile;
|
|
91
|
+
if (!fileToSave) {
|
|
92
|
+
throw new Error('No history file to save');
|
|
93
|
+
}
|
|
94
|
+
fileToSave.updatedAt = new Date().toISOString();
|
|
95
|
+
this.historyFile = fileToSave;
|
|
96
|
+
const content = JSON.stringify(fileToSave, null, 2);
|
|
97
|
+
fs.writeFileSync(this.historyPath, content, { mode: 0o600 });
|
|
98
|
+
}
|
|
99
|
+
/**
|
|
100
|
+
* Add a new entry to history
|
|
101
|
+
*/
|
|
102
|
+
addEntry(command, args, metadata) {
|
|
103
|
+
const historyFile = this.load();
|
|
104
|
+
const entry = {
|
|
105
|
+
id: generateId(),
|
|
106
|
+
command,
|
|
107
|
+
args,
|
|
108
|
+
timestamp: new Date().toISOString(),
|
|
109
|
+
status: 'pending',
|
|
110
|
+
metadata,
|
|
111
|
+
};
|
|
112
|
+
historyFile.entries.unshift(entry);
|
|
113
|
+
// Trim to max entries
|
|
114
|
+
if (historyFile.entries.length > MAX_HISTORY_ENTRIES) {
|
|
115
|
+
historyFile.entries = historyFile.entries.slice(0, MAX_HISTORY_ENTRIES);
|
|
116
|
+
}
|
|
117
|
+
this.save(historyFile);
|
|
118
|
+
return entry;
|
|
119
|
+
}
|
|
120
|
+
/**
|
|
121
|
+
* Update an existing entry
|
|
122
|
+
*/
|
|
123
|
+
updateEntry(id, updates) {
|
|
124
|
+
const historyFile = this.load();
|
|
125
|
+
const entryIndex = historyFile.entries.findIndex(e => e.id === id);
|
|
126
|
+
if (entryIndex === -1) {
|
|
127
|
+
return null;
|
|
128
|
+
}
|
|
129
|
+
historyFile.entries[entryIndex] = {
|
|
130
|
+
...historyFile.entries[entryIndex],
|
|
131
|
+
...updates,
|
|
132
|
+
};
|
|
133
|
+
this.save(historyFile);
|
|
134
|
+
return historyFile.entries[entryIndex];
|
|
135
|
+
}
|
|
136
|
+
/**
|
|
137
|
+
* Complete an entry with success or failure
|
|
138
|
+
*/
|
|
139
|
+
completeEntry(id, status, duration, result) {
|
|
140
|
+
return this.updateEntry(id, { status, duration, result });
|
|
141
|
+
}
|
|
142
|
+
/**
|
|
143
|
+
* Get history entries with optional filtering.
|
|
144
|
+
* Reads from local file storage.
|
|
145
|
+
*/
|
|
146
|
+
async getEntries(options = {}) {
|
|
147
|
+
// Read from local file
|
|
148
|
+
const historyFile = this.load();
|
|
149
|
+
let entries = [...historyFile.entries];
|
|
150
|
+
// Filter by command
|
|
151
|
+
if (options.command) {
|
|
152
|
+
entries = entries.filter(e => e.command.toLowerCase().includes(options.command.toLowerCase()));
|
|
153
|
+
}
|
|
154
|
+
// Filter by status
|
|
155
|
+
if (options.status) {
|
|
156
|
+
entries = entries.filter(e => e.status === options.status);
|
|
157
|
+
}
|
|
158
|
+
// Filter by since date
|
|
159
|
+
if (options.since) {
|
|
160
|
+
const sinceDate = new Date(options.since);
|
|
161
|
+
entries = entries.filter(e => new Date(e.timestamp) >= sinceDate);
|
|
162
|
+
}
|
|
163
|
+
// Filter by until date
|
|
164
|
+
if (options.until) {
|
|
165
|
+
const untilDate = new Date(options.until);
|
|
166
|
+
entries = entries.filter(e => new Date(e.timestamp) <= untilDate);
|
|
167
|
+
}
|
|
168
|
+
// Apply limit
|
|
169
|
+
if (options.limit && options.limit > 0) {
|
|
170
|
+
entries = entries.slice(0, options.limit);
|
|
171
|
+
}
|
|
172
|
+
return entries;
|
|
173
|
+
}
|
|
174
|
+
/**
|
|
175
|
+
* Get a single entry by ID
|
|
176
|
+
*/
|
|
177
|
+
getEntry(id) {
|
|
178
|
+
const historyFile = this.load();
|
|
179
|
+
return historyFile.entries.find(e => e.id === id) || null;
|
|
180
|
+
}
|
|
181
|
+
/**
|
|
182
|
+
* Clear all history
|
|
183
|
+
*/
|
|
184
|
+
clear() {
|
|
185
|
+
this.historyFile = createEmptyHistoryFile();
|
|
186
|
+
this.save();
|
|
187
|
+
}
|
|
188
|
+
/**
|
|
189
|
+
* Reload history file from disk
|
|
190
|
+
*/
|
|
191
|
+
reload() {
|
|
192
|
+
this.historyFile = null;
|
|
193
|
+
return this.load();
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
/**
|
|
197
|
+
* Singleton instance for global access
|
|
198
|
+
*/
|
|
199
|
+
export const historyManager = new HistoryManager();
|
|
@@ -0,0 +1,318 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Hooks Configuration
|
|
3
|
+
*
|
|
4
|
+
* Parses and validates `.nimbus/hooks.yaml` configuration files.
|
|
5
|
+
* Provides types and utilities for the Nimbus hooks system that allows
|
|
6
|
+
* users to run custom scripts before/after tool invocations.
|
|
7
|
+
*/
|
|
8
|
+
import * as fs from 'fs';
|
|
9
|
+
import * as path from 'path';
|
|
10
|
+
/** All valid hook event names for validation */
|
|
11
|
+
const VALID_HOOK_EVENTS = [
|
|
12
|
+
'PreToolUse',
|
|
13
|
+
'PostToolUse',
|
|
14
|
+
'PermissionRequest',
|
|
15
|
+
];
|
|
16
|
+
/** Default timeout in milliseconds for hook execution */
|
|
17
|
+
export const DEFAULT_HOOK_TIMEOUT = 30_000;
|
|
18
|
+
/**
|
|
19
|
+
* Parse raw YAML text into an array of meaningful lines with indentation info.
|
|
20
|
+
* Strips comments and blank lines.
|
|
21
|
+
*
|
|
22
|
+
* @param text - Raw YAML content
|
|
23
|
+
* @returns Array of parsed lines with indentation levels
|
|
24
|
+
*/
|
|
25
|
+
function tokenizeYaml(text) {
|
|
26
|
+
const lines = [];
|
|
27
|
+
for (const raw of text.split('\n')) {
|
|
28
|
+
// Strip inline comments (but not inside quoted strings)
|
|
29
|
+
const withoutComment = raw.replace(/#.*$/, '');
|
|
30
|
+
const trimmed = withoutComment.trimEnd();
|
|
31
|
+
if (trimmed.length === 0) {
|
|
32
|
+
continue;
|
|
33
|
+
}
|
|
34
|
+
const indent = trimmed.search(/\S/);
|
|
35
|
+
if (indent === -1) {
|
|
36
|
+
continue;
|
|
37
|
+
}
|
|
38
|
+
lines.push({ indent, content: trimmed.trim() });
|
|
39
|
+
}
|
|
40
|
+
return lines;
|
|
41
|
+
}
|
|
42
|
+
/**
|
|
43
|
+
* Remove surrounding quotes (single or double) from a string value.
|
|
44
|
+
*
|
|
45
|
+
* @param value - Potentially quoted string
|
|
46
|
+
* @returns Unquoted string
|
|
47
|
+
*/
|
|
48
|
+
function unquote(value) {
|
|
49
|
+
if ((value.startsWith('"') && value.endsWith('"')) ||
|
|
50
|
+
(value.startsWith("'") && value.endsWith("'"))) {
|
|
51
|
+
return value.slice(1, -1);
|
|
52
|
+
}
|
|
53
|
+
return value;
|
|
54
|
+
}
|
|
55
|
+
/**
|
|
56
|
+
* Coerce a raw YAML string value to the appropriate JS primitive.
|
|
57
|
+
*
|
|
58
|
+
* @param raw - Raw string value from YAML
|
|
59
|
+
* @returns Coerced value (string, number, boolean, or null)
|
|
60
|
+
*/
|
|
61
|
+
function coerceValue(raw) {
|
|
62
|
+
if (raw === 'true') {
|
|
63
|
+
return true;
|
|
64
|
+
}
|
|
65
|
+
if (raw === 'false') {
|
|
66
|
+
return false;
|
|
67
|
+
}
|
|
68
|
+
if (raw === 'null' || raw === '~') {
|
|
69
|
+
return null;
|
|
70
|
+
}
|
|
71
|
+
const unquoted = unquote(raw);
|
|
72
|
+
if (unquoted !== raw) {
|
|
73
|
+
// Was quoted -- keep as string
|
|
74
|
+
return unquoted;
|
|
75
|
+
}
|
|
76
|
+
// Try number coercion
|
|
77
|
+
if (raw !== '' && !isNaN(Number(raw))) {
|
|
78
|
+
return Number(raw);
|
|
79
|
+
}
|
|
80
|
+
return raw;
|
|
81
|
+
}
|
|
82
|
+
/**
|
|
83
|
+
* Minimal recursive-descent YAML parser.
|
|
84
|
+
*
|
|
85
|
+
* Handles the subset of YAML needed for hooks configuration:
|
|
86
|
+
* - Top-level maps
|
|
87
|
+
* - Arrays of objects (using `- key: value` syntax)
|
|
88
|
+
* - Scalar values (string, number, boolean)
|
|
89
|
+
* - Nested maps
|
|
90
|
+
*
|
|
91
|
+
* This is intentionally NOT a full YAML parser. It covers the structure
|
|
92
|
+
* required by `.nimbus/hooks.yaml` without requiring an external dependency.
|
|
93
|
+
*
|
|
94
|
+
* @param text - Raw YAML content
|
|
95
|
+
* @returns Parsed object
|
|
96
|
+
*/
|
|
97
|
+
function parseYaml(text) {
|
|
98
|
+
const lines = tokenizeYaml(text);
|
|
99
|
+
let pos = 0;
|
|
100
|
+
/**
|
|
101
|
+
* Parse a mapping (object) at the given indentation level.
|
|
102
|
+
*/
|
|
103
|
+
function parseMapping(minIndent) {
|
|
104
|
+
const result = {};
|
|
105
|
+
while (pos < lines.length) {
|
|
106
|
+
const line = lines[pos];
|
|
107
|
+
// If the line is at a lower indent, we've left this mapping
|
|
108
|
+
if (line.indent < minIndent) {
|
|
109
|
+
break;
|
|
110
|
+
}
|
|
111
|
+
// Skip lines that are deeper than expected (shouldn't happen in
|
|
112
|
+
// well-formed input, but be defensive)
|
|
113
|
+
if (line.indent > minIndent && !line.content.startsWith('- ')) {
|
|
114
|
+
pos++;
|
|
115
|
+
continue;
|
|
116
|
+
}
|
|
117
|
+
// Array items are handled by the caller (parseArray)
|
|
118
|
+
if (line.content.startsWith('- ')) {
|
|
119
|
+
break;
|
|
120
|
+
}
|
|
121
|
+
const colonIdx = line.content.indexOf(':');
|
|
122
|
+
if (colonIdx === -1) {
|
|
123
|
+
pos++;
|
|
124
|
+
continue;
|
|
125
|
+
}
|
|
126
|
+
const key = line.content.slice(0, colonIdx).trim();
|
|
127
|
+
const rest = line.content.slice(colonIdx + 1).trim();
|
|
128
|
+
pos++;
|
|
129
|
+
if (rest.length > 0) {
|
|
130
|
+
// Inline scalar value: `key: value`
|
|
131
|
+
result[key] = coerceValue(rest);
|
|
132
|
+
}
|
|
133
|
+
else {
|
|
134
|
+
// Value is on subsequent indented lines -- either a nested map or array
|
|
135
|
+
if (pos < lines.length && lines[pos].indent > minIndent) {
|
|
136
|
+
const childIndent = lines[pos].indent;
|
|
137
|
+
if (lines[pos].content.startsWith('- ')) {
|
|
138
|
+
result[key] = parseArray(childIndent);
|
|
139
|
+
}
|
|
140
|
+
else {
|
|
141
|
+
result[key] = parseMapping(childIndent);
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
else {
|
|
145
|
+
// Empty value
|
|
146
|
+
result[key] = null;
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
return result;
|
|
151
|
+
}
|
|
152
|
+
/**
|
|
153
|
+
* Parse an array at the given indentation level.
|
|
154
|
+
* Each array element starts with `- ` and can contain inline key-value
|
|
155
|
+
* pairs or a nested block mapping.
|
|
156
|
+
*/
|
|
157
|
+
function parseArray(minIndent) {
|
|
158
|
+
const result = [];
|
|
159
|
+
while (pos < lines.length) {
|
|
160
|
+
const line = lines[pos];
|
|
161
|
+
if (line.indent < minIndent) {
|
|
162
|
+
break;
|
|
163
|
+
}
|
|
164
|
+
if (!line.content.startsWith('- ')) {
|
|
165
|
+
break;
|
|
166
|
+
}
|
|
167
|
+
// Strip the leading `- `
|
|
168
|
+
const afterDash = line.content.slice(2).trim();
|
|
169
|
+
pos++;
|
|
170
|
+
if (afterDash.includes(':')) {
|
|
171
|
+
// Inline object start: `- key: value`
|
|
172
|
+
const obj = {};
|
|
173
|
+
const colonIdx = afterDash.indexOf(':');
|
|
174
|
+
const key = afterDash.slice(0, colonIdx).trim();
|
|
175
|
+
const val = afterDash.slice(colonIdx + 1).trim();
|
|
176
|
+
obj[key] = val.length > 0 ? coerceValue(val) : null;
|
|
177
|
+
// Collect subsequent indented key-value pairs belonging to the same item
|
|
178
|
+
while (pos < lines.length) {
|
|
179
|
+
const next = lines[pos];
|
|
180
|
+
// Must be indented deeper than the `- ` marker and NOT be another array item
|
|
181
|
+
if (next.indent <= minIndent || next.content.startsWith('- ')) {
|
|
182
|
+
break;
|
|
183
|
+
}
|
|
184
|
+
const nextColon = next.content.indexOf(':');
|
|
185
|
+
if (nextColon === -1) {
|
|
186
|
+
pos++;
|
|
187
|
+
continue;
|
|
188
|
+
}
|
|
189
|
+
const nk = next.content.slice(0, nextColon).trim();
|
|
190
|
+
const nv = next.content.slice(nextColon + 1).trim();
|
|
191
|
+
obj[nk] = nv.length > 0 ? coerceValue(nv) : null;
|
|
192
|
+
pos++;
|
|
193
|
+
}
|
|
194
|
+
result.push(obj);
|
|
195
|
+
}
|
|
196
|
+
else if (afterDash.length > 0) {
|
|
197
|
+
// Scalar array element: `- value`
|
|
198
|
+
result.push(coerceValue(afterDash));
|
|
199
|
+
}
|
|
200
|
+
else {
|
|
201
|
+
// Block-style object under `- `
|
|
202
|
+
if (pos < lines.length && lines[pos].indent > minIndent) {
|
|
203
|
+
const childIndent = lines[pos].indent;
|
|
204
|
+
result.push(parseMapping(childIndent));
|
|
205
|
+
}
|
|
206
|
+
}
|
|
207
|
+
}
|
|
208
|
+
return result;
|
|
209
|
+
}
|
|
210
|
+
return parseMapping(0);
|
|
211
|
+
}
|
|
212
|
+
// ---------------------------------------------------------------------------
|
|
213
|
+
// Validation
|
|
214
|
+
// ---------------------------------------------------------------------------
|
|
215
|
+
/**
|
|
216
|
+
* Validate a single hook definition and return any errors found.
|
|
217
|
+
*
|
|
218
|
+
* Checks:
|
|
219
|
+
* - `match` is a non-empty string that compiles as a valid RegExp
|
|
220
|
+
* - `command` is a non-empty string
|
|
221
|
+
* - `timeout`, if provided, is a positive number
|
|
222
|
+
*
|
|
223
|
+
* @param hook - The hook definition to validate
|
|
224
|
+
* @returns Array of human-readable error strings (empty if valid)
|
|
225
|
+
*/
|
|
226
|
+
export function validateHookDefinition(hook) {
|
|
227
|
+
const errors = [];
|
|
228
|
+
// match
|
|
229
|
+
if (typeof hook.match !== 'string' || hook.match.length === 0) {
|
|
230
|
+
errors.push('hook "match" must be a non-empty string');
|
|
231
|
+
}
|
|
232
|
+
else {
|
|
233
|
+
try {
|
|
234
|
+
new RegExp(hook.match);
|
|
235
|
+
}
|
|
236
|
+
catch {
|
|
237
|
+
errors.push(`hook "match" is not a valid regex: "${hook.match}"`);
|
|
238
|
+
}
|
|
239
|
+
}
|
|
240
|
+
// command
|
|
241
|
+
if (typeof hook.command !== 'string' || hook.command.length === 0) {
|
|
242
|
+
errors.push('hook "command" must be a non-empty string');
|
|
243
|
+
}
|
|
244
|
+
// timeout (optional)
|
|
245
|
+
if (hook.timeout !== undefined) {
|
|
246
|
+
if (typeof hook.timeout !== 'number' || hook.timeout <= 0) {
|
|
247
|
+
errors.push('hook "timeout" must be a positive number');
|
|
248
|
+
}
|
|
249
|
+
}
|
|
250
|
+
return errors;
|
|
251
|
+
}
|
|
252
|
+
// ---------------------------------------------------------------------------
|
|
253
|
+
// Loader
|
|
254
|
+
// ---------------------------------------------------------------------------
|
|
255
|
+
/**
|
|
256
|
+
* Load and validate a hooks configuration from `<projectDir>/.nimbus/hooks.yaml`.
|
|
257
|
+
*
|
|
258
|
+
* @param projectDir - Absolute or relative path to the project root directory
|
|
259
|
+
* @returns Parsed and validated `HooksConfig`, or `null` if the file does not exist
|
|
260
|
+
* @throws Error if the file exists but contains invalid configuration
|
|
261
|
+
*
|
|
262
|
+
* @example
|
|
263
|
+
* ```ts
|
|
264
|
+
* const config = loadHooksConfig('/path/to/project');
|
|
265
|
+
* if (config) {
|
|
266
|
+
* console.log(config.hooks.PreToolUse);
|
|
267
|
+
* }
|
|
268
|
+
* ```
|
|
269
|
+
*/
|
|
270
|
+
export function loadHooksConfig(projectDir) {
|
|
271
|
+
const configPath = path.join(projectDir, '.nimbus', 'hooks.yaml');
|
|
272
|
+
if (!fs.existsSync(configPath)) {
|
|
273
|
+
return null;
|
|
274
|
+
}
|
|
275
|
+
const raw = fs.readFileSync(configPath, 'utf-8');
|
|
276
|
+
const parsed = parseYaml(raw);
|
|
277
|
+
// Validate top-level structure
|
|
278
|
+
if (!parsed.hooks || typeof parsed.hooks !== 'object') {
|
|
279
|
+
throw new Error(`Invalid hooks config at ${configPath}: missing top-level "hooks" key`);
|
|
280
|
+
}
|
|
281
|
+
const hooksRaw = parsed.hooks;
|
|
282
|
+
// Build the validated config, ensuring all three event types are present
|
|
283
|
+
const config = {
|
|
284
|
+
hooks: {
|
|
285
|
+
PreToolUse: [],
|
|
286
|
+
PostToolUse: [],
|
|
287
|
+
PermissionRequest: [],
|
|
288
|
+
},
|
|
289
|
+
};
|
|
290
|
+
for (const [eventName, definitions] of Object.entries(hooksRaw)) {
|
|
291
|
+
// Validate event name
|
|
292
|
+
if (!VALID_HOOK_EVENTS.includes(eventName)) {
|
|
293
|
+
throw new Error(`Invalid hooks config at ${configPath}: unknown hook event "${eventName}". ` +
|
|
294
|
+
`Valid events: ${VALID_HOOK_EVENTS.join(', ')}`);
|
|
295
|
+
}
|
|
296
|
+
const event = eventName;
|
|
297
|
+
if (!Array.isArray(definitions)) {
|
|
298
|
+
throw new Error(`Invalid hooks config at ${configPath}: "${eventName}" must be an array of hook definitions`);
|
|
299
|
+
}
|
|
300
|
+
for (let i = 0; i < definitions.length; i++) {
|
|
301
|
+
const def = definitions[i];
|
|
302
|
+
if (typeof def !== 'object' || def === null) {
|
|
303
|
+
throw new Error(`Invalid hooks config at ${configPath}: ${eventName}[${i}] must be an object`);
|
|
304
|
+
}
|
|
305
|
+
const hookDef = {
|
|
306
|
+
match: String(def.match ?? ''),
|
|
307
|
+
command: String(def.command ?? ''),
|
|
308
|
+
timeout: def.timeout !== undefined && def.timeout !== null ? Number(def.timeout) : undefined,
|
|
309
|
+
};
|
|
310
|
+
const validationErrors = validateHookDefinition(hookDef);
|
|
311
|
+
if (validationErrors.length > 0) {
|
|
312
|
+
throw new Error(`Invalid hooks config at ${configPath}: ${eventName}[${i}]: ${validationErrors.join('; ')}`);
|
|
313
|
+
}
|
|
314
|
+
config.hooks[event].push(hookDef);
|
|
315
|
+
}
|
|
316
|
+
}
|
|
317
|
+
return config;
|
|
318
|
+
}
|