resend-cli 1.2.0 → 1.2.2

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 (193) hide show
  1. package/.github/workflows/release.yml +35 -8
  2. package/README.md +12 -1
  3. package/biome.json +1 -1
  4. package/bun.lock +0 -3
  5. package/package.json +3 -4
  6. package/src/cli.ts +23 -5
  7. package/src/commands/auth/login.ts +10 -8
  8. package/src/commands/doctor.ts +30 -112
  9. package/src/lib/client.ts +3 -0
  10. package/src/lib/config.ts +2 -3
  11. package/src/lib/spinner.ts +17 -10
  12. package/src/lib/update-check.ts +172 -0
  13. package/tests/commands/auth/login.test.ts +3 -1
  14. package/tests/lib/config.test.ts +4 -6
  15. package/tests/lib/update-check.test.ts +169 -0
  16. package/.claude/worktrees/emails-list/.claude/settings.local.json +0 -5
  17. package/.claude/worktrees/emails-list/.github/scripts/pr-title-check.js +0 -34
  18. package/.claude/worktrees/emails-list/.github/workflows/ci.yml +0 -32
  19. package/.claude/worktrees/emails-list/.github/workflows/pr-title-check.yml +0 -13
  20. package/.claude/worktrees/emails-list/.github/workflows/release.yml +0 -93
  21. package/.claude/worktrees/emails-list/CHANGELOG.md +0 -31
  22. package/.claude/worktrees/emails-list/LICENSE +0 -21
  23. package/.claude/worktrees/emails-list/README.md +0 -424
  24. package/.claude/worktrees/emails-list/biome.json +0 -36
  25. package/.claude/worktrees/emails-list/bun.lock +0 -76
  26. package/.claude/worktrees/emails-list/bunfig.toml +0 -2
  27. package/.claude/worktrees/emails-list/install.ps1 +0 -140
  28. package/.claude/worktrees/emails-list/install.sh +0 -301
  29. package/.claude/worktrees/emails-list/package.json +0 -43
  30. package/.claude/worktrees/emails-list/renovate.json +0 -6
  31. package/.claude/worktrees/emails-list/src/cli.ts +0 -74
  32. package/.claude/worktrees/emails-list/src/commands/api-keys/create.ts +0 -114
  33. package/.claude/worktrees/emails-list/src/commands/api-keys/delete.ts +0 -47
  34. package/.claude/worktrees/emails-list/src/commands/api-keys/index.ts +0 -26
  35. package/.claude/worktrees/emails-list/src/commands/api-keys/list.ts +0 -35
  36. package/.claude/worktrees/emails-list/src/commands/api-keys/utils.ts +0 -8
  37. package/.claude/worktrees/emails-list/src/commands/auth/index.ts +0 -20
  38. package/.claude/worktrees/emails-list/src/commands/auth/login.ts +0 -207
  39. package/.claude/worktrees/emails-list/src/commands/auth/logout.ts +0 -105
  40. package/.claude/worktrees/emails-list/src/commands/broadcasts/create.ts +0 -196
  41. package/.claude/worktrees/emails-list/src/commands/broadcasts/delete.ts +0 -46
  42. package/.claude/worktrees/emails-list/src/commands/broadcasts/get.ts +0 -59
  43. package/.claude/worktrees/emails-list/src/commands/broadcasts/index.ts +0 -43
  44. package/.claude/worktrees/emails-list/src/commands/broadcasts/list.ts +0 -60
  45. package/.claude/worktrees/emails-list/src/commands/broadcasts/send.ts +0 -56
  46. package/.claude/worktrees/emails-list/src/commands/broadcasts/update.ts +0 -95
  47. package/.claude/worktrees/emails-list/src/commands/broadcasts/utils.ts +0 -35
  48. package/.claude/worktrees/emails-list/src/commands/contact-properties/create.ts +0 -118
  49. package/.claude/worktrees/emails-list/src/commands/contact-properties/delete.ts +0 -48
  50. package/.claude/worktrees/emails-list/src/commands/contact-properties/get.ts +0 -46
  51. package/.claude/worktrees/emails-list/src/commands/contact-properties/index.ts +0 -48
  52. package/.claude/worktrees/emails-list/src/commands/contact-properties/list.ts +0 -68
  53. package/.claude/worktrees/emails-list/src/commands/contact-properties/update.ts +0 -88
  54. package/.claude/worktrees/emails-list/src/commands/contact-properties/utils.ts +0 -17
  55. package/.claude/worktrees/emails-list/src/commands/contacts/add-segment.ts +0 -78
  56. package/.claude/worktrees/emails-list/src/commands/contacts/create.ts +0 -122
  57. package/.claude/worktrees/emails-list/src/commands/contacts/delete.ts +0 -49
  58. package/.claude/worktrees/emails-list/src/commands/contacts/get.ts +0 -53
  59. package/.claude/worktrees/emails-list/src/commands/contacts/index.ts +0 -58
  60. package/.claude/worktrees/emails-list/src/commands/contacts/list.ts +0 -57
  61. package/.claude/worktrees/emails-list/src/commands/contacts/remove-segment.ts +0 -48
  62. package/.claude/worktrees/emails-list/src/commands/contacts/segments.ts +0 -39
  63. package/.claude/worktrees/emails-list/src/commands/contacts/topics.ts +0 -45
  64. package/.claude/worktrees/emails-list/src/commands/contacts/update-topics.ts +0 -90
  65. package/.claude/worktrees/emails-list/src/commands/contacts/update.ts +0 -77
  66. package/.claude/worktrees/emails-list/src/commands/contacts/utils.ts +0 -119
  67. package/.claude/worktrees/emails-list/src/commands/doctor.ts +0 -298
  68. package/.claude/worktrees/emails-list/src/commands/domains/create.ts +0 -83
  69. package/.claude/worktrees/emails-list/src/commands/domains/delete.ts +0 -42
  70. package/.claude/worktrees/emails-list/src/commands/domains/get.ts +0 -47
  71. package/.claude/worktrees/emails-list/src/commands/domains/index.ts +0 -35
  72. package/.claude/worktrees/emails-list/src/commands/domains/list.ts +0 -53
  73. package/.claude/worktrees/emails-list/src/commands/domains/update.ts +0 -75
  74. package/.claude/worktrees/emails-list/src/commands/domains/utils.ts +0 -44
  75. package/.claude/worktrees/emails-list/src/commands/domains/verify.ts +0 -38
  76. package/.claude/worktrees/emails-list/src/commands/emails/batch.ts +0 -140
  77. package/.claude/worktrees/emails-list/src/commands/emails/index.ts +0 -28
  78. package/.claude/worktrees/emails-list/src/commands/emails/list.ts +0 -73
  79. package/.claude/worktrees/emails-list/src/commands/emails/receiving/attachment.ts +0 -55
  80. package/.claude/worktrees/emails-list/src/commands/emails/receiving/attachments.ts +0 -68
  81. package/.claude/worktrees/emails-list/src/commands/emails/receiving/get.ts +0 -58
  82. package/.claude/worktrees/emails-list/src/commands/emails/receiving/index.ts +0 -28
  83. package/.claude/worktrees/emails-list/src/commands/emails/receiving/list.ts +0 -59
  84. package/.claude/worktrees/emails-list/src/commands/emails/receiving/utils.ts +0 -38
  85. package/.claude/worktrees/emails-list/src/commands/emails/send.ts +0 -189
  86. package/.claude/worktrees/emails-list/src/commands/open.ts +0 -24
  87. package/.claude/worktrees/emails-list/src/commands/segments/create.ts +0 -50
  88. package/.claude/worktrees/emails-list/src/commands/segments/delete.ts +0 -47
  89. package/.claude/worktrees/emails-list/src/commands/segments/get.ts +0 -38
  90. package/.claude/worktrees/emails-list/src/commands/segments/index.ts +0 -36
  91. package/.claude/worktrees/emails-list/src/commands/segments/list.ts +0 -58
  92. package/.claude/worktrees/emails-list/src/commands/segments/utils.ts +0 -7
  93. package/.claude/worktrees/emails-list/src/commands/teams/index.ts +0 -10
  94. package/.claude/worktrees/emails-list/src/commands/teams/list.ts +0 -35
  95. package/.claude/worktrees/emails-list/src/commands/teams/remove.ts +0 -83
  96. package/.claude/worktrees/emails-list/src/commands/teams/switch.ts +0 -73
  97. package/.claude/worktrees/emails-list/src/commands/topics/create.ts +0 -73
  98. package/.claude/worktrees/emails-list/src/commands/topics/delete.ts +0 -47
  99. package/.claude/worktrees/emails-list/src/commands/topics/get.ts +0 -42
  100. package/.claude/worktrees/emails-list/src/commands/topics/index.ts +0 -42
  101. package/.claude/worktrees/emails-list/src/commands/topics/list.ts +0 -34
  102. package/.claude/worktrees/emails-list/src/commands/topics/update.ts +0 -59
  103. package/.claude/worktrees/emails-list/src/commands/topics/utils.ts +0 -16
  104. package/.claude/worktrees/emails-list/src/commands/webhooks/create.ts +0 -128
  105. package/.claude/worktrees/emails-list/src/commands/webhooks/delete.ts +0 -49
  106. package/.claude/worktrees/emails-list/src/commands/webhooks/get.ts +0 -42
  107. package/.claude/worktrees/emails-list/src/commands/webhooks/index.ts +0 -44
  108. package/.claude/worktrees/emails-list/src/commands/webhooks/list.ts +0 -55
  109. package/.claude/worktrees/emails-list/src/commands/webhooks/update.ts +0 -83
  110. package/.claude/worktrees/emails-list/src/commands/webhooks/utils.ts +0 -36
  111. package/.claude/worktrees/emails-list/src/commands/whoami.ts +0 -71
  112. package/.claude/worktrees/emails-list/src/lib/actions.ts +0 -157
  113. package/.claude/worktrees/emails-list/src/lib/client.ts +0 -34
  114. package/.claude/worktrees/emails-list/src/lib/config.ts +0 -211
  115. package/.claude/worktrees/emails-list/src/lib/files.ts +0 -15
  116. package/.claude/worktrees/emails-list/src/lib/help-text.ts +0 -38
  117. package/.claude/worktrees/emails-list/src/lib/output.ts +0 -54
  118. package/.claude/worktrees/emails-list/src/lib/pagination.ts +0 -36
  119. package/.claude/worktrees/emails-list/src/lib/prompts.ts +0 -149
  120. package/.claude/worktrees/emails-list/src/lib/spinner.ts +0 -93
  121. package/.claude/worktrees/emails-list/src/lib/table.ts +0 -57
  122. package/.claude/worktrees/emails-list/src/lib/tty.ts +0 -28
  123. package/.claude/worktrees/emails-list/src/lib/version.ts +0 -4
  124. package/.claude/worktrees/emails-list/tests/commands/api-keys/create.test.ts +0 -195
  125. package/.claude/worktrees/emails-list/tests/commands/api-keys/delete.test.ts +0 -156
  126. package/.claude/worktrees/emails-list/tests/commands/api-keys/list.test.ts +0 -133
  127. package/.claude/worktrees/emails-list/tests/commands/auth/login.test.ts +0 -119
  128. package/.claude/worktrees/emails-list/tests/commands/auth/logout.test.ts +0 -146
  129. package/.claude/worktrees/emails-list/tests/commands/broadcasts/create.test.ts +0 -447
  130. package/.claude/worktrees/emails-list/tests/commands/broadcasts/delete.test.ts +0 -182
  131. package/.claude/worktrees/emails-list/tests/commands/broadcasts/get.test.ts +0 -146
  132. package/.claude/worktrees/emails-list/tests/commands/broadcasts/list.test.ts +0 -196
  133. package/.claude/worktrees/emails-list/tests/commands/broadcasts/send.test.ts +0 -161
  134. package/.claude/worktrees/emails-list/tests/commands/broadcasts/update.test.ts +0 -283
  135. package/.claude/worktrees/emails-list/tests/commands/contact-properties/create.test.ts +0 -250
  136. package/.claude/worktrees/emails-list/tests/commands/contact-properties/delete.test.ts +0 -183
  137. package/.claude/worktrees/emails-list/tests/commands/contact-properties/get.test.ts +0 -144
  138. package/.claude/worktrees/emails-list/tests/commands/contact-properties/list.test.ts +0 -180
  139. package/.claude/worktrees/emails-list/tests/commands/contact-properties/update.test.ts +0 -216
  140. package/.claude/worktrees/emails-list/tests/commands/contacts/add-segment.test.ts +0 -188
  141. package/.claude/worktrees/emails-list/tests/commands/contacts/create.test.ts +0 -270
  142. package/.claude/worktrees/emails-list/tests/commands/contacts/delete.test.ts +0 -192
  143. package/.claude/worktrees/emails-list/tests/commands/contacts/get.test.ts +0 -148
  144. package/.claude/worktrees/emails-list/tests/commands/contacts/list.test.ts +0 -175
  145. package/.claude/worktrees/emails-list/tests/commands/contacts/remove-segment.test.ts +0 -166
  146. package/.claude/worktrees/emails-list/tests/commands/contacts/segments.test.ts +0 -167
  147. package/.claude/worktrees/emails-list/tests/commands/contacts/topics.test.ts +0 -163
  148. package/.claude/worktrees/emails-list/tests/commands/contacts/update-topics.test.ts +0 -247
  149. package/.claude/worktrees/emails-list/tests/commands/contacts/update.test.ts +0 -205
  150. package/.claude/worktrees/emails-list/tests/commands/doctor.test.ts +0 -165
  151. package/.claude/worktrees/emails-list/tests/commands/domains/create.test.ts +0 -192
  152. package/.claude/worktrees/emails-list/tests/commands/domains/delete.test.ts +0 -156
  153. package/.claude/worktrees/emails-list/tests/commands/domains/get.test.ts +0 -137
  154. package/.claude/worktrees/emails-list/tests/commands/domains/list.test.ts +0 -164
  155. package/.claude/worktrees/emails-list/tests/commands/domains/update.test.ts +0 -223
  156. package/.claude/worktrees/emails-list/tests/commands/domains/verify.test.ts +0 -117
  157. package/.claude/worktrees/emails-list/tests/commands/emails/batch.test.ts +0 -313
  158. package/.claude/worktrees/emails-list/tests/commands/emails/list.test.ts +0 -196
  159. package/.claude/worktrees/emails-list/tests/commands/emails/receiving/attachment.test.ts +0 -140
  160. package/.claude/worktrees/emails-list/tests/commands/emails/receiving/attachments.test.ts +0 -168
  161. package/.claude/worktrees/emails-list/tests/commands/emails/receiving/get.test.ts +0 -140
  162. package/.claude/worktrees/emails-list/tests/commands/emails/receiving/list.test.ts +0 -181
  163. package/.claude/worktrees/emails-list/tests/commands/emails/send.test.ts +0 -309
  164. package/.claude/worktrees/emails-list/tests/commands/segments/create.test.ts +0 -163
  165. package/.claude/worktrees/emails-list/tests/commands/segments/delete.test.ts +0 -182
  166. package/.claude/worktrees/emails-list/tests/commands/segments/get.test.ts +0 -137
  167. package/.claude/worktrees/emails-list/tests/commands/segments/list.test.ts +0 -173
  168. package/.claude/worktrees/emails-list/tests/commands/teams/list.test.ts +0 -63
  169. package/.claude/worktrees/emails-list/tests/commands/teams/remove.test.ts +0 -103
  170. package/.claude/worktrees/emails-list/tests/commands/teams/switch.test.ts +0 -96
  171. package/.claude/worktrees/emails-list/tests/commands/topics/create.test.ts +0 -191
  172. package/.claude/worktrees/emails-list/tests/commands/topics/delete.test.ts +0 -156
  173. package/.claude/worktrees/emails-list/tests/commands/topics/get.test.ts +0 -125
  174. package/.claude/worktrees/emails-list/tests/commands/topics/list.test.ts +0 -124
  175. package/.claude/worktrees/emails-list/tests/commands/topics/update.test.ts +0 -177
  176. package/.claude/worktrees/emails-list/tests/commands/webhooks/create.test.ts +0 -224
  177. package/.claude/worktrees/emails-list/tests/commands/webhooks/delete.test.ts +0 -156
  178. package/.claude/worktrees/emails-list/tests/commands/webhooks/get.test.ts +0 -125
  179. package/.claude/worktrees/emails-list/tests/commands/webhooks/list.test.ts +0 -177
  180. package/.claude/worktrees/emails-list/tests/commands/webhooks/update.test.ts +0 -206
  181. package/.claude/worktrees/emails-list/tests/commands/whoami.test.ts +0 -99
  182. package/.claude/worktrees/emails-list/tests/helpers.ts +0 -93
  183. package/.claude/worktrees/emails-list/tests/lib/client.test.ts +0 -71
  184. package/.claude/worktrees/emails-list/tests/lib/config.test.ts +0 -414
  185. package/.claude/worktrees/emails-list/tests/lib/files.test.ts +0 -65
  186. package/.claude/worktrees/emails-list/tests/lib/help-text.test.ts +0 -97
  187. package/.claude/worktrees/emails-list/tests/lib/output.test.ts +0 -127
  188. package/.claude/worktrees/emails-list/tests/lib/prompts.test.ts +0 -178
  189. package/.claude/worktrees/emails-list/tests/lib/spinner.test.ts +0 -146
  190. package/.claude/worktrees/emails-list/tests/lib/table.test.ts +0 -63
  191. package/.claude/worktrees/emails-list/tests/lib/tty.test.ts +0 -85
  192. package/.claude/worktrees/emails-list/tsconfig.json +0 -14
  193. package/.github/workflows/test-build-windows.yml +0 -44
