resend-cli 1.2.1 → 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 (191) hide show
  1. package/biome.json +1 -1
  2. package/bun.lock +0 -3
  3. package/package.json +2 -3
  4. package/src/cli.ts +11 -1
  5. package/src/commands/auth/login.ts +35 -8
  6. package/src/commands/doctor.ts +33 -115
  7. package/src/commands/teams/remove.ts +5 -2
  8. package/src/commands/teams/switch.ts +3 -0
  9. package/src/lib/config.ts +37 -31
  10. package/src/lib/spinner.ts +17 -10
  11. package/src/lib/update-check.ts +172 -0
  12. package/tests/commands/auth/login.test.ts +37 -0
  13. package/tests/lib/config.test.ts +38 -7
  14. package/tests/lib/update-check.test.ts +169 -0
  15. package/.claude/worktrees/emails-list/.claude/settings.local.json +0 -5
  16. package/.claude/worktrees/emails-list/.github/scripts/pr-title-check.js +0 -34
  17. package/.claude/worktrees/emails-list/.github/workflows/ci.yml +0 -32
  18. package/.claude/worktrees/emails-list/.github/workflows/pr-title-check.yml +0 -13
  19. package/.claude/worktrees/emails-list/.github/workflows/release.yml +0 -93
  20. package/.claude/worktrees/emails-list/CHANGELOG.md +0 -31
  21. package/.claude/worktrees/emails-list/LICENSE +0 -21
  22. package/.claude/worktrees/emails-list/README.md +0 -424
  23. package/.claude/worktrees/emails-list/biome.json +0 -36
  24. package/.claude/worktrees/emails-list/bun.lock +0 -76
  25. package/.claude/worktrees/emails-list/bunfig.toml +0 -2
  26. package/.claude/worktrees/emails-list/install.ps1 +0 -140
  27. package/.claude/worktrees/emails-list/install.sh +0 -301
  28. package/.claude/worktrees/emails-list/package.json +0 -43
  29. package/.claude/worktrees/emails-list/renovate.json +0 -6
  30. package/.claude/worktrees/emails-list/src/cli.ts +0 -74
  31. package/.claude/worktrees/emails-list/src/commands/api-keys/create.ts +0 -114
  32. package/.claude/worktrees/emails-list/src/commands/api-keys/delete.ts +0 -47
  33. package/.claude/worktrees/emails-list/src/commands/api-keys/index.ts +0 -26
  34. package/.claude/worktrees/emails-list/src/commands/api-keys/list.ts +0 -35
  35. package/.claude/worktrees/emails-list/src/commands/api-keys/utils.ts +0 -8
  36. package/.claude/worktrees/emails-list/src/commands/auth/index.ts +0 -20
  37. package/.claude/worktrees/emails-list/src/commands/auth/login.ts +0 -207
  38. package/.claude/worktrees/emails-list/src/commands/auth/logout.ts +0 -105
  39. package/.claude/worktrees/emails-list/src/commands/broadcasts/create.ts +0 -196
  40. package/.claude/worktrees/emails-list/src/commands/broadcasts/delete.ts +0 -46
  41. package/.claude/worktrees/emails-list/src/commands/broadcasts/get.ts +0 -59
  42. package/.claude/worktrees/emails-list/src/commands/broadcasts/index.ts +0 -43
  43. package/.claude/worktrees/emails-list/src/commands/broadcasts/list.ts +0 -60
  44. package/.claude/worktrees/emails-list/src/commands/broadcasts/send.ts +0 -56
  45. package/.claude/worktrees/emails-list/src/commands/broadcasts/update.ts +0 -95
  46. package/.claude/worktrees/emails-list/src/commands/broadcasts/utils.ts +0 -35
  47. package/.claude/worktrees/emails-list/src/commands/contact-properties/create.ts +0 -118
  48. package/.claude/worktrees/emails-list/src/commands/contact-properties/delete.ts +0 -48
  49. package/.claude/worktrees/emails-list/src/commands/contact-properties/get.ts +0 -46
  50. package/.claude/worktrees/emails-list/src/commands/contact-properties/index.ts +0 -48
  51. package/.claude/worktrees/emails-list/src/commands/contact-properties/list.ts +0 -68
  52. package/.claude/worktrees/emails-list/src/commands/contact-properties/update.ts +0 -88
  53. package/.claude/worktrees/emails-list/src/commands/contact-properties/utils.ts +0 -17
  54. package/.claude/worktrees/emails-list/src/commands/contacts/add-segment.ts +0 -78
  55. package/.claude/worktrees/emails-list/src/commands/contacts/create.ts +0 -122
  56. package/.claude/worktrees/emails-list/src/commands/contacts/delete.ts +0 -49
  57. package/.claude/worktrees/emails-list/src/commands/contacts/get.ts +0 -53
  58. package/.claude/worktrees/emails-list/src/commands/contacts/index.ts +0 -58
  59. package/.claude/worktrees/emails-list/src/commands/contacts/list.ts +0 -57
  60. package/.claude/worktrees/emails-list/src/commands/contacts/remove-segment.ts +0 -48
  61. package/.claude/worktrees/emails-list/src/commands/contacts/segments.ts +0 -39
  62. package/.claude/worktrees/emails-list/src/commands/contacts/topics.ts +0 -45
  63. package/.claude/worktrees/emails-list/src/commands/contacts/update-topics.ts +0 -90
  64. package/.claude/worktrees/emails-list/src/commands/contacts/update.ts +0 -77
  65. package/.claude/worktrees/emails-list/src/commands/contacts/utils.ts +0 -119
  66. package/.claude/worktrees/emails-list/src/commands/doctor.ts +0 -298
  67. package/.claude/worktrees/emails-list/src/commands/domains/create.ts +0 -83
  68. package/.claude/worktrees/emails-list/src/commands/domains/delete.ts +0 -42
  69. package/.claude/worktrees/emails-list/src/commands/domains/get.ts +0 -47
  70. package/.claude/worktrees/emails-list/src/commands/domains/index.ts +0 -35
  71. package/.claude/worktrees/emails-list/src/commands/domains/list.ts +0 -53
  72. package/.claude/worktrees/emails-list/src/commands/domains/update.ts +0 -75
  73. package/.claude/worktrees/emails-list/src/commands/domains/utils.ts +0 -44
  74. package/.claude/worktrees/emails-list/src/commands/domains/verify.ts +0 -38
  75. package/.claude/worktrees/emails-list/src/commands/emails/batch.ts +0 -140
  76. package/.claude/worktrees/emails-list/src/commands/emails/index.ts +0 -28
  77. package/.claude/worktrees/emails-list/src/commands/emails/list.ts +0 -73
  78. package/.claude/worktrees/emails-list/src/commands/emails/receiving/attachment.ts +0 -55
  79. package/.claude/worktrees/emails-list/src/commands/emails/receiving/attachments.ts +0 -68
  80. package/.claude/worktrees/emails-list/src/commands/emails/receiving/get.ts +0 -58
  81. package/.claude/worktrees/emails-list/src/commands/emails/receiving/index.ts +0 -28
  82. package/.claude/worktrees/emails-list/src/commands/emails/receiving/list.ts +0 -59
  83. package/.claude/worktrees/emails-list/src/commands/emails/receiving/utils.ts +0 -38
  84. package/.claude/worktrees/emails-list/src/commands/emails/send.ts +0 -189
  85. package/.claude/worktrees/emails-list/src/commands/open.ts +0 -24
  86. package/.claude/worktrees/emails-list/src/commands/segments/create.ts +0 -50
  87. package/.claude/worktrees/emails-list/src/commands/segments/delete.ts +0 -47
  88. package/.claude/worktrees/emails-list/src/commands/segments/get.ts +0 -38
  89. package/.claude/worktrees/emails-list/src/commands/segments/index.ts +0 -36
  90. package/.claude/worktrees/emails-list/src/commands/segments/list.ts +0 -58
  91. package/.claude/worktrees/emails-list/src/commands/segments/utils.ts +0 -7
  92. package/.claude/worktrees/emails-list/src/commands/teams/index.ts +0 -10
  93. package/.claude/worktrees/emails-list/src/commands/teams/list.ts +0 -35
  94. package/.claude/worktrees/emails-list/src/commands/teams/remove.ts +0 -83
  95. package/.claude/worktrees/emails-list/src/commands/teams/switch.ts +0 -73
  96. package/.claude/worktrees/emails-list/src/commands/topics/create.ts +0 -73
  97. package/.claude/worktrees/emails-list/src/commands/topics/delete.ts +0 -47
  98. package/.claude/worktrees/emails-list/src/commands/topics/get.ts +0 -42
  99. package/.claude/worktrees/emails-list/src/commands/topics/index.ts +0 -42
  100. package/.claude/worktrees/emails-list/src/commands/topics/list.ts +0 -34
  101. package/.claude/worktrees/emails-list/src/commands/topics/update.ts +0 -59
  102. package/.claude/worktrees/emails-list/src/commands/topics/utils.ts +0 -16
  103. package/.claude/worktrees/emails-list/src/commands/webhooks/create.ts +0 -128
  104. package/.claude/worktrees/emails-list/src/commands/webhooks/delete.ts +0 -49
  105. package/.claude/worktrees/emails-list/src/commands/webhooks/get.ts +0 -42
  106. package/.claude/worktrees/emails-list/src/commands/webhooks/index.ts +0 -44
  107. package/.claude/worktrees/emails-list/src/commands/webhooks/list.ts +0 -55
  108. package/.claude/worktrees/emails-list/src/commands/webhooks/update.ts +0 -83
  109. package/.claude/worktrees/emails-list/src/commands/webhooks/utils.ts +0 -36
  110. package/.claude/worktrees/emails-list/src/commands/whoami.ts +0 -71
  111. package/.claude/worktrees/emails-list/src/lib/actions.ts +0 -157
  112. package/.claude/worktrees/emails-list/src/lib/client.ts +0 -34
  113. package/.claude/worktrees/emails-list/src/lib/config.ts +0 -211
  114. package/.claude/worktrees/emails-list/src/lib/files.ts +0 -15
  115. package/.claude/worktrees/emails-list/src/lib/help-text.ts +0 -38
  116. package/.claude/worktrees/emails-list/src/lib/output.ts +0 -54
  117. package/.claude/worktrees/emails-list/src/lib/pagination.ts +0 -36
  118. package/.claude/worktrees/emails-list/src/lib/prompts.ts +0 -149
  119. package/.claude/worktrees/emails-list/src/lib/spinner.ts +0 -93
  120. package/.claude/worktrees/emails-list/src/lib/table.ts +0 -57
  121. package/.claude/worktrees/emails-list/src/lib/tty.ts +0 -28
  122. package/.claude/worktrees/emails-list/src/lib/version.ts +0 -4
  123. package/.claude/worktrees/emails-list/tests/commands/api-keys/create.test.ts +0 -195
  124. package/.claude/worktrees/emails-list/tests/commands/api-keys/delete.test.ts +0 -156
  125. package/.claude/worktrees/emails-list/tests/commands/api-keys/list.test.ts +0 -133
  126. package/.claude/worktrees/emails-list/tests/commands/auth/login.test.ts +0 -119
  127. package/.claude/worktrees/emails-list/tests/commands/auth/logout.test.ts +0 -146
  128. package/.claude/worktrees/emails-list/tests/commands/broadcasts/create.test.ts +0 -447
  129. package/.claude/worktrees/emails-list/tests/commands/broadcasts/delete.test.ts +0 -182
  130. package/.claude/worktrees/emails-list/tests/commands/broadcasts/get.test.ts +0 -146
  131. package/.claude/worktrees/emails-list/tests/commands/broadcasts/list.test.ts +0 -196
  132. package/.claude/worktrees/emails-list/tests/commands/broadcasts/send.test.ts +0 -161
  133. package/.claude/worktrees/emails-list/tests/commands/broadcasts/update.test.ts +0 -283
  134. package/.claude/worktrees/emails-list/tests/commands/contact-properties/create.test.ts +0 -250
  135. package/.claude/worktrees/emails-list/tests/commands/contact-properties/delete.test.ts +0 -183
  136. package/.claude/worktrees/emails-list/tests/commands/contact-properties/get.test.ts +0 -144
  137. package/.claude/worktrees/emails-list/tests/commands/contact-properties/list.test.ts +0 -180
  138. package/.claude/worktrees/emails-list/tests/commands/contact-properties/update.test.ts +0 -216
  139. package/.claude/worktrees/emails-list/tests/commands/contacts/add-segment.test.ts +0 -188
  140. package/.claude/worktrees/emails-list/tests/commands/contacts/create.test.ts +0 -270
  141. package/.claude/worktrees/emails-list/tests/commands/contacts/delete.test.ts +0 -192
  142. package/.claude/worktrees/emails-list/tests/commands/contacts/get.test.ts +0 -148
  143. package/.claude/worktrees/emails-list/tests/commands/contacts/list.test.ts +0 -175
  144. package/.claude/worktrees/emails-list/tests/commands/contacts/remove-segment.test.ts +0 -166
  145. package/.claude/worktrees/emails-list/tests/commands/contacts/segments.test.ts +0 -167
  146. package/.claude/worktrees/emails-list/tests/commands/contacts/topics.test.ts +0 -163
  147. package/.claude/worktrees/emails-list/tests/commands/contacts/update-topics.test.ts +0 -247
  148. package/.claude/worktrees/emails-list/tests/commands/contacts/update.test.ts +0 -205
  149. package/.claude/worktrees/emails-list/tests/commands/doctor.test.ts +0 -165
  150. package/.claude/worktrees/emails-list/tests/commands/domains/create.test.ts +0 -192
  151. package/.claude/worktrees/emails-list/tests/commands/domains/delete.test.ts +0 -156
  152. package/.claude/worktrees/emails-list/tests/commands/domains/get.test.ts +0 -137
  153. package/.claude/worktrees/emails-list/tests/commands/domains/list.test.ts +0 -164
  154. package/.claude/worktrees/emails-list/tests/commands/domains/update.test.ts +0 -223
  155. package/.claude/worktrees/emails-list/tests/commands/domains/verify.test.ts +0 -117
  156. package/.claude/worktrees/emails-list/tests/commands/emails/batch.test.ts +0 -313
  157. package/.claude/worktrees/emails-list/tests/commands/emails/list.test.ts +0 -196
  158. package/.claude/worktrees/emails-list/tests/commands/emails/receiving/attachment.test.ts +0 -140
  159. package/.claude/worktrees/emails-list/tests/commands/emails/receiving/attachments.test.ts +0 -168
  160. package/.claude/worktrees/emails-list/tests/commands/emails/receiving/get.test.ts +0 -140
  161. package/.claude/worktrees/emails-list/tests/commands/emails/receiving/list.test.ts +0 -181
  162. package/.claude/worktrees/emails-list/tests/commands/emails/send.test.ts +0 -309
  163. package/.claude/worktrees/emails-list/tests/commands/segments/create.test.ts +0 -163
  164. package/.claude/worktrees/emails-list/tests/commands/segments/delete.test.ts +0 -182
  165. package/.claude/worktrees/emails-list/tests/commands/segments/get.test.ts +0 -137
  166. package/.claude/worktrees/emails-list/tests/commands/segments/list.test.ts +0 -173
  167. package/.claude/worktrees/emails-list/tests/commands/teams/list.test.ts +0 -63
  168. package/.claude/worktrees/emails-list/tests/commands/teams/remove.test.ts +0 -103
  169. package/.claude/worktrees/emails-list/tests/commands/teams/switch.test.ts +0 -96
  170. package/.claude/worktrees/emails-list/tests/commands/topics/create.test.ts +0 -191
  171. package/.claude/worktrees/emails-list/tests/commands/topics/delete.test.ts +0 -156
  172. package/.claude/worktrees/emails-list/tests/commands/topics/get.test.ts +0 -125
  173. package/.claude/worktrees/emails-list/tests/commands/topics/list.test.ts +0 -124
  174. package/.claude/worktrees/emails-list/tests/commands/topics/update.test.ts +0 -177
  175. package/.claude/worktrees/emails-list/tests/commands/webhooks/create.test.ts +0 -224
  176. package/.claude/worktrees/emails-list/tests/commands/webhooks/delete.test.ts +0 -156
  177. package/.claude/worktrees/emails-list/tests/commands/webhooks/get.test.ts +0 -125
  178. package/.claude/worktrees/emails-list/tests/commands/webhooks/list.test.ts +0 -177
  179. package/.claude/worktrees/emails-list/tests/commands/webhooks/update.test.ts +0 -206
  180. package/.claude/worktrees/emails-list/tests/commands/whoami.test.ts +0 -99
  181. package/.claude/worktrees/emails-list/tests/helpers.ts +0 -93
  182. package/.claude/worktrees/emails-list/tests/lib/client.test.ts +0 -71
  183. package/.claude/worktrees/emails-list/tests/lib/config.test.ts +0 -414
  184. package/.claude/worktrees/emails-list/tests/lib/files.test.ts +0 -65
  185. package/.claude/worktrees/emails-list/tests/lib/help-text.test.ts +0 -97
  186. package/.claude/worktrees/emails-list/tests/lib/output.test.ts +0 -127
  187. package/.claude/worktrees/emails-list/tests/lib/prompts.test.ts +0 -178
  188. package/.claude/worktrees/emails-list/tests/lib/spinner.test.ts +0 -146
  189. package/.claude/worktrees/emails-list/tests/lib/table.test.ts +0 -63
  190. package/.claude/worktrees/emails-list/tests/lib/tty.test.ts +0 -85
  191. package/.claude/worktrees/emails-list/tsconfig.json +0 -14
