@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
|
@@ -104,22 +104,45 @@ export class AnthropicProvider extends BaseProvider {
|
|
|
104
104
|
}
|
|
105
105
|
}
|
|
106
106
|
|
|
107
|
+
/** H4: Format a rate limit error with actionable guidance. */
|
|
108
|
+
private formatRateLimitError(error: unknown): Error {
|
|
109
|
+
const retryAfter = (error as { headers?: Record<string, string> })?.headers?.['retry-after'];
|
|
110
|
+
const waitMsg = retryAfter ? `Wait ${retryAfter}s and retry` : 'Wait ~60 seconds and retry';
|
|
111
|
+
return new Error(
|
|
112
|
+
`Rate limit reached. Options:\n` +
|
|
113
|
+
` • ${waitMsg}\n` +
|
|
114
|
+
` • Switch to a faster model: /model claude-haiku-4-5\n` +
|
|
115
|
+
` • Check usage: https://console.anthropic.com/settings/usage`
|
|
116
|
+
);
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
/** H4: Return true if the error is an Anthropic rate limit (429). */
|
|
120
|
+
private isRateLimitError(error: unknown): boolean {
|
|
121
|
+
if (!error || typeof error !== 'object') return false;
|
|
122
|
+
const e = error as { status?: number; error?: { type?: string } };
|
|
123
|
+
return e.status === 429 || e.error?.type === 'rate_limit_error';
|
|
124
|
+
}
|
|
125
|
+
|
|
107
126
|
async completeWithTools(request: ToolCompletionRequest): Promise<LLMResponse> {
|
|
108
127
|
const systemPrompt = this.extractSystemPrompt(request.messages);
|
|
109
128
|
const messages = this.convertMessages(this.filterSystemMessages(request.messages));
|
|
110
129
|
|
|
111
130
|
const toolChoice = this.convertToolChoice(request.toolChoice);
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
131
|
+
try {
|
|
132
|
+
const response = await this.client.messages.create({
|
|
133
|
+
model: request.model || this.defaultModel,
|
|
134
|
+
max_tokens: request.maxTokens || 4096,
|
|
135
|
+
messages,
|
|
136
|
+
system: systemPrompt,
|
|
137
|
+
...(request.toolChoice !== 'none' && { tools: this.convertTools(request.tools) }),
|
|
138
|
+
...(toolChoice && { tool_choice: toolChoice }),
|
|
139
|
+
temperature: request.temperature,
|
|
140
|
+
});
|
|
141
|
+
return this.convertResponse(response);
|
|
142
|
+
} catch (error) {
|
|
143
|
+
if (this.isRateLimitError(error)) throw this.formatRateLimitError(error);
|
|
144
|
+
throw error;
|
|
145
|
+
}
|
|
123
146
|
}
|
|
124
147
|
|
|
125
148
|
async *streamWithTools(request: ToolCompletionRequest): AsyncIterable<StreamChunk> {
|
|
@@ -127,15 +150,21 @@ export class AnthropicProvider extends BaseProvider {
|
|
|
127
150
|
const messages = this.convertMessages(this.filterSystemMessages(request.messages));
|
|
128
151
|
|
|
129
152
|
const toolChoice = this.convertToolChoice(request.toolChoice);
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
153
|
+
let stream: Awaited<ReturnType<typeof this.client.messages.stream>>;
|
|
154
|
+
try {
|
|
155
|
+
stream = await this.client.messages.stream({
|
|
156
|
+
model: request.model || this.defaultModel,
|
|
157
|
+
max_tokens: request.maxTokens || 4096,
|
|
158
|
+
messages,
|
|
159
|
+
system: systemPrompt,
|
|
160
|
+
...(request.toolChoice !== 'none' && { tools: this.convertTools(request.tools) }),
|
|
161
|
+
...(toolChoice && { tool_choice: toolChoice }),
|
|
162
|
+
temperature: request.temperature,
|
|
163
|
+
});
|
|
164
|
+
} catch (error) {
|
|
165
|
+
if (this.isRateLimitError(error)) throw this.formatRateLimitError(error);
|
|
166
|
+
throw error;
|
|
167
|
+
}
|
|
139
168
|
|
|
140
169
|
let usage: StreamChunk['usage'] | undefined;
|
|
141
170
|
let inputTokensFromStart = 0; // Captured from message_start event
|
|
@@ -165,7 +194,7 @@ export class AnthropicProvider extends BaseProvider {
|
|
|
165
194
|
yield { done: false, toolCallStart: { id: currentToolId, name: currentToolName } };
|
|
166
195
|
}
|
|
167
196
|
} else if (event.type === 'content_block_delta') {
|
|
168
|
-
if (event.delta.type === 'text_delta') {
|
|
197
|
+
if (event.delta.type === 'text_delta' && event.delta.text) {
|
|
169
198
|
yield { content: event.delta.text, done: false };
|
|
170
199
|
} else if ((event.delta as any).type === 'input_json_delta') {
|
|
171
200
|
const existing = toolCallAccumulator.get(toolCallIndex);
|
package/src/llm/router.ts
CHANGED
|
@@ -143,8 +143,8 @@ export class LLMRouter {
|
|
|
143
143
|
isConfigured = bridge.isProviderConfigured;
|
|
144
144
|
getApiKey = bridge.getProviderApiKey;
|
|
145
145
|
} catch (err) {
|
|
146
|
-
// Auth-bridge unavailable (
|
|
147
|
-
logger.
|
|
146
|
+
// Auth-bridge unavailable (open-source build) — fall back to env-only
|
|
147
|
+
logger.debug(
|
|
148
148
|
'Auth-bridge unavailable, using environment variables only:',
|
|
149
149
|
err instanceof Error ? err.message : String(err)
|
|
150
150
|
);
|
|
@@ -479,17 +479,15 @@ export class LLMRouter {
|
|
|
479
479
|
}
|
|
480
480
|
try {
|
|
481
481
|
let lastUsage: StreamChunk['usage'] | undefined;
|
|
482
|
-
|
|
482
|
+
// Yield each chunk immediately (no buffering) for real-time streaming UX.
|
|
483
483
|
for await (const chunk of p.streamWithTools(request)) {
|
|
484
|
-
bufferedChunks.push(chunk);
|
|
485
484
|
if (chunk.usage) {
|
|
486
485
|
lastUsage = chunk.usage;
|
|
487
486
|
}
|
|
488
|
-
}
|
|
489
|
-
self.circuitBreaker.recordSuccess(p.name);
|
|
490
|
-
for (const chunk of bufferedChunks) {
|
|
491
487
|
yield chunk;
|
|
492
488
|
}
|
|
489
|
+
// Record success after the `done: true` chunk has been received and yielded.
|
|
490
|
+
self.circuitBreaker.recordSuccess(p.name);
|
|
493
491
|
if (lastUsage) {
|
|
494
492
|
const model = request.model || defaultModel;
|
|
495
493
|
const cost = calculateCost(
|
|
@@ -1033,3 +1031,74 @@ export class LLMRouter {
|
|
|
1033
1031
|
request.maxTokens = Math.min(request.maxTokens || 4096, maxTokens);
|
|
1034
1032
|
}
|
|
1035
1033
|
}
|
|
1034
|
+
|
|
1035
|
+
// ---------------------------------------------------------------------------
|
|
1036
|
+
// Gap 6: List authenticated providers for /model command
|
|
1037
|
+
// ---------------------------------------------------------------------------
|
|
1038
|
+
|
|
1039
|
+
/**
|
|
1040
|
+
* Return the names of providers that have valid credentials configured.
|
|
1041
|
+
* Checks both environment variables and the auth store.
|
|
1042
|
+
*/
|
|
1043
|
+
export function listAuthenticatedProviders(): string[] {
|
|
1044
|
+
const authenticated: string[] = [];
|
|
1045
|
+
if (process.env.ANTHROPIC_API_KEY) authenticated.push('anthropic');
|
|
1046
|
+
if (process.env.OPENAI_API_KEY) authenticated.push('openai');
|
|
1047
|
+
if (process.env.GOOGLE_API_KEY || process.env.GOOGLE_GENERATIVE_AI_API_KEY) authenticated.push('google');
|
|
1048
|
+
if (process.env.GROQ_API_KEY) authenticated.push('groq');
|
|
1049
|
+
if (process.env.OPENROUTER_API_KEY) authenticated.push('openrouter');
|
|
1050
|
+
if (process.env.AWS_ACCESS_KEY_ID || process.env.AWS_PROFILE) authenticated.push('bedrock');
|
|
1051
|
+
return authenticated;
|
|
1052
|
+
}
|
|
1053
|
+
|
|
1054
|
+
// ---------------------------------------------------------------------------
|
|
1055
|
+
// Gap 18: Multi-model routing based on task complexity
|
|
1056
|
+
// ---------------------------------------------------------------------------
|
|
1057
|
+
|
|
1058
|
+
/** Complexity tiers for automatic model selection. */
|
|
1059
|
+
export type TaskComplexity = 'simple' | 'moderate' | 'complex';
|
|
1060
|
+
|
|
1061
|
+
/**
|
|
1062
|
+
* Classify a user message as simple, moderate, or complex.
|
|
1063
|
+
*
|
|
1064
|
+
* - **simple**: short status/list/describe queries (<200 chars, no code generation)
|
|
1065
|
+
* - **complex**: long messages, code generation, architectural reasoning
|
|
1066
|
+
* - **moderate**: everything else
|
|
1067
|
+
*/
|
|
1068
|
+
export function classifyTaskComplexity(message: string): TaskComplexity {
|
|
1069
|
+
const lower = message.toLowerCase().trim();
|
|
1070
|
+
|
|
1071
|
+
// Simple: short status/list queries
|
|
1072
|
+
if (
|
|
1073
|
+
message.length < 200 &&
|
|
1074
|
+
/^(list|show|get|check|status|what is|what are|describe|which|where|who|ping|echo)\b/.test(lower)
|
|
1075
|
+
) {
|
|
1076
|
+
return 'simple';
|
|
1077
|
+
}
|
|
1078
|
+
|
|
1079
|
+
// Complex: long messages or keywords that imply heavy reasoning
|
|
1080
|
+
if (
|
|
1081
|
+
message.length > 500 ||
|
|
1082
|
+
/\b(implement|design|architect|refactor|migrate|rewrite|build|create|scaffold|generate|optimize|debug|diagnose|analyze)\b/.test(lower)
|
|
1083
|
+
) {
|
|
1084
|
+
return 'complex';
|
|
1085
|
+
}
|
|
1086
|
+
|
|
1087
|
+
return 'moderate';
|
|
1088
|
+
}
|
|
1089
|
+
|
|
1090
|
+
/**
|
|
1091
|
+
* Select the appropriate model string for a given complexity level.
|
|
1092
|
+
* If `preferredModel` is provided it always wins (user override).
|
|
1093
|
+
*/
|
|
1094
|
+
export function routeModel(complexity: TaskComplexity, preferredModel?: string): string {
|
|
1095
|
+
if (preferredModel) return preferredModel;
|
|
1096
|
+
switch (complexity) {
|
|
1097
|
+
case 'simple':
|
|
1098
|
+
return 'anthropic/claude-haiku-4-5-20251001';
|
|
1099
|
+
case 'complex':
|
|
1100
|
+
return 'anthropic/claude-opus-4-6';
|
|
1101
|
+
default:
|
|
1102
|
+
return 'anthropic/claude-sonnet-4-20250514';
|
|
1103
|
+
}
|
|
1104
|
+
}
|
package/src/lsp/languages.ts
CHANGED
|
@@ -114,3 +114,6 @@ export function getLanguageForFile(filePath: string): LanguageConfig | undefined
|
|
|
114
114
|
export function getLanguagePriority(): LanguageConfig[] {
|
|
115
115
|
return [...LANGUAGE_CONFIGS];
|
|
116
116
|
}
|
|
117
|
+
|
|
118
|
+
/** Language IDs relevant for DevOps workflows. */
|
|
119
|
+
export const DEVOPS_LANGUAGE_IDS = ['terraform', 'yaml', 'docker'] as const;
|
package/src/lsp/manager.ts
CHANGED
|
@@ -9,6 +9,7 @@
|
|
|
9
9
|
import { readFile } from 'node:fs/promises';
|
|
10
10
|
import { exec } from 'node:child_process';
|
|
11
11
|
import { promisify } from 'node:util';
|
|
12
|
+
import { EventEmitter } from 'node:events';
|
|
12
13
|
import { LSPClient, type Diagnostic } from './client';
|
|
13
14
|
import { getLanguageForFile, LANGUAGE_CONFIGS, type LanguageConfig } from './languages';
|
|
14
15
|
|
|
@@ -26,16 +27,21 @@ export interface LSPStatus {
|
|
|
26
27
|
available: boolean;
|
|
27
28
|
}
|
|
28
29
|
|
|
29
|
-
export class LSPManager {
|
|
30
|
+
export class LSPManager extends EventEmitter {
|
|
30
31
|
private clients = new Map<string, LSPClient>();
|
|
31
|
-
private idleTimers = new Map<string,
|
|
32
|
+
private idleTimers = new Map<string, ReturnType<typeof setTimeout>>();
|
|
32
33
|
private rootUri: string;
|
|
33
34
|
private availabilityCache = new Map<string, boolean>();
|
|
34
35
|
private fileVersions = new Map<string, number>();
|
|
35
36
|
private enabled: boolean = true;
|
|
37
|
+
private enabledLanguages?: readonly string[];
|
|
38
|
+
/** C4: Track languages already reported as unavailable to avoid duplicate events. */
|
|
39
|
+
private failedLSPs = new Set<string>();
|
|
36
40
|
|
|
37
|
-
constructor(rootUri?: string) {
|
|
41
|
+
constructor(rootUri?: string, options?: { enabledLanguages?: readonly string[] }) {
|
|
42
|
+
super();
|
|
38
43
|
this.rootUri = rootUri ?? process.cwd();
|
|
44
|
+
this.enabledLanguages = options?.enabledLanguages;
|
|
39
45
|
}
|
|
40
46
|
|
|
41
47
|
/** Enable or disable LSP integration. */
|
|
@@ -60,6 +66,11 @@ export class LSPManager {
|
|
|
60
66
|
return;
|
|
61
67
|
}
|
|
62
68
|
|
|
69
|
+
// Skip non-enabled language servers if a filter is set
|
|
70
|
+
if (this.enabledLanguages && !this.enabledLanguages.includes(config.id)) {
|
|
71
|
+
return;
|
|
72
|
+
}
|
|
73
|
+
|
|
63
74
|
// Ensure the client is running
|
|
64
75
|
const client = await this.ensureClient(config);
|
|
65
76
|
if (!client) {
|
|
@@ -201,6 +212,11 @@ export class LSPManager {
|
|
|
201
212
|
// Check if the binary is available
|
|
202
213
|
const available = await this.isAvailable(config);
|
|
203
214
|
if (!available) {
|
|
215
|
+
// C4: Emit once per language so the TUI can surface a diagnostic message
|
|
216
|
+
if (!this.failedLSPs.has(config.id)) {
|
|
217
|
+
this.failedLSPs.add(config.id);
|
|
218
|
+
this.emit('lsp-unavailable', config.name ?? config.id, config.command);
|
|
219
|
+
}
|
|
204
220
|
return null;
|
|
205
221
|
}
|
|
206
222
|
|
|
@@ -264,9 +280,9 @@ export class LSPManager {
|
|
|
264
280
|
let lspManagerInstance: LSPManager | null = null;
|
|
265
281
|
|
|
266
282
|
/** Get or create the singleton LSP manager. */
|
|
267
|
-
export function getLSPManager(rootUri?: string): LSPManager {
|
|
283
|
+
export function getLSPManager(rootUri?: string, options?: { enabledLanguages?: readonly string[] }): LSPManager {
|
|
268
284
|
if (!lspManagerInstance) {
|
|
269
|
-
lspManagerInstance = new LSPManager(rootUri);
|
|
285
|
+
lspManagerInstance = new LSPManager(rootUri, options);
|
|
270
286
|
}
|
|
271
287
|
return lspManagerInstance;
|
|
272
288
|
}
|
package/src/nimbus.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
#!/usr/bin/env
|
|
1
|
+
#!/usr/bin/env node
|
|
2
2
|
/**
|
|
3
3
|
* Nimbus CLI — Main Entry Point
|
|
4
4
|
*
|
|
@@ -97,7 +97,16 @@ process.on('uncaughtException', error => {
|
|
|
97
97
|
});
|
|
98
98
|
|
|
99
99
|
async function main() {
|
|
100
|
-
|
|
100
|
+
let args = process.argv.slice(2);
|
|
101
|
+
|
|
102
|
+
// G11: Global --project / --cwd flag: change working directory before command dispatch
|
|
103
|
+
const cwdFlagIdx = args.findIndex(a => a === '--project' || a === '--cwd');
|
|
104
|
+
if (cwdFlagIdx !== -1 && args[cwdFlagIdx + 1]) {
|
|
105
|
+
const { resolve } = await import('node:path');
|
|
106
|
+
const targetDir = resolve(args[cwdFlagIdx + 1]);
|
|
107
|
+
process.chdir(targetDir);
|
|
108
|
+
args.splice(cwdFlagIdx, 2);
|
|
109
|
+
}
|
|
101
110
|
|
|
102
111
|
// Handle --version and -v before anything else (no init needed)
|
|
103
112
|
if (args[0] === '--version' || args[0] === '-v') {
|
|
@@ -105,6 +114,22 @@ async function main() {
|
|
|
105
114
|
process.exit(0);
|
|
106
115
|
}
|
|
107
116
|
|
|
117
|
+
// Handle --completion-install / completion <shell> — print shell completion script
|
|
118
|
+
if (args[0] === '--completion-install' || args[0] === 'completion') {
|
|
119
|
+
const shell = args[1] ?? 'bash';
|
|
120
|
+
const { join } = await import('node:path');
|
|
121
|
+
const { readFileSync } = await import('node:fs');
|
|
122
|
+
const { fileURLToPath } = await import('node:url');
|
|
123
|
+
const dir = join(fileURLToPath(import.meta.url), '../../completions');
|
|
124
|
+
try {
|
|
125
|
+
process.stdout.write(readFileSync(join(dir, `nimbus.${shell}`), 'utf-8'));
|
|
126
|
+
} catch {
|
|
127
|
+
console.error(`No completion script for shell: ${shell}. Available: bash, zsh, fish`);
|
|
128
|
+
process.exit(1);
|
|
129
|
+
}
|
|
130
|
+
process.exit(0);
|
|
131
|
+
}
|
|
132
|
+
|
|
108
133
|
// Show help when explicitly requested — handles both `nimbus --help`
|
|
109
134
|
// and subcommand-level help like `nimbus chat --help` or `nimbus tf -h`.
|
|
110
135
|
if (args.includes('--help') || args.includes('-h')) {
|
|
@@ -167,6 +192,14 @@ async function main() {
|
|
|
167
192
|
}
|
|
168
193
|
}
|
|
169
194
|
|
|
195
|
+
// Resolve aliases before dispatching (L2)
|
|
196
|
+
try {
|
|
197
|
+
const { resolveAlias } = await import('./commands/alias');
|
|
198
|
+
args = resolveAlias(args);
|
|
199
|
+
} catch {
|
|
200
|
+
/* alias file not found — ignore */
|
|
201
|
+
}
|
|
202
|
+
|
|
170
203
|
// Import and run CLI command router
|
|
171
204
|
const { runCommand } = await import('./cli');
|
|
172
205
|
await runCommand(args);
|
|
@@ -185,23 +218,9 @@ async function main() {
|
|
|
185
218
|
}
|
|
186
219
|
} catch (error: any) {
|
|
187
220
|
const msg = error.message || String(error);
|
|
188
|
-
if (
|
|
189
|
-
console.error(
|
|
190
|
-
'Error: Nimbus requires the Bun runtime (for bun:sqlite and other built-in APIs).'
|
|
191
|
-
);
|
|
192
|
-
console.error('');
|
|
193
|
-
console.error('If you have Bun installed, run:');
|
|
194
|
-
console.error(' bun src/nimbus.ts');
|
|
195
|
-
console.error('');
|
|
196
|
-
console.error('To install Bun:');
|
|
197
|
-
console.error(' curl -fsSL https://bun.sh/install | bash');
|
|
198
|
-
console.error('');
|
|
199
|
-
console.error('Or install the pre-built binary (no Bun required):');
|
|
200
|
-
console.error(' brew install the-ai-project-co/tap/nimbus');
|
|
201
|
-
console.error(' # or download from GitHub Releases');
|
|
202
|
-
} else if (error.code === 'MODULE_NOT_FOUND') {
|
|
221
|
+
if (error.code === 'MODULE_NOT_FOUND') {
|
|
203
222
|
console.error(`Error: Missing module — ${msg}`);
|
|
204
|
-
console.error('Run "
|
|
223
|
+
console.error('Run "npm install" to install dependencies.');
|
|
205
224
|
} else {
|
|
206
225
|
console.error(`Error: ${msg}`);
|
|
207
226
|
}
|
package/src/sessions/manager.ts
CHANGED
|
@@ -13,6 +13,16 @@ import type { Database } from '../compat/sqlite';
|
|
|
13
13
|
import type { SessionRecord, SessionStatus, SessionEvent, SessionFileEdit } from './types';
|
|
14
14
|
import type { LLMMessage } from '../llm/types';
|
|
15
15
|
|
|
16
|
+
/** Infra context persisted per session (terraform workspace, kubectl context, etc.) */
|
|
17
|
+
export interface SessionInfraContext {
|
|
18
|
+
terraformWorkspace?: string;
|
|
19
|
+
kubectlContext?: string;
|
|
20
|
+
awsProfile?: string;
|
|
21
|
+
awsRegion?: string;
|
|
22
|
+
gcpProject?: string;
|
|
23
|
+
azureSubscription?: string;
|
|
24
|
+
}
|
|
25
|
+
|
|
16
26
|
/** Singleton session manager instance. */
|
|
17
27
|
let instance: SessionManager | null = null;
|
|
18
28
|
|
|
@@ -22,8 +32,17 @@ export class SessionManager {
|
|
|
22
32
|
private fileEdits: Map<string, SessionFileEdit[]> = new Map();
|
|
23
33
|
private eventListeners: Array<(event: SessionEvent) => void> = [];
|
|
24
34
|
|
|
25
|
-
|
|
35
|
+
/** Pending conversation writes accumulated before the next debounced flush. */
|
|
36
|
+
private pendingConversationFlush: Map<string, {
|
|
37
|
+
messages: LLMMessage[];
|
|
38
|
+
stats?: Partial<Pick<SessionRecord, 'tokenCount' | 'costUSD' | 'snapshotCount' | 'mode' | 'model'>>;
|
|
39
|
+
}> = new Map();
|
|
40
|
+
private _flushTimer: ReturnType<typeof setTimeout> | null = null;
|
|
41
|
+
private readonly flushDebounceMs: number;
|
|
42
|
+
|
|
43
|
+
constructor(db?: Database, options?: { flushDebounceMs?: number }) {
|
|
26
44
|
this.db = db || getDb();
|
|
45
|
+
this.flushDebounceMs = options?.flushDebounceMs ?? 5000;
|
|
27
46
|
this.ensureTable();
|
|
28
47
|
}
|
|
29
48
|
|
|
@@ -214,6 +233,11 @@ export class SessionManager {
|
|
|
214
233
|
this.emit({ type: 'destroyed', sessionId, timestamp: new Date() });
|
|
215
234
|
}
|
|
216
235
|
|
|
236
|
+
/** M2: Rename a session (update its display name). */
|
|
237
|
+
rename(sessionId: string, name: string): void {
|
|
238
|
+
this.db.prepare('UPDATE sessions SET name = ? WHERE id = ?').run(name, sessionId);
|
|
239
|
+
}
|
|
240
|
+
|
|
217
241
|
/** Update session metadata (tokens, cost, mode, etc.). */
|
|
218
242
|
updateSession(
|
|
219
243
|
sessionId: string,
|
|
@@ -275,6 +299,67 @@ export class SessionManager {
|
|
|
275
299
|
}
|
|
276
300
|
}
|
|
277
301
|
|
|
302
|
+
/**
|
|
303
|
+
* Atomically save conversation messages AND update session stats.
|
|
304
|
+
* Writes are debounced (default 5s) and batched for performance.
|
|
305
|
+
* Use `flushAll()` for immediate persistence (shutdown / completion).
|
|
306
|
+
*/
|
|
307
|
+
saveConversationAndStats(
|
|
308
|
+
sessionId: string,
|
|
309
|
+
messages: LLMMessage[],
|
|
310
|
+
stats: Partial<Pick<SessionRecord, 'tokenCount' | 'costUSD' | 'snapshotCount' | 'mode' | 'model'>>
|
|
311
|
+
): void {
|
|
312
|
+
// Accumulate into the pending flush map (latest write wins per session)
|
|
313
|
+
const existing = this.pendingConversationFlush.get(sessionId);
|
|
314
|
+
this.pendingConversationFlush.set(sessionId, {
|
|
315
|
+
messages,
|
|
316
|
+
stats: existing?.stats ? { ...existing.stats, ...stats } : stats,
|
|
317
|
+
});
|
|
318
|
+
this._scheduleFlush();
|
|
319
|
+
}
|
|
320
|
+
|
|
321
|
+
/** Schedule a debounced flush (no-op if already scheduled). */
|
|
322
|
+
private _scheduleFlush(): void {
|
|
323
|
+
if (this._flushTimer !== null) return;
|
|
324
|
+
if (this.flushDebounceMs === 0) {
|
|
325
|
+
// Immediate flush path (used in tests)
|
|
326
|
+
this._flushNow();
|
|
327
|
+
return;
|
|
328
|
+
}
|
|
329
|
+
this._flushTimer = setTimeout(() => {
|
|
330
|
+
this._flushTimer = null;
|
|
331
|
+
this._flushNow();
|
|
332
|
+
}, this.flushDebounceMs);
|
|
333
|
+
}
|
|
334
|
+
|
|
335
|
+
/** Write all pending conversation entries to SQLite. */
|
|
336
|
+
private _flushNow(): void {
|
|
337
|
+
if (this.pendingConversationFlush.size === 0) return;
|
|
338
|
+
const pending = this.pendingConversationFlush;
|
|
339
|
+
this.pendingConversationFlush = new Map();
|
|
340
|
+
const txn = this.db.transaction(() => {
|
|
341
|
+
for (const [sessionId, { messages, stats }] of pending) {
|
|
342
|
+
this.saveConversation(sessionId, messages);
|
|
343
|
+
if (stats && Object.keys(stats).length > 0) {
|
|
344
|
+
this.updateSession(sessionId, stats);
|
|
345
|
+
}
|
|
346
|
+
}
|
|
347
|
+
});
|
|
348
|
+
txn();
|
|
349
|
+
}
|
|
350
|
+
|
|
351
|
+
/**
|
|
352
|
+
* Flush all pending conversation writes immediately.
|
|
353
|
+
* Must be called on clean shutdown and session completion.
|
|
354
|
+
*/
|
|
355
|
+
flushAll(): void {
|
|
356
|
+
if (this._flushTimer !== null) {
|
|
357
|
+
clearTimeout(this._flushTimer);
|
|
358
|
+
this._flushTimer = null;
|
|
359
|
+
}
|
|
360
|
+
this._flushNow();
|
|
361
|
+
}
|
|
362
|
+
|
|
278
363
|
/** Load conversation messages for a session. Returns empty array if not found. */
|
|
279
364
|
loadConversation(sessionId: string): LLMMessage[] {
|
|
280
365
|
const row: any = this.db
|
|
@@ -321,6 +406,28 @@ export class SessionManager {
|
|
|
321
406
|
return conflicts;
|
|
322
407
|
}
|
|
323
408
|
|
|
409
|
+
/** Persist infra context (terraform workspace, kubectl context, etc.) for a session. */
|
|
410
|
+
setInfraContext(sessionId: string, ctx: SessionInfraContext): void {
|
|
411
|
+
const row: any = this.db.prepare('SELECT metadata FROM sessions WHERE id = ?').get(sessionId);
|
|
412
|
+
const existing = row?.metadata ? JSON.parse(row.metadata) : {};
|
|
413
|
+
const updated = { ...existing, infraContext: ctx };
|
|
414
|
+
this.db
|
|
415
|
+
.prepare("UPDATE sessions SET metadata = ?, updated_at = datetime('now') WHERE id = ?")
|
|
416
|
+
.run(JSON.stringify(updated), sessionId);
|
|
417
|
+
}
|
|
418
|
+
|
|
419
|
+
/** Retrieve infra context for a session. Returns null if not set. */
|
|
420
|
+
getInfraContext(sessionId: string): SessionInfraContext | null {
|
|
421
|
+
const row: any = this.db.prepare('SELECT metadata FROM sessions WHERE id = ?').get(sessionId);
|
|
422
|
+
if (!row?.metadata) return null;
|
|
423
|
+
try {
|
|
424
|
+
const meta = JSON.parse(row.metadata);
|
|
425
|
+
return meta.infraContext ?? null;
|
|
426
|
+
} catch {
|
|
427
|
+
return null;
|
|
428
|
+
}
|
|
429
|
+
}
|
|
430
|
+
|
|
324
431
|
/** Listen for session events. */
|
|
325
432
|
onEvent(listener: (event: SessionEvent) => void): () => void {
|
|
326
433
|
this.eventListeners.push(listener);
|
package/src/sharing/sync.ts
CHANGED
|
@@ -18,6 +18,7 @@ import type { LLMMessage } from '../llm/types';
|
|
|
18
18
|
export const _deps = {
|
|
19
19
|
getConversation: undefined as ((id: string) => any) | undefined,
|
|
20
20
|
getSessionManager: undefined as (() => { get: (id: string) => any }) | undefined,
|
|
21
|
+
getDb: undefined as (() => any) | undefined,
|
|
21
22
|
};
|
|
22
23
|
|
|
23
24
|
function getConversation(id: string) {
|
|
@@ -60,6 +61,9 @@ export interface SharedSession {
|
|
|
60
61
|
* Lazily import the DB to avoid circular dependency.
|
|
61
62
|
*/
|
|
62
63
|
function getDb() {
|
|
64
|
+
if (_deps.getDb) {
|
|
65
|
+
return _deps.getDb();
|
|
66
|
+
}
|
|
63
67
|
try {
|
|
64
68
|
// eslint-disable-next-line @typescript-eslint/no-var-requires
|
|
65
69
|
const { getDb: _getDb } = require('../state/db');
|
package/src/sharing/viewer.ts
CHANGED
|
@@ -95,3 +95,69 @@ function escapeHtml(text: string): string {
|
|
|
95
95
|
.replace(/"/g, '"')
|
|
96
96
|
.replace(/'/g, ''');
|
|
97
97
|
}
|
|
98
|
+
|
|
99
|
+
// ---------------------------------------------------------------------------
|
|
100
|
+
// Gap 4: Runbook / Session Export
|
|
101
|
+
// ---------------------------------------------------------------------------
|
|
102
|
+
|
|
103
|
+
export interface RunbookMessage {
|
|
104
|
+
role: 'user' | 'assistant' | 'system';
|
|
105
|
+
content: string;
|
|
106
|
+
timestamp?: Date;
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
export interface RunbookSession {
|
|
110
|
+
id?: string;
|
|
111
|
+
model?: string;
|
|
112
|
+
mode?: string;
|
|
113
|
+
costUSD?: number;
|
|
114
|
+
tokenCount?: number;
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
/**
|
|
118
|
+
* Format a chat session as a Markdown runbook for team documentation.
|
|
119
|
+
*
|
|
120
|
+
* Produces a self-contained `.md` file with:
|
|
121
|
+
* - Session metadata header (model, date, mode, cost)
|
|
122
|
+
* - Each user message as a `## User` section
|
|
123
|
+
* - Each assistant message as an `## Agent` section
|
|
124
|
+
* - Tool calls as collapsible `<details>` blocks
|
|
125
|
+
* - System messages skipped (they are implementation details)
|
|
126
|
+
*/
|
|
127
|
+
export function formatSessionAsRunbook(
|
|
128
|
+
messages: RunbookMessage[],
|
|
129
|
+
session?: RunbookSession
|
|
130
|
+
): string {
|
|
131
|
+
const date = new Date().toISOString().slice(0, 19).replace('T', ' ');
|
|
132
|
+
const lines: string[] = [
|
|
133
|
+
'# Nimbus Session Runbook',
|
|
134
|
+
'',
|
|
135
|
+
'| Field | Value |',
|
|
136
|
+
'|-------|-------|',
|
|
137
|
+
`| Date | ${date} |`,
|
|
138
|
+
`| Model | ${session?.model ?? 'default'} |`,
|
|
139
|
+
`| Mode | ${session?.mode ?? 'build'} |`,
|
|
140
|
+
`| Tokens | ${session?.tokenCount?.toLocaleString() ?? 'N/A'} |`,
|
|
141
|
+
`| Cost | $${session?.costUSD?.toFixed(4) ?? '0.0000'} |`,
|
|
142
|
+
'',
|
|
143
|
+
'---',
|
|
144
|
+
'',
|
|
145
|
+
];
|
|
146
|
+
|
|
147
|
+
for (const msg of messages) {
|
|
148
|
+
if (msg.role === 'system') continue;
|
|
149
|
+
|
|
150
|
+
const heading = msg.role === 'user' ? '## User' : '## Agent';
|
|
151
|
+
lines.push(heading);
|
|
152
|
+
lines.push('');
|
|
153
|
+
|
|
154
|
+
// Preserve code blocks; escape HTML outside them
|
|
155
|
+
const content = msg.content.trim();
|
|
156
|
+
lines.push(content);
|
|
157
|
+
lines.push('');
|
|
158
|
+
lines.push('---');
|
|
159
|
+
lines.push('');
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
return lines.join('\n');
|
|
163
|
+
}
|
package/src/tools/file-ops.ts
CHANGED
|
@@ -127,6 +127,28 @@ export class FileSystemOperations {
|
|
|
127
127
|
const resolvedPath = this.resolvePath(filePath);
|
|
128
128
|
logger.info(`Reading file: ${resolvedPath}`);
|
|
129
129
|
|
|
130
|
+
// Guard against large files that would overflow the context window.
|
|
131
|
+
// Warn and truncate at 500 KB — enough for most source files.
|
|
132
|
+
const MAX_READ_BYTES = 500 * 1024;
|
|
133
|
+
const stat = await fs.stat(resolvedPath);
|
|
134
|
+
if (stat.size > MAX_READ_BYTES) {
|
|
135
|
+
const buffer = Buffer.alloc(MAX_READ_BYTES);
|
|
136
|
+
const fd = await fs.open(resolvedPath, 'r');
|
|
137
|
+
let bytesRead = 0;
|
|
138
|
+
try {
|
|
139
|
+
const result = await fd.read(buffer, 0, MAX_READ_BYTES, 0);
|
|
140
|
+
bytesRead = result.bytesRead;
|
|
141
|
+
} finally {
|
|
142
|
+
await fd.close();
|
|
143
|
+
}
|
|
144
|
+
const truncated = buffer.slice(0, bytesRead).toString(encoding);
|
|
145
|
+
return (
|
|
146
|
+
truncated +
|
|
147
|
+
`\n\n[File truncated: ${(stat.size / 1024).toFixed(1)} KB total, showing first ${(bytesRead / 1024).toFixed(0)} KB. ` +
|
|
148
|
+
`Use line range parameters to read specific sections.]`
|
|
149
|
+
);
|
|
150
|
+
}
|
|
151
|
+
|
|
130
152
|
const content = await fs.readFile(resolvedPath, encoding);
|
|
131
153
|
return content;
|
|
132
154
|
}
|