@@ -3,13 +3,46 @@ on:
3
3
  push:
4
4
  tags:
5
5
  - 'v*'
6
- permissions:
7
- contents: write
8
6
  concurrency:
9
7
  group: release
10
8
  cancel-in-progress: false
11
9
  jobs:
10
+ test-binary:
11
+ strategy:
12
+ fail-fast: false
13
+ matrix:
14
+ include:
15
+ - os: macos-latest
16
+ target: bun-darwin-arm64
17
+ binary: dist/resend
18
+ flags: "--bytecode"
19
+ - os: ubuntu-latest
20
+ target: bun-linux-x64
21
+ binary: dist/resend
22
+ flags: "--bytecode"
23
+ - os: windows-latest
24
+ target: bun-windows-x64
25
+ binary: dist/resend.exe
26
+ flags: ""
27
+ runs-on: ${{ matrix.os }}
28
+ steps:
29
+ - uses: actions/checkout@v6
30
+
31
+ - uses: oven-sh/setup-bun@v2
32
+
33
+ - name: Install dependencies
34
+ run: bun install --frozen-lockfile
35
+
36
+ - name: Build binary
37
+ run: bun build --compile --minify --sourcemap ${{ matrix.flags }} src/cli.ts --target=${{ matrix.target }} --outfile ${{ matrix.binary }}
38
+
39
+ - name: Verify binary runs
40
+ run: ${{ matrix.binary }} --version
41
+
12
42
  release:
