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,34 +0,0 @@
1
- import { Resend } from 'resend';
2
- import { resolveApiKey } from './config';
3
- import { errorMessage, outputError } from './output';
4
-
5
- export type GlobalOpts = {
6
- apiKey?: string;
7
- json?: boolean;
8
- quiet?: boolean;
9
- team?: string;
10
- };
11
-
12
- export function createClient(flagValue?: string, teamName?: string): Resend {
13
- const resolved = resolveApiKey(flagValue, teamName);
14
- if (!resolved) {
15
- throw new Error(
16
- 'No API key found. Set RESEND_API_KEY, use --api-key, or run: resend login',
17
- );
18
- }
19
- return new Resend(resolved.key);
20
- }
21
-
22
- export function requireClient(opts: GlobalOpts): Resend {
23
- try {
24
- return createClient(opts.apiKey, opts.team);
25
- } catch (err) {
26
- outputError(
27
- {
28
- message: errorMessage(err, 'Failed to create client'),
29
- code: 'auth_error',
30
- },
31
- { json: opts.json },
32
- );
33
- }
34
- }
@@ -1,211 +0,0 @@
1
- import { chmodSync, mkdirSync, readFileSync, writeFileSync } from 'node:fs';
2
- import { homedir } from 'node:os';
3
- import { join } from 'node:path';
4
-
5
- export type ApiKeySource = 'flag' | 'env' | 'config';
6
- export type ResolvedKey = { key: string; source: ApiKeySource; team?: string };
7
-
8
- export type TeamProfile = { api_key: string };
9
- export type CredentialsFile = {
10
- active_team: string;
11
- teams: Record<string, TeamProfile>;
12
- };
13
-
14
- export function getConfigDir(): string {
15
- if (process.env.XDG_CONFIG_HOME) {
16
- return join(process.env.XDG_CONFIG_HOME, 'resend');
17
- }
18
- if (process.platform === 'win32' && process.env.APPDATA) {
19
- return join(process.env.APPDATA, 'resend');
20
- }
21
- return join(homedir(), '.config', 'resend');
22
- }
23
-
24
- function getCredentialsPath(): string {
25
- return join(getConfigDir(), 'credentials.json');
26
- }
27
-
28
- export function readCredentials(): CredentialsFile | null {
29
- try {
30
- const data = JSON.parse(readFileSync(getCredentialsPath(), 'utf-8'));
31
- // Support legacy format: { api_key: "re_xxx" }
32
- if (data.api_key && !data.teams) {
33
- return {
34
- active_team: 'default',
35
- teams: { default: { api_key: data.api_key } },
36
- };
37
- }
38
- if (data.teams) {
39
- return data as CredentialsFile;
40
- }
41
- return null;
42
- } catch {
43
- return null;
44
- }
45
- }
46
-
47
- export function writeCredentials(creds: CredentialsFile): string {
48
- const configDir = getConfigDir();
49
- mkdirSync(configDir, { recursive: true, mode: 0o700 });
50
-
51
- const configPath = getCredentialsPath();
52
- writeFileSync(configPath, `${JSON.stringify(creds, null, 2)}\n`, {
53
- mode: 0o600,
54
- });
55
- chmodSync(configPath, 0o600);
56
-
57
- return configPath;
58
- }
59
-
60
- export function resolveTeamName(flagValue?: string): string {
61
- if (flagValue) {
62
- return flagValue;
63
- }
64
-
65
- const envTeam = process.env.RESEND_TEAM;
66
- if (envTeam) {
67
- return envTeam;
68
- }
69
-
70
- const creds = readCredentials();
71
- if (creds?.active_team) {
72
- return creds.active_team;
73
- }
74
-
75
- return 'default';
76
- }
77
-
78
- export function resolveApiKey(
79
- flagValue?: string,
80
- teamName?: string,
81
- ): ResolvedKey | null {
82
- if (flagValue) {
83
- return { key: flagValue, source: 'flag' };
84
- }
85
-
86
- const envKey = process.env.RESEND_API_KEY;
87
- if (envKey) {
88
- return { key: envKey, source: 'env' };
89
- }
90
-
91
- const creds = readCredentials();
92
- if (creds) {
93
- const team = resolveTeamName(teamName);
94
- const profile = creds.teams[team];
95
- if (profile?.api_key) {
96
- return { key: profile.api_key, source: 'config', team };
97
- }
98
- }
99
-
100
- return null;
101
- }
102
-
103
- export function storeApiKey(apiKey: string, teamName?: string): string {
104
- const team = teamName || 'default';
105
- const creds = readCredentials() || { active_team: 'default', teams: {} };
106
-
107
- creds.teams[team] = { api_key: apiKey };
108
-
109
- // If this is the first team, set it as active
110
- if (Object.keys(creds.teams).length === 1) {
111
- creds.active_team = team;
112
- }
113
-
114
- return writeCredentials(creds);
115
- }
116
-
117
- export function removeAllApiKeys(): string {
118
- const configPath = getCredentialsPath();
119
- const { unlinkSync } = require('node:fs');
120
- unlinkSync(configPath);
121
- return configPath;
122
- }
123
-
124
- export function removeApiKey(teamName?: string): string {
125
- const creds = readCredentials();
126
- if (!creds) {
127
- const configPath = getCredentialsPath();
128
- // Try to delete legacy file
129
- const { unlinkSync } = require('node:fs');
130
- unlinkSync(configPath);
131
- return configPath;
132
- }
133
-
134
- const team = teamName || resolveTeamName();
135
- delete creds.teams[team];
136
-
137
- // If we removed the active team, switch to first available or "default"
138
- if (creds.active_team === team) {
139
- const remaining = Object.keys(creds.teams);
140
- creds.active_team = remaining[0] || 'default';
141
- }
142
-
143
- // If no teams left, delete the file
144
- if (Object.keys(creds.teams).length === 0) {
145
- const { unlinkSync } = require('node:fs');
146
- const configPath = getCredentialsPath();
147
- unlinkSync(configPath);
148
- return configPath;
149
- }
150
-
151
- return writeCredentials(creds);
152
- }
153
-
154
- export function setActiveTeam(teamName: string): void {
155
- const creds = readCredentials();
156
- if (!creds) {
157
- throw new Error('No credentials file found. Run: resend login');
158
- }
159
- if (!creds.teams[teamName]) {
160
- throw new Error(
161
- `Team "${teamName}" not found. Available teams: ${Object.keys(creds.teams).join(', ')}`,
162
- );
163
- }
164
- creds.active_team = teamName;
165
- writeCredentials(creds);
166
- }
167
-
168
- export function listTeams(): Array<{ name: string; active: boolean }> {
169
- const creds = readCredentials();
170
- if (!creds) {
171
- return [];
172
- }
173
- return Object.keys(creds.teams).map((name) => ({
174
- name,
175
- active: name === creds.active_team,
176
- }));
177
- }
178
-
179
- export function maskKey(key: string): string {
180
- if (key.length <= 7) {
181
- return `${key.slice(0, 3)}...`;
182
- }
183
- return `${key.slice(0, 3)}...${key.slice(-4)}`;
184
- }
185
-
186
- export function removeTeam(teamName: string): void {
187
- const creds = readCredentials();
188
- if (!creds) {
189
- throw new Error('No credentials file found.');
190
- }
191
- if (!creds.teams[teamName]) {
192
- throw new Error(
193
- `Team "${teamName}" not found. Available teams: ${Object.keys(creds.teams).join(', ')}`,
194
- );
195
- }
196
-
197
- delete creds.teams[teamName];
198
-
199
- if (creds.active_team === teamName) {
200
- const remaining = Object.keys(creds.teams);
201
- creds.active_team = remaining[0] || 'default';
202
- }
203
-
204
- if (Object.keys(creds.teams).length === 0) {
205
- const { unlinkSync } = require('node:fs');
206
- unlinkSync(getCredentialsPath());
207
- return;
208
- }
209
-
210
- writeCredentials(creds);
211
- }
@@ -1,15 +0,0 @@
1
- import { readFileSync } from 'node:fs';
2
- import type { GlobalOpts } from './client';
3
- import { outputError } from './output';
4
-
5
- /** Read a text file, exiting with file_read_error if unreadable. */
6
- export function readFile(filePath: string, globalOpts: GlobalOpts): string {
7
- try {
8
- return readFileSync(filePath, 'utf-8');
9
- } catch {
10
- outputError(
11
- { message: `Failed to read file: ${filePath}`, code: 'file_read_error' },
12
- { json: globalOpts.json },
13
- );
14
- }
15
- }
@@ -1,38 +0,0 @@
1
- export interface HelpTextOptions {
2
- context?: string; // All text before Global options (includes Non-interactive line if needed)
3
- output?: string; // Content after "Output (--json or piped):" header (raw string, may be multi-line)
4
- errorCodes?: string[]; // ['auth_error', 'list_error'] — joined with ' | '
5
- examples: string[]; // Command strings without '$ ' prefix (added automatically)
6
- setup?: boolean; // true = --json only (no --api-key); default false = full API variant
7
- }
8
-
9
- const GLOBAL_OPTS_FULL = `Global options:
10
- --api-key <key> API key (or set RESEND_API_KEY env var)
11
- --team <name> Team profile to use (overrides RESEND_TEAM)
12
- --json Force JSON output (also auto-enabled when stdout is piped)
13
- -q, --quiet Suppress spinners and status output (implies --json)`;
14
-
15
- const GLOBAL_OPTS_SETUP = `Global options:
16
- --team <name> Team profile to save the key to
17
- --json Force JSON output
18
- -q, --quiet Suppress spinners and status output (implies --json)`;
19
-
20
- const ERROR_ENVELOPE = ` {"error":{"message":"<message>","code":"<code>"}}`;
21
-
22
- export function buildHelpText(opts: HelpTextOptions): string {
23
- const parts: string[] = [];
24
- if (opts.context != null) {
25
- parts.push(opts.context);
26
- }
27
- parts.push(opts.setup ? GLOBAL_OPTS_SETUP : GLOBAL_OPTS_FULL);
28
- if (opts.output != null) {
29
- parts.push(`Output (--json or piped):\n${opts.output}`);
30
- }
31
- if (opts.errorCodes != null) {
32
- parts.push(
33
- `Errors (exit code 1):\n${ERROR_ENVELOPE}\n Codes: ${opts.errorCodes.join(' | ')}`,
34
- );
35
- }
36
- parts.push(`Examples:\n${opts.examples.map((e) => ` $ ${e}`).join('\n')}`);
37
- return `\n${parts.join('\n\n')}`;
38
- }
@@ -1,54 +0,0 @@
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
- }
@@ -1,36 +0,0 @@
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
- }
@@ -1,149 +0,0 @@
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
- }
@@ -1,93 +0,0 @@
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
- }
@@ -1,57 +0,0 @@
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
- }