@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,376 @@
1
+ /**
2
+ * Azure Kubernetes Service CLI Commands
3
+ *
4
+ * Operations for AKS clusters
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 { AzureCommandOptions } from './index';
19
+
20
+ const execFileAsync = promisify(execFile);
21
+
22
+ /**
23
+ * Run AKS safety checks
24
+ */
25
+ async function runAksSafetyChecks(
26
+ action: string,
27
+ clusterName: string,
28
+ options: AzureCommandOptions
29
+ ): Promise<SafetyCheckResult> {
30
+ const safetyPolicy = loadSafetyPolicy();
31
+
32
+ const context: SafetyContext = {
33
+ operation: action,
34
+ type: 'azure',
35
+ environment: options.subscription || 'default',
36
+ resources: [clusterName],
37
+ metadata: {
38
+ resourceType: 'aks-cluster',
39
+ resourceGroup: options.resourceGroup,
40
+ },
41
+ };
42
+
43
+ return evaluateSafety(context, safetyPolicy);
44
+ }
45
+
46
+ /**
47
+ * Main AKS command router
48
+ */
49
+ export async function aksCommand(
50
+ action: string,
51
+ args: string[],
52
+ options: AzureCommandOptions
53
+ ): Promise<void> {
54
+ logger.info('Running Azure AKS command', { action, args, options });
55
+
56
+ switch (action) {
57
+ case 'list':
58
+ await listClusters(options);
59
+ break;
60
+
61
+ case 'show':
62
+ if (!args[0]) {
63
+ ui.error('Cluster name required');
64
+ return;
65
+ }
66
+ await showCluster(args[0], options);
67
+ break;
68
+
69
+ case 'get-credentials':
70
+ if (!args[0]) {
71
+ ui.error('Cluster name required');
72
+ return;
73
+ }
74
+ await getCredentials(args[0], options);
75
+ break;
76
+
77
+ case 'scale':
78
+ if (args.length < 2) {
79
+ ui.error('Cluster name and node count required');
80
+ return;
81
+ }
82
+ await scaleCluster(args[0], parseInt(args[1], 10), options);
83
+ break;
84
+
85
+ case 'delete':
86
+ if (!args[0]) {
87
+ ui.error('Cluster name required');
88
+ return;
89
+ }
90
+ await deleteCluster(args[0], options);
91
+ break;
92
+
93
+ default:
94
+ showAksHelp();
95
+ break;
96
+ }
97
+ }
98
+
99
+ /**
100
+ * List AKS clusters
101
+ */
102
+ async function listClusters(options: AzureCommandOptions): Promise<void> {
103
+ ui.header('AKS Clusters');
104
+ ui.newLine();
105
+
106
+ const azArgs = ['aks', 'list', '-o', 'json'];
107
+ if (options.subscription) {
108
+ azArgs.push('--subscription', options.subscription);
109
+ }
110
+ if (options.resourceGroup) {
111
+ azArgs.push('-g', options.resourceGroup);
112
+ }
113
+
114
+ try {
115
+ ui.startSpinner({ message: 'Fetching AKS clusters...' });
116
+ const { stdout } = await execFileAsync('az', azArgs);
117
+ ui.stopSpinnerSuccess('Clusters fetched');
118
+
119
+ const clusters = JSON.parse(stdout || '[]');
120
+
121
+ if (clusters.length === 0) {
122
+ ui.info('No AKS clusters found');
123
+ return;
124
+ }
125
+
126
+ ui.print(`Found ${clusters.length} cluster(s)\n`);
127
+
128
+ // Display table
129
+ ui.print(
130
+ ui.color(
131
+ `${
132
+ 'Name'.padEnd(25) +
133
+ 'Resource Group'.padEnd(25) +
134
+ 'Location'.padEnd(15) +
135
+ 'K8s Version'.padEnd(15)
136
+ }Status`,
137
+ 'cyan'
138
+ )
139
+ );
140
+ ui.print('─'.repeat(95));
141
+
142
+ for (const cluster of clusters) {
143
+ const name = cluster.name?.substring(0, 24) || '';
144
+ const rg = cluster.resourceGroup?.substring(0, 24) || '';
145
+ const location = cluster.location?.substring(0, 14) || '';
146
+ const version = cluster.kubernetesVersion?.substring(0, 14) || '';
147
+ const status = cluster.provisioningState || '';
148
+
149
+ const statusColor =
150
+ status === 'Succeeded'
151
+ ? 'green'
152
+ : status === 'Failed'
153
+ ? 'red'
154
+ : status === 'Updating'
155
+ ? 'yellow'
156
+ : 'white';
157
+
158
+ ui.print(
159
+ `${name.padEnd(25)}${rg.padEnd(25)}${location.padEnd(15)}${version.padEnd(15)}${ui.color(status, statusColor as 'green' | 'red' | 'yellow' | 'white')}`
160
+ );
161
+ }
162
+ } catch (error: unknown) {
163
+ ui.stopSpinnerFail('Failed to fetch clusters');
164
+ const message = error instanceof Error ? error.message : 'Unknown error';
165
+ logger.error('Failed to list AKS clusters', { error: message });
166
+ ui.error(`Failed to list clusters: ${message}`);
167
+ }
168
+ }
169
+
170
+ /**
171
+ * Show AKS cluster details
172
+ */
173
+ async function showCluster(clusterName: string, options: AzureCommandOptions): Promise<void> {
174
+ ui.header(`AKS Cluster: ${clusterName}`);
175
+ ui.newLine();
176
+
177
+ if (!options.resourceGroup) {
178
+ ui.error('Resource group required (use -g)');
179
+ return;
180
+ }
181
+
182
+ const azArgs = ['aks', 'show', '-n', clusterName, '-g', options.resourceGroup, '-o', 'json'];
183
+ if (options.subscription) {
184
+ azArgs.push('--subscription', options.subscription);
185
+ }
186
+
187
+ try {
188
+ ui.startSpinner({ message: 'Fetching cluster details...' });
189
+ const { stdout } = await execFileAsync('az', azArgs);
190
+ ui.stopSpinnerSuccess('Details fetched');
191
+
192
+ const cluster = JSON.parse(stdout);
193
+
194
+ ui.print(ui.bold('Basic Information:'));
195
+ ui.print(` Name: ${cluster.name}`);
196
+ ui.print(` Resource Group: ${cluster.resourceGroup}`);
197
+ ui.print(` Location: ${cluster.location}`);
198
+ ui.print(` Kubernetes Ver: ${cluster.kubernetesVersion}`);
199
+ ui.print(` Status: ${cluster.provisioningState}`);
200
+ ui.print(` FQDN: ${cluster.fqdn}`);
201
+ ui.newLine();
202
+
203
+ ui.print(ui.bold('Network:'));
204
+ const networkProfile = cluster.networkProfile || {};
205
+ ui.print(` Network Plugin: ${networkProfile.networkPlugin}`);
206
+ ui.print(` Network Policy: ${networkProfile.networkPolicy || 'None'}`);
207
+ ui.print(` Service CIDR: ${networkProfile.serviceCidr}`);
208
+ ui.print(` DNS Service IP: ${networkProfile.dnsServiceIp}`);
209
+ ui.print(` Pod CIDR: ${networkProfile.podCidr || 'N/A'}`);
210
+ ui.newLine();
211
+
212
+ ui.print(ui.bold('Node Pools:'));
213
+ const nodePools = cluster.agentPoolProfiles || [];
214
+ for (const pool of nodePools) {
215
+ ui.print(` ${ui.color(pool.name, 'cyan')}`);
216
+ ui.print(` VM Size: ${pool.vmSize}`);
217
+ ui.print(` Node Count: ${pool.count}`);
218
+ ui.print(` OS Type: ${pool.osType}`);
219
+ ui.print(` Mode: ${pool.mode}`);
220
+ ui.newLine();
221
+ }
222
+ } catch (error: unknown) {
223
+ ui.stopSpinnerFail('Failed to fetch details');
224
+ const message = error instanceof Error ? error.message : 'Unknown error';
225
+ logger.error('Failed to show cluster', { error: message });
226
+ ui.error(`Failed to show cluster: ${message}`);
227
+ }
228
+ }
229
+
230
+ /**
231
+ * Get cluster credentials for kubectl
232
+ */
233
+ async function getCredentials(clusterName: string, options: AzureCommandOptions): Promise<void> {
234
+ if (!options.resourceGroup) {
235
+ ui.error('Resource group required (use -g)');
236
+ return;
237
+ }
238
+
239
+ const azArgs = ['aks', 'get-credentials', '-n', clusterName, '-g', options.resourceGroup];
240
+ if (options.subscription) {
241
+ azArgs.push('--subscription', options.subscription);
242
+ }
243
+
244
+ try {
245
+ ui.startSpinner({ message: `Getting credentials for ${clusterName}...` });
246
+ await execFileAsync('az', azArgs);
247
+ ui.stopSpinnerSuccess(`Credentials configured for cluster ${clusterName}`);
248
+ ui.info('You can now use kubectl to interact with this cluster');
249
+ } catch (error: unknown) {
250
+ ui.stopSpinnerFail('Failed to get credentials');
251
+ const message = error instanceof Error ? error.message : 'Unknown error';
252
+ logger.error('Failed to get credentials', { error: message });
253
+ ui.error(`Failed to get credentials: ${message}`);
254
+ }
255
+ }
256
+
257
+ /**
258
+ * Scale AKS cluster
259
+ */
260
+ async function scaleCluster(
261
+ clusterName: string,
262
+ nodeCount: number,
263
+ options: AzureCommandOptions
264
+ ): Promise<void> {
265
+ if (!options.resourceGroup) {
266
+ ui.error('Resource group required (use -g)');
267
+ return;
268
+ }
269
+
270
+ // Run safety checks
271
+ const safetyResult = await runAksSafetyChecks('scale', clusterName, options);
272
+
273
+ displaySafetySummary({
274
+ operation: `scale ${clusterName} to ${nodeCount} nodes`,
275
+ risks: safetyResult.risks,
276
+ passed: safetyResult.passed,
277
+ });
278
+
279
+ if (safetyResult.requiresApproval) {
280
+ const result = await promptForApproval({
281
+ title: 'Scale AKS Cluster',
282
+ operation: `az aks scale -n ${clusterName} -g ${options.resourceGroup} --node-count ${nodeCount}`,
283
+ risks: safetyResult.risks,
284
+ });
285
+
286
+ if (!result.approved) {
287
+ ui.warning('Operation cancelled');
288
+ return;
289
+ }
290
+ }
291
+
292
+ const azArgs = [
293
+ 'aks',
294
+ 'scale',
295
+ '-n',
296
+ clusterName,
297
+ '-g',
298
+ options.resourceGroup,
299
+ '--node-count',
300
+ String(nodeCount),
301
+ ];
302
+ if (options.subscription) {
303
+ azArgs.push('--subscription', options.subscription);
304
+ }
305
+
306
+ try {
307
+ ui.startSpinner({ message: `Scaling cluster ${clusterName} to ${nodeCount} nodes...` });
308
+ await execFileAsync('az', azArgs);
309
+ ui.stopSpinnerSuccess(`Cluster ${clusterName} scaled to ${nodeCount} nodes`);
310
+ } catch (error: unknown) {
311
+ ui.stopSpinnerFail('Failed to scale cluster');
312
+ const message = error instanceof Error ? error.message : 'Unknown error';
313
+ logger.error('Failed to scale cluster', { error: message });
314
+ ui.error(`Failed to scale cluster: ${message}`);
315
+ }
316
+ }
317
+
318
+ /**
319
+ * Delete AKS cluster (requires safety approval)
320
+ */
321
+ async function deleteCluster(clusterName: string, options: AzureCommandOptions): Promise<void> {
322
+ if (!options.resourceGroup) {
323
+ ui.error('Resource group required (use -g)');
324
+ return;
325
+ }
326
+
327
+ // Run safety checks
328
+ const safetyResult = await runAksSafetyChecks('delete', clusterName, options);
329
+
330
+ displaySafetySummary({
331
+ operation: `delete cluster ${clusterName}`,
332
+ risks: safetyResult.risks,
333
+ passed: safetyResult.passed,
334
+ });
335
+
336
+ if (safetyResult.requiresApproval) {
337
+ const result = await promptForApproval({
338
+ title: 'Delete AKS Cluster',
339
+ operation: `az aks delete -n ${clusterName} -g ${options.resourceGroup}`,
340
+ risks: safetyResult.risks,
341
+ });
342
+
343
+ if (!result.approved) {
344
+ ui.warning('Operation cancelled');
345
+ return;
346
+ }
347
+ }
348
+
349
+ const azArgs = ['aks', 'delete', '-n', clusterName, '-g', options.resourceGroup, '--yes'];
350
+ if (options.subscription) {
351
+ azArgs.push('--subscription', options.subscription);
352
+ }
353
+
354
+ try {
355
+ ui.startSpinner({ message: `Deleting cluster ${clusterName}...` });
356
+ await execFileAsync('az', azArgs);
357
+ ui.stopSpinnerSuccess(`Cluster ${clusterName} deleted`);
358
+ } catch (error: unknown) {
359
+ ui.stopSpinnerFail('Failed to delete cluster');
360
+ const message = error instanceof Error ? error.message : 'Unknown error';
361
+ logger.error('Failed to delete cluster', { error: message });
362
+ ui.error(`Failed to delete cluster: ${message}`);
363
+ }
364
+ }
365
+
366
+ /**
367
+ * Show AKS help
368
+ */
369
+ function showAksHelp(): void {
370
+ ui.print(ui.bold('AKS Commands:'));
371
+ ui.print(' list List all AKS clusters');
372
+ ui.print(' show <name> -g <rg> Show cluster details');
373
+ ui.print(' get-credentials <name> -g <rg> Configure kubectl for cluster');
374
+ ui.print(' scale <name> <count> -g <rg> Scale cluster (requires approval)');
375
+ ui.print(' delete <name> -g <rg> Delete cluster (requires approval)');
376
+ }
@@ -0,0 +1,253 @@
1
+ /**
2
+ * Azure Functions CLI Commands
3
+ *
4
+ * Operations for Azure Function Apps
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 { AzureCommandOptions } from './index';
12
+
13
+ const execFileAsync = promisify(execFile);
14
+
15
+ /**
16
+ * Main Functions command router
17
+ */
18
+ export async function functionsCommand(
19
+ action: string,
20
+ args: string[],
21
+ options: AzureCommandOptions
22
+ ): Promise<void> {
23
+ logger.info('Running Azure Functions command', { action, args, options });
24
+
25
+ switch (action) {
26
+ case 'list':
27
+ await listFunctionApps(options);
28
+ break;
29
+
30
+ case 'show':
31
+ if (!args[0]) {
32
+ ui.error('Function app name required');
33
+ return;
34
+ }
35
+ await showFunctionApp(args[0], options);
36
+ break;
37
+
38
+ case 'functions':
39
+ if (!args[0]) {
40
+ ui.error('Function app name required');
41
+ return;
42
+ }
43
+ await listFunctions(args[0], options);
44
+ break;
45
+
46
+ default:
47
+ showFunctionsHelp();
48
+ break;
49
+ }
50
+ }
51
+
52
+ /**
53
+ * List Function Apps
54
+ */
55
+ async function listFunctionApps(options: AzureCommandOptions): Promise<void> {
56
+ ui.header('Azure Function Apps');
57
+ ui.newLine();
58
+
59
+ const azArgs = ['functionapp', 'list', '-o', 'json'];
60
+ if (options.subscription) {
61
+ azArgs.push('--subscription', options.subscription);
62
+ }
63
+ if (options.resourceGroup) {
64
+ azArgs.push('-g', options.resourceGroup);
65
+ }
66
+
67
+ try {
68
+ ui.startSpinner({ message: 'Fetching function apps...' });
69
+ const { stdout } = await execFileAsync('az', azArgs);
70
+ ui.stopSpinnerSuccess('Function apps fetched');
71
+
72
+ const apps = JSON.parse(stdout || '[]');
73
+
74
+ if (apps.length === 0) {
75
+ ui.info('No function apps found');
76
+ return;
77
+ }
78
+
79
+ ui.print(`Found ${apps.length} function app(s)\n`);
80
+
81
+ // Display table
82
+ ui.print(
83
+ ui.color(
84
+ `${
85
+ 'Name'.padEnd(30) +
86
+ 'Resource Group'.padEnd(25) +
87
+ 'Location'.padEnd(15) +
88
+ 'Runtime'.padEnd(15)
89
+ }State`,
90
+ 'cyan'
91
+ )
92
+ );
93
+ ui.print('─'.repeat(100));
94
+
95
+ for (const app of apps) {
96
+ const name = app.name?.substring(0, 29) || '';
97
+ const rg = app.resourceGroup?.substring(0, 24) || '';
98
+ const location = app.location?.substring(0, 14) || '';
99
+ const runtime =
100
+ app.siteConfig?.linuxFxVersion?.substring(0, 14) ||
101
+ app.siteConfig?.windowsFxVersion?.substring(0, 14) ||
102
+ 'N/A';
103
+ const state = app.state || '';
104
+
105
+ const stateColor = state === 'Running' ? 'green' : state === 'Stopped' ? 'red' : 'white';
106
+
107
+ ui.print(
108
+ `${name.padEnd(30)}${rg.padEnd(25)}${location.padEnd(15)}${runtime.padEnd(15)}${ui.color(state, stateColor as 'green' | 'red' | 'white')}`
109
+ );
110
+ }
111
+ } catch (error: unknown) {
112
+ ui.stopSpinnerFail('Failed to fetch function apps');
113
+ const message = error instanceof Error ? error.message : 'Unknown error';
114
+ logger.error('Failed to list function apps', { error: message });
115
+ ui.error(`Failed to list function apps: ${message}`);
116
+ }
117
+ }
118
+
119
+ /**
120
+ * Show Function App details
121
+ */
122
+ async function showFunctionApp(appName: string, options: AzureCommandOptions): Promise<void> {
123
+ ui.header(`Function App: ${appName}`);
124
+ ui.newLine();
125
+
126
+ if (!options.resourceGroup) {
127
+ ui.error('Resource group required (use -g)');
128
+ return;
129
+ }
130
+
131
+ const azArgs = ['functionapp', 'show', '-n', appName, '-g', options.resourceGroup, '-o', 'json'];
132
+ if (options.subscription) {
133
+ azArgs.push('--subscription', options.subscription);
134
+ }
135
+
136
+ try {
137
+ ui.startSpinner({ message: 'Fetching function app details...' });
138
+ const { stdout } = await execFileAsync('az', azArgs);
139
+ ui.stopSpinnerSuccess('Details fetched');
140
+
141
+ const app = JSON.parse(stdout);
142
+
143
+ ui.print(ui.bold('Basic Information:'));
144
+ ui.print(` Name: ${app.name}`);
145
+ ui.print(` Resource Group: ${app.resourceGroup}`);
146
+ ui.print(` Location: ${app.location}`);
147
+ ui.print(` State: ${app.state}`);
148
+ ui.print(` Default Host: ${app.defaultHostName}`);
149
+ ui.newLine();
150
+
151
+ ui.print(ui.bold('Configuration:'));
152
+ ui.print(
153
+ ` Runtime: ${app.siteConfig?.linuxFxVersion || app.siteConfig?.windowsFxVersion || 'N/A'}`
154
+ );
155
+ ui.print(` Node Version: ${app.siteConfig?.nodeVersion || 'N/A'}`);
156
+ ui.print(` Python Version: ${app.siteConfig?.pythonVersion || 'N/A'}`);
157
+ ui.print(` HTTPS Only: ${app.httpsOnly ? 'Yes' : 'No'}`);
158
+ ui.newLine();
159
+
160
+ ui.print(ui.bold('Resources:'));
161
+ ui.print(` App Service Plan: ${app.serverFarmId?.split('/').pop() || 'Consumption'}`);
162
+ ui.print(` Kind: ${app.kind}`);
163
+ ui.newLine();
164
+
165
+ ui.print(ui.bold('Outbound IPs:'));
166
+ const outboundIps = app.outboundIpAddresses?.split(',') || [];
167
+ for (const ip of outboundIps.slice(0, 5)) {
168
+ ui.print(` - ${ip}`);
169
+ }
170
+ if (outboundIps.length > 5) {
171
+ ui.print(ui.dim(` ... and ${outboundIps.length - 5} more`));
172
+ }
173
+ } catch (error: unknown) {
174
+ ui.stopSpinnerFail('Failed to fetch details');
175
+ const message = error instanceof Error ? error.message : 'Unknown error';
176
+ logger.error('Failed to show function app', { error: message });
177
+ ui.error(`Failed to show function app: ${message}`);
178
+ }
179
+ }
180
+
181
+ /**
182
+ * List functions in a Function App
183
+ */
184
+ async function listFunctions(appName: string, options: AzureCommandOptions): Promise<void> {
185
+ ui.header(`Functions in ${appName}`);
186
+ ui.newLine();
187
+
188
+ if (!options.resourceGroup) {
189
+ ui.error('Resource group required (use -g)');
190
+ return;
191
+ }
192
+
193
+ const azArgs = [
194
+ 'functionapp',
195
+ 'function',
196
+ 'list',
197
+ '-n',
198
+ appName,
199
+ '-g',
200
+ options.resourceGroup,
201
+ '-o',
202
+ 'json',
203
+ ];
204
+ if (options.subscription) {
205
+ azArgs.push('--subscription', options.subscription);
206
+ }
207
+
208
+ try {
209
+ ui.startSpinner({ message: 'Fetching functions...' });
210
+ const { stdout } = await execFileAsync('az', azArgs);
211
+ ui.stopSpinnerSuccess('Functions fetched');
212
+
213
+ const functions = JSON.parse(stdout || '[]');
214
+
215
+ if (functions.length === 0) {
216
+ ui.info('No functions found');
217
+ return;
218
+ }
219
+
220
+ ui.print(`Found ${functions.length} function(s)\n`);
221
+
222
+ for (const fn of functions) {
223
+ const name = fn.name?.split('/').pop() || fn.name;
224
+ const enabled = fn.isDisabled === false;
225
+
226
+ ui.print(
227
+ ` ${ui.color(name, 'cyan')} ${enabled ? ui.color('[enabled]', 'green') : ui.color('[disabled]', 'red')}`
228
+ );
229
+
230
+ // Show config if available
231
+ if (fn.config?.bindings) {
232
+ for (const binding of fn.config.bindings) {
233
+ ui.print(ui.dim(` ${binding.direction}: ${binding.type}`));
234
+ }
235
+ }
236
+ }
237
+ } catch (error: unknown) {
238
+ ui.stopSpinnerFail('Failed to fetch functions');
239
+ const message = error instanceof Error ? error.message : 'Unknown error';
240
+ logger.error('Failed to list functions', { error: message });
241
+ ui.error(`Failed to list functions: ${message}`);
242
+ }
243
+ }
244
+
245
+ /**
246
+ * Show Functions help
247
+ */
248
+ function showFunctionsHelp(): void {
249
+ ui.print(ui.bold('Azure Functions Commands:'));
250
+ ui.print(' list List all function apps');
251
+ ui.print(' show <name> -g <rg> Show function app details');
252
+ ui.print(' functions <name> -g <rg> List functions in app');
253
+ }