43
+ needs: test-binary
44
+ permissions:
45
+ contents: write
13
46
  runs-on: blacksmith-2vcpu-ubuntu-2204
14
47
  steps:
15
48
  - uses: actions/checkout@v6
@@ -64,12 +97,6 @@ jobs:
64
97
  ```pwsh
65
98
  irm https://resend.com/install.ps1 | iex
66
99
  ```
67
-
68
- **GitHub CLI** _(use while repo is private)_
69
- ```sh
70
- gh release download --repo resend/resend-cli --pattern "resend-darwin-arm64.tar.gz"
71
- tar -xzf resend-darwin-arm64.tar.gz && sudo mv resend /usr/local/bin/ && rm resend-darwin-arm64.tar.gz
72
- ```
73
100
  files: |
74
101
  dist/resend-darwin-arm64.tar.gz
75
102
  dist/resend-darwin-x64.tar.gz
package/README.md CHANGED
@@ -1,6 +1,17 @@
1
1
  # Resend CLI
2
2
 
3
- Command-line interface for the [Resend](https://resend.com) email API. Works for humans, AI agents, and CI/CD pipelines.
3
+ The official CLI for [Resend](https://resend.com).
4
+
5
+ Built for humans, AI agents, and CI/CD pipelines.
6
+
7
+ ```
8
+ ██████╗ ███████╗███████╗███████╗███╗ ██╗██████╗
9
+ ██╔══██╗██╔════╝██╔════╝██╔════╝████╗ ██║██╔══██╗
10
+ ██████╔╝█████╗ ███████╗█████╗ ██╔██╗ ██║██║ ██║
11
+ ██╔══██╗██╔══╝ ╚════██║██╔══╝ ██║╚██╗██║██║ ██║
12
+ ██║ ██║███████╗███████║███████╗██║ ╚████║██████╔╝
13
+ ╚═╝ ╚═╝╚══════╝╚══════╝╚══════╝╚═╝ ╚═══╝╚═════╝
14
+ ```
4
15
 
5
16
  ## Install
6
17
 
package/biome.json CHANGED
@@ -31,6 +31,6 @@
31
31
  }
32
32
  },
