@mtndev/cli 1.1.0 → 2.0.1

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 (260) hide show
  1. package/CHANGELOG.md +91 -0
  2. package/cjs/__chunks/VH7oDXx4.cjs +28 -0
  3. package/cjs/commands/aws/ec2/allow-ip/handler.cjs +261 -0
  4. package/cjs/commands/aws/ec2/allow-ip/handler.d.ts +6 -0
  5. package/cjs/commands/aws/ec2/allow-ip/index.cjs +25 -0
  6. package/cjs/commands/aws/ec2/allow-ip/index.d.ts +3 -0
  7. package/cjs/commands/aws/ec2/allow-ip/schema.cjs +33 -0
  8. package/cjs/commands/aws/ec2/allow-ip/schema.d.ts +33 -0
  9. package/cjs/commands/aws/ec2/allow-ip/utils.cjs +379 -0
  10. package/cjs/commands/aws/ec2/allow-ip/utils.d.ts +189 -0
  11. package/cjs/commands/aws/iam/admin-group.cjs +111 -0
  12. package/cjs/commands/aws/iam/admin-group.d.ts +55 -0
  13. package/cjs/commands/aws/iam/break-glass/handler.cjs +127 -0
  14. package/cjs/commands/aws/iam/break-glass/handler.d.ts +7 -0
  15. package/cjs/commands/aws/iam/break-glass/index.cjs +7 -0
  16. package/cjs/commands/aws/iam/break-glass/index.d.ts +2 -0
  17. package/cjs/commands/aws/iam/break-glass/schema.cjs +21 -0
  18. package/cjs/commands/aws/iam/break-glass/schema.d.ts +25 -0
  19. package/cjs/commands/aws/iam/check-admin/handler.cjs +89 -0
  20. package/cjs/commands/aws/iam/check-admin/handler.d.ts +5 -0
  21. package/cjs/commands/aws/iam/check-admin/index.cjs +7 -0
  22. package/cjs/commands/aws/iam/check-admin/index.d.ts +2 -0
  23. package/cjs/commands/aws/iam/check-admin/schema.cjs +19 -0
  24. package/cjs/commands/aws/iam/check-admin/schema.d.ts +19 -0
  25. package/cjs/commands/aws/iam/policies/s3-restrictions-deny-non-s3-services.json +20 -0
  26. package/cjs/commands/aws/iam/policies/sts-federation.json +11 -0
  27. package/cjs/commands/aws/iam/policies/user-admins-deny-list.json +175 -0
  28. package/cjs/commands/aws/iam/policies/user-admins-deny-regions-list.json +41 -0
  29. package/cjs/commands/aws/iam/policies/user-admins-self-service-iam.json +56 -0
  30. package/cjs/commands/aws/iam/policies/user-admins-write-list.json +117 -0
  31. package/cjs/commands/aws/iam/remove-admin/handler.cjs +120 -0
  32. package/cjs/commands/aws/iam/remove-admin/handler.d.ts +6 -0
  33. package/cjs/commands/aws/iam/remove-admin/index.cjs +7 -0
  34. package/cjs/commands/aws/iam/remove-admin/index.d.ts +2 -0
  35. package/cjs/commands/aws/iam/remove-admin/schema.cjs +22 -0
  36. package/cjs/commands/aws/iam/remove-admin/schema.d.ts +28 -0
  37. package/cjs/commands/aws/iam/setup-acl/handler.cjs +227 -0
  38. package/cjs/commands/aws/iam/setup-acl/handler.d.ts +6 -0
  39. package/cjs/commands/aws/iam/setup-acl/index.cjs +13 -0
  40. package/cjs/commands/aws/iam/setup-acl/index.d.ts +3 -0
  41. package/cjs/commands/aws/iam/setup-acl/schema.cjs +19 -0
  42. package/cjs/commands/aws/iam/setup-acl/schema.d.ts +24 -0
  43. package/cjs/commands/aws/iam/setup-acl/utils.cjs +305 -0
  44. package/cjs/commands/aws/iam/setup-acl/utils.d.ts +61 -0
  45. package/cjs/commands/aws/iam/users/handler.cjs +59 -0
  46. package/cjs/commands/aws/iam/users/handler.d.ts +5 -0
  47. package/cjs/commands/aws/iam/users/index.cjs +18 -0
  48. package/cjs/commands/aws/iam/users/index.d.ts +3 -0
  49. package/cjs/commands/aws/iam/users/schema.cjs +17 -0
  50. package/cjs/commands/aws/iam/users/schema.d.ts +18 -0
  51. package/cjs/commands/aws/iam/users/utils.cjs +454 -0
  52. package/cjs/commands/aws/iam/users/utils.d.ts +119 -0
  53. package/cjs/commands/aws/index.cjs +223 -0
  54. package/cjs/commands/aws/index.d.ts +14 -0
  55. package/cjs/commands/aws/route53/add-record/handler.cjs +264 -0
  56. package/cjs/commands/aws/route53/add-record/handler.d.ts +36 -0
  57. package/cjs/commands/aws/route53/add-record/index.cjs +26 -0
  58. package/cjs/commands/aws/route53/add-record/index.d.ts +3 -0
  59. package/cjs/commands/aws/route53/add-record/schema.cjs +43 -0
  60. package/cjs/commands/aws/route53/add-record/schema.d.ts +45 -0
  61. package/cjs/commands/aws/route53/add-record/utils.cjs +404 -0
  62. package/cjs/commands/aws/route53/add-record/utils.d.ts +216 -0
  63. package/cjs/commands/aws/route53/remove-record/handler.cjs +114 -0
  64. package/cjs/commands/aws/route53/remove-record/handler.d.ts +6 -0
  65. package/cjs/commands/aws/route53/remove-record/index.cjs +5 -0
  66. package/cjs/commands/aws/route53/remove-record/index.d.ts +2 -0
  67. package/cjs/commands/aws/route53/remove-record/schema.cjs +22 -0
  68. package/cjs/commands/aws/route53/remove-record/schema.d.ts +30 -0
  69. package/cjs/commands/aws/schema.cjs +19 -0
  70. package/cjs/commands/aws/schema.d.ts +13 -0
  71. package/cjs/commands/aws/ses/send-email/handler.cjs +73 -0
  72. package/cjs/commands/aws/ses/send-email/handler.d.ts +5 -0
  73. package/cjs/commands/aws/ses/send-email/index.cjs +9 -0
  74. package/cjs/commands/aws/ses/send-email/index.d.ts +3 -0
  75. package/cjs/commands/aws/ses/send-email/schema.cjs +24 -0
  76. package/cjs/commands/aws/ses/send-email/schema.d.ts +39 -0
  77. package/cjs/commands/aws/ses/send-email/utils.cjs +87 -0
  78. package/cjs/commands/aws/ses/send-email/utils.d.ts +47 -0
  79. package/cjs/commands/aws/ses/smtp-password.cjs +72 -0
  80. package/cjs/commands/aws/ses/smtp-password.d.ts +21 -0
  81. package/cjs/commands/aws/utils/exec-aws.cjs +145 -0
  82. package/cjs/commands/aws/utils/exec-aws.d.ts +79 -0
  83. package/cjs/commands/aws/utils/resolve-policy-path.cjs +15 -0
  84. package/cjs/commands/aws/utils/resolve-policy-path.d.ts +8 -0
  85. package/cjs/commands/aws/utils/resolve-profile.cjs +93 -0
  86. package/cjs/commands/aws/utils/resolve-profile.d.ts +28 -0
  87. package/cjs/commands/aws/utils/tty.cjs +54 -0
  88. package/cjs/commands/aws/utils/tty.d.ts +24 -0
  89. package/cjs/commands/db/create-readonly-user/handler.cjs +254 -0
  90. package/cjs/commands/db/create-readonly-user/handler.d.ts +5 -0
  91. package/cjs/commands/db/create-readonly-user/index.cjs +18 -0
  92. package/cjs/commands/db/create-readonly-user/index.d.ts +3 -0
  93. package/cjs/commands/db/create-readonly-user/mysql.cjs +102 -0
  94. package/cjs/commands/db/create-readonly-user/mysql.d.ts +33 -0
  95. package/cjs/commands/db/create-readonly-user/postgres.cjs +209 -0
  96. package/cjs/commands/db/create-readonly-user/postgres.d.ts +31 -0
  97. package/cjs/commands/db/create-readonly-user/schema.cjs +39 -0
  98. package/cjs/commands/db/create-readonly-user/schema.d.ts +55 -0
  99. package/cjs/commands/db/create-readonly-user/utils.cjs +160 -0
  100. package/cjs/commands/db/create-readonly-user/utils.d.ts +112 -0
  101. package/cjs/commands/db/index.cjs +46 -0
  102. package/cjs/commands/db/index.d.ts +6 -0
  103. package/cjs/commands/doppler/handler.cjs +95 -9
  104. package/cjs/commands/doppler/index.cjs +33 -41
  105. package/cjs/commands/doppler/index.d.ts +1 -0
  106. package/cjs/commands/doppler/schema.cjs +130 -6
  107. package/cjs/commands/doppler/schema.d.ts +233 -17
  108. package/cjs/commands/doppler/set-secret.cjs +378 -0
  109. package/cjs/commands/doppler/set-secret.d.ts +113 -0
  110. package/cjs/commands/doppler/utils/exec.cjs +106 -12
  111. package/cjs/commands/doppler/utils.cjs +412 -30
  112. package/cjs/commands/doppler/utils.d.ts +32 -8
  113. package/cjs/commands/icons/handler.cjs +82 -8
  114. package/cjs/commands/icons/index.cjs +15 -31
  115. package/cjs/commands/icons/schema.cjs +101 -13
  116. package/cjs/commands/icons/schema.d.ts +1 -1
  117. package/cjs/commands/icons/utils.cjs +131 -13
  118. package/cjs/commands/index.cjs +54 -82
  119. package/cjs/commands/schema.cjs +25 -8
  120. package/cjs/index.cjs +3 -4
  121. package/esm/commands/aws/ec2/allow-ip/handler.d.ts +6 -0
  122. package/esm/commands/aws/ec2/allow-ip/handler.js +258 -0
  123. package/esm/commands/aws/ec2/allow-ip/index.d.ts +3 -0
  124. package/esm/commands/aws/ec2/allow-ip/index.js +4 -0
  125. package/esm/commands/aws/ec2/allow-ip/schema.d.ts +33 -0
  126. package/esm/commands/aws/ec2/allow-ip/schema.js +29 -0
  127. package/esm/commands/aws/ec2/allow-ip/utils.d.ts +189 -0
  128. package/esm/commands/aws/ec2/allow-ip/utils.js +363 -0
  129. package/esm/commands/aws/iam/admin-group.d.ts +55 -0
  130. package/esm/commands/aws/iam/admin-group.js +104 -0
  131. package/esm/commands/aws/iam/break-glass/handler.d.ts +7 -0
  132. package/esm/commands/aws/iam/break-glass/handler.js +124 -0
  133. package/esm/commands/aws/iam/break-glass/index.d.ts +2 -0
  134. package/esm/commands/aws/iam/break-glass/index.js +4 -0
  135. package/esm/commands/aws/iam/break-glass/schema.d.ts +25 -0
  136. package/esm/commands/aws/iam/break-glass/schema.js +19 -0
  137. package/esm/commands/aws/iam/check-admin/handler.d.ts +5 -0
  138. package/esm/commands/aws/iam/check-admin/handler.js +86 -0
  139. package/esm/commands/aws/iam/check-admin/index.d.ts +2 -0
  140. package/esm/commands/aws/iam/check-admin/index.js +4 -0
  141. package/esm/commands/aws/iam/check-admin/schema.d.ts +19 -0
  142. package/esm/commands/aws/iam/check-admin/schema.js +17 -0
  143. package/esm/commands/aws/iam/policies/s3-restrictions-deny-non-s3-services.json +20 -0
  144. package/esm/commands/aws/iam/policies/sts-federation.json +11 -0
  145. package/esm/commands/aws/iam/policies/user-admins-deny-list.json +175 -0
  146. package/esm/commands/aws/iam/policies/user-admins-deny-regions-list.json +41 -0
  147. package/esm/commands/aws/iam/policies/user-admins-self-service-iam.json +56 -0
  148. package/esm/commands/aws/iam/policies/user-admins-write-list.json +117 -0
  149. package/esm/commands/aws/iam/remove-admin/handler.d.ts +6 -0
  150. package/esm/commands/aws/iam/remove-admin/handler.js +117 -0
  151. package/esm/commands/aws/iam/remove-admin/index.d.ts +2 -0
  152. package/esm/commands/aws/iam/remove-admin/index.js +4 -0
  153. package/esm/commands/aws/iam/remove-admin/schema.d.ts +28 -0
  154. package/esm/commands/aws/iam/remove-admin/schema.js +20 -0
  155. package/esm/commands/aws/iam/setup-acl/handler.d.ts +6 -0
  156. package/esm/commands/aws/iam/setup-acl/handler.js +224 -0
  157. package/esm/commands/aws/iam/setup-acl/index.d.ts +3 -0
  158. package/esm/commands/aws/iam/setup-acl/index.js +4 -0
  159. package/esm/commands/aws/iam/setup-acl/schema.d.ts +24 -0
  160. package/esm/commands/aws/iam/setup-acl/schema.js +18 -0
  161. package/esm/commands/aws/iam/setup-acl/utils.d.ts +61 -0
  162. package/esm/commands/aws/iam/setup-acl/utils.js +298 -0
  163. package/esm/commands/aws/iam/users/handler.d.ts +5 -0
  164. package/esm/commands/aws/iam/users/handler.js +56 -0
  165. package/esm/commands/aws/iam/users/index.d.ts +3 -0
  166. package/esm/commands/aws/iam/users/index.js +4 -0
  167. package/esm/commands/aws/iam/users/schema.d.ts +18 -0
  168. package/esm/commands/aws/iam/users/schema.js +16 -0
  169. package/esm/commands/aws/iam/users/utils.d.ts +119 -0
  170. package/esm/commands/aws/iam/users/utils.js +440 -0
  171. package/esm/commands/aws/index.d.ts +14 -0
  172. package/esm/commands/aws/index.js +142 -0
  173. package/esm/commands/aws/route53/add-record/handler.d.ts +36 -0
  174. package/esm/commands/aws/route53/add-record/handler.js +258 -0
  175. package/esm/commands/aws/route53/add-record/index.d.ts +3 -0
  176. package/esm/commands/aws/route53/add-record/index.js +4 -0
  177. package/esm/commands/aws/route53/add-record/schema.d.ts +45 -0
  178. package/esm/commands/aws/route53/add-record/schema.js +40 -0
  179. package/esm/commands/aws/route53/add-record/utils.d.ts +216 -0
  180. package/esm/commands/aws/route53/add-record/utils.js +389 -0
  181. package/esm/commands/aws/route53/remove-record/handler.d.ts +6 -0
  182. package/esm/commands/aws/route53/remove-record/handler.js +111 -0
  183. package/esm/commands/aws/route53/remove-record/index.d.ts +2 -0
  184. package/esm/commands/aws/route53/remove-record/index.js +3 -0
  185. package/esm/commands/aws/route53/remove-record/schema.d.ts +30 -0
  186. package/esm/commands/aws/route53/remove-record/schema.js +21 -0
  187. package/esm/commands/aws/schema.d.ts +13 -0
  188. package/esm/commands/aws/schema.js +16 -0
  189. package/esm/commands/aws/ses/send-email/handler.d.ts +5 -0
  190. package/esm/commands/aws/ses/send-email/handler.js +70 -0
  191. package/esm/commands/aws/ses/send-email/index.d.ts +3 -0
  192. package/esm/commands/aws/ses/send-email/index.js +4 -0
  193. package/esm/commands/aws/ses/send-email/schema.d.ts +39 -0
  194. package/esm/commands/aws/ses/send-email/schema.js +23 -0
  195. package/esm/commands/aws/ses/send-email/utils.d.ts +47 -0
  196. package/esm/commands/aws/ses/send-email/utils.js +84 -0
  197. package/esm/commands/aws/ses/smtp-password.d.ts +21 -0
  198. package/esm/commands/aws/ses/smtp-password.js +69 -0
  199. package/esm/commands/aws/utils/exec-aws.d.ts +79 -0
  200. package/esm/commands/aws/utils/exec-aws.js +137 -0
  201. package/esm/commands/aws/utils/resolve-policy-path.d.ts +8 -0
  202. package/esm/commands/aws/utils/resolve-policy-path.js +14 -0
  203. package/esm/commands/aws/utils/resolve-profile.d.ts +28 -0
  204. package/esm/commands/aws/utils/resolve-profile.js +88 -0
  205. package/esm/commands/aws/utils/tty.d.ts +24 -0
  206. package/esm/commands/aws/utils/tty.js +51 -0
  207. package/esm/commands/db/create-readonly-user/handler.d.ts +5 -0
  208. package/esm/commands/db/create-readonly-user/handler.js +251 -0
  209. package/esm/commands/db/create-readonly-user/index.d.ts +3 -0
  210. package/esm/commands/db/create-readonly-user/index.js +4 -0
  211. package/esm/commands/db/create-readonly-user/mysql.d.ts +33 -0
  212. package/esm/commands/db/create-readonly-user/mysql.js +98 -0
  213. package/esm/commands/db/create-readonly-user/postgres.d.ts +31 -0
  214. package/esm/commands/db/create-readonly-user/postgres.js +205 -0
  215. package/esm/commands/db/create-readonly-user/schema.d.ts +55 -0
  216. package/esm/commands/db/create-readonly-user/schema.js +35 -0
  217. package/esm/commands/db/create-readonly-user/utils.d.ts +112 -0
  218. package/esm/commands/db/create-readonly-user/utils.js +151 -0
  219. package/esm/commands/db/index.d.ts +6 -0
  220. package/esm/commands/db/index.js +31 -0
  221. package/esm/commands/doppler/handler.js +92 -9
  222. package/esm/commands/doppler/index.d.ts +1 -0
  223. package/esm/commands/doppler/index.js +6 -41
  224. package/esm/commands/doppler/schema.d.ts +233 -17
  225. package/esm/commands/doppler/schema.js +125 -7
  226. package/esm/commands/doppler/set-secret.d.ts +113 -0
  227. package/esm/commands/doppler/set-secret.js +370 -0
  228. package/esm/commands/doppler/utils/exec.js +100 -12
  229. package/esm/commands/doppler/utils.d.ts +32 -8
  230. package/esm/commands/doppler/utils.js +396 -30
  231. package/esm/commands/icons/handler.js +79 -8
  232. package/esm/commands/icons/index.js +4 -31
  233. package/esm/commands/icons/schema.d.ts +1 -1
  234. package/esm/commands/icons/schema.js +97 -15
  235. package/esm/commands/icons/utils.js +125 -13
  236. package/esm/commands/index.js +49 -78
  237. package/esm/commands/schema.js +22 -7
  238. package/esm/index.js +3 -4
  239. package/package.json +12 -2
  240. package/vitest.config.ts +11 -0
  241. package/cjs/__chunks/4GFJUCSP.cjs +0 -52
  242. package/cjs/__chunks/4SFWHCB3.cjs +0 -133
  243. package/cjs/__chunks/B2KFW7T7.cjs +0 -1
  244. package/cjs/__chunks/EMMC33QA.cjs +0 -401
  245. package/cjs/__chunks/INFOPAHN.cjs +0 -117
  246. package/cjs/__chunks/J36P6UNE.cjs +0 -75
  247. package/cjs/__chunks/NIMXXHJQ.cjs +0 -92
  248. package/cjs/__chunks/NSRFG4DF.cjs +0 -100
  249. package/cjs/__chunks/WNJ3ZYLI.cjs +0 -1
  250. package/cjs/__chunks/ZMDIOZPT.cjs +0 -22
  251. package/esm/__chunks/46HGXFME.js +0 -0
  252. package/esm/__chunks/6G4PXXKF.js +0 -75
  253. package/esm/__chunks/6L5RNR7G.js +0 -100
  254. package/esm/__chunks/FL7AILGH.js +0 -133
  255. package/esm/__chunks/HJSXC6HP.js +0 -117
  256. package/esm/__chunks/O262GK4B.js +0 -401
  257. package/esm/__chunks/O3CCA76Q.js +0 -22
  258. package/esm/__chunks/SJXQJ7RS.js +0 -92
  259. package/esm/__chunks/SNCKIAOR.js +0 -0
  260. package/esm/__chunks/X3R5PSWH.js +0 -52
