@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,432 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* nimbus serve -- Headless HTTP API Server
|
|
3
|
+
*
|
|
4
|
+
* Exposes the Nimbus agent loop as a REST + SSE API designed for
|
|
5
|
+
* consumption by the Web UI, IDE extensions, and third-party integrations.
|
|
6
|
+
*
|
|
7
|
+
* Endpoints:
|
|
8
|
+
* POST /api/chat -- Send a message, receive SSE streaming response
|
|
9
|
+
* POST /api/run -- Non-interactive single prompt (JSON response)
|
|
10
|
+
* GET /api/sessions -- List all sessions
|
|
11
|
+
* GET /api/session/:id -- Session details + conversation messages
|
|
12
|
+
* POST /api/session/:id -- Continue an existing session (SSE streaming)
|
|
13
|
+
* GET /api/health -- Health check
|
|
14
|
+
* GET /api/openapi.json -- OpenAPI 3.1 specification
|
|
15
|
+
*
|
|
16
|
+
* Usage:
|
|
17
|
+
* nimbus serve # localhost:4200
|
|
18
|
+
* nimbus serve --port 8080 # custom port
|
|
19
|
+
* nimbus serve --host 0.0.0.0 # bind to all interfaces
|
|
20
|
+
* nimbus serve --auth admin:secret # enable HTTP Basic Auth
|
|
21
|
+
*
|
|
22
|
+
* @module cli/serve
|
|
23
|
+
*/
|
|
24
|
+
import { Elysia } from 'elysia';
|
|
25
|
+
import { cors } from '@elysiajs/cors';
|
|
26
|
+
import { initApp } from '../app';
|
|
27
|
+
import { runAgentLoop } from '../agent/loop';
|
|
28
|
+
import { defaultToolRegistry } from '../tools/schemas/types';
|
|
29
|
+
import { standardTools } from '../tools/schemas/standard';
|
|
30
|
+
import { devopsTools } from '../tools/schemas/devops';
|
|
31
|
+
import { SessionManager } from '../sessions/manager';
|
|
32
|
+
import { saveConversation, getConversation } from '../state/conversations';
|
|
33
|
+
import { shareSession, getSharedSession, listShares } from '../sharing/sync';
|
|
34
|
+
import { ContextManager } from '../agent/context-manager';
|
|
35
|
+
import { getOpenAPISpec } from './openapi-spec';
|
|
36
|
+
import { createAuthMiddleware } from './serve-auth';
|
|
37
|
+
// ---------------------------------------------------------------------------
|
|
38
|
+
// Helpers
|
|
39
|
+
// ---------------------------------------------------------------------------
|
|
40
|
+
/**
|
|
41
|
+
* Ensure the default tool registry is populated.
|
|
42
|
+
* Idempotent -- skips tools that are already registered.
|
|
43
|
+
*/
|
|
44
|
+
function ensureToolsRegistered() {
|
|
45
|
+
if (defaultToolRegistry.size > 0) {
|
|
46
|
+
return;
|
|
47
|
+
}
|
|
48
|
+
for (const tool of [...standardTools, ...devopsTools]) {
|
|
49
|
+
try {
|
|
50
|
+
defaultToolRegistry.register(tool);
|
|
51
|
+
}
|
|
52
|
+
catch {
|
|
53
|
+
// Already registered -- skip.
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
/**
|
|
58
|
+
* Validate and narrow a mode string to the AgentMode union.
|
|
59
|
+
*/
|
|
60
|
+
function parseMode(mode) {
|
|
61
|
+
if (mode === 'plan' || mode === 'build' || mode === 'deploy') {
|
|
62
|
+
return mode;
|
|
63
|
+
}
|
|
64
|
+
return 'build';
|
|
65
|
+
}
|
|
66
|
+
/**
|
|
67
|
+
* Create an SSE-formatted ReadableStream that runs the agent loop and
|
|
68
|
+
* emits events for text, tool calls, completion, and errors.
|
|
69
|
+
*
|
|
70
|
+
* SSE event types:
|
|
71
|
+
* session -- { id, mode }
|
|
72
|
+
* text -- { content }
|
|
73
|
+
* tool_start -- { id, name, input? }
|
|
74
|
+
* tool_end -- { id, name, output?, isError }
|
|
75
|
+
* done -- { turns, usage, cost }
|
|
76
|
+
* error -- { message }
|
|
77
|
+
*/
|
|
78
|
+
function createAgentSSEStream(userMessage, history, sessionId, mode, model, router, contextManager, sessionManager) {
|
|
79
|
+
return new ReadableStream({
|
|
80
|
+
async start(controller) {
|
|
81
|
+
const encoder = new TextEncoder();
|
|
82
|
+
const send = (event, data) => {
|
|
83
|
+
const payload = `event: ${event}\ndata: ${JSON.stringify(data)}\n\n`;
|
|
84
|
+
controller.enqueue(encoder.encode(payload));
|
|
85
|
+
};
|
|
86
|
+
try {
|
|
87
|
+
send('session', { id: sessionId, mode });
|
|
88
|
+
const result = await runAgentLoop(userMessage, history, {
|
|
89
|
+
router,
|
|
90
|
+
toolRegistry: defaultToolRegistry,
|
|
91
|
+
mode,
|
|
92
|
+
model,
|
|
93
|
+
sessionId,
|
|
94
|
+
onText: (text) => {
|
|
95
|
+
send('text', { content: text });
|
|
96
|
+
},
|
|
97
|
+
onToolCallStart: (toolCall) => {
|
|
98
|
+
send('tool_start', {
|
|
99
|
+
id: toolCall.id,
|
|
100
|
+
name: toolCall.name,
|
|
101
|
+
input: toolCall.input,
|
|
102
|
+
});
|
|
103
|
+
},
|
|
104
|
+
onToolCallEnd: (toolCall, toolResult) => {
|
|
105
|
+
send('tool_end', {
|
|
106
|
+
id: toolCall.id,
|
|
107
|
+
name: toolCall.name,
|
|
108
|
+
output: typeof toolResult.output === 'string'
|
|
109
|
+
? toolResult.output.slice(0, 5000)
|
|
110
|
+
: toolResult.output,
|
|
111
|
+
isError: toolResult.isError,
|
|
112
|
+
});
|
|
113
|
+
},
|
|
114
|
+
});
|
|
115
|
+
// Persist conversation
|
|
116
|
+
saveConversation(sessionId, userMessage.slice(0, 100), result.messages, model);
|
|
117
|
+
// Update session stats
|
|
118
|
+
sessionManager.updateSession(sessionId, {
|
|
119
|
+
tokenCount: result.usage.totalTokens,
|
|
120
|
+
costUSD: result.totalCost,
|
|
121
|
+
});
|
|
122
|
+
send('done', {
|
|
123
|
+
turns: result.turns,
|
|
124
|
+
usage: result.usage,
|
|
125
|
+
cost: result.totalCost,
|
|
126
|
+
});
|
|
127
|
+
}
|
|
128
|
+
catch (error) {
|
|
129
|
+
const msg = error instanceof Error ? error.message : String(error);
|
|
130
|
+
send('error', { message: msg });
|
|
131
|
+
}
|
|
132
|
+
finally {
|
|
133
|
+
controller.close();
|
|
134
|
+
}
|
|
135
|
+
},
|
|
136
|
+
});
|
|
137
|
+
}
|
|
138
|
+
/**
|
|
139
|
+
* Wrap a ReadableStream as an SSE Response with proper headers.
|
|
140
|
+
*/
|
|
141
|
+
function sseResponse(stream) {
|
|
142
|
+
return new Response(stream, {
|
|
143
|
+
headers: {
|
|
144
|
+
'Content-Type': 'text/event-stream',
|
|
145
|
+
'Cache-Control': 'no-cache',
|
|
146
|
+
Connection: 'keep-alive',
|
|
147
|
+
'Access-Control-Allow-Origin': '*',
|
|
148
|
+
},
|
|
149
|
+
});
|
|
150
|
+
}
|
|
151
|
+
// ---------------------------------------------------------------------------
|
|
152
|
+
// Main Command
|
|
153
|
+
// ---------------------------------------------------------------------------
|
|
154
|
+
/**
|
|
155
|
+
* Start the Nimbus headless API server.
|
|
156
|
+
*
|
|
157
|
+
* Initializes the app context (DB + LLM router), registers tools, sets up
|
|
158
|
+
* all API routes via Elysia, and starts listening.
|
|
159
|
+
*/
|
|
160
|
+
export async function serveCommand(options) {
|
|
161
|
+
const port = options.port ?? 4200;
|
|
162
|
+
const host = options.host ?? 'localhost';
|
|
163
|
+
// M1: stop a running background server
|
|
164
|
+
if (options.stop) {
|
|
165
|
+
const { join } = await import('node:path');
|
|
166
|
+
const { homedir } = await import('node:os');
|
|
167
|
+
const { readFileSync, unlinkSync, existsSync } = await import('node:fs');
|
|
168
|
+
const pidFile = join(homedir(), '.nimbus', 'serve.pid');
|
|
169
|
+
if (!existsSync(pidFile)) {
|
|
170
|
+
console.log('No background nimbus serve process found.');
|
|
171
|
+
return;
|
|
172
|
+
}
|
|
173
|
+
const pid = parseInt(readFileSync(pidFile, 'utf-8').trim(), 10);
|
|
174
|
+
try {
|
|
175
|
+
process.kill(pid, 'SIGTERM');
|
|
176
|
+
unlinkSync(pidFile);
|
|
177
|
+
console.log(`Stopped nimbus serve (PID: ${pid})`);
|
|
178
|
+
}
|
|
179
|
+
catch (e) {
|
|
180
|
+
console.error(`Failed to stop process ${pid}: ${e.message}`);
|
|
181
|
+
unlinkSync(pidFile);
|
|
182
|
+
}
|
|
183
|
+
return;
|
|
184
|
+
}
|
|
185
|
+
// M1: daemonize — spawn detached child and exit
|
|
186
|
+
if (options.background) {
|
|
187
|
+
const { spawn } = await import('node:child_process');
|
|
188
|
+
const { join } = await import('node:path');
|
|
189
|
+
const { homedir } = await import('node:os');
|
|
190
|
+
const { writeFileSync, mkdirSync } = await import('node:fs');
|
|
191
|
+
const nimbusDir = join(homedir(), '.nimbus');
|
|
192
|
+
mkdirSync(nimbusDir, { recursive: true });
|
|
193
|
+
const childArgs = [process.argv[1], 'serve', '--port', String(port)];
|
|
194
|
+
if (options.host)
|
|
195
|
+
childArgs.push('--host', options.host);
|
|
196
|
+
if (options.auth)
|
|
197
|
+
childArgs.push('--auth', options.auth);
|
|
198
|
+
const child = spawn(process.execPath, childArgs, {
|
|
199
|
+
detached: true,
|
|
200
|
+
stdio: 'ignore',
|
|
201
|
+
env: process.env,
|
|
202
|
+
});
|
|
203
|
+
child.unref();
|
|
204
|
+
writeFileSync(join(nimbusDir, 'serve.pid'), String(child.pid), 'utf-8');
|
|
205
|
+
console.log(`nimbus serve started in background (PID: ${child.pid})`);
|
|
206
|
+
console.log(`Stop with: nimbus serve stop`);
|
|
207
|
+
return;
|
|
208
|
+
}
|
|
209
|
+
// G19: Security guard — non-localhost binding requires explicit auth
|
|
210
|
+
const isLocalhost = !host || host === 'localhost' || host === '127.0.0.1' || host === '::1';
|
|
211
|
+
if (!isLocalhost && !options.auth) {
|
|
212
|
+
throw new Error(`Cannot bind nimbus serve to ${host} without authentication.\n` +
|
|
213
|
+
'Use --auth user:pass to enable HTTP Basic Auth, or bind to localhost only.');
|
|
214
|
+
}
|
|
215
|
+
// G19: Auto-generate token for localhost when no auth provided
|
|
216
|
+
let autoToken;
|
|
217
|
+
if (isLocalhost && !options.auth) {
|
|
218
|
+
const { randomBytes } = await import('node:crypto');
|
|
219
|
+
autoToken = randomBytes(16).toString('hex');
|
|
220
|
+
process.stderr.write(`\nNimbus API token: ${autoToken}\n`);
|
|
221
|
+
process.stderr.write('Pass as: Authorization: Bearer <token>\n\n');
|
|
222
|
+
}
|
|
223
|
+
// ------------------------------------------------------------------
|
|
224
|
+
// Initialize core systems
|
|
225
|
+
// ------------------------------------------------------------------
|
|
226
|
+
const { router } = await initApp();
|
|
227
|
+
const sessionManager = SessionManager.getInstance();
|
|
228
|
+
const contextManager = new ContextManager();
|
|
229
|
+
ensureToolsRegistered();
|
|
230
|
+
// ------------------------------------------------------------------
|
|
231
|
+
// Build Elysia app
|
|
232
|
+
// ------------------------------------------------------------------
|
|
233
|
+
const app = new Elysia().use(cors({
|
|
234
|
+
origin: true,
|
|
235
|
+
methods: ['GET', 'POST', 'OPTIONS'],
|
|
236
|
+
allowedHeaders: ['Content-Type', 'Authorization'],
|
|
237
|
+
}));
|
|
238
|
+
// HTTP Basic Auth (explicit or auto-generated token)
|
|
239
|
+
if (options.auth) {
|
|
240
|
+
const colonIdx = options.auth.indexOf(':');
|
|
241
|
+
if (colonIdx > 0) {
|
|
242
|
+
const user = options.auth.slice(0, colonIdx);
|
|
243
|
+
const pass = options.auth.slice(colonIdx + 1);
|
|
244
|
+
app.onBeforeHandle(createAuthMiddleware({ username: user, password: pass }));
|
|
245
|
+
}
|
|
246
|
+
}
|
|
247
|
+
else if (autoToken) {
|
|
248
|
+
// G19: Bearer token auth for localhost when no explicit auth provided
|
|
249
|
+
const capturedToken = autoToken;
|
|
250
|
+
app.onBeforeHandle((ctx) => {
|
|
251
|
+
const authHeader = ctx.request.headers.get('Authorization') ?? '';
|
|
252
|
+
if (authHeader !== `Bearer ${capturedToken}`) {
|
|
253
|
+
ctx.set.status = 401;
|
|
254
|
+
return { error: 'Unauthorized. Pass Authorization: Bearer <token>' };
|
|
255
|
+
}
|
|
256
|
+
});
|
|
257
|
+
}
|
|
258
|
+
// ------------------------------------------------------------------
|
|
259
|
+
// GET /api/health
|
|
260
|
+
// ------------------------------------------------------------------
|
|
261
|
+
app.get('/api/health', () => ({
|
|
262
|
+
status: 'ok',
|
|
263
|
+
version: '0.2.0',
|
|
264
|
+
uptime: process.uptime(),
|
|
265
|
+
db: true,
|
|
266
|
+
llm: true,
|
|
267
|
+
}));
|
|
268
|
+
// ------------------------------------------------------------------
|
|
269
|
+
// GET /api/openapi.json
|
|
270
|
+
// ------------------------------------------------------------------
|
|
271
|
+
app.get('/api/openapi.json', () => getOpenAPISpec());
|
|
272
|
+
// ------------------------------------------------------------------
|
|
273
|
+
// GET /api/sessions
|
|
274
|
+
// ------------------------------------------------------------------
|
|
275
|
+
app.get('/api/sessions', () => ({
|
|
276
|
+
sessions: sessionManager.list(),
|
|
277
|
+
}));
|
|
278
|
+
// ------------------------------------------------------------------
|
|
279
|
+
// GET /api/session/:id
|
|
280
|
+
// ------------------------------------------------------------------
|
|
281
|
+
app.get('/api/session/:id', ({ params }) => {
|
|
282
|
+
const session = sessionManager.get(params.id);
|
|
283
|
+
if (!session) {
|
|
284
|
+
return new Response(JSON.stringify({ error: 'Session not found' }), {
|
|
285
|
+
status: 404,
|
|
286
|
+
headers: { 'Content-Type': 'application/json' },
|
|
287
|
+
});
|
|
288
|
+
}
|
|
289
|
+
const conversation = getConversation(params.id);
|
|
290
|
+
return {
|
|
291
|
+
session,
|
|
292
|
+
messages: conversation?.messages ?? [],
|
|
293
|
+
};
|
|
294
|
+
});
|
|
295
|
+
// ------------------------------------------------------------------
|
|
296
|
+
// POST /api/chat -- SSE streaming chat
|
|
297
|
+
// ------------------------------------------------------------------
|
|
298
|
+
app.post('/api/chat', async (ctx) => {
|
|
299
|
+
const body = ctx.body;
|
|
300
|
+
const mode = parseMode(body.mode);
|
|
301
|
+
// Get or create session
|
|
302
|
+
let sessionId = body.sessionId;
|
|
303
|
+
let session = sessionId ? sessionManager.get(sessionId) : null;
|
|
304
|
+
if (!session) {
|
|
305
|
+
session = sessionManager.create({
|
|
306
|
+
name: `API Session ${new Date().toISOString().slice(0, 16)}`,
|
|
307
|
+
mode,
|
|
308
|
+
model: body.model,
|
|
309
|
+
});
|
|
310
|
+
sessionId = session.id;
|
|
311
|
+
}
|
|
312
|
+
// Load existing conversation history
|
|
313
|
+
const existing = getConversation(sessionId);
|
|
314
|
+
const history = existing?.messages ?? [];
|
|
315
|
+
const stream = createAgentSSEStream(body.message, history, sessionId, mode, body.model, router, contextManager, sessionManager);
|
|
316
|
+
return sseResponse(stream);
|
|
317
|
+
});
|
|
318
|
+
// ------------------------------------------------------------------
|
|
319
|
+
// POST /api/run -- Non-interactive single prompt
|
|
320
|
+
// ------------------------------------------------------------------
|
|
321
|
+
app.post('/api/run', async (ctx) => {
|
|
322
|
+
const body = ctx.body;
|
|
323
|
+
const mode = parseMode(body.mode);
|
|
324
|
+
const session = sessionManager.create({
|
|
325
|
+
name: `Run: ${body.prompt.slice(0, 50)}`,
|
|
326
|
+
mode,
|
|
327
|
+
model: body.model,
|
|
328
|
+
});
|
|
329
|
+
try {
|
|
330
|
+
const result = await runAgentLoop(body.prompt, [], {
|
|
331
|
+
router,
|
|
332
|
+
toolRegistry: defaultToolRegistry,
|
|
333
|
+
mode,
|
|
334
|
+
model: body.model,
|
|
335
|
+
});
|
|
336
|
+
// Persist conversation and mark session complete
|
|
337
|
+
saveConversation(session.id, body.prompt.slice(0, 100), result.messages, body.model);
|
|
338
|
+
sessionManager.complete(session.id);
|
|
339
|
+
// Extract final assistant message
|
|
340
|
+
const lastAssistant = [...result.messages].reverse().find(m => m.role === 'assistant');
|
|
341
|
+
return {
|
|
342
|
+
sessionId: session.id,
|
|
343
|
+
response: lastAssistant?.content ?? '',
|
|
344
|
+
turns: result.turns,
|
|
345
|
+
usage: result.usage,
|
|
346
|
+
cost: result.totalCost,
|
|
347
|
+
};
|
|
348
|
+
}
|
|
349
|
+
catch (error) {
|
|
350
|
+
const msg = error instanceof Error ? error.message : String(error);
|
|
351
|
+
sessionManager.complete(session.id);
|
|
352
|
+
return new Response(JSON.stringify({ error: msg }), {
|
|
353
|
+
status: 500,
|
|
354
|
+
headers: { 'Content-Type': 'application/json' },
|
|
355
|
+
});
|
|
356
|
+
}
|
|
357
|
+
});
|
|
358
|
+
// ------------------------------------------------------------------
|
|
359
|
+
// POST /api/session/:id -- Continue existing session (SSE)
|
|
360
|
+
// ------------------------------------------------------------------
|
|
361
|
+
app.post('/api/session/:id', async (ctx) => {
|
|
362
|
+
const params = ctx.params;
|
|
363
|
+
const body = ctx.body;
|
|
364
|
+
const session = sessionManager.get(params.id);
|
|
365
|
+
if (!session) {
|
|
366
|
+
return new Response(JSON.stringify({ error: 'Session not found' }), {
|
|
367
|
+
status: 404,
|
|
368
|
+
headers: { 'Content-Type': 'application/json' },
|
|
369
|
+
});
|
|
370
|
+
}
|
|
371
|
+
const existing = getConversation(params.id);
|
|
372
|
+
const history = existing?.messages ?? [];
|
|
373
|
+
const mode = parseMode(session.mode);
|
|
374
|
+
const stream = createAgentSSEStream(body.message, history, params.id, mode, body.model ?? session.model, router, contextManager, sessionManager);
|
|
375
|
+
return sseResponse(stream);
|
|
376
|
+
});
|
|
377
|
+
// ------------------------------------------------------------------
|
|
378
|
+
// POST /api/share -- Share a session
|
|
379
|
+
// ------------------------------------------------------------------
|
|
380
|
+
app.post('/api/share', ctx => {
|
|
381
|
+
const body = ctx.body;
|
|
382
|
+
const shared = shareSession(body.sessionId, {
|
|
383
|
+
isLive: body.isLive,
|
|
384
|
+
ttlDays: body.ttlDays,
|
|
385
|
+
});
|
|
386
|
+
if (!shared) {
|
|
387
|
+
return new Response(JSON.stringify({ error: 'Session not found' }), {
|
|
388
|
+
status: 404,
|
|
389
|
+
headers: { 'Content-Type': 'application/json' },
|
|
390
|
+
});
|
|
391
|
+
}
|
|
392
|
+
return {
|
|
393
|
+
shareId: shared.id,
|
|
394
|
+
url: `http://${host}:${port}/nimbus/share/${shared.id}`,
|
|
395
|
+
expiresAt: shared.expiresAt,
|
|
396
|
+
isLive: shared.isLive,
|
|
397
|
+
};
|
|
398
|
+
});
|
|
399
|
+
// ------------------------------------------------------------------
|
|
400
|
+
// GET /api/share/:id -- Get shared session
|
|
401
|
+
// ------------------------------------------------------------------
|
|
402
|
+
app.get('/api/share/:id', ({ params }) => {
|
|
403
|
+
const shared = getSharedSession(params.id);
|
|
404
|
+
if (!shared) {
|
|
405
|
+
return new Response(JSON.stringify({ error: 'Shared session not found or expired' }), {
|
|
406
|
+
status: 404,
|
|
407
|
+
headers: { 'Content-Type': 'application/json' },
|
|
408
|
+
});
|
|
409
|
+
}
|
|
410
|
+
return shared;
|
|
411
|
+
});
|
|
412
|
+
// ------------------------------------------------------------------
|
|
413
|
+
// GET /api/shares -- List all shares
|
|
414
|
+
// ------------------------------------------------------------------
|
|
415
|
+
app.get('/api/shares', () => ({
|
|
416
|
+
shares: listShares(),
|
|
417
|
+
}));
|
|
418
|
+
// ------------------------------------------------------------------
|
|
419
|
+
// Start listening
|
|
420
|
+
// ------------------------------------------------------------------
|
|
421
|
+
app.listen({ port, hostname: host });
|
|
422
|
+
console.log(`
|
|
423
|
+
Nimbus API Server
|
|
424
|
+
─────────────────────────────
|
|
425
|
+
Local: http://${host}:${port}
|
|
426
|
+
Health: http://${host}:${port}/api/health
|
|
427
|
+
OpenAPI: http://${host}:${port}/api/openapi.json
|
|
428
|
+
${options.auth ? ' Auth: HTTP Basic Auth enabled' : ' Auth: None (use --auth user:pass to enable)'}
|
|
429
|
+
|
|
430
|
+
Press Ctrl+C to stop.
|
|
431
|
+
`);
|
|
432
|
+
}
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* nimbus web -- Start the API server and open the Web UI
|
|
3
|
+
*
|
|
4
|
+
* Convenience command that:
|
|
5
|
+
* 1. Starts `nimbus serve` on the configured port
|
|
6
|
+
* 2. Opens the astron-landing Web UI in the default browser
|
|
7
|
+
*
|
|
8
|
+
* Usage:
|
|
9
|
+
* nimbus web # serve on 4200, open http://localhost:6001/nimbus
|
|
10
|
+
* nimbus web --port 8080 # custom serve port
|
|
11
|
+
* nimbus web --ui-url https://app.example.com/nimbus # custom Web UI URL
|
|
12
|
+
*/
|
|
13
|
+
import { spawn } from 'node:child_process';
|
|
14
|
+
import { serveCommand } from './serve';
|
|
15
|
+
/**
|
|
16
|
+
* Open a URL in the default browser (cross-platform).
|
|
17
|
+
*/
|
|
18
|
+
async function openBrowser(url) {
|
|
19
|
+
const { platform } = process;
|
|
20
|
+
const cmd = platform === 'darwin'
|
|
21
|
+
? ['open', url]
|
|
22
|
+
: platform === 'win32'
|
|
23
|
+
? ['cmd', '/c', 'start', url]
|
|
24
|
+
: ['xdg-open', url];
|
|
25
|
+
const proc = spawn(cmd[0], cmd.slice(1), { stdio: 'ignore', detached: true });
|
|
26
|
+
proc.unref();
|
|
27
|
+
}
|
|
28
|
+
/**
|
|
29
|
+
* Run the web command: start serve and open browser.
|
|
30
|
+
*/
|
|
31
|
+
export async function webCommand(options) {
|
|
32
|
+
const port = options.port ?? 4200;
|
|
33
|
+
const uiUrl = options.uiUrl ?? 'http://localhost:6001/nimbus';
|
|
34
|
+
console.log(`Starting Nimbus API server on port ${port}...`);
|
|
35
|
+
console.log(`Opening Web UI at ${uiUrl}\n`);
|
|
36
|
+
// Open browser after a short delay to let the server start (skipped with --no-open)
|
|
37
|
+
if (!options.noOpen) {
|
|
38
|
+
setTimeout(() => {
|
|
39
|
+
openBrowser(uiUrl).catch(() => {
|
|
40
|
+
console.log(`Could not open browser. Please visit: ${uiUrl}`);
|
|
41
|
+
});
|
|
42
|
+
}, 1500);
|
|
43
|
+
}
|
|
44
|
+
// Start the server (this blocks)
|
|
45
|
+
await serveCommand({
|
|
46
|
+
port,
|
|
47
|
+
host: options.host,
|
|
48
|
+
auth: options.auth,
|
|
49
|
+
});
|
|
50
|
+
}
|