resend-cli 1.0.3 → 1.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 (365) hide show
  1. package/.claude/settings.local.json +5 -0
  2. package/.claude/worktrees/emails-list/.claude/settings.local.json +5 -0
  3. package/.claude/worktrees/emails-list/.github/scripts/pr-title-check.js +34 -0
  4. package/.claude/worktrees/emails-list/.github/workflows/ci.yml +32 -0
  5. package/.claude/worktrees/emails-list/.github/workflows/pr-title-check.yml +13 -0
  6. package/.claude/worktrees/emails-list/.github/workflows/release.yml +93 -0
  7. package/.claude/worktrees/emails-list/CHANGELOG.md +31 -0
  8. package/.claude/worktrees/emails-list/LICENSE +21 -0
  9. package/.claude/worktrees/emails-list/README.md +424 -0
  10. package/.claude/worktrees/emails-list/biome.json +36 -0
  11. package/.claude/worktrees/emails-list/bun.lock +76 -0
  12. package/.claude/worktrees/emails-list/bunfig.toml +2 -0
  13. package/.claude/worktrees/emails-list/install.ps1 +140 -0
  14. package/.claude/worktrees/emails-list/install.sh +301 -0
  15. package/.claude/worktrees/emails-list/package.json +43 -0
  16. package/.claude/worktrees/emails-list/renovate.json +6 -0
  17. package/.claude/worktrees/emails-list/src/cli.ts +74 -0
  18. package/.claude/worktrees/emails-list/src/commands/api-keys/create.ts +114 -0
  19. package/.claude/worktrees/emails-list/src/commands/api-keys/delete.ts +47 -0
  20. package/.claude/worktrees/emails-list/src/commands/api-keys/index.ts +26 -0
  21. package/.claude/worktrees/emails-list/src/commands/api-keys/list.ts +35 -0
  22. package/.claude/worktrees/emails-list/src/commands/api-keys/utils.ts +8 -0
  23. package/.claude/worktrees/emails-list/src/commands/auth/index.ts +20 -0
  24. package/.claude/worktrees/emails-list/src/commands/auth/login.ts +207 -0
  25. package/.claude/worktrees/emails-list/src/commands/auth/logout.ts +105 -0
  26. package/.claude/worktrees/emails-list/src/commands/broadcasts/create.ts +196 -0
  27. package/.claude/worktrees/emails-list/src/commands/broadcasts/delete.ts +46 -0
  28. package/.claude/worktrees/emails-list/src/commands/broadcasts/get.ts +59 -0
  29. package/.claude/worktrees/emails-list/src/commands/broadcasts/index.ts +43 -0
  30. package/.claude/worktrees/emails-list/src/commands/broadcasts/list.ts +60 -0
  31. package/.claude/worktrees/emails-list/src/commands/broadcasts/send.ts +56 -0
  32. package/.claude/worktrees/emails-list/src/commands/broadcasts/update.ts +95 -0
  33. package/.claude/worktrees/emails-list/src/commands/broadcasts/utils.ts +35 -0
  34. package/.claude/worktrees/emails-list/src/commands/contact-properties/create.ts +118 -0
  35. package/.claude/worktrees/emails-list/src/commands/contact-properties/delete.ts +48 -0
  36. package/.claude/worktrees/emails-list/src/commands/contact-properties/get.ts +46 -0
  37. package/.claude/worktrees/emails-list/src/commands/contact-properties/index.ts +48 -0
  38. package/.claude/worktrees/emails-list/src/commands/contact-properties/list.ts +68 -0
  39. package/.claude/worktrees/emails-list/src/commands/contact-properties/update.ts +88 -0
  40. package/.claude/worktrees/emails-list/src/commands/contact-properties/utils.ts +17 -0
  41. package/.claude/worktrees/emails-list/src/commands/contacts/add-segment.ts +78 -0
  42. package/.claude/worktrees/emails-list/src/commands/contacts/create.ts +122 -0
  43. package/.claude/worktrees/emails-list/src/commands/contacts/delete.ts +49 -0
  44. package/.claude/worktrees/emails-list/src/commands/contacts/get.ts +53 -0
  45. package/.claude/worktrees/emails-list/src/commands/contacts/index.ts +58 -0
  46. package/.claude/worktrees/emails-list/src/commands/contacts/list.ts +57 -0
  47. package/.claude/worktrees/emails-list/src/commands/contacts/remove-segment.ts +48 -0
  48. package/.claude/worktrees/emails-list/src/commands/contacts/segments.ts +39 -0
  49. package/.claude/worktrees/emails-list/src/commands/contacts/topics.ts +45 -0
  50. package/.claude/worktrees/emails-list/src/commands/contacts/update-topics.ts +90 -0
  51. package/.claude/worktrees/emails-list/src/commands/contacts/update.ts +77 -0
  52. package/.claude/worktrees/emails-list/src/commands/contacts/utils.ts +119 -0
  53. package/.claude/worktrees/emails-list/src/commands/doctor.ts +298 -0
  54. package/.claude/worktrees/emails-list/src/commands/domains/create.ts +83 -0
  55. package/.claude/worktrees/emails-list/src/commands/domains/delete.ts +42 -0
  56. package/.claude/worktrees/emails-list/src/commands/domains/get.ts +47 -0
  57. package/.claude/worktrees/emails-list/src/commands/domains/index.ts +35 -0
  58. package/.claude/worktrees/emails-list/src/commands/domains/list.ts +53 -0
  59. package/.claude/worktrees/emails-list/src/commands/domains/update.ts +75 -0
  60. package/.claude/worktrees/emails-list/src/commands/domains/utils.ts +44 -0
  61. package/.claude/worktrees/emails-list/src/commands/domains/verify.ts +38 -0
  62. package/.claude/worktrees/emails-list/src/commands/emails/batch.ts +140 -0
  63. package/.claude/worktrees/emails-list/src/commands/emails/index.ts +28 -0
  64. package/.claude/worktrees/emails-list/src/commands/emails/list.ts +73 -0
  65. package/.claude/worktrees/emails-list/src/commands/emails/receiving/attachment.ts +55 -0
  66. package/.claude/worktrees/emails-list/src/commands/emails/receiving/attachments.ts +68 -0
  67. package/.claude/worktrees/emails-list/src/commands/emails/receiving/get.ts +58 -0
  68. package/.claude/worktrees/emails-list/src/commands/emails/receiving/index.ts +28 -0
  69. package/.claude/worktrees/emails-list/src/commands/emails/receiving/list.ts +59 -0
  70. package/.claude/worktrees/emails-list/src/commands/emails/receiving/utils.ts +38 -0
  71. package/.claude/worktrees/emails-list/src/commands/emails/send.ts +189 -0
  72. package/.claude/worktrees/emails-list/src/commands/open.ts +24 -0
  73. package/.claude/worktrees/emails-list/src/commands/segments/create.ts +50 -0
  74. package/.claude/worktrees/emails-list/src/commands/segments/delete.ts +47 -0
  75. package/.claude/worktrees/emails-list/src/commands/segments/get.ts +38 -0
  76. package/.claude/worktrees/emails-list/src/commands/segments/index.ts +36 -0
  77. package/.claude/worktrees/emails-list/src/commands/segments/list.ts +58 -0
  78. package/.claude/worktrees/emails-list/src/commands/segments/utils.ts +7 -0
  79. package/.claude/worktrees/emails-list/src/commands/teams/index.ts +10 -0
  80. package/.claude/worktrees/emails-list/src/commands/teams/list.ts +35 -0
  81. package/.claude/worktrees/emails-list/src/commands/teams/remove.ts +83 -0
  82. package/.claude/worktrees/emails-list/src/commands/teams/switch.ts +73 -0
  83. package/.claude/worktrees/emails-list/src/commands/topics/create.ts +73 -0
  84. package/.claude/worktrees/emails-list/src/commands/topics/delete.ts +47 -0
  85. package/.claude/worktrees/emails-list/src/commands/topics/get.ts +42 -0
  86. package/.claude/worktrees/emails-list/src/commands/topics/index.ts +42 -0
  87. package/.claude/worktrees/emails-list/src/commands/topics/list.ts +34 -0
  88. package/.claude/worktrees/emails-list/src/commands/topics/update.ts +59 -0
  89. package/.claude/worktrees/emails-list/src/commands/topics/utils.ts +16 -0
  90. package/.claude/worktrees/emails-list/src/commands/webhooks/create.ts +128 -0
  91. package/.claude/worktrees/emails-list/src/commands/webhooks/delete.ts +49 -0
  92. package/.claude/worktrees/emails-list/src/commands/webhooks/get.ts +42 -0
  93. package/.claude/worktrees/emails-list/src/commands/webhooks/index.ts +44 -0
  94. package/.claude/worktrees/emails-list/src/commands/webhooks/list.ts +55 -0
  95. package/.claude/worktrees/emails-list/src/commands/webhooks/update.ts +83 -0
  96. package/.claude/worktrees/emails-list/src/commands/webhooks/utils.ts +36 -0
  97. package/.claude/worktrees/emails-list/src/commands/whoami.ts +71 -0
  98. package/.claude/worktrees/emails-list/src/lib/actions.ts +157 -0
  99. package/.claude/worktrees/emails-list/src/lib/client.ts +34 -0
  100. package/.claude/worktrees/emails-list/src/lib/config.ts +211 -0
  101. package/.claude/worktrees/emails-list/src/lib/files.ts +15 -0
  102. package/.claude/worktrees/emails-list/src/lib/help-text.ts +38 -0
  103. package/.claude/worktrees/emails-list/src/lib/output.ts +54 -0
  104. package/.claude/worktrees/emails-list/src/lib/pagination.ts +36 -0
  105. package/.claude/worktrees/emails-list/src/lib/prompts.ts +149 -0
  106. package/.claude/worktrees/emails-list/src/lib/spinner.ts +93 -0
  107. package/.claude/worktrees/emails-list/src/lib/table.ts +57 -0
  108. package/.claude/worktrees/emails-list/src/lib/tty.ts +28 -0
  109. package/.claude/worktrees/emails-list/src/lib/version.ts +4 -0
  110. package/.claude/worktrees/emails-list/tests/commands/api-keys/create.test.ts +195 -0
  111. package/.claude/worktrees/emails-list/tests/commands/api-keys/delete.test.ts +156 -0
  112. package/.claude/worktrees/emails-list/tests/commands/api-keys/list.test.ts +133 -0
  113. package/.claude/worktrees/emails-list/tests/commands/auth/login.test.ts +119 -0
  114. package/.claude/worktrees/emails-list/tests/commands/auth/logout.test.ts +146 -0
  115. package/.claude/worktrees/emails-list/tests/commands/broadcasts/create.test.ts +447 -0
  116. package/.claude/worktrees/emails-list/tests/commands/broadcasts/delete.test.ts +182 -0
  117. package/.claude/worktrees/emails-list/tests/commands/broadcasts/get.test.ts +146 -0
  118. package/.claude/worktrees/emails-list/tests/commands/broadcasts/list.test.ts +196 -0
  119. package/.claude/worktrees/emails-list/tests/commands/broadcasts/send.test.ts +161 -0
  120. package/.claude/worktrees/emails-list/tests/commands/broadcasts/update.test.ts +283 -0
  121. package/.claude/worktrees/emails-list/tests/commands/contact-properties/create.test.ts +250 -0
  122. package/.claude/worktrees/emails-list/tests/commands/contact-properties/delete.test.ts +183 -0
  123. package/.claude/worktrees/emails-list/tests/commands/contact-properties/get.test.ts +144 -0
  124. package/.claude/worktrees/emails-list/tests/commands/contact-properties/list.test.ts +180 -0
  125. package/.claude/worktrees/emails-list/tests/commands/contact-properties/update.test.ts +216 -0
  126. package/.claude/worktrees/emails-list/tests/commands/contacts/add-segment.test.ts +188 -0
  127. package/.claude/worktrees/emails-list/tests/commands/contacts/create.test.ts +270 -0
  128. package/.claude/worktrees/emails-list/tests/commands/contacts/delete.test.ts +192 -0
  129. package/.claude/worktrees/emails-list/tests/commands/contacts/get.test.ts +148 -0
  130. package/.claude/worktrees/emails-list/tests/commands/contacts/list.test.ts +175 -0
  131. package/.claude/worktrees/emails-list/tests/commands/contacts/remove-segment.test.ts +166 -0
  132. package/.claude/worktrees/emails-list/tests/commands/contacts/segments.test.ts +167 -0
  133. package/.claude/worktrees/emails-list/tests/commands/contacts/topics.test.ts +163 -0
  134. package/.claude/worktrees/emails-list/tests/commands/contacts/update-topics.test.ts +247 -0
  135. package/.claude/worktrees/emails-list/tests/commands/contacts/update.test.ts +205 -0
  136. package/.claude/worktrees/emails-list/tests/commands/doctor.test.ts +165 -0
  137. package/.claude/worktrees/emails-list/tests/commands/domains/create.test.ts +192 -0
  138. package/.claude/worktrees/emails-list/tests/commands/domains/delete.test.ts +156 -0
  139. package/.claude/worktrees/emails-list/tests/commands/domains/get.test.ts +137 -0
  140. package/.claude/worktrees/emails-list/tests/commands/domains/list.test.ts +164 -0
  141. package/.claude/worktrees/emails-list/tests/commands/domains/update.test.ts +223 -0
  142. package/.claude/worktrees/emails-list/tests/commands/domains/verify.test.ts +117 -0
  143. package/.claude/worktrees/emails-list/tests/commands/emails/batch.test.ts +313 -0
  144. package/.claude/worktrees/emails-list/tests/commands/emails/list.test.ts +196 -0
  145. package/.claude/worktrees/emails-list/tests/commands/emails/receiving/attachment.test.ts +140 -0
  146. package/.claude/worktrees/emails-list/tests/commands/emails/receiving/attachments.test.ts +168 -0
  147. package/.claude/worktrees/emails-list/tests/commands/emails/receiving/get.test.ts +140 -0
  148. package/.claude/worktrees/emails-list/tests/commands/emails/receiving/list.test.ts +181 -0
  149. package/.claude/worktrees/emails-list/tests/commands/emails/send.test.ts +309 -0
  150. package/.claude/worktrees/emails-list/tests/commands/segments/create.test.ts +163 -0
  151. package/.claude/worktrees/emails-list/tests/commands/segments/delete.test.ts +182 -0
  152. package/.claude/worktrees/emails-list/tests/commands/segments/get.test.ts +137 -0
  153. package/.claude/worktrees/emails-list/tests/commands/segments/list.test.ts +173 -0
  154. package/.claude/worktrees/emails-list/tests/commands/teams/list.test.ts +63 -0
  155. package/.claude/worktrees/emails-list/tests/commands/teams/remove.test.ts +103 -0
  156. package/.claude/worktrees/emails-list/tests/commands/teams/switch.test.ts +96 -0
  157. package/.claude/worktrees/emails-list/tests/commands/topics/create.test.ts +191 -0
  158. package/.claude/worktrees/emails-list/tests/commands/topics/delete.test.ts +156 -0
  159. package/.claude/worktrees/emails-list/tests/commands/topics/get.test.ts +125 -0
  160. package/.claude/worktrees/emails-list/tests/commands/topics/list.test.ts +124 -0
  161. package/.claude/worktrees/emails-list/tests/commands/topics/update.test.ts +177 -0
  162. package/.claude/worktrees/emails-list/tests/commands/webhooks/create.test.ts +224 -0
  163. package/.claude/worktrees/emails-list/tests/commands/webhooks/delete.test.ts +156 -0
  164. package/.claude/worktrees/emails-list/tests/commands/webhooks/get.test.ts +125 -0
  165. package/.claude/worktrees/emails-list/tests/commands/webhooks/list.test.ts +177 -0
  166. package/.claude/worktrees/emails-list/tests/commands/webhooks/update.test.ts +206 -0
  167. package/.claude/worktrees/emails-list/tests/commands/whoami.test.ts +99 -0
  168. package/.claude/worktrees/emails-list/tests/helpers.ts +93 -0
  169. package/.claude/worktrees/emails-list/tests/lib/client.test.ts +71 -0
  170. package/.claude/worktrees/emails-list/tests/lib/config.test.ts +414 -0
  171. package/.claude/worktrees/emails-list/tests/lib/files.test.ts +65 -0
  172. package/.claude/worktrees/emails-list/tests/lib/help-text.test.ts +97 -0
  173. package/.claude/worktrees/emails-list/tests/lib/output.test.ts +127 -0
  174. package/.claude/worktrees/emails-list/tests/lib/prompts.test.ts +178 -0
  175. package/.claude/worktrees/emails-list/tests/lib/spinner.test.ts +146 -0
  176. package/.claude/worktrees/emails-list/tests/lib/table.test.ts +63 -0
  177. package/.claude/worktrees/emails-list/tests/lib/tty.test.ts +85 -0
  178. package/.claude/worktrees/emails-list/tsconfig.json +14 -0
  179. package/.github/scripts/pr-title-check.js +34 -0
  180. package/.github/workflows/ci.yml +32 -0
  181. package/.github/workflows/pr-title-check.yml +13 -0
  182. package/.github/workflows/release.yml +93 -0
  183. package/.github/workflows/test-build-windows.yml +44 -0
  184. package/.github/workflows/test-install-windows.yml +48 -0
  185. package/CHANGELOG.md +31 -0
  186. package/LICENSE +21 -21
  187. package/README.md +424 -19
  188. package/biome.json +36 -0
  189. package/bun.lock +76 -0
  190. package/bunfig.toml +2 -0
  191. package/docs/agent-dx-gaps.md +167 -0
  192. package/docs/missing-commands.md +58 -0
  193. package/docs/production-readiness.md +99 -0
  194. package/docs/secure-key-storage.md +174 -0
  195. package/install.ps1 +141 -0
  196. package/install.sh +301 -0
  197. package/package.json +43 -22
  198. package/renovate.json +4 -0
  199. package/src/cli.ts +74 -0
  200. package/src/commands/api-keys/create.ts +114 -0
  201. package/src/commands/api-keys/delete.ts +47 -0
  202. package/src/commands/api-keys/index.ts +26 -0
  203. package/src/commands/api-keys/list.ts +35 -0
  204. package/src/commands/api-keys/utils.ts +8 -0
  205. package/src/commands/auth/index.ts +20 -0
  206. package/src/commands/auth/login.ts +232 -0
  207. package/src/commands/auth/logout.ts +105 -0
  208. package/src/commands/broadcasts/create.ts +196 -0
  209. package/src/commands/broadcasts/delete.ts +46 -0
  210. package/src/commands/broadcasts/get.ts +59 -0
  211. package/src/commands/broadcasts/index.ts +43 -0
  212. package/src/commands/broadcasts/list.ts +60 -0
  213. package/src/commands/broadcasts/send.ts +56 -0
  214. package/src/commands/broadcasts/update.ts +95 -0
  215. package/src/commands/broadcasts/utils.ts +35 -0
  216. package/src/commands/contact-properties/create.ts +118 -0
  217. package/src/commands/contact-properties/delete.ts +48 -0
  218. package/src/commands/contact-properties/get.ts +46 -0
  219. package/src/commands/contact-properties/index.ts +48 -0
  220. package/src/commands/contact-properties/list.ts +68 -0
  221. package/src/commands/contact-properties/update.ts +88 -0
  222. package/src/commands/contact-properties/utils.ts +17 -0
  223. package/src/commands/contacts/add-segment.ts +78 -0
  224. package/src/commands/contacts/create.ts +122 -0
  225. package/src/commands/contacts/delete.ts +49 -0
  226. package/src/commands/contacts/get.ts +53 -0
  227. package/src/commands/contacts/index.ts +58 -0
  228. package/src/commands/contacts/list.ts +57 -0
  229. package/src/commands/contacts/remove-segment.ts +48 -0
  230. package/src/commands/contacts/segments.ts +39 -0
  231. package/src/commands/contacts/topics.ts +45 -0
  232. package/src/commands/contacts/update-topics.ts +90 -0
  233. package/src/commands/contacts/update.ts +77 -0
  234. package/src/commands/contacts/utils.ts +119 -0
  235. package/src/commands/doctor.ts +298 -0
  236. package/src/commands/domains/create.ts +83 -0
  237. package/src/commands/domains/delete.ts +42 -0
  238. package/src/commands/domains/get.ts +47 -0
  239. package/src/commands/domains/index.ts +35 -0
  240. package/src/commands/domains/list.ts +53 -0
  241. package/src/commands/domains/update.ts +75 -0
  242. package/src/commands/domains/utils.ts +44 -0
  243. package/src/commands/domains/verify.ts +38 -0
  244. package/src/commands/emails/batch.ts +140 -0
  245. package/src/commands/emails/index.ts +24 -0
  246. package/src/commands/emails/receiving/attachment.ts +55 -0
  247. package/src/commands/emails/receiving/attachments.ts +68 -0
  248. package/src/commands/emails/receiving/get.ts +58 -0
  249. package/src/commands/emails/receiving/index.ts +28 -0
  250. package/src/commands/emails/receiving/list.ts +59 -0
  251. package/src/commands/emails/receiving/utils.ts +38 -0
  252. package/src/commands/emails/send.ts +189 -0
  253. package/src/commands/open.ts +24 -0
  254. package/src/commands/segments/create.ts +50 -0
  255. package/src/commands/segments/delete.ts +47 -0
  256. package/src/commands/segments/get.ts +38 -0
  257. package/src/commands/segments/index.ts +36 -0
  258. package/src/commands/segments/list.ts +58 -0
  259. package/src/commands/segments/utils.ts +7 -0
  260. package/src/commands/teams/index.ts +10 -0
  261. package/src/commands/teams/list.ts +35 -0
  262. package/src/commands/teams/remove.ts +86 -0
  263. package/src/commands/teams/switch.ts +76 -0
  264. package/src/commands/topics/create.ts +73 -0
  265. package/src/commands/topics/delete.ts +47 -0
  266. package/src/commands/topics/get.ts +42 -0
  267. package/src/commands/topics/index.ts +42 -0
  268. package/src/commands/topics/list.ts +34 -0
  269. package/src/commands/topics/update.ts +59 -0
  270. package/src/commands/topics/utils.ts +16 -0
  271. package/src/commands/webhooks/create.ts +128 -0
  272. package/src/commands/webhooks/delete.ts +49 -0
  273. package/src/commands/webhooks/get.ts +42 -0
  274. package/src/commands/webhooks/index.ts +44 -0
  275. package/src/commands/webhooks/list.ts +55 -0
  276. package/src/commands/webhooks/update.ts +83 -0
  277. package/src/commands/webhooks/utils.ts +36 -0
  278. package/src/commands/whoami.ts +71 -0
  279. package/src/lib/actions.ts +157 -0
  280. package/src/lib/client.ts +34 -0
  281. package/src/lib/config.ts +218 -0
  282. package/src/lib/files.ts +15 -0
  283. package/src/lib/help-text.ts +38 -0
  284. package/src/lib/output.ts +54 -0
  285. package/src/lib/pagination.ts +36 -0
  286. package/src/lib/prompts.ts +149 -0
  287. package/src/lib/spinner.ts +93 -0
  288. package/src/lib/table.ts +57 -0
  289. package/src/lib/tty.ts +28 -0
  290. package/src/lib/version.ts +4 -0
  291. package/tests/commands/api-keys/create.test.ts +195 -0
  292. package/tests/commands/api-keys/delete.test.ts +156 -0
  293. package/tests/commands/api-keys/list.test.ts +133 -0
  294. package/tests/commands/auth/login.test.ts +154 -0
  295. package/tests/commands/auth/logout.test.ts +146 -0
  296. package/tests/commands/broadcasts/create.test.ts +447 -0
  297. package/tests/commands/broadcasts/delete.test.ts +182 -0
  298. package/tests/commands/broadcasts/get.test.ts +146 -0
  299. package/tests/commands/broadcasts/list.test.ts +196 -0
  300. package/tests/commands/broadcasts/send.test.ts +161 -0
  301. package/tests/commands/broadcasts/update.test.ts +283 -0
  302. package/tests/commands/contact-properties/create.test.ts +250 -0
  303. package/tests/commands/contact-properties/delete.test.ts +183 -0
  304. package/tests/commands/contact-properties/get.test.ts +144 -0
  305. package/tests/commands/contact-properties/list.test.ts +180 -0
  306. package/tests/commands/contact-properties/update.test.ts +216 -0
  307. package/tests/commands/contacts/add-segment.test.ts +188 -0
  308. package/tests/commands/contacts/create.test.ts +270 -0
  309. package/tests/commands/contacts/delete.test.ts +192 -0
  310. package/tests/commands/contacts/get.test.ts +148 -0
  311. package/tests/commands/contacts/list.test.ts +175 -0
  312. package/tests/commands/contacts/remove-segment.test.ts +166 -0
  313. package/tests/commands/contacts/segments.test.ts +167 -0
  314. package/tests/commands/contacts/topics.test.ts +163 -0
  315. package/tests/commands/contacts/update-topics.test.ts +247 -0
  316. package/tests/commands/contacts/update.test.ts +205 -0
  317. package/tests/commands/doctor.test.ts +165 -0
  318. package/tests/commands/domains/create.test.ts +192 -0
  319. package/tests/commands/domains/delete.test.ts +156 -0
  320. package/tests/commands/domains/get.test.ts +137 -0
  321. package/tests/commands/domains/list.test.ts +164 -0
  322. package/tests/commands/domains/update.test.ts +223 -0
  323. package/tests/commands/domains/verify.test.ts +117 -0
  324. package/tests/commands/emails/batch.test.ts +313 -0
  325. package/tests/commands/emails/receiving/attachment.test.ts +140 -0
  326. package/tests/commands/emails/receiving/attachments.test.ts +168 -0
  327. package/tests/commands/emails/receiving/get.test.ts +140 -0
  328. package/tests/commands/emails/receiving/list.test.ts +181 -0
  329. package/tests/commands/emails/send.test.ts +309 -0
  330. package/tests/commands/segments/create.test.ts +163 -0
  331. package/tests/commands/segments/delete.test.ts +182 -0
  332. package/tests/commands/segments/get.test.ts +137 -0
  333. package/tests/commands/segments/list.test.ts +173 -0
  334. package/tests/commands/teams/list.test.ts +63 -0
  335. package/tests/commands/teams/remove.test.ts +103 -0
  336. package/tests/commands/teams/switch.test.ts +96 -0
  337. package/tests/commands/topics/create.test.ts +191 -0
  338. package/tests/commands/topics/delete.test.ts +156 -0
  339. package/tests/commands/topics/get.test.ts +125 -0
  340. package/tests/commands/topics/list.test.ts +124 -0
  341. package/tests/commands/topics/update.test.ts +177 -0
  342. package/tests/commands/webhooks/create.test.ts +224 -0
  343. package/tests/commands/webhooks/delete.test.ts +156 -0
  344. package/tests/commands/webhooks/get.test.ts +125 -0
  345. package/tests/commands/webhooks/list.test.ts +177 -0
  346. package/tests/commands/webhooks/update.test.ts +206 -0
  347. package/tests/commands/whoami.test.ts +99 -0
  348. package/tests/helpers.ts +93 -0
  349. package/tests/lib/client.test.ts +71 -0
  350. package/tests/lib/config.test.ts +447 -0
  351. package/tests/lib/files.test.ts +65 -0
  352. package/tests/lib/help-text.test.ts +97 -0
  353. package/tests/lib/output.test.ts +127 -0
  354. package/tests/lib/prompts.test.ts +178 -0
  355. package/tests/lib/spinner.test.ts +146 -0
  356. package/tests/lib/table.test.ts +63 -0
  357. package/tests/lib/tty.test.ts +85 -0
  358. package/tsconfig.json +14 -0
  359. package/src/index.js +0 -72
  360. package/src/routes.js +0 -37
  361. package/src/sections/apikeys.js +0 -99
  362. package/src/sections/audiences.js +0 -84
  363. package/src/sections/contacts.js +0 -177
  364. package/src/sections/domain.js +0 -195
  365. package/src/sections/email.js +0 -132