@@ -1,36 +0,0 @@
1
- import { Command } from '@commander-js/extra-typings';
2
- import { buildHelpText } from '../../lib/help-text';
3
- import { createSegmentCommand } from './create';
4
- import { deleteSegmentCommand } from './delete';
5
- import { getSegmentCommand } from './get';
6
- import { listSegmentsCommand } from './list';
7
-
8
- export const segmentsCommand = new Command('segments')
9
- .description(
10
- 'Manage segments — named groups of contacts used to target broadcasts',
11
- )
12
- .addHelpText(
13
- 'after',
14
- buildHelpText({
15
- context: `Segments are the modern replacement for Audiences (deprecated).
16
- A segment is a named group of contacts. Broadcasts target segments via segment_id.
17
- Contacts can belong to multiple segments.
18
-
19
- Segment membership is managed through the contacts namespace:
20
- resend contacts add-segment <contactId> --segment-id <segmentId>
21
- resend contacts remove-segment <contactId> <segmentId>
22
- resend contacts segments <contactId>
23
-
24
- There is no "update" endpoint — to rename a segment, delete it and recreate.`,
25
- examples: [
26
- 'resend segments list',
27
- 'resend segments create --name "Newsletter Subscribers"',
28
- 'resend segments get 78261eea-8f8b-4381-83c6-79fa7120f1cf',
29
- 'resend segments delete 78261eea-8f8b-4381-83c6-79fa7120f1cf --yes',
30
- ],
31
- }),
32
- )
33
- .addCommand(createSegmentCommand)
34
- .addCommand(getSegmentCommand)
35
- .addCommand(listSegmentsCommand, { isDefault: true })
36
- .addCommand(deleteSegmentCommand);
@@ -1,58 +0,0 @@
1
- import { Command } from '@commander-js/extra-typings';
2
- import { runList } from '../../lib/actions';
3
- import type { GlobalOpts } from '../../lib/client';
4
- import { buildHelpText } from '../../lib/help-text';
5
- import {
6
- buildPaginationOpts,
7
- parseLimitOpt,
8
- printPaginationHint,
9
- } from '../../lib/pagination';
10
- import { renderSegmentsTable } from './utils';
11
-
12
- export const listSegmentsCommand = new Command('list')
13
- .alias('ls')
14
- .description('List all segments')
15
- .option('--limit <n>', 'Maximum number of segments to return (1-100)', '10')
16
- .option('--after <cursor>', 'Return segments after this cursor (next page)')
17
- .option(
18
- '--before <cursor>',
19
- 'Return segments before this cursor (previous page)',
20
- )
21
- .addHelpText(
22
- 'after',
23
- buildHelpText({
24
- context: `Pagination: use --after or --before with a segment ID as the cursor.
25
- Only one of --after or --before may be used at a time.
26
- The response includes has_more: true when additional pages exist.
27
-
28
- Use "resend segments list" to discover segment IDs for use with broadcasts
29
- or "resend contacts add-segment".`,
30
- output: ` {"object":"list","data":[{"id":"<uuid>","name":"<name>","created_at":"<iso-date>"}],"has_more":false}`,
31
- errorCodes: ['auth_error', 'invalid_limit', 'list_error'],
32
- examples: [
33
- 'resend segments list',
34
- 'resend segments list --limit 25 --json',
35
- 'resend segments list --after 78261eea-8f8b-4381-83c6-79fa7120f1cf --json',
36
- ],
37
- }),
38
- )
39
- .action(async (opts, cmd) => {
40
- const globalOpts = cmd.optsWithGlobals() as GlobalOpts;
41
- const limit = parseLimitOpt(opts.limit, globalOpts);
42
- const paginationOpts = buildPaginationOpts(limit, opts.after, opts.before);
43
- await runList(
44
- {
45
- spinner: {
46
- loading: 'Fetching segments...',
47
- success: 'Segments fetched',
48
- fail: 'Failed to list segments',
49
- },
50
- sdkCall: (resend) => resend.segments.list(paginationOpts),
51
- onInteractive: (list) => {
52
- console.log(renderSegmentsTable(list.data));
53
- printPaginationHint(list);
54
- },
55
- },
56
- globalOpts,
57
- );
58
- });
@@ -1,7 +0,0 @@
1
- import type { Segment } from 'resend';
2
- import { renderTable } from '../../lib/table';
3
-
4
- export function renderSegmentsTable(segments: Segment[]): string {
5
- const rows = segments.map((s) => [s.name, s.id, s.created_at]);
6
- return renderTable(['Name', 'ID', 'Created'], rows, '(no segments)');
7
- }
@@ -1,10 +0,0 @@
1
- import { Command } from '@commander-js/extra-typings';
2
- import { listCommand } from './list';
3
- import { removeCommand } from './remove';
4
- import { switchCommand } from './switch';
5
-
6
- export const teamsCommand = new Command('teams')
7
- .description('Manage team profiles for multiple API keys')
8
- .addCommand(listCommand)
9
- .addCommand(switchCommand)
10
- .addCommand(removeCommand);
@@ -1,35 +0,0 @@
1
- import { Command } from '@commander-js/extra-typings';
2
- import type { GlobalOpts } from '../../lib/client';
3
- import { listTeams } from '../../lib/config';
4
- import { outputResult } from '../../lib/output';
5
- import { isInteractive } from '../../lib/tty';
6
-
7
- export const listCommand = new Command('list')
8
- .description('List all team profiles')
9
- .action((_opts, cmd) => {
10
- const globalOpts = cmd.optsWithGlobals() as GlobalOpts;
11
- const teams = listTeams();
12
-
13
- if (globalOpts.json) {
14
- outputResult({ teams }, { json: true });
15
- return;
16
- }
17
-
18
- if (teams.length === 0) {
19
- console.log('No teams configured. Run: resend login');
20
- return;
21
- }
22
-
23
- if (isInteractive()) {
24
- console.log('\n Teams\n');
25
- }
26
-
27
- for (const team of teams) {
28
- const marker = team.active ? ' (active)' : '';
29
- console.log(` ${team.active ? '▸' : ' '} ${team.name}${marker}`);
30
- }
31
-
32
- if (isInteractive()) {
33
- console.log('');
34
- }
35
- });
@@ -1,83 +0,0 @@
1
- import * as p from '@clack/prompts';
2
- import { Command } from '@commander-js/extra-typings';
3
- import type { GlobalOpts } from '../../lib/client';
4
- import { listTeams, removeTeam } from '../../lib/config';
5
- import { errorMessage, outputError, outputResult } from '../../lib/output';
6
- import { cancelAndExit } from '../../lib/prompts';
7
- import { isInteractive } from '../../lib/tty';
8
-
9
- export const removeCommand = new Command('remove')
10
- .description('Remove a team profile')
11
- .argument('[name]', 'Team name to remove')
12
- .action(async (name, _opts, cmd) => {
13
- const globalOpts = cmd.optsWithGlobals() as GlobalOpts;
14
-
15
- let teamName = name;
16
-
17
- if (!teamName) {
18
- if (!isInteractive()) {
19
- outputError(
20
- {
21
- message:
22
- 'Missing team name. Provide a team name in non-interactive mode.',
23
- code: 'missing_name',
24
- },
25
- { json: globalOpts.json },
26
- );
27
- }
28
-
29
- const teams = listTeams();
30
- if (teams.length === 0) {
31
- outputError(
32
- {
33
- message: 'No teams configured. Run `resend login` first.',
34
- code: 'no_teams',
35
- },
36
- { json: globalOpts.json },
37
- );
38
- }
39
-
40
- const choice = await p.select({
41
- message: 'Remove which team?',
42
- options: teams.map((t) => ({
43
- value: t.name,
44
- label: t.name,
45
- hint: t.active ? 'active' : undefined,
46
- })),
47
- });
48
-
49
- if (p.isCancel(choice)) {
50
- cancelAndExit('Remove cancelled.');
51
- }
52
-
53
- teamName = choice;
54
- }
55
-
56
- if (!globalOpts.json && isInteractive()) {
57
- const confirmed = await p.confirm({
58
- message: `Remove team '${teamName}' and its API key?`,
59
- });
60
-
61
- if (p.isCancel(confirmed) || !confirmed) {
62
- cancelAndExit('Remove cancelled.');
63
- }
64
- }
65
-
66
- try {
67
- removeTeam(teamName);
68
- } catch (err) {
69
- outputError(
70
- {
71
- message: errorMessage(err, 'Failed to remove team'),
72
- code: 'remove_failed',
73
- },
74
- { json: globalOpts.json },
75
- );
76
- }
77
-
78
- if (globalOpts.json) {
79
- outputResult({ success: true, removed_team: teamName }, { json: true });
80
- } else if (isInteractive()) {
81
- console.log(`Team '${teamName}' removed.`);
82
- }
83
- });
@@ -1,73 +0,0 @@
1
- import * as p from '@clack/prompts';
2
- import { Command } from '@commander-js/extra-typings';
3
- import type { GlobalOpts } from '../../lib/client';
4
- import { listTeams, setActiveTeam } from '../../lib/config';
5
- import { errorMessage, outputError, outputResult } from '../../lib/output';
6
- import { cancelAndExit } from '../../lib/prompts';
7
- import { isInteractive } from '../../lib/tty';
8
-
9
- export const switchCommand = new Command('switch')
10
- .description('Switch the active team profile')
11
- .argument('[name]', 'Team name to switch to')
12
- .action(async (name, _opts, cmd) => {
13
- const globalOpts = cmd.optsWithGlobals() as GlobalOpts;
14
-
15
- let teamName = name;
16
-
17
- if (!teamName) {
18
- if (!isInteractive()) {
19
- outputError(
20
- {
21
- message:
22
- 'Missing team name. Provide a team name in non-interactive mode.',
23
- code: 'missing_name',
24
- },
25
- { json: globalOpts.json },
26
- );
27
- }
28
-
29
- const teams = listTeams();
30
- if (teams.length === 0) {
31
- outputError(
32
- {
33
- message: 'No teams configured. Run `resend login` first.',
34
- code: 'no_teams',
35
- },
36
- { json: globalOpts.json },
37
- );
38
- }
39
-
40
- const choice = await p.select({
41
- message: 'Switch to which team?',
42
- options: teams.map((t) => ({
43
- value: t.name,
44
- label: t.name,
45
- hint: t.active ? 'active' : undefined,
46
- })),
47
- });
48
-
49
- if (p.isCancel(choice)) {
50
- cancelAndExit('Switch cancelled.');
51
- }
52
-
53
- teamName = choice;
54
- }
55
-
56
- try {
57
- setActiveTeam(teamName);
58
- } catch (err) {
59
- outputError(
60
- {
61
- message: errorMessage(err, 'Failed to switch team'),
62
- code: 'switch_failed',
63
- },
64
- { json: globalOpts.json },
65
- );
66
- }
67
-
68
- if (globalOpts.json) {
69
- outputResult({ success: true, active_team: teamName }, { json: true });
70
- } else if (isInteractive()) {
71
- console.log(`Switched to team '${teamName}'.`);
72
- }
73
- });
@@ -1,73 +0,0 @@
1
- import { Command, Option } from '@commander-js/extra-typings';
2
- import { runCreate } from '../../lib/actions';
3
- import type { GlobalOpts } from '../../lib/client';
4
- import { buildHelpText } from '../../lib/help-text';
5
- import { requireText } from '../../lib/prompts';
6
-
7
- export const createTopicCommand = new Command('create')
8
- .description('Create a new topic for subscription management')
9
- .option('--name <name>', 'Topic name (required)')
10
- .option(
11
- '--description <description>',
12
- 'Description shown to contacts when managing subscriptions',
13
- )
14
- .addOption(
15
- new Option(
16
- '--default-subscription <mode>',
17
- 'Default subscription state for contacts',
18
- )
19
- .choices(['opt_in', 'opt_out'] as const)
20
- .default('opt_in' as const),
21
- )
22
- .addHelpText(
23
- 'after',
24
- buildHelpText({
25
- context: `Topics enable fine-grained subscription management. Contacts can opt in or out of
26
- individual topics. Broadcasts can target only contacts opted into a specific topic.
27
-
28
- Example topics: "Product Updates", "Security Alerts", "Weekly Digest".
29
-
30
- --default-subscription controls what happens for contacts with no explicit subscription:
31
- opt_in Contacts receive broadcasts unless they explicitly opt out (default)
32
- opt_out Contacts do NOT receive broadcasts unless they explicitly opt in
33
-
34
- Non-interactive: --name is required.`,
35
- output: ` {"id":"<uuid>"}`,
36
- errorCodes: ['auth_error', 'missing_name', 'create_error'],
37
- examples: [
38
- 'resend topics create --name "Product Updates"',
39
- 'resend topics create --name "Weekly Digest" --default-subscription opt_out',
40
- 'resend topics create --name "Security Alerts" --description "Critical security notices" --json',
41
- ],
42
- }),
43
- )
44
- .action(async (opts, cmd) => {
45
- const globalOpts = cmd.optsWithGlobals() as GlobalOpts;
46
-
47
- const name = await requireText(
48
- opts.name,
49
- { message: 'Topic name', placeholder: 'Product Updates' },
50
- { message: 'Missing --name flag.', code: 'missing_name' },
51
- globalOpts,
52
- );
53
-
54
- await runCreate(
55
- {
56
- spinner: {
57
- loading: 'Creating topic...',
58
- success: 'Topic created',
59
- fail: 'Failed to create topic',
60
- },
61
- sdkCall: (resend) =>
62
- resend.topics.create({
63
- name,
64
- defaultSubscription: opts.defaultSubscription,
65
- ...(opts.description && { description: opts.description }),
66
- }),
67
- onInteractive: (data) => {
68
- console.log(`\nTopic created: ${data.id}`);
69
- },
70
- },
71
- globalOpts,
72
- );
73
- });
@@ -1,47 +0,0 @@
1
- import { Command } from '@commander-js/extra-typings';
2
- import { runDelete } from '../../lib/actions';
3
- import type { GlobalOpts } from '../../lib/client';
4
- import { buildHelpText } from '../../lib/help-text';
5
-
6
- export const deleteTopicCommand = new Command('delete')
7
- .alias('rm')
8
- .description('Delete a topic')
9
- .argument('<id>', 'Topic UUID')
10
- .option(
11
- '--yes',
12
- 'Skip the confirmation prompt (required in non-interactive mode)',
13
- )
14
- .addHelpText(
15
- 'after',
16
- buildHelpText({
17
- context: `Warning: Deleting a topic removes all contact subscriptions to that topic and may affect
18
- broadcasts that reference this topic_id. Contacts themselves are not deleted.
19
-
20
- Non-interactive: --yes is required to confirm deletion when stdin/stdout is not a TTY.`,
21
- output: ` {"object":"topic","id":"<uuid>","deleted":true}`,
22
- errorCodes: ['auth_error', 'confirmation_required', 'delete_error'],
23
- examples: [
24
- 'resend topics delete 78261eea-8f8b-4381-83c6-79fa7120f1cf --yes',
25
- 'resend topics delete 78261eea-8f8b-4381-83c6-79fa7120f1cf --yes --json',
26
- ],
27
- }),
28
- )
29
- .action(async (id, opts, cmd) => {
30
- const globalOpts = cmd.optsWithGlobals() as GlobalOpts;
31
- await runDelete(
32
- id,
33
- !!opts.yes,
34
- {
35
- confirmMessage: `Delete topic ${id}?\nAll contact subscriptions and broadcast associations will be removed.`,
36
- spinner: {
37
- loading: 'Deleting topic...',
38
- success: 'Topic deleted',
39
- fail: 'Failed to delete topic',
40
- },
41
- object: 'topic',
42
- successMsg: 'Topic deleted.',
43
- sdkCall: (resend) => resend.topics.remove(id),
44
- },
45
- globalOpts,
46
- );
47
- });
@@ -1,42 +0,0 @@
1
- import { Command } from '@commander-js/extra-typings';
2
- import { runGet } from '../../lib/actions';
3
- import type { GlobalOpts } from '../../lib/client';
4
- import { buildHelpText } from '../../lib/help-text';
5
-
6
- export const getTopicCommand = new Command('get')
7
- .description('Retrieve a topic by ID')
8
- .argument('<id>', 'Topic UUID')
9
- .addHelpText(
10
- 'after',
11
- buildHelpText({
12
- output: ` {"id":"<uuid>","name":"<name>","description":"<desc>","default_subscription":"opt_in|opt_out","created_at":"<iso-date>"}`,
13
- errorCodes: ['auth_error', 'fetch_error'],
14
- examples: [
15
- 'resend topics get 78261eea-8f8b-4381-83c6-79fa7120f1cf',
16
- 'resend topics get 78261eea-8f8b-4381-83c6-79fa7120f1cf --json',
17
- ],
18
- }),
19
- )
20
- .action(async (id, _opts, cmd) => {
21
- const globalOpts = cmd.optsWithGlobals() as GlobalOpts;
22
- await runGet(
23
- {
24
- spinner: {
25
- loading: 'Fetching topic...',
26
- success: 'Topic fetched',
27
- fail: 'Failed to fetch topic',
28
- },
29
- sdkCall: (resend) => resend.topics.get(id),
30
- onInteractive: (data) => {
31
- console.log(`\n${data.name}`);
32
- console.log(`ID: ${data.id}`);
33
- if (data.description) {
34
- console.log(`Description: ${data.description}`);
35
- }
36
- console.log(`Default subscription: ${data.default_subscription}`);
37
- console.log(`Created: ${data.created_at}`);
38
- },
39
- },
40
- globalOpts,
41
- );
42
- });
@@ -1,42 +0,0 @@
1
- import { Command } from '@commander-js/extra-typings';
2
- import { buildHelpText } from '../../lib/help-text';
3
- import { createTopicCommand } from './create';
4
- import { deleteTopicCommand } from './delete';
5
- import { getTopicCommand } from './get';
6
- import { listTopicsCommand } from './list';
7
- import { updateTopicCommand } from './update';
8
-
9
- export const topicsCommand = new Command('topics')
10
- .description('Manage topics for contact subscription preferences')
11
- .addHelpText(
12
- 'after',
13
- buildHelpText({
14
- context: `Topics enable fine-grained subscription management beyond the global unsubscribe flag.
15
- A contact can opt in or out of individual topics independently.
16
-
17
- Broadcasts can target a topic_id so only contacts who have opted into that topic
18
- receive the email. Contacts with no subscription record use the topic's
19
- default_subscription setting.
20
-
21
- Subscription states:
22
- opt_in Contact will receive broadcasts for this topic
23
- opt_out Contact will NOT receive broadcasts for this topic
24
-
25
- Contact topic subscriptions are managed via the contacts namespace:
26
- resend contacts topics <contactId>
27
- resend contacts update-topics <contactId> --topic-id <id> --subscription opt_in`,
28
- examples: [
29
- 'resend topics list',
30
- 'resend topics create --name "Product Updates"',
31
- 'resend topics create --name "Weekly Digest" --default-subscription opt_out',
32
- 'resend topics get 78261eea-8f8b-4381-83c6-79fa7120f1cf',
33
- 'resend topics update 78261eea-8f8b-4381-83c6-79fa7120f1cf --name "Security Alerts"',
34
- 'resend topics delete 78261eea-8f8b-4381-83c6-79fa7120f1cf --yes',
35
- ],
36
- }),
37
- )
38
- .addCommand(createTopicCommand)
39
- .addCommand(getTopicCommand)
40
- .addCommand(listTopicsCommand, { isDefault: true })
41
- .addCommand(updateTopicCommand)
42
- .addCommand(deleteTopicCommand);
@@ -1,34 +0,0 @@
1
- import { Command } from '@commander-js/extra-typings';
2
- import { runList } from '../../lib/actions';
3
- import type { GlobalOpts } from '../../lib/client';
4
- import { buildHelpText } from '../../lib/help-text';
5
- import { renderTopicsTable } from './utils';
6
-
7
- export const listTopicsCommand = new Command('list')
8
- .alias('ls')
9
- .description('List all topics')
10
- .addHelpText(
11
- 'after',
12
- buildHelpText({
13
- context: `Returns all topics in your account. Topic subscription management for individual
14
- contacts is handled via "resend contacts topics <contactId>".`,
15
- output: ` {"data":[{"id":"<uuid>","name":"<name>","description":"<desc>","default_subscription":"opt_in|opt_out","created_at":"<iso-date>"}]}`,
16
- errorCodes: ['auth_error', 'list_error'],
17
- examples: ['resend topics list', 'resend topics list --json'],
18
- }),
19
- )
20
- .action(async (_opts, cmd) => {
21
- const globalOpts = cmd.optsWithGlobals() as GlobalOpts;
22
- await runList(
23
- {
24
- spinner: {
25
- loading: 'Fetching topics...',
26
- success: 'Topics fetched',
27
- fail: 'Failed to list topics',
28
- },
29
- sdkCall: (resend) => resend.topics.list(),
30
- onInteractive: (list) => console.log(renderTopicsTable(list.data)),
31
- },
32
- globalOpts,
33
- );
34
- });
@@ -1,59 +0,0 @@
1
- import { Command } from '@commander-js/extra-typings';
2
- import { runWrite } from '../../lib/actions';
3
- import type { GlobalOpts } from '../../lib/client';
4
- import { buildHelpText } from '../../lib/help-text';
5
- import { outputError } from '../../lib/output';
6
-
7
- export const updateTopicCommand = new Command('update')
8
- .description("Update a topic's name or description")
9
- .argument('<id>', 'Topic UUID')
10
- .option('--name <name>', 'New topic name')
11
- .option('--description <description>', 'New description shown to contacts')
12
- .addHelpText(
13
- 'after',
14
- buildHelpText({
15
- context: `At least one of --name or --description must be provided to update the topic.
16
-
17
- Note: --default-subscription cannot be changed after creation.
18
- To change the default subscription, delete the topic and recreate it.`,
19
- output: ` {"id":"<uuid>"}`,
20
- errorCodes: ['auth_error', 'no_changes', 'update_error'],
21
- examples: [
22
- 'resend topics update 78261eea-8f8b-4381-83c6-79fa7120f1cf --name "Security Alerts"',
23
- 'resend topics update 78261eea-8f8b-4381-83c6-79fa7120f1cf --description "Critical notices" --json',
24
- ],
25
- }),
26
- )
27
- .action(async (id, opts, cmd) => {
28
- const globalOpts = cmd.optsWithGlobals() as GlobalOpts;
29
-
30
- if (!opts.name && !opts.description) {
31
- outputError(
32
- {
33
- message:
34
- 'Provide at least one option to update: --name or --description.',
35
- code: 'no_changes',
36
- },
37
- { json: globalOpts.json },
38
- );
39
- }
40
-
41
- await runWrite(
42
- {
43
- spinner: {
44
- loading: 'Updating topic...',
45
- success: 'Topic updated',
46
- fail: 'Failed to update topic',
47
- },
48
- sdkCall: (resend) =>
49
- resend.topics.update({
50
- id,
51
- ...(opts.name && { name: opts.name }),
52
- ...(opts.description && { description: opts.description }),
53
- }),
54
- errorCode: 'update_error',
55
- successMsg: `Topic updated: ${id}`,
56
- },
57
- globalOpts,
58
- );
59
- });
@@ -1,16 +0,0 @@
1
- import type { Topic } from 'resend';
2
- import { renderTable } from '../../lib/table';
3
-
4
- export function renderTopicsTable(topics: Topic[]): string {
5
- const rows = topics.map((t) => [
6
- t.name,
7
- t.description ?? '',
8
- t.id,
9
- t.created_at,
10
- ]);
11
- return renderTable(
12
- ['Name', 'Description', 'ID', 'Created'],
13
- rows,
14
- '(no topics)',
15
- );
16
- }