33
33
  "files": {
34
- "includes": ["**", "!**/dist", "!**/bun.lock"]
34
+ "includes": ["**", "!**/dist", "!**/bun.lock", "!.claude"]
35
35
  }
36
36
  }
package/bun.lock CHANGED
@@ -9,7 +9,6 @@
9
9
  "@commander-js/extra-typings": "14.0.0",
10
10
  "commander": "14.0.3",
11
11
  "resend": "6.9.3",
12
- "unicode-animations": "1.0.3",
13
12
  },
14
13
  "devDependencies": {
15
14
  "@biomejs/biome": "2.4.6",
@@ -69,8 +68,6 @@
69
68
 
70
69
  "undici-types": ["undici-types@7.16.0", "", {}, "sha512-Zz+aZWSj8LE6zoxD+xrjh4VfkIG8Ya6LvYkZqtUQGJPZjYl53ypCaUwWqo7eI0x66KBGeRo+mlBEkMSeSZ38Nw=="],
71
70
 
72
- "unicode-animations": ["unicode-animations@1.0.3", "", { "dependencies": { "unicode-animations": "^1.0.1" }, "bin": { "unicode-animations": "scripts/demo.cjs" } }, "sha512-+klB2oWwcYZjYWhwP4Pr8UZffWDFVx6jKeIahE6z0QYyM2dwDeDPyn5nevCYbyotxvtT9lh21cVURO1RX0+YMg=="],
73
-
74
71
  "uuid": ["uuid@10.0.0", "", { "bin": { "uuid": "dist/bin/uuid" } }, "sha512-8XkAphELsDnEGrDxUOHB3RGvXz6TeuYSGEZBOjtTtPm2lwhGBjLgOzLHB63IUWfBpNucQjND6d3AOudO+H3RWQ=="],
75
72
  }