@@ -0,0 +1,363 @@
1
+ import { runAws } from "../../utils/exec-aws.js";
2
+ import { DEFAULT_ROAMING_NAME } from "./schema.js";
3
+ //#region src/commands/aws/ec2/allow-ip/utils.js
4
+ /**
5
+ * @typedef {Object} Ec2Instance
6
+ * @property {string} instanceId
7
+ * @property {string} name
8
+ * @property {string} state
9
+ * @property {string} instanceType
10
+ * @property {string | null} publicIp
11
+ * @property {string[]} securityGroupIds
12
+ */
13
+ /**
14
+ * @typedef {Object} SecurityGroup
15
+ * @property {string} groupId
16
+ * @property {string} groupName
17
+ * @property {string} description
18
+ */
19
+ /**
20
+ * @typedef {Object} SshIngressRule
21
+ * @property {string} cidrIp
22
+ * @property {string} description
23
+ * @property {number} fromPort
24
+ * @property {number} toPort
25
+ * @property {string} ipProtocol
26
+ */
27
+ /**
28
+ * Determine the caller's current public IPv4 address.
29
+ * @returns {Promise<string>}
30
+ */
31
+ async function getCurrentPublicIp() {
32
+ for (const url of ["https://checkip.amazonaws.com", "https://api.ipify.org"]) try {
33
+ const controller = new AbortController();
34
+ const timeout = setTimeout(() => controller.abort(), 8e3);
35
+ const response = await fetch(url, { signal: controller.signal });
36
+ clearTimeout(timeout);
37
+ if (!response.ok) continue;
38
+ const ip = (await response.text()).trim();
39
+ if (ip && ip.includes(".")) return ip;
40
+ } catch {
41
+ continue;
42
+ }
43
+ throw new Error("Could not determine your current public IP address");
44
+ }
45
+ /**
46
+ * List regions that are opted-in / enabled for the profile.
47
+ * @param {{ profile?: string; region: string; verbose?: boolean }} params
48
+ * @returns {string[]}
49
+ */
50
+ function listEnabledRegions(params) {
51
+ const { profile, region, verbose } = params;
52
+ const data = runAws({
53
+ profile,
54
+ region,
55
+ args: [
56
+ "ec2",
57
+ "describe-regions",
58
+ "--query",
59
+ "Regions[?OptInStatus!=`not-opted-in`].RegionName"
60
+ ],
61
+ verbose
62
+ });
63
+ return (Array.isArray(data) ? data : []).map((value) => String(value)).sort();
64
+ }
65
+ /**
66
+ * List non-terminated EC2 instances in a region.
67
+ * @param {{ profile?: string; region: string; verbose?: boolean }} params
68
+ * @returns {Ec2Instance[]}
69
+ */
70
+ function listInstances(params) {
71
+ const { profile, region, verbose } = params;
72
+ const data = runAws({
73
+ profile,
74
+ region,
75
+ args: ["ec2", "describe-instances"],
76
+ verbose
77
+ });
78
+ /** @type {Ec2Instance[]} */
79
+ const instances = [];
80
+ for (const reservation of data.Reservations ?? []) for (const raw of reservation.Instances ?? []) {
81
+ const state = raw.State?.Name ?? "unknown";
82
+ if (state === "terminated" || state === "shutting-down") continue;
83
+ let name = "";
84
+ for (const tag of raw.Tags ?? []) if (tag.Key === "Name") {
85
+ name = tag.Value ?? "";
86
+ break;
87
+ }
88
+ const securityGroupIds = (raw.SecurityGroups ?? []).map((group) => group.GroupId).filter((id) => Boolean(id));
89
+ instances.push({
90
+ instanceId: raw.InstanceId,
91
+ name,
92
+ state,
93
+ instanceType: raw.InstanceType ?? "unknown",
94
+ publicIp: raw.PublicIpAddress ?? null,
95
+ securityGroupIds
96
+ });
97
+ }
98
+ instances.sort((a, b) => a.name.toLowerCase().localeCompare(b.name.toLowerCase()) || a.instanceId.localeCompare(b.instanceId));
99
+ return instances;
100
+ }
101
+ /**
102
+ * Describe security groups by id.
103
+ * @param {{ profile?: string; region: string; groupIds: string[]; verbose?: boolean }} params
104
+ * @returns {SecurityGroup[]}
105
+ */
106
+ function describeSecurityGroups(params) {
107
+ const { profile, region, groupIds, verbose } = params;
108
+ if (groupIds.length === 0) return [];
109
+ /** @type {SecurityGroup[]} */
110
+ const groups = (runAws({
111
+ profile,
112
+ region,
113
+ args: [
114
+ "ec2",
115
+ "describe-security-groups",
116
+ "--group-ids",
117
+ ...groupIds
118
+ ],
119
+ verbose
120
+ }).SecurityGroups ?? []).map((raw) => ({
121
+ groupId: raw.GroupId,
122
+ groupName: raw.GroupName ?? "",
123
+ description: raw.Description ?? ""
124
+ }));
125
+ groups.sort((a, b) => a.groupName.localeCompare(b.groupName));
126
+ return groups;
127
+ }
128
+ /**
129
+ * Load full details for a single security group.
130
+ * @param {{ profile?: string; region: string; groupId: string; verbose?: boolean }} params
131
+ * @returns {any | null}
132
+ */
133
+ function getSecurityGroupDetails(params) {
134
+ const { profile, region, groupId, verbose } = params;
135
+ const securityGroups = runAws({
136
+ profile,
137
+ region,
138
+ args: [
139
+ "ec2",
140
+ "describe-security-groups",
141
+ "--group-ids",
142
+ groupId
143
+ ],
144
+ verbose
145
+ }).SecurityGroups ?? [];
146
+ if (securityGroups.length === 0) return null;
147
+ return securityGroups[0];
148
+ }
149
+ /**
150
+ * @param {string} cidrIp
151
+ * @returns {boolean}
152
+ */
153
+ function isSingleHostCidr(cidrIp) {
154
+ return cidrIp.endsWith("/32") && cidrIp !== "0.0.0.0/0";
155
+ }
156
+ /**
157
+ * @param {any} permission
158
+ * @returns {boolean}
159
+ */
160
+ function portAllowsSsh(permission) {
161
+ const protocol = permission.IpProtocol;
162
+ if (protocol !== "tcp" && protocol !== "6") return false;
163
+ const fromPort = permission.FromPort;
164
+ const toPort = permission.ToPort;
165
+ if (fromPort === void 0 && toPort === void 0) return true;
166
+ if (fromPort === void 0 || toPort === void 0) return false;
167
+ return fromPort <= 22 && 22 <= toPort;
168
+ }
169
+ /**
170
+ * Extract single-host SSH ingress rules (port 22, /32) from a security group.
171
+ * @param {any} securityGroup
172
+ * @returns {SshIngressRule[]}
173
+ */
174
+ function listSshIngressRules(securityGroup) {
175
+ /** @type {SshIngressRule[]} */
176
+ const rules = [];
177
+ for (const permission of securityGroup.IpPermissions ?? []) {
178
+ if (!portAllowsSsh(permission)) continue;
179
+ const fromPort = permission.FromPort ?? 22;
180
+ const toPort = permission.ToPort ?? 22;
181
+ const ipProtocol = permission.IpProtocol ?? "tcp";
182
+ for (const ipRange of permission.IpRanges ?? []) {
183
+ const cidrIp = ipRange.CidrIp;
184
+ if (!cidrIp || !isSingleHostCidr(cidrIp)) continue;
185
+ rules.push({
186
+ cidrIp,
187
+ description: ipRange.Description ?? "",
188
+ fromPort,
189
+ toPort,
190
+ ipProtocol
191
+ });
192
+ }
193
+ }
194
+ return rules;
195
+ }
196
+ /**
197
+ * Derive a first name from the IAM ARN or profile (md-{name}-...).
198
+ * @param {{ profile?: string; identity: { Arn?: string } }} params
199
+ * @returns {string}
200
+ */
201
+ function extractRoamingFirstName(params) {
202
+ const { profile, identity } = params;
203
+ const arn = identity.Arn ?? "";
204
+ const username = arn.includes("/") ? arn.split("/").pop() ?? "" : "";
205
+ for (const candidate of [username, profile ?? ""]) {
206
+ if (!candidate) continue;
207
+ const match = /^md-([^-]+)-/.exec(candidate);
208
+ if (match && match[1]) return match[1].charAt(0).toUpperCase() + match[1].slice(1).toLowerCase();
209
+ }
210
+ return DEFAULT_ROAMING_NAME;
211
+ }
212
+ /**
213
+ * @param {string} firstName
214
+ * @returns {string}
215
+ */
216
+ function roamingRuleDescription(firstName) {
217
+ return `${firstName} Roaming`;
218
+ }
219
+ /**
220
+ * Find an existing rule whose description matches the roaming rule name.
221
+ * @param {SshIngressRule[]} sshRules
222
+ * @param {string} ruleDescription
223
+ * @returns {SshIngressRule | null}
224
+ */
225
+ function resolveRoamingRule(sshRules, ruleDescription) {
226
+ const target = ruleDescription.toLowerCase();
227
+ for (const rule of sshRules) if (rule.description.trim().toLowerCase() === target) return rule;
228
+ return null;
229
+ }
230
+ /**
231
+ * Find an existing rule that already allows the given CIDR.
232
+ * @param {SshIngressRule[]} sshRules
233
+ * @param {string} newCidr
234
+ * @returns {SshIngressRule | null}
235
+ */
236
+ function ruleAlreadyAllowsIp(sshRules, newCidr) {
237
+ for (const rule of sshRules) if (rule.cidrIp === newCidr) return rule;
238
+ return null;
239
+ }
240
+ /**
241
+ * @param {{
242
+ * cidrIp: string;
243
+ * description: string;
244
+ * fromPort?: number;
245
+ * toPort?: number;
246
+ * ipProtocol?: string;
247
+ * }} params
248
+ * @returns {string} JSON string for the --ip-permissions argument
249
+ */
250
+ function buildIpPermissions(params) {
251
+ const { cidrIp, description, fromPort = 22, toPort = 22, ipProtocol = "tcp" } = params;
252
+ return JSON.stringify([{
253
+ IpProtocol: ipProtocol,
254
+ FromPort: fromPort,
255
+ ToPort: toPort,
256
+ IpRanges: [{
257
+ CidrIp: cidrIp,
258
+ Description: description
259
+ }]
260
+ }]);
261
+ }
262
+ /**
263
+ * Authorize a new SSH ingress rule.
264
+ * @param {{
265
+ * profile?: string;
266
+ * region: string;
267
+ * groupId: string;
268
+ * cidrIp: string;
269
+ * description: string;
270
+ * fromPort?: number;
271
+ * toPort?: number;
272
+ * ipProtocol?: string;
273
+ * verbose?: boolean;
274
+ * }} params
275
+ */
276
+ function authorizeRule(params) {
277
+ const { profile, region, groupId, verbose, ...rule } = params;
278
+ runAws({
279
+ profile,
280
+ region,
281
+ args: [
282
+ "ec2",
283
+ "authorize-security-group-ingress",
284
+ "--group-id",
285
+ groupId,
286
+ "--ip-permissions",
287
+ buildIpPermissions(rule)
288
+ ],
289
+ expectJson: false,
290
+ verbose
291
+ });
292
+ }
293
+ /**
294
+ * Revoke an existing SSH ingress rule.
295
+ * @param {{
296
+ * profile?: string;
297
+ * region: string;
298
+ * groupId: string;
299
+ * rule: SshIngressRule;
300
+ * verbose?: boolean;
301
+ * }} params
302
+ */
303
+ function revokeRule(params) {
304
+ const { profile, region, groupId, rule, verbose } = params;
305
+ runAws({
306
+ profile,
307
+ region,
308
+ args: [
309
+ "ec2",
310
+ "revoke-security-group-ingress",
311
+ "--group-id",
312
+ groupId,
313
+ "--ip-permissions",
314
+ buildIpPermissions({
315
+ cidrIp: rule.cidrIp,
316
+ description: rule.description,
317
+ fromPort: rule.fromPort,
318
+ toPort: rule.toPort,
319
+ ipProtocol: rule.ipProtocol
320
+ })
321
+ ],
322
+ expectJson: false,
323
+ verbose
324
+ });
325
+ }
326
+ /**
327
+ * @param {Ec2Instance} instance
328
+ * @returns {string}
329
+ */
330
+ function instanceLabel(instance) {
331
+ const publicIp = instance.publicIp || "no public IP";
332
+ const name = instance.name || "(no name)";
333
+ return `${instance.instanceId} ${name} ${instance.state} ${instance.instanceType} ${publicIp}`;
334
+ }
335
+ /**
336
+ * @param {SecurityGroup} group
337
+ * @returns {string}
338
+ */
339
+ function securityGroupLabel(group) {
340
+ return `${group.groupId} ${group.groupName} ${group.description}`;
341
+ }
342
+ /**
343
+ * @param {SshIngressRule} rule
344
+ * @returns {string}
345
+ */
346
+ function sshRuleLabel(rule) {
347
+ const description = rule.description || "(no description)";
348
+ return `${rule.cidrIp} ${description}`;
349
+ }
350
+ /**
351
+ * Print single-host SSH rules using the given logger.
352
+ * @param {SshIngressRule[]} sshRules
353
+ * @param {(message: string) => void} [log]
354
+ */
355
+ function printSshRules(sshRules, log = console.log) {
356
+ if (sshRules.length === 0) {
357
+ log(" (no single-host SSH rules on port 22)");
358
+ return;
359
+ }
360
+ for (const rule of sshRules) log(` - ${sshRuleLabel(rule)}`);
361
+ }
362
+ //#endregion
363
+ export { authorizeRule, describeSecurityGroups, extractRoamingFirstName, getCurrentPublicIp, getSecurityGroupDetails, instanceLabel, listEnabledRegions, listInstances, listSshIngressRules, printSshRules, resolveRoamingRule, revokeRule, roamingRuleDescription, ruleAlreadyAllowsIp, securityGroupLabel, sshRuleLabel };
@@ -0,0 +1,55 @@
1
+ /**
2
+ * A function bound to a profile's working credential mode (direct or aws-vault).
3
+ * @typedef {(args: string[], options?: { expectJson?: boolean }) => any} ProfileRunner
4
+ */
5
+ /**
6
+ * List aws-vault profiles that have stored credentials.
7
+ * @returns {string[]}
8
+ */
9
+ export function listVaultProfiles(): string[];
10
+ /**
11
+ * Resolve which profiles to scan for admin-group commands.
12
+ * Uses `--profile`, then `AWS_PROFILE`, for a single profile unless `--all` is set.
13
+ * @param {{ profile?: string; all?: boolean }} params
14
+ * @returns {string[]}
15
+ */
16
+ export function resolveAdminScanProfiles({ profile, all }: {
17
+ profile?: string;
18
+ all?: boolean;
19
+ }): string[];
20
+ /**
21
+ * @param {{ Users?: Array<{ UserName: string }> }} group
22
+ * @returns {string[]}
23
+ */
24
+ export function groupUserNames(group: {
25
+ Users?: Array<{
26
+ UserName: string;
27
+ }>;
28
+ }): string[];
29
+ /**
30
+ * Resolve a working credential runner for a profile and read the admin group.
31
+ * Tries stored credentials first, then falls back to `aws-vault exec`.
32
+ * The returned `run` reuses whichever mode worked, so callers can perform
33
+ * follow-up mutations (e.g. remove-user-from-group) with the same credentials.
34
+ * @param {{ profile: string; verbose?: boolean }} params
35
+ * @returns {{ accountId: string; arn: string; users: string[]; run: ProfileRunner } | null} Null when the profile is skipped
36
+ */
37
+ export function resolveProfileScan(params: {
38
+ profile: string;
39
+ verbose?: boolean;
40
+ }): {
41
+ accountId: string;
42
+ arn: string;
43
+ users: string[];
44
+ run: ProfileRunner;
45
+ } | null;
46
+ /**
47
+ * The IAM group that grants break-glass admin access and should normally be empty.
48
+ */
49
+ export const ADMIN_GROUP_NAME: "acl-admin-do-not-use";
50
+ /**
51
+ * A function bound to a profile's working credential mode (direct or aws-vault).
52
+ */
53
+ export type ProfileRunner = (args: string[], options?: {
54
+ expectJson?: boolean;
55
+ }) => any;
@@ -0,0 +1,104 @@
1
+ import { runAws, runAwsVault } from "../utils/exec-aws.js";
2
+ import chalk from "chalk";
3
+ import { spawnSync } from "child_process";
4
+ //#region src/commands/aws/iam/admin-group.js
5
+ /**
6
+ * The IAM group that grants break-glass admin access and should normally be empty.
7
+ */
8
+ const ADMIN_GROUP_NAME = "acl-admin-do-not-use";
9
+ const VAULT_DURATION = process.env.AWS_VAULT_DURATION ?? "15m";
10
+ /**
11
+ * A function bound to a profile's working credential mode (direct or aws-vault).
12
+ * @typedef {(args: string[], options?: { expectJson?: boolean }) => any} ProfileRunner
13
+ */
14
+ /**
15
+ * List aws-vault profiles that have stored credentials.
16
+ * @returns {string[]}
17
+ */
18
+ function listVaultProfiles() {
19
+ const result = spawnSync("aws-vault", ["list", "--credentials"], { encoding: "utf-8" });
20
+ if (result.status !== 0) return [];
21
+ return (result.stdout ?? "").split("\n").map((line) => line.trim()).filter((line) => line.length > 0);
22
+ }
23
+ /**
24
+ * Resolve which profiles to scan for admin-group commands.
25
+ * Uses `--profile`, then `AWS_PROFILE`, for a single profile unless `--all` is set.
26
+ * @param {{ profile?: string; all?: boolean }} params
27
+ * @returns {string[]}
28
+ */
29
+ function resolveAdminScanProfiles({ profile, all = false }) {
30
+ const targetProfile = profile ?? process.env.AWS_PROFILE;
31
+ if (!all && targetProfile) return [targetProfile];
32
+ const profiles = listVaultProfiles();
33
+ if (profiles.length === 0) throw new Error("No aws-vault profiles with credentials found.");
34
+ return profiles;
35
+ }
36
+ /**
37
+ * @param {{ Users?: Array<{ UserName: string }> }} group
38
+ * @returns {string[]}
39
+ */
40
+ function groupUserNames(group) {
41
+ return (group.Users ?? []).map((user) => user.UserName);
42
+ }
43
+ /**
44
+ * Resolve a working credential runner for a profile and read the admin group.
45
+ * Tries stored credentials first, then falls back to `aws-vault exec`.
46
+ * The returned `run` reuses whichever mode worked, so callers can perform
47
+ * follow-up mutations (e.g. remove-user-from-group) with the same credentials.
48
+ * @param {{ profile: string; verbose?: boolean }} params
49
+ * @returns {{ accountId: string; arn: string; users: string[]; run: ProfileRunner } | null} Null when the profile is skipped
50
+ */
51
+ function resolveProfileScan(params) {
52
+ const { profile, verbose = false } = params;
53
+ const idArgs = ["sts", "get-caller-identity"];
54
+ const groupArgs = [
55
+ "iam",
56
+ "get-group",
57
+ "--group-name",
58
+ ADMIN_GROUP_NAME
59
+ ];
60
+ /** @type {ProfileRunner} */
61
+ const directRun = (args, options = {}) => runAws({
62
+ profile,
63
+ args,
64
+ expectJson: options.expectJson ?? true,
65
+ verbose
66
+ });
67
+ /** @type {ProfileRunner} */
68
+ const vaultRun = (args, options = {}) => runAwsVault({
69
+ profile,
70
+ args,
71
+ duration: VAULT_DURATION,
72
+ expectJson: options.expectJson ?? true,
73
+ verbose
74
+ });
75
+ try {
76
+ const identity = directRun(idArgs);
77
+ const group = directRun(groupArgs);
78
+ return {
79
+ accountId: identity.Account,
80
+ arn: identity.Arn,
81
+ users: groupUserNames(group),
82
+ run: directRun
83
+ };
84
+ } catch (directError) {
85
+ try {
86
+ const identity = vaultRun(idArgs);
87
+ const group = vaultRun(groupArgs);
88
+ return {
89
+ accountId: identity.Account,
90
+ arn: identity.Arn,
91
+ users: groupUserNames(group),
92
+ run: vaultRun
93
+ };
94
+ } catch {
95
+ if (verbose) {
96
+ const reason = directError instanceof Error ? directError.message.split("\n")[0] ?? "" : String(directError);
97
+ console.log(chalk.dim(`skip ${profile} — ${reason}`));
98
+ }
99
+ return null;
100
+ }
101
+ }
102
+ }
103
+ //#endregion
104
+ export { ADMIN_GROUP_NAME, groupUserNames, listVaultProfiles, resolveAdminScanProfiles, resolveProfileScan };
@@ -0,0 +1,7 @@
1
+ /**
2
+ * `aws iam break-glass` handler: grant break-glass admin access by adding a user
3
+ * to the acl-admin-do-not-use group for a single profile. Defaults to the
4
+ * caller's own IAM user when no --user is given.
5
+ * @param {unknown} options
6
+ */
7
+ export function breakGlassCommand(options: unknown): Promise<void>;
@@ -0,0 +1,124 @@
1
+ import { promptYesNo } from "../../utils/tty.js";
2
+ import { requireCommands } from "../../utils/exec-aws.js";
3
+ import { resolveProfile } from "../../utils/resolve-profile.js";
4
+ import { ADMIN_GROUP_NAME, resolveProfileScan } from "../admin-group.js";
5
+ import { breakGlassOptionsSchema } from "./schema.js";
6
+ import chalk from "chalk";
7
+ import { ZodError } from "zod";
8
+ //#region src/commands/aws/iam/break-glass/handler.js
9
+ /**
10
+ * Derive an IAM user name from a caller-identity ARN.
11
+ * Returns null for non-user ARNs (assumed roles, root, etc.).
12
+ * @param {string} arn
13
+ * @returns {string | null}
14
+ */
15
+ function userNameFromArn(arn) {
16
+ const index = arn.indexOf(":user/");
17
+ if (index === -1) return null;
18
+ const name = arn.slice(index + 6).split("/").pop();
19
+ return name && name.length > 0 ? name : null;
20
+ }
21
+ /**
22
+ * `aws iam break-glass` handler: grant break-glass admin access by adding a user
23
+ * to the acl-admin-do-not-use group for a single profile. Defaults to the
24
+ * caller's own IAM user when no --user is given.
25
+ * @param {unknown} options
26
+ */
27
+ async function breakGlassCommand(options) {
28
+ try {
29
+ const { profile, user, yes, dryRun, verbose } = breakGlassOptionsSchema.parse(options);
30
+ requireCommands(["aws"]);
31
+ const targetProfile = await resolveProfile({ profile });
32
+ if (!targetProfile) {
33
+ console.error(chalk.red("No profile specified. Pass --profile <name> or set AWS_PROFILE."));
34
+ process.exit(1);
35
+ }
36
+ const scan = resolveProfileScan({
37
+ profile: targetProfile,
38
+ verbose
39
+ });
40
+ if (!scan) {
41
+ console.error(chalk.red(`\n❌ Could not read ${ADMIN_GROUP_NAME} for profile "${targetProfile}".`));
42
+ console.error(chalk.yellow(" Check your credentials, and run `mtn aws iam setup-acl` first if the group does not exist yet.\n"));
43
+ process.exit(1);
44
+ }
45
+ /** @type {string[]} */
46
+ let targets = user;
47
+ if (targets.length === 0) {
48
+ const derived = userNameFromArn(scan.arn);
49
+ if (!derived) {
50
+ console.error(chalk.red(`\n❌ Could not determine your IAM user from the caller identity:\n ${scan.arn}`));
51
+ console.error(chalk.yellow(" Pass --user <name> to choose who to grant.\n"));
52
+ process.exit(1);
53
+ }
54
+ targets = [derived];
55
+ }
56
+ const alreadyMembers = new Set(scan.users);
57
+ const toGrant = targets.filter((name) => !alreadyMembers.has(name));
58
+ const existing = targets.filter((name) => alreadyMembers.has(name));
59
+ console.log(chalk.blue(`\n==> Break-glass admin (${ADMIN_GROUP_NAME}) — profile ${targetProfile} (${scan.accountId})\n`));
60
+ for (const name of existing) console.log(chalk.dim(`-- ${name} already has break-glass access`));
61
+ if (toGrant.length === 0) {
62
+ console.log(chalk.green("\nNothing to do — all target user(s) already have break-glass access.\n"));
63
+ return;
64
+ }
65
+ console.log(chalk.red.bold(`⚠ This grants AdministratorAccess to ${toGrant.length} user(s): ${toGrant.join(", ")}`));
66
+ if (dryRun) {
67
+ for (const name of toGrant) console.log(chalk.yellow(` [dry-run] would add ${name} to ${ADMIN_GROUP_NAME}`));
68
+ console.log(chalk.yellow("\n[dry-run] Re-run without --dry-run to apply.\n"));
69
+ return;
70
+ }
71
+ let confirmed = yes;
72
+ if (!confirmed) confirmed = await promptYesNo({
73
+ message: ` Grant break-glass admin to ${toGrant.length} user(s) in ${targetProfile}?`,
74
+ defaultYes: false
75
+ });
76
+ if (!confirmed) {
77
+ console.log(chalk.dim(" aborted (not confirmed)\n"));
78
+ return;
79
+ }
80
+ let granted = 0;
81
+ for (const name of toGrant) try {
82
+ scan.run([
83
+ "iam",
84
+ "add-user-to-group",
85
+ "--group-name",
86
+ ADMIN_GROUP_NAME,
87
+ "--user-name",
88
+ name
89
+ ], { expectJson: false });
90
+ console.log(chalk.green(` granted ${name}`));
91
+ granted += 1;
92
+ } catch (grantError) {
93
+ const reason = grantError instanceof Error ? grantError.message.split("\n")[0] ?? "" : String(grantError);
94
+ console.log(chalk.red(` failed to grant ${name}: ${reason}`));
95
+ }
96
+ console.log(chalk.blue("\n==> Summary"));
97
+ console.log(` Profile: ${targetProfile} (${scan.accountId})`);
98
+ console.log(` Users granted: ${granted}`);
99
+ console.log(` Already had it: ${existing.length}`);
100
+ console.log(chalk.yellow("\n💡 Revoke with `mtn aws iam remove-admin` when you are done."));
101
+ console.log(chalk.dim(" You may need a fresh session (e.g. clear the aws-vault cache) before the new permissions apply.\n"));
102
+ } catch (error) {
103
+ if (error instanceof ZodError) {
104
+ console.error(chalk.red("\n❌ Invalid command options:"));
105
+ error.errors.forEach((err) => {
106
+ console.error(chalk.red(` ${err.path.join(".")}: ${err.message}`));
107
+ });
108
+ process.exit(1);
109
+ }
110
+ const verbose = options?.verbose || false;
111
+ console.error(chalk.red("\n❌ break-glass failed:"));
112
+ if (error instanceof Error) {
113
+ console.error(chalk.red(` ${error.message}`));
114
+ if (verbose && error.stack) {
115
+ console.error(chalk.gray("\nStack trace:"));
116
+ console.error(chalk.gray(error.stack));
117
+ }
118
+ } else console.error(chalk.red(` ${String(error)}`));
119
+ console.error(chalk.yellow("\n💡 Requires the AWS CLI and iam:AddUserToGroup on the target profile.\n"));
120
+ process.exit(1);
121
+ }
122
+ }
123
+ //#endregion
124
+ export { breakGlassCommand };
@@ -0,0 +1,2 @@
1
+ export * from "./handler.js";
2
+ export * from "./schema.js";
@@ -0,0 +1,4 @@
1
+ import { ADMIN_GROUP_NAME } from "../admin-group.js";
2
+ import { breakGlassOptionsSchema } from "./schema.js";
3
+ import { breakGlassCommand } from "./handler.js";
4
+ export { ADMIN_GROUP_NAME, breakGlassCommand, breakGlassOptionsSchema };
@@ -0,0 +1,25 @@
1
+ export { ADMIN_GROUP_NAME } from "../admin-group.js";
2
+ /**
3
+ * Zod schema for `aws iam break-glass` options.
4
+ */
5
+ export const breakGlassOptionsSchema: z.ZodObject<{
6
+ profile: z.ZodOptional<z.ZodString>;
7
+ user: z.ZodDefault<z.ZodOptional<z.ZodArray<z.ZodString, "many">>>;
8
+ yes: z.ZodDefault<z.ZodOptional<z.ZodBoolean>>;
9
+ dryRun: z.ZodDefault<z.ZodOptional<z.ZodBoolean>>;
10
+ verbose: z.ZodDefault<z.ZodOptional<z.ZodBoolean>>;
11
+ }, "strip", z.ZodTypeAny, {
12
+ verbose: boolean;
13
+ yes: boolean;
14
+ user: string[];
15
+ dryRun: boolean;
16
+ profile?: string | undefined;
17
+ }, {
18
+ profile?: string | undefined;
19
+ verbose?: boolean | undefined;
20
+ yes?: boolean | undefined;
21
+ user?: string[] | undefined;
22
+ dryRun?: boolean | undefined;
23
+ }>;
24
+ export type BreakGlassOptions = z.infer<typeof breakGlassOptionsSchema>;
25
+ import { z } from "zod";