@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,952 @@
1
+ /**
2
+ * AWS Operations — Embedded tool (stripped HTTP wrappers)
3
+ *
4
+ * Merged from services/aws-tools-service/src/aws/ec2.ts, s3.ts, iam.ts
5
+ * Uses lazy imports for AWS SDK to keep binary size small.
6
+ */
7
+
8
+ import { logger } from '../utils';
9
+
10
+ // ==========================================
11
+ // Shared Types
12
+ // ==========================================
13
+
14
+ export interface OperationResult<T = any> {
15
+ success: boolean;
16
+ data?: T;
17
+ error?: string;
18
+ }
19
+
20
+ export interface AwsConfig {
21
+ region?: string;
22
+ accessKeyId?: string;
23
+ secretAccessKey?: string;
24
+ sessionToken?: string;
25
+ }
26
+
27
+ // ==========================================
28
+ // EC2 Types
29
+ // ==========================================
30
+
31
+ export interface ListInstancesOptions {
32
+ instanceIds?: string[];
33
+ filters?: Record<string, string[]>;
34
+ maxResults?: number;
35
+ nextToken?: string;
36
+ }
37
+
38
+ export interface RunInstanceOptions {
39
+ imageId: string;
40
+ instanceType: string;
41
+ minCount?: number;
42
+ maxCount?: number;
43
+ keyName?: string;
44
+ securityGroupIds?: string[];
45
+ subnetId?: string;
46
+ userData?: string;
47
+ tags?: Record<string, string>;
48
+ ebsOptimized?: boolean;
49
+ monitoring?: boolean;
50
+ }
51
+
52
+ // ==========================================
53
+ // S3 Types
54
+ // ==========================================
55
+
56
+ export interface ListObjectsOptions {
57
+ bucket: string;
58
+ prefix?: string;
59
+ delimiter?: string;
60
+ maxKeys?: number;
61
+ continuationToken?: string;
62
+ }
63
+
64
+ export interface PutObjectOptions {
65
+ bucket: string;
66
+ key: string;
67
+ body: string | Buffer;
68
+ contentType?: string;
69
+ metadata?: Record<string, string>;
70
+ tags?: Record<string, string>;
71
+ }
72
+
73
+ export interface CopyObjectOptions {
74
+ sourceBucket: string;
75
+ sourceKey: string;
76
+ destinationBucket: string;
77
+ destinationKey: string;
78
+ }
79
+
80
+ // ==========================================
81
+ // IAM Types
82
+ // ==========================================
83
+
84
+ export interface IAMListOptions {
85
+ maxItems?: number;
86
+ marker?: string;
87
+ pathPrefix?: string;
88
+ }
89
+
90
+ export interface CreateRoleOptions {
91
+ roleName: string;
92
+ assumeRolePolicyDocument: string;
93
+ description?: string;
94
+ path?: string;
95
+ maxSessionDuration?: number;
96
+ tags?: Record<string, string>;
97
+ }
98
+
99
+ export interface CreatePolicyOptions {
100
+ policyName: string;
101
+ policyDocument: string;
102
+ description?: string;
103
+ path?: string;
104
+ tags?: Record<string, string>;
105
+ }
106
+
107
+ /**
108
+ * Unified AWS Operations class merging EC2, S3, and IAM operations.
109
+ * All AWS SDK imports are lazy to minimize binary size.
110
+ */
111
+ export class AwsOperations {
112
+ private config: AwsConfig;
113
+
114
+ constructor(config: AwsConfig = {}) {
115
+ this.config = {
116
+ region: config.region || process.env.AWS_REGION || 'us-east-1',
117
+ accessKeyId: config.accessKeyId,
118
+ secretAccessKey: config.secretAccessKey,
119
+ sessionToken: config.sessionToken,
120
+ };
121
+ }
122
+
123
+ /**
124
+ * Build common client config
125
+ */
126
+ private getClientConfig(): Record<string, any> {
127
+ const clientConfig: Record<string, any> = {
128
+ region: this.config.region,
129
+ };
130
+
131
+ if (this.config.accessKeyId && this.config.secretAccessKey) {
132
+ clientConfig.credentials = {
133
+ accessKeyId: this.config.accessKeyId,
134
+ secretAccessKey: this.config.secretAccessKey,
135
+ sessionToken: this.config.sessionToken,
136
+ };
137
+ }
138
+
139
+ return clientConfig;
140
+ }
141
+
142
+ // ==========================================
143
+ // EC2 Operations
144
+ // ==========================================
145
+
146
+ /**
147
+ * List EC2 instances
148
+ */
149
+ async listInstances(options: ListInstancesOptions = {}): Promise<OperationResult> {
150
+ try {
151
+ const { EC2Client, DescribeInstancesCommand } = await import('@aws-sdk/client-ec2');
152
+ const client = new EC2Client(this.getClientConfig());
153
+
154
+ const input: any = {};
155
+
156
+ if (options.instanceIds && options.instanceIds.length > 0) {
157
+ input.InstanceIds = options.instanceIds;
158
+ }
159
+
160
+ if (options.filters) {
161
+ input.Filters = Object.entries(options.filters).map(([name, values]) => ({
162
+ Name: name,
163
+ Values: values,
164
+ }));
165
+ }
166
+
167
+ if (options.maxResults) {
168
+ input.MaxResults = options.maxResults;
169
+ }
170
+
171
+ if (options.nextToken) {
172
+ input.NextToken = options.nextToken;
173
+ }
174
+
175
+ const command = new DescribeInstancesCommand(input);
176
+ const response = await client.send(command);
177
+
178
+ const instances = response.Reservations?.flatMap(
179
+ (r: any) =>
180
+ r.Instances?.map((i: any) => ({
181
+ instanceId: i.InstanceId,
182
+ instanceType: i.InstanceType,
183
+ state: i.State?.Name,
184
+ publicIpAddress: i.PublicIpAddress,
185
+ privateIpAddress: i.PrivateIpAddress,
186
+ launchTime: i.LaunchTime,
187
+ availabilityZone: i.Placement?.AvailabilityZone,
188
+ vpcId: i.VpcId,
189
+ subnetId: i.SubnetId,
190
+ imageId: i.ImageId,
191
+ keyName: i.KeyName,
192
+ tags: i.Tags?.reduce(
193
+ (acc: any, tag: any) => {
194
+ if (tag.Key) {
195
+ acc[tag.Key] = tag.Value || '';
196
+ }
197
+ return acc;
198
+ },
199
+ {} as Record<string, string>
200
+ ),
201
+ })) || []
202
+ );
203
+
204
+ return {
205
+ success: true,
206
+ data: {
207
+ instances: instances || [],
208
+ nextToken: response.NextToken,
209
+ },
210
+ };
211
+ } catch (error: any) {
212
+ logger.error('Failed to list instances', error);
213
+ return { success: false, error: error.message };
214
+ }
215
+ }
216
+
217
+ /**
218
+ * Start EC2 instances
219
+ */
220
+ async startInstances(instanceIds: string[]): Promise<OperationResult> {
221
+ try {
222
+ const { EC2Client, StartInstancesCommand } = await import('@aws-sdk/client-ec2');
223
+ const client = new EC2Client(this.getClientConfig());
224
+
225
+ const command = new StartInstancesCommand({ InstanceIds: instanceIds });
226
+ const response = await client.send(command);
227
+
228
+ const results = response.StartingInstances?.map((i: any) => ({
229
+ instanceId: i.InstanceId,
230
+ previousState: i.PreviousState?.Name,
231
+ currentState: i.CurrentState?.Name,
232
+ }));
233
+
234
+ return { success: true, data: { instances: results || [] } };
235
+ } catch (error: any) {
236
+ logger.error('Failed to start instances', error);
237
+ return { success: false, error: error.message };
238
+ }
239
+ }
240
+
241
+ /**
242
+ * Start a single instance (convenience wrapper)
243
+ */
244
+ async startInstance(instanceId: string): Promise<OperationResult> {
245
+ return this.startInstances([instanceId]);
246
+ }
247
+
248
+ /**
249
+ * Stop EC2 instances
250
+ */
251
+ async stopInstances(instanceIds: string[], force?: boolean): Promise<OperationResult> {
252
+ try {
253
+ const { EC2Client, StopInstancesCommand } = await import('@aws-sdk/client-ec2');
254
+ const client = new EC2Client(this.getClientConfig());
255
+
256
+ const command = new StopInstancesCommand({ InstanceIds: instanceIds, Force: force });
257
+ const response = await client.send(command);
258
+
259
+ const results = response.StoppingInstances?.map((i: any) => ({
260
+ instanceId: i.InstanceId,
261
+ previousState: i.PreviousState?.Name,
262
+ currentState: i.CurrentState?.Name,
263
+ }));
264
+
265
+ return { success: true, data: { instances: results || [] } };
266
+ } catch (error: any) {
267
+ logger.error('Failed to stop instances', error);
268
+ return { success: false, error: error.message };
269
+ }
270
+ }
271
+
272
+ /**
273
+ * Stop a single instance (convenience wrapper)
274
+ */
275
+ async stopInstance(instanceId: string, force?: boolean): Promise<OperationResult> {
276
+ return this.stopInstances([instanceId], force);
277
+ }
278
+
279
+ /**
280
+ * Reboot EC2 instances
281
+ */
282
+ async rebootInstances(instanceIds: string[]): Promise<OperationResult> {
283
+ try {
284
+ const { EC2Client, RebootInstancesCommand } = await import('@aws-sdk/client-ec2');
285
+ const client = new EC2Client(this.getClientConfig());
286
+
287
+ const command = new RebootInstancesCommand({ InstanceIds: instanceIds });
288
+ await client.send(command);
289
+
290
+ return {
291
+ success: true,
292
+ data: { message: `Reboot initiated for instances: ${instanceIds.join(', ')}` },
293
+ };
294
+ } catch (error: any) {
295
+ logger.error('Failed to reboot instances', error);
296
+ return { success: false, error: error.message };
297
+ }
298
+ }
299
+
300
+ /**
301
+ * Terminate EC2 instances
302
+ */
303
+ async terminateInstances(instanceIds: string[]): Promise<OperationResult> {
304
+ try {
305
+ const { EC2Client, TerminateInstancesCommand } = await import('@aws-sdk/client-ec2');
306
+ const client = new EC2Client(this.getClientConfig());
307
+
308
+ const command = new TerminateInstancesCommand({ InstanceIds: instanceIds });
309
+ const response = await client.send(command);
310
+
311
+ const results = response.TerminatingInstances?.map((i: any) => ({
312
+ instanceId: i.InstanceId,
313
+ previousState: i.PreviousState?.Name,
314
+ currentState: i.CurrentState?.Name,
315
+ }));
316
+
317
+ return { success: true, data: { instances: results || [] } };
318
+ } catch (error: any) {
319
+ logger.error('Failed to terminate instances', error);
320
+ return { success: false, error: error.message };
321
+ }
322
+ }
323
+
324
+ /**
325
+ * Terminate a single instance (convenience wrapper)
326
+ */
327
+ async terminateInstance(instanceId: string): Promise<OperationResult> {
328
+ return this.terminateInstances([instanceId]);
329
+ }
330
+
331
+ /**
332
+ * Describe instance status
333
+ */
334
+ async getInstanceStatus(instanceIds: string[]): Promise<OperationResult> {
335
+ try {
336
+ const { EC2Client, DescribeInstanceStatusCommand } = await import('@aws-sdk/client-ec2');
337
+ const client = new EC2Client(this.getClientConfig());
338
+
339
+ const command = new DescribeInstanceStatusCommand({
340
+ InstanceIds: instanceIds,
341
+ IncludeAllInstances: true,
342
+ });
343
+ const response = await client.send(command);
344
+
345
+ const statuses = response.InstanceStatuses?.map((s: any) => ({
346
+ instanceId: s.InstanceId,
347
+ instanceState: s.InstanceState?.Name,
348
+ instanceStatus: s.InstanceStatus?.Status,
349
+ systemStatus: s.SystemStatus?.Status,
350
+ availabilityZone: s.AvailabilityZone,
351
+ }));
352
+
353
+ return { success: true, data: { statuses: statuses || [] } };
354
+ } catch (error: any) {
355
+ logger.error('Failed to get instance status', error);
356
+ return { success: false, error: error.message };
357
+ }
358
+ }
359
+
360
+ /**
361
+ * List VPCs
362
+ */
363
+ async listVpcs(): Promise<OperationResult> {
364
+ try {
365
+ const { EC2Client, DescribeVpcsCommand } = await import('@aws-sdk/client-ec2');
366
+ const client = new EC2Client(this.getClientConfig());
367
+
368
+ const command = new DescribeVpcsCommand({});
369
+ const response = await client.send(command);
370
+
371
+ const vpcs = response.Vpcs?.map((v: any) => ({
372
+ vpcId: v.VpcId,
373
+ cidrBlock: v.CidrBlock,
374
+ state: v.State,
375
+ isDefault: v.IsDefault,
376
+ tags: v.Tags?.reduce(
377
+ (acc: any, tag: any) => {
378
+ if (tag.Key) {
379
+ acc[tag.Key] = tag.Value || '';
380
+ }
381
+ return acc;
382
+ },
383
+ {} as Record<string, string>
384
+ ),
385
+ }));
386
+
387
+ return { success: true, data: { vpcs: vpcs || [] } };
388
+ } catch (error: any) {
389
+ logger.error('Failed to list VPCs', error);
390
+ return { success: false, error: error.message };
391
+ }
392
+ }
393
+
394
+ /**
395
+ * List subnets
396
+ */
397
+ async listSubnets(vpcId?: string): Promise<OperationResult> {
398
+ try {
399
+ const { EC2Client, DescribeSubnetsCommand } = await import('@aws-sdk/client-ec2');
400
+ const client = new EC2Client(this.getClientConfig());
401
+
402
+ const input: any = {};
403
+ if (vpcId) {
404
+ input.Filters = [{ Name: 'vpc-id', Values: [vpcId] }];
405
+ }
406
+
407
+ const command = new DescribeSubnetsCommand(input);
408
+ const response = await client.send(command);
409
+
410
+ const subnets = response.Subnets?.map((s: any) => ({
411
+ subnetId: s.SubnetId,
412
+ vpcId: s.VpcId,
413
+ cidrBlock: s.CidrBlock,
414
+ availabilityZone: s.AvailabilityZone,
415
+ availableIpAddressCount: s.AvailableIpAddressCount,
416
+ defaultForAz: s.DefaultForAz,
417
+ tags: s.Tags?.reduce(
418
+ (acc: any, tag: any) => {
419
+ if (tag.Key) {
420
+ acc[tag.Key] = tag.Value || '';
421
+ }
422
+ return acc;
423
+ },
424
+ {} as Record<string, string>
425
+ ),
426
+ }));
427
+
428
+ return { success: true, data: { subnets: subnets || [] } };
429
+ } catch (error: any) {
430
+ logger.error('Failed to list subnets', error);
431
+ return { success: false, error: error.message };
432
+ }
433
+ }
434
+
435
+ /**
436
+ * List security groups
437
+ */
438
+ async listSecurityGroups(vpcId?: string): Promise<OperationResult> {
439
+ try {
440
+ const { EC2Client, DescribeSecurityGroupsCommand } = await import('@aws-sdk/client-ec2');
441
+ const client = new EC2Client(this.getClientConfig());
442
+
443
+ const input: any = {};
444
+ if (vpcId) {
445
+ input.Filters = [{ Name: 'vpc-id', Values: [vpcId] }];
446
+ }
447
+
448
+ const command = new DescribeSecurityGroupsCommand(input);
449
+ const response = await client.send(command);
450
+
451
+ const groups = response.SecurityGroups?.map((g: any) => ({
452
+ groupId: g.GroupId,
453
+ groupName: g.GroupName,
454
+ description: g.Description,
455
+ vpcId: g.VpcId,
456
+ }));
457
+
458
+ return { success: true, data: { securityGroups: groups || [] } };
459
+ } catch (error: any) {
460
+ logger.error('Failed to list security groups', error);
461
+ return { success: false, error: error.message };
462
+ }
463
+ }
464
+
465
+ /**
466
+ * List regions
467
+ */
468
+ async listRegions(): Promise<OperationResult> {
469
+ try {
470
+ const { EC2Client, DescribeRegionsCommand } = await import('@aws-sdk/client-ec2');
471
+ const client = new EC2Client(this.getClientConfig());
472
+
473
+ const command = new DescribeRegionsCommand({});
474
+ const response = await client.send(command);
475
+
476
+ const regions = response.Regions?.map((r: any) => ({
477
+ regionName: r.RegionName,
478
+ endpoint: r.Endpoint,
479
+ }));
480
+
481
+ return { success: true, data: { regions: regions || [] } };
482
+ } catch (error: any) {
483
+ logger.error('Failed to list regions', error);
484
+ return { success: false, error: error.message };
485
+ }
486
+ }
487
+
488
+ // ==========================================
489
+ // S3 Operations
490
+ // ==========================================
491
+
492
+ /**
493
+ * List all S3 buckets
494
+ */
495
+ async listBuckets(): Promise<OperationResult> {
496
+ try {
497
+ const { S3Client, ListBucketsCommand } = await import('@aws-sdk/client-s3');
498
+ const client = new S3Client(this.getClientConfig());
499
+
500
+ const command = new ListBucketsCommand({});
501
+ const response = await client.send(command);
502
+
503
+ const buckets = response.Buckets?.map((b: any) => ({
504
+ name: b.Name,
505
+ creationDate: b.CreationDate,
506
+ }));
507
+
508
+ return {
509
+ success: true,
510
+ data: {
511
+ buckets: buckets || [],
512
+ owner: {
513
+ id: response.Owner?.ID,
514
+ displayName: response.Owner?.DisplayName,
515
+ },
516
+ },
517
+ };
518
+ } catch (error: any) {
519
+ logger.error('Failed to list buckets', error);
520
+ return { success: false, error: error.message };
521
+ }
522
+ }
523
+
524
+ /**
525
+ * List objects in a bucket
526
+ */
527
+ async listObjects(options: ListObjectsOptions): Promise<OperationResult> {
528
+ try {
529
+ const { S3Client, ListObjectsV2Command } = await import('@aws-sdk/client-s3');
530
+ const client = new S3Client(this.getClientConfig());
531
+
532
+ const input: any = { Bucket: options.bucket };
533
+
534
+ if (options.prefix) {
535
+ input.Prefix = options.prefix;
536
+ }
537
+ if (options.delimiter) {
538
+ input.Delimiter = options.delimiter;
539
+ }
540
+ if (options.maxKeys) {
541
+ input.MaxKeys = options.maxKeys;
542
+ }
543
+ if (options.continuationToken) {
544
+ input.ContinuationToken = options.continuationToken;
545
+ }
546
+
547
+ const command = new ListObjectsV2Command(input);
548
+ const response = await client.send(command);
549
+
550
+ const objects = response.Contents?.map((o: any) => ({
551
+ key: o.Key,
552
+ size: o.Size,
553
+ lastModified: o.LastModified,
554
+ etag: o.ETag,
555
+ storageClass: o.StorageClass,
556
+ }));
557
+
558
+ const commonPrefixes = response.CommonPrefixes?.map((p: any) => p.Prefix);
559
+
560
+ return {
561
+ success: true,
562
+ data: {
563
+ objects: objects || [],
564
+ commonPrefixes: commonPrefixes || [],
565
+ isTruncated: response.IsTruncated,
566
+ nextContinuationToken: response.NextContinuationToken,
567
+ keyCount: response.KeyCount,
568
+ },
569
+ };
570
+ } catch (error: any) {
571
+ logger.error('Failed to list objects', error);
572
+ return { success: false, error: error.message };
573
+ }
574
+ }
575
+
576
+ /**
577
+ * Get object from bucket
578
+ */
579
+ async getObject(bucket: string, key: string): Promise<OperationResult> {
580
+ try {
581
+ const { S3Client, GetObjectCommand } = await import('@aws-sdk/client-s3');
582
+ const client = new S3Client(this.getClientConfig());
583
+
584
+ const command = new GetObjectCommand({ Bucket: bucket, Key: key });
585
+ const response = await client.send(command);
586
+
587
+ const body = await response.Body?.transformToString();
588
+
589
+ return {
590
+ success: true,
591
+ data: {
592
+ body,
593
+ contentType: response.ContentType,
594
+ contentLength: response.ContentLength,
595
+ lastModified: response.LastModified,
596
+ etag: response.ETag,
597
+ metadata: response.Metadata,
598
+ },
599
+ };
600
+ } catch (error: any) {
601
+ logger.error('Failed to get object', error);
602
+ return { success: false, error: error.message };
603
+ }
604
+ }
605
+
606
+ /**
607
+ * Put object to bucket
608
+ */
609
+ async putObject(options: PutObjectOptions): Promise<OperationResult> {
610
+ try {
611
+ const { S3Client, PutObjectCommand } = await import('@aws-sdk/client-s3');
612
+ const client = new S3Client(this.getClientConfig());
613
+
614
+ const command = new PutObjectCommand({
615
+ Bucket: options.bucket,
616
+ Key: options.key,
617
+ Body: options.body,
618
+ ContentType: options.contentType,
619
+ Metadata: options.metadata,
620
+ Tagging: options.tags
621
+ ? Object.entries(options.tags)
622
+ .map(([k, v]) => `${encodeURIComponent(k)}=${encodeURIComponent(v)}`)
623
+ .join('&')
624
+ : undefined,
625
+ });
626
+
627
+ const response = await client.send(command);
628
+
629
+ return {
630
+ success: true,
631
+ data: { etag: response.ETag, versionId: response.VersionId },
632
+ };
633
+ } catch (error: any) {
634
+ logger.error('Failed to put object', error);
635
+ return { success: false, error: error.message };
636
+ }
637
+ }
638
+
639
+ /**
640
+ * Delete object from bucket
641
+ */
642
+ async deleteObject(bucket: string, key: string): Promise<OperationResult> {
643
+ try {
644
+ const { S3Client, DeleteObjectCommand } = await import('@aws-sdk/client-s3');
645
+ const client = new S3Client(this.getClientConfig());
646
+
647
+ const command = new DeleteObjectCommand({ Bucket: bucket, Key: key });
648
+ const response = await client.send(command);
649
+
650
+ return {
651
+ success: true,
652
+ data: { deleteMarker: response.DeleteMarker, versionId: response.VersionId },
653
+ };
654
+ } catch (error: any) {
655
+ logger.error('Failed to delete object', error);
656
+ return { success: false, error: error.message };
657
+ }
658
+ }
659
+
660
+ /**
661
+ * Create a bucket
662
+ */
663
+ async createBucket(bucket: string, region?: string): Promise<OperationResult> {
664
+ try {
665
+ const { S3Client, CreateBucketCommand } = await import('@aws-sdk/client-s3');
666
+ const targetRegion = region || this.config.region || 'us-east-1';
667
+ const client = new S3Client({ ...this.getClientConfig(), region: targetRegion });
668
+
669
+ const command = new CreateBucketCommand({
670
+ Bucket: bucket,
671
+ CreateBucketConfiguration:
672
+ targetRegion !== 'us-east-1' ? { LocationConstraint: targetRegion as any } : undefined,
673
+ });
674
+
675
+ await client.send(command);
676
+
677
+ return { success: true, data: { bucket, region: targetRegion } };
678
+ } catch (error: any) {
679
+ logger.error('Failed to create bucket', error);
680
+ return { success: false, error: error.message };
681
+ }
682
+ }
683
+
684
+ /**
685
+ * Delete a bucket
686
+ */
687
+ async deleteBucket(bucket: string): Promise<OperationResult> {
688
+ try {
689
+ const { S3Client, DeleteBucketCommand } = await import('@aws-sdk/client-s3');
690
+ const client = new S3Client(this.getClientConfig());
691
+
692
+ const command = new DeleteBucketCommand({ Bucket: bucket });
693
+ await client.send(command);
694
+
695
+ return { success: true, data: { message: `Bucket ${bucket} deleted` } };
696
+ } catch (error: any) {
697
+ logger.error('Failed to delete bucket', error);
698
+ return { success: false, error: error.message };
699
+ }
700
+ }
701
+
702
+ // ==========================================
703
+ // IAM Operations
704
+ // ==========================================
705
+
706
+ /**
707
+ * List IAM users
708
+ */
709
+ async listUsers(options: IAMListOptions = {}): Promise<OperationResult> {
710
+ try {
711
+ const { IAMClient, ListUsersCommand } = await import('@aws-sdk/client-iam');
712
+ const client = new IAMClient(this.getClientConfig());
713
+
714
+ const input: any = {};
715
+ if (options.maxItems) {
716
+ input.MaxItems = options.maxItems;
717
+ }
718
+ if (options.marker) {
719
+ input.Marker = options.marker;
720
+ }
721
+ if (options.pathPrefix) {
722
+ input.PathPrefix = options.pathPrefix;
723
+ }
724
+
725
+ const command = new ListUsersCommand(input);
726
+ const response = await client.send(command);
727
+
728
+ const users = response.Users?.map((u: any) => ({
729
+ userName: u.UserName,
730
+ userId: u.UserId,
731
+ arn: u.Arn,
732
+ path: u.Path,
733
+ createDate: u.CreateDate,
734
+ passwordLastUsed: u.PasswordLastUsed,
735
+ }));
736
+
737
+ return {
738
+ success: true,
739
+ data: {
740
+ users: users || [],
741
+ isTruncated: response.IsTruncated,
742
+ marker: response.Marker,
743
+ },
744
+ };
745
+ } catch (error: any) {
746
+ logger.error('Failed to list users', error);
747
+ return { success: false, error: error.message };
748
+ }
749
+ }
750
+
751
+ /**
752
+ * List IAM roles
753
+ */
754
+ async listRoles(options: IAMListOptions = {}): Promise<OperationResult> {
755
+ try {
756
+ const { IAMClient, ListRolesCommand } = await import('@aws-sdk/client-iam');
757
+ const client = new IAMClient(this.getClientConfig());
758
+
759
+ const input: any = {};
760
+ if (options.maxItems) {
761
+ input.MaxItems = options.maxItems;
762
+ }
763
+ if (options.marker) {
764
+ input.Marker = options.marker;
765
+ }
766
+ if (options.pathPrefix) {
767
+ input.PathPrefix = options.pathPrefix;
768
+ }
769
+
770
+ const command = new ListRolesCommand(input);
771
+ const response = await client.send(command);
772
+
773
+ const roles = response.Roles?.map((r: any) => ({
774
+ roleName: r.RoleName,
775
+ roleId: r.RoleId,
776
+ arn: r.Arn,
777
+ path: r.Path,
778
+ createDate: r.CreateDate,
779
+ description: r.Description,
780
+ maxSessionDuration: r.MaxSessionDuration,
781
+ }));
782
+
783
+ return {
784
+ success: true,
785
+ data: {
786
+ roles: roles || [],
787
+ isTruncated: response.IsTruncated,
788
+ marker: response.Marker,
789
+ },
790
+ };
791
+ } catch (error: any) {
792
+ logger.error('Failed to list roles', error);
793
+ return { success: false, error: error.message };
794
+ }
795
+ }
796
+
797
+ /**
798
+ * List IAM policies
799
+ */
800
+ async listPolicies(
801
+ options: IAMListOptions & { scope?: 'All' | 'AWS' | 'Local'; onlyAttached?: boolean } = {}
802
+ ): Promise<OperationResult> {
803
+ try {
804
+ const { IAMClient, ListPoliciesCommand } = await import('@aws-sdk/client-iam');
805
+ const client = new IAMClient(this.getClientConfig());
806
+
807
+ const command = new ListPoliciesCommand({
808
+ MaxItems: options.maxItems,
809
+ Marker: options.marker,
810
+ PathPrefix: options.pathPrefix,
811
+ Scope: options.scope,
812
+ OnlyAttached: options.onlyAttached,
813
+ });
814
+
815
+ const response = await client.send(command);
816
+
817
+ const policies = response.Policies?.map((p: any) => ({
818
+ policyName: p.PolicyName,
819
+ policyId: p.PolicyId,
820
+ arn: p.Arn,
821
+ path: p.Path,
822
+ createDate: p.CreateDate,
823
+ updateDate: p.UpdateDate,
824
+ attachmentCount: p.AttachmentCount,
825
+ isAttachable: p.IsAttachable,
826
+ description: p.Description,
827
+ }));
828
+
829
+ return {
830
+ success: true,
831
+ data: {
832
+ policies: policies || [],
833
+ isTruncated: response.IsTruncated,
834
+ marker: response.Marker,
835
+ },
836
+ };
837
+ } catch (error: any) {
838
+ logger.error('Failed to list policies', error);
839
+ return { success: false, error: error.message };
840
+ }
841
+ }
842
+
843
+ /**
844
+ * List IAM groups
845
+ */
846
+ async listGroups(options: IAMListOptions = {}): Promise<OperationResult> {
847
+ try {
848
+ const { IAMClient, ListGroupsCommand } = await import('@aws-sdk/client-iam');
849
+ const client = new IAMClient(this.getClientConfig());
850
+
851
+ const command = new ListGroupsCommand({
852
+ MaxItems: options.maxItems,
853
+ Marker: options.marker,
854
+ PathPrefix: options.pathPrefix,
855
+ });
856
+
857
+ const response = await client.send(command);
858
+
859
+ const groups = response.Groups?.map((g: any) => ({
860
+ groupName: g.GroupName,
861
+ groupId: g.GroupId,
862
+ arn: g.Arn,
863
+ path: g.Path,
864
+ createDate: g.CreateDate,
865
+ }));
866
+
867
+ return {
868
+ success: true,
869
+ data: {
870
+ groups: groups || [],
871
+ isTruncated: response.IsTruncated,
872
+ marker: response.Marker,
873
+ },
874
+ };
875
+ } catch (error: any) {
876
+ logger.error('Failed to list groups', error);
877
+ return { success: false, error: error.message };
878
+ }
879
+ }
880
+
881
+ /**
882
+ * Get IAM user details
883
+ */
884
+ async getUser(userName?: string): Promise<OperationResult> {
885
+ try {
886
+ const { IAMClient, GetUserCommand } = await import('@aws-sdk/client-iam');
887
+ const client = new IAMClient(this.getClientConfig());
888
+
889
+ const command = new GetUserCommand(userName ? { UserName: userName } : {});
890
+ const response = await client.send(command);
891
+
892
+ const user = response.User;
893
+
894
+ return {
895
+ success: true,
896
+ data: {
897
+ userName: user?.UserName,
898
+ userId: user?.UserId,
899
+ arn: user?.Arn,
900
+ path: user?.Path,
901
+ createDate: user?.CreateDate,
902
+ passwordLastUsed: user?.PasswordLastUsed,
903
+ },
904
+ };
905
+ } catch (error: any) {
906
+ logger.error('Failed to get user', error);
907
+ return { success: false, error: error.message };
908
+ }
909
+ }
910
+
911
+ /**
912
+ * Attach policy to role
913
+ */
914
+ async attachRolePolicy(roleName: string, policyArn: string): Promise<OperationResult> {
915
+ try {
916
+ const { IAMClient, AttachRolePolicyCommand } = await import('@aws-sdk/client-iam');
917
+ const client = new IAMClient(this.getClientConfig());
918
+
919
+ const command = new AttachRolePolicyCommand({ RoleName: roleName, PolicyArn: policyArn });
920
+ await client.send(command);
921
+
922
+ return {
923
+ success: true,
924
+ data: { message: `Policy ${policyArn} attached to role ${roleName}` },
925
+ };
926
+ } catch (error: any) {
927
+ logger.error('Failed to attach role policy', error);
928
+ return { success: false, error: error.message };
929
+ }
930
+ }
931
+
932
+ /**
933
+ * Detach policy from role
934
+ */
935
+ async detachRolePolicy(roleName: string, policyArn: string): Promise<OperationResult> {
936
+ try {
937
+ const { IAMClient, DetachRolePolicyCommand } = await import('@aws-sdk/client-iam');
938
+ const client = new IAMClient(this.getClientConfig());
939
+
940
+ const command = new DetachRolePolicyCommand({ RoleName: roleName, PolicyArn: policyArn });
941
+ await client.send(command);
942
+
943
+ return {
944
+ success: true,
945
+ data: { message: `Policy ${policyArn} detached from role ${roleName}` },
946
+ };
947
+ } catch (error: any) {
948
+ logger.error('Failed to detach role policy', error);
949
+ return { success: false, error: error.message };
950
+ }
951
+ }
952
+ }