@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.
Files changed (469) hide show
  1. package/bin/nimbus +26 -10
  2. package/bin/nimbus.cmd +41 -0
  3. package/bin/nimbus.mjs +70 -0
  4. package/completions/nimbus.bash +38 -0
  5. package/completions/nimbus.fish +48 -0
  6. package/completions/nimbus.zsh +81 -0
  7. package/dist/src/agent/compaction-agent.js +215 -0
  8. package/dist/src/agent/context-manager.js +385 -0
  9. package/dist/src/agent/context.js +322 -0
  10. package/dist/src/agent/deploy-preview.js +395 -0
  11. package/dist/src/agent/expand-files.js +95 -0
  12. package/dist/src/agent/index.js +18 -0
  13. package/dist/src/agent/loop.js +1535 -0
  14. package/dist/src/agent/modes.js +347 -0
  15. package/dist/src/agent/permissions.js +396 -0
  16. package/dist/src/agent/subagents/base.js +67 -0
  17. package/dist/src/agent/subagents/cost.js +45 -0
  18. package/dist/src/agent/subagents/explore.js +36 -0
  19. package/dist/src/agent/subagents/general.js +41 -0
  20. package/dist/src/agent/subagents/index.js +88 -0
  21. package/dist/src/agent/subagents/infra.js +52 -0
  22. package/dist/src/agent/subagents/security.js +60 -0
  23. package/dist/src/agent/system-prompt.js +860 -0
  24. package/dist/src/app.js +152 -0
  25. package/dist/src/audit/activity-log.js +209 -0
  26. package/dist/src/audit/compliance-checker.js +419 -0
  27. package/dist/src/audit/cost-tracker.js +231 -0
  28. package/dist/src/audit/index.js +10 -0
  29. package/dist/src/audit/security-scanner.js +490 -0
  30. package/dist/src/auth/guard.js +64 -0
  31. package/dist/src/auth/index.js +19 -0
  32. package/dist/src/auth/keychain.js +79 -0
  33. package/dist/src/auth/oauth.js +389 -0
  34. package/dist/src/auth/providers.js +415 -0
  35. package/dist/src/auth/sso.js +87 -0
  36. package/dist/src/auth/store.js +424 -0
  37. package/dist/src/auth/types.js +5 -0
  38. package/dist/src/cli/index.js +8 -0
  39. package/dist/src/cli/init.js +1048 -0
  40. package/dist/src/cli/openapi-spec.js +346 -0
  41. package/dist/src/cli/run.js +505 -0
  42. package/dist/src/cli/serve-auth.js +56 -0
  43. package/dist/src/cli/serve.js +432 -0
  44. package/dist/src/cli/web.js +50 -0
  45. package/dist/src/cli.js +1574 -0
  46. package/dist/src/clients/core-engine-client.js +156 -0
  47. package/dist/src/clients/enterprise-client.js +246 -0
  48. package/dist/src/clients/generator-client.js +219 -0
  49. package/dist/src/clients/git-client.js +367 -0
  50. package/dist/src/clients/github-client.js +229 -0
  51. package/dist/src/clients/helm-client.js +299 -0
  52. package/dist/src/clients/index.js +18 -0
  53. package/dist/src/clients/k8s-client.js +270 -0
  54. package/dist/src/clients/llm-client.js +119 -0
  55. package/dist/src/clients/rest-client.js +104 -0
  56. package/dist/src/clients/service-discovery.js +35 -0
  57. package/dist/src/clients/terraform-client.js +302 -0
  58. package/dist/src/clients/tools-client.js +1227 -0
  59. package/dist/src/clients/ws-client.js +93 -0
  60. package/dist/src/commands/alias.js +91 -0
  61. package/dist/src/commands/analyze/index.js +313 -0
  62. package/dist/src/commands/apply/helm.js +375 -0
  63. package/dist/src/commands/apply/index.js +176 -0
  64. package/dist/src/commands/apply/k8s.js +350 -0
  65. package/dist/src/commands/apply/terraform.js +465 -0
  66. package/dist/src/commands/ask.js +137 -0
  67. package/dist/src/commands/audit/index.js +322 -0
  68. package/dist/src/commands/auth-cloud.js +345 -0
  69. package/dist/src/commands/auth-list.js +112 -0
  70. package/dist/src/commands/auth-profile.js +104 -0
  71. package/dist/src/commands/auth-refresh.js +161 -0
  72. package/dist/src/commands/auth-status.js +122 -0
  73. package/dist/src/commands/aws/ec2.js +402 -0
  74. package/dist/src/commands/aws/iam.js +304 -0
  75. package/dist/src/commands/aws/index.js +108 -0
  76. package/dist/src/commands/aws/lambda.js +317 -0
  77. package/dist/src/commands/aws/rds.js +345 -0
  78. package/dist/src/commands/aws/s3.js +346 -0
  79. package/dist/src/commands/aws/vpc.js +302 -0
  80. package/dist/src/commands/aws-discover.js +413 -0
  81. package/dist/src/commands/aws-terraform.js +618 -0
  82. package/dist/src/commands/azure/aks.js +305 -0
  83. package/dist/src/commands/azure/functions.js +200 -0
  84. package/dist/src/commands/azure/index.js +93 -0
  85. package/dist/src/commands/azure/storage.js +378 -0
  86. package/dist/src/commands/azure/vm.js +291 -0
  87. package/dist/src/commands/billing/index.js +224 -0
  88. package/dist/src/commands/chat.js +259 -0
  89. package/dist/src/commands/completions.js +255 -0
  90. package/dist/src/commands/config.js +291 -0
  91. package/dist/src/commands/cost/cloud-cost-estimator.js +211 -0
  92. package/dist/src/commands/cost/estimator.js +73 -0
  93. package/dist/src/commands/cost/index.js +625 -0
  94. package/dist/src/commands/cost/parsers/terraform.js +234 -0
  95. package/dist/src/commands/cost/parsers/types.js +4 -0
  96. package/dist/src/commands/cost/pricing/aws.js +501 -0
  97. package/dist/src/commands/cost/pricing/azure.js +462 -0
  98. package/dist/src/commands/cost/pricing/gcp.js +359 -0
  99. package/dist/src/commands/cost/pricing/index.js +24 -0
  100. package/dist/src/commands/demo.js +196 -0
  101. package/dist/src/commands/deploy.js +215 -0
  102. package/dist/src/commands/doctor.js +1291 -0
  103. package/dist/src/commands/drift/index.js +674 -0
  104. package/dist/src/commands/explain.js +235 -0
  105. package/dist/src/commands/export.js +120 -0
  106. package/dist/src/commands/feedback.js +319 -0
  107. package/dist/src/commands/fix.js +263 -0
  108. package/dist/src/commands/fs/index.js +338 -0
  109. package/dist/src/commands/gcp/compute.js +266 -0
  110. package/dist/src/commands/gcp/functions.js +221 -0
  111. package/dist/src/commands/gcp/gke.js +357 -0
  112. package/dist/src/commands/gcp/iam.js +295 -0
  113. package/dist/src/commands/gcp/index.js +105 -0
  114. package/dist/src/commands/gcp/storage.js +232 -0
  115. package/dist/src/commands/generate-helm.js +1026 -0
  116. package/dist/src/commands/generate-k8s.js +1263 -0
  117. package/dist/src/commands/generate-terraform.js +1058 -0
  118. package/dist/src/commands/gh/index.js +663 -0
  119. package/dist/src/commands/git/index.js +1208 -0
  120. package/dist/src/commands/helm/index.js +985 -0
  121. package/dist/src/commands/help.js +639 -0
  122. package/dist/src/commands/history.js +120 -0
  123. package/dist/src/commands/import.js +782 -0
  124. package/dist/src/commands/incident.js +144 -0
  125. package/dist/src/commands/index.js +109 -0
  126. package/dist/src/commands/init.js +955 -0
  127. package/dist/src/commands/k8s/index.js +979 -0
  128. package/dist/src/commands/login.js +588 -0
  129. package/dist/src/commands/logout.js +61 -0
  130. package/dist/src/commands/logs.js +160 -0
  131. package/dist/src/commands/onboarding.js +382 -0
  132. package/dist/src/commands/pipeline.js +153 -0
  133. package/dist/src/commands/plan/display.js +216 -0
  134. package/dist/src/commands/plan/index.js +525 -0
  135. package/dist/src/commands/plugin.js +325 -0
  136. package/dist/src/commands/preview.js +356 -0
  137. package/dist/src/commands/profile.js +297 -0
  138. package/dist/src/commands/questionnaire.js +1021 -0
  139. package/dist/src/commands/resume.js +35 -0
  140. package/dist/src/commands/rollback.js +259 -0
  141. package/dist/src/commands/rollout.js +74 -0
  142. package/dist/src/commands/runbook.js +307 -0
  143. package/dist/src/commands/schedule.js +202 -0
  144. package/dist/src/commands/status.js +213 -0
  145. package/dist/src/commands/team/index.js +309 -0
  146. package/dist/src/commands/team-context.js +200 -0
  147. package/dist/src/commands/template.js +204 -0
  148. package/dist/src/commands/tf/index.js +989 -0
  149. package/dist/src/commands/upgrade.js +515 -0
  150. package/dist/src/commands/usage/index.js +118 -0
  151. package/dist/src/commands/version.js +145 -0
  152. package/dist/src/commands/watch.js +127 -0
  153. package/dist/src/compat/index.js +2 -0
  154. package/dist/src/compat/runtime.js +10 -0
  155. package/dist/src/compat/sqlite.js +144 -0
  156. package/dist/src/config/index.js +6 -0
  157. package/dist/src/config/manager.js +469 -0
  158. package/dist/src/config/mode-store.js +57 -0
  159. package/dist/src/config/profiles.js +66 -0
  160. package/dist/src/config/safety-policy.js +251 -0
  161. package/dist/src/config/schema.js +107 -0
  162. package/dist/src/config/types.js +311 -0
  163. package/dist/src/config/workspace-state.js +38 -0
  164. package/dist/src/context/context-db.js +138 -0
  165. package/dist/src/demo/index.js +295 -0
  166. package/dist/src/demo/scenarios/full-journey.js +226 -0
  167. package/dist/src/demo/scenarios/getting-started.js +124 -0
  168. package/dist/src/demo/scenarios/helm-release.js +334 -0
  169. package/dist/src/demo/scenarios/k8s-deployment.js +190 -0
  170. package/dist/src/demo/scenarios/terraform-vpc.js +167 -0
  171. package/dist/src/demo/types.js +6 -0
  172. package/dist/src/engine/cost-estimator.js +334 -0
  173. package/dist/src/engine/diagram-generator.js +192 -0
  174. package/dist/src/engine/drift-detector.js +688 -0
  175. package/dist/src/engine/executor.js +832 -0
  176. package/dist/src/engine/index.js +39 -0
  177. package/dist/src/engine/orchestrator.js +436 -0
  178. package/dist/src/engine/planner.js +616 -0
  179. package/dist/src/engine/safety.js +609 -0
  180. package/dist/src/engine/verifier.js +664 -0
  181. package/dist/src/enterprise/audit.js +241 -0
  182. package/dist/src/enterprise/auth.js +189 -0
  183. package/dist/src/enterprise/billing.js +512 -0
  184. package/dist/src/enterprise/index.js +16 -0
  185. package/dist/src/enterprise/teams.js +315 -0
  186. package/dist/src/generator/best-practices.js +1375 -0
  187. package/dist/src/generator/helm.js +495 -0
  188. package/dist/src/generator/index.js +11 -0
  189. package/dist/src/generator/intent-parser.js +420 -0
  190. package/dist/src/generator/kubernetes.js +773 -0
  191. package/dist/src/generator/terraform.js +1472 -0
  192. package/dist/src/history/index.js +6 -0
  193. package/dist/src/history/manager.js +199 -0
  194. package/dist/src/history/types.js +6 -0
  195. package/dist/src/hooks/config.js +318 -0
  196. package/dist/src/hooks/engine.js +317 -0
  197. package/dist/src/hooks/index.js +2 -0
  198. package/dist/src/llm/auth-bridge.js +157 -0
  199. package/dist/src/llm/circuit-breaker.js +116 -0
  200. package/dist/src/llm/config-loader.js +172 -0
  201. package/dist/src/llm/cost-calculator.js +137 -0
  202. package/dist/src/llm/index.js +7 -0
  203. package/dist/src/llm/model-aliases.js +99 -0
  204. package/dist/src/llm/provider-registry.js +57 -0
  205. package/dist/src/llm/providers/anthropic.js +430 -0
  206. package/dist/src/llm/providers/bedrock.js +409 -0
  207. package/dist/src/llm/providers/google.js +344 -0
  208. package/dist/src/llm/providers/ollama.js +661 -0
  209. package/dist/src/llm/providers/openai-compatible.js +289 -0
  210. package/dist/src/llm/providers/openai.js +284 -0
  211. package/dist/src/llm/providers/openrouter.js +293 -0
  212. package/dist/src/llm/router.js +844 -0
  213. package/dist/src/llm/types.js +69 -0
  214. package/dist/src/lsp/client.js +239 -0
  215. package/dist/src/lsp/languages.js +95 -0
  216. package/dist/src/lsp/manager.js +243 -0
  217. package/dist/src/mcp/client.js +289 -0
  218. package/dist/src/mcp/index.js +5 -0
  219. package/dist/src/mcp/manager.js +113 -0
  220. package/dist/src/nimbus.js +212 -0
  221. package/dist/src/plugins/index.js +13 -0
  222. package/dist/src/plugins/loader.js +280 -0
  223. package/dist/src/plugins/manager.js +282 -0
  224. package/dist/src/plugins/types.js +23 -0
  225. package/dist/src/scanners/cicd-scanner.js +230 -0
  226. package/dist/src/scanners/cloud-scanner.js +415 -0
  227. package/dist/src/scanners/framework-scanner.js +430 -0
  228. package/dist/src/scanners/iac-scanner.js +350 -0
  229. package/dist/src/scanners/index.js +454 -0
  230. package/dist/src/scanners/language-scanner.js +258 -0
  231. package/dist/src/scanners/package-manager-scanner.js +252 -0
  232. package/dist/src/scanners/types.js +6 -0
  233. package/dist/src/sessions/manager.js +395 -0
  234. package/dist/src/sessions/types.js +4 -0
  235. package/dist/src/sharing/sync.js +238 -0
  236. package/dist/src/sharing/viewer.js +131 -0
  237. package/dist/src/snapshots/index.js +1 -0
  238. package/dist/src/snapshots/manager.js +432 -0
  239. package/dist/src/state/artifacts.js +94 -0
  240. package/dist/src/state/audit.js +73 -0
  241. package/dist/src/state/billing.js +126 -0
  242. package/dist/src/state/checkpoints.js +81 -0
  243. package/dist/src/state/config.js +58 -0
  244. package/dist/src/state/conversations.js +7 -0
  245. package/dist/src/state/credentials.js +96 -0
  246. package/dist/src/state/db.js +53 -0
  247. package/dist/src/state/index.js +23 -0
  248. package/dist/src/state/messages.js +76 -0
  249. package/dist/src/state/projects.js +92 -0
  250. package/dist/src/state/schema.js +233 -0
  251. package/dist/src/state/sessions.js +79 -0
  252. package/dist/src/state/teams.js +131 -0
  253. package/dist/src/telemetry.js +91 -0
  254. package/dist/src/tools/aws-ops.js +747 -0
  255. package/dist/src/tools/azure-ops.js +491 -0
  256. package/dist/src/tools/file-ops.js +451 -0
  257. package/dist/src/tools/gcp-ops.js +559 -0
  258. package/dist/src/tools/git-ops.js +557 -0
  259. package/dist/src/tools/github-ops.js +460 -0
  260. package/dist/src/tools/helm-ops.js +634 -0
  261. package/dist/src/tools/index.js +16 -0
  262. package/dist/src/tools/k8s-ops.js +579 -0
  263. package/dist/src/tools/schemas/converter.js +129 -0
  264. package/dist/src/tools/schemas/devops.js +3319 -0
  265. package/dist/src/tools/schemas/index.js +19 -0
  266. package/dist/src/tools/schemas/standard.js +966 -0
  267. package/dist/src/tools/schemas/types.js +409 -0
  268. package/dist/src/tools/spawn-exec.js +109 -0
  269. package/dist/src/tools/terraform-ops.js +627 -0
  270. package/dist/src/types/config.js +1 -0
  271. package/dist/src/types/drift.js +4 -0
  272. package/dist/src/types/enterprise.js +5 -0
  273. package/dist/src/types/index.js +14 -0
  274. package/dist/src/types/plan.js +1 -0
  275. package/dist/src/types/request.js +1 -0
  276. package/dist/src/types/response.js +1 -0
  277. package/dist/src/types/service.js +1 -0
  278. package/dist/src/ui/App.js +1672 -0
  279. package/dist/src/ui/DeployPreview.js +60 -0
  280. package/dist/src/ui/FileDiffModal.js +108 -0
  281. package/dist/src/ui/Header.js +46 -0
  282. package/dist/src/ui/HelpModal.js +9 -0
  283. package/dist/src/ui/InputBox.js +408 -0
  284. package/dist/src/ui/MessageList.js +795 -0
  285. package/dist/src/ui/PermissionPrompt.js +72 -0
  286. package/dist/src/ui/StatusBar.js +109 -0
  287. package/dist/src/ui/TerminalPane.js +31 -0
  288. package/dist/src/ui/ToolCallDisplay.js +303 -0
  289. package/dist/src/ui/TreePane.js +83 -0
  290. package/dist/src/ui/chat-ui.js +721 -0
  291. package/dist/src/ui/index.js +11 -0
  292. package/dist/src/ui/ink/index.js +1325 -0
  293. package/dist/src/ui/streaming.js +137 -0
  294. package/dist/src/ui/theme.js +78 -0
  295. package/dist/src/ui/types.js +7 -0
  296. package/dist/src/utils/analytics.js +61 -0
  297. package/dist/src/utils/cost-warning.js +25 -0
  298. package/dist/src/utils/env.js +42 -0
  299. package/dist/src/utils/errors.js +54 -0
  300. package/dist/src/utils/event-bus.js +22 -0
  301. package/dist/src/utils/index.js +16 -0
  302. package/dist/src/utils/logger.js +150 -0
  303. package/dist/src/utils/rate-limiter.js +90 -0
  304. package/dist/src/utils/service-auth.js +36 -0
  305. package/dist/src/utils/validation.js +39 -0
  306. package/dist/src/version.js +3 -0
  307. package/dist/src/watcher/index.js +192 -0
  308. package/dist/src/wizard/approval.js +275 -0
  309. package/dist/src/wizard/index.js +13 -0
  310. package/dist/src/wizard/prompts.js +273 -0
  311. package/dist/src/wizard/types.js +4 -0
  312. package/dist/src/wizard/ui.js +453 -0
  313. package/dist/src/wizard/wizard.js +227 -0
  314. package/package.json +31 -23
  315. package/src/__tests__/alias.test.ts +133 -0
  316. package/src/__tests__/app.test.ts +1 -1
  317. package/src/__tests__/audit.test.ts +1 -1
  318. package/src/__tests__/circuit-breaker.test.ts +1 -1
  319. package/src/__tests__/cli-run.test.ts +237 -1
  320. package/src/__tests__/compat-sqlite.test.ts +68 -0
  321. package/src/__tests__/context-manager.test.ts +131 -1
  322. package/src/__tests__/context.test.ts +1 -1
  323. package/src/__tests__/devops-terminal-gaps.test.ts +718 -0
  324. package/src/__tests__/doctor.test.ts +48 -0
  325. package/src/__tests__/enterprise.test.ts +1 -1
  326. package/src/__tests__/export.test.ts +236 -0
  327. package/src/__tests__/gap-11-18-20.test.ts +958 -0
  328. package/src/__tests__/generator.test.ts +1 -1
  329. package/src/__tests__/helm-streaming.test.ts +127 -0
  330. package/src/__tests__/hooks.test.ts +1 -1
  331. package/src/__tests__/incident.test.ts +179 -0
  332. package/src/__tests__/init.test.ts +55 -4
  333. package/src/__tests__/intent-parser.test.ts +1 -1
  334. package/src/__tests__/llm-router.test.ts +1 -1
  335. package/src/__tests__/logs.test.ts +107 -0
  336. package/src/__tests__/loop-errors.test.ts +244 -0
  337. package/src/__tests__/lsp.test.ts +1 -1
  338. package/src/__tests__/modes.test.ts +1 -1
  339. package/src/__tests__/perf-optimizations.test.ts +847 -0
  340. package/src/__tests__/permissions.test.ts +1 -1
  341. package/src/__tests__/pipeline.test.ts +50 -0
  342. package/src/__tests__/polish-phase3.test.ts +340 -0
  343. package/src/__tests__/profile.test.ts +237 -0
  344. package/src/__tests__/rollback.test.ts +83 -0
  345. package/src/__tests__/runbook.test.ts +219 -0
  346. package/src/__tests__/schedule.test.ts +206 -0
  347. package/src/__tests__/serve.test.ts +1 -1
  348. package/src/__tests__/sessions.test.ts +96 -1
  349. package/src/__tests__/sharing.test.ts +53 -1
  350. package/src/__tests__/snapshots.test.ts +1 -1
  351. package/src/__tests__/standalone-migration.test.ts +199 -0
  352. package/src/__tests__/state-db.test.ts +1 -1
  353. package/src/__tests__/status.test.ts +158 -0
  354. package/src/__tests__/stream-with-tools.test.ts +71 -25
  355. package/src/__tests__/subagents.test.ts +1 -1
  356. package/src/__tests__/system-prompt.test.ts +82 -3
  357. package/src/__tests__/terminal-gap-v2.test.ts +395 -0
  358. package/src/__tests__/terminal-parity.test.ts +393 -0
  359. package/src/__tests__/tf-apply.test.ts +187 -0
  360. package/src/__tests__/tool-converter.test.ts +1 -1
  361. package/src/__tests__/tool-schemas.test.ts +209 -4
  362. package/src/__tests__/tools.test.ts +4 -3
  363. package/src/__tests__/version-json.test.ts +184 -0
  364. package/src/__tests__/version.test.ts +1 -1
  365. package/src/__tests__/watch.test.ts +129 -0
  366. package/src/agent/compaction-agent.ts +40 -1
  367. package/src/agent/context-manager.ts +67 -3
  368. package/src/agent/deploy-preview.ts +62 -1
  369. package/src/agent/expand-files.ts +108 -0
  370. package/src/agent/loop.ts +1312 -31
  371. package/src/agent/permissions.ts +51 -4
  372. package/src/agent/system-prompt.ts +573 -19
  373. package/src/app.ts +58 -0
  374. package/src/audit/security-scanner.ts +45 -0
  375. package/src/auth/keychain.ts +82 -0
  376. package/src/auth/oauth.ts +15 -5
  377. package/src/cli/init.ts +378 -5
  378. package/src/cli/run.ts +407 -16
  379. package/src/cli/serve.ts +78 -1
  380. package/src/cli/web.ts +10 -6
  381. package/src/cli.ts +312 -1
  382. package/src/clients/service-discovery.ts +30 -25
  383. package/src/commands/alias.ts +100 -0
  384. package/src/commands/audit/index.ts +121 -2
  385. package/src/commands/auth-cloud.ts +113 -0
  386. package/src/commands/auth-refresh.ts +187 -0
  387. package/src/commands/aws-discover.ts +144 -251
  388. package/src/commands/aws-terraform.ts +68 -118
  389. package/src/commands/chat.ts +9 -3
  390. package/src/commands/completions.ts +268 -0
  391. package/src/commands/config.ts +26 -0
  392. package/src/commands/cost/index.ts +218 -2
  393. package/src/commands/deploy.ts +260 -0
  394. package/src/commands/doctor.ts +744 -152
  395. package/src/commands/drift/index.ts +371 -23
  396. package/src/commands/export.ts +146 -0
  397. package/src/commands/generate-k8s.ts +9 -61
  398. package/src/commands/generate-terraform.ts +191 -449
  399. package/src/commands/help.ts +212 -36
  400. package/src/commands/history.ts +8 -1
  401. package/src/commands/incident.ts +166 -0
  402. package/src/commands/init.ts +5 -0
  403. package/src/commands/login.ts +86 -1
  404. package/src/commands/logs.ts +167 -0
  405. package/src/commands/onboarding.ts +211 -34
  406. package/src/commands/pipeline.ts +186 -0
  407. package/src/commands/plugin.ts +398 -0
  408. package/src/commands/profile.ts +342 -0
  409. package/src/commands/questionnaire.ts +0 -98
  410. package/src/commands/resume.ts +26 -34
  411. package/src/commands/rollback.ts +315 -0
  412. package/src/commands/rollout.ts +88 -0
  413. package/src/commands/runbook.ts +346 -0
  414. package/src/commands/schedule.ts +236 -0
  415. package/src/commands/status.ts +252 -0
  416. package/src/commands/team-context.ts +220 -0
  417. package/src/commands/template.ts +58 -57
  418. package/src/commands/tf/index.ts +70 -11
  419. package/src/commands/upgrade.ts +57 -0
  420. package/src/commands/version.ts +54 -50
  421. package/src/commands/watch.ts +153 -0
  422. package/src/compat/runtime.ts +1 -1
  423. package/src/compat/sqlite.ts +75 -5
  424. package/src/config/mode-store.ts +62 -0
  425. package/src/config/profiles.ts +84 -0
  426. package/src/config/types.ts +83 -1
  427. package/src/config/workspace-state.ts +53 -0
  428. package/src/engine/cost-estimator.ts +52 -10
  429. package/src/engine/executor.ts +33 -2
  430. package/src/engine/planner.ts +68 -1
  431. package/src/generator/terraform.ts +8 -0
  432. package/src/history/manager.ts +2 -74
  433. package/src/hooks/engine.ts +5 -4
  434. package/src/llm/cost-calculator.ts +2 -2
  435. package/src/llm/providers/anthropic.ts +50 -21
  436. package/src/llm/router.ts +76 -7
  437. package/src/lsp/languages.ts +3 -0
  438. package/src/lsp/manager.ts +21 -5
  439. package/src/nimbus.ts +37 -18
  440. package/src/sessions/manager.ts +108 -1
  441. package/src/sharing/sync.ts +4 -0
  442. package/src/sharing/viewer.ts +66 -0
  443. package/src/tools/file-ops.ts +22 -0
  444. package/src/tools/schemas/devops.ts +3007 -117
  445. package/src/tools/schemas/standard.ts +5 -1
  446. package/src/tools/schemas/types.ts +31 -1
  447. package/src/tools/spawn-exec.ts +148 -0
  448. package/src/ui/App.tsx +1183 -66
  449. package/src/ui/DeployPreview.tsx +62 -57
  450. package/src/ui/FileDiffModal.tsx +162 -0
  451. package/src/ui/Header.tsx +87 -24
  452. package/src/ui/HelpModal.tsx +57 -0
  453. package/src/ui/InputBox.tsx +163 -10
  454. package/src/ui/MessageList.tsx +487 -40
  455. package/src/ui/PermissionPrompt.tsx +17 -5
  456. package/src/ui/StatusBar.tsx +122 -3
  457. package/src/ui/TerminalPane.tsx +84 -0
  458. package/src/ui/ToolCallDisplay.tsx +252 -18
  459. package/src/ui/TreePane.tsx +132 -0
  460. package/src/ui/chat-ui.ts +41 -44
  461. package/src/ui/ink/index.ts +771 -38
  462. package/src/ui/streaming.ts +1 -1
  463. package/src/ui/theme.ts +104 -0
  464. package/src/ui/types.ts +18 -0
  465. package/src/version.ts +1 -1
  466. package/src/watcher/index.ts +66 -15
  467. package/src/wizard/types.ts +1 -0
  468. package/src/wizard/ui.ts +1 -1
  469. 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
+ }