@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,357 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* GCP GKE CLI Commands
|
|
3
|
+
*
|
|
4
|
+
* Operations for Google Kubernetes Engine clusters
|
|
5
|
+
*/
|
|
6
|
+
import { execFile } from 'child_process';
|
|
7
|
+
import { promisify } from 'util';
|
|
8
|
+
import { logger } from '../../utils';
|
|
9
|
+
import { ui } from '../../wizard/ui';
|
|
10
|
+
import { loadSafetyPolicy, evaluateSafety, } from '../../config/safety-policy';
|
|
11
|
+
import { promptForApproval, displaySafetySummary } from '../../wizard/approval';
|
|
12
|
+
const execFileAsync = promisify(execFile);
|
|
13
|
+
/**
|
|
14
|
+
* Run GKE safety checks
|
|
15
|
+
*/
|
|
16
|
+
async function runGkeSafetyChecks(action, clusterName, options) {
|
|
17
|
+
const safetyPolicy = loadSafetyPolicy();
|
|
18
|
+
const context = {
|
|
19
|
+
operation: action,
|
|
20
|
+
type: 'gcp',
|
|
21
|
+
environment: options.project || 'default',
|
|
22
|
+
resources: [clusterName],
|
|
23
|
+
metadata: {
|
|
24
|
+
resourceType: 'gke-cluster',
|
|
25
|
+
resourceId: clusterName,
|
|
26
|
+
},
|
|
27
|
+
};
|
|
28
|
+
return evaluateSafety(context, safetyPolicy);
|
|
29
|
+
}
|
|
30
|
+
/**
|
|
31
|
+
* Main GKE command router
|
|
32
|
+
*/
|
|
33
|
+
export async function gkeCommand(action, args, options) {
|
|
34
|
+
logger.info('Running GCP GKE command', { action, args, options });
|
|
35
|
+
switch (action) {
|
|
36
|
+
case 'clusters':
|
|
37
|
+
case 'list':
|
|
38
|
+
await listClusters(options);
|
|
39
|
+
break;
|
|
40
|
+
case 'describe':
|
|
41
|
+
if (!args[0]) {
|
|
42
|
+
ui.error('Cluster name required');
|
|
43
|
+
return;
|
|
44
|
+
}
|
|
45
|
+
await describeCluster(args[0], options);
|
|
46
|
+
break;
|
|
47
|
+
case 'get-credentials':
|
|
48
|
+
if (!args[0]) {
|
|
49
|
+
ui.error('Cluster name required');
|
|
50
|
+
return;
|
|
51
|
+
}
|
|
52
|
+
await getCredentials(args[0], options);
|
|
53
|
+
break;
|
|
54
|
+
case 'node-pools':
|
|
55
|
+
if (!args[0]) {
|
|
56
|
+
ui.error('Cluster name required');
|
|
57
|
+
return;
|
|
58
|
+
}
|
|
59
|
+
await listNodePools(args[0], options);
|
|
60
|
+
break;
|
|
61
|
+
case 'resize':
|
|
62
|
+
if (args.length < 2) {
|
|
63
|
+
ui.error('Cluster name and node count required');
|
|
64
|
+
return;
|
|
65
|
+
}
|
|
66
|
+
await resizeCluster(args[0], parseInt(args[1], 10), options);
|
|
67
|
+
break;
|
|
68
|
+
case 'delete':
|
|
69
|
+
if (!args[0]) {
|
|
70
|
+
ui.error('Cluster name required');
|
|
71
|
+
return;
|
|
72
|
+
}
|
|
73
|
+
await deleteCluster(args[0], options);
|
|
74
|
+
break;
|
|
75
|
+
default:
|
|
76
|
+
showGkeHelp();
|
|
77
|
+
break;
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
/**
|
|
81
|
+
* List GKE clusters
|
|
82
|
+
*/
|
|
83
|
+
async function listClusters(options) {
|
|
84
|
+
ui.header('GKE Clusters');
|
|
85
|
+
ui.newLine();
|
|
86
|
+
const gcloudArgs = ['container', 'clusters', 'list', '--format=json'];
|
|
87
|
+
if (options.project) {
|
|
88
|
+
gcloudArgs.push(`--project=${options.project}`);
|
|
89
|
+
}
|
|
90
|
+
if (options.region) {
|
|
91
|
+
gcloudArgs.push(`--region=${options.region}`);
|
|
92
|
+
}
|
|
93
|
+
if (options.zone) {
|
|
94
|
+
gcloudArgs.push(`--zone=${options.zone}`);
|
|
95
|
+
}
|
|
96
|
+
try {
|
|
97
|
+
ui.startSpinner({ message: 'Fetching clusters...' });
|
|
98
|
+
const { stdout } = await execFileAsync('gcloud', gcloudArgs);
|
|
99
|
+
ui.stopSpinnerSuccess('Clusters fetched');
|
|
100
|
+
const clusters = JSON.parse(stdout || '[]');
|
|
101
|
+
if (clusters.length === 0) {
|
|
102
|
+
ui.info('No clusters found');
|
|
103
|
+
return;
|
|
104
|
+
}
|
|
105
|
+
ui.print(`Found ${clusters.length} cluster(s)\n`);
|
|
106
|
+
// Display table
|
|
107
|
+
ui.print(ui.color(`${'Name'.padEnd(25) + 'Location'.padEnd(20) + 'Node Count'.padEnd(12) + 'Status'.padEnd(12)}Version`, 'cyan'));
|
|
108
|
+
ui.print('─'.repeat(90));
|
|
109
|
+
for (const cluster of clusters) {
|
|
110
|
+
const name = cluster.name?.substring(0, 24) || '';
|
|
111
|
+
const location = cluster.location?.substring(0, 19) || '';
|
|
112
|
+
const nodeCount = String(cluster.currentNodeCount || 0);
|
|
113
|
+
const status = cluster.status || '';
|
|
114
|
+
const version = cluster.currentMasterVersion || '';
|
|
115
|
+
const statusColor = status === 'RUNNING'
|
|
116
|
+
? 'green'
|
|
117
|
+
: status === 'PROVISIONING'
|
|
118
|
+
? 'yellow'
|
|
119
|
+
: status === 'ERROR'
|
|
120
|
+
? 'red'
|
|
121
|
+
: 'white';
|
|
122
|
+
ui.print(`${name.padEnd(25)}${location.padEnd(20)}${nodeCount.padEnd(12)}${ui.color(status.padEnd(12), statusColor)}${version}`);
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
catch (error) {
|
|
126
|
+
ui.stopSpinnerFail('Failed to fetch clusters');
|
|
127
|
+
const message = error instanceof Error ? error.message : 'Unknown error';
|
|
128
|
+
logger.error('Failed to list clusters', { error: message });
|
|
129
|
+
ui.error(`Failed to list clusters: ${message}`);
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
/**
|
|
133
|
+
* Describe a specific cluster
|
|
134
|
+
*/
|
|
135
|
+
async function describeCluster(clusterName, options) {
|
|
136
|
+
ui.header(`Cluster: ${clusterName}`);
|
|
137
|
+
ui.newLine();
|
|
138
|
+
const gcloudArgs = ['container', 'clusters', 'describe', clusterName, '--format=json'];
|
|
139
|
+
if (options.project) {
|
|
140
|
+
gcloudArgs.push(`--project=${options.project}`);
|
|
141
|
+
}
|
|
142
|
+
if (options.region) {
|
|
143
|
+
gcloudArgs.push(`--region=${options.region}`);
|
|
144
|
+
}
|
|
145
|
+
if (options.zone) {
|
|
146
|
+
gcloudArgs.push(`--zone=${options.zone}`);
|
|
147
|
+
}
|
|
148
|
+
try {
|
|
149
|
+
ui.startSpinner({ message: 'Fetching cluster details...' });
|
|
150
|
+
const { stdout } = await execFileAsync('gcloud', gcloudArgs);
|
|
151
|
+
ui.stopSpinnerSuccess('Details fetched');
|
|
152
|
+
const cluster = JSON.parse(stdout);
|
|
153
|
+
ui.print(ui.bold('Basic Information:'));
|
|
154
|
+
ui.print(` Name: ${cluster.name}`);
|
|
155
|
+
ui.print(` Location: ${cluster.location}`);
|
|
156
|
+
ui.print(` Status: ${cluster.status}`);
|
|
157
|
+
ui.print(` Master Version: ${cluster.currentMasterVersion}`);
|
|
158
|
+
ui.print(` Node Count: ${cluster.currentNodeCount}`);
|
|
159
|
+
ui.print(` Endpoint: ${cluster.endpoint}`);
|
|
160
|
+
ui.newLine();
|
|
161
|
+
ui.print(ui.bold('Node Config:'));
|
|
162
|
+
const nodeConfig = cluster.nodeConfig || {};
|
|
163
|
+
ui.print(` Machine Type: ${nodeConfig.machineType}`);
|
|
164
|
+
ui.print(` Disk Size: ${nodeConfig.diskSizeGb} GB`);
|
|
165
|
+
ui.print(` Disk Type: ${nodeConfig.diskType}`);
|
|
166
|
+
ui.newLine();
|
|
167
|
+
ui.print(ui.bold('Networking:'));
|
|
168
|
+
ui.print(` Network: ${cluster.network}`);
|
|
169
|
+
ui.print(` Subnetwork: ${cluster.subnetwork}`);
|
|
170
|
+
ui.print(` Cluster CIDR: ${cluster.clusterIpv4Cidr}`);
|
|
171
|
+
ui.print(` Services CIDR: ${cluster.servicesIpv4Cidr}`);
|
|
172
|
+
}
|
|
173
|
+
catch (error) {
|
|
174
|
+
ui.stopSpinnerFail('Failed to fetch details');
|
|
175
|
+
const message = error instanceof Error ? error.message : 'Unknown error';
|
|
176
|
+
logger.error('Failed to describe cluster', { error: message });
|
|
177
|
+
ui.error(`Failed to describe cluster: ${message}`);
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
/**
|
|
181
|
+
* Get cluster credentials for kubectl
|
|
182
|
+
*/
|
|
183
|
+
async function getCredentials(clusterName, options) {
|
|
184
|
+
const gcloudArgs = ['container', 'clusters', 'get-credentials', clusterName];
|
|
185
|
+
if (options.project) {
|
|
186
|
+
gcloudArgs.push(`--project=${options.project}`);
|
|
187
|
+
}
|
|
188
|
+
if (options.region) {
|
|
189
|
+
gcloudArgs.push(`--region=${options.region}`);
|
|
190
|
+
}
|
|
191
|
+
if (options.zone) {
|
|
192
|
+
gcloudArgs.push(`--zone=${options.zone}`);
|
|
193
|
+
}
|
|
194
|
+
try {
|
|
195
|
+
ui.startSpinner({ message: `Getting credentials for ${clusterName}...` });
|
|
196
|
+
await execFileAsync('gcloud', gcloudArgs);
|
|
197
|
+
ui.stopSpinnerSuccess(`Credentials configured for cluster ${clusterName}`);
|
|
198
|
+
ui.info('You can now use kubectl to interact with this cluster');
|
|
199
|
+
}
|
|
200
|
+
catch (error) {
|
|
201
|
+
ui.stopSpinnerFail('Failed to get credentials');
|
|
202
|
+
const message = error instanceof Error ? error.message : 'Unknown error';
|
|
203
|
+
logger.error('Failed to get credentials', { error: message });
|
|
204
|
+
ui.error(`Failed to get credentials: ${message}`);
|
|
205
|
+
}
|
|
206
|
+
}
|
|
207
|
+
/**
|
|
208
|
+
* List node pools in a cluster
|
|
209
|
+
*/
|
|
210
|
+
async function listNodePools(clusterName, options) {
|
|
211
|
+
ui.header(`Node Pools in ${clusterName}`);
|
|
212
|
+
ui.newLine();
|
|
213
|
+
const gcloudArgs = ['container', 'node-pools', 'list', '--cluster', clusterName, '--format=json'];
|
|
214
|
+
if (options.project) {
|
|
215
|
+
gcloudArgs.push(`--project=${options.project}`);
|
|
216
|
+
}
|
|
217
|
+
if (options.region) {
|
|
218
|
+
gcloudArgs.push(`--region=${options.region}`);
|
|
219
|
+
}
|
|
220
|
+
if (options.zone) {
|
|
221
|
+
gcloudArgs.push(`--zone=${options.zone}`);
|
|
222
|
+
}
|
|
223
|
+
try {
|
|
224
|
+
ui.startSpinner({ message: 'Fetching node pools...' });
|
|
225
|
+
const { stdout } = await execFileAsync('gcloud', gcloudArgs);
|
|
226
|
+
ui.stopSpinnerSuccess('Node pools fetched');
|
|
227
|
+
const nodePools = JSON.parse(stdout || '[]');
|
|
228
|
+
if (nodePools.length === 0) {
|
|
229
|
+
ui.info('No node pools found');
|
|
230
|
+
return;
|
|
231
|
+
}
|
|
232
|
+
ui.print(`Found ${nodePools.length} node pool(s)\n`);
|
|
233
|
+
// Display table
|
|
234
|
+
ui.print(ui.color(`${'Name'.padEnd(25) + 'Machine Type'.padEnd(20) + 'Node Count'.padEnd(12)}Version`, 'cyan'));
|
|
235
|
+
ui.print('─'.repeat(75));
|
|
236
|
+
for (const pool of nodePools) {
|
|
237
|
+
const name = pool.name?.substring(0, 24) || '';
|
|
238
|
+
const machineType = pool.config?.machineType?.substring(0, 19) || '';
|
|
239
|
+
const nodeCount = String(pool.initialNodeCount || 0);
|
|
240
|
+
const version = pool.version || '';
|
|
241
|
+
ui.print(`${name.padEnd(25)}${machineType.padEnd(20)}${nodeCount.padEnd(12)}${version}`);
|
|
242
|
+
}
|
|
243
|
+
}
|
|
244
|
+
catch (error) {
|
|
245
|
+
ui.stopSpinnerFail('Failed to fetch node pools');
|
|
246
|
+
const message = error instanceof Error ? error.message : 'Unknown error';
|
|
247
|
+
logger.error('Failed to list node pools', { error: message });
|
|
248
|
+
ui.error(`Failed to list node pools: ${message}`);
|
|
249
|
+
}
|
|
250
|
+
}
|
|
251
|
+
/**
|
|
252
|
+
* Resize cluster node pool
|
|
253
|
+
*/
|
|
254
|
+
async function resizeCluster(clusterName, nodeCount, options) {
|
|
255
|
+
// Run safety checks
|
|
256
|
+
const safetyResult = await runGkeSafetyChecks('resize', clusterName, options);
|
|
257
|
+
displaySafetySummary({
|
|
258
|
+
operation: `resize ${clusterName} to ${nodeCount} nodes`,
|
|
259
|
+
risks: safetyResult.risks,
|
|
260
|
+
passed: safetyResult.passed,
|
|
261
|
+
});
|
|
262
|
+
if (safetyResult.requiresApproval) {
|
|
263
|
+
const result = await promptForApproval({
|
|
264
|
+
title: 'Resize GKE Cluster',
|
|
265
|
+
operation: `gcloud container clusters resize ${clusterName} --num-nodes=${nodeCount}`,
|
|
266
|
+
risks: safetyResult.risks,
|
|
267
|
+
});
|
|
268
|
+
if (!result.approved) {
|
|
269
|
+
ui.warning('Operation cancelled');
|
|
270
|
+
return;
|
|
271
|
+
}
|
|
272
|
+
}
|
|
273
|
+
const gcloudArgs = [
|
|
274
|
+
'container',
|
|
275
|
+
'clusters',
|
|
276
|
+
'resize',
|
|
277
|
+
clusterName,
|
|
278
|
+
`--num-nodes=${nodeCount}`,
|
|
279
|
+
'--quiet',
|
|
280
|
+
];
|
|
281
|
+
if (options.project) {
|
|
282
|
+
gcloudArgs.push(`--project=${options.project}`);
|
|
283
|
+
}
|
|
284
|
+
if (options.region) {
|
|
285
|
+
gcloudArgs.push(`--region=${options.region}`);
|
|
286
|
+
}
|
|
287
|
+
if (options.zone) {
|
|
288
|
+
gcloudArgs.push(`--zone=${options.zone}`);
|
|
289
|
+
}
|
|
290
|
+
try {
|
|
291
|
+
ui.startSpinner({ message: `Resizing cluster ${clusterName} to ${nodeCount} nodes...` });
|
|
292
|
+
await execFileAsync('gcloud', gcloudArgs);
|
|
293
|
+
ui.stopSpinnerSuccess(`Cluster ${clusterName} resized to ${nodeCount} nodes`);
|
|
294
|
+
}
|
|
295
|
+
catch (error) {
|
|
296
|
+
ui.stopSpinnerFail('Failed to resize cluster');
|
|
297
|
+
const message = error instanceof Error ? error.message : 'Unknown error';
|
|
298
|
+
logger.error('Failed to resize cluster', { error: message });
|
|
299
|
+
ui.error(`Failed to resize cluster: ${message}`);
|
|
300
|
+
}
|
|
301
|
+
}
|
|
302
|
+
/**
|
|
303
|
+
* Delete a cluster (requires safety approval)
|
|
304
|
+
*/
|
|
305
|
+
async function deleteCluster(clusterName, options) {
|
|
306
|
+
// Run safety checks
|
|
307
|
+
const safetyResult = await runGkeSafetyChecks('delete', clusterName, options);
|
|
308
|
+
displaySafetySummary({
|
|
309
|
+
operation: `delete cluster ${clusterName}`,
|
|
310
|
+
risks: safetyResult.risks,
|
|
311
|
+
passed: safetyResult.passed,
|
|
312
|
+
});
|
|
313
|
+
if (safetyResult.requiresApproval) {
|
|
314
|
+
const result = await promptForApproval({
|
|
315
|
+
title: 'Delete GKE Cluster',
|
|
316
|
+
operation: `gcloud container clusters delete ${clusterName}`,
|
|
317
|
+
risks: safetyResult.risks,
|
|
318
|
+
});
|
|
319
|
+
if (!result.approved) {
|
|
320
|
+
ui.warning('Operation cancelled');
|
|
321
|
+
return;
|
|
322
|
+
}
|
|
323
|
+
}
|
|
324
|
+
const gcloudArgs = ['container', 'clusters', 'delete', clusterName, '--quiet'];
|
|
325
|
+
if (options.project) {
|
|
326
|
+
gcloudArgs.push(`--project=${options.project}`);
|
|
327
|
+
}
|
|
328
|
+
if (options.region) {
|
|
329
|
+
gcloudArgs.push(`--region=${options.region}`);
|
|
330
|
+
}
|
|
331
|
+
if (options.zone) {
|
|
332
|
+
gcloudArgs.push(`--zone=${options.zone}`);
|
|
333
|
+
}
|
|
334
|
+
try {
|
|
335
|
+
ui.startSpinner({ message: `Deleting cluster ${clusterName}...` });
|
|
336
|
+
await execFileAsync('gcloud', gcloudArgs);
|
|
337
|
+
ui.stopSpinnerSuccess(`Cluster ${clusterName} deleted`);
|
|
338
|
+
}
|
|
339
|
+
catch (error) {
|
|
340
|
+
ui.stopSpinnerFail('Failed to delete cluster');
|
|
341
|
+
const message = error instanceof Error ? error.message : 'Unknown error';
|
|
342
|
+
logger.error('Failed to delete cluster', { error: message });
|
|
343
|
+
ui.error(`Failed to delete cluster: ${message}`);
|
|
344
|
+
}
|
|
345
|
+
}
|
|
346
|
+
/**
|
|
347
|
+
* Show GKE help
|
|
348
|
+
*/
|
|
349
|
+
function showGkeHelp() {
|
|
350
|
+
ui.print(ui.bold('GKE Commands:'));
|
|
351
|
+
ui.print(' clusters List all GKE clusters');
|
|
352
|
+
ui.print(' describe <name> Show cluster details');
|
|
353
|
+
ui.print(' get-credentials <name> Configure kubectl for cluster');
|
|
354
|
+
ui.print(' node-pools <cluster> List node pools in cluster');
|
|
355
|
+
ui.print(' resize <cluster> <count> Resize cluster (requires approval)');
|
|
356
|
+
ui.print(' delete <name> Delete cluster (requires approval)');
|
|
357
|
+
}
|
|
@@ -0,0 +1,295 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* GCP IAM CLI Commands
|
|
3
|
+
*
|
|
4
|
+
* Operations for IAM service accounts and roles
|
|
5
|
+
*/
|
|
6
|
+
import { execFile } from 'child_process';
|
|
7
|
+
import { promisify } from 'util';
|
|
8
|
+
import { logger } from '../../utils';
|
|
9
|
+
import { ui } from '../../wizard/ui';
|
|
10
|
+
const execFileAsync = promisify(execFile);
|
|
11
|
+
/**
|
|
12
|
+
* Main IAM command router
|
|
13
|
+
*/
|
|
14
|
+
export async function iamCommand(action, args, options) {
|
|
15
|
+
logger.info('Running GCP IAM command', { action, args, options });
|
|
16
|
+
switch (action) {
|
|
17
|
+
case 'service-accounts':
|
|
18
|
+
case 'sa':
|
|
19
|
+
await listServiceAccounts(options);
|
|
20
|
+
break;
|
|
21
|
+
case 'describe-sa':
|
|
22
|
+
if (!args[0]) {
|
|
23
|
+
ui.error('Service account email required');
|
|
24
|
+
return;
|
|
25
|
+
}
|
|
26
|
+
await describeServiceAccount(args[0], options);
|
|
27
|
+
break;
|
|
28
|
+
case 'roles':
|
|
29
|
+
await listRoles(options);
|
|
30
|
+
break;
|
|
31
|
+
case 'describe-role':
|
|
32
|
+
if (!args[0]) {
|
|
33
|
+
ui.error('Role name required');
|
|
34
|
+
return;
|
|
35
|
+
}
|
|
36
|
+
await describeRole(args[0], options);
|
|
37
|
+
break;
|
|
38
|
+
case 'bindings':
|
|
39
|
+
await listBindings(options);
|
|
40
|
+
break;
|
|
41
|
+
default:
|
|
42
|
+
showIamHelp();
|
|
43
|
+
break;
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
/**
|
|
47
|
+
* List service accounts
|
|
48
|
+
*/
|
|
49
|
+
async function listServiceAccounts(options) {
|
|
50
|
+
ui.header('Service Accounts');
|
|
51
|
+
ui.newLine();
|
|
52
|
+
const gcloudArgs = ['iam', 'service-accounts', 'list', '--format=json'];
|
|
53
|
+
if (options.project) {
|
|
54
|
+
gcloudArgs.push(`--project=${options.project}`);
|
|
55
|
+
}
|
|
56
|
+
try {
|
|
57
|
+
ui.startSpinner({ message: 'Fetching service accounts...' });
|
|
58
|
+
const { stdout } = await execFileAsync('gcloud', gcloudArgs);
|
|
59
|
+
ui.stopSpinnerSuccess('Service accounts fetched');
|
|
60
|
+
const accounts = JSON.parse(stdout || '[]');
|
|
61
|
+
if (accounts.length === 0) {
|
|
62
|
+
ui.info('No service accounts found');
|
|
63
|
+
return;
|
|
64
|
+
}
|
|
65
|
+
ui.print(`Found ${accounts.length} service account(s)\n`);
|
|
66
|
+
// Display table
|
|
67
|
+
ui.print(ui.color(`${'Display Name'.padEnd(30) + 'Email'.padEnd(50)}Disabled`, 'cyan'));
|
|
68
|
+
ui.print('─'.repeat(90));
|
|
69
|
+
for (const account of accounts) {
|
|
70
|
+
const displayName = account.displayName?.substring(0, 29) || '(no name)';
|
|
71
|
+
const email = account.email?.substring(0, 49) || '';
|
|
72
|
+
const disabled = account.disabled ? 'Yes' : 'No';
|
|
73
|
+
ui.print(`${displayName.padEnd(30)}${email.padEnd(50)}${disabled}`);
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
catch (error) {
|
|
77
|
+
ui.stopSpinnerFail('Failed to fetch service accounts');
|
|
78
|
+
const message = error instanceof Error ? error.message : 'Unknown error';
|
|
79
|
+
logger.error('Failed to list service accounts', { error: message });
|
|
80
|
+
ui.error(`Failed to list service accounts: ${message}`);
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
/**
|
|
84
|
+
* Describe a specific service account
|
|
85
|
+
*/
|
|
86
|
+
async function describeServiceAccount(email, options) {
|
|
87
|
+
ui.header(`Service Account: ${email}`);
|
|
88
|
+
ui.newLine();
|
|
89
|
+
const gcloudArgs = ['iam', 'service-accounts', 'describe', email, '--format=json'];
|
|
90
|
+
if (options.project) {
|
|
91
|
+
gcloudArgs.push(`--project=${options.project}`);
|
|
92
|
+
}
|
|
93
|
+
try {
|
|
94
|
+
ui.startSpinner({ message: 'Fetching service account details...' });
|
|
95
|
+
const { stdout } = await execFileAsync('gcloud', gcloudArgs);
|
|
96
|
+
ui.stopSpinnerSuccess('Details fetched');
|
|
97
|
+
const account = JSON.parse(stdout);
|
|
98
|
+
ui.print(ui.bold('Basic Information:'));
|
|
99
|
+
ui.print(` Display Name: ${account.displayName || '(none)'}`);
|
|
100
|
+
ui.print(` Email: ${account.email}`);
|
|
101
|
+
ui.print(` Unique ID: ${account.uniqueId}`);
|
|
102
|
+
ui.print(` Disabled: ${account.disabled ? 'Yes' : 'No'}`);
|
|
103
|
+
ui.print(` Description: ${account.description || '(none)'}`);
|
|
104
|
+
ui.newLine();
|
|
105
|
+
// Get keys
|
|
106
|
+
try {
|
|
107
|
+
const keysArgs = [
|
|
108
|
+
'iam',
|
|
109
|
+
'service-accounts',
|
|
110
|
+
'keys',
|
|
111
|
+
'list',
|
|
112
|
+
'--iam-account',
|
|
113
|
+
email,
|
|
114
|
+
'--format=json',
|
|
115
|
+
];
|
|
116
|
+
if (options.project) {
|
|
117
|
+
keysArgs.push(`--project=${options.project}`);
|
|
118
|
+
}
|
|
119
|
+
const { stdout: keysOutput } = await execFileAsync('gcloud', keysArgs);
|
|
120
|
+
const keys = JSON.parse(keysOutput || '[]');
|
|
121
|
+
ui.print(ui.bold('Keys:'));
|
|
122
|
+
if (keys.length === 0) {
|
|
123
|
+
ui.print(' No keys found');
|
|
124
|
+
}
|
|
125
|
+
else {
|
|
126
|
+
for (const key of keys) {
|
|
127
|
+
const keyId = key.name?.split('/').pop() || '';
|
|
128
|
+
const keyType = key.keyType || '';
|
|
129
|
+
const validAfter = key.validAfterTime || '';
|
|
130
|
+
const validBefore = key.validBeforeTime || '';
|
|
131
|
+
ui.print(` Key ID: ${keyId}`);
|
|
132
|
+
ui.print(` Type: ${keyType}`);
|
|
133
|
+
ui.print(` Valid After: ${validAfter}`);
|
|
134
|
+
ui.print(` Valid Before: ${validBefore}`);
|
|
135
|
+
ui.newLine();
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
catch {
|
|
140
|
+
ui.print(ui.bold('Keys:'));
|
|
141
|
+
ui.print(' Unable to fetch keys');
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
catch (error) {
|
|
145
|
+
ui.stopSpinnerFail('Failed to fetch details');
|
|
146
|
+
const message = error instanceof Error ? error.message : 'Unknown error';
|
|
147
|
+
logger.error('Failed to describe service account', { error: message });
|
|
148
|
+
ui.error(`Failed to describe service account: ${message}`);
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
/**
|
|
152
|
+
* List predefined roles
|
|
153
|
+
*/
|
|
154
|
+
async function listRoles(options) {
|
|
155
|
+
ui.header('IAM Roles');
|
|
156
|
+
ui.newLine();
|
|
157
|
+
// List project-level custom roles
|
|
158
|
+
const gcloudArgs = ['iam', 'roles', 'list', '--format=json'];
|
|
159
|
+
if (options.project) {
|
|
160
|
+
gcloudArgs.push(`--project=${options.project}`);
|
|
161
|
+
}
|
|
162
|
+
try {
|
|
163
|
+
ui.startSpinner({ message: 'Fetching roles...' });
|
|
164
|
+
const { stdout } = await execFileAsync('gcloud', gcloudArgs);
|
|
165
|
+
ui.stopSpinnerSuccess('Roles fetched');
|
|
166
|
+
const roles = JSON.parse(stdout || '[]');
|
|
167
|
+
if (roles.length === 0) {
|
|
168
|
+
ui.info('No custom roles found. Use gcloud iam roles list --filter="stage=GA" to see predefined roles.');
|
|
169
|
+
return;
|
|
170
|
+
}
|
|
171
|
+
ui.print(`Found ${roles.length} custom role(s)\n`);
|
|
172
|
+
// Display table
|
|
173
|
+
ui.print(ui.color(`${'Name'.padEnd(40) + 'Title'.padEnd(35)}Stage`, 'cyan'));
|
|
174
|
+
ui.print('─'.repeat(85));
|
|
175
|
+
for (const role of roles) {
|
|
176
|
+
const name = role.name?.split('/').pop()?.substring(0, 39) || '';
|
|
177
|
+
const title = role.title?.substring(0, 34) || '';
|
|
178
|
+
const stage = role.stage || '';
|
|
179
|
+
ui.print(`${name.padEnd(40)}${title.padEnd(35)}${stage}`);
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
catch (error) {
|
|
183
|
+
ui.stopSpinnerFail('Failed to fetch roles');
|
|
184
|
+
const message = error instanceof Error ? error.message : 'Unknown error';
|
|
185
|
+
logger.error('Failed to list roles', { error: message });
|
|
186
|
+
ui.error(`Failed to list roles: ${message}`);
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
/**
|
|
190
|
+
* Describe a specific role
|
|
191
|
+
*/
|
|
192
|
+
async function describeRole(roleName, options) {
|
|
193
|
+
ui.header(`Role: ${roleName}`);
|
|
194
|
+
ui.newLine();
|
|
195
|
+
const gcloudArgs = ['iam', 'roles', 'describe', roleName, '--format=json'];
|
|
196
|
+
if (options.project && !roleName.startsWith('roles/')) {
|
|
197
|
+
gcloudArgs.push(`--project=${options.project}`);
|
|
198
|
+
}
|
|
199
|
+
try {
|
|
200
|
+
ui.startSpinner({ message: 'Fetching role details...' });
|
|
201
|
+
const { stdout } = await execFileAsync('gcloud', gcloudArgs);
|
|
202
|
+
ui.stopSpinnerSuccess('Details fetched');
|
|
203
|
+
const role = JSON.parse(stdout);
|
|
204
|
+
ui.print(ui.bold('Basic Information:'));
|
|
205
|
+
ui.print(` Name: ${role.name}`);
|
|
206
|
+
ui.print(` Title: ${role.title}`);
|
|
207
|
+
ui.print(` Description: ${role.description || '(none)'}`);
|
|
208
|
+
ui.print(` Stage: ${role.stage}`);
|
|
209
|
+
ui.print(` ETag: ${role.etag}`);
|
|
210
|
+
ui.newLine();
|
|
211
|
+
ui.print(ui.bold('Permissions:'));
|
|
212
|
+
const permissions = role.includedPermissions || [];
|
|
213
|
+
if (permissions.length === 0) {
|
|
214
|
+
ui.print(' No permissions');
|
|
215
|
+
}
|
|
216
|
+
else {
|
|
217
|
+
ui.print(` Total: ${permissions.length} permission(s)`);
|
|
218
|
+
// Show first 20 permissions
|
|
219
|
+
const displayPerms = permissions.slice(0, 20);
|
|
220
|
+
for (const perm of displayPerms) {
|
|
221
|
+
ui.print(` ${perm}`);
|
|
222
|
+
}
|
|
223
|
+
if (permissions.length > 20) {
|
|
224
|
+
ui.print(ui.dim(` ... and ${permissions.length - 20} more`));
|
|
225
|
+
}
|
|
226
|
+
}
|
|
227
|
+
}
|
|
228
|
+
catch (error) {
|
|
229
|
+
ui.stopSpinnerFail('Failed to fetch details');
|
|
230
|
+
const message = error instanceof Error ? error.message : 'Unknown error';
|
|
231
|
+
logger.error('Failed to describe role', { error: message });
|
|
232
|
+
ui.error(`Failed to describe role: ${message}`);
|
|
233
|
+
}
|
|
234
|
+
}
|
|
235
|
+
/**
|
|
236
|
+
* List IAM bindings for the project
|
|
237
|
+
*/
|
|
238
|
+
async function listBindings(options) {
|
|
239
|
+
ui.header('IAM Policy Bindings');
|
|
240
|
+
ui.newLine();
|
|
241
|
+
let projectId = options.project || '';
|
|
242
|
+
if (!projectId) {
|
|
243
|
+
// Get current project
|
|
244
|
+
try {
|
|
245
|
+
const { stdout: projectOut } = await execFileAsync('gcloud', [
|
|
246
|
+
'config',
|
|
247
|
+
'get-value',
|
|
248
|
+
'project',
|
|
249
|
+
]);
|
|
250
|
+
projectId = projectOut.trim();
|
|
251
|
+
}
|
|
252
|
+
catch {
|
|
253
|
+
ui.error('Project not specified and no default project configured');
|
|
254
|
+
return;
|
|
255
|
+
}
|
|
256
|
+
}
|
|
257
|
+
const gcloudArgs = ['projects', 'get-iam-policy', projectId, '--format=json'];
|
|
258
|
+
try {
|
|
259
|
+
ui.startSpinner({ message: 'Fetching IAM policy...' });
|
|
260
|
+
const { stdout } = await execFileAsync('gcloud', gcloudArgs);
|
|
261
|
+
ui.stopSpinnerSuccess('Policy fetched');
|
|
262
|
+
const policy = JSON.parse(stdout);
|
|
263
|
+
const bindings = policy.bindings || [];
|
|
264
|
+
if (bindings.length === 0) {
|
|
265
|
+
ui.info('No IAM bindings found');
|
|
266
|
+
return;
|
|
267
|
+
}
|
|
268
|
+
ui.print(`Found ${bindings.length} binding(s)\n`);
|
|
269
|
+
for (const binding of bindings) {
|
|
270
|
+
ui.print(ui.bold(`Role: ${binding.role}`));
|
|
271
|
+
ui.print(' Members:');
|
|
272
|
+
for (const member of binding.members || []) {
|
|
273
|
+
ui.print(` - ${member}`);
|
|
274
|
+
}
|
|
275
|
+
ui.newLine();
|
|
276
|
+
}
|
|
277
|
+
}
|
|
278
|
+
catch (error) {
|
|
279
|
+
ui.stopSpinnerFail('Failed to fetch policy');
|
|
280
|
+
const message = error instanceof Error ? error.message : 'Unknown error';
|
|
281
|
+
logger.error('Failed to get IAM policy', { error: message });
|
|
282
|
+
ui.error(`Failed to get IAM policy: ${message}`);
|
|
283
|
+
}
|
|
284
|
+
}
|
|
285
|
+
/**
|
|
286
|
+
* Show IAM help
|
|
287
|
+
*/
|
|
288
|
+
function showIamHelp() {
|
|
289
|
+
ui.print(ui.bold('IAM Commands:'));
|
|
290
|
+
ui.print(' service-accounts List all service accounts');
|
|
291
|
+
ui.print(' describe-sa <email> Show service account details');
|
|
292
|
+
ui.print(' roles List custom roles');
|
|
293
|
+
ui.print(' describe-role <name> Show role details');
|
|
294
|
+
ui.print(' bindings Show project IAM policy bindings');
|
|
295
|
+
}
|