76
73
  }
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "resend-cli",
3
- "version": "1.2.0",
4
- "description": "Resend CLI — email for developers",
3
+ "version": "1.2.2",
4
+ "description": "The official CLI for Resend",
5
5
  "license": "MIT",
6
6
  "repository": {
7
7
  "type": "git",
@@ -32,8 +32,7 @@
32
32
  "commander": "14.0.3",
33
33
  "@commander-js/extra-typings": "14.0.0",
34
34
  "resend": "6.9.3",
35
- "@clack/prompts": "1.1.0",
36
- "unicode-animations": "1.0.3"
35
+ "@clack/prompts": "1.1.0"
37
36
  },
38
37
  "devDependencies": {
39
38
  "@biomejs/biome": "2.4.6",
package/src/cli.ts CHANGED
@@ -15,11 +15,23 @@ import { teamsCommand } from './commands/teams/index';
15
15
  import { topicsCommand } from './commands/topics/index';
16
16
  import { webhooksCommand } from './commands/webhooks/index';
17
17
  import { whoamiCommand } from './commands/whoami';
18
+ import { errorMessage, outputError } from './lib/output';
19
+ import { checkForUpdates } from './lib/update-check';
18
20
  import { PACKAGE_NAME, VERSION } from './lib/version';
19
21
 
22
+ const BANNER = `
23
+ ██████╗ ███████╗███████╗███████╗███╗ ██╗██████╗
24
+ ██╔══██╗██╔════╝██╔════╝██╔════╝████╗ ██║██╔══██╗
25
+ ██████╔╝█████╗ ███████╗█████╗ ██╔██╗ ██║██║ ██║
26
+ ██╔══██╗██╔══╝ ╚════██║██╔══╝ ██║╚██╗██║██║ ██║
27
+ ██║ ██║███████╗███████║███████╗██║ ╚████║██████╔╝
28
+ ╚═╝ ╚═╝╚══════╝╚══════╝╚══════╝╚═╝ ╚═══╝╚═════╝
29
+ `;
30
+
20
31
  const program = new Command()
21
32
  .name('resend')
22
33
  .description('Resend CLI — email for developers')
34
+ .addHelpText('beforeAll', BANNER)
23
35
  .version(
24
36
  `${PACKAGE_NAME} v${VERSION}`,
25
37
  '-v, --version',
@@ -50,10 +62,8 @@ Output:
50
62
  Errors always exit with code 1: {"error":{"message":"...","code":"..."}}
51
63
 
52
64
  Examples:
53
- $ resend login --key re_123456789
54
- $ resend emails send --from you@domain.com --to user@example.com --subject "Hi" --text "Hello"
55
- $ resend emails batch --file ./emails.json --json
56
- $ resend doctor --json`,
65
+ $ resend login
66
+ $ resend emails send`,
57
67
  )
58
68
  .addCommand(loginCommand)
59
69
  .addCommand(logoutCommand)
@@ -71,4 +81,12 @@ Examples:
71
81
  .addCommand(openCommand)
72
82
  .addCommand(whoamiCommand);
73
83
 
74
- program.parse();
84
+ program
85
+ .parseAsync()
86
+ .then(() => checkForUpdates().catch(() => {}))
87
+ .catch((err) => {
88
+ outputError({
89
+ message: errorMessage(err, 'An unexpected error occurred'),
90
+ code: 'unexpected_error',
91
+ });
92
+ });
@@ -133,11 +133,7 @@ export const loginCommand = new Command('login')
133
133
  );
