@build-astron-co/nimbus 0.2.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 (313) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +628 -0
  3. package/bin/nimbus +38 -0
  4. package/package.json +80 -0
  5. package/src/__tests__/app.test.ts +76 -0
  6. package/src/__tests__/audit.test.ts +877 -0
  7. package/src/__tests__/circuit-breaker.test.ts +116 -0
  8. package/src/__tests__/cli-run.test.ts +115 -0
  9. package/src/__tests__/context-manager.test.ts +502 -0
  10. package/src/__tests__/context.test.ts +242 -0
  11. package/src/__tests__/enterprise.test.ts +401 -0
  12. package/src/__tests__/generator.test.ts +433 -0
  13. package/src/__tests__/hooks.test.ts +582 -0
  14. package/src/__tests__/init.test.ts +436 -0
  15. package/src/__tests__/intent-parser.test.ts +229 -0
  16. package/src/__tests__/llm-router.test.ts +209 -0
  17. package/src/__tests__/lsp.test.ts +293 -0
  18. package/src/__tests__/modes.test.ts +336 -0
  19. package/src/__tests__/permissions.test.ts +338 -0
  20. package/src/__tests__/serve.test.ts +275 -0
  21. package/src/__tests__/sessions.test.ts +227 -0
  22. package/src/__tests__/sharing.test.ts +288 -0
  23. package/src/__tests__/snapshots.test.ts +581 -0
  24. package/src/__tests__/state-db.test.ts +334 -0
  25. package/src/__tests__/stream-with-tools.test.ts +732 -0
  26. package/src/__tests__/subagents.test.ts +176 -0
  27. package/src/__tests__/system-prompt.test.ts +169 -0
  28. package/src/__tests__/tool-converter.test.ts +256 -0
  29. package/src/__tests__/tool-schemas.test.ts +397 -0
  30. package/src/__tests__/tools.test.ts +143 -0
  31. package/src/__tests__/version.test.ts +49 -0
  32. package/src/agent/compaction-agent.ts +227 -0
  33. package/src/agent/context-manager.ts +435 -0
  34. package/src/agent/context.ts +427 -0
  35. package/src/agent/deploy-preview.ts +426 -0
  36. package/src/agent/index.ts +68 -0
  37. package/src/agent/loop.ts +717 -0
  38. package/src/agent/modes.ts +429 -0
  39. package/src/agent/permissions.ts +466 -0
  40. package/src/agent/subagents/base.ts +116 -0
  41. package/src/agent/subagents/cost.ts +51 -0
  42. package/src/agent/subagents/explore.ts +42 -0
  43. package/src/agent/subagents/general.ts +54 -0
  44. package/src/agent/subagents/index.ts +102 -0
  45. package/src/agent/subagents/infra.ts +59 -0
  46. package/src/agent/subagents/security.ts +69 -0
  47. package/src/agent/system-prompt.ts +436 -0
  48. package/src/app.ts +122 -0
  49. package/src/audit/activity-log.ts +290 -0
  50. package/src/audit/compliance-checker.ts +540 -0
  51. package/src/audit/cost-tracker.ts +318 -0
  52. package/src/audit/index.ts +23 -0
  53. package/src/audit/security-scanner.ts +596 -0
  54. package/src/auth/guard.ts +75 -0
  55. package/src/auth/index.ts +56 -0
  56. package/src/auth/oauth.ts +455 -0
  57. package/src/auth/providers.ts +470 -0
  58. package/src/auth/sso.ts +113 -0
  59. package/src/auth/store.ts +505 -0
  60. package/src/auth/types.ts +187 -0
  61. package/src/build.ts +141 -0
  62. package/src/cli/index.ts +16 -0
  63. package/src/cli/init.ts +854 -0
  64. package/src/cli/openapi-spec.ts +356 -0
  65. package/src/cli/run.ts +237 -0
  66. package/src/cli/serve-auth.ts +80 -0
  67. package/src/cli/serve.ts +462 -0
  68. package/src/cli/web.ts +67 -0
  69. package/src/cli.ts +1417 -0
  70. package/src/clients/core-engine-client.ts +227 -0
  71. package/src/clients/enterprise-client.ts +334 -0
  72. package/src/clients/generator-client.ts +351 -0
  73. package/src/clients/git-client.ts +627 -0
  74. package/src/clients/github-client.ts +410 -0
  75. package/src/clients/helm-client.ts +504 -0
  76. package/src/clients/index.ts +80 -0
  77. package/src/clients/k8s-client.ts +497 -0
  78. package/src/clients/llm-client.ts +161 -0
  79. package/src/clients/rest-client.ts +130 -0
  80. package/src/clients/service-discovery.ts +33 -0
  81. package/src/clients/terraform-client.ts +482 -0
  82. package/src/clients/tools-client.ts +1843 -0
  83. package/src/clients/ws-client.ts +115 -0
  84. package/src/commands/analyze/index.ts +352 -0
  85. package/src/commands/apply/helm.ts +473 -0
  86. package/src/commands/apply/index.ts +213 -0
  87. package/src/commands/apply/k8s.ts +454 -0
  88. package/src/commands/apply/terraform.ts +582 -0
  89. package/src/commands/ask.ts +167 -0
  90. package/src/commands/audit/index.ts +238 -0
  91. package/src/commands/auth-cloud.ts +294 -0
  92. package/src/commands/auth-list.ts +134 -0
  93. package/src/commands/auth-profile.ts +121 -0
  94. package/src/commands/auth-status.ts +141 -0
  95. package/src/commands/aws/ec2.ts +501 -0
  96. package/src/commands/aws/iam.ts +397 -0
  97. package/src/commands/aws/index.ts +133 -0
  98. package/src/commands/aws/lambda.ts +396 -0
  99. package/src/commands/aws/rds.ts +439 -0
  100. package/src/commands/aws/s3.ts +439 -0
  101. package/src/commands/aws/vpc.ts +393 -0
  102. package/src/commands/aws-discover.ts +649 -0
  103. package/src/commands/aws-terraform.ts +805 -0
  104. package/src/commands/azure/aks.ts +376 -0
  105. package/src/commands/azure/functions.ts +253 -0
  106. package/src/commands/azure/index.ts +116 -0
  107. package/src/commands/azure/storage.ts +478 -0
  108. package/src/commands/azure/vm.ts +355 -0
  109. package/src/commands/billing/index.ts +256 -0
  110. package/src/commands/chat.ts +314 -0
  111. package/src/commands/config.ts +346 -0
  112. package/src/commands/cost/cloud-cost-estimator.ts +266 -0
  113. package/src/commands/cost/estimator.ts +79 -0
  114. package/src/commands/cost/index.ts +594 -0
  115. package/src/commands/cost/parsers/terraform.ts +273 -0
  116. package/src/commands/cost/parsers/types.ts +25 -0
  117. package/src/commands/cost/pricing/aws.ts +544 -0
  118. package/src/commands/cost/pricing/azure.ts +499 -0
  119. package/src/commands/cost/pricing/gcp.ts +396 -0
  120. package/src/commands/cost/pricing/index.ts +40 -0
  121. package/src/commands/demo.ts +250 -0
  122. package/src/commands/doctor.ts +794 -0
  123. package/src/commands/drift/index.ts +439 -0
  124. package/src/commands/explain.ts +277 -0
  125. package/src/commands/feedback.ts +389 -0
  126. package/src/commands/fix.ts +324 -0
  127. package/src/commands/fs/index.ts +402 -0
  128. package/src/commands/gcp/compute.ts +325 -0
  129. package/src/commands/gcp/functions.ts +271 -0
  130. package/src/commands/gcp/gke.ts +438 -0
  131. package/src/commands/gcp/iam.ts +344 -0
  132. package/src/commands/gcp/index.ts +129 -0
  133. package/src/commands/gcp/storage.ts +284 -0
  134. package/src/commands/generate-helm.ts +1249 -0
  135. package/src/commands/generate-k8s.ts +1560 -0
  136. package/src/commands/generate-terraform.ts +1460 -0
  137. package/src/commands/gh/index.ts +863 -0
  138. package/src/commands/git/index.ts +1343 -0
  139. package/src/commands/helm/index.ts +1126 -0
  140. package/src/commands/help.ts +539 -0
  141. package/src/commands/history.ts +142 -0
  142. package/src/commands/import.ts +868 -0
  143. package/src/commands/index.ts +367 -0
  144. package/src/commands/init.ts +1046 -0
  145. package/src/commands/k8s/index.ts +1137 -0
  146. package/src/commands/login.ts +631 -0
  147. package/src/commands/logout.ts +83 -0
  148. package/src/commands/onboarding.ts +228 -0
  149. package/src/commands/plan/display.ts +279 -0
  150. package/src/commands/plan/index.ts +599 -0
  151. package/src/commands/preview.ts +452 -0
  152. package/src/commands/questionnaire.ts +1270 -0
  153. package/src/commands/resume.ts +55 -0
  154. package/src/commands/team/index.ts +346 -0
  155. package/src/commands/template.ts +232 -0
  156. package/src/commands/tf/index.ts +1034 -0
  157. package/src/commands/upgrade.ts +550 -0
  158. package/src/commands/usage/index.ts +134 -0
  159. package/src/commands/version.ts +170 -0
  160. package/src/compat/index.ts +2 -0
  161. package/src/compat/runtime.ts +12 -0
  162. package/src/compat/sqlite.ts +107 -0
  163. package/src/config/index.ts +17 -0
  164. package/src/config/manager.ts +530 -0
  165. package/src/config/safety-policy.ts +358 -0
  166. package/src/config/schema.ts +125 -0
  167. package/src/config/types.ts +527 -0
  168. package/src/context/context-db.ts +199 -0
  169. package/src/demo/index.ts +349 -0
  170. package/src/demo/scenarios/full-journey.ts +229 -0
  171. package/src/demo/scenarios/getting-started.ts +127 -0
  172. package/src/demo/scenarios/helm-release.ts +341 -0
  173. package/src/demo/scenarios/k8s-deployment.ts +194 -0
  174. package/src/demo/scenarios/terraform-vpc.ts +170 -0
  175. package/src/demo/types.ts +92 -0
  176. package/src/engine/cost-estimator.ts +438 -0
  177. package/src/engine/diagram-generator.ts +256 -0
  178. package/src/engine/drift-detector.ts +902 -0
  179. package/src/engine/executor.ts +1035 -0
  180. package/src/engine/index.ts +76 -0
  181. package/src/engine/orchestrator.ts +636 -0
  182. package/src/engine/planner.ts +720 -0
  183. package/src/engine/safety.ts +743 -0
  184. package/src/engine/verifier.ts +770 -0
  185. package/src/enterprise/audit.ts +348 -0
  186. package/src/enterprise/auth.ts +270 -0
  187. package/src/enterprise/billing.ts +822 -0
  188. package/src/enterprise/index.ts +17 -0
  189. package/src/enterprise/teams.ts +443 -0
  190. package/src/generator/best-practices.ts +1608 -0
  191. package/src/generator/helm.ts +630 -0
  192. package/src/generator/index.ts +37 -0
  193. package/src/generator/intent-parser.ts +514 -0
  194. package/src/generator/kubernetes.ts +976 -0
  195. package/src/generator/terraform.ts +1867 -0
  196. package/src/history/index.ts +8 -0
  197. package/src/history/manager.ts +322 -0
  198. package/src/history/types.ts +34 -0
  199. package/src/hooks/config.ts +432 -0
  200. package/src/hooks/engine.ts +391 -0
  201. package/src/hooks/index.ts +4 -0
  202. package/src/llm/auth-bridge.ts +198 -0
  203. package/src/llm/circuit-breaker.ts +140 -0
  204. package/src/llm/config-loader.ts +201 -0
  205. package/src/llm/cost-calculator.ts +171 -0
  206. package/src/llm/index.ts +8 -0
  207. package/src/llm/model-aliases.ts +115 -0
  208. package/src/llm/provider-registry.ts +63 -0
  209. package/src/llm/providers/anthropic.ts +433 -0
  210. package/src/llm/providers/bedrock.ts +477 -0
  211. package/src/llm/providers/google.ts +405 -0
  212. package/src/llm/providers/ollama.ts +767 -0
  213. package/src/llm/providers/openai-compatible.ts +340 -0
  214. package/src/llm/providers/openai.ts +328 -0
  215. package/src/llm/providers/openrouter.ts +338 -0
  216. package/src/llm/router.ts +1035 -0
  217. package/src/llm/types.ts +232 -0
  218. package/src/lsp/client.ts +298 -0
  219. package/src/lsp/languages.ts +116 -0
  220. package/src/lsp/manager.ts +278 -0
  221. package/src/mcp/client.ts +402 -0
  222. package/src/mcp/index.ts +5 -0
  223. package/src/mcp/manager.ts +133 -0
  224. package/src/nimbus.ts +214 -0
  225. package/src/plugins/index.ts +27 -0
  226. package/src/plugins/loader.ts +334 -0
  227. package/src/plugins/manager.ts +376 -0
  228. package/src/plugins/types.ts +284 -0
  229. package/src/scanners/cicd-scanner.ts +258 -0
  230. package/src/scanners/cloud-scanner.ts +466 -0
  231. package/src/scanners/framework-scanner.ts +469 -0
  232. package/src/scanners/iac-scanner.ts +388 -0
  233. package/src/scanners/index.ts +539 -0
  234. package/src/scanners/language-scanner.ts +276 -0
  235. package/src/scanners/package-manager-scanner.ts +277 -0
  236. package/src/scanners/types.ts +172 -0
  237. package/src/sessions/manager.ts +365 -0
  238. package/src/sessions/types.ts +44 -0
  239. package/src/sharing/sync.ts +296 -0
  240. package/src/sharing/viewer.ts +97 -0
  241. package/src/snapshots/index.ts +2 -0
  242. package/src/snapshots/manager.ts +530 -0
  243. package/src/state/artifacts.ts +147 -0
  244. package/src/state/audit.ts +137 -0
  245. package/src/state/billing.ts +240 -0
  246. package/src/state/checkpoints.ts +117 -0
  247. package/src/state/config.ts +67 -0
  248. package/src/state/conversations.ts +14 -0
  249. package/src/state/credentials.ts +154 -0
  250. package/src/state/db.ts +58 -0
  251. package/src/state/index.ts +26 -0
  252. package/src/state/messages.ts +115 -0
  253. package/src/state/projects.ts +123 -0
  254. package/src/state/schema.ts +236 -0
  255. package/src/state/sessions.ts +147 -0
  256. package/src/state/teams.ts +200 -0
  257. package/src/telemetry.ts +108 -0
  258. package/src/tools/aws-ops.ts +952 -0
  259. package/src/tools/azure-ops.ts +579 -0
  260. package/src/tools/file-ops.ts +593 -0
  261. package/src/tools/gcp-ops.ts +625 -0
  262. package/src/tools/git-ops.ts +773 -0
  263. package/src/tools/github-ops.ts +799 -0
  264. package/src/tools/helm-ops.ts +943 -0
  265. package/src/tools/index.ts +17 -0
  266. package/src/tools/k8s-ops.ts +819 -0
  267. package/src/tools/schemas/converter.ts +184 -0
  268. package/src/tools/schemas/devops.ts +612 -0
  269. package/src/tools/schemas/index.ts +73 -0
  270. package/src/tools/schemas/standard.ts +1144 -0
  271. package/src/tools/schemas/types.ts +705 -0
  272. package/src/tools/terraform-ops.ts +862 -0
  273. package/src/types/ambient.d.ts +193 -0
  274. package/src/types/config.ts +83 -0
  275. package/src/types/drift.ts +116 -0
  276. package/src/types/enterprise.ts +335 -0
  277. package/src/types/index.ts +20 -0
  278. package/src/types/plan.ts +44 -0
  279. package/src/types/request.ts +65 -0
  280. package/src/types/response.ts +54 -0
  281. package/src/types/service.ts +51 -0
  282. package/src/ui/App.tsx +997 -0
  283. package/src/ui/DeployPreview.tsx +169 -0
  284. package/src/ui/Header.tsx +68 -0
  285. package/src/ui/InputBox.tsx +350 -0
  286. package/src/ui/MessageList.tsx +585 -0
  287. package/src/ui/PermissionPrompt.tsx +151 -0
  288. package/src/ui/StatusBar.tsx +158 -0
  289. package/src/ui/ToolCallDisplay.tsx +409 -0
  290. package/src/ui/chat-ui.ts +853 -0
  291. package/src/ui/index.ts +33 -0
  292. package/src/ui/ink/index.ts +711 -0
  293. package/src/ui/streaming.ts +176 -0
  294. package/src/ui/types.ts +57 -0
  295. package/src/utils/analytics.ts +72 -0
  296. package/src/utils/cost-warning.ts +27 -0
  297. package/src/utils/env.ts +46 -0
  298. package/src/utils/errors.ts +69 -0
  299. package/src/utils/event-bus.ts +38 -0
  300. package/src/utils/index.ts +24 -0
  301. package/src/utils/logger.ts +171 -0
  302. package/src/utils/rate-limiter.ts +121 -0
  303. package/src/utils/service-auth.ts +49 -0
  304. package/src/utils/validation.ts +53 -0
  305. package/src/version.ts +4 -0
  306. package/src/watcher/index.ts +163 -0
  307. package/src/wizard/approval.ts +383 -0
  308. package/src/wizard/index.ts +25 -0
  309. package/src/wizard/prompts.ts +338 -0
  310. package/src/wizard/types.ts +171 -0
  311. package/src/wizard/ui.ts +556 -0
  312. package/src/wizard/wizard.ts +304 -0
  313. package/tsconfig.json +24 -0