@@ -0,0 +1,54 @@
1
+ export function errorMessage(err: unknown, fallback: string): string {
2
+ return err instanceof Error ? err.message : fallback;
3
+ }
4
+
5
+ export interface OutputOptions {
6
+ json?: boolean;
7
+ exitCode?: number;
8
+ }
9
+
10
+ function shouldOutputJson(json?: boolean): boolean {
11
+ if (json) {
12
+ return true;
13
+ }
14
+ if (!process.stdout.isTTY) {
15
+ return true;
16
+ }
17
+ return false;
18
+ }
19
+
20
+ export function outputResult(data: unknown, opts: OutputOptions = {}): void {
21
+ if (shouldOutputJson(opts.json)) {
22
+ console.log(JSON.stringify(data, null, 2));
23
+ } else {
24
+ if (typeof data === 'string') {
25
+ console.log(data);
26
+ } else {
27
+ console.log(JSON.stringify(data, null, 2));
28
+ }
29
+ }
30
+ if (opts.exitCode !== undefined) {
31
+ process.exit(opts.exitCode);
32
+ }
33
+ }
34
+
35
+ export function outputError(
36
+ error: { message: string; code?: string },
37
+ opts: OutputOptions = {},
38
+ ): never {
39
+ const exitCode = opts.exitCode ?? 1;
40
+
41
+ if (shouldOutputJson(opts.json)) {
42
+ console.error(
43
+ JSON.stringify(
44
+ { error: { message: error.message, code: error.code ?? 'unknown' } },
45
+ null,
46
+ 2,
47
+ ),
48
+ );
49
+ } else {
50
+ console.error(`Error: ${error.message}`);
51
+ }
52
+
53
+ process.exit(exitCode);
54
+ }
@@ -0,0 +1,36 @@
1
+ import type { GlobalOpts } from './client';
2
+ import { outputError } from './output';
3
+
4
+ export function parseLimitOpt(raw: string, globalOpts: GlobalOpts): number {
5
+ const limit = parseInt(raw, 10);
6
+ if (Number.isNaN(limit) || limit < 1 || limit > 100) {
7
+ outputError(
8
+ {
9
+ message: '--limit must be an integer between 1 and 100',
10
+ code: 'invalid_limit',
11
+ },
12
+ { json: globalOpts.json },
13
+ );
14
+ }
15
+ return limit;
16
+ }
17
+
18
+ export function buildPaginationOpts(
19
+ limit: number,
20
+ after?: string,
21
+ before?: string,
22
+ ) {
23
+ return after ? { limit, after } : before ? { limit, before } : { limit };
24
+ }
25
+
26
+ export function printPaginationHint(list: {
27
+ has_more: boolean;
28
+ data: Array<{ id: string }>;
29
+ }): void {
30
+ if (list.has_more && list.data.length > 0) {
31
+ const last = list.data[list.data.length - 1];
32
+ console.log(
33
+ `\nMore results available. Use --after ${last.id} to fetch the next page.`,
34
+ );
35
+ }
36
+ }
@@ -0,0 +1,149 @@
1
+ import * as p from '@clack/prompts';
2
+ import type { GlobalOpts } from './client';
3
+ import { outputError } from './output';
4
+ import { isInteractive } from './tty';
5
+
6
+ export interface FieldSpec {
7
+ flag: string;
8
+ message: string;
9
+ placeholder?: string;
10
+ required?: boolean;
11
+ validate?: (value: string | undefined) => string | undefined;
12
+ }
13
+
14
+ export function cancelAndExit(message: string): never {
15
+ p.cancel(message);
16
+ process.exit(0);
17
+ }
18
+
19
+ /**
20
+ * Guard a delete action: error in non-interactive mode (no --yes), or show a
21
+ * confirmation prompt in interactive mode. Exits the process on cancel/rejection.
22
+ */
23
+ export async function confirmDelete(
24
+ _id: string,
25
+ confirmMessage: string,
26
+ globalOpts: GlobalOpts,
27
+ ): Promise<void> {
28
+ if (!isInteractive()) {
29
+ outputError(
30
+ {
31
+ message: 'Use --yes to confirm deletion in non-interactive mode.',
32
+ code: 'confirmation_required',
33
+ },
34
+ { json: globalOpts.json },
35
+ );
36
+ }
37
+
38
+ const confirmed = await p.confirm({ message: confirmMessage });
39
+ if (p.isCancel(confirmed) || !confirmed) {
40
+ cancelAndExit('Deletion cancelled.');
41
+ }
42
+ }
43
+
44
+ export async function requireText(
45
+ value: string | undefined,
46
+ prompt: {
47
+ message: string;
48
+ placeholder?: string;
49
+ validate?: (value: string | undefined) => string | Error | undefined;
50
+ },
51
+ error: { message: string; code: string },
52
+ globalOpts: GlobalOpts,
53
+ ): Promise<string> {
54
+ if (value) {
55
+ return value;
56
+ }
57
+
58
+ if (!isInteractive()) {
59
+ outputError(error, { json: globalOpts.json });
60
+ }
61
+
62
+ const result = await p.text({
63
+ message: prompt.message,
64
+ placeholder: prompt.placeholder,
65
+ validate:
66
+ prompt.validate ??
67
+ ((v) =>
68
+ !v || v.length === 0 ? `${prompt.message} is required` : undefined),
69
+ });
70
+ if (p.isCancel(result)) {
71
+ cancelAndExit('Cancelled.');
72
+ }
73
+ return result;
74
+ }
75
+
76
+ export async function requireSelect<V extends string>(
77
+ value: V | undefined,
78
+ prompt: {
79
+ message: string;
80
+ options: Parameters<typeof p.select<V>>[0]['options'];
81
+ },
82
+ error: { message: string; code: string },
83
+ globalOpts: GlobalOpts,
84
+ ): Promise<V> {
85
+ if (value) {
86
+ return value;
87
+ }
88
+
89
+ if (!isInteractive()) {
90
+ outputError(error, { json: globalOpts.json });
91
+ }
92
+
93
+ const result = await p.select<V>({
94
+ message: prompt.message,
95
+ options: prompt.options,
96
+ });
97
+ if (p.isCancel(result)) {
98
+ cancelAndExit('Cancelled.');
99
+ }
100
+ return result;
101
+ }
102
+
103
+ export async function promptForMissing<
104
+ T extends Record<string, string | undefined>,
105
+ >(
106
+ current: T,
107
+ fields: FieldSpec[],
108
+ globalOpts: GlobalOpts,
109
+ ): Promise<{ [K in keyof T]: string }> {
110
+ const missing = fields.filter(
111
+ (f) => f.required !== false && !current[f.flag],
112
+ );
113
+
114
+ if (missing.length === 0) {
115
+ return current as { [K in keyof T]: string };
116
+ }
117
+
118
+ if (!isInteractive()) {
119
+ const flags = missing.map((f) => `--${f.flag}`).join(', ');
120
+ outputError(
121
+ { message: `Missing required flags: ${flags}`, code: 'missing_flags' },
122
+ { json: globalOpts.json },
123
+ );
124
+ }
125
+
126
+ const result = await p.group(
127
+ Object.fromEntries(
128
+ missing.map((field) => [
129
+ field.flag,
130
+ () =>
131
+ p.text({
132
+ message: field.message,
133
+ placeholder: field.placeholder,
134
+ validate:
135
+ field.validate ??
136
+ ((v) =>
137
+ !v || v.length === 0
138
+ ? `${field.message} is required`
139
+ : undefined),
140
+ }),
141
+ ]),
142
+ ),
143
+ {
144
+ onCancel: () => cancelAndExit('Operation cancelled.'),
145
+ },
146
+ );
147
+
148
+ return { ...current, ...result } as { [K in keyof T]: string };
149
+ }
@@ -0,0 +1,93 @@
1
+ import spinners from 'unicode-animations';
2
+ import type { GlobalOpts } from './client';
3
+ import { errorMessage, outputError } from './output';
4
+ import { isInteractive, isUnicodeSupported } from './tty';
5
+
6
+ // Status symbols generated via String.fromCodePoint() — never literal Unicode in
7
+ // source — to prevent UTF-8 → Latin-1 corruption when the npm package is bundled.
8
+ const TICK = isUnicodeSupported ? String.fromCodePoint(0x2714) : 'v'; // ✔
9
+ const WARN = isUnicodeSupported ? String.fromCodePoint(0x26a0) : '!'; // ⚠
10
+ const CROSS = isUnicodeSupported ? String.fromCodePoint(0x2717) : 'x'; // ✗
11
+
12
+ type SdkResponse<T> = { data: T | null; error: { message: string } | null };
13
+
14
+ /**
15
+ * Wraps an SDK call with a spinner, unified error handling, and automatic stop/fail.
16
+ * Eliminates the repeated try/catch + spinner boilerplate across all command files.
17
+ */
18
+ export async function withSpinner<T>(
19
+ messages: { loading: string; success: string; fail: string },
20
+ call: () => Promise<SdkResponse<T>>,
21
+ errorCode: string,
22
+ globalOpts: GlobalOpts,
23
+ ): Promise<T> {
24
+ const spinner = createSpinner(messages.loading, 'braille', globalOpts.quiet);
25
+ try {
26
+ const { data, error } = await call();
27
+ if (error) {
28
+ spinner.fail(messages.fail);
29
+ outputError(
30
+ { message: error.message, code: errorCode },
31
+ { json: globalOpts.json },
32
+ );
33
+ }
34
+ if (data === null) {
35
+ spinner.fail(messages.fail);
36
+ outputError(
37
+ { message: 'Unexpected empty response', code: errorCode },
38
+ { json: globalOpts.json },
39
+ );
40
+ }
41
+ spinner.stop(messages.success);
42
+ return data;
43
+ } catch (err) {
44
+ spinner.fail(messages.fail);
45
+ return outputError(
46
+ { message: errorMessage(err, 'Unknown error'), code: errorCode },
47
+ { json: globalOpts.json },
48
+ );
49
+ }
50
+ }
51
+
52
+ export type SpinnerName = keyof typeof spinners;
53
+
54
+ export function createSpinner(
55
+ message: string,
56
+ name: SpinnerName = 'braille',
57
+ quiet?: boolean,
58
+ ) {
59
+ if (quiet || !isInteractive()) {
60
+ return {
61
+ update(_msg: string) {},
62
+ stop(_msg: string) {},
63
+ warn(_msg: string) {},
64
+ fail(_msg: string) {},
65
+ };
66
+ }
67
+
68
+ const { frames, interval } = spinners[name];
69
+ let i = 0;
70
+ let text = message;
71
+
72
+ const timer = setInterval(() => {
73
+ process.stderr.write(`\r\x1B[2K ${frames[i++ % frames.length]} ${text}`);
74
+ }, interval);
75
+
76
+ return {
77
+ update(msg: string) {
78
+ text = msg;
79
+ },
80
+ stop(msg: string) {
81
+ clearInterval(timer);
82
+ process.stderr.write(`\r\x1B[2K ${TICK} ${msg}\n`);
83
+ },
84
+ warn(msg: string) {
85
+ clearInterval(timer);
86
+ process.stderr.write(`\r\x1B[2K ${WARN} ${msg}\n`);
87
+ },
88
+ fail(msg: string) {
89
+ clearInterval(timer);
90
+ process.stderr.write(`\r\x1B[2K ${CROSS} ${msg}\n`);
91
+ },
92
+ };
93
+ }
@@ -0,0 +1,57 @@
1
+ import { isUnicodeSupported } from './tty';
2
+
3
+ // All box-drawing characters generated via String.fromCodePoint() — never literal
4
+ // Unicode in source — to prevent UTF-8 → Latin-1 corruption in npm bundles.
5
+ const BOX = isUnicodeSupported
6
+ ? {
7
+ h: String.fromCodePoint(0x2500), // ─
8
+ v: String.fromCodePoint(0x2502), // │
9
+ tl: String.fromCodePoint(0x250c), // ┌
10
+ tr: String.fromCodePoint(0x2510), // ┐
11
+ bl: String.fromCodePoint(0x2514), // └
12
+ br: String.fromCodePoint(0x2518), // ┘
13
+ lm: String.fromCodePoint(0x251c), // ├
14
+ rm: String.fromCodePoint(0x2524), // ┤
15
+ tm: String.fromCodePoint(0x252c), // ┬
16
+ bm: String.fromCodePoint(0x2534), // ┴
17
+ mm: String.fromCodePoint(0x253c), // ┼
18
+ }
19
+ : {
20
+ h: '-',
21
+ v: '|',
22
+ tl: '+',
23
+ tr: '+',
24
+ bl: '+',
25
+ br: '+',
26
+ lm: '+',
27
+ rm: '+',
28
+ tm: '+',
29
+ bm: '+',
30
+ mm: '+',
31
+ };
32
+
33
+ export function renderTable(
34
+ headers: string[],
35
+ rows: string[][],
36
+ emptyMessage = '(no results)',
37
+ ): string {
38
+ if (rows.length === 0) {
39
+ return emptyMessage;
40
+ }
41
+ const widths = headers.map((h, i) =>
42
+ Math.max(h.length, ...rows.map((r) => r[i].length)),
43
+ );
44
+ const top =
45
+ BOX.tl + widths.map((w) => BOX.h.repeat(w + 2)).join(BOX.tm) + BOX.tr;
46
+ const mid =
47
+ BOX.lm + widths.map((w) => BOX.h.repeat(w + 2)).join(BOX.mm) + BOX.rm;
48
+ const bot =
49
+ BOX.bl + widths.map((w) => BOX.h.repeat(w + 2)).join(BOX.bm) + BOX.br;
50
+ const row = (cells: string[]) =>
51
+ BOX.v +
52
+ ' ' +
53
+ cells.map((c, i) => c.padEnd(widths[i])).join(` ${BOX.v} `) +
54
+ ' ' +
55
+ BOX.v;
56
+ return [top, row(headers), mid, ...rows.map(row), bot].join('\n');
57
+ }
package/src/lib/tty.ts ADDED
@@ -0,0 +1,28 @@
1
+ export function isInteractive(): boolean {
2
+ if (!process.stdin.isTTY || !process.stdout.isTTY) {
3
+ return false;
4
+ }
5
+ if (process.env.CI === 'true' || process.env.CI === '1') {
6
+ return false;
7
+ }
8
+ if (process.env.GITHUB_ACTIONS) {
9
+ return false;
10
+ }
11
+ if (process.env.TERM === 'dumb') {
12
+ return false;
13
+ }
14
+ return true;
15
+ }
16
+
17
+ /**
18
+ * True on macOS/Linux and on Windows terminals that support Unicode
19
+ * (Windows Terminal, VS Code integrated terminal).
20
+ * False on legacy Windows cmd.exe — callers should fall back to ASCII symbols.
21
+ *
22
+ * Generated via String.fromCodePoint() (never literal Unicode in source)
23
+ * to prevent UTF-8 → Latin-1 corruption when the npm package is bundled.
24
+ */
25
+ export const isUnicodeSupported: boolean =
26
+ process.platform !== 'win32' ||
27
+ Boolean(process.env.WT_SESSION) ||
28
+ process.env.TERM_PROGRAM === 'vscode';
@@ -0,0 +1,4 @@
1
+ import pkg from '../../package.json';
2
+
3
+ export const VERSION: string = pkg.version;
4
+ export const PACKAGE_NAME: string = pkg.name;
@@ -0,0 +1,195 @@
1
+ import {
2
+ afterEach,
3
+ beforeEach,
4
+ describe,
5
+ expect,
6
+ mock,
7
+ spyOn,
8
+ test,
9
+ } from 'bun:test';
10
+ import {
11
+ captureTestEnv,
12
+ expectExit1,
13
+ mockExitThrow,
14
+ mockSdkError,
15
+ setNonInteractive,
16
+ setupOutputSpies,
17
+ } from '../../helpers';
18
+
19
+ const mockCreate = mock(async () => ({
20
+ data: { id: 'test-key-id', token: 're_testtoken1234567890' },
21
+ error: null,
22
+ }));
23
+
24
+ mock.module('resend', () => ({
25
+ Resend: class MockResend {
26
+ constructor(public key: string) {}
27
+ apiKeys = { create: mockCreate };
28
+ },
29
+ }));
30
+
31
+ describe('api-keys create command', () => {
32
+ const restoreEnv = captureTestEnv();
33
+ let spies: ReturnType<typeof setupOutputSpies> | undefined;
34
+ let errorSpy: ReturnType<typeof spyOn> | undefined;
35
+ let stderrSpy: ReturnType<typeof spyOn> | undefined;
36
+ let exitSpy: ReturnType<typeof spyOn> | undefined;
37
+
38
+ beforeEach(() => {
39
+ process.env.RESEND_API_KEY = 're_test_key';
40
+ mockCreate.mockClear();
41
+ });
42
+
43
+ afterEach(() => {
44
+ restoreEnv();
45
+ spies?.restore();
46
+ errorSpy?.mockRestore();
47
+ stderrSpy?.mockRestore();
48
+ exitSpy?.mockRestore();
49
+ spies = undefined;
50
+ errorSpy = undefined;
51
+ stderrSpy = undefined;
52
+ exitSpy = undefined;
53
+ });
54
+
55
+ test('creates API key with --name flag', async () => {
56
+ spies = setupOutputSpies();
57
+
58
+ const { createApiKeyCommand } = await import(
59
+ '../../../src/commands/api-keys/create'
60
+ );
61
+ await createApiKeyCommand.parseAsync(['--name', 'Production'], {
62
+ from: 'user',
63
+ });
64
+
65
+ expect(mockCreate).toHaveBeenCalledTimes(1);
66
+ const args = mockCreate.mock.calls[0][0] as Record<string, unknown>;
67
+ expect(args.name).toBe('Production');
68
+ });
69
+
70
+ test('passes permission flag to SDK', async () => {
71
+ spies = setupOutputSpies();
72
+
73
+ const { createApiKeyCommand } = await import(
74
+ '../../../src/commands/api-keys/create'
75
+ );
76
+ await createApiKeyCommand.parseAsync(
77
+ ['--name', 'CI Token', '--permission', 'sending_access'],
78
+ { from: 'user' },
79
+ );
80
+
81
+ const args = mockCreate.mock.calls[0][0] as Record<string, unknown>;
82
+ expect(args.permission).toBe('sending_access');
83
+ });
84
+
85
+ test('passes domain_id (snake_case) to SDK when --domain-id is provided', async () => {
86
+ spies = setupOutputSpies();
87
+
88
+ const { createApiKeyCommand } = await import(
89
+ '../../../src/commands/api-keys/create'
90
+ );
91
+ await createApiKeyCommand.parseAsync(
92
+ [
93
+ '--name',
94
+ 'Domain Token',
95
+ '--permission',
96
+ 'sending_access',
97
+ '--domain-id',
98
+ 'domain-123',
99
+ ],
100
+ { from: 'user' },
101
+ );
102
+
103
+ const args = mockCreate.mock.calls[0][0] as Record<string, unknown>;
104
+ expect(args.domain_id).toBe('domain-123');
105
+ });
106
+
107
+ test('outputs JSON result when non-interactive', async () => {
108
+ spies = setupOutputSpies();
109
+
110
+ const { createApiKeyCommand } = await import(
111
+ '../../../src/commands/api-keys/create'
112
+ );
113
+ await createApiKeyCommand.parseAsync(['--name', 'Production'], {
114
+ from: 'user',
115
+ });
116
+
117
+ const output = spies.logSpy.mock.calls[0][0] as string;
118
+ const parsed = JSON.parse(output);
119
+ expect(parsed.id).toBe('test-key-id');
120
+ expect(parsed.token).toBe('re_testtoken1234567890');
121
+ });
122
+
123
+ test('errors with missing_name when --name absent in non-interactive mode', async () => {
124
+ setNonInteractive();
125
+ errorSpy = spyOn(console, 'error').mockImplementation(() => {});
126
+ exitSpy = mockExitThrow();
127
+
128
+ const { createApiKeyCommand } = await import(
129
+ '../../../src/commands/api-keys/create'
130
+ );
131
+ await expectExit1(() =>
132
+ createApiKeyCommand.parseAsync([], { from: 'user' }),
133
+ );
134
+
135
+ const output = errorSpy.mock.calls.map((c) => c[0]).join(' ');
136
+ expect(output).toContain('missing_name');
137
+ });
138
+
139
+ test('does not call SDK when missing_name error is raised', async () => {
140
+ setNonInteractive();
141
+ errorSpy = spyOn(console, 'error').mockImplementation(() => {});
142
+ exitSpy = mockExitThrow();
143
+
144
+ const { createApiKeyCommand } = await import(
145
+ '../../../src/commands/api-keys/create'
146
+ );
147
+ await expectExit1(() =>
148
+ createApiKeyCommand.parseAsync([], { from: 'user' }),
149
+ );
150
+
151
+ expect(mockCreate).not.toHaveBeenCalled();
152
+ });
153
+
154
+ test('errors with auth_error when no API key', async () => {
155
+ setNonInteractive();
156
+ delete process.env.RESEND_API_KEY;
157
+ process.env.XDG_CONFIG_HOME = '/tmp/nonexistent-resend';
158
+ errorSpy = spyOn(console, 'error').mockImplementation(() => {});
159
+ exitSpy = mockExitThrow();
160
+
161
+ const { createApiKeyCommand } = await import(
162
+ '../../../src/commands/api-keys/create'
163
+ );
164
+ await expectExit1(() =>
165
+ createApiKeyCommand.parseAsync(['--name', 'Production'], {
166
+ from: 'user',
167
+ }),
168
+ );
169
+
170
+ const output = errorSpy.mock.calls.map((c) => c[0]).join(' ');
171
+ expect(output).toContain('auth_error');
172
+ });
173
+
174
+ test('errors with create_error when SDK returns an error', async () => {
175
+ setNonInteractive();
176
+ mockCreate.mockResolvedValueOnce(
177
+ mockSdkError('Name already taken', 'validation_error'),
178
+ );
179
+ errorSpy = spyOn(console, 'error').mockImplementation(() => {});
180
+ stderrSpy = spyOn(process.stderr, 'write').mockImplementation(() => true);
181
+ exitSpy = mockExitThrow();
182
+
183
+ const { createApiKeyCommand } = await import(
184
+ '../../../src/commands/api-keys/create'
185
+ );
186
+ await expectExit1(() =>
187
+ createApiKeyCommand.parseAsync(['--name', 'Production'], {
188
+ from: 'user',
189
+ }),
190
+ );
191
+
192
+ const output = errorSpy.mock.calls.map((c) => c[0]).join(' ');
193
+ expect(output).toContain('create_error');
194
+ });
195
+ });