134
134
  }
135
135
 
136
- const spinner = createSpinner(
137
- 'Validating API key...',
138
- 'braille',
139
- globalOpts.quiet,
140
- );
136
+ const spinner = createSpinner('Validating API key...', globalOpts.quiet);
141
137
 
142
138
  try {
143
139
  const resend = new Resend(apiKey);
@@ -190,7 +186,7 @@ export const loginCommand = new Command('login')
190
186
  if (choice === '__new__') {
191
187
  const newName = await p.text({
192
188
  message: 'Enter a name for the new team:',
193
- validate: (v) => validateTeamName(v),
189
+ validate: (v) => validateTeamName(v as string),
194
190
  });
195
191
  if (p.isCancel(newName)) {
196
192
  cancelAndExit('Login cancelled.');
@@ -211,8 +207,14 @@ export const loginCommand = new Command('login')
211
207
  if (teamName) {
212
208
  try {
213
209
  setActiveTeam(teamName);
214
- } catch {
215
- // Team was just stored, so this should not fail
210
+ } catch (err) {
211
+ outputError(
212
+ {
213
+ message: errorMessage(err, 'Failed to switch team'),
214
+ code: 'switch_failed',
215
+ },
216
+ { json: globalOpts.json },
217
+ );
216
218
  }
217
219
  }
218
220
 
@@ -1,6 +1,3 @@
1
- import { existsSync } from 'node:fs';
2
- import { homedir } from 'node:os';
3
- import { join } from 'node:path';
4
1
  import { Command } from '@commander-js/extra-typings';
5
2
  import { Resend } from 'resend';
6
3
  import type { GlobalOpts } from '../lib/client';
@@ -9,7 +6,8 @@ import { buildHelpText } from '../lib/help-text';
9
6
  import { errorMessage, outputResult } from '../lib/output';
10
7
  import { createSpinner } from '../lib/spinner';
11
8
  import { isInteractive } from '../lib/tty';
12
- import { PACKAGE_NAME, VERSION } from '../lib/version';
9
+ import { GITHUB_RELEASES_URL } from '../lib/update-check';
10
+ import { VERSION } from '../lib/version';
13
11
 
14
12
  type CheckStatus = 'pass' | 'warn' | 'fail';
15
13
 
@@ -20,21 +18,12 @@ type CheckResult = {
20
18
  detail?: string;
21
19
  };
22
20
 
23
- const statusIcons: Record<CheckStatus, string> = {
24
- pass: '\x1B[32m✔\x1B[0m',
25
- warn: '\x1B[33m!\x1B[0m',
26
- fail: '\x1B[31m✗\x1B[0m',
27
- };
28
-
29
21
  async function checkCliVersion(): Promise<CheckResult> {
30
22
  try {
31
- const encodedName = encodeURIComponent(PACKAGE_NAME);
32
- const res = await fetch(
33
- `https://registry.npmjs.org/${encodedName}/latest`,
34
- {
35
- signal: AbortSignal.timeout(5000),
36
- },
37
- );
23
+ const res = await fetch(GITHUB_RELEASES_URL, {
24
+ headers: { Accept: 'application/vnd.github.v3+json' },
25
+ signal: AbortSignal.timeout(5000),
26
+ });
38
27
  if (!res.ok) {
39
28
  return {
40
29
  name: 'CLI Version',
@@ -42,8 +31,19 @@ async function checkCliVersion(): Promise<CheckResult> {
42
31
  message: `v${VERSION} (could not check for updates)`,
43
32
  };
44
33
  }
45
- const data = (await res.json()) as { version?: string };
46
- const latest = data.version ?? 'unknown';
34
+ const data = (await res.json()) as {
35
+ tag_name?: string;
36
+ prerelease?: boolean;
37
+ draft?: boolean;
38
+ };
39
+ if (data.prerelease || data.draft) {
40
+ return {
41
+ name: 'CLI Version',
42
+ status: 'warn',
43
+ message: `v${VERSION} (could not check for updates)`,
44
+ };
45
+ }
46
+ const latest = data.tag_name?.replace(/^v/, '') ?? 'unknown';
47
47
  if (latest === VERSION) {
48
48
  return {
49
49
  name: 'CLI Version',
@@ -84,8 +84,10 @@ function checkApiKeyPresence(flagValue?: string): CheckResult {
84
84
  };
85
85
  }
86
86
 
87
- async function checkApiValidationAndDomains(): Promise<CheckResult> {
88
- const resolved = resolveApiKey();
87
+ async function checkApiValidationAndDomains(
88
+ flagValue?: string,
89
+ ): Promise<CheckResult> {
90
+ const resolved = resolveApiKey(flagValue);
89
91
  if (!resolved) {
90
92
  return {
91
93
  name: 'API Validation',
@@ -143,73 +145,8 @@ async function checkApiValidationAndDomains(): Promise<CheckResult> {
143
145
  }
144
146
  }
145
147
 
146
- function checkAgentDetection(): CheckResult {
147
- const home = homedir();
148
- const agents: { name: string; found: boolean }[] = [];
149
-
150
- // OpenClaw
151
- agents.push({
152
- name: 'OpenClaw',
153
- found: existsSync(join(home, 'clawd', 'skills')),
154
- });
155
-
156
- // Cursor
157
- agents.push({ name: 'Cursor', found: existsSync(join(home, '.cursor')) });
158
-
159
- // Claude Desktop
160
- const claudeConfigPaths =
161
- process.platform === 'darwin'
162
- ? [
163
- join(
164
- home,
165
- 'Library',
166
- 'Application Support',
167
- 'Claude',
168
- 'claude_desktop_config.json',
169
- ),
170
- ]
171
- : process.platform === 'win32'
172
- ? [
173
- join(
174
- process.env.APPDATA ?? '',
175
- 'Claude',
176
- 'claude_desktop_config.json',
177
- ),
178
- ]
179
- : [join(home, '.config', 'Claude', 'claude_desktop_config.json')];
180
- agents.push({
181
- name: 'Claude Desktop',
182
- found: claudeConfigPaths.some(existsSync),
183
- });
184
-
185
- // VS Code MCP
186
- agents.push({
187
- name: 'VS Code',
188
- found: existsSync(join(process.cwd(), '.vscode', 'mcp.json')),
189
- });
190
-
191
- const detected = agents.filter((a) => a.found);
192
-
193
- if (detected.length === 0) {
194
- return {
195
- name: 'AI Agents',
196
- status: 'pass',
197
- message: 'No AI agents detected',
198
- };
199
- }
200
-
201
- return {
202
- name: 'AI Agents',
203
- status: 'pass',
204
- message: `Detected: ${detected.map((a) => a.name).join(', ')}`,
205
- detail: 'Future: run `resend setup <agent>` to configure integration',
206
- };
207
- }
208
-
209
148
  export const doctorCommand = new Command('doctor')
210
- .description(
211
- 'Check CLI version, API key, domain status, and AI agent detection',
212
- )
149
+ .description('Check CLI version, API key, and domain status')
213
150
  .addHelpText(
214
151
  'after',
215
152
  buildHelpText({
@@ -217,9 +154,8 @@ export const doctorCommand = new Command('doctor')
217
154
  context: `Checks performed:
218
155
  CLI Version Is the installed version up to date?
219
156
  API Key Is a key present (--api-key, RESEND_API_KEY, or credentials file)?
220
- API Validation Is the key valid and accepted by the Resend API?
221
- AI Agents Detected: Claude Desktop, Cursor, VS Code MCP, OpenClaw`,
222
- output: ` {\n "ok": true,\n "checks": [\n {"name":"CLI Version","status":"pass","message":"v0.1.0 (latest)"},\n {"name":"API Key","status":"pass","message":"re_...abcd (source: env)"},\n {"name":"Domains","status":"pass","message":"1 verified, 0 pending"},\n {"name":"AI Agents","status":"pass","message":"Detected: Claude Desktop"}\n ]\n }\n status values: "pass" | "warn" | "fail"\n Exit code 1 if any check has status "fail"`,
157
+ API Validation Is the key valid and accepted by the Resend API?`,
158
+ output: ` {\n "ok": true,\n "checks": [\n {"name":"CLI Version","status":"pass","message":"v0.1.0 (latest)"},\n {"name":"API Key","status":"pass","message":"re_...abcd (source: env)"},\n {"name":"Domains","status":"pass","message":"1 verified, 0 pending"}\n ]\n }\n status values: "pass" | "warn" | "fail"\n Exit code 1 if any check has status "fail"`,
223
159
  examples: ['resend doctor', 'resend doctor --json'],
224
160
  }),
225
161
  )
@@ -233,9 +169,7 @@ export const doctorCommand = new Command('doctor')
233
169
  }
234
170
 
235
171
  // Check 1: CLI Version
236
- let spinner = interactive
237
- ? createSpinner('Checking CLI version...', 'orbit')
238
- : null;
172
+ let spinner = interactive ? createSpinner('Checking CLI version...') : null;
239
173
  const versionCheck = await checkCliVersion();
240
174
  checks.push(versionCheck);
241
175
  if (versionCheck.status === 'warn') {
@@ -245,7 +179,7 @@ export const doctorCommand = new Command('doctor')
245
179
  }
246
180
 
247
181
  // Check 2: API Key
248
- spinner = interactive ? createSpinner('Checking API key...', 'scan') : null;
182
+ spinner = interactive ? createSpinner('Checking API key...') : null;
249
183
  const keyCheck = checkApiKeyPresence(globalOpts.apiKey);
250
184
  checks.push(keyCheck);
251
185
  if (keyCheck.status === 'fail') {
@@ -256,9 +190,9 @@ export const doctorCommand = new Command('doctor')
256
190
 
257
191
  // Check 3: API Validation + Domains
258
192
  spinner = interactive
259
- ? createSpinner('Validating API key & domains...', 'scan')
193
+ ? createSpinner('Validating API key & domains...')
260
194
  : null;
261
- const domainCheck = await checkApiValidationAndDomains();
195
+ const domainCheck = await checkApiValidationAndDomains(globalOpts.apiKey);
262
196
  checks.push(domainCheck);
263
197
  if (domainCheck.status === 'fail') {
264
198
  spinner?.fail(domainCheck.message);
@@ -268,26 +202,10 @@ export const doctorCommand = new Command('doctor')
268
202
  spinner?.stop(domainCheck.message);
269
203
  }
270
204
 
271
- // Check 4: Agent Detection
272
- spinner = interactive
273
- ? createSpinner('Detecting AI agents...', 'scan')
274
- : null;
275
- const agentCheck = checkAgentDetection();
276
- checks.push(agentCheck);
277
- spinner?.stop(agentCheck.message);
278
-
279
205
  const hasFails = checks.some((c) => c.status === 'fail');
280
206
 
281
207
  if (!globalOpts.json && isInteractive()) {
282
208
  console.log('');
283
- for (const check of checks) {
284
- const icon = statusIcons[check.status];
285
- console.log(` ${icon} ${check.name}: ${check.message}`);
286
- if (check.detail) {
287
- console.log(` ${check.detail}`);
288
- }
289
- }
290
- console.log('');
291
209
  } else {
292
210
  outputResult({ ok: !hasFails, checks }, { json: globalOpts.json });
293
211
  }
package/src/lib/client.ts CHANGED
@@ -1,6 +1,7 @@
1
1
  import { Resend } from 'resend';
2
2
  import { resolveApiKey } from './config';
3
3
  import { errorMessage, outputError } from './output';
4
+ import { VERSION } from './version';
4
5
 
5
6
  export type GlobalOpts = {
6
7
  apiKey?: string;
@@ -9,6 +10,8 @@ export type GlobalOpts = {
9
10
  team?: string;
10
11
  };
11
12
 
13
+ process.env.RESEND_USER_AGENT = `resend-cli:${VERSION}`;
14
+
12
15
  export function createClient(flagValue?: string, teamName?: string): Resend {
13
16
  const resolved = resolveApiKey(flagValue, teamName);
14
17
  if (!resolved) {
package/src/lib/config.ts CHANGED
@@ -203,8 +203,8 @@ export function validateTeamName(name: string): string | undefined {
203
203
  if (name.length > 64) {
204
204
  return 'Team name must be 64 characters or fewer';
205
205
  }
206
- if (!/^[a-z0-9_-]+$/.test(name)) {
207
- return 'Team name must contain only lowercase letters, numbers, dashes, and underscores';
206
+ if (!/^[a-zA-Z0-9_-]+$/.test(name)) {
207
+ return 'Team name must contain only letters, numbers, dashes, and underscores';
208
208
  }
209
209
  return undefined;
210
210
  }
@@ -215,4 +215,3 @@ export function maskKey(key: string): string {
215
215
  }
216
216
  return `${key.slice(0, 3)}...${key.slice(-4)}`;
217
217
  }
218
-
@@ -1,4 +1,3 @@
1
- import spinners from 'unicode-animations';
2
1
  import type { GlobalOpts } from './client';
3
2
  import { errorMessage, outputError } from './output';
4
3
  import { isInteractive, isUnicodeSupported } from './tty';
@@ -9,6 +8,19 @@ const TICK = isUnicodeSupported ? String.fromCodePoint(0x2714) : 'v'; // ✔
9
8
  const WARN = isUnicodeSupported ? String.fromCodePoint(0x26a0) : '!'; // ⚠
10
9
  const CROSS = isUnicodeSupported ? String.fromCodePoint(0x2717) : 'x'; // ✗
11
10
 
11
+ // Braille spinner: cycle through U+2800-block dot patterns.
12
+ const SPINNER_FRAMES = [
13
+ '\u2839',
14
+ '\u2838',
15
+ '\u2834',
16
+ '\u2826',
17
+ '\u2807',
18
+ '\u280F',
19
+ '\u2819',
20
+ '\u2839',
21
+ ];
22
+ const SPINNER_INTERVAL = 80;
23
+
12
24
  type SdkResponse<T> = { data: T | null; error: { message: string } | null };
13
25
 
14
26
  /**
@@ -21,7 +33,7 @@ export async function withSpinner<T>(
21
33
  errorCode: string,
22
34
  globalOpts: GlobalOpts,
23
35
  ): Promise<T> {
24
- const spinner = createSpinner(messages.loading, 'braille', globalOpts.quiet);
36
+ const spinner = createSpinner(messages.loading, globalOpts.quiet);
25
37
  try {
26
38
  const { data, error } = await call();
27
39
  if (error) {
@@ -49,13 +61,7 @@ export async function withSpinner<T>(
49
61
  }
50
62
  }
51
63
 
52
- export type SpinnerName = keyof typeof spinners;
53
-
54
- export function createSpinner(
55
- message: string,
56
- name: SpinnerName = 'braille',
57
- quiet?: boolean,
58
- ) {
64
+ export function createSpinner(message: string, quiet?: boolean) {
59
65
  if (quiet || !isInteractive()) {
60
66
  return {
61
67
  update(_msg: string) {},
@@ -65,7 +71,8 @@ export function createSpinner(
65
71
  };
66
72
  }
67
73
 
68
- const { frames, interval } = spinners[name];
74
+ const frames = isUnicodeSupported ? SPINNER_FRAMES : ['-', '\\', '|', '/'];
75
+ const interval = SPINNER_INTERVAL;
69
76
  let i = 0;
70
77
  let text = message;
71
78