@@ -0,0 +1,344 @@
1
+ /**
2
+ * GCP IAM CLI Commands
3
+ *
4
+ * Operations for IAM service accounts and roles
5
+ */
6
+
7
+ import { execFile } from 'child_process';
8
+ import { promisify } from 'util';
9
+ import { logger } from '../../utils';
10
+ import { ui } from '../../wizard/ui';
11
+ import type { GcpCommandOptions } from './index';
12
+
13
+ const execFileAsync = promisify(execFile);
14
+
15
+ /**
16
+ * Main IAM command router
17
+ */
18
+ export async function iamCommand(
19
+ action: string,
20
+ args: string[],
21
+ options: GcpCommandOptions
22
+ ): Promise<void> {
23
+ logger.info('Running GCP IAM command', { action, args, options });
24
+
25
+ switch (action) {
26
+ case 'service-accounts':
27
+ case 'sa':
28
+ await listServiceAccounts(options);
29
+ break;
30
+
31
+ case 'describe-sa':
32
+ if (!args[0]) {
33
+ ui.error('Service account email required');
34
+ return;
35
+ }
36
+ await describeServiceAccount(args[0], options);
37
+ break;
38
+
39
+ case 'roles':
40
+ await listRoles(options);
41
+ break;
42
+
43
+ case 'describe-role':
44
+ if (!args[0]) {
45
+ ui.error('Role name required');
46
+ return;
47
+ }
48
+ await describeRole(args[0], options);
49
+ break;
50
+
51
+ case 'bindings':
52
+ await listBindings(options);
53
+ break;
54
+
55
+ default:
56
+ showIamHelp();
57
+ break;
58
+ }
59
+ }
60
+
61
+ /**
62
+ * List service accounts
63
+ */
64
+ async function listServiceAccounts(options: GcpCommandOptions): Promise<void> {
65
+ ui.header('Service Accounts');
66
+ ui.newLine();
67
+
68
+ const gcloudArgs = ['iam', 'service-accounts', 'list', '--format=json'];
69
+ if (options.project) {
70
+ gcloudArgs.push(`--project=${options.project}`);
71
+ }
72
+
73
+ try {
74
+ ui.startSpinner({ message: 'Fetching service accounts...' });
75
+ const { stdout } = await execFileAsync('gcloud', gcloudArgs);
76
+ ui.stopSpinnerSuccess('Service accounts fetched');
77
+
78
+ const accounts = JSON.parse(stdout || '[]');
79
+
80
+ if (accounts.length === 0) {
81
+ ui.info('No service accounts found');
82
+ return;
83
+ }
84
+
85
+ ui.print(`Found ${accounts.length} service account(s)\n`);
86
+
87
+ // Display table
88
+ ui.print(ui.color(`${'Display Name'.padEnd(30) + 'Email'.padEnd(50)}Disabled`, 'cyan'));
89
+ ui.print('─'.repeat(90));
90
+
91
+ for (const account of accounts) {
92
+ const displayName = account.displayName?.substring(0, 29) || '(no name)';
93
+ const email = account.email?.substring(0, 49) || '';
94
+ const disabled = account.disabled ? 'Yes' : 'No';
95
+
96
+ ui.print(`${displayName.padEnd(30)}${email.padEnd(50)}${disabled}`);
97
+ }
98
+ } catch (error: unknown) {
99
+ ui.stopSpinnerFail('Failed to fetch service accounts');
100
+ const message = error instanceof Error ? error.message : 'Unknown error';
101
+ logger.error('Failed to list service accounts', { error: message });
102
+ ui.error(`Failed to list service accounts: ${message}`);
103
+ }
104
+ }
105
+
106
+ /**
107
+ * Describe a specific service account
108
+ */
109
+ async function describeServiceAccount(email: string, options: GcpCommandOptions): Promise<void> {
110
+ ui.header(`Service Account: ${email}`);
111
+ ui.newLine();
112
+
113
+ const gcloudArgs = ['iam', 'service-accounts', 'describe', email, '--format=json'];
114
+ if (options.project) {
115
+ gcloudArgs.push(`--project=${options.project}`);
116
+ }
117
+
118
+ try {
119
+ ui.startSpinner({ message: 'Fetching service account details...' });
120
+ const { stdout } = await execFileAsync('gcloud', gcloudArgs);
121
+ ui.stopSpinnerSuccess('Details fetched');
122
+
123
+ const account = JSON.parse(stdout);
124
+
125
+ ui.print(ui.bold('Basic Information:'));
126
+ ui.print(` Display Name: ${account.displayName || '(none)'}`);
127
+ ui.print(` Email: ${account.email}`);
128
+ ui.print(` Unique ID: ${account.uniqueId}`);
129
+ ui.print(` Disabled: ${account.disabled ? 'Yes' : 'No'}`);
130
+ ui.print(` Description: ${account.description || '(none)'}`);
131
+ ui.newLine();
132
+
133
+ // Get keys
134
+ try {
135
+ const keysArgs = [
136
+ 'iam',
137
+ 'service-accounts',
138
+ 'keys',
139
+ 'list',
140
+ '--iam-account',
141
+ email,
142
+ '--format=json',
143
+ ];
144
+ if (options.project) {
145
+ keysArgs.push(`--project=${options.project}`);
146
+ }
147
+ const { stdout: keysOutput } = await execFileAsync('gcloud', keysArgs);
148
+ const keys = JSON.parse(keysOutput || '[]');
149
+
150
+ ui.print(ui.bold('Keys:'));
151
+ if (keys.length === 0) {
152
+ ui.print(' No keys found');
153
+ } else {
154
+ for (const key of keys) {
155
+ const keyId = key.name?.split('/').pop() || '';
156
+ const keyType = key.keyType || '';
157
+ const validAfter = key.validAfterTime || '';
158
+ const validBefore = key.validBeforeTime || '';
159
+
160
+ ui.print(` Key ID: ${keyId}`);
161
+ ui.print(` Type: ${keyType}`);
162
+ ui.print(` Valid After: ${validAfter}`);
163
+ ui.print(` Valid Before: ${validBefore}`);
164
+ ui.newLine();
165
+ }
166
+ }
167
+ } catch {
168
+ ui.print(ui.bold('Keys:'));
169
+ ui.print(' Unable to fetch keys');
170
+ }
171
+ } catch (error: unknown) {
172
+ ui.stopSpinnerFail('Failed to fetch details');
173
+ const message = error instanceof Error ? error.message : 'Unknown error';
174
+ logger.error('Failed to describe service account', { error: message });
175
+ ui.error(`Failed to describe service account: ${message}`);
176
+ }
177
+ }
178
+
179
+ /**
180
+ * List predefined roles
181
+ */
182
+ async function listRoles(options: GcpCommandOptions): Promise<void> {
183
+ ui.header('IAM Roles');
184
+ ui.newLine();
185
+
186
+ // List project-level custom roles
187
+ const gcloudArgs = ['iam', 'roles', 'list', '--format=json'];
188
+ if (options.project) {
189
+ gcloudArgs.push(`--project=${options.project}`);
190
+ }
191
+
192
+ try {
193
+ ui.startSpinner({ message: 'Fetching roles...' });
194
+ const { stdout } = await execFileAsync('gcloud', gcloudArgs);
195
+ ui.stopSpinnerSuccess('Roles fetched');
196
+
197
+ const roles = JSON.parse(stdout || '[]');
198
+
199
+ if (roles.length === 0) {
200
+ ui.info(
201
+ 'No custom roles found. Use gcloud iam roles list --filter="stage=GA" to see predefined roles.'
202
+ );
203
+ return;
204
+ }
205
+
206
+ ui.print(`Found ${roles.length} custom role(s)\n`);
207
+
208
+ // Display table
209
+ ui.print(ui.color(`${'Name'.padEnd(40) + 'Title'.padEnd(35)}Stage`, 'cyan'));
210
+ ui.print('─'.repeat(85));
211
+
212
+ for (const role of roles) {
213
+ const name = role.name?.split('/').pop()?.substring(0, 39) || '';
214
+ const title = role.title?.substring(0, 34) || '';
215
+ const stage = role.stage || '';
216
+
217
+ ui.print(`${name.padEnd(40)}${title.padEnd(35)}${stage}`);
218
+ }
219
+ } catch (error: unknown) {
220
+ ui.stopSpinnerFail('Failed to fetch roles');
221
+ const message = error instanceof Error ? error.message : 'Unknown error';
222
+ logger.error('Failed to list roles', { error: message });
223
+ ui.error(`Failed to list roles: ${message}`);
224
+ }
225
+ }
226
+
227
+ /**
228
+ * Describe a specific role
229
+ */
230
+ async function describeRole(roleName: string, options: GcpCommandOptions): Promise<void> {
231
+ ui.header(`Role: ${roleName}`);
232
+ ui.newLine();
233
+
234
+ const gcloudArgs = ['iam', 'roles', 'describe', roleName, '--format=json'];
235
+ if (options.project && !roleName.startsWith('roles/')) {
236
+ gcloudArgs.push(`--project=${options.project}`);
237
+ }
238
+
239
+ try {
240
+ ui.startSpinner({ message: 'Fetching role details...' });
241
+ const { stdout } = await execFileAsync('gcloud', gcloudArgs);
242
+ ui.stopSpinnerSuccess('Details fetched');
243
+
244
+ const role = JSON.parse(stdout);
245
+
246
+ ui.print(ui.bold('Basic Information:'));
247
+ ui.print(` Name: ${role.name}`);
248
+ ui.print(` Title: ${role.title}`);
249
+ ui.print(` Description: ${role.description || '(none)'}`);
250
+ ui.print(` Stage: ${role.stage}`);
251
+ ui.print(` ETag: ${role.etag}`);
252
+ ui.newLine();
253
+
254
+ ui.print(ui.bold('Permissions:'));
255
+ const permissions = role.includedPermissions || [];
256
+ if (permissions.length === 0) {
257
+ ui.print(' No permissions');
258
+ } else {
259
+ ui.print(` Total: ${permissions.length} permission(s)`);
260
+ // Show first 20 permissions
261
+ const displayPerms = permissions.slice(0, 20);
262
+ for (const perm of displayPerms) {
263
+ ui.print(` ${perm}`);
264
+ }
265
+ if (permissions.length > 20) {
266
+ ui.print(ui.dim(` ... and ${permissions.length - 20} more`));
267
+ }
268
+ }
269
+ } catch (error: unknown) {
270
+ ui.stopSpinnerFail('Failed to fetch details');
271
+ const message = error instanceof Error ? error.message : 'Unknown error';
272
+ logger.error('Failed to describe role', { error: message });
273
+ ui.error(`Failed to describe role: ${message}`);
274
+ }
275
+ }
276
+
277
+ /**
278
+ * List IAM bindings for the project
279
+ */
280
+ async function listBindings(options: GcpCommandOptions): Promise<void> {
281
+ ui.header('IAM Policy Bindings');
282
+ ui.newLine();
283
+
284
+ let projectId = options.project || '';
285
+
286
+ if (!projectId) {
287
+ // Get current project
288
+ try {
289
+ const { stdout: projectOut } = await execFileAsync('gcloud', [
290
+ 'config',
291
+ 'get-value',
292
+ 'project',
293
+ ]);
294
+ projectId = projectOut.trim();
295
+ } catch {
296
+ ui.error('Project not specified and no default project configured');
297
+ return;
298
+ }
299
+ }
300
+
301
+ const gcloudArgs = ['projects', 'get-iam-policy', projectId, '--format=json'];
302
+
303
+ try {
304
+ ui.startSpinner({ message: 'Fetching IAM policy...' });
305
+ const { stdout } = await execFileAsync('gcloud', gcloudArgs);
306
+ ui.stopSpinnerSuccess('Policy fetched');
307
+
308
+ const policy = JSON.parse(stdout);
309
+ const bindings = policy.bindings || [];
310
+
311
+ if (bindings.length === 0) {
312
+ ui.info('No IAM bindings found');
313
+ return;
314
+ }
315
+
316
+ ui.print(`Found ${bindings.length} binding(s)\n`);
317
+
318
+ for (const binding of bindings) {
319
+ ui.print(ui.bold(`Role: ${binding.role}`));
320
+ ui.print(' Members:');
321
+ for (const member of binding.members || []) {
322
+ ui.print(` - ${member}`);
323
+ }
324
+ ui.newLine();
325
+ }
326
+ } catch (error: unknown) {
327
+ ui.stopSpinnerFail('Failed to fetch policy');
328
+ const message = error instanceof Error ? error.message : 'Unknown error';
329
+ logger.error('Failed to get IAM policy', { error: message });
330
+ ui.error(`Failed to get IAM policy: ${message}`);
331
+ }
332
+ }
333
+
334
+ /**
335
+ * Show IAM help
336
+ */
337
+ function showIamHelp(): void {
338
+ ui.print(ui.bold('IAM Commands:'));
339
+ ui.print(' service-accounts List all service accounts');
340
+ ui.print(' describe-sa <email> Show service account details');
341
+ ui.print(' roles List custom roles');
342
+ ui.print(' describe-role <name> Show role details');
343
+ ui.print(' bindings Show project IAM policy bindings');
344
+ }
@@ -0,0 +1,129 @@
1
+ /**
2
+ * GCP CLI Commands
3
+ *
4
+ * Wrapper for Google Cloud CLI operations with enhanced output and safety checks
5
+ *
6
+ * Usage:
7
+ * nimbus gcp compute list
8
+ * nimbus gcp storage ls
9
+ * nimbus gcp gke clusters
10
+ * nimbus gcp functions list
11
+ * nimbus gcp iam service-accounts
12
+ */
13
+
14
+ import { logger } from '../../utils';
15
+ import { ui } from '../../wizard/ui';
16
+ import { computeCommand } from './compute';
17
+ import { storageCommand } from './storage';
18
+ import { gkeCommand } from './gke';
19
+ import { functionsCommand } from './functions';
20
+ import { iamCommand } from './iam';
21
+
22
+ export interface GcpCommandOptions {
23
+ project?: string;
24
+ region?: string;
25
+ zone?: string;
26
+ format?: 'json' | 'table' | 'text';
27
+ }
28
+
29
+ /**
30
+ * Parse common GCP options from args
31
+ */
32
+ export function parseGcpOptions(args: string[]): GcpCommandOptions {
33
+ const options: GcpCommandOptions = {};
34
+
35
+ for (let i = 0; i < args.length; i++) {
36
+ const arg = args[i];
37
+
38
+ if ((arg === '--project' || arg === '-p') && args[i + 1]) {
39
+ options.project = args[++i];
40
+ } else if ((arg === '--region' || arg === '-r') && args[i + 1]) {
41
+ options.region = args[++i];
42
+ } else if ((arg === '--zone' || arg === '-z') && args[i + 1]) {
43
+ options.zone = args[++i];
44
+ } else if ((arg === '--format' || arg === '-f') && args[i + 1]) {
45
+ options.format = args[++i] as 'json' | 'table' | 'text';
46
+ }
47
+ }
48
+
49
+ return options;
50
+ }
51
+
52
+ /**
53
+ * Main GCP command router
54
+ */
55
+ export async function gcpCommand(subcommand: string, args: string[]): Promise<void> {
56
+ logger.info('Running GCP command', { subcommand, args });
57
+
58
+ const options = parseGcpOptions(args);
59
+ const positionalArgs = args.filter(arg => !arg.startsWith('-') && !arg.startsWith('--'));
60
+
61
+ switch (subcommand) {
62
+ case 'compute':
63
+ await computeCommand(positionalArgs[0], positionalArgs.slice(1), options);
64
+ break;
65
+
66
+ case 'storage':
67
+ await storageCommand(positionalArgs[0], positionalArgs.slice(1), options);
68
+ break;
69
+
70
+ case 'gke':
71
+ await gkeCommand(positionalArgs[0], positionalArgs.slice(1), options);
72
+ break;
73
+
74
+ case 'functions':
75
+ await functionsCommand(positionalArgs[0], positionalArgs.slice(1), options);
76
+ break;
77
+
78
+ case 'iam':
79
+ await iamCommand(positionalArgs[0], positionalArgs.slice(1), options);
80
+ break;
81
+
82
+ default:
83
+ showGcpHelp();
84
+ break;
85
+ }
86
+ }
87
+
88
+ /**
89
+ * Show GCP command help
90
+ */
91
+ function showGcpHelp(): void {
92
+ ui.header('Nimbus GCP Commands');
93
+ ui.newLine();
94
+
95
+ ui.print('Usage: nimbus gcp <service> <action> [options]');
96
+ ui.newLine();
97
+
98
+ ui.print(ui.bold('Services:'));
99
+ ui.print(' compute Compute Engine operations');
100
+ ui.print(' storage Cloud Storage operations');
101
+ ui.print(' gke Google Kubernetes Engine operations');
102
+ ui.print(' functions Cloud Functions operations');
103
+ ui.print(' iam IAM service accounts and roles');
104
+ ui.newLine();
105
+
106
+ ui.print(ui.bold('Common Options:'));
107
+ ui.print(' --project, -p GCP project ID');
108
+ ui.print(' --region, -r GCP region');
109
+ ui.print(' --zone, -z GCP zone');
110
+ ui.print(' --format, -f Output format (json, table, text)');
111
+ ui.newLine();
112
+
113
+ ui.print(ui.bold('Examples:'));
114
+ ui.print(' nimbus gcp compute list List all Compute Engine instances');
115
+ ui.print(' nimbus gcp compute describe my-instance Describe specific instance');
116
+ ui.print(' nimbus gcp storage ls List all Cloud Storage buckets');
117
+ ui.print(' nimbus gcp storage ls gs://my-bucket List objects in bucket');
118
+ ui.print(' nimbus gcp gke clusters List GKE clusters');
119
+ ui.print(' nimbus gcp functions list List Cloud Functions');
120
+ }
121
+
122
+ // Re-export subcommands
123
+ export { computeCommand } from './compute';
124
+ export { storageCommand } from './storage';
125
+ export { gkeCommand } from './gke';
126
+ export { functionsCommand } from './functions';
127
+ export { iamCommand } from './iam';
128
+
129
+ export default gcpCommand;