@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,325 @@
1
+ /**
2
+ * GCP Compute Engine CLI Commands
3
+ *
4
+ * Operations for Compute Engine instances
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 {
12
+ loadSafetyPolicy,
13
+ evaluateSafety,
14
+ type SafetyContext,
15
+ type SafetyCheckResult,
16
+ } from '../../config/safety-policy';
17
+ import { promptForApproval, displaySafetySummary } from '../../wizard/approval';
18
+ import type { GcpCommandOptions } from './index';
19
+
20
+ const execFileAsync = promisify(execFile);
21
+
22
+ /**
23
+ * Run Compute Engine safety checks
24
+ */
25
+ async function runComputeSafetyChecks(
26
+ action: string,
27
+ instanceId: string,
28
+ options: GcpCommandOptions
29
+ ): Promise<SafetyCheckResult> {
30
+ const safetyPolicy = loadSafetyPolicy();
31
+
32
+ const context: SafetyContext = {
33
+ operation: action,
34
+ type: 'gcp',
35
+ environment: options.project || 'default',
36
+ resources: [instanceId],
37
+ metadata: {
38
+ resourceType: 'compute-instance',
39
+ resourceId: instanceId,
40
+ },
41
+ };
42
+
43
+ return evaluateSafety(context, safetyPolicy);
44
+ }
45
+
46
+ /**
47
+ * Main Compute Engine command router
48
+ */
49
+ export async function computeCommand(
50
+ action: string,
51
+ args: string[],
52
+ options: GcpCommandOptions
53
+ ): Promise<void> {
54
+ logger.info('Running GCP Compute command', { action, args, options });
55
+
56
+ switch (action) {
57
+ case 'list':
58
+ await listInstances(options);
59
+ break;
60
+
61
+ case 'describe':
62
+ if (!args[0]) {
63
+ ui.error('Instance name required');
64
+ return;
65
+ }
66
+ await describeInstance(args[0], options);
67
+ break;
68
+
69
+ case 'start':
70
+ if (!args[0]) {
71
+ ui.error('Instance name required');
72
+ return;
73
+ }
74
+ await startInstance(args[0], options);
75
+ break;
76
+
77
+ case 'stop':
78
+ if (!args[0]) {
79
+ ui.error('Instance name required');
80
+ return;
81
+ }
82
+ await stopInstance(args[0], options);
83
+ break;
84
+
85
+ case 'delete':
86
+ if (!args[0]) {
87
+ ui.error('Instance name required');
88
+ return;
89
+ }
90
+ await deleteInstance(args[0], options);
91
+ break;
92
+
93
+ default:
94
+ showComputeHelp();
95
+ break;
96
+ }
97
+ }
98
+
99
+ /**
100
+ * List Compute Engine instances
101
+ */
102
+ async function listInstances(options: GcpCommandOptions): Promise<void> {
103
+ ui.header('Compute Engine Instances');
104
+ ui.newLine();
105
+
106
+ const gcloudArgs = ['compute', 'instances', 'list', '--format=json'];
107
+ if (options.project) {
108
+ gcloudArgs.push(`--project=${options.project}`);
109
+ }
110
+ if (options.zone) {
111
+ gcloudArgs.push(`--zones=${options.zone}`);
112
+ }
113
+
114
+ try {
115
+ ui.startSpinner({ message: 'Fetching instances...' });
116
+ const { stdout } = await execFileAsync('gcloud', gcloudArgs);
117
+ ui.stopSpinnerSuccess('Instances fetched');
118
+
119
+ const instances = JSON.parse(stdout || '[]');
120
+
121
+ if (instances.length === 0) {
122
+ ui.info('No instances found');
123
+ return;
124
+ }
125
+
126
+ ui.print(`Found ${instances.length} instance(s)\n`);
127
+
128
+ // Display table
129
+ ui.print(
130
+ ui.color(
131
+ `${
132
+ 'Name'.padEnd(25) + 'Zone'.padEnd(25) + 'Machine Type'.padEnd(20) + 'Status'.padEnd(12)
133
+ }External IP`,
134
+ 'cyan'
135
+ )
136
+ );
137
+ ui.print('─'.repeat(100));
138
+
139
+ for (const instance of instances) {
140
+ const name = instance.name?.substring(0, 24) || '';
141
+ const zone = instance.zone?.split('/').pop()?.substring(0, 24) || '';
142
+ const machineType = instance.machineType?.split('/').pop()?.substring(0, 19) || '';
143
+ const status = instance.status || '';
144
+ const externalIp = instance.networkInterfaces?.[0]?.accessConfigs?.[0]?.natIP || 'None';
145
+
146
+ const statusColor =
147
+ status === 'RUNNING'
148
+ ? 'green'
149
+ : status === 'TERMINATED'
150
+ ? 'red'
151
+ : status === 'STOPPING'
152
+ ? 'yellow'
153
+ : 'white';
154
+
155
+ ui.print(
156
+ `${name.padEnd(25)}${zone.padEnd(25)}${machineType.padEnd(20)}${ui.color(status.padEnd(12), statusColor as 'green' | 'red' | 'yellow' | 'white')}${externalIp}`
157
+ );
158
+ }
159
+ } catch (error: unknown) {
160
+ ui.stopSpinnerFail('Failed to fetch instances');
161
+ const message = error instanceof Error ? error.message : 'Unknown error';
162
+ logger.error('Failed to list instances', { error: message });
163
+ ui.error(`Failed to list instances: ${message}`);
164
+ }
165
+ }
166
+
167
+ /**
168
+ * Describe a specific instance
169
+ */
170
+ async function describeInstance(instanceName: string, options: GcpCommandOptions): Promise<void> {
171
+ ui.header(`Instance: ${instanceName}`);
172
+ ui.newLine();
173
+
174
+ const gcloudArgs = ['compute', 'instances', 'describe', instanceName, '--format=json'];
175
+ if (options.project) {
176
+ gcloudArgs.push(`--project=${options.project}`);
177
+ }
178
+ if (options.zone) {
179
+ gcloudArgs.push(`--zone=${options.zone}`);
180
+ }
181
+
182
+ try {
183
+ ui.startSpinner({ message: 'Fetching instance details...' });
184
+ const { stdout } = await execFileAsync('gcloud', gcloudArgs);
185
+ ui.stopSpinnerSuccess('Details fetched');
186
+
187
+ const instance = JSON.parse(stdout);
188
+
189
+ ui.print(ui.bold('Basic Information:'));
190
+ ui.print(` Name: ${instance.name}`);
191
+ ui.print(` Zone: ${instance.zone?.split('/').pop()}`);
192
+ ui.print(` Machine Type: ${instance.machineType?.split('/').pop()}`);
193
+ ui.print(` Status: ${instance.status}`);
194
+ ui.print(` Created: ${instance.creationTimestamp}`);
195
+ ui.newLine();
196
+
197
+ ui.print(ui.bold('Network:'));
198
+ for (const nic of instance.networkInterfaces || []) {
199
+ ui.print(` Network: ${nic.network?.split('/').pop()}`);
200
+ ui.print(` Internal IP: ${nic.networkIP}`);
201
+ if (nic.accessConfigs?.[0]?.natIP) {
202
+ ui.print(` External IP: ${nic.accessConfigs[0].natIP}`);
203
+ }
204
+ }
205
+ ui.newLine();
206
+
207
+ ui.print(ui.bold('Disks:'));
208
+ for (const disk of instance.disks || []) {
209
+ ui.print(` Name: ${disk.source?.split('/').pop()}`);
210
+ ui.print(` Boot: ${disk.boot ? 'Yes' : 'No'}`);
211
+ ui.print(` Size: ${disk.diskSizeGb} GB`);
212
+ }
213
+ } catch (error: unknown) {
214
+ ui.stopSpinnerFail('Failed to fetch details');
215
+ const message = error instanceof Error ? error.message : 'Unknown error';
216
+ logger.error('Failed to describe instance', { error: message });
217
+ ui.error(`Failed to describe instance: ${message}`);
218
+ }
219
+ }
220
+
221
+ /**
222
+ * Start an instance
223
+ */
224
+ async function startInstance(instanceName: string, options: GcpCommandOptions): Promise<void> {
225
+ const gcloudArgs = ['compute', 'instances', 'start', instanceName];
226
+ if (options.project) {
227
+ gcloudArgs.push(`--project=${options.project}`);
228
+ }
229
+ if (options.zone) {
230
+ gcloudArgs.push(`--zone=${options.zone}`);
231
+ }
232
+
233
+ try {
234
+ ui.startSpinner({ message: `Starting instance ${instanceName}...` });
235
+ await execFileAsync('gcloud', gcloudArgs);
236
+ ui.stopSpinnerSuccess(`Instance ${instanceName} started`);
237
+ } catch (error: unknown) {
238
+ ui.stopSpinnerFail(`Failed to start instance`);
239
+ const message = error instanceof Error ? error.message : 'Unknown error';
240
+ logger.error('Failed to start instance', { error: message });
241
+ ui.error(`Failed to start instance: ${message}`);
242
+ }
243
+ }
244
+
245
+ /**
246
+ * Stop an instance
247
+ */
248
+ async function stopInstance(instanceName: string, options: GcpCommandOptions): Promise<void> {
249
+ const gcloudArgs = ['compute', 'instances', 'stop', instanceName];
250
+ if (options.project) {
251
+ gcloudArgs.push(`--project=${options.project}`);
252
+ }
253
+ if (options.zone) {
254
+ gcloudArgs.push(`--zone=${options.zone}`);
255
+ }
256
+
257
+ try {
258
+ ui.startSpinner({ message: `Stopping instance ${instanceName}...` });
259
+ await execFileAsync('gcloud', gcloudArgs);
260
+ ui.stopSpinnerSuccess(`Instance ${instanceName} stopped`);
261
+ } catch (error: unknown) {
262
+ ui.stopSpinnerFail(`Failed to stop instance`);
263
+ const message = error instanceof Error ? error.message : 'Unknown error';
264
+ logger.error('Failed to stop instance', { error: message });
265
+ ui.error(`Failed to stop instance: ${message}`);
266
+ }
267
+ }
268
+
269
+ /**
270
+ * Delete an instance (requires safety approval)
271
+ */
272
+ async function deleteInstance(instanceName: string, options: GcpCommandOptions): Promise<void> {
273
+ // Run safety checks
274
+ const safetyResult = await runComputeSafetyChecks('delete', instanceName, options);
275
+
276
+ displaySafetySummary({
277
+ operation: `delete ${instanceName}`,
278
+ risks: safetyResult.risks,
279
+ passed: safetyResult.passed,
280
+ });
281
+
282
+ if (safetyResult.requiresApproval) {
283
+ const result = await promptForApproval({
284
+ title: 'Delete Compute Engine Instance',
285
+ operation: `gcloud compute instances delete ${instanceName}`,
286
+ risks: safetyResult.risks,
287
+ });
288
+
289
+ if (!result.approved) {
290
+ ui.warning('Operation cancelled');
291
+ return;
292
+ }
293
+ }
294
+
295
+ const gcloudArgs = ['compute', 'instances', 'delete', instanceName, '--quiet'];
296
+ if (options.project) {
297
+ gcloudArgs.push(`--project=${options.project}`);
298
+ }
299
+ if (options.zone) {
300
+ gcloudArgs.push(`--zone=${options.zone}`);
301
+ }
302
+
303
+ try {
304
+ ui.startSpinner({ message: `Deleting instance ${instanceName}...` });
305
+ await execFileAsync('gcloud', gcloudArgs);
306
+ ui.stopSpinnerSuccess(`Instance ${instanceName} deleted`);
307
+ } catch (error: unknown) {
308
+ ui.stopSpinnerFail(`Failed to delete instance`);
309
+ const message = error instanceof Error ? error.message : 'Unknown error';
310
+ logger.error('Failed to delete instance', { error: message });
311
+ ui.error(`Failed to delete instance: ${message}`);
312
+ }
313
+ }
314
+
315
+ /**
316
+ * Show Compute help
317
+ */
318
+ function showComputeHelp(): void {
319
+ ui.print(ui.bold('Compute Engine Commands:'));
320
+ ui.print(' list List all instances');
321
+ ui.print(' describe <name> Show instance details');
322
+ ui.print(' start <name> Start an instance');
323
+ ui.print(' stop <name> Stop an instance');
324
+ ui.print(' delete <name> Delete an instance (requires approval)');
325
+ }
@@ -0,0 +1,271 @@
1
+ /**
2
+ * GCP Cloud Functions CLI Commands
3
+ *
4
+ * Operations for Cloud Functions
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 Cloud Functions command router
17
+ */
18
+ export async function functionsCommand(
19
+ action: string,
20
+ args: string[],
21
+ options: GcpCommandOptions
22
+ ): Promise<void> {
23
+ logger.info('Running GCP Functions command', { action, args, options });
24
+
25
+ switch (action) {
26
+ case 'list':
27
+ await listFunctions(options);
28
+ break;
29
+
30
+ case 'describe':
31
+ if (!args[0]) {
32
+ ui.error('Function name required');
33
+ return;
34
+ }
35
+ await describeFunction(args[0], options);
36
+ break;
37
+
38
+ case 'call':
39
+ if (!args[0]) {
40
+ ui.error('Function name required');
41
+ return;
42
+ }
43
+ await callFunction(args[0], args[1], options);
44
+ break;
45
+
46
+ case 'logs':
47
+ if (!args[0]) {
48
+ ui.error('Function name required');
49
+ return;
50
+ }
51
+ await getFunctionLogs(args[0], options);
52
+ break;
53
+
54
+ default:
55
+ showFunctionsHelp();
56
+ break;
57
+ }
58
+ }
59
+
60
+ /**
61
+ * List Cloud Functions
62
+ */
63
+ async function listFunctions(options: GcpCommandOptions): Promise<void> {
64
+ ui.header('Cloud Functions');
65
+ ui.newLine();
66
+
67
+ const gcloudArgs = ['functions', 'list', '--format=json'];
68
+ if (options.project) {
69
+ gcloudArgs.push(`--project=${options.project}`);
70
+ }
71
+ if (options.region) {
72
+ gcloudArgs.push(`--regions=${options.region}`);
73
+ }
74
+
75
+ try {
76
+ ui.startSpinner({ message: 'Fetching functions...' });
77
+ const { stdout } = await execFileAsync('gcloud', gcloudArgs);
78
+ ui.stopSpinnerSuccess('Functions fetched');
79
+
80
+ const functions = JSON.parse(stdout || '[]');
81
+
82
+ if (functions.length === 0) {
83
+ ui.info('No functions found');
84
+ return;
85
+ }
86
+
87
+ ui.print(`Found ${functions.length} function(s)\n`);
88
+
89
+ // Display table
90
+ ui.print(
91
+ ui.color(
92
+ `${
93
+ 'Name'.padEnd(30) + 'Region'.padEnd(15) + 'Runtime'.padEnd(15) + 'Trigger'.padEnd(15)
94
+ }State`,
95
+ 'cyan'
96
+ )
97
+ );
98
+ ui.print('─'.repeat(90));
99
+
100
+ for (const fn of functions) {
101
+ const name = fn.name?.split('/').pop()?.substring(0, 29) || '';
102
+ const region = fn.name?.split('/')[3]?.substring(0, 14) || '';
103
+ const runtime = fn.runtime?.substring(0, 14) || '';
104
+ const trigger = fn.httpsTrigger ? 'HTTP' : fn.eventTrigger ? 'Event' : 'Unknown';
105
+ const state = fn.state || '';
106
+
107
+ const stateColor =
108
+ state === 'ACTIVE'
109
+ ? 'green'
110
+ : state === 'DEPLOYING'
111
+ ? 'yellow'
112
+ : state === 'FAILED'
113
+ ? 'red'
114
+ : 'white';
115
+
116
+ ui.print(
117
+ `${name.padEnd(30)}${region.padEnd(15)}${runtime.padEnd(15)}${trigger.padEnd(15)}${ui.color(state, stateColor as 'green' | 'yellow' | 'red' | 'white')}`
118
+ );
119
+ }
120
+ } catch (error: unknown) {
121
+ ui.stopSpinnerFail('Failed to fetch functions');
122
+ const message = error instanceof Error ? error.message : 'Unknown error';
123
+ logger.error('Failed to list functions', { error: message });
124
+ ui.error(`Failed to list functions: ${message}`);
125
+ }
126
+ }
127
+
128
+ /**
129
+ * Describe a specific function
130
+ */
131
+ async function describeFunction(functionName: string, options: GcpCommandOptions): Promise<void> {
132
+ ui.header(`Function: ${functionName}`);
133
+ ui.newLine();
134
+
135
+ const gcloudArgs = ['functions', 'describe', functionName, '--format=json'];
136
+ if (options.project) {
137
+ gcloudArgs.push(`--project=${options.project}`);
138
+ }
139
+ if (options.region) {
140
+ gcloudArgs.push(`--region=${options.region}`);
141
+ }
142
+
143
+ try {
144
+ ui.startSpinner({ message: 'Fetching function details...' });
145
+ const { stdout } = await execFileAsync('gcloud', gcloudArgs);
146
+ ui.stopSpinnerSuccess('Details fetched');
147
+
148
+ const fn = JSON.parse(stdout);
149
+
150
+ ui.print(ui.bold('Basic Information:'));
151
+ ui.print(` Name: ${fn.name?.split('/').pop()}`);
152
+ ui.print(` Region: ${fn.name?.split('/')[3]}`);
153
+ ui.print(` State: ${fn.state}`);
154
+ ui.print(` Runtime: ${fn.runtime}`);
155
+ ui.print(` Entry Point: ${fn.entryPoint}`);
156
+ ui.newLine();
157
+
158
+ ui.print(ui.bold('Resources:'));
159
+ ui.print(` Memory: ${fn.availableMemoryMb} MB`);
160
+ ui.print(` Timeout: ${fn.timeout}`);
161
+ ui.print(` Max Instances: ${fn.maxInstances || 'default'}`);
162
+ ui.newLine();
163
+
164
+ ui.print(ui.bold('Trigger:'));
165
+ if (fn.httpsTrigger) {
166
+ ui.print(` Type: HTTP`);
167
+ ui.print(` URL: ${fn.httpsTrigger.url}`);
168
+ } else if (fn.eventTrigger) {
169
+ ui.print(` Type: Event`);
170
+ ui.print(` Event Type: ${fn.eventTrigger.eventType}`);
171
+ ui.print(` Resource: ${fn.eventTrigger.resource}`);
172
+ }
173
+ ui.newLine();
174
+
175
+ ui.print(ui.bold('Timestamps:'));
176
+ ui.print(` Created: ${fn.buildId ? `Build ID: ${fn.buildId}` : 'N/A'}`);
177
+ ui.print(` Updated: ${fn.updateTime}`);
178
+ } catch (error: unknown) {
179
+ ui.stopSpinnerFail('Failed to fetch details');
180
+ const message = error instanceof Error ? error.message : 'Unknown error';
181
+ logger.error('Failed to describe function', { error: message });
182
+ ui.error(`Failed to describe function: ${message}`);
183
+ }
184
+ }
185
+
186
+ /**
187
+ * Call a function
188
+ */
189
+ async function callFunction(
190
+ functionName: string,
191
+ data: string | undefined,
192
+ options: GcpCommandOptions
193
+ ): Promise<void> {
194
+ const gcloudArgs = ['functions', 'call', functionName];
195
+ if (data) {
196
+ gcloudArgs.push('--data', data);
197
+ }
198
+ if (options.project) {
199
+ gcloudArgs.push(`--project=${options.project}`);
200
+ }
201
+ if (options.region) {
202
+ gcloudArgs.push(`--region=${options.region}`);
203
+ }
204
+
205
+ try {
206
+ ui.startSpinner({ message: `Calling function ${functionName}...` });
207
+ const { stdout, stderr } = await execFileAsync('gcloud', gcloudArgs);
208
+ ui.stopSpinnerSuccess('Function invoked');
209
+ ui.newLine();
210
+
211
+ if (stdout) {
212
+ ui.print(ui.bold('Response:'));
213
+ ui.print(stdout);
214
+ }
215
+
216
+ if (stderr) {
217
+ ui.print(ui.bold('Execution ID:'));
218
+ ui.print(stderr);
219
+ }
220
+ } catch (error: unknown) {
221
+ ui.stopSpinnerFail('Failed to call function');
222
+ const message = error instanceof Error ? error.message : 'Unknown error';
223
+ logger.error('Failed to call function', { error: message });
224
+ ui.error(`Failed to call function: ${message}`);
225
+ }
226
+ }
227
+
228
+ /**
229
+ * Get function logs
230
+ */
231
+ async function getFunctionLogs(functionName: string, options: GcpCommandOptions): Promise<void> {
232
+ ui.header(`Logs for ${functionName}`);
233
+ ui.newLine();
234
+
235
+ const gcloudArgs = ['functions', 'logs', 'read', functionName, '--limit=50'];
236
+ if (options.project) {
237
+ gcloudArgs.push(`--project=${options.project}`);
238
+ }
239
+ if (options.region) {
240
+ gcloudArgs.push(`--region=${options.region}`);
241
+ }
242
+
243
+ try {
244
+ ui.startSpinner({ message: 'Fetching logs...' });
245
+ const { stdout } = await execFileAsync('gcloud', gcloudArgs);
246
+ ui.stopSpinnerSuccess('Logs fetched');
247
+
248
+ if (!stdout.trim()) {
249
+ ui.info('No logs found');
250
+ return;
251
+ }
252
+
253
+ ui.print(stdout);
254
+ } catch (error: unknown) {
255
+ ui.stopSpinnerFail('Failed to fetch logs');
256
+ const message = error instanceof Error ? error.message : 'Unknown error';
257
+ logger.error('Failed to get logs', { error: message });
258
+ ui.error(`Failed to get logs: ${message}`);
259
+ }
260
+ }
261
+
262
+ /**
263
+ * Show Functions help
264
+ */
265
+ function showFunctionsHelp(): void {
266
+ ui.print(ui.bold('Cloud Functions Commands:'));
267
+ ui.print(' list List all functions');
268
+ ui.print(' describe <name> Show function details');
269
+ ui.print(' call <name> [data] Invoke a function');
270
+ ui.print(' logs <name> View function logs');
271
+ }