groundmail-cli 0.0.0-stage → 0.16.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +74 -2
- package/dist/chunk-4SSJDDBV.js +9 -0
- package/dist/cli.d.ts +2 -0
- package/dist/cli.js +3495 -0
- package/dist/react-IXBRBSFN.js +1453 -0
- package/package.json +49 -4
package/dist/cli.js
ADDED
|
@@ -0,0 +1,3495 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import "./chunk-4SSJDDBV.js";
|
|
3
|
+
|
|
4
|
+
// src/commands/emails/send.ts
|
|
5
|
+
function readFlag(args, flag) {
|
|
6
|
+
const index = args.indexOf(flag);
|
|
7
|
+
if (index === -1) return void 0;
|
|
8
|
+
return args[index + 1];
|
|
9
|
+
}
|
|
10
|
+
function readFlags(args, flag) {
|
|
11
|
+
const values = [];
|
|
12
|
+
for (let index = 0; index < args.length; index += 1) {
|
|
13
|
+
if (args[index] === flag && args[index + 1]) values.push(args[index + 1]);
|
|
14
|
+
}
|
|
15
|
+
return values;
|
|
16
|
+
}
|
|
17
|
+
function parseTags(values) {
|
|
18
|
+
const tags = values.map((value) => {
|
|
19
|
+
const separator = value.indexOf("=");
|
|
20
|
+
if (separator <= 0) {
|
|
21
|
+
return null;
|
|
22
|
+
}
|
|
23
|
+
const name = value.slice(0, separator).trim();
|
|
24
|
+
const tagValue = value.slice(separator + 1).trim();
|
|
25
|
+
if (!name || !tagValue) return null;
|
|
26
|
+
return { name, value: tagValue };
|
|
27
|
+
});
|
|
28
|
+
if (tags.some((tag) => tag == null)) {
|
|
29
|
+
return { error: "--tag must be in name=value format" };
|
|
30
|
+
}
|
|
31
|
+
return tags;
|
|
32
|
+
}
|
|
33
|
+
function parseSendCommandArgs(args) {
|
|
34
|
+
const parsedTags = parseTags(readFlags(args, "--tag"));
|
|
35
|
+
const category = readFlag(args, "--category");
|
|
36
|
+
return {
|
|
37
|
+
from: readFlag(args, "--from"),
|
|
38
|
+
to: readFlag(args, "--to"),
|
|
39
|
+
subject: readFlag(args, "--subject"),
|
|
40
|
+
html: readFlag(args, "--html"),
|
|
41
|
+
text: readFlag(args, "--text"),
|
|
42
|
+
tags: Array.isArray(parsedTags) ? parsedTags : void 0,
|
|
43
|
+
scheduledAt: readFlag(args, "--schedule"),
|
|
44
|
+
category,
|
|
45
|
+
unsubscribe: args.includes("--unsubscribe") ? true : void 0,
|
|
46
|
+
apiKey: readFlag(args, "--api-key"),
|
|
47
|
+
baseUrl: readFlag(args, "--base-url")
|
|
48
|
+
};
|
|
49
|
+
}
|
|
50
|
+
async function runSendCommand(args, deps) {
|
|
51
|
+
const env = deps.env ?? process.env;
|
|
52
|
+
const stdout = deps.stdout ?? ((line) => console.log(line));
|
|
53
|
+
const stderr = deps.stderr ?? ((line) => console.error(line));
|
|
54
|
+
const options = parseSendCommandOptions(args, env);
|
|
55
|
+
if ("error" in options) {
|
|
56
|
+
stderr(options.error);
|
|
57
|
+
return 1;
|
|
58
|
+
}
|
|
59
|
+
try {
|
|
60
|
+
const result = await deps.sendEmail({
|
|
61
|
+
from: options.from,
|
|
62
|
+
to: options.to,
|
|
63
|
+
subject: options.subject,
|
|
64
|
+
html: options.html,
|
|
65
|
+
text: options.text,
|
|
66
|
+
tags: options.tags,
|
|
67
|
+
scheduledAt: options.scheduledAt,
|
|
68
|
+
category: options.category,
|
|
69
|
+
unsubscribe: options.unsubscribe
|
|
70
|
+
});
|
|
71
|
+
stdout(JSON.stringify(result));
|
|
72
|
+
return 0;
|
|
73
|
+
} catch (error) {
|
|
74
|
+
stderr(error instanceof Error ? error.message : String(error));
|
|
75
|
+
return 1;
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
function parseSendCommandOptions(args, env) {
|
|
79
|
+
const parsed = parseSendCommandArgs(args);
|
|
80
|
+
const apiKey = parsed.apiKey || env.GROUNDMAIL_API_KEY;
|
|
81
|
+
const parsedTags = parseTags(readFlags(args, "--tag"));
|
|
82
|
+
if (!apiKey) {
|
|
83
|
+
return { error: "Missing API key. Set GROUNDMAIL_API_KEY or pass --api-key." };
|
|
84
|
+
}
|
|
85
|
+
if ("error" in parsedTags) {
|
|
86
|
+
return { error: parsedTags.error };
|
|
87
|
+
}
|
|
88
|
+
if (!parsed.from || !parsed.to || !parsed.subject) {
|
|
89
|
+
return { error: "Required flags: --from --to --subject (and --html or --text)" };
|
|
90
|
+
}
|
|
91
|
+
if (!parsed.html && !parsed.text) {
|
|
92
|
+
return { error: "Provide --html or --text" };
|
|
93
|
+
}
|
|
94
|
+
return {
|
|
95
|
+
from: parsed.from,
|
|
96
|
+
to: parsed.to,
|
|
97
|
+
subject: parsed.subject,
|
|
98
|
+
html: parsed.html,
|
|
99
|
+
text: parsed.text,
|
|
100
|
+
tags: parsedTags.length ? parsedTags : void 0,
|
|
101
|
+
scheduledAt: parsed.scheduledAt,
|
|
102
|
+
category: parsed.category,
|
|
103
|
+
unsubscribe: parsed.unsubscribe,
|
|
104
|
+
apiKey,
|
|
105
|
+
baseUrl: parsed.baseUrl
|
|
106
|
+
};
|
|
107
|
+
}
|
|
108
|
+
function createDefaultSendDeps(apiKey, baseUrl) {
|
|
109
|
+
return {
|
|
110
|
+
sendEmail: async (params) => {
|
|
111
|
+
const { Groundmail } = await import("groundmail");
|
|
112
|
+
const client = new Groundmail(apiKey, baseUrl ? { baseUrl } : void 0);
|
|
113
|
+
return client.emails.send(params);
|
|
114
|
+
}
|
|
115
|
+
};
|
|
116
|
+
}
|
|
117
|
+
async function runSendCommandWithClient(args, env = process.env) {
|
|
118
|
+
const parsed = parseSendCommandOptions(args, env);
|
|
119
|
+
if ("error" in parsed) {
|
|
120
|
+
return { exitCode: 1, error: parsed.error };
|
|
121
|
+
}
|
|
122
|
+
const deps = createDefaultSendDeps(parsed.apiKey, parsed.baseUrl);
|
|
123
|
+
const exitCode = await runSendCommand(args, { ...deps, env });
|
|
124
|
+
return { exitCode };
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
// src/commands/emails/batch.ts
|
|
128
|
+
import { readFile } from "fs/promises";
|
|
129
|
+
function readFlag2(args, flag) {
|
|
130
|
+
const index = args.indexOf(flag);
|
|
131
|
+
if (index === -1) return void 0;
|
|
132
|
+
return args[index + 1];
|
|
133
|
+
}
|
|
134
|
+
function readFirstPositional(args) {
|
|
135
|
+
for (let index = 0; index < args.length; index += 1) {
|
|
136
|
+
const arg = args[index];
|
|
137
|
+
if (arg.startsWith("--")) {
|
|
138
|
+
if (arg === "--api-key" || arg === "--base-url" || arg === "--file") index += 1;
|
|
139
|
+
continue;
|
|
140
|
+
}
|
|
141
|
+
return arg;
|
|
142
|
+
}
|
|
143
|
+
return void 0;
|
|
144
|
+
}
|
|
145
|
+
async function runBatchCommand(args, deps) {
|
|
146
|
+
const env = deps.env ?? process.env;
|
|
147
|
+
const stdout = deps.stdout ?? ((line) => console.log(line));
|
|
148
|
+
const stderr = deps.stderr ?? ((line) => console.error(line));
|
|
149
|
+
const apiKey = readFlag2(args, "--api-key") || env.GROUNDMAIL_API_KEY;
|
|
150
|
+
if (!apiKey) {
|
|
151
|
+
stderr("Missing API key. Set GROUNDMAIL_API_KEY or pass --api-key.");
|
|
152
|
+
return { exitCode: 1, error: "missing key" };
|
|
153
|
+
}
|
|
154
|
+
const filePath = readFlag2(args, "--file") || readFirstPositional(args);
|
|
155
|
+
if (!filePath) {
|
|
156
|
+
stderr("Required: --file <emails.json>");
|
|
157
|
+
return { exitCode: 1, error: "missing file" };
|
|
158
|
+
}
|
|
159
|
+
try {
|
|
160
|
+
const readTextFile = deps.readTextFile ?? ((path) => readFile(path, "utf8"));
|
|
161
|
+
const source = await readTextFile(filePath);
|
|
162
|
+
const payload = parseBatchPayload(source);
|
|
163
|
+
if ("error" in payload) {
|
|
164
|
+
stderr(payload.error);
|
|
165
|
+
return { exitCode: 1, error: payload.error };
|
|
166
|
+
}
|
|
167
|
+
const result = await deps.sendBatch(payload.emails);
|
|
168
|
+
stdout(JSON.stringify(result));
|
|
169
|
+
return { exitCode: 0 };
|
|
170
|
+
} catch (error) {
|
|
171
|
+
stderr(error instanceof Error ? error.message : String(error));
|
|
172
|
+
return { exitCode: 1, error: String(error) };
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
async function runBatchCommandWithClient(args, env = process.env) {
|
|
176
|
+
const apiKey = readFlag2(args, "--api-key") || env.GROUNDMAIL_API_KEY;
|
|
177
|
+
const baseUrl = readFlag2(args, "--base-url");
|
|
178
|
+
if (!apiKey) {
|
|
179
|
+
return { exitCode: 1, error: "Missing API key. Set GROUNDMAIL_API_KEY or pass --api-key." };
|
|
180
|
+
}
|
|
181
|
+
const { Groundmail } = await import("groundmail");
|
|
182
|
+
const client = new Groundmail(apiKey, baseUrl ? { baseUrl } : void 0);
|
|
183
|
+
return runBatchCommand(args, {
|
|
184
|
+
env,
|
|
185
|
+
sendBatch: (emails) => client.emails.batch(emails)
|
|
186
|
+
});
|
|
187
|
+
}
|
|
188
|
+
function parseBatchPayload(source) {
|
|
189
|
+
let parsed;
|
|
190
|
+
try {
|
|
191
|
+
parsed = JSON.parse(source);
|
|
192
|
+
} catch {
|
|
193
|
+
return { error: "Batch file must be valid JSON." };
|
|
194
|
+
}
|
|
195
|
+
const emails = Array.isArray(parsed) ? parsed : parsed && typeof parsed === "object" && "emails" in parsed && Array.isArray(parsed.emails) ? parsed.emails : null;
|
|
196
|
+
if (!emails || emails.length === 0) {
|
|
197
|
+
return { error: "Batch file must contain a non-empty array of emails." };
|
|
198
|
+
}
|
|
199
|
+
return { emails };
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
// src/commands/emails/cancel.ts
|
|
203
|
+
function readFlag3(args, flag) {
|
|
204
|
+
const index = args.indexOf(flag);
|
|
205
|
+
if (index === -1) return void 0;
|
|
206
|
+
return args[index + 1];
|
|
207
|
+
}
|
|
208
|
+
async function runCancelCommand(args, deps) {
|
|
209
|
+
const env = deps.env ?? process.env;
|
|
210
|
+
const stdout = deps.stdout ?? ((line) => console.log(line));
|
|
211
|
+
const stderr = deps.stderr ?? ((line) => console.error(line));
|
|
212
|
+
const apiKey = readFlag3(args, "--api-key") || env.GROUNDMAIL_API_KEY;
|
|
213
|
+
const id = readFlag3(args, "--id") || args.find((arg) => !arg.startsWith("--"));
|
|
214
|
+
if (!apiKey) {
|
|
215
|
+
stderr("Missing API key. Set GROUNDMAIL_API_KEY or pass --api-key.");
|
|
216
|
+
return { exitCode: 1, error: "missing key" };
|
|
217
|
+
}
|
|
218
|
+
if (!id) {
|
|
219
|
+
stderr("Required: --id <msg_\u2026>");
|
|
220
|
+
return { exitCode: 1, error: "missing id" };
|
|
221
|
+
}
|
|
222
|
+
try {
|
|
223
|
+
const result = await deps.cancelEmail(id);
|
|
224
|
+
stdout(JSON.stringify(result));
|
|
225
|
+
return { exitCode: 0 };
|
|
226
|
+
} catch (error) {
|
|
227
|
+
stderr(error instanceof Error ? error.message : String(error));
|
|
228
|
+
return { exitCode: 1, error: String(error) };
|
|
229
|
+
}
|
|
230
|
+
}
|
|
231
|
+
async function runCancelCommandWithClient(args, env = process.env) {
|
|
232
|
+
const apiKey = readFlag3(args, "--api-key") || env.GROUNDMAIL_API_KEY;
|
|
233
|
+
const baseUrl = readFlag3(args, "--base-url");
|
|
234
|
+
if (!apiKey) {
|
|
235
|
+
return { exitCode: 1, error: "Missing API key. Set GROUNDMAIL_API_KEY or pass --api-key." };
|
|
236
|
+
}
|
|
237
|
+
const { Groundmail } = await import("groundmail");
|
|
238
|
+
const client = new Groundmail(apiKey, baseUrl ? { baseUrl } : void 0);
|
|
239
|
+
return runCancelCommand(args, {
|
|
240
|
+
env,
|
|
241
|
+
cancelEmail: (id) => client.emails.cancel(id)
|
|
242
|
+
});
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
// src/commands/emails/resend.ts
|
|
246
|
+
function readFlag4(args, flag) {
|
|
247
|
+
const index = args.indexOf(flag);
|
|
248
|
+
if (index === -1) return void 0;
|
|
249
|
+
return args[index + 1];
|
|
250
|
+
}
|
|
251
|
+
async function runResendCommand(args, deps) {
|
|
252
|
+
const env = deps.env ?? process.env;
|
|
253
|
+
const stdout = deps.stdout ?? ((line) => console.log(line));
|
|
254
|
+
const stderr = deps.stderr ?? ((line) => console.error(line));
|
|
255
|
+
const apiKey = readFlag4(args, "--api-key") || env.GROUNDMAIL_API_KEY;
|
|
256
|
+
const id = readFlag4(args, "--id") || args.find((arg) => !arg.startsWith("--"));
|
|
257
|
+
if (!apiKey) {
|
|
258
|
+
stderr("Missing API key. Set GROUNDMAIL_API_KEY or pass --api-key.");
|
|
259
|
+
return { exitCode: 1, error: "missing key" };
|
|
260
|
+
}
|
|
261
|
+
if (!id) {
|
|
262
|
+
stderr("Required: --id <msg_\u2026>");
|
|
263
|
+
return { exitCode: 1, error: "missing id" };
|
|
264
|
+
}
|
|
265
|
+
try {
|
|
266
|
+
const result = await deps.resendEmail(id);
|
|
267
|
+
stdout(JSON.stringify(result));
|
|
268
|
+
return { exitCode: 0 };
|
|
269
|
+
} catch (error) {
|
|
270
|
+
stderr(error instanceof Error ? error.message : String(error));
|
|
271
|
+
return { exitCode: 1, error: String(error) };
|
|
272
|
+
}
|
|
273
|
+
}
|
|
274
|
+
async function runResendCommandWithClient(args, env = process.env) {
|
|
275
|
+
const apiKey = readFlag4(args, "--api-key") || env.GROUNDMAIL_API_KEY;
|
|
276
|
+
const baseUrl = readFlag4(args, "--base-url");
|
|
277
|
+
if (!apiKey) {
|
|
278
|
+
return { exitCode: 1, error: "Missing API key. Set GROUNDMAIL_API_KEY or pass --api-key." };
|
|
279
|
+
}
|
|
280
|
+
const { Groundmail } = await import("groundmail");
|
|
281
|
+
const client = new Groundmail(apiKey, baseUrl ? { baseUrl } : void 0);
|
|
282
|
+
return runResendCommand(args, {
|
|
283
|
+
env,
|
|
284
|
+
resendEmail: (id) => client.emails.resend(id)
|
|
285
|
+
});
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
// src/commands/emails/get.ts
|
|
289
|
+
function readFlag5(args, flag) {
|
|
290
|
+
const index = args.indexOf(flag);
|
|
291
|
+
if (index === -1) return void 0;
|
|
292
|
+
return args[index + 1];
|
|
293
|
+
}
|
|
294
|
+
async function runGetCommand(args, deps) {
|
|
295
|
+
const env = deps.env ?? process.env;
|
|
296
|
+
const stdout = deps.stdout ?? ((line) => console.log(line));
|
|
297
|
+
const stderr = deps.stderr ?? ((line) => console.error(line));
|
|
298
|
+
const id = readFlag5(args, "--id") || args.find((a) => !a.startsWith("--"));
|
|
299
|
+
const apiKey = readFlag5(args, "--api-key") || env.GROUNDMAIL_API_KEY;
|
|
300
|
+
if (!apiKey) {
|
|
301
|
+
stderr("Missing API key. Set GROUNDMAIL_API_KEY or pass --api-key.");
|
|
302
|
+
return { exitCode: 1, error: "missing key" };
|
|
303
|
+
}
|
|
304
|
+
if (!id) {
|
|
305
|
+
stderr("Required: --id <msg_\u2026>");
|
|
306
|
+
return { exitCode: 1, error: "missing id" };
|
|
307
|
+
}
|
|
308
|
+
try {
|
|
309
|
+
const result = await deps.getEmail(id);
|
|
310
|
+
stdout(JSON.stringify(result));
|
|
311
|
+
return { exitCode: 0 };
|
|
312
|
+
} catch (error) {
|
|
313
|
+
stderr(error instanceof Error ? error.message : String(error));
|
|
314
|
+
return { exitCode: 1, error: String(error) };
|
|
315
|
+
}
|
|
316
|
+
}
|
|
317
|
+
async function runGetCommandWithClient(args, env = process.env) {
|
|
318
|
+
const apiKey = readFlag5(args, "--api-key") || env.GROUNDMAIL_API_KEY;
|
|
319
|
+
const baseUrl = readFlag5(args, "--base-url");
|
|
320
|
+
if (!apiKey) {
|
|
321
|
+
return { exitCode: 1, error: "Missing API key. Set GROUNDMAIL_API_KEY or pass --api-key." };
|
|
322
|
+
}
|
|
323
|
+
const { Groundmail } = await import("groundmail");
|
|
324
|
+
const client = new Groundmail(apiKey, baseUrl ? { baseUrl } : void 0);
|
|
325
|
+
return runGetCommand(args, {
|
|
326
|
+
env,
|
|
327
|
+
getEmail: (id) => client.emails.get(id)
|
|
328
|
+
});
|
|
329
|
+
}
|
|
330
|
+
|
|
331
|
+
// src/commands/emails/insights.ts
|
|
332
|
+
function readFlag6(args, flag) {
|
|
333
|
+
const index = args.indexOf(flag);
|
|
334
|
+
if (index === -1) return void 0;
|
|
335
|
+
return args[index + 1];
|
|
336
|
+
}
|
|
337
|
+
async function runInsightsCommand(args, deps) {
|
|
338
|
+
const env = deps.env ?? process.env;
|
|
339
|
+
const stdout = deps.stdout ?? ((line) => console.log(line));
|
|
340
|
+
const stderr = deps.stderr ?? ((line) => console.error(line));
|
|
341
|
+
const apiKey = readFlag6(args, "--api-key") || env.GROUNDMAIL_API_KEY;
|
|
342
|
+
if (!apiKey) {
|
|
343
|
+
stderr("Missing API key. Set GROUNDMAIL_API_KEY or pass --api-key.");
|
|
344
|
+
return { exitCode: 1, error: "missing key" };
|
|
345
|
+
}
|
|
346
|
+
const windowFlag = readFlag6(args, "--window");
|
|
347
|
+
if (windowFlag && windowFlag !== "7d" && windowFlag !== "30d") {
|
|
348
|
+
stderr("Invalid --window. Use 7d or 30d.");
|
|
349
|
+
return { exitCode: 1, error: "invalid window" };
|
|
350
|
+
}
|
|
351
|
+
try {
|
|
352
|
+
const result = await deps.getInsights(windowFlag);
|
|
353
|
+
stdout(JSON.stringify(result));
|
|
354
|
+
return { exitCode: 0 };
|
|
355
|
+
} catch (error) {
|
|
356
|
+
stderr(error instanceof Error ? error.message : String(error));
|
|
357
|
+
return { exitCode: 1, error: String(error) };
|
|
358
|
+
}
|
|
359
|
+
}
|
|
360
|
+
async function runInsightsCommandWithClient(args, env = process.env) {
|
|
361
|
+
const apiKey = readFlag6(args, "--api-key") || env.GROUNDMAIL_API_KEY;
|
|
362
|
+
const baseUrl = readFlag6(args, "--base-url");
|
|
363
|
+
if (!apiKey) {
|
|
364
|
+
return { exitCode: 1, error: "Missing API key. Set GROUNDMAIL_API_KEY or pass --api-key." };
|
|
365
|
+
}
|
|
366
|
+
const { Groundmail } = await import("groundmail");
|
|
367
|
+
const client = new Groundmail(apiKey, baseUrl ? { baseUrl } : void 0);
|
|
368
|
+
return runInsightsCommand(args, {
|
|
369
|
+
env,
|
|
370
|
+
getInsights: (window) => client.emails.insights(window)
|
|
371
|
+
});
|
|
372
|
+
}
|
|
373
|
+
|
|
374
|
+
// src/commands/emails/list.ts
|
|
375
|
+
function readFlag7(args, flag) {
|
|
376
|
+
const index = args.indexOf(flag);
|
|
377
|
+
if (index === -1) return void 0;
|
|
378
|
+
return args[index + 1];
|
|
379
|
+
}
|
|
380
|
+
async function runListCommand(args, deps) {
|
|
381
|
+
const env = deps.env ?? process.env;
|
|
382
|
+
const stdout = deps.stdout ?? ((line) => console.log(line));
|
|
383
|
+
const stderr = deps.stderr ?? ((line) => console.error(line));
|
|
384
|
+
const apiKey = readFlag7(args, "--api-key") || env.GROUNDMAIL_API_KEY;
|
|
385
|
+
if (!apiKey) {
|
|
386
|
+
stderr("Missing API key. Set GROUNDMAIL_API_KEY or pass --api-key.");
|
|
387
|
+
return { exitCode: 1, error: "missing key" };
|
|
388
|
+
}
|
|
389
|
+
const limitRaw = readFlag7(args, "--limit");
|
|
390
|
+
const cursor = readFlag7(args, "--cursor");
|
|
391
|
+
const limit = limitRaw ? Number(limitRaw) : void 0;
|
|
392
|
+
try {
|
|
393
|
+
const result = await deps.listEmails({
|
|
394
|
+
...limit != null && Number.isFinite(limit) ? { limit } : {},
|
|
395
|
+
...cursor ? { cursor } : {}
|
|
396
|
+
});
|
|
397
|
+
stdout(JSON.stringify(result));
|
|
398
|
+
return { exitCode: 0 };
|
|
399
|
+
} catch (error) {
|
|
400
|
+
stderr(error instanceof Error ? error.message : String(error));
|
|
401
|
+
return { exitCode: 1, error: String(error) };
|
|
402
|
+
}
|
|
403
|
+
}
|
|
404
|
+
async function runListCommandWithClient(args, env = process.env) {
|
|
405
|
+
const apiKey = readFlag7(args, "--api-key") || env.GROUNDMAIL_API_KEY;
|
|
406
|
+
const baseUrl = readFlag7(args, "--base-url");
|
|
407
|
+
if (!apiKey) {
|
|
408
|
+
return { exitCode: 1, error: "Missing API key. Set GROUNDMAIL_API_KEY or pass --api-key." };
|
|
409
|
+
}
|
|
410
|
+
const { Groundmail } = await import("groundmail");
|
|
411
|
+
const client = new Groundmail(apiKey, baseUrl ? { baseUrl } : void 0);
|
|
412
|
+
return runListCommand(args, {
|
|
413
|
+
env,
|
|
414
|
+
listEmails: (params) => client.emails.list(params)
|
|
415
|
+
});
|
|
416
|
+
}
|
|
417
|
+
|
|
418
|
+
// src/commands/events/send.ts
|
|
419
|
+
function readFlag8(args, flag) {
|
|
420
|
+
const index = args.indexOf(flag);
|
|
421
|
+
if (index === -1) return void 0;
|
|
422
|
+
return args[index + 1];
|
|
423
|
+
}
|
|
424
|
+
function resolveApiKey(args, env) {
|
|
425
|
+
return readFlag8(args, "--api-key") || env.GROUNDMAIL_API_KEY || null;
|
|
426
|
+
}
|
|
427
|
+
async function createClient(apiKey, baseUrl) {
|
|
428
|
+
const { Groundmail } = await import("groundmail");
|
|
429
|
+
return new Groundmail(apiKey, baseUrl ? { baseUrl } : void 0);
|
|
430
|
+
}
|
|
431
|
+
async function runSendEventCommand(args, env = process.env) {
|
|
432
|
+
const apiKey = resolveApiKey(args, env);
|
|
433
|
+
if (!apiKey) {
|
|
434
|
+
return { exitCode: 1, error: "Missing API key. Set GROUNDMAIL_API_KEY or pass --api-key." };
|
|
435
|
+
}
|
|
436
|
+
const name = readFlag8(args, "--name") || readFlag8(args, "--type");
|
|
437
|
+
if (!name) {
|
|
438
|
+
return { exitCode: 1, error: "Required flag: --name or --type" };
|
|
439
|
+
}
|
|
440
|
+
const email = readFlag8(args, "--email");
|
|
441
|
+
const userId = readFlag8(args, "--user-id");
|
|
442
|
+
const dataRaw = readFlag8(args, "--data");
|
|
443
|
+
const idempotencyKey = readFlag8(args, "--idempotency-key");
|
|
444
|
+
const baseUrl = readFlag8(args, "--base-url");
|
|
445
|
+
let data;
|
|
446
|
+
if (dataRaw) {
|
|
447
|
+
try {
|
|
448
|
+
const parsed = JSON.parse(dataRaw);
|
|
449
|
+
if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) {
|
|
450
|
+
return { exitCode: 1, error: "--data must be a JSON object" };
|
|
451
|
+
}
|
|
452
|
+
data = parsed;
|
|
453
|
+
} catch (error) {
|
|
454
|
+
return {
|
|
455
|
+
exitCode: 1,
|
|
456
|
+
error: error instanceof Error ? error.message : "--data must be valid JSON"
|
|
457
|
+
};
|
|
458
|
+
}
|
|
459
|
+
}
|
|
460
|
+
try {
|
|
461
|
+
const client = await createClient(apiKey, baseUrl);
|
|
462
|
+
const result = await client.events.trigger({
|
|
463
|
+
name,
|
|
464
|
+
email,
|
|
465
|
+
user_id: userId,
|
|
466
|
+
data,
|
|
467
|
+
idempotencyKey
|
|
468
|
+
});
|
|
469
|
+
console.log(JSON.stringify(result));
|
|
470
|
+
return { exitCode: 0 };
|
|
471
|
+
} catch (error) {
|
|
472
|
+
return { exitCode: 1, error: error instanceof Error ? error.message : String(error) };
|
|
473
|
+
}
|
|
474
|
+
}
|
|
475
|
+
|
|
476
|
+
// src/commands/automations/shared.ts
|
|
477
|
+
import { readFile as readFile2 } from "fs/promises";
|
|
478
|
+
function readFlag9(args, flag) {
|
|
479
|
+
const index = args.indexOf(flag);
|
|
480
|
+
if (index === -1) return void 0;
|
|
481
|
+
return args[index + 1];
|
|
482
|
+
}
|
|
483
|
+
function parseAutomationsCommonArgs(args) {
|
|
484
|
+
return {
|
|
485
|
+
apiKey: readFlag9(args, "--api-key"),
|
|
486
|
+
baseUrl: readFlag9(args, "--base-url")
|
|
487
|
+
};
|
|
488
|
+
}
|
|
489
|
+
function resolveApiKey2(options, env) {
|
|
490
|
+
return options.apiKey || env.GROUNDMAIL_API_KEY || null;
|
|
491
|
+
}
|
|
492
|
+
async function createAutomationsClient(apiKey, baseUrl) {
|
|
493
|
+
const { Groundmail } = await import("groundmail");
|
|
494
|
+
return new Groundmail(apiKey, baseUrl ? { baseUrl } : void 0);
|
|
495
|
+
}
|
|
496
|
+
async function readAutomationDefinition(args) {
|
|
497
|
+
const file = readFlag9(args, "--file");
|
|
498
|
+
if (file) {
|
|
499
|
+
try {
|
|
500
|
+
const raw = await readFile2(file, "utf8");
|
|
501
|
+
const parsed = JSON.parse(raw);
|
|
502
|
+
if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) {
|
|
503
|
+
return { ok: false, error: "--file must contain a JSON object" };
|
|
504
|
+
}
|
|
505
|
+
return { ok: true, value: parsed };
|
|
506
|
+
} catch (error) {
|
|
507
|
+
return {
|
|
508
|
+
ok: false,
|
|
509
|
+
error: error instanceof Error ? error.message : "Failed to read --file"
|
|
510
|
+
};
|
|
511
|
+
}
|
|
512
|
+
}
|
|
513
|
+
const name = readFlag9(args, "--name");
|
|
514
|
+
const triggerEvent = readFlag9(args, "--trigger") || readFlag9(args, "--event");
|
|
515
|
+
const stepsRaw = readFlag9(args, "--steps");
|
|
516
|
+
let steps;
|
|
517
|
+
if (stepsRaw) {
|
|
518
|
+
try {
|
|
519
|
+
steps = JSON.parse(stepsRaw);
|
|
520
|
+
} catch (error) {
|
|
521
|
+
return {
|
|
522
|
+
ok: false,
|
|
523
|
+
error: error instanceof Error ? error.message : "--steps must be valid JSON"
|
|
524
|
+
};
|
|
525
|
+
}
|
|
526
|
+
}
|
|
527
|
+
return {
|
|
528
|
+
ok: true,
|
|
529
|
+
value: {
|
|
530
|
+
...name ? { name } : {},
|
|
531
|
+
...triggerEvent ? { trigger: { type: "event", event: triggerEvent } } : {},
|
|
532
|
+
...steps !== void 0 ? { steps } : {}
|
|
533
|
+
}
|
|
534
|
+
};
|
|
535
|
+
}
|
|
536
|
+
|
|
537
|
+
// src/commands/automations/list.ts
|
|
538
|
+
async function runListCommand2(args, env = process.env) {
|
|
539
|
+
const options = parseAutomationsCommonArgs(args);
|
|
540
|
+
const apiKey = resolveApiKey2(options, env);
|
|
541
|
+
if (!apiKey) {
|
|
542
|
+
return { exitCode: 1, error: "Missing API key. Set GROUNDMAIL_API_KEY or pass --api-key." };
|
|
543
|
+
}
|
|
544
|
+
try {
|
|
545
|
+
const client = await createAutomationsClient(apiKey, options.baseUrl);
|
|
546
|
+
const result = await client.automations.list();
|
|
547
|
+
console.log(JSON.stringify(result));
|
|
548
|
+
return { exitCode: 0 };
|
|
549
|
+
} catch (error) {
|
|
550
|
+
return { exitCode: 1, error: error instanceof Error ? error.message : String(error) };
|
|
551
|
+
}
|
|
552
|
+
}
|
|
553
|
+
|
|
554
|
+
// src/commands/automations/get.ts
|
|
555
|
+
async function runGetCommand2(args, env = process.env) {
|
|
556
|
+
const options = parseAutomationsCommonArgs(args);
|
|
557
|
+
const apiKey = resolveApiKey2(options, env);
|
|
558
|
+
if (!apiKey) {
|
|
559
|
+
return { exitCode: 1, error: "Missing API key. Set GROUNDMAIL_API_KEY or pass --api-key." };
|
|
560
|
+
}
|
|
561
|
+
const id = readFlag9(args, "--id");
|
|
562
|
+
if (!id) return { exitCode: 1, error: "Required flag: --id" };
|
|
563
|
+
try {
|
|
564
|
+
const client = await createAutomationsClient(apiKey, options.baseUrl);
|
|
565
|
+
const result = await client.automations.get(id);
|
|
566
|
+
console.log(JSON.stringify(result));
|
|
567
|
+
return { exitCode: 0 };
|
|
568
|
+
} catch (error) {
|
|
569
|
+
return { exitCode: 1, error: error instanceof Error ? error.message : String(error) };
|
|
570
|
+
}
|
|
571
|
+
}
|
|
572
|
+
|
|
573
|
+
// src/commands/automations/create.ts
|
|
574
|
+
async function runCreateCommand(args, env = process.env) {
|
|
575
|
+
const options = parseAutomationsCommonArgs(args);
|
|
576
|
+
const apiKey = resolveApiKey2(options, env);
|
|
577
|
+
if (!apiKey) {
|
|
578
|
+
return { exitCode: 1, error: "Missing API key. Set GROUNDMAIL_API_KEY or pass --api-key." };
|
|
579
|
+
}
|
|
580
|
+
const definition = await readAutomationDefinition(args);
|
|
581
|
+
if (!definition.ok) return { exitCode: 1, error: definition.error };
|
|
582
|
+
const { name, trigger, steps } = definition.value;
|
|
583
|
+
if (!name || typeof name !== "string") {
|
|
584
|
+
return { exitCode: 1, error: "Required: --name or name in --file" };
|
|
585
|
+
}
|
|
586
|
+
if (!trigger) {
|
|
587
|
+
return { exitCode: 1, error: "Required: --trigger/--event or trigger in --file" };
|
|
588
|
+
}
|
|
589
|
+
if (!steps) {
|
|
590
|
+
return { exitCode: 1, error: "Required: --steps or steps in --file" };
|
|
591
|
+
}
|
|
592
|
+
try {
|
|
593
|
+
const client = await createAutomationsClient(apiKey, options.baseUrl);
|
|
594
|
+
const result = await client.automations.create({
|
|
595
|
+
name,
|
|
596
|
+
trigger,
|
|
597
|
+
steps
|
|
598
|
+
});
|
|
599
|
+
console.log(JSON.stringify(result));
|
|
600
|
+
return { exitCode: 0 };
|
|
601
|
+
} catch (error) {
|
|
602
|
+
return { exitCode: 1, error: error instanceof Error ? error.message : String(error) };
|
|
603
|
+
}
|
|
604
|
+
}
|
|
605
|
+
|
|
606
|
+
// src/commands/automations/update.ts
|
|
607
|
+
async function runUpdateCommand(args, env = process.env) {
|
|
608
|
+
const options = parseAutomationsCommonArgs(args);
|
|
609
|
+
const apiKey = resolveApiKey2(options, env);
|
|
610
|
+
if (!apiKey) {
|
|
611
|
+
return { exitCode: 1, error: "Missing API key. Set GROUNDMAIL_API_KEY or pass --api-key." };
|
|
612
|
+
}
|
|
613
|
+
const id = readFlag9(args, "--id");
|
|
614
|
+
if (!id) return { exitCode: 1, error: "Required flag: --id" };
|
|
615
|
+
const definition = await readAutomationDefinition(args);
|
|
616
|
+
if (!definition.ok) return { exitCode: 1, error: definition.error };
|
|
617
|
+
const { name, trigger, steps } = definition.value;
|
|
618
|
+
if (!name && !trigger && !steps) {
|
|
619
|
+
return {
|
|
620
|
+
exitCode: 1,
|
|
621
|
+
error: "Nothing to update. Pass --file and/or --name/--trigger/--steps."
|
|
622
|
+
};
|
|
623
|
+
}
|
|
624
|
+
try {
|
|
625
|
+
const client = await createAutomationsClient(apiKey, options.baseUrl);
|
|
626
|
+
const result = await client.automations.update(id, {
|
|
627
|
+
...name ? { name } : {},
|
|
628
|
+
...trigger ? { trigger } : {},
|
|
629
|
+
...steps ? { steps } : {}
|
|
630
|
+
});
|
|
631
|
+
console.log(JSON.stringify(result));
|
|
632
|
+
return { exitCode: 0 };
|
|
633
|
+
} catch (error) {
|
|
634
|
+
return { exitCode: 1, error: error instanceof Error ? error.message : String(error) };
|
|
635
|
+
}
|
|
636
|
+
}
|
|
637
|
+
|
|
638
|
+
// src/commands/automations/actions.ts
|
|
639
|
+
async function runActivateCommand(args, env = process.env) {
|
|
640
|
+
const options = parseAutomationsCommonArgs(args);
|
|
641
|
+
const apiKey = resolveApiKey2(options, env);
|
|
642
|
+
if (!apiKey) {
|
|
643
|
+
return { exitCode: 1, error: "Missing API key. Set GROUNDMAIL_API_KEY or pass --api-key." };
|
|
644
|
+
}
|
|
645
|
+
const id = readFlag9(args, "--id");
|
|
646
|
+
if (!id) return { exitCode: 1, error: "Required flag: --id" };
|
|
647
|
+
try {
|
|
648
|
+
const client = await createAutomationsClient(apiKey, options.baseUrl);
|
|
649
|
+
const result = await client.automations.activate(id);
|
|
650
|
+
console.log(JSON.stringify(result));
|
|
651
|
+
return { exitCode: 0 };
|
|
652
|
+
} catch (error) {
|
|
653
|
+
return { exitCode: 1, error: error instanceof Error ? error.message : String(error) };
|
|
654
|
+
}
|
|
655
|
+
}
|
|
656
|
+
async function runPauseCommand(args, env = process.env) {
|
|
657
|
+
const options = parseAutomationsCommonArgs(args);
|
|
658
|
+
const apiKey = resolveApiKey2(options, env);
|
|
659
|
+
if (!apiKey) {
|
|
660
|
+
return { exitCode: 1, error: "Missing API key. Set GROUNDMAIL_API_KEY or pass --api-key." };
|
|
661
|
+
}
|
|
662
|
+
const id = readFlag9(args, "--id");
|
|
663
|
+
if (!id) return { exitCode: 1, error: "Required flag: --id" };
|
|
664
|
+
try {
|
|
665
|
+
const client = await createAutomationsClient(apiKey, options.baseUrl);
|
|
666
|
+
const result = await client.automations.pause(id);
|
|
667
|
+
console.log(JSON.stringify(result));
|
|
668
|
+
return { exitCode: 0 };
|
|
669
|
+
} catch (error) {
|
|
670
|
+
return { exitCode: 1, error: error instanceof Error ? error.message : String(error) };
|
|
671
|
+
}
|
|
672
|
+
}
|
|
673
|
+
async function runDeleteCommand(args, env = process.env) {
|
|
674
|
+
const options = parseAutomationsCommonArgs(args);
|
|
675
|
+
const apiKey = resolveApiKey2(options, env);
|
|
676
|
+
if (!apiKey) {
|
|
677
|
+
return { exitCode: 1, error: "Missing API key. Set GROUNDMAIL_API_KEY or pass --api-key." };
|
|
678
|
+
}
|
|
679
|
+
const id = readFlag9(args, "--id");
|
|
680
|
+
if (!id) return { exitCode: 1, error: "Required flag: --id" };
|
|
681
|
+
try {
|
|
682
|
+
const client = await createAutomationsClient(apiKey, options.baseUrl);
|
|
683
|
+
const result = await client.automations.remove(id);
|
|
684
|
+
console.log(JSON.stringify(result));
|
|
685
|
+
return { exitCode: 0 };
|
|
686
|
+
} catch (error) {
|
|
687
|
+
return { exitCode: 1, error: error instanceof Error ? error.message : String(error) };
|
|
688
|
+
}
|
|
689
|
+
}
|
|
690
|
+
|
|
691
|
+
// src/commands/domains/shared.ts
|
|
692
|
+
function readFlag10(args, flag) {
|
|
693
|
+
const index = args.indexOf(flag);
|
|
694
|
+
if (index === -1) return void 0;
|
|
695
|
+
return args[index + 1];
|
|
696
|
+
}
|
|
697
|
+
function parseDomainsCommonArgs(args) {
|
|
698
|
+
return {
|
|
699
|
+
apiKey: readFlag10(args, "--api-key"),
|
|
700
|
+
baseUrl: readFlag10(args, "--base-url"),
|
|
701
|
+
name: readFlag10(args, "--name") || readFlag10(args, "--domain"),
|
|
702
|
+
id: readFlag10(args, "--id")
|
|
703
|
+
};
|
|
704
|
+
}
|
|
705
|
+
function resolveApiKey3(options, env) {
|
|
706
|
+
return options.apiKey || env.GROUNDMAIL_API_KEY || null;
|
|
707
|
+
}
|
|
708
|
+
async function createDomainsClient(apiKey, baseUrl) {
|
|
709
|
+
const { Groundmail } = await import("groundmail");
|
|
710
|
+
return new Groundmail(apiKey, baseUrl ? { baseUrl } : void 0);
|
|
711
|
+
}
|
|
712
|
+
function formatDnsRecords(records) {
|
|
713
|
+
const lines = ["DNS records to add:", ""];
|
|
714
|
+
for (const record of records) {
|
|
715
|
+
lines.push(` ${record.type.padEnd(6)} ${record.host}`);
|
|
716
|
+
if (record.type === "MX" && record.priority != null) {
|
|
717
|
+
lines.push(` priority: ${record.priority}`);
|
|
718
|
+
lines.push(` mail server: ${record.value}`);
|
|
719
|
+
} else {
|
|
720
|
+
lines.push(` ${record.value}`);
|
|
721
|
+
}
|
|
722
|
+
lines.push(` (${record.purpose})`);
|
|
723
|
+
lines.push("");
|
|
724
|
+
}
|
|
725
|
+
return lines.join("\n").trimEnd();
|
|
726
|
+
}
|
|
727
|
+
|
|
728
|
+
// src/commands/domains/add.ts
|
|
729
|
+
async function runAddCommand(args, env = process.env) {
|
|
730
|
+
const options = parseDomainsCommonArgs(args);
|
|
731
|
+
const apiKey = resolveApiKey3(options, env);
|
|
732
|
+
if (!apiKey) {
|
|
733
|
+
return { exitCode: 1, error: "Missing API key. Set GROUNDMAIL_API_KEY or pass --api-key." };
|
|
734
|
+
}
|
|
735
|
+
const name = options.name || args.find((arg) => !arg.startsWith("--") && arg.includes("."));
|
|
736
|
+
if (!name) {
|
|
737
|
+
return {
|
|
738
|
+
exitCode: 1,
|
|
739
|
+
error: "Missing domain. Usage: groundmail domains add example.com"
|
|
740
|
+
};
|
|
741
|
+
}
|
|
742
|
+
try {
|
|
743
|
+
const client = await createDomainsClient(apiKey, options.baseUrl);
|
|
744
|
+
const inbound = readFlag10(args, "--inbound");
|
|
745
|
+
const result = await client.domains.create({
|
|
746
|
+
name,
|
|
747
|
+
...inbound === "true" || inbound === "1" ? { inbound_enabled: true } : {}
|
|
748
|
+
});
|
|
749
|
+
console.log(JSON.stringify(result));
|
|
750
|
+
console.error("");
|
|
751
|
+
console.error(`Domain ${result.domain.name} added (${result.domain.status}).`);
|
|
752
|
+
console.error(formatDnsRecords(result.records));
|
|
753
|
+
console.error("");
|
|
754
|
+
console.error(`Then: groundmail domains verify ${result.domain.name}`);
|
|
755
|
+
return { exitCode: 0 };
|
|
756
|
+
} catch (error) {
|
|
757
|
+
return { exitCode: 1, error: error instanceof Error ? error.message : String(error) };
|
|
758
|
+
}
|
|
759
|
+
}
|
|
760
|
+
|
|
761
|
+
// src/commands/domains/apply.ts
|
|
762
|
+
async function runApplyCommand(args, env = process.env) {
|
|
763
|
+
const options = parseDomainsCommonArgs(args);
|
|
764
|
+
const apiKey = resolveApiKey3(options, env);
|
|
765
|
+
if (!apiKey) {
|
|
766
|
+
return { exitCode: 1, error: "Missing API key. Set GROUNDMAIL_API_KEY or pass --api-key." };
|
|
767
|
+
}
|
|
768
|
+
const providerFlag = args.includes("--provider") ? args[args.indexOf("--provider") + 1] : void 0;
|
|
769
|
+
const provider = (providerFlag || "").toLowerCase();
|
|
770
|
+
if (provider !== "cloudflare" && provider !== "godaddy" && provider !== "vercel") {
|
|
771
|
+
return {
|
|
772
|
+
exitCode: 1,
|
|
773
|
+
error: "Missing or invalid --provider. Use --provider cloudflare, --provider vercel, or --provider godaddy."
|
|
774
|
+
};
|
|
775
|
+
}
|
|
776
|
+
const idOrName = options.id || options.name || args.find((arg, i) => {
|
|
777
|
+
if (arg.startsWith("--")) return false;
|
|
778
|
+
const prev = args[i - 1];
|
|
779
|
+
if (prev === "--provider" || prev === "--api-key" || prev === "--base-url" || prev === "--name" || prev === "--domain" || prev === "--id") {
|
|
780
|
+
return false;
|
|
781
|
+
}
|
|
782
|
+
return arg.includes(".") || arg.length > 8;
|
|
783
|
+
});
|
|
784
|
+
if (!idOrName) {
|
|
785
|
+
return {
|
|
786
|
+
exitCode: 1,
|
|
787
|
+
error: "Missing domain. Usage: groundmail domains apply example.com --provider cloudflare"
|
|
788
|
+
};
|
|
789
|
+
}
|
|
790
|
+
try {
|
|
791
|
+
const client = await createDomainsClient(apiKey, options.baseUrl);
|
|
792
|
+
const localCloudflare = env.CLOUDFLARE_API_TOKEN || env.CLOUDFLARE_API_KEY;
|
|
793
|
+
const localVercel = env.VERCEL_API_TOKEN;
|
|
794
|
+
const useStoredCloudflare = provider === "cloudflare" && !localCloudflare;
|
|
795
|
+
const useStoredVercel = provider === "vercel" && !localVercel;
|
|
796
|
+
const result = useStoredCloudflare || useStoredVercel ? await client.domains.apply(idOrName, { provider }) : await client.domains.applyDns({
|
|
797
|
+
idOrName,
|
|
798
|
+
provider,
|
|
799
|
+
cloudflareApiToken: localCloudflare,
|
|
800
|
+
cloudflareZoneId: env.CLOUDFLARE_ZONE_ID,
|
|
801
|
+
godaddyApiKey: env.GODADDY_API_KEY,
|
|
802
|
+
godaddyApiSecret: env.GODADDY_API_SECRET,
|
|
803
|
+
vercelApiToken: localVercel,
|
|
804
|
+
vercelTeamId: env.VERCEL_TEAM_ID
|
|
805
|
+
});
|
|
806
|
+
console.log(JSON.stringify(result));
|
|
807
|
+
console.error(
|
|
808
|
+
useStoredCloudflare ? `Applied ${result.results.length} DNS change(s) via stored Cloudflare credentials for ${result.domain}.` : useStoredVercel ? `Applied ${result.results.length} DNS change(s) via stored Vercel credentials for ${result.domain}.` : `Applied ${result.results.length} DNS change(s) via ${result.provider} for ${result.domain}.`
|
|
809
|
+
);
|
|
810
|
+
for (const row of result.results) {
|
|
811
|
+
console.error(` ${row.action.padEnd(10)} ${row.purpose} \u2192 ${row.host}`);
|
|
812
|
+
}
|
|
813
|
+
console.error("");
|
|
814
|
+
console.error(`Next: groundmail domains verify ${result.domain}`);
|
|
815
|
+
return { exitCode: 0 };
|
|
816
|
+
} catch (error) {
|
|
817
|
+
return { exitCode: 1, error: error instanceof Error ? error.message : String(error) };
|
|
818
|
+
}
|
|
819
|
+
}
|
|
820
|
+
|
|
821
|
+
// src/commands/domains/delete.ts
|
|
822
|
+
var POSITIONAL_FLAGS = ["--api-key", "--base-url", "--name", "--domain", "--id"];
|
|
823
|
+
async function runDeleteCommand2(args, env = process.env) {
|
|
824
|
+
const options = parseDomainsCommonArgs(args);
|
|
825
|
+
const apiKey = resolveApiKey3(options, env);
|
|
826
|
+
if (!apiKey) return { exitCode: 1, error: "Missing API key. Set GROUNDMAIL_API_KEY or pass --api-key." };
|
|
827
|
+
const idOrName = options.id || options.name || readFirstPositional2(args);
|
|
828
|
+
if (!idOrName) return { exitCode: 1, error: "Missing domain. Usage: groundmail domains delete example.com" };
|
|
829
|
+
try {
|
|
830
|
+
const client = await createDomainsClient(apiKey, options.baseUrl);
|
|
831
|
+
console.log(JSON.stringify(await client.domains.delete(idOrName)));
|
|
832
|
+
return { exitCode: 0 };
|
|
833
|
+
} catch (error) {
|
|
834
|
+
return { exitCode: 1, error: error instanceof Error ? error.message : String(error) };
|
|
835
|
+
}
|
|
836
|
+
}
|
|
837
|
+
function readFirstPositional2(args) {
|
|
838
|
+
for (let i = 0; i < args.length; i += 1) {
|
|
839
|
+
const arg = args[i];
|
|
840
|
+
if (arg.startsWith("--")) {
|
|
841
|
+
if (POSITIONAL_FLAGS.includes(arg)) i += 1;
|
|
842
|
+
continue;
|
|
843
|
+
}
|
|
844
|
+
return arg;
|
|
845
|
+
}
|
|
846
|
+
return void 0;
|
|
847
|
+
}
|
|
848
|
+
|
|
849
|
+
// src/commands/domains/get.ts
|
|
850
|
+
var POSITIONAL_FLAGS2 = ["--api-key", "--base-url", "--name", "--domain", "--id"];
|
|
851
|
+
async function runGetCommand3(args, env = process.env) {
|
|
852
|
+
const options = parseDomainsCommonArgs(args);
|
|
853
|
+
const apiKey = resolveApiKey3(options, env);
|
|
854
|
+
if (!apiKey) return { exitCode: 1, error: "Missing API key. Set GROUNDMAIL_API_KEY or pass --api-key." };
|
|
855
|
+
const idOrName = options.id || options.name || readFirstPositional3(args);
|
|
856
|
+
if (!idOrName) return { exitCode: 1, error: "Missing domain. Usage: groundmail domains get example.com" };
|
|
857
|
+
try {
|
|
858
|
+
const client = await createDomainsClient(apiKey, options.baseUrl);
|
|
859
|
+
console.log(JSON.stringify(await client.domains.get(idOrName)));
|
|
860
|
+
return { exitCode: 0 };
|
|
861
|
+
} catch (error) {
|
|
862
|
+
return { exitCode: 1, error: error instanceof Error ? error.message : String(error) };
|
|
863
|
+
}
|
|
864
|
+
}
|
|
865
|
+
function readFirstPositional3(args) {
|
|
866
|
+
for (let i = 0; i < args.length; i += 1) {
|
|
867
|
+
const arg = args[i];
|
|
868
|
+
if (arg.startsWith("--")) {
|
|
869
|
+
if (POSITIONAL_FLAGS2.includes(arg)) i += 1;
|
|
870
|
+
continue;
|
|
871
|
+
}
|
|
872
|
+
return arg;
|
|
873
|
+
}
|
|
874
|
+
return void 0;
|
|
875
|
+
}
|
|
876
|
+
|
|
877
|
+
// src/commands/domains/list.ts
|
|
878
|
+
async function runListCommand3(args, env = process.env) {
|
|
879
|
+
const options = parseDomainsCommonArgs(args);
|
|
880
|
+
const apiKey = resolveApiKey3(options, env);
|
|
881
|
+
if (!apiKey) {
|
|
882
|
+
return { exitCode: 1, error: "Missing API key. Set GROUNDMAIL_API_KEY or pass --api-key." };
|
|
883
|
+
}
|
|
884
|
+
try {
|
|
885
|
+
const client = await createDomainsClient(apiKey, options.baseUrl);
|
|
886
|
+
const result = await client.domains.list();
|
|
887
|
+
console.log(JSON.stringify(result));
|
|
888
|
+
return { exitCode: 0 };
|
|
889
|
+
} catch (error) {
|
|
890
|
+
return { exitCode: 1, error: error instanceof Error ? error.message : String(error) };
|
|
891
|
+
}
|
|
892
|
+
}
|
|
893
|
+
|
|
894
|
+
// src/commands/domains/update.ts
|
|
895
|
+
function parseBooleanFlag(value, flag) {
|
|
896
|
+
if (value == null) return void 0;
|
|
897
|
+
if (value === "true" || value === "1") return true;
|
|
898
|
+
if (value === "false" || value === "0") return false;
|
|
899
|
+
throw new Error(`${flag} must be true or false`);
|
|
900
|
+
}
|
|
901
|
+
var POSITIONAL_FLAGS3 = [
|
|
902
|
+
"--api-key",
|
|
903
|
+
"--base-url",
|
|
904
|
+
"--name",
|
|
905
|
+
"--domain",
|
|
906
|
+
"--id",
|
|
907
|
+
"--tls-mode",
|
|
908
|
+
"--open-tracking",
|
|
909
|
+
"--click-tracking",
|
|
910
|
+
"--inbound"
|
|
911
|
+
];
|
|
912
|
+
function readFirstPositional4(args) {
|
|
913
|
+
for (let i = 0; i < args.length; i += 1) {
|
|
914
|
+
const arg = args[i];
|
|
915
|
+
if (arg.startsWith("--")) {
|
|
916
|
+
if (POSITIONAL_FLAGS3.includes(arg)) i += 1;
|
|
917
|
+
continue;
|
|
918
|
+
}
|
|
919
|
+
return arg;
|
|
920
|
+
}
|
|
921
|
+
return void 0;
|
|
922
|
+
}
|
|
923
|
+
async function runUpdateCommand2(args, env = process.env) {
|
|
924
|
+
const options = parseDomainsCommonArgs(args);
|
|
925
|
+
const apiKey = resolveApiKey3(options, env);
|
|
926
|
+
if (!apiKey) {
|
|
927
|
+
return { exitCode: 1, error: "Missing API key. Set GROUNDMAIL_API_KEY or pass --api-key." };
|
|
928
|
+
}
|
|
929
|
+
const idOrName = options.id || options.name || readFirstPositional4(args);
|
|
930
|
+
if (!idOrName) {
|
|
931
|
+
return {
|
|
932
|
+
exitCode: 1,
|
|
933
|
+
error: "Missing domain. Usage: groundmail domains update example.com --inbound true"
|
|
934
|
+
};
|
|
935
|
+
}
|
|
936
|
+
try {
|
|
937
|
+
const payload = {};
|
|
938
|
+
const tlsMode = readFlag10(args, "--tls-mode");
|
|
939
|
+
if (tlsMode) {
|
|
940
|
+
if (tlsMode !== "opportunistic" && tlsMode !== "enforced") {
|
|
941
|
+
return { exitCode: 1, error: "--tls-mode must be opportunistic or enforced" };
|
|
942
|
+
}
|
|
943
|
+
payload.tls_mode = tlsMode;
|
|
944
|
+
}
|
|
945
|
+
const openTracking = parseBooleanFlag(readFlag10(args, "--open-tracking"), "--open-tracking");
|
|
946
|
+
if (openTracking !== void 0) payload.open_tracking = openTracking;
|
|
947
|
+
const clickTracking = parseBooleanFlag(readFlag10(args, "--click-tracking"), "--click-tracking");
|
|
948
|
+
if (clickTracking !== void 0) payload.click_tracking = clickTracking;
|
|
949
|
+
const inbound = parseBooleanFlag(readFlag10(args, "--inbound"), "--inbound");
|
|
950
|
+
if (inbound !== void 0) payload.inbound_enabled = inbound;
|
|
951
|
+
if (!Object.keys(payload).length) {
|
|
952
|
+
return {
|
|
953
|
+
exitCode: 1,
|
|
954
|
+
error: "No updates provided. Pass --inbound true|false, --tls-mode opportunistic|enforced, --open-tracking true|false, or --click-tracking true|false."
|
|
955
|
+
};
|
|
956
|
+
}
|
|
957
|
+
const client = await createDomainsClient(apiKey, options.baseUrl);
|
|
958
|
+
console.log(JSON.stringify(await client.domains.update(idOrName, payload)));
|
|
959
|
+
return { exitCode: 0 };
|
|
960
|
+
} catch (error) {
|
|
961
|
+
return { exitCode: 1, error: error instanceof Error ? error.message : String(error) };
|
|
962
|
+
}
|
|
963
|
+
}
|
|
964
|
+
|
|
965
|
+
// src/commands/domains/verify.ts
|
|
966
|
+
async function runVerifyCommand(args, env = process.env) {
|
|
967
|
+
const options = parseDomainsCommonArgs(args);
|
|
968
|
+
const apiKey = resolveApiKey3(options, env);
|
|
969
|
+
if (!apiKey) {
|
|
970
|
+
return { exitCode: 1, error: "Missing API key. Set GROUNDMAIL_API_KEY or pass --api-key." };
|
|
971
|
+
}
|
|
972
|
+
const idOrName = options.id || options.name || args.find((arg) => !arg.startsWith("--") && (arg.includes(".") || arg.length > 8));
|
|
973
|
+
if (!idOrName) {
|
|
974
|
+
return {
|
|
975
|
+
exitCode: 1,
|
|
976
|
+
error: "Missing domain. Usage: groundmail domains verify example.com"
|
|
977
|
+
};
|
|
978
|
+
}
|
|
979
|
+
try {
|
|
980
|
+
const client = await createDomainsClient(apiKey, options.baseUrl);
|
|
981
|
+
const result = await client.domains.verify(idOrName);
|
|
982
|
+
console.log(JSON.stringify(result));
|
|
983
|
+
if (result.verified) {
|
|
984
|
+
console.error(`Domain ${result.domain.name} verified.`);
|
|
985
|
+
}
|
|
986
|
+
return { exitCode: 0 };
|
|
987
|
+
} catch (error) {
|
|
988
|
+
return { exitCode: 1, error: error instanceof Error ? error.message : String(error) };
|
|
989
|
+
}
|
|
990
|
+
}
|
|
991
|
+
|
|
992
|
+
// src/commands/received-emails/shared.ts
|
|
993
|
+
function readFlag11(args, flag) {
|
|
994
|
+
const index = args.indexOf(flag);
|
|
995
|
+
if (index === -1) return void 0;
|
|
996
|
+
return args[index + 1];
|
|
997
|
+
}
|
|
998
|
+
function parseReceivedEmailsCommonArgs(args) {
|
|
999
|
+
return {
|
|
1000
|
+
apiKey: readFlag11(args, "--api-key"),
|
|
1001
|
+
baseUrl: readFlag11(args, "--base-url"),
|
|
1002
|
+
id: readFlag11(args, "--id")
|
|
1003
|
+
};
|
|
1004
|
+
}
|
|
1005
|
+
function resolveApiKey4(options, env) {
|
|
1006
|
+
return options.apiKey || env.GROUNDMAIL_API_KEY || null;
|
|
1007
|
+
}
|
|
1008
|
+
async function createReceivedEmailsClient(apiKey, baseUrl) {
|
|
1009
|
+
const { Groundmail } = await import("groundmail");
|
|
1010
|
+
return new Groundmail(apiKey, baseUrl ? { baseUrl } : void 0);
|
|
1011
|
+
}
|
|
1012
|
+
|
|
1013
|
+
// src/commands/received-emails/get.ts
|
|
1014
|
+
var POSITIONAL_FLAGS4 = ["--api-key", "--base-url", "--id"];
|
|
1015
|
+
function readFirstPositional5(args) {
|
|
1016
|
+
for (let i = 0; i < args.length; i += 1) {
|
|
1017
|
+
const arg = args[i];
|
|
1018
|
+
if (arg.startsWith("--")) {
|
|
1019
|
+
if (POSITIONAL_FLAGS4.includes(arg)) i += 1;
|
|
1020
|
+
continue;
|
|
1021
|
+
}
|
|
1022
|
+
return arg;
|
|
1023
|
+
}
|
|
1024
|
+
return void 0;
|
|
1025
|
+
}
|
|
1026
|
+
async function runGetCommand4(args, env = process.env) {
|
|
1027
|
+
const options = parseReceivedEmailsCommonArgs(args);
|
|
1028
|
+
const apiKey = resolveApiKey4(options, env);
|
|
1029
|
+
if (!apiKey) {
|
|
1030
|
+
return { exitCode: 1, error: "Missing API key. Set GROUNDMAIL_API_KEY or pass --api-key." };
|
|
1031
|
+
}
|
|
1032
|
+
const id = options.id || readFirstPositional5(args);
|
|
1033
|
+
if (!id) {
|
|
1034
|
+
return { exitCode: 1, error: "Missing id. Usage: groundmail received-emails get inb_..." };
|
|
1035
|
+
}
|
|
1036
|
+
try {
|
|
1037
|
+
const client = await createReceivedEmailsClient(apiKey, options.baseUrl);
|
|
1038
|
+
console.log(JSON.stringify(await client.receivedEmails.get(id)));
|
|
1039
|
+
return { exitCode: 0 };
|
|
1040
|
+
} catch (error) {
|
|
1041
|
+
return { exitCode: 1, error: error instanceof Error ? error.message : String(error) };
|
|
1042
|
+
}
|
|
1043
|
+
}
|
|
1044
|
+
|
|
1045
|
+
// src/commands/received-emails/list.ts
|
|
1046
|
+
async function runListCommand4(args, env = process.env) {
|
|
1047
|
+
const options = parseReceivedEmailsCommonArgs(args);
|
|
1048
|
+
const apiKey = resolveApiKey4(options, env);
|
|
1049
|
+
if (!apiKey) {
|
|
1050
|
+
return { exitCode: 1, error: "Missing API key. Set GROUNDMAIL_API_KEY or pass --api-key." };
|
|
1051
|
+
}
|
|
1052
|
+
try {
|
|
1053
|
+
const client = await createReceivedEmailsClient(apiKey, options.baseUrl);
|
|
1054
|
+
console.log(
|
|
1055
|
+
JSON.stringify(
|
|
1056
|
+
await client.receivedEmails.list({
|
|
1057
|
+
limit: readFlag11(args, "--limit") ? Number(readFlag11(args, "--limit")) : void 0,
|
|
1058
|
+
cursor: readFlag11(args, "--cursor"),
|
|
1059
|
+
domain: readFlag11(args, "--domain")
|
|
1060
|
+
})
|
|
1061
|
+
)
|
|
1062
|
+
);
|
|
1063
|
+
return { exitCode: 0 };
|
|
1064
|
+
} catch (error) {
|
|
1065
|
+
return { exitCode: 1, error: error instanceof Error ? error.message : String(error) };
|
|
1066
|
+
}
|
|
1067
|
+
}
|
|
1068
|
+
|
|
1069
|
+
// src/commands/webhooks/shared.ts
|
|
1070
|
+
function readFlag12(args, flag) {
|
|
1071
|
+
const index = args.indexOf(flag);
|
|
1072
|
+
if (index === -1) return void 0;
|
|
1073
|
+
return args[index + 1];
|
|
1074
|
+
}
|
|
1075
|
+
function parseWebhooksCommonArgs(args) {
|
|
1076
|
+
return {
|
|
1077
|
+
apiKey: readFlag12(args, "--api-key"),
|
|
1078
|
+
baseUrl: readFlag12(args, "--base-url")
|
|
1079
|
+
};
|
|
1080
|
+
}
|
|
1081
|
+
function resolveApiKey5(options, env) {
|
|
1082
|
+
return options.apiKey || env.GROUNDMAIL_API_KEY || null;
|
|
1083
|
+
}
|
|
1084
|
+
async function createWebhooksClient(apiKey, baseUrl) {
|
|
1085
|
+
const { Groundmail } = await import("groundmail");
|
|
1086
|
+
return new Groundmail(apiKey, baseUrl ? { baseUrl } : void 0);
|
|
1087
|
+
}
|
|
1088
|
+
|
|
1089
|
+
// src/commands/webhooks/create.ts
|
|
1090
|
+
async function runCreateCommand2(args, env = process.env) {
|
|
1091
|
+
const options = parseWebhooksCommonArgs(args);
|
|
1092
|
+
const apiKey = resolveApiKey5(options, env);
|
|
1093
|
+
if (!apiKey) {
|
|
1094
|
+
return { exitCode: 1, error: "Missing API key. Set GROUNDMAIL_API_KEY or pass --api-key." };
|
|
1095
|
+
}
|
|
1096
|
+
const url = readFlag12(args, "--url");
|
|
1097
|
+
if (!url) {
|
|
1098
|
+
return { exitCode: 1, error: "Required flag: --url" };
|
|
1099
|
+
}
|
|
1100
|
+
try {
|
|
1101
|
+
const client = await createWebhooksClient(apiKey, options.baseUrl);
|
|
1102
|
+
const result = await client.webhooks.create({ url });
|
|
1103
|
+
console.log(JSON.stringify(result));
|
|
1104
|
+
return { exitCode: 0 };
|
|
1105
|
+
} catch (error) {
|
|
1106
|
+
return { exitCode: 1, error: error instanceof Error ? error.message : String(error) };
|
|
1107
|
+
}
|
|
1108
|
+
}
|
|
1109
|
+
|
|
1110
|
+
// src/commands/webhooks/get.ts
|
|
1111
|
+
var POSITIONAL_FLAGS5 = ["--api-key", "--base-url", "--id"];
|
|
1112
|
+
async function runGetCommand5(args, env = process.env) {
|
|
1113
|
+
const options = parseWebhooksCommonArgs(args);
|
|
1114
|
+
const apiKey = resolveApiKey5(options, env);
|
|
1115
|
+
if (!apiKey) return { exitCode: 1, error: "Missing API key. Set GROUNDMAIL_API_KEY or pass --api-key." };
|
|
1116
|
+
const id = readFlag13(args, "--id") || readFirstPositional6(args);
|
|
1117
|
+
if (!id) return { exitCode: 1, error: "Missing webhook id. Pass --id wh_\u2026" };
|
|
1118
|
+
try {
|
|
1119
|
+
const client = await createWebhooksClient(apiKey, options.baseUrl);
|
|
1120
|
+
console.log(JSON.stringify(await client.webhooks.get(id)));
|
|
1121
|
+
return { exitCode: 0 };
|
|
1122
|
+
} catch (error) {
|
|
1123
|
+
return { exitCode: 1, error: error instanceof Error ? error.message : String(error) };
|
|
1124
|
+
}
|
|
1125
|
+
}
|
|
1126
|
+
function readFlag13(args, flag) {
|
|
1127
|
+
const index = args.indexOf(flag);
|
|
1128
|
+
return index === -1 ? void 0 : args[index + 1];
|
|
1129
|
+
}
|
|
1130
|
+
function readFirstPositional6(args) {
|
|
1131
|
+
for (let i = 0; i < args.length; i += 1) {
|
|
1132
|
+
const arg = args[i];
|
|
1133
|
+
if (arg.startsWith("--")) {
|
|
1134
|
+
if (POSITIONAL_FLAGS5.includes(arg)) i += 1;
|
|
1135
|
+
continue;
|
|
1136
|
+
}
|
|
1137
|
+
return arg;
|
|
1138
|
+
}
|
|
1139
|
+
return void 0;
|
|
1140
|
+
}
|
|
1141
|
+
|
|
1142
|
+
// src/commands/webhooks/delete.ts
|
|
1143
|
+
async function runDeleteCommand3(args, env = process.env) {
|
|
1144
|
+
const options = parseWebhooksCommonArgs(args);
|
|
1145
|
+
const apiKey = resolveApiKey5(options, env);
|
|
1146
|
+
if (!apiKey) {
|
|
1147
|
+
return { exitCode: 1, error: "Missing API key. Set GROUNDMAIL_API_KEY or pass --api-key." };
|
|
1148
|
+
}
|
|
1149
|
+
const id = readFlag12(args, "--id");
|
|
1150
|
+
if (!id) {
|
|
1151
|
+
return { exitCode: 1, error: "Required flag: --id" };
|
|
1152
|
+
}
|
|
1153
|
+
try {
|
|
1154
|
+
const client = await createWebhooksClient(apiKey, options.baseUrl);
|
|
1155
|
+
const result = await client.webhooks.delete(id);
|
|
1156
|
+
console.log(JSON.stringify(result));
|
|
1157
|
+
return { exitCode: 0 };
|
|
1158
|
+
} catch (error) {
|
|
1159
|
+
return { exitCode: 1, error: error instanceof Error ? error.message : String(error) };
|
|
1160
|
+
}
|
|
1161
|
+
}
|
|
1162
|
+
|
|
1163
|
+
// src/commands/webhooks/list.ts
|
|
1164
|
+
async function runListCommand5(args, env = process.env) {
|
|
1165
|
+
const options = parseWebhooksCommonArgs(args);
|
|
1166
|
+
const apiKey = resolveApiKey5(options, env);
|
|
1167
|
+
if (!apiKey) {
|
|
1168
|
+
return { exitCode: 1, error: "Missing API key. Set GROUNDMAIL_API_KEY or pass --api-key." };
|
|
1169
|
+
}
|
|
1170
|
+
try {
|
|
1171
|
+
const client = await createWebhooksClient(apiKey, options.baseUrl);
|
|
1172
|
+
const result = await client.webhooks.list();
|
|
1173
|
+
console.log(JSON.stringify(result));
|
|
1174
|
+
return { exitCode: 0 };
|
|
1175
|
+
} catch (error) {
|
|
1176
|
+
return { exitCode: 1, error: error instanceof Error ? error.message : String(error) };
|
|
1177
|
+
}
|
|
1178
|
+
}
|
|
1179
|
+
|
|
1180
|
+
// src/commands/webhooks/update.ts
|
|
1181
|
+
var POSITIONAL_FLAGS6 = ["--api-key", "--base-url", "--id", "--url", "--event", "--enabled"];
|
|
1182
|
+
async function runUpdateCommand3(args, env = process.env) {
|
|
1183
|
+
const options = parseWebhooksCommonArgs(args);
|
|
1184
|
+
const apiKey = resolveApiKey5(options, env);
|
|
1185
|
+
if (!apiKey) return { exitCode: 1, error: "Missing API key. Set GROUNDMAIL_API_KEY or pass --api-key." };
|
|
1186
|
+
const id = readFlag14(args, "--id") || readFirstPositional7(args);
|
|
1187
|
+
if (!id) return { exitCode: 1, error: "Missing webhook id. Pass --id wh_\u2026" };
|
|
1188
|
+
const url = readFlag14(args, "--url");
|
|
1189
|
+
const events = readFlags2(args, "--event");
|
|
1190
|
+
const enabled = parseEnabledFlag(readFlag14(args, "--enabled"));
|
|
1191
|
+
if ("error" in enabled) return { exitCode: 1, error: enabled.error };
|
|
1192
|
+
if (!url && events.length === 0 && enabled.value === void 0) {
|
|
1193
|
+
return { exitCode: 1, error: "Nothing to update. Pass --url, --event, or --enabled true|false." };
|
|
1194
|
+
}
|
|
1195
|
+
const payload = {
|
|
1196
|
+
...url ? { url } : {},
|
|
1197
|
+
...events.length > 0 ? { events } : {},
|
|
1198
|
+
...enabled.value !== void 0 ? { enabled: enabled.value } : {}
|
|
1199
|
+
};
|
|
1200
|
+
try {
|
|
1201
|
+
const client = await createWebhooksClient(apiKey, options.baseUrl);
|
|
1202
|
+
console.log(JSON.stringify(await client.webhooks.update(id, payload)));
|
|
1203
|
+
return { exitCode: 0 };
|
|
1204
|
+
} catch (error) {
|
|
1205
|
+
return { exitCode: 1, error: error instanceof Error ? error.message : String(error) };
|
|
1206
|
+
}
|
|
1207
|
+
}
|
|
1208
|
+
function readFlag14(args, flag) {
|
|
1209
|
+
const index = args.indexOf(flag);
|
|
1210
|
+
return index === -1 ? void 0 : args[index + 1];
|
|
1211
|
+
}
|
|
1212
|
+
function readFlags2(args, flag) {
|
|
1213
|
+
const values = [];
|
|
1214
|
+
for (let i = 0; i < args.length; i += 1) {
|
|
1215
|
+
if (args[i] === flag && args[i + 1]) values.push(args[i + 1]);
|
|
1216
|
+
}
|
|
1217
|
+
return values;
|
|
1218
|
+
}
|
|
1219
|
+
function readFirstPositional7(args) {
|
|
1220
|
+
for (let i = 0; i < args.length; i += 1) {
|
|
1221
|
+
const arg = args[i];
|
|
1222
|
+
if (arg.startsWith("--")) {
|
|
1223
|
+
if (POSITIONAL_FLAGS6.includes(arg)) i += 1;
|
|
1224
|
+
continue;
|
|
1225
|
+
}
|
|
1226
|
+
return arg;
|
|
1227
|
+
}
|
|
1228
|
+
return void 0;
|
|
1229
|
+
}
|
|
1230
|
+
function parseEnabledFlag(value) {
|
|
1231
|
+
if (value === void 0) return { value: void 0 };
|
|
1232
|
+
if (value === "true") return { value: true };
|
|
1233
|
+
if (value === "false") return { value: false };
|
|
1234
|
+
return { error: "--enabled must be true or false" };
|
|
1235
|
+
}
|
|
1236
|
+
|
|
1237
|
+
// src/commands/keys/list.ts
|
|
1238
|
+
function readFlag15(args, flag) {
|
|
1239
|
+
const index = args.indexOf(flag);
|
|
1240
|
+
if (index === -1) return void 0;
|
|
1241
|
+
return args[index + 1];
|
|
1242
|
+
}
|
|
1243
|
+
async function runListKeysCommandWithClient(args, env = process.env) {
|
|
1244
|
+
const apiKey = readFlag15(args, "--api-key") || env.GROUNDMAIL_API_KEY;
|
|
1245
|
+
const baseUrl = readFlag15(args, "--base-url");
|
|
1246
|
+
if (!apiKey) {
|
|
1247
|
+
return { exitCode: 1, error: "Missing API key. Set GROUNDMAIL_API_KEY or pass --api-key." };
|
|
1248
|
+
}
|
|
1249
|
+
const { Groundmail } = await import("groundmail");
|
|
1250
|
+
const client = new Groundmail(apiKey, baseUrl ? { baseUrl } : void 0);
|
|
1251
|
+
try {
|
|
1252
|
+
const limitRaw = readFlag15(args, "--limit");
|
|
1253
|
+
const cursor = readFlag15(args, "--cursor");
|
|
1254
|
+
const result = await client.apiKeys.list({
|
|
1255
|
+
...limitRaw ? { limit: Number(limitRaw) } : {},
|
|
1256
|
+
...cursor ? { cursor } : {}
|
|
1257
|
+
});
|
|
1258
|
+
console.log(JSON.stringify(result));
|
|
1259
|
+
return { exitCode: 0 };
|
|
1260
|
+
} catch (error) {
|
|
1261
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
1262
|
+
console.error(message);
|
|
1263
|
+
return { exitCode: 1, error: message };
|
|
1264
|
+
}
|
|
1265
|
+
}
|
|
1266
|
+
|
|
1267
|
+
// src/session/brand.ts
|
|
1268
|
+
var GROUNDMAIL = {
|
|
1269
|
+
bin: "groundmail",
|
|
1270
|
+
product: "Groundmail",
|
|
1271
|
+
configDirName: "groundmail",
|
|
1272
|
+
keyEnvVar: "GROUNDMAIL_API_KEY",
|
|
1273
|
+
npx: "npx -y groundmail-cli@latest"
|
|
1274
|
+
};
|
|
1275
|
+
function cliBrand(_env = process.env) {
|
|
1276
|
+
return GROUNDMAIL;
|
|
1277
|
+
}
|
|
1278
|
+
var SDK_DEFAULT_API_URL = "https://api.groundmail.dev";
|
|
1279
|
+
var DEFAULT_API_URL_ENV = "GROUNDMAIL_DEFAULT_API_URL";
|
|
1280
|
+
function defaultApiUrl(env = process.env) {
|
|
1281
|
+
return trimUrl(env[DEFAULT_API_URL_ENV]) || SDK_DEFAULT_API_URL;
|
|
1282
|
+
}
|
|
1283
|
+
function explicitApiUrl(env = process.env) {
|
|
1284
|
+
return trimUrl(env.GROUNDMAIL_API_URL) || null;
|
|
1285
|
+
}
|
|
1286
|
+
function trimUrl(value) {
|
|
1287
|
+
return (value ?? "").trim().replace(/\/+$/, "");
|
|
1288
|
+
}
|
|
1289
|
+
|
|
1290
|
+
// package.json
|
|
1291
|
+
var version = "0.16.0";
|
|
1292
|
+
|
|
1293
|
+
// src/user-agent.ts
|
|
1294
|
+
function buildUserAgent(own) {
|
|
1295
|
+
return `${own.name}/${own.version} node/${process.versions.node}`;
|
|
1296
|
+
}
|
|
1297
|
+
function withUserAgent(init, userAgent) {
|
|
1298
|
+
const headers = init?.headers;
|
|
1299
|
+
if (headers instanceof Headers) {
|
|
1300
|
+
const next = new Headers(headers);
|
|
1301
|
+
if (!next.has("user-agent")) next.set("User-Agent", userAgent);
|
|
1302
|
+
return { ...init, headers: next };
|
|
1303
|
+
}
|
|
1304
|
+
if (Array.isArray(headers)) {
|
|
1305
|
+
const has2 = headers.some(([key]) => key.toLowerCase() === "user-agent");
|
|
1306
|
+
return { ...init, headers: has2 ? headers : [...headers, ["User-Agent", userAgent]] };
|
|
1307
|
+
}
|
|
1308
|
+
const record = headers ?? {};
|
|
1309
|
+
const has = Object.keys(record).some((key) => key.toLowerCase() === "user-agent");
|
|
1310
|
+
return { ...init, headers: has ? record : { ...record, "User-Agent": userAgent } };
|
|
1311
|
+
}
|
|
1312
|
+
function fetchWithUserAgent(userAgent) {
|
|
1313
|
+
return ((input, init) => globalThis.fetch(input, withUserAgent(init, userAgent)));
|
|
1314
|
+
}
|
|
1315
|
+
|
|
1316
|
+
// src/client-info.ts
|
|
1317
|
+
var CLI_PACKAGE = { name: "groundmail-cli", version };
|
|
1318
|
+
|
|
1319
|
+
// src/session/http.ts
|
|
1320
|
+
var CliRequestError = class extends Error {
|
|
1321
|
+
};
|
|
1322
|
+
async function apiRequest(baseUrl, path, options = {}) {
|
|
1323
|
+
const doFetch = fetchWithUserAgent(buildUserAgent(CLI_PACKAGE));
|
|
1324
|
+
const url = `${baseUrl.replace(/\/+$/, "")}${path}`;
|
|
1325
|
+
let response;
|
|
1326
|
+
try {
|
|
1327
|
+
response = await doFetch(url, {
|
|
1328
|
+
method: options.method ?? (options.body === void 0 ? "GET" : "POST"),
|
|
1329
|
+
headers: {
|
|
1330
|
+
Accept: "application/json",
|
|
1331
|
+
...options.body === void 0 ? {} : { "Content-Type": "application/json" },
|
|
1332
|
+
...options.apiKey ? { Authorization: `Bearer ${options.apiKey}` } : {}
|
|
1333
|
+
},
|
|
1334
|
+
...options.body === void 0 ? {} : { body: JSON.stringify(options.body) }
|
|
1335
|
+
});
|
|
1336
|
+
} catch (error) {
|
|
1337
|
+
const reason = error instanceof Error ? error.message : String(error);
|
|
1338
|
+
throw new CliRequestError(`Could not reach ${baseUrl}: ${reason}`);
|
|
1339
|
+
}
|
|
1340
|
+
const text = await response.text();
|
|
1341
|
+
let body = {};
|
|
1342
|
+
try {
|
|
1343
|
+
const parsed = text ? JSON.parse(text) : {};
|
|
1344
|
+
body = parsed && typeof parsed === "object" && !Array.isArray(parsed) ? parsed : { value: parsed };
|
|
1345
|
+
} catch {
|
|
1346
|
+
body = { error: text.slice(0, 300) || `HTTP ${response.status}` };
|
|
1347
|
+
}
|
|
1348
|
+
return { ok: response.ok, status: response.status, body };
|
|
1349
|
+
}
|
|
1350
|
+
function sentence(text) {
|
|
1351
|
+
return /[.!?…)]$/.test(text.trim()) ? text.trim() : `${text.trim()}.`;
|
|
1352
|
+
}
|
|
1353
|
+
function errorMessage(response, fallback) {
|
|
1354
|
+
const error = response.body.error;
|
|
1355
|
+
if (typeof error === "string" && error.trim()) return error.trim();
|
|
1356
|
+
if (error && typeof error === "object") {
|
|
1357
|
+
const message = error.message;
|
|
1358
|
+
if (typeof message === "string" && message.trim()) return message.trim();
|
|
1359
|
+
}
|
|
1360
|
+
return `${fallback} (HTTP ${response.status}).`;
|
|
1361
|
+
}
|
|
1362
|
+
|
|
1363
|
+
// src/commands/login.ts
|
|
1364
|
+
import { spawn } from "child_process";
|
|
1365
|
+
import { hostname } from "os";
|
|
1366
|
+
|
|
1367
|
+
// src/session/args.ts
|
|
1368
|
+
var COMMON_VALUE_FLAGS = ["--api-key", "--base-url", "--workspace"];
|
|
1369
|
+
var COMMON_BOOLEAN_FLAGS = ["--json"];
|
|
1370
|
+
function parseArgs(argv, spec) {
|
|
1371
|
+
const valueFlags = new Set(spec.values ?? []);
|
|
1372
|
+
const booleanFlags = new Set(spec.booleans ?? []);
|
|
1373
|
+
const values = {};
|
|
1374
|
+
const booleans = {};
|
|
1375
|
+
const positionals = [];
|
|
1376
|
+
for (let i = 0; i < argv.length; i++) {
|
|
1377
|
+
const arg = argv[i];
|
|
1378
|
+
if (arg === "--") {
|
|
1379
|
+
positionals.push(...argv.slice(i + 1));
|
|
1380
|
+
break;
|
|
1381
|
+
}
|
|
1382
|
+
if (!arg.startsWith("--") || arg === "-") {
|
|
1383
|
+
positionals.push(arg);
|
|
1384
|
+
continue;
|
|
1385
|
+
}
|
|
1386
|
+
const eq = arg.indexOf("=");
|
|
1387
|
+
const name = eq === -1 ? arg : arg.slice(0, eq);
|
|
1388
|
+
if (valueFlags.has(name)) {
|
|
1389
|
+
const value = eq === -1 ? argv[i + 1] : arg.slice(eq + 1);
|
|
1390
|
+
if (value === void 0 || eq === -1 && value.startsWith("--")) {
|
|
1391
|
+
return { ok: false, error: `${name} needs a value.` };
|
|
1392
|
+
}
|
|
1393
|
+
values[name] = value;
|
|
1394
|
+
if (eq === -1) i++;
|
|
1395
|
+
continue;
|
|
1396
|
+
}
|
|
1397
|
+
if (booleanFlags.has(name) && eq === -1) {
|
|
1398
|
+
booleans[name] = true;
|
|
1399
|
+
continue;
|
|
1400
|
+
}
|
|
1401
|
+
const negated = name.startsWith("--no-") ? `--${name.slice("--no-".length)}` : null;
|
|
1402
|
+
if (negated && booleanFlags.has(negated) && eq === -1) {
|
|
1403
|
+
booleans[negated] = false;
|
|
1404
|
+
continue;
|
|
1405
|
+
}
|
|
1406
|
+
const known = [...valueFlags, ...booleanFlags].sort().join(", ");
|
|
1407
|
+
return { ok: false, error: `Unknown flag ${name}.${known ? ` This command takes: ${known}.` : ""}` };
|
|
1408
|
+
}
|
|
1409
|
+
return { ok: true, args: { values, booleans, positionals } };
|
|
1410
|
+
}
|
|
1411
|
+
function wantsJson(args, stdout = process.stdout) {
|
|
1412
|
+
if (args.booleans["--json"] !== void 0) return args.booleans["--json"];
|
|
1413
|
+
return !stdout.isTTY;
|
|
1414
|
+
}
|
|
1415
|
+
var EXIT_OK = 0;
|
|
1416
|
+
var EXIT_FAILED = 1;
|
|
1417
|
+
var EXIT_USAGE = 2;
|
|
1418
|
+
|
|
1419
|
+
// src/session/config.ts
|
|
1420
|
+
import { chmodSync, mkdirSync, readFileSync, renameSync, writeFileSync } from "fs";
|
|
1421
|
+
import { homedir } from "os";
|
|
1422
|
+
import { dirname, join } from "path";
|
|
1423
|
+
function configDir(env = process.env) {
|
|
1424
|
+
const override2 = env.GROUNDMAIL_CONFIG_DIR?.trim();
|
|
1425
|
+
if (override2) return override2;
|
|
1426
|
+
const base = env.XDG_CONFIG_HOME?.trim() || (process.platform === "win32" ? env.APPDATA?.trim() || join(homedir(), "AppData", "Roaming") : "") || join(homedir(), ".config");
|
|
1427
|
+
return join(base, cliBrand(env).configDirName);
|
|
1428
|
+
}
|
|
1429
|
+
function configPath(env = process.env) {
|
|
1430
|
+
return join(configDir(env), "config.json");
|
|
1431
|
+
}
|
|
1432
|
+
function loadConfig(env = process.env) {
|
|
1433
|
+
try {
|
|
1434
|
+
const parsed = JSON.parse(readFileSync(configPath(env), "utf8"));
|
|
1435
|
+
if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) return {};
|
|
1436
|
+
const record = parsed;
|
|
1437
|
+
const workspaces = {};
|
|
1438
|
+
for (const [id, entry] of Object.entries(record.workspaces ?? {})) {
|
|
1439
|
+
const hasKey = typeof entry?.apiKey === "string" && entry.apiKey.length > 0;
|
|
1440
|
+
const hasOAuth = Boolean(entry?.oauth && typeof entry.oauth === "object" && typeof entry.oauth.clientId === "string");
|
|
1441
|
+
if (entry && typeof entry.id === "string" && (hasKey || hasOAuth)) {
|
|
1442
|
+
workspaces[id] = entry;
|
|
1443
|
+
}
|
|
1444
|
+
}
|
|
1445
|
+
return {
|
|
1446
|
+
...typeof record.current === "string" && workspaces[record.current] ? { current: record.current } : {},
|
|
1447
|
+
workspaces,
|
|
1448
|
+
...record.pendingLogin && typeof record.pendingLogin.deviceCode === "string" ? { pendingLogin: record.pendingLogin } : {}
|
|
1449
|
+
};
|
|
1450
|
+
} catch {
|
|
1451
|
+
return {};
|
|
1452
|
+
}
|
|
1453
|
+
}
|
|
1454
|
+
function saveConfig(config, env = process.env) {
|
|
1455
|
+
const file = configPath(env);
|
|
1456
|
+
mkdirSync(dirname(file), { recursive: true, mode: 448 });
|
|
1457
|
+
const tmp = `${file}.${process.pid}.tmp`;
|
|
1458
|
+
writeFileSync(tmp, `${JSON.stringify(config, null, 2)}
|
|
1459
|
+
`, { mode: 384 });
|
|
1460
|
+
renameSync(tmp, file);
|
|
1461
|
+
try {
|
|
1462
|
+
chmodSync(file, 384);
|
|
1463
|
+
} catch {
|
|
1464
|
+
}
|
|
1465
|
+
}
|
|
1466
|
+
function updateConfig(change, env = process.env) {
|
|
1467
|
+
const next = change(loadConfig(env));
|
|
1468
|
+
saveConfig(next, env);
|
|
1469
|
+
return next;
|
|
1470
|
+
}
|
|
1471
|
+
function savedWorkspaces(config) {
|
|
1472
|
+
return Object.values(config.workspaces ?? {}).sort((a, b) => a.name.localeCompare(b.name));
|
|
1473
|
+
}
|
|
1474
|
+
function findSavedWorkspace(config, wanted) {
|
|
1475
|
+
const all = savedWorkspaces(config);
|
|
1476
|
+
const key = wanted.trim();
|
|
1477
|
+
const byId = all.find((workspace) => workspace.id === key);
|
|
1478
|
+
if (byId) return { ok: true, workspace: byId };
|
|
1479
|
+
const byName = all.filter((workspace) => workspace.name.trim().toLowerCase() === key.toLowerCase());
|
|
1480
|
+
if (byName.length === 1) return { ok: true, workspace: byName[0] };
|
|
1481
|
+
return { ok: false, reason: byName.length ? "ambiguous" : "none", matches: byName };
|
|
1482
|
+
}
|
|
1483
|
+
|
|
1484
|
+
// src/session/keychain.ts
|
|
1485
|
+
import { createRequire } from "module";
|
|
1486
|
+
var SERVICE = "groundmail-cli";
|
|
1487
|
+
var override;
|
|
1488
|
+
var cached;
|
|
1489
|
+
function platformLabel() {
|
|
1490
|
+
if (process.platform === "darwin") return "the macOS Keychain";
|
|
1491
|
+
if (process.platform === "win32") return "Windows Credential Manager";
|
|
1492
|
+
return "the system keyring";
|
|
1493
|
+
}
|
|
1494
|
+
function loadKeyring() {
|
|
1495
|
+
try {
|
|
1496
|
+
const require2 = createRequire(import.meta.url);
|
|
1497
|
+
return require2("@napi-rs/keyring");
|
|
1498
|
+
} catch {
|
|
1499
|
+
return null;
|
|
1500
|
+
}
|
|
1501
|
+
}
|
|
1502
|
+
function secretStore(env = process.env) {
|
|
1503
|
+
if (override !== void 0) return override;
|
|
1504
|
+
if (/^(1|true|yes)$/i.test(env.GROUNDMAIL_NO_KEYCHAIN?.trim() ?? "")) return null;
|
|
1505
|
+
if (cached !== void 0) return cached;
|
|
1506
|
+
const keyring = loadKeyring();
|
|
1507
|
+
if (!keyring) {
|
|
1508
|
+
cached = null;
|
|
1509
|
+
return cached;
|
|
1510
|
+
}
|
|
1511
|
+
cached = {
|
|
1512
|
+
label: platformLabel(),
|
|
1513
|
+
get(account) {
|
|
1514
|
+
try {
|
|
1515
|
+
return new keyring.Entry(SERVICE, account).getPassword() ?? null;
|
|
1516
|
+
} catch {
|
|
1517
|
+
return null;
|
|
1518
|
+
}
|
|
1519
|
+
},
|
|
1520
|
+
set(account, secret) {
|
|
1521
|
+
try {
|
|
1522
|
+
const entry = new keyring.Entry(SERVICE, account);
|
|
1523
|
+
entry.setPassword(secret);
|
|
1524
|
+
return entry.getPassword() === secret;
|
|
1525
|
+
} catch {
|
|
1526
|
+
return false;
|
|
1527
|
+
}
|
|
1528
|
+
},
|
|
1529
|
+
delete(account) {
|
|
1530
|
+
try {
|
|
1531
|
+
new keyring.Entry(SERVICE, account).deletePassword();
|
|
1532
|
+
} catch {
|
|
1533
|
+
}
|
|
1534
|
+
}
|
|
1535
|
+
};
|
|
1536
|
+
return cached;
|
|
1537
|
+
}
|
|
1538
|
+
|
|
1539
|
+
// src/session/oauth.ts
|
|
1540
|
+
var CLI_OAUTH_CLIENT_ID = "groundmail-cli";
|
|
1541
|
+
var DEVICE_CODE_GRANT_TYPE = "urn:ietf:params:oauth:grant-type:device_code";
|
|
1542
|
+
var EXPIRY_SKEW_MS = 60 * 1e3;
|
|
1543
|
+
function oauthErrorCode(response) {
|
|
1544
|
+
const error = response.body.error;
|
|
1545
|
+
return typeof error === "string" ? error : null;
|
|
1546
|
+
}
|
|
1547
|
+
function oauthErrorMessage(response, fallback) {
|
|
1548
|
+
const description = response.body.error_description;
|
|
1549
|
+
if (typeof description === "string" && description.trim()) return description.trim();
|
|
1550
|
+
return errorMessage(response, fallback);
|
|
1551
|
+
}
|
|
1552
|
+
function isTokenSet(body) {
|
|
1553
|
+
return typeof body.access_token === "string" && typeof body.refresh_token === "string";
|
|
1554
|
+
}
|
|
1555
|
+
function keychainAccount(apiUrl, workspaceId) {
|
|
1556
|
+
return `${apiUrl.replace(/\/+$/, "")}#${workspaceId}`;
|
|
1557
|
+
}
|
|
1558
|
+
function storeRefreshToken(input, env = process.env) {
|
|
1559
|
+
const keychain = secretStore(env);
|
|
1560
|
+
if (keychain?.set(keychainAccount(input.apiUrl, input.workspaceId), input.refreshToken)) {
|
|
1561
|
+
return { clientId: CLI_OAUTH_CLIENT_ID, scope: input.scope, store: "keychain" };
|
|
1562
|
+
}
|
|
1563
|
+
return { clientId: CLI_OAUTH_CLIENT_ID, scope: input.scope, store: "file", refreshToken: input.refreshToken };
|
|
1564
|
+
}
|
|
1565
|
+
function readRefreshToken(workspace, apiUrl, env = process.env) {
|
|
1566
|
+
const oauth = workspace.oauth;
|
|
1567
|
+
if (!oauth) return null;
|
|
1568
|
+
if (oauth.store === "file") return oauth.refreshToken ?? null;
|
|
1569
|
+
return secretStore(env)?.get(keychainAccount(apiUrl, workspace.id)) ?? null;
|
|
1570
|
+
}
|
|
1571
|
+
function forgetRefreshToken(workspace, apiUrl, env = process.env) {
|
|
1572
|
+
if (workspace.oauth?.store === "keychain") secretStore(env)?.delete(keychainAccount(apiUrl, workspace.id));
|
|
1573
|
+
}
|
|
1574
|
+
function storeLabel(oauth, configFile, env = process.env) {
|
|
1575
|
+
return oauth.store === "keychain" ? secretStore(env)?.label ?? "the system keychain" : `${configFile} (readable by you only)`;
|
|
1576
|
+
}
|
|
1577
|
+
async function refresh(apiUrl, refreshToken, scope, env) {
|
|
1578
|
+
return apiRequest(apiUrl, "/oauth/token", {
|
|
1579
|
+
body: {
|
|
1580
|
+
grant_type: "refresh_token",
|
|
1581
|
+
refresh_token: refreshToken,
|
|
1582
|
+
client_id: CLI_OAUTH_CLIENT_ID,
|
|
1583
|
+
...scope ? { scope } : {}
|
|
1584
|
+
},
|
|
1585
|
+
env
|
|
1586
|
+
});
|
|
1587
|
+
}
|
|
1588
|
+
async function accessTokenFor(workspace, apiUrl, options = {}) {
|
|
1589
|
+
const env = options.env ?? process.env;
|
|
1590
|
+
const now = options.now ?? Date.now;
|
|
1591
|
+
const brand = cliBrand(env);
|
|
1592
|
+
const oauth = workspace.oauth;
|
|
1593
|
+
if (!oauth) return { ok: false, error: "Not an OAuth sign-in." };
|
|
1594
|
+
const narrowed = Boolean(options.scope && options.scope.trim() !== oauth.scope);
|
|
1595
|
+
if (!narrowed && oauth.accessToken && (oauth.accessTokenExpiresAt ?? 0) - EXPIRY_SKEW_MS > now()) {
|
|
1596
|
+
return { ok: true, token: oauth.accessToken, scope: oauth.scope };
|
|
1597
|
+
}
|
|
1598
|
+
const signInAgain = `Run ${brand.bin} login${workspace.name ? ` --workspace "${workspace.name}"` : ""} to sign in again.`;
|
|
1599
|
+
let refreshToken = readRefreshToken(workspace, apiUrl, env);
|
|
1600
|
+
if (!refreshToken) return { ok: false, error: `The saved sign-in for ${workspace.name} has no refresh token. ${signInAgain}` };
|
|
1601
|
+
for (let attempt = 0; attempt < 2; attempt++) {
|
|
1602
|
+
const response = await refresh(apiUrl, refreshToken, narrowed ? options.scope.trim() : null, env);
|
|
1603
|
+
if (response.ok && isTokenSet(response.body)) {
|
|
1604
|
+
const tokens = response.body;
|
|
1605
|
+
const stored = storeRefreshToken({ apiUrl, workspaceId: workspace.id, refreshToken: tokens.refresh_token, scope: oauth.scope }, env);
|
|
1606
|
+
updateConfig((config) => {
|
|
1607
|
+
const current = config.workspaces?.[workspace.id];
|
|
1608
|
+
if (!current) return config;
|
|
1609
|
+
const next = {
|
|
1610
|
+
...stored,
|
|
1611
|
+
scope: oauth.scope,
|
|
1612
|
+
...narrowed ? { accessToken: current.oauth?.accessToken, accessTokenExpiresAt: current.oauth?.accessTokenExpiresAt } : { accessToken: tokens.access_token, accessTokenExpiresAt: now() + tokens.expires_in * 1e3 }
|
|
1613
|
+
};
|
|
1614
|
+
return { ...config, workspaces: { ...config.workspaces, [workspace.id]: { ...current, oauth: next } } };
|
|
1615
|
+
}, env);
|
|
1616
|
+
return { ok: true, token: tokens.access_token, scope: tokens.scope };
|
|
1617
|
+
}
|
|
1618
|
+
const code = oauthErrorCode(response);
|
|
1619
|
+
if (code === "invalid_grant" && attempt === 0) {
|
|
1620
|
+
const latest = loadConfig(env).workspaces?.[workspace.id];
|
|
1621
|
+
const latestToken = latest ? readRefreshToken(latest, apiUrl, env) : null;
|
|
1622
|
+
if (latest?.oauth && latestToken && latestToken !== refreshToken) {
|
|
1623
|
+
if (!narrowed && latest.oauth.accessToken && (latest.oauth.accessTokenExpiresAt ?? 0) - EXPIRY_SKEW_MS > now()) {
|
|
1624
|
+
return { ok: true, token: latest.oauth.accessToken, scope: latest.oauth.scope };
|
|
1625
|
+
}
|
|
1626
|
+
refreshToken = latestToken;
|
|
1627
|
+
continue;
|
|
1628
|
+
}
|
|
1629
|
+
}
|
|
1630
|
+
if (code === "invalid_grant") {
|
|
1631
|
+
return { ok: false, error: `${oauthErrorMessage(response, "The sign-in is no longer valid")} ${signInAgain}`.trim() };
|
|
1632
|
+
}
|
|
1633
|
+
if (code === "invalid_scope") {
|
|
1634
|
+
return { ok: false, error: `${oauthErrorMessage(response, "That scope was not approved for this sign-in")} To get it: ${brand.bin} login --scope "${options.scope ?? ""}".` };
|
|
1635
|
+
}
|
|
1636
|
+
return { ok: false, error: oauthErrorMessage(response, "Could not refresh the access token") };
|
|
1637
|
+
}
|
|
1638
|
+
return { ok: false, error: `The sign-in is no longer valid. ${signInAgain}` };
|
|
1639
|
+
}
|
|
1640
|
+
async function revokeRefreshToken(apiUrl, refreshToken, env = process.env) {
|
|
1641
|
+
return apiRequest(apiUrl, "/oauth/revoke", {
|
|
1642
|
+
body: { token: refreshToken, token_type_hint: "refresh_token", client_id: CLI_OAUTH_CLIENT_ID },
|
|
1643
|
+
env
|
|
1644
|
+
});
|
|
1645
|
+
}
|
|
1646
|
+
|
|
1647
|
+
// src/session/credentials.ts
|
|
1648
|
+
function envApiKey(env = process.env) {
|
|
1649
|
+
return env.GROUNDMAIL_API_KEY?.trim() || null;
|
|
1650
|
+
}
|
|
1651
|
+
function currentSavedWorkspace(config, env = process.env) {
|
|
1652
|
+
const brand = cliBrand(env);
|
|
1653
|
+
const all = savedWorkspaces(config);
|
|
1654
|
+
if (config.current && config.workspaces?.[config.current]) {
|
|
1655
|
+
return { ok: true, workspace: config.workspaces[config.current] };
|
|
1656
|
+
}
|
|
1657
|
+
if (all.length === 1) return { ok: true, workspace: all[0] };
|
|
1658
|
+
if (all.length === 0) {
|
|
1659
|
+
return {
|
|
1660
|
+
ok: false,
|
|
1661
|
+
reason: "none",
|
|
1662
|
+
error: `Not signed in. Run: ${brand.bin} login (it prints a link to approve in the browser). Or set ${brand.keyEnvVar}.`
|
|
1663
|
+
};
|
|
1664
|
+
}
|
|
1665
|
+
return {
|
|
1666
|
+
ok: false,
|
|
1667
|
+
reason: "ambiguous",
|
|
1668
|
+
error: `Signed in to ${all.length} workspaces on this computer and none is picked: ${all.map((workspace) => `"${workspace.name}"`).join(", ")}. Pick one: ${brand.bin} workspaces use <name>, or pass --workspace <name>.`
|
|
1669
|
+
};
|
|
1670
|
+
}
|
|
1671
|
+
function savedApiUrl(workspace, env = process.env, flagUrl) {
|
|
1672
|
+
return trimUrl(flagUrl) || explicitApiUrl(env) || trimUrl(workspace?.apiUrl) || defaultApiUrl(env);
|
|
1673
|
+
}
|
|
1674
|
+
async function savedCredential(workspace, env = process.env) {
|
|
1675
|
+
if (workspace.oauth) {
|
|
1676
|
+
const issuer = trimUrl(workspace.apiUrl) || defaultApiUrl(env);
|
|
1677
|
+
const token = await accessTokenFor(workspace, issuer, { env });
|
|
1678
|
+
return token.ok ? { ok: true, token: token.token, kind: "oauth" } : token;
|
|
1679
|
+
}
|
|
1680
|
+
if (workspace.apiKey) return { ok: true, token: workspace.apiKey, kind: "api_key" };
|
|
1681
|
+
return { ok: false, error: `The saved sign-in for ${workspace.name} has no credential. Run ${cliBrand(env).bin} login.` };
|
|
1682
|
+
}
|
|
1683
|
+
async function resolveAuth(flags, env = process.env, config = loadConfig(env)) {
|
|
1684
|
+
const brand = cliBrand(env);
|
|
1685
|
+
const flagUrl = trimUrl(flags.baseUrl) || null;
|
|
1686
|
+
const urlFor = (workspace) => savedApiUrl(workspace, env, flagUrl);
|
|
1687
|
+
const flagKey = flags.apiKey?.trim();
|
|
1688
|
+
if (flagKey) {
|
|
1689
|
+
return { ok: true, auth: { apiKey: flagKey, baseUrl: urlFor(null), source: "flag", kind: "api_key", workspace: null } };
|
|
1690
|
+
}
|
|
1691
|
+
const fromSaved = async (workspace) => {
|
|
1692
|
+
const baseUrl = urlFor(workspace);
|
|
1693
|
+
const credential = await savedCredential(workspace, env);
|
|
1694
|
+
if (!credential.ok) return credential;
|
|
1695
|
+
return { ok: true, auth: { apiKey: credential.token, baseUrl, source: "saved", kind: credential.kind, workspace } };
|
|
1696
|
+
};
|
|
1697
|
+
if (flags.workspace?.trim()) {
|
|
1698
|
+
const match = findSavedWorkspace(config, flags.workspace);
|
|
1699
|
+
if (!match.ok) {
|
|
1700
|
+
const saved = savedWorkspaces(config);
|
|
1701
|
+
return {
|
|
1702
|
+
ok: false,
|
|
1703
|
+
error: match.reason === "ambiguous" ? `More than one saved workspace is called "${flags.workspace}". Use its id: ${match.matches.map((w) => w.id).join(", ")}.` : `Not signed in to "${flags.workspace}" on this computer.${saved.length ? ` Signed in: ${saved.map((w) => `"${w.name}"`).join(", ")}.` : ""} To add it: ${brand.bin} login --workspace "${flags.workspace}".`
|
|
1704
|
+
};
|
|
1705
|
+
}
|
|
1706
|
+
return fromSaved(match.workspace);
|
|
1707
|
+
}
|
|
1708
|
+
const fromEnv = envApiKey(env);
|
|
1709
|
+
if (fromEnv) {
|
|
1710
|
+
return { ok: true, auth: { apiKey: fromEnv, baseUrl: urlFor(null), source: "env", kind: "api_key", workspace: null } };
|
|
1711
|
+
}
|
|
1712
|
+
const current = currentSavedWorkspace(config, env);
|
|
1713
|
+
if (!current.ok) return { ok: false, error: current.error };
|
|
1714
|
+
return fromSaved(current.workspace);
|
|
1715
|
+
}
|
|
1716
|
+
async function applySavedSession(argv, env = process.env, config) {
|
|
1717
|
+
const hasFlag = (flag) => argv.some((arg) => arg === flag || arg.startsWith(`${flag}=`));
|
|
1718
|
+
let workspace = null;
|
|
1719
|
+
let notice;
|
|
1720
|
+
if (!hasFlag("--api-key") && !envApiKey(env)) {
|
|
1721
|
+
const current = currentSavedWorkspace(config ?? loadConfig(env), env);
|
|
1722
|
+
if (current.ok) {
|
|
1723
|
+
workspace = current.workspace;
|
|
1724
|
+
const credential = await savedCredential(current.workspace, env);
|
|
1725
|
+
if (credential.ok) env.GROUNDMAIL_API_KEY = credential.token;
|
|
1726
|
+
else notice = credential.error;
|
|
1727
|
+
} else if (current.reason === "ambiguous") {
|
|
1728
|
+
notice = current.error;
|
|
1729
|
+
}
|
|
1730
|
+
}
|
|
1731
|
+
if (hasFlag("--base-url")) return { argv, notice };
|
|
1732
|
+
const url = explicitApiUrl(env) || trimUrl(workspace?.apiUrl) || trimUrl(env.GROUNDMAIL_DEFAULT_API_URL);
|
|
1733
|
+
return { argv: url ? [...argv, "--base-url", url] : argv, notice };
|
|
1734
|
+
}
|
|
1735
|
+
|
|
1736
|
+
// src/session/io.ts
|
|
1737
|
+
var processIo = {
|
|
1738
|
+
out: (text) => process.stdout.write(text.endsWith("\n") ? text : `${text}
|
|
1739
|
+
`),
|
|
1740
|
+
err: (text) => process.stderr.write(text.endsWith("\n") ? text : `${text}
|
|
1741
|
+
`),
|
|
1742
|
+
get isTTY() {
|
|
1743
|
+
return Boolean(process.stdout.isTTY);
|
|
1744
|
+
}
|
|
1745
|
+
};
|
|
1746
|
+
function printJson(io, value) {
|
|
1747
|
+
io.out(JSON.stringify(value, null, 2));
|
|
1748
|
+
}
|
|
1749
|
+
|
|
1750
|
+
// src/commands/login.ts
|
|
1751
|
+
var realSleep = (ms) => new Promise((resolve) => setTimeout(resolve, ms));
|
|
1752
|
+
var CLI_SCOPES = [
|
|
1753
|
+
"full",
|
|
1754
|
+
"emails:send",
|
|
1755
|
+
"emails:read",
|
|
1756
|
+
"domains:read",
|
|
1757
|
+
"domains:write",
|
|
1758
|
+
"webhooks:write",
|
|
1759
|
+
"templates:write",
|
|
1760
|
+
"suppressions:write",
|
|
1761
|
+
"account:read"
|
|
1762
|
+
];
|
|
1763
|
+
function parseScopeFlag(value) {
|
|
1764
|
+
const tokens = String(value ?? "").split(/[\s,]+/).map((token) => token.trim()).filter(Boolean);
|
|
1765
|
+
if (!tokens.length) return { ok: true, scope: "full" };
|
|
1766
|
+
const unknown = tokens.filter((token) => !CLI_SCOPES.includes(token));
|
|
1767
|
+
if (unknown.length) {
|
|
1768
|
+
return { ok: false, error: `Unknown scope: ${unknown.join(", ")}. Scopes: ${CLI_SCOPES.join(", ")}.` };
|
|
1769
|
+
}
|
|
1770
|
+
if (tokens.includes("full")) return { ok: true, scope: "full" };
|
|
1771
|
+
return { ok: true, scope: CLI_SCOPES.filter((scope) => tokens.includes(scope)).join(" ") };
|
|
1772
|
+
}
|
|
1773
|
+
function openInBrowser(url) {
|
|
1774
|
+
const [cmd, args] = process.platform === "darwin" ? ["open", [url]] : process.platform === "win32" ? ["rundll32", ["url.dll,FileProtocolHandler", url]] : ["xdg-open", [url]];
|
|
1775
|
+
try {
|
|
1776
|
+
const child = spawn(cmd, args, { stdio: "ignore", detached: true });
|
|
1777
|
+
child.on("error", () => {
|
|
1778
|
+
});
|
|
1779
|
+
child.unref();
|
|
1780
|
+
} catch {
|
|
1781
|
+
}
|
|
1782
|
+
}
|
|
1783
|
+
function sourceLabel(source, env) {
|
|
1784
|
+
if (source === "flag") return "--api-key";
|
|
1785
|
+
if (source === "env") return cliBrand(env).keyEnvVar;
|
|
1786
|
+
return "saved login";
|
|
1787
|
+
}
|
|
1788
|
+
function defaultDeviceName() {
|
|
1789
|
+
try {
|
|
1790
|
+
return hostname().slice(0, 80) || "this computer";
|
|
1791
|
+
} catch {
|
|
1792
|
+
return "this computer";
|
|
1793
|
+
}
|
|
1794
|
+
}
|
|
1795
|
+
var dropPending = (env) => updateConfig(({ pendingLogin: _drop, ...rest }) => rest, env);
|
|
1796
|
+
async function runLoginCommand(argv, deps = {}) {
|
|
1797
|
+
const io = deps.io ?? processIo;
|
|
1798
|
+
const env = deps.env ?? process.env;
|
|
1799
|
+
const sleep = deps.sleep ?? realSleep;
|
|
1800
|
+
const now = deps.now ?? Date.now;
|
|
1801
|
+
const brand = cliBrand(env);
|
|
1802
|
+
const usage = `Usage: ${brand.bin} login [--scope <scopes>] [--workspace <name>] [--new] [--no-browser] [--wait|--no-wait] [--json]`;
|
|
1803
|
+
const parsed = parseArgs(argv, {
|
|
1804
|
+
values: ["--base-url", "--workspace", "--scope", "--device-name"],
|
|
1805
|
+
booleans: ["--json", "--browser", "--wait", "--new"]
|
|
1806
|
+
});
|
|
1807
|
+
if (!parsed.ok) return { exitCode: EXIT_USAGE, error: `${parsed.error}
|
|
1808
|
+
${usage}` };
|
|
1809
|
+
const { args } = parsed;
|
|
1810
|
+
if (args.positionals.length) {
|
|
1811
|
+
return { exitCode: EXIT_USAGE, error: `${brand.bin} login takes no arguments. To sign in to a workspace by name: ${brand.bin} login --workspace "<name>".` };
|
|
1812
|
+
}
|
|
1813
|
+
const scopeFlag = parseScopeFlag(args.values["--scope"]);
|
|
1814
|
+
if (!scopeFlag.ok) return { exitCode: EXIT_USAGE, error: `${scopeFlag.error}
|
|
1815
|
+
${usage}` };
|
|
1816
|
+
const scope = scopeFlag.scope;
|
|
1817
|
+
const json = wantsJson(args, io);
|
|
1818
|
+
const wait = args.booleans["--wait"] ?? io.isTTY;
|
|
1819
|
+
const browser = args.booleans["--browser"] ?? true;
|
|
1820
|
+
const apiUrl = trimUrl(args.values["--base-url"]) || explicitApiUrl(env) || defaultApiUrl(env);
|
|
1821
|
+
const workspaceHint = args.values["--workspace"]?.trim() || null;
|
|
1822
|
+
const deviceName = args.values["--device-name"]?.trim() || (deps.deviceName ?? defaultDeviceName)();
|
|
1823
|
+
try {
|
|
1824
|
+
const config = loadConfig(env);
|
|
1825
|
+
let login = config.pendingLogin;
|
|
1826
|
+
const resumed = Boolean(
|
|
1827
|
+
!args.booleans["--new"] && login && login.kind === "oauth" && login.apiUrl === apiUrl && login.expiresAt > now() + 5e3 && (login.workspace ?? null) === workspaceHint && (login.scope ?? "full") === scope
|
|
1828
|
+
);
|
|
1829
|
+
if (!resumed && !workspaceHint && !args.booleans["--new"] && !args.values["--scope"]) {
|
|
1830
|
+
const current = currentSavedWorkspace(config, env);
|
|
1831
|
+
if (current.ok && current.workspace.oauth && (trimUrl(current.workspace.apiUrl) || defaultApiUrl(env)) === apiUrl) {
|
|
1832
|
+
const next = `To sign in to another workspace: ${brand.bin} login --workspace "<name>". For other scopes: ${brand.bin} login --scope <scopes>. To sign in as someone else: ${brand.bin} logout, then ${brand.bin} login.`;
|
|
1833
|
+
if (json) {
|
|
1834
|
+
printJson(io, {
|
|
1835
|
+
status: "signed_in",
|
|
1836
|
+
already: true,
|
|
1837
|
+
workspace: { id: current.workspace.id, name: current.workspace.name },
|
|
1838
|
+
email: current.workspace.email ?? null,
|
|
1839
|
+
scope: current.workspace.oauth.scope,
|
|
1840
|
+
config_path: configPath(env),
|
|
1841
|
+
next
|
|
1842
|
+
});
|
|
1843
|
+
} else {
|
|
1844
|
+
io.out(`Already signed in${current.workspace.email ? ` as ${current.workspace.email}` : ""} to ${current.workspace.name}.
|
|
1845
|
+
${next}`);
|
|
1846
|
+
}
|
|
1847
|
+
return { exitCode: EXIT_OK };
|
|
1848
|
+
}
|
|
1849
|
+
}
|
|
1850
|
+
if (!resumed) {
|
|
1851
|
+
const start = await apiRequest(apiUrl, "/oauth/device_authorization", {
|
|
1852
|
+
body: {
|
|
1853
|
+
client_id: CLI_OAUTH_CLIENT_ID,
|
|
1854
|
+
scope,
|
|
1855
|
+
device_name: deviceName,
|
|
1856
|
+
...workspaceHint ? { workspace: workspaceHint } : {}
|
|
1857
|
+
},
|
|
1858
|
+
env
|
|
1859
|
+
});
|
|
1860
|
+
if (!start.ok) return { exitCode: EXIT_FAILED, error: oauthErrorMessage(start, "Could not start the sign-in") };
|
|
1861
|
+
const body = start.body;
|
|
1862
|
+
const url = typeof body.verification_uri_complete === "string" ? body.verification_uri_complete : body.verification_uri;
|
|
1863
|
+
if (typeof body.device_code !== "string" || typeof body.user_code !== "string" || typeof url !== "string") {
|
|
1864
|
+
return { exitCode: EXIT_FAILED, error: `Unexpected answer from ${apiUrl}/oauth/device_authorization. Is --base-url right?` };
|
|
1865
|
+
}
|
|
1866
|
+
login = {
|
|
1867
|
+
kind: "oauth",
|
|
1868
|
+
scope,
|
|
1869
|
+
deviceCode: body.device_code,
|
|
1870
|
+
userCode: body.user_code,
|
|
1871
|
+
url,
|
|
1872
|
+
interval: Math.max(1, Number(body.interval) || 5),
|
|
1873
|
+
expiresAt: now() + (Number(body.expires_in) || 600) * 1e3,
|
|
1874
|
+
apiUrl,
|
|
1875
|
+
workspace: workspaceHint
|
|
1876
|
+
};
|
|
1877
|
+
const pending = login;
|
|
1878
|
+
updateConfig((current) => ({ ...current, pendingLogin: pending }), env);
|
|
1879
|
+
}
|
|
1880
|
+
const active = login;
|
|
1881
|
+
const link = `${resumed ? "Still waiting for approval" : `Sign in to ${brand.product}`}: open this link and approve code ${active.userCode}
|
|
1882
|
+
${active.url}`;
|
|
1883
|
+
if (!json) {
|
|
1884
|
+
io.out(`${link}${wait ? "\nWaiting\u2026 (Ctrl-C to stop; run the same command again to pick up where you left off)" : ""}`);
|
|
1885
|
+
} else if (wait) {
|
|
1886
|
+
io.err(link);
|
|
1887
|
+
}
|
|
1888
|
+
if (browser && !resumed && !env.CI) (deps.openBrowser ?? openInBrowser)(active.url);
|
|
1889
|
+
let interval = active.interval;
|
|
1890
|
+
for (; ; ) {
|
|
1891
|
+
const poll = await apiRequest(apiUrl, "/oauth/token", {
|
|
1892
|
+
body: { grant_type: DEVICE_CODE_GRANT_TYPE, device_code: active.deviceCode, client_id: CLI_OAUTH_CLIENT_ID },
|
|
1893
|
+
env
|
|
1894
|
+
});
|
|
1895
|
+
if (poll.ok && isTokenSet(poll.body)) {
|
|
1896
|
+
return finishLogin(poll.body, active, { io, env, json, now });
|
|
1897
|
+
}
|
|
1898
|
+
const code = oauthErrorCode(poll);
|
|
1899
|
+
if (code === "slow_down") interval += 5;
|
|
1900
|
+
else if (code === "access_denied") {
|
|
1901
|
+
dropPending(env);
|
|
1902
|
+
return {
|
|
1903
|
+
exitCode: EXIT_FAILED,
|
|
1904
|
+
error: `${sentence(oauthErrorMessage(poll, "The sign-in was canceled in the browser"))} Run ${brand.bin} login to start again.`
|
|
1905
|
+
};
|
|
1906
|
+
} else if (code !== "authorization_pending") {
|
|
1907
|
+
if (poll.status < 500 && poll.status !== 429) dropPending(env);
|
|
1908
|
+
if (code === "expired_token" || code === "invalid_grant") {
|
|
1909
|
+
return { exitCode: EXIT_FAILED, error: `The sign-in code expired or was already used. Run ${brand.bin} login again.` };
|
|
1910
|
+
}
|
|
1911
|
+
return { exitCode: EXIT_FAILED, error: oauthErrorMessage(poll, "The sign-in check failed") };
|
|
1912
|
+
}
|
|
1913
|
+
if (!wait || now() + interval * 1e3 > active.expiresAt) {
|
|
1914
|
+
const next = `Not signed in yet. Approve code ${active.userCode} in the browser, then run ${brand.bin} login again to finish.`;
|
|
1915
|
+
if (json) {
|
|
1916
|
+
printJson(io, {
|
|
1917
|
+
status: "pending",
|
|
1918
|
+
user_code: active.userCode,
|
|
1919
|
+
verification_url: active.url,
|
|
1920
|
+
expires_at: new Date(active.expiresAt).toISOString(),
|
|
1921
|
+
next
|
|
1922
|
+
});
|
|
1923
|
+
} else {
|
|
1924
|
+
io.out(next);
|
|
1925
|
+
}
|
|
1926
|
+
return { exitCode: EXIT_OK };
|
|
1927
|
+
}
|
|
1928
|
+
await sleep(interval * 1e3);
|
|
1929
|
+
}
|
|
1930
|
+
} catch (error) {
|
|
1931
|
+
if (error instanceof CliRequestError) return { exitCode: EXIT_FAILED, error: error.message };
|
|
1932
|
+
throw error;
|
|
1933
|
+
}
|
|
1934
|
+
}
|
|
1935
|
+
async function finishLogin(tokens, login, { io, env, json, now }) {
|
|
1936
|
+
const brand = cliBrand(env);
|
|
1937
|
+
const workspace = tokens.workspace ?? {};
|
|
1938
|
+
const user = tokens.user ?? {};
|
|
1939
|
+
const id = typeof workspace.id === "string" && workspace.id ? workspace.id : "default";
|
|
1940
|
+
const scope = typeof tokens.scope === "string" && tokens.scope ? tokens.scope : login.scope ?? "full";
|
|
1941
|
+
const previous = loadConfig(env).workspaces?.[id];
|
|
1942
|
+
let replacedKey = false;
|
|
1943
|
+
if (previous?.apiKey) {
|
|
1944
|
+
const response = await apiRequest(login.apiUrl, "/cli/session", { method: "DELETE", apiKey: previous.apiKey, env }).catch(() => null);
|
|
1945
|
+
replacedKey = Boolean(response?.ok && response.body.revoked === true);
|
|
1946
|
+
}
|
|
1947
|
+
if (previous?.oauth) {
|
|
1948
|
+
const oldRefresh = readRefreshToken(previous, login.apiUrl, env);
|
|
1949
|
+
if (oldRefresh) await revokeRefreshToken(login.apiUrl, oldRefresh, env).catch(() => null);
|
|
1950
|
+
}
|
|
1951
|
+
const stored = storeRefreshToken({ apiUrl: login.apiUrl, workspaceId: id, refreshToken: tokens.refresh_token, scope }, env);
|
|
1952
|
+
const saved = {
|
|
1953
|
+
id,
|
|
1954
|
+
name: typeof workspace.name === "string" && workspace.name ? workspace.name : "Workspace",
|
|
1955
|
+
oauth: { ...stored, accessToken: tokens.access_token, accessTokenExpiresAt: now() + tokens.expires_in * 1e3 },
|
|
1956
|
+
...login.apiUrl !== defaultApiUrl(env) ? { apiUrl: login.apiUrl } : {},
|
|
1957
|
+
email: typeof user.email === "string" ? user.email : null,
|
|
1958
|
+
savedAt: new Date(now()).toISOString()
|
|
1959
|
+
};
|
|
1960
|
+
const config = updateConfig(({ pendingLogin: _drop, ...rest }) => ({
|
|
1961
|
+
...rest,
|
|
1962
|
+
workspaces: { ...rest.workspaces ?? {}, [id]: saved },
|
|
1963
|
+
current: id
|
|
1964
|
+
}), env);
|
|
1965
|
+
const others = savedWorkspaces(config).filter((entry) => entry.id !== id);
|
|
1966
|
+
const envKey = envApiKey(env);
|
|
1967
|
+
const envNote = envKey ? `${sourceLabel("env", env)} is set in this environment and is used instead of this sign-in until you unset it.` : null;
|
|
1968
|
+
const where = storeLabel(saved.oauth, configPath(env), env);
|
|
1969
|
+
if (json) {
|
|
1970
|
+
printJson(io, {
|
|
1971
|
+
status: "signed_in",
|
|
1972
|
+
user: tokens.user ?? null,
|
|
1973
|
+
workspace: { id: saved.id, name: saved.name },
|
|
1974
|
+
scope,
|
|
1975
|
+
refresh_token_store: stored.store,
|
|
1976
|
+
config_path: configPath(env),
|
|
1977
|
+
other_workspaces: others.map((entry) => ({ id: entry.id, name: entry.name })),
|
|
1978
|
+
...replacedKey ? { replaced_api_key: true } : {},
|
|
1979
|
+
...envNote ? { note: envNote } : {}
|
|
1980
|
+
});
|
|
1981
|
+
return { exitCode: EXIT_OK };
|
|
1982
|
+
}
|
|
1983
|
+
const lines = [
|
|
1984
|
+
`Signed in${saved.email ? ` as ${saved.email}` : ""} to ${saved.name}.`,
|
|
1985
|
+
`Access: ${scope === "full" ? "full" : scope.split(" ").join(", ")}. Tokens refresh on their own; the refresh token is in ${where}.`
|
|
1986
|
+
];
|
|
1987
|
+
if (replacedKey) lines.push("The API key an earlier login saved was revoked; this sign-in replaces it.");
|
|
1988
|
+
if (others.length) {
|
|
1989
|
+
lines.push(
|
|
1990
|
+
`Also signed in here: ${others.map((entry) => entry.name).join(", ")}. Switch with ${brand.bin} workspaces use <name>.`
|
|
1991
|
+
);
|
|
1992
|
+
}
|
|
1993
|
+
if (envNote) lines.push(envNote);
|
|
1994
|
+
lines.push(`Next: ${brand.bin} domains list`);
|
|
1995
|
+
io.out(lines.join("\n"));
|
|
1996
|
+
return { exitCode: EXIT_OK };
|
|
1997
|
+
}
|
|
1998
|
+
async function runLogoutCommand(argv, deps = {}) {
|
|
1999
|
+
const io = deps.io ?? processIo;
|
|
2000
|
+
const env = deps.env ?? process.env;
|
|
2001
|
+
const brand = cliBrand(env);
|
|
2002
|
+
const parsed = parseArgs(argv, { values: ["--workspace", "--base-url"], booleans: ["--json", "--all"] });
|
|
2003
|
+
if (!parsed.ok) return { exitCode: EXIT_USAGE, error: `${parsed.error}
|
|
2004
|
+
Usage: ${brand.bin} logout [--workspace <name>] [--all]` };
|
|
2005
|
+
const { args } = parsed;
|
|
2006
|
+
const json = wantsJson(args, io);
|
|
2007
|
+
const config = loadConfig(env);
|
|
2008
|
+
const all = savedWorkspaces(config);
|
|
2009
|
+
let targets;
|
|
2010
|
+
if (args.booleans["--all"]) {
|
|
2011
|
+
targets = all;
|
|
2012
|
+
} else if (args.values["--workspace"]) {
|
|
2013
|
+
const match = findSavedWorkspace(config, args.values["--workspace"]);
|
|
2014
|
+
if (!match.ok) {
|
|
2015
|
+
return { exitCode: EXIT_FAILED, error: `Not signed in to "${args.values["--workspace"]}" on this computer.` };
|
|
2016
|
+
}
|
|
2017
|
+
targets = [match.workspace];
|
|
2018
|
+
} else {
|
|
2019
|
+
const current = config.current ? config.workspaces?.[config.current] : all.length === 1 ? all[0] : void 0;
|
|
2020
|
+
if (!current && all.length > 1) {
|
|
2021
|
+
return {
|
|
2022
|
+
exitCode: EXIT_USAGE,
|
|
2023
|
+
error: `Signed in to ${all.length} workspaces and none is picked. Say which: ${brand.bin} logout --workspace <name>, or ${brand.bin} logout --all.`
|
|
2024
|
+
};
|
|
2025
|
+
}
|
|
2026
|
+
targets = current ? [current] : [];
|
|
2027
|
+
}
|
|
2028
|
+
const results = [];
|
|
2029
|
+
for (const workspace of targets) {
|
|
2030
|
+
const apiUrl = trimUrl(args.values["--base-url"]) || explicitApiUrl(env) || trimUrl(workspace.apiUrl) || defaultApiUrl(env);
|
|
2031
|
+
let revoked = false;
|
|
2032
|
+
let warning;
|
|
2033
|
+
try {
|
|
2034
|
+
if (workspace.oauth) {
|
|
2035
|
+
const refreshToken = readRefreshToken(workspace, apiUrl, env);
|
|
2036
|
+
if (refreshToken) {
|
|
2037
|
+
const response = await revokeRefreshToken(apiUrl, refreshToken, env);
|
|
2038
|
+
revoked = response.ok;
|
|
2039
|
+
if (!response.ok) warning = oauthErrorMessage(response, "Could not revoke the sign-in");
|
|
2040
|
+
}
|
|
2041
|
+
forgetRefreshToken(workspace, apiUrl, env);
|
|
2042
|
+
} else if (workspace.apiKey) {
|
|
2043
|
+
const response = await apiRequest(apiUrl, "/cli/session", { method: "DELETE", apiKey: workspace.apiKey, env });
|
|
2044
|
+
revoked = response.ok && response.body.revoked === true;
|
|
2045
|
+
if (!response.ok && response.status !== 401) warning = errorMessage(response, "Could not revoke the key");
|
|
2046
|
+
}
|
|
2047
|
+
} catch (error) {
|
|
2048
|
+
warning = error instanceof Error ? error.message : String(error);
|
|
2049
|
+
}
|
|
2050
|
+
results.push({ id: workspace.id, name: workspace.name, revoked, ...warning ? { warning } : {} });
|
|
2051
|
+
}
|
|
2052
|
+
const removing = new Set(targets.map((workspace) => workspace.id));
|
|
2053
|
+
const hadPending = Boolean(config.pendingLogin);
|
|
2054
|
+
if (removing.size || hadPending) {
|
|
2055
|
+
updateConfig((current) => {
|
|
2056
|
+
const workspaces = Object.fromEntries(
|
|
2057
|
+
Object.entries(current.workspaces ?? {}).filter(([id]) => !removing.has(id))
|
|
2058
|
+
);
|
|
2059
|
+
const { pendingLogin: _drop, current: picked, ...rest } = current;
|
|
2060
|
+
return { ...rest, workspaces, ...picked && !removing.has(picked) ? { current: picked } : {} };
|
|
2061
|
+
}, env);
|
|
2062
|
+
}
|
|
2063
|
+
const envNote = envApiKey(env) ? `${sourceLabel("env", env)} is still set in this environment, so commands keep using it.` : null;
|
|
2064
|
+
if (json) {
|
|
2065
|
+
printJson(io, { signed_out: results, config_path: configPath(env), ...envNote ? { note: envNote } : {} });
|
|
2066
|
+
return { exitCode: EXIT_OK };
|
|
2067
|
+
}
|
|
2068
|
+
if (!results.length) {
|
|
2069
|
+
io.out(`Not signed in on this computer.${envNote ? ` ${envNote}` : ""}`);
|
|
2070
|
+
return { exitCode: EXIT_OK };
|
|
2071
|
+
}
|
|
2072
|
+
const lines = results.map((result) => {
|
|
2073
|
+
const target = targets.find((workspace) => workspace.id === result.id);
|
|
2074
|
+
const what = target?.oauth ? "the sign-in" : "the key";
|
|
2075
|
+
const revokeHint = target?.oauth ? "Revoke it in Settings \u2192 CLIs & agents." : "Revoke it on the API keys page.";
|
|
2076
|
+
return `Signed out of ${result.name}: ${result.revoked ? `${what} was revoked and` : `${what} was`} removed from this computer.${result.warning ? ` (Could not revoke it: ${result.warning} ${revokeHint})` : ""}`;
|
|
2077
|
+
});
|
|
2078
|
+
if (envNote) lines.push(envNote);
|
|
2079
|
+
io.out(lines.join("\n"));
|
|
2080
|
+
return { exitCode: EXIT_OK };
|
|
2081
|
+
}
|
|
2082
|
+
async function runWhoamiCommand(argv, deps = {}) {
|
|
2083
|
+
const io = deps.io ?? processIo;
|
|
2084
|
+
const env = deps.env ?? process.env;
|
|
2085
|
+
const brand = cliBrand(env);
|
|
2086
|
+
const parsed = parseArgs(argv, { values: [...COMMON_VALUE_FLAGS], booleans: [...COMMON_BOOLEAN_FLAGS] });
|
|
2087
|
+
if (!parsed.ok) return { exitCode: EXIT_USAGE, error: `${parsed.error}
|
|
2088
|
+
Usage: ${brand.bin} whoami [--workspace <name>] [--json]` };
|
|
2089
|
+
const { args } = parsed;
|
|
2090
|
+
try {
|
|
2091
|
+
const resolved = await resolveAuth(
|
|
2092
|
+
{ apiKey: args.values["--api-key"], baseUrl: args.values["--base-url"], workspace: args.values["--workspace"] },
|
|
2093
|
+
env
|
|
2094
|
+
);
|
|
2095
|
+
if (!resolved.ok) return { exitCode: EXIT_FAILED, error: resolved.error };
|
|
2096
|
+
const { auth } = resolved;
|
|
2097
|
+
const response = await apiRequest(auth.baseUrl, "/cli/session", { apiKey: auth.apiKey, env });
|
|
2098
|
+
if (!response.ok) {
|
|
2099
|
+
const hint = response.status === 401 ? ` The credential was rejected; run ${brand.bin} login to sign in again.` : "";
|
|
2100
|
+
return { exitCode: EXIT_FAILED, error: `${sentence(errorMessage(response, "Could not check the sign-in"))}${hint}` };
|
|
2101
|
+
}
|
|
2102
|
+
const session = response.body;
|
|
2103
|
+
const source = sourceLabel(auth.source, env);
|
|
2104
|
+
if (wantsJson(args, io)) {
|
|
2105
|
+
printJson(io, { ...response.body, source, api_url: auth.baseUrl, config_path: configPath(env) });
|
|
2106
|
+
return { exitCode: EXIT_OK };
|
|
2107
|
+
}
|
|
2108
|
+
const scope = session.scope ?? (session.key?.scope === "sending" ? "emails:send emails:read" : "full");
|
|
2109
|
+
const credential = session.oauth ? `Signed in with OAuth: ${session.oauth.client_name ?? brand.product}${session.oauth.device_name ? ` on ${session.oauth.device_name}` : ""}, from ${source}` : `Key: ${session.key?.name ?? "?"} (${session.key?.prefix ?? "?"}\u2026), from ${source}`;
|
|
2110
|
+
io.out(
|
|
2111
|
+
[
|
|
2112
|
+
session.user?.email ?? "(an API key; no person is recorded for it)",
|
|
2113
|
+
`Workspace: ${session.workspace?.name ?? "?"} (${session.workspace?.id ?? "?"})`,
|
|
2114
|
+
credential,
|
|
2115
|
+
`Scopes: ${scope === "full" ? "full" : scope.split(" ").join(", ")}`,
|
|
2116
|
+
...session.allowed_domains?.length ? [`Sends only from: ${session.allowed_domains.join(", ")}`] : [],
|
|
2117
|
+
...auth.baseUrl !== defaultApiUrl(env) ? [`API: ${auth.baseUrl}`] : []
|
|
2118
|
+
].join("\n")
|
|
2119
|
+
);
|
|
2120
|
+
return { exitCode: EXIT_OK };
|
|
2121
|
+
} catch (error) {
|
|
2122
|
+
if (error instanceof CliRequestError) return { exitCode: EXIT_FAILED, error: error.message };
|
|
2123
|
+
throw error;
|
|
2124
|
+
}
|
|
2125
|
+
}
|
|
2126
|
+
|
|
2127
|
+
// src/commands/keys/create.ts
|
|
2128
|
+
function readFlag16(args, flag) {
|
|
2129
|
+
const index = args.indexOf(flag);
|
|
2130
|
+
if (index === -1) return void 0;
|
|
2131
|
+
return args[index + 1];
|
|
2132
|
+
}
|
|
2133
|
+
async function runCreateKeyCommandWithClient(args, env = process.env) {
|
|
2134
|
+
const apiKey = readFlag16(args, "--api-key") || env.GROUNDMAIL_API_KEY;
|
|
2135
|
+
const baseUrl = readFlag16(args, "--base-url") || defaultApiUrl(env);
|
|
2136
|
+
if (!apiKey) {
|
|
2137
|
+
return { exitCode: 1, error: "Missing API key. Set GROUNDMAIL_API_KEY or pass --api-key." };
|
|
2138
|
+
}
|
|
2139
|
+
const name = readFlag16(args, "--name");
|
|
2140
|
+
const scope = readFlag16(args, "--scope");
|
|
2141
|
+
if (scope && scope !== "full" && scope !== "sending") {
|
|
2142
|
+
return { exitCode: 1, error: "--scope must be full or sending (or use --scopes for fine-grained scopes)" };
|
|
2143
|
+
}
|
|
2144
|
+
const scopesFlag = readFlag16(args, "--scopes");
|
|
2145
|
+
const scopes = scopesFlag !== void 0 ? parseScopeFlag(scopesFlag) : null;
|
|
2146
|
+
if (scopes && !scopes.ok) return { exitCode: 1, error: scopes.error };
|
|
2147
|
+
const domainsFlag = readFlag16(args, "--allowed-domains");
|
|
2148
|
+
const allowedDomains = domainsFlag?.split(/[\s,]+/).map((domain) => domain.trim()).filter(Boolean);
|
|
2149
|
+
try {
|
|
2150
|
+
const response = await apiRequest(baseUrl, "/api-keys", {
|
|
2151
|
+
apiKey,
|
|
2152
|
+
env,
|
|
2153
|
+
body: {
|
|
2154
|
+
...name ? { name } : {},
|
|
2155
|
+
...scope && !scopes ? { scope } : {},
|
|
2156
|
+
...scopes?.ok ? { scopes: scopes.scope.split(" ") } : {},
|
|
2157
|
+
...allowedDomains?.length ? { allowed_domains: allowedDomains } : {}
|
|
2158
|
+
}
|
|
2159
|
+
});
|
|
2160
|
+
if (!response.ok) {
|
|
2161
|
+
return { exitCode: 1, error: errorMessage(response, "Could not create the key") };
|
|
2162
|
+
}
|
|
2163
|
+
console.log(JSON.stringify(response.body));
|
|
2164
|
+
return { exitCode: 0 };
|
|
2165
|
+
} catch (error) {
|
|
2166
|
+
return { exitCode: 1, error: error instanceof Error ? error.message : String(error) };
|
|
2167
|
+
}
|
|
2168
|
+
}
|
|
2169
|
+
|
|
2170
|
+
// src/commands/keys/delete.ts
|
|
2171
|
+
function readFlag17(args, flag) {
|
|
2172
|
+
const index = args.indexOf(flag);
|
|
2173
|
+
if (index === -1) return void 0;
|
|
2174
|
+
return args[index + 1];
|
|
2175
|
+
}
|
|
2176
|
+
async function runDeleteKeyCommandWithClient(args, env = process.env) {
|
|
2177
|
+
const apiKey = readFlag17(args, "--api-key") || env.GROUNDMAIL_API_KEY;
|
|
2178
|
+
const baseUrl = readFlag17(args, "--base-url");
|
|
2179
|
+
const id = readFlag17(args, "--id");
|
|
2180
|
+
if (!apiKey) {
|
|
2181
|
+
return { exitCode: 1, error: "Missing API key. Set GROUNDMAIL_API_KEY or pass --api-key." };
|
|
2182
|
+
}
|
|
2183
|
+
if (!id) {
|
|
2184
|
+
return { exitCode: 1, error: "Required: --id <api-key-id>" };
|
|
2185
|
+
}
|
|
2186
|
+
const { Groundmail } = await import("groundmail");
|
|
2187
|
+
const client = new Groundmail(apiKey, baseUrl ? { baseUrl } : void 0);
|
|
2188
|
+
try {
|
|
2189
|
+
const result = await client.apiKeys.remove(id);
|
|
2190
|
+
console.log(JSON.stringify(result));
|
|
2191
|
+
return { exitCode: 0 };
|
|
2192
|
+
} catch (error) {
|
|
2193
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
2194
|
+
console.error(message);
|
|
2195
|
+
return { exitCode: 1, error: message };
|
|
2196
|
+
}
|
|
2197
|
+
}
|
|
2198
|
+
|
|
2199
|
+
// src/commands/suppressions/shared.ts
|
|
2200
|
+
function readFlag18(args, flag) {
|
|
2201
|
+
const index = args.indexOf(flag);
|
|
2202
|
+
if (index === -1) return void 0;
|
|
2203
|
+
return args[index + 1];
|
|
2204
|
+
}
|
|
2205
|
+
function parseSuppressionsCommonArgs(args) {
|
|
2206
|
+
return {
|
|
2207
|
+
apiKey: readFlag18(args, "--api-key"),
|
|
2208
|
+
baseUrl: readFlag18(args, "--base-url")
|
|
2209
|
+
};
|
|
2210
|
+
}
|
|
2211
|
+
function resolveApiKey6(options, env) {
|
|
2212
|
+
return options.apiKey || env.GROUNDMAIL_API_KEY || null;
|
|
2213
|
+
}
|
|
2214
|
+
async function createSuppressionsClient(apiKey, baseUrl) {
|
|
2215
|
+
const { Groundmail } = await import("groundmail");
|
|
2216
|
+
return new Groundmail(apiKey, baseUrl ? { baseUrl } : void 0);
|
|
2217
|
+
}
|
|
2218
|
+
|
|
2219
|
+
// src/commands/suppressions/list.ts
|
|
2220
|
+
async function runListCommand6(args, env = process.env) {
|
|
2221
|
+
const options = parseSuppressionsCommonArgs(args);
|
|
2222
|
+
const apiKey = resolveApiKey6(options, env);
|
|
2223
|
+
if (!apiKey) {
|
|
2224
|
+
return { exitCode: 1, error: "Missing API key. Set GROUNDMAIL_API_KEY or pass --api-key." };
|
|
2225
|
+
}
|
|
2226
|
+
try {
|
|
2227
|
+
const client = await createSuppressionsClient(apiKey, options.baseUrl);
|
|
2228
|
+
const limitRaw = readFlag18(args, "--limit");
|
|
2229
|
+
const cursor = readFlag18(args, "--cursor");
|
|
2230
|
+
const email = readFlag18(args, "--email");
|
|
2231
|
+
const result = await client.suppressions.list({
|
|
2232
|
+
...limitRaw ? { limit: Number(limitRaw) } : {},
|
|
2233
|
+
...cursor ? { cursor } : {},
|
|
2234
|
+
...email ? { email } : {}
|
|
2235
|
+
});
|
|
2236
|
+
console.log(JSON.stringify(result));
|
|
2237
|
+
return { exitCode: 0 };
|
|
2238
|
+
} catch (error) {
|
|
2239
|
+
return { exitCode: 1, error: error instanceof Error ? error.message : String(error) };
|
|
2240
|
+
}
|
|
2241
|
+
}
|
|
2242
|
+
|
|
2243
|
+
// src/commands/suppressions/add.ts
|
|
2244
|
+
async function runAddCommand2(args, env = process.env) {
|
|
2245
|
+
const options = parseSuppressionsCommonArgs(args);
|
|
2246
|
+
const apiKey = resolveApiKey6(options, env);
|
|
2247
|
+
if (!apiKey) {
|
|
2248
|
+
return { exitCode: 1, error: "Missing API key. Set GROUNDMAIL_API_KEY or pass --api-key." };
|
|
2249
|
+
}
|
|
2250
|
+
const email = readFlag18(args, "--email") || args.find((a) => a.includes("@") && !a.startsWith("-"));
|
|
2251
|
+
if (!email) {
|
|
2252
|
+
return { exitCode: 1, error: "Missing email. Pass --email user@example.com" };
|
|
2253
|
+
}
|
|
2254
|
+
try {
|
|
2255
|
+
const client = await createSuppressionsClient(apiKey, options.baseUrl);
|
|
2256
|
+
const reason = readFlag18(args, "--reason");
|
|
2257
|
+
const result = await client.suppressions.create({
|
|
2258
|
+
email,
|
|
2259
|
+
...reason ? { reason } : {}
|
|
2260
|
+
});
|
|
2261
|
+
console.log(JSON.stringify(result));
|
|
2262
|
+
return { exitCode: 0 };
|
|
2263
|
+
} catch (error) {
|
|
2264
|
+
return { exitCode: 1, error: error instanceof Error ? error.message : String(error) };
|
|
2265
|
+
}
|
|
2266
|
+
}
|
|
2267
|
+
|
|
2268
|
+
// src/commands/suppressions/remove.ts
|
|
2269
|
+
async function runRemoveCommand(args, env = process.env) {
|
|
2270
|
+
const options = parseSuppressionsCommonArgs(args);
|
|
2271
|
+
const apiKey = resolveApiKey6(options, env);
|
|
2272
|
+
if (!apiKey) {
|
|
2273
|
+
return { exitCode: 1, error: "Missing API key. Set GROUNDMAIL_API_KEY or pass --api-key." };
|
|
2274
|
+
}
|
|
2275
|
+
const idOrEmail = readFlag18(args, "--id") || readFlag18(args, "--email") || args.find((a) => !a.startsWith("-") && a !== "remove" && a !== "delete");
|
|
2276
|
+
if (!idOrEmail) {
|
|
2277
|
+
return { exitCode: 1, error: "Missing id or email. Pass --id <uuid> or --email user@example.com" };
|
|
2278
|
+
}
|
|
2279
|
+
try {
|
|
2280
|
+
const client = await createSuppressionsClient(apiKey, options.baseUrl);
|
|
2281
|
+
const result = await client.suppressions.remove(idOrEmail);
|
|
2282
|
+
console.log(JSON.stringify(result));
|
|
2283
|
+
return { exitCode: 0 };
|
|
2284
|
+
} catch (error) {
|
|
2285
|
+
return { exitCode: 1, error: error instanceof Error ? error.message : String(error) };
|
|
2286
|
+
}
|
|
2287
|
+
}
|
|
2288
|
+
|
|
2289
|
+
// src/commands/templates/shared.ts
|
|
2290
|
+
function readFlag19(args, flag) {
|
|
2291
|
+
const index = args.indexOf(flag);
|
|
2292
|
+
if (index === -1) return void 0;
|
|
2293
|
+
return args[index + 1];
|
|
2294
|
+
}
|
|
2295
|
+
function parseTemplatesCommonArgs(args) {
|
|
2296
|
+
return {
|
|
2297
|
+
apiKey: readFlag19(args, "--api-key"),
|
|
2298
|
+
baseUrl: readFlag19(args, "--base-url")
|
|
2299
|
+
};
|
|
2300
|
+
}
|
|
2301
|
+
function resolveApiKey7(options, env) {
|
|
2302
|
+
return options.apiKey || env.GROUNDMAIL_API_KEY || null;
|
|
2303
|
+
}
|
|
2304
|
+
async function createTemplatesClient(apiKey, baseUrl) {
|
|
2305
|
+
const { Groundmail } = await import("groundmail");
|
|
2306
|
+
return new Groundmail(apiKey, baseUrl ? { baseUrl } : void 0);
|
|
2307
|
+
}
|
|
2308
|
+
|
|
2309
|
+
// src/commands/templates/delete.ts
|
|
2310
|
+
var POSITIONAL_FLAGS7 = ["--api-key", "--base-url", "--id", "--alias"];
|
|
2311
|
+
async function runDeleteCommand4(args, env = process.env) {
|
|
2312
|
+
const options = parseTemplatesCommonArgs(args);
|
|
2313
|
+
const apiKey = resolveApiKey7(options, env);
|
|
2314
|
+
if (!apiKey) return { exitCode: 1, error: "Missing API key. Set GROUNDMAIL_API_KEY or pass --api-key." };
|
|
2315
|
+
const idOrAlias = readFlag19(args, "--id") || readFlag19(args, "--alias") || readFirstPositional8(args);
|
|
2316
|
+
if (!idOrAlias) return { exitCode: 1, error: "Missing template id or alias. Pass --id tpl_\u2026 or --alias welcome" };
|
|
2317
|
+
try {
|
|
2318
|
+
const client = await createTemplatesClient(apiKey, options.baseUrl);
|
|
2319
|
+
console.log(JSON.stringify(await client.templates.remove(idOrAlias)));
|
|
2320
|
+
return { exitCode: 0 };
|
|
2321
|
+
} catch (error) {
|
|
2322
|
+
return { exitCode: 1, error: error instanceof Error ? error.message : String(error) };
|
|
2323
|
+
}
|
|
2324
|
+
}
|
|
2325
|
+
function readFirstPositional8(args) {
|
|
2326
|
+
for (let i = 0; i < args.length; i += 1) {
|
|
2327
|
+
const arg = args[i];
|
|
2328
|
+
if (arg.startsWith("--")) {
|
|
2329
|
+
if (POSITIONAL_FLAGS7.includes(arg)) i += 1;
|
|
2330
|
+
continue;
|
|
2331
|
+
}
|
|
2332
|
+
return arg;
|
|
2333
|
+
}
|
|
2334
|
+
return void 0;
|
|
2335
|
+
}
|
|
2336
|
+
|
|
2337
|
+
// src/commands/templates/duplicate.ts
|
|
2338
|
+
var POSITIONAL_FLAGS8 = ["--api-key", "--base-url", "--id", "--alias", "--name"];
|
|
2339
|
+
async function runDuplicateCommand(args, env = process.env) {
|
|
2340
|
+
const options = parseTemplatesCommonArgs(args);
|
|
2341
|
+
const apiKey = resolveApiKey7(options, env);
|
|
2342
|
+
if (!apiKey) return { exitCode: 1, error: "Missing API key. Set GROUNDMAIL_API_KEY or pass --api-key." };
|
|
2343
|
+
const idOrAlias = readFlag19(args, "--id") || readFlag19(args, "--alias") || readFirstPositional9(args);
|
|
2344
|
+
if (!idOrAlias) return { exitCode: 1, error: "Missing template id or alias. Pass --id tpl_\u2026 or --alias welcome" };
|
|
2345
|
+
const name = readFlag19(args, "--name");
|
|
2346
|
+
try {
|
|
2347
|
+
const client = await createTemplatesClient(apiKey, options.baseUrl);
|
|
2348
|
+
console.log(JSON.stringify(await client.templates.duplicate(idOrAlias, name ? { name } : {})));
|
|
2349
|
+
return { exitCode: 0 };
|
|
2350
|
+
} catch (error) {
|
|
2351
|
+
return { exitCode: 1, error: error instanceof Error ? error.message : String(error) };
|
|
2352
|
+
}
|
|
2353
|
+
}
|
|
2354
|
+
function readFirstPositional9(args) {
|
|
2355
|
+
for (let i = 0; i < args.length; i += 1) {
|
|
2356
|
+
const arg = args[i];
|
|
2357
|
+
if (arg.startsWith("--")) {
|
|
2358
|
+
if (POSITIONAL_FLAGS8.includes(arg)) i += 1;
|
|
2359
|
+
continue;
|
|
2360
|
+
}
|
|
2361
|
+
return arg;
|
|
2362
|
+
}
|
|
2363
|
+
return void 0;
|
|
2364
|
+
}
|
|
2365
|
+
|
|
2366
|
+
// src/commands/templates/get.ts
|
|
2367
|
+
var POSITIONAL_FLAGS9 = ["--api-key", "--base-url", "--id", "--alias"];
|
|
2368
|
+
async function runGetCommand6(args, env = process.env) {
|
|
2369
|
+
const options = parseTemplatesCommonArgs(args);
|
|
2370
|
+
const apiKey = resolveApiKey7(options, env);
|
|
2371
|
+
if (!apiKey) return { exitCode: 1, error: "Missing API key. Set GROUNDMAIL_API_KEY or pass --api-key." };
|
|
2372
|
+
const idOrAlias = readFlag19(args, "--id") || readFlag19(args, "--alias") || readFirstPositional10(args);
|
|
2373
|
+
if (!idOrAlias) return { exitCode: 1, error: "Missing template id or alias. Pass --id tpl_\u2026 or --alias welcome" };
|
|
2374
|
+
try {
|
|
2375
|
+
const client = await createTemplatesClient(apiKey, options.baseUrl);
|
|
2376
|
+
console.log(JSON.stringify(await client.templates.get(idOrAlias)));
|
|
2377
|
+
return { exitCode: 0 };
|
|
2378
|
+
} catch (error) {
|
|
2379
|
+
return { exitCode: 1, error: error instanceof Error ? error.message : String(error) };
|
|
2380
|
+
}
|
|
2381
|
+
}
|
|
2382
|
+
function readFirstPositional10(args) {
|
|
2383
|
+
for (let i = 0; i < args.length; i += 1) {
|
|
2384
|
+
const arg = args[i];
|
|
2385
|
+
if (arg.startsWith("--")) {
|
|
2386
|
+
if (POSITIONAL_FLAGS9.includes(arg)) i += 1;
|
|
2387
|
+
continue;
|
|
2388
|
+
}
|
|
2389
|
+
return arg;
|
|
2390
|
+
}
|
|
2391
|
+
return void 0;
|
|
2392
|
+
}
|
|
2393
|
+
|
|
2394
|
+
// src/commands/templates/list.ts
|
|
2395
|
+
async function runListCommand7(args, env = process.env) {
|
|
2396
|
+
const options = parseTemplatesCommonArgs(args);
|
|
2397
|
+
const apiKey = resolveApiKey7(options, env);
|
|
2398
|
+
if (!apiKey) {
|
|
2399
|
+
return { exitCode: 1, error: "Missing API key. Set GROUNDMAIL_API_KEY or pass --api-key." };
|
|
2400
|
+
}
|
|
2401
|
+
try {
|
|
2402
|
+
const client = await createTemplatesClient(apiKey, options.baseUrl);
|
|
2403
|
+
const limitRaw = readFlag19(args, "--limit");
|
|
2404
|
+
const cursor = readFlag19(args, "--cursor");
|
|
2405
|
+
const status = readFlag19(args, "--status");
|
|
2406
|
+
const result = await client.templates.list({
|
|
2407
|
+
...limitRaw ? { limit: Number(limitRaw) } : {},
|
|
2408
|
+
...cursor ? { cursor } : {},
|
|
2409
|
+
...status ? { status } : {}
|
|
2410
|
+
});
|
|
2411
|
+
console.log(JSON.stringify(result));
|
|
2412
|
+
return { exitCode: 0 };
|
|
2413
|
+
} catch (error) {
|
|
2414
|
+
return { exitCode: 1, error: error instanceof Error ? error.message : String(error) };
|
|
2415
|
+
}
|
|
2416
|
+
}
|
|
2417
|
+
|
|
2418
|
+
// src/commands/templates/publish.ts
|
|
2419
|
+
var POSITIONAL_FLAGS10 = ["--api-key", "--base-url", "--id", "--alias"];
|
|
2420
|
+
async function runPublishCommand(args, env = process.env) {
|
|
2421
|
+
const options = parseTemplatesCommonArgs(args);
|
|
2422
|
+
const apiKey = resolveApiKey7(options, env);
|
|
2423
|
+
if (!apiKey) return { exitCode: 1, error: "Missing API key. Set GROUNDMAIL_API_KEY or pass --api-key." };
|
|
2424
|
+
const idOrAlias = readFlag19(args, "--id") || readFlag19(args, "--alias") || readFirstPositional11(args);
|
|
2425
|
+
if (!idOrAlias) return { exitCode: 1, error: "Missing template id or alias. Pass --id tpl_\u2026 or --alias welcome" };
|
|
2426
|
+
try {
|
|
2427
|
+
const client = await createTemplatesClient(apiKey, options.baseUrl);
|
|
2428
|
+
console.log(JSON.stringify(await client.templates.publish(idOrAlias)));
|
|
2429
|
+
return { exitCode: 0 };
|
|
2430
|
+
} catch (error) {
|
|
2431
|
+
return { exitCode: 1, error: error instanceof Error ? error.message : String(error) };
|
|
2432
|
+
}
|
|
2433
|
+
}
|
|
2434
|
+
function readFirstPositional11(args) {
|
|
2435
|
+
for (let i = 0; i < args.length; i += 1) {
|
|
2436
|
+
const arg = args[i];
|
|
2437
|
+
if (arg.startsWith("--")) {
|
|
2438
|
+
if (POSITIONAL_FLAGS10.includes(arg)) i += 1;
|
|
2439
|
+
continue;
|
|
2440
|
+
}
|
|
2441
|
+
return arg;
|
|
2442
|
+
}
|
|
2443
|
+
return void 0;
|
|
2444
|
+
}
|
|
2445
|
+
|
|
2446
|
+
// src/commands/templates/render-source.ts
|
|
2447
|
+
var HTML_EXTENSIONS = [".html", ".htm"];
|
|
2448
|
+
var TEXT_EXTENSIONS = [".txt", ".text"];
|
|
2449
|
+
var MODULE_EXTENSIONS = [".js", ".mjs", ".cjs", ".jsx", ".ts", ".tsx"];
|
|
2450
|
+
var TYPESCRIPT_EXTENSIONS = [".ts", ".tsx", ".jsx"];
|
|
2451
|
+
function extensionOf(filePath) {
|
|
2452
|
+
const match = /\.[^.\/\\]+$/.exec(filePath);
|
|
2453
|
+
return match ? match[0].toLowerCase() : "";
|
|
2454
|
+
}
|
|
2455
|
+
async function defaultReadFile(filePath) {
|
|
2456
|
+
const { readFile: readFile3 } = await import("fs/promises");
|
|
2457
|
+
return readFile3(filePath, "utf8");
|
|
2458
|
+
}
|
|
2459
|
+
async function defaultImportModule(filePath) {
|
|
2460
|
+
const { pathToFileURL } = await import("url");
|
|
2461
|
+
const { resolve } = await import("path");
|
|
2462
|
+
const url = pathToFileURL(resolve(filePath)).href;
|
|
2463
|
+
try {
|
|
2464
|
+
return await import(url);
|
|
2465
|
+
} catch (error) {
|
|
2466
|
+
if (!TYPESCRIPT_EXTENSIONS.includes(extensionOf(filePath))) throw error;
|
|
2467
|
+
try {
|
|
2468
|
+
const specifier = "tsx/esm/api";
|
|
2469
|
+
const tsx = await import(specifier);
|
|
2470
|
+
tsx.register();
|
|
2471
|
+
} catch {
|
|
2472
|
+
throw new Error(
|
|
2473
|
+
`Cannot import ${filePath} directly. Install tsx to push TypeScript or JSX templates (npm install -D tsx), or pre-render the file to .html.`
|
|
2474
|
+
);
|
|
2475
|
+
}
|
|
2476
|
+
return import(url);
|
|
2477
|
+
}
|
|
2478
|
+
}
|
|
2479
|
+
async function loadReactEmailRender() {
|
|
2480
|
+
try {
|
|
2481
|
+
const mod = await import("@react-email/render");
|
|
2482
|
+
const render = mod.render ?? mod.default?.render;
|
|
2483
|
+
if (typeof render !== "function") throw new Error("missing render export");
|
|
2484
|
+
return render;
|
|
2485
|
+
} catch {
|
|
2486
|
+
throw new Error(
|
|
2487
|
+
"Rendering a React email requires @react-email/render. Install it with: npm install @react-email/render react"
|
|
2488
|
+
);
|
|
2489
|
+
}
|
|
2490
|
+
}
|
|
2491
|
+
async function toElement(mod, props) {
|
|
2492
|
+
const record = mod;
|
|
2493
|
+
const exported = record?.default ?? mod;
|
|
2494
|
+
if (typeof exported === "function") {
|
|
2495
|
+
const react = await import("./react-IXBRBSFN.js");
|
|
2496
|
+
const createElement = react.createElement ?? react.default?.createElement;
|
|
2497
|
+
if (typeof createElement !== "function") {
|
|
2498
|
+
throw new Error("Rendering a React email requires react. Install it with: npm install react");
|
|
2499
|
+
}
|
|
2500
|
+
return createElement(exported, props);
|
|
2501
|
+
}
|
|
2502
|
+
if (!exported) {
|
|
2503
|
+
throw new Error("The template module must export a React component as its default export.");
|
|
2504
|
+
}
|
|
2505
|
+
return exported;
|
|
2506
|
+
}
|
|
2507
|
+
async function loadTemplateSource(filePath, options = {}, deps = {}) {
|
|
2508
|
+
const readFile3 = deps.readFile ?? defaultReadFile;
|
|
2509
|
+
const importModule = deps.importModule ?? defaultImportModule;
|
|
2510
|
+
const extension = extensionOf(filePath);
|
|
2511
|
+
if (HTML_EXTENSIONS.includes(extension)) {
|
|
2512
|
+
return { html: await readFile3(filePath) };
|
|
2513
|
+
}
|
|
2514
|
+
if (TEXT_EXTENSIONS.includes(extension)) {
|
|
2515
|
+
return { text: await readFile3(filePath) };
|
|
2516
|
+
}
|
|
2517
|
+
if (!MODULE_EXTENSIONS.includes(extension)) {
|
|
2518
|
+
throw new Error(
|
|
2519
|
+
`Unsupported template file "${filePath}". Use .html, .txt, or a module exporting a React component (${MODULE_EXTENSIONS.join(", ")}).`
|
|
2520
|
+
);
|
|
2521
|
+
}
|
|
2522
|
+
const mod = await importModule(filePath);
|
|
2523
|
+
const element = await toElement(mod, options.props ?? {});
|
|
2524
|
+
const render = await loadReactEmailRender();
|
|
2525
|
+
const html = await render(element);
|
|
2526
|
+
let text;
|
|
2527
|
+
try {
|
|
2528
|
+
text = await render(element, { plainText: true });
|
|
2529
|
+
} catch {
|
|
2530
|
+
text = void 0;
|
|
2531
|
+
}
|
|
2532
|
+
return { html, ...text ? { text } : {} };
|
|
2533
|
+
}
|
|
2534
|
+
|
|
2535
|
+
// src/commands/templates/push.ts
|
|
2536
|
+
var VALUE_FLAGS = /* @__PURE__ */ new Set([
|
|
2537
|
+
"--alias",
|
|
2538
|
+
"--subject",
|
|
2539
|
+
"--name",
|
|
2540
|
+
"--text",
|
|
2541
|
+
"--preheader",
|
|
2542
|
+
"--props",
|
|
2543
|
+
"--api-key",
|
|
2544
|
+
"--base-url"
|
|
2545
|
+
]);
|
|
2546
|
+
function readPositional(args) {
|
|
2547
|
+
for (let index = 0; index < args.length; index += 1) {
|
|
2548
|
+
const arg = args[index];
|
|
2549
|
+
if (arg.startsWith("--")) {
|
|
2550
|
+
if (VALUE_FLAGS.has(arg)) index += 1;
|
|
2551
|
+
continue;
|
|
2552
|
+
}
|
|
2553
|
+
return arg;
|
|
2554
|
+
}
|
|
2555
|
+
return void 0;
|
|
2556
|
+
}
|
|
2557
|
+
function statusOf(error) {
|
|
2558
|
+
const status = error?.status;
|
|
2559
|
+
return typeof status === "number" ? status : void 0;
|
|
2560
|
+
}
|
|
2561
|
+
async function runPushCommand(args, env = process.env, deps = {}) {
|
|
2562
|
+
const options = parseTemplatesCommonArgs(args);
|
|
2563
|
+
const apiKey = resolveApiKey7(options, env);
|
|
2564
|
+
if (!apiKey) {
|
|
2565
|
+
return { exitCode: 1, error: "Missing API key. Set GROUNDMAIL_API_KEY or pass --api-key." };
|
|
2566
|
+
}
|
|
2567
|
+
const file = readFlag19(args, "--file") ?? readPositional(args);
|
|
2568
|
+
if (!file) {
|
|
2569
|
+
return { exitCode: 1, error: "Missing template file. Usage: templates push <file> --alias <alias>" };
|
|
2570
|
+
}
|
|
2571
|
+
const alias = readFlag19(args, "--alias");
|
|
2572
|
+
if (!alias) {
|
|
2573
|
+
return { exitCode: 1, error: "Missing --alias. The alias identifies which template to update." };
|
|
2574
|
+
}
|
|
2575
|
+
const subject = readFlag19(args, "--subject");
|
|
2576
|
+
const name = readFlag19(args, "--name");
|
|
2577
|
+
const preheader = readFlag19(args, "--preheader");
|
|
2578
|
+
const textFile = readFlag19(args, "--text");
|
|
2579
|
+
const propsRaw = readFlag19(args, "--props");
|
|
2580
|
+
const shouldPublish = args.includes("--publish");
|
|
2581
|
+
let props = {};
|
|
2582
|
+
if (propsRaw) {
|
|
2583
|
+
try {
|
|
2584
|
+
const parsed = JSON.parse(propsRaw);
|
|
2585
|
+
if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) {
|
|
2586
|
+
throw new Error("not an object");
|
|
2587
|
+
}
|
|
2588
|
+
props = parsed;
|
|
2589
|
+
} catch {
|
|
2590
|
+
return { exitCode: 1, error: `--props must be a JSON object, e.g. --props '{"name":"Ada"}'` };
|
|
2591
|
+
}
|
|
2592
|
+
}
|
|
2593
|
+
try {
|
|
2594
|
+
const source = await loadTemplateSource(file, { props }, deps);
|
|
2595
|
+
if (textFile) {
|
|
2596
|
+
const textSource = await loadTemplateSource(textFile, { props }, deps);
|
|
2597
|
+
source.text = textSource.text ?? textSource.html;
|
|
2598
|
+
}
|
|
2599
|
+
if (!source.html && !source.text) {
|
|
2600
|
+
return { exitCode: 1, error: `${file} produced no content to push.` };
|
|
2601
|
+
}
|
|
2602
|
+
const createClient2 = deps.createClient ?? createTemplatesClient;
|
|
2603
|
+
const client = await createClient2(apiKey, options.baseUrl);
|
|
2604
|
+
let existed = true;
|
|
2605
|
+
try {
|
|
2606
|
+
await client.templates.get(alias);
|
|
2607
|
+
} catch (error) {
|
|
2608
|
+
if (statusOf(error) !== 404) throw error;
|
|
2609
|
+
existed = false;
|
|
2610
|
+
}
|
|
2611
|
+
const body = {
|
|
2612
|
+
format: "html",
|
|
2613
|
+
...source.html ? { html: source.html } : {},
|
|
2614
|
+
...source.text ? { text: source.text } : {},
|
|
2615
|
+
...subject ? { subject } : {},
|
|
2616
|
+
...preheader ? { preheader } : {},
|
|
2617
|
+
...name ? { name } : {}
|
|
2618
|
+
};
|
|
2619
|
+
let result;
|
|
2620
|
+
if (existed) {
|
|
2621
|
+
result = await client.templates.update(alias, body);
|
|
2622
|
+
} else {
|
|
2623
|
+
if (!subject) {
|
|
2624
|
+
return {
|
|
2625
|
+
exitCode: 1,
|
|
2626
|
+
error: `Template "${alias}" does not exist yet. Pass --subject to create it.`
|
|
2627
|
+
};
|
|
2628
|
+
}
|
|
2629
|
+
result = await client.templates.create({
|
|
2630
|
+
name: name || alias,
|
|
2631
|
+
subject,
|
|
2632
|
+
alias,
|
|
2633
|
+
...body
|
|
2634
|
+
});
|
|
2635
|
+
}
|
|
2636
|
+
if (shouldPublish) {
|
|
2637
|
+
result = await client.templates.publish(alias);
|
|
2638
|
+
}
|
|
2639
|
+
console.log(JSON.stringify({ ...result, pushed: { file, alias, created: !existed } }));
|
|
2640
|
+
return { exitCode: 0 };
|
|
2641
|
+
} catch (error) {
|
|
2642
|
+
return { exitCode: 1, error: error instanceof Error ? error.message : String(error) };
|
|
2643
|
+
}
|
|
2644
|
+
}
|
|
2645
|
+
|
|
2646
|
+
// src/commands/templates/create.ts
|
|
2647
|
+
async function runCreateCommand3(args, env = process.env) {
|
|
2648
|
+
const options = parseTemplatesCommonArgs(args);
|
|
2649
|
+
const apiKey = resolveApiKey7(options, env);
|
|
2650
|
+
if (!apiKey) {
|
|
2651
|
+
return { exitCode: 1, error: "Missing API key. Set GROUNDMAIL_API_KEY or pass --api-key." };
|
|
2652
|
+
}
|
|
2653
|
+
const name = readFlag19(args, "--name");
|
|
2654
|
+
const subject = readFlag19(args, "--subject");
|
|
2655
|
+
const html = readFlag19(args, "--html");
|
|
2656
|
+
const text = readFlag19(args, "--text");
|
|
2657
|
+
const alias = readFlag19(args, "--alias");
|
|
2658
|
+
const category = readFlag19(args, "--category");
|
|
2659
|
+
const unsubscribeEnabled = args.includes("--unsubscribe-enabled");
|
|
2660
|
+
if (!name) {
|
|
2661
|
+
return { exitCode: 1, error: 'Missing name. Pass --name "Welcome"' };
|
|
2662
|
+
}
|
|
2663
|
+
if (!subject) {
|
|
2664
|
+
return { exitCode: 1, error: 'Missing subject. Pass --subject "Hello {{name}}"' };
|
|
2665
|
+
}
|
|
2666
|
+
if (!html && !text) {
|
|
2667
|
+
return { exitCode: 1, error: 'Missing body. Pass --html "<p>Hi</p>" or --text "Hi"' };
|
|
2668
|
+
}
|
|
2669
|
+
try {
|
|
2670
|
+
const client = await createTemplatesClient(apiKey, options.baseUrl);
|
|
2671
|
+
const result = await client.templates.create({
|
|
2672
|
+
name,
|
|
2673
|
+
subject,
|
|
2674
|
+
...html ? { html } : {},
|
|
2675
|
+
...text ? { text } : {},
|
|
2676
|
+
...alias ? { alias } : {},
|
|
2677
|
+
...category ? { category } : {},
|
|
2678
|
+
...unsubscribeEnabled ? { unsubscribe_enabled: true } : {}
|
|
2679
|
+
});
|
|
2680
|
+
console.log(JSON.stringify(result));
|
|
2681
|
+
return { exitCode: 0 };
|
|
2682
|
+
} catch (error) {
|
|
2683
|
+
return { exitCode: 1, error: error instanceof Error ? error.message : String(error) };
|
|
2684
|
+
}
|
|
2685
|
+
}
|
|
2686
|
+
|
|
2687
|
+
// src/commands/templates/update.ts
|
|
2688
|
+
var POSITIONAL_FLAGS11 = [
|
|
2689
|
+
"--api-key",
|
|
2690
|
+
"--base-url",
|
|
2691
|
+
"--id",
|
|
2692
|
+
"--alias",
|
|
2693
|
+
"--set-alias",
|
|
2694
|
+
"--name",
|
|
2695
|
+
"--subject",
|
|
2696
|
+
"--html",
|
|
2697
|
+
"--text",
|
|
2698
|
+
"--variable"
|
|
2699
|
+
];
|
|
2700
|
+
async function runUpdateCommand4(args, env = process.env) {
|
|
2701
|
+
const options = parseTemplatesCommonArgs(args);
|
|
2702
|
+
const apiKey = resolveApiKey7(options, env);
|
|
2703
|
+
if (!apiKey) {
|
|
2704
|
+
return { exitCode: 1, error: "Missing API key. Set GROUNDMAIL_API_KEY or pass --api-key." };
|
|
2705
|
+
}
|
|
2706
|
+
const idOrAlias = readFlag19(args, "--id") || readFlag19(args, "--alias") || readFirstPositional12(args);
|
|
2707
|
+
if (!idOrAlias) {
|
|
2708
|
+
return { exitCode: 1, error: "Missing template id or alias. Pass --id tpl_\u2026 or --alias welcome" };
|
|
2709
|
+
}
|
|
2710
|
+
const name = readFlag19(args, "--name");
|
|
2711
|
+
const subject = readFlag19(args, "--subject");
|
|
2712
|
+
const html = readFlag19(args, "--html");
|
|
2713
|
+
const text = readFlag19(args, "--text");
|
|
2714
|
+
const alias = readFlag19(args, "--set-alias");
|
|
2715
|
+
const variables = readFlags3(args, "--variable");
|
|
2716
|
+
const category = readFlag19(args, "--category");
|
|
2717
|
+
const unsubscribeEnabled = args.includes("--unsubscribe-enabled");
|
|
2718
|
+
const unsubscribeDisabled = args.includes("--no-unsubscribe-enabled");
|
|
2719
|
+
if (!name && !subject && !html && !text && !alias && variables.length === 0 && !category && !unsubscribeEnabled && !unsubscribeDisabled) {
|
|
2720
|
+
return {
|
|
2721
|
+
exitCode: 1,
|
|
2722
|
+
error: "Nothing to update. Pass --name, --subject, --html, --text, --set-alias, --category, --variable, or unsubscribe flags."
|
|
2723
|
+
};
|
|
2724
|
+
}
|
|
2725
|
+
try {
|
|
2726
|
+
const client = await createTemplatesClient(apiKey, options.baseUrl);
|
|
2727
|
+
const result = await client.templates.update(idOrAlias, {
|
|
2728
|
+
...name ? { name } : {},
|
|
2729
|
+
...subject ? { subject } : {},
|
|
2730
|
+
...html ? { html } : {},
|
|
2731
|
+
...text ? { text } : {},
|
|
2732
|
+
...alias ? { alias } : {},
|
|
2733
|
+
...variables.length > 0 ? { variables } : {},
|
|
2734
|
+
...category ? { category } : {},
|
|
2735
|
+
...unsubscribeEnabled ? { unsubscribe_enabled: true } : {},
|
|
2736
|
+
...unsubscribeDisabled ? { unsubscribe_enabled: false } : {}
|
|
2737
|
+
});
|
|
2738
|
+
console.log(JSON.stringify(result));
|
|
2739
|
+
return { exitCode: 0 };
|
|
2740
|
+
} catch (error) {
|
|
2741
|
+
return { exitCode: 1, error: error instanceof Error ? error.message : String(error) };
|
|
2742
|
+
}
|
|
2743
|
+
}
|
|
2744
|
+
function readFlags3(args, flag) {
|
|
2745
|
+
const values = [];
|
|
2746
|
+
for (let i = 0; i < args.length; i += 1) {
|
|
2747
|
+
if (args[i] === flag && args[i + 1]) values.push(args[i + 1]);
|
|
2748
|
+
}
|
|
2749
|
+
return values;
|
|
2750
|
+
}
|
|
2751
|
+
function readFirstPositional12(args) {
|
|
2752
|
+
for (let i = 0; i < args.length; i += 1) {
|
|
2753
|
+
const arg = args[i];
|
|
2754
|
+
if (arg.startsWith("--")) {
|
|
2755
|
+
if (POSITIONAL_FLAGS11.includes(arg)) i += 1;
|
|
2756
|
+
continue;
|
|
2757
|
+
}
|
|
2758
|
+
return arg;
|
|
2759
|
+
}
|
|
2760
|
+
return void 0;
|
|
2761
|
+
}
|
|
2762
|
+
|
|
2763
|
+
// src/commands/templates/from-starter.ts
|
|
2764
|
+
var POSITIONAL_FLAGS12 = ["--api-key", "--base-url", "--alias"];
|
|
2765
|
+
function readFirstPositional13(args) {
|
|
2766
|
+
for (let i = 0; i < args.length; i += 1) {
|
|
2767
|
+
const arg = args[i];
|
|
2768
|
+
if (arg.startsWith("--")) {
|
|
2769
|
+
if (POSITIONAL_FLAGS12.includes(arg)) i += 1;
|
|
2770
|
+
continue;
|
|
2771
|
+
}
|
|
2772
|
+
return arg;
|
|
2773
|
+
}
|
|
2774
|
+
return void 0;
|
|
2775
|
+
}
|
|
2776
|
+
async function runFromStarterCommand(args, env = process.env) {
|
|
2777
|
+
const options = parseTemplatesCommonArgs(args);
|
|
2778
|
+
const apiKey = resolveApiKey7(options, env);
|
|
2779
|
+
if (!apiKey) {
|
|
2780
|
+
return { exitCode: 1, error: "Missing API key. Set GROUNDMAIL_API_KEY or pass --api-key." };
|
|
2781
|
+
}
|
|
2782
|
+
const alias = readFlag19(args, "--alias") || readFirstPositional13(args);
|
|
2783
|
+
if (!alias) {
|
|
2784
|
+
return {
|
|
2785
|
+
exitCode: 1,
|
|
2786
|
+
error: "Missing starter alias. Pass password-reset (or --alias welcome). List: groundmail templates starters"
|
|
2787
|
+
};
|
|
2788
|
+
}
|
|
2789
|
+
try {
|
|
2790
|
+
const client = await createTemplatesClient(apiKey, options.baseUrl);
|
|
2791
|
+
console.log(JSON.stringify(await client.templates.fromStarter(alias)));
|
|
2792
|
+
return { exitCode: 0 };
|
|
2793
|
+
} catch (error) {
|
|
2794
|
+
return { exitCode: 1, error: error instanceof Error ? error.message : String(error) };
|
|
2795
|
+
}
|
|
2796
|
+
}
|
|
2797
|
+
async function runListStartersCommand(args, env = process.env) {
|
|
2798
|
+
const options = parseTemplatesCommonArgs(args);
|
|
2799
|
+
const apiKey = resolveApiKey7(options, env);
|
|
2800
|
+
if (!apiKey) {
|
|
2801
|
+
return { exitCode: 1, error: "Missing API key. Set GROUNDMAIL_API_KEY or pass --api-key." };
|
|
2802
|
+
}
|
|
2803
|
+
try {
|
|
2804
|
+
const client = await createTemplatesClient(apiKey, options.baseUrl);
|
|
2805
|
+
console.log(JSON.stringify(await client.templates.listStarters()));
|
|
2806
|
+
return { exitCode: 0 };
|
|
2807
|
+
} catch (error) {
|
|
2808
|
+
return { exitCode: 1, error: error instanceof Error ? error.message : String(error) };
|
|
2809
|
+
}
|
|
2810
|
+
}
|
|
2811
|
+
|
|
2812
|
+
// src/commands/templates/versions.ts
|
|
2813
|
+
var POSITIONAL_FLAGS13 = ["--api-key", "--base-url", "--id", "--alias", "--version", "--from", "--to"];
|
|
2814
|
+
function readFirstPositional14(args) {
|
|
2815
|
+
for (let i = 0; i < args.length; i += 1) {
|
|
2816
|
+
const arg = args[i];
|
|
2817
|
+
if (arg.startsWith("--")) {
|
|
2818
|
+
if (POSITIONAL_FLAGS13.includes(arg)) i += 1;
|
|
2819
|
+
continue;
|
|
2820
|
+
}
|
|
2821
|
+
return arg;
|
|
2822
|
+
}
|
|
2823
|
+
return void 0;
|
|
2824
|
+
}
|
|
2825
|
+
async function runVersionsCommand(args, env = process.env) {
|
|
2826
|
+
const options = parseTemplatesCommonArgs(args);
|
|
2827
|
+
const apiKey = resolveApiKey7(options, env);
|
|
2828
|
+
if (!apiKey) {
|
|
2829
|
+
return { exitCode: 1, error: "Missing API key. Set GROUNDMAIL_API_KEY or pass --api-key." };
|
|
2830
|
+
}
|
|
2831
|
+
const idOrAlias = readFlag19(args, "--id") || readFlag19(args, "--alias") || readFirstPositional14(args);
|
|
2832
|
+
if (!idOrAlias) {
|
|
2833
|
+
return { exitCode: 1, error: "Missing template id or alias. Pass --id tpl_\u2026 or --alias welcome" };
|
|
2834
|
+
}
|
|
2835
|
+
try {
|
|
2836
|
+
const client = await createTemplatesClient(apiKey, options.baseUrl);
|
|
2837
|
+
console.log(JSON.stringify(await client.templates.listVersions(idOrAlias)));
|
|
2838
|
+
return { exitCode: 0 };
|
|
2839
|
+
} catch (error) {
|
|
2840
|
+
return { exitCode: 1, error: error instanceof Error ? error.message : String(error) };
|
|
2841
|
+
}
|
|
2842
|
+
}
|
|
2843
|
+
async function runRestoreCommand(args, env = process.env) {
|
|
2844
|
+
const options = parseTemplatesCommonArgs(args);
|
|
2845
|
+
const apiKey = resolveApiKey7(options, env);
|
|
2846
|
+
if (!apiKey) {
|
|
2847
|
+
return { exitCode: 1, error: "Missing API key. Set GROUNDMAIL_API_KEY or pass --api-key." };
|
|
2848
|
+
}
|
|
2849
|
+
const idOrAlias = readFlag19(args, "--id") || readFlag19(args, "--alias") || readFirstPositional14(args);
|
|
2850
|
+
const versionRaw = readFlag19(args, "--version");
|
|
2851
|
+
const version2 = versionRaw ? Number.parseInt(versionRaw, 10) : NaN;
|
|
2852
|
+
if (!idOrAlias || !Number.isFinite(version2) || version2 < 1) {
|
|
2853
|
+
return {
|
|
2854
|
+
exitCode: 1,
|
|
2855
|
+
error: "Usage: groundmail templates restore --id <id|alias> --version <n>"
|
|
2856
|
+
};
|
|
2857
|
+
}
|
|
2858
|
+
try {
|
|
2859
|
+
const client = await createTemplatesClient(apiKey, options.baseUrl);
|
|
2860
|
+
console.log(JSON.stringify(await client.templates.restoreVersion(idOrAlias, version2)));
|
|
2861
|
+
return { exitCode: 0 };
|
|
2862
|
+
} catch (error) {
|
|
2863
|
+
return { exitCode: 1, error: error instanceof Error ? error.message : String(error) };
|
|
2864
|
+
}
|
|
2865
|
+
}
|
|
2866
|
+
async function runTestSendCommand(args, env = process.env) {
|
|
2867
|
+
const options = parseTemplatesCommonArgs(args);
|
|
2868
|
+
const apiKey = resolveApiKey7(options, env);
|
|
2869
|
+
if (!apiKey) {
|
|
2870
|
+
return { exitCode: 1, error: "Missing API key. Set GROUNDMAIL_API_KEY or pass --api-key." };
|
|
2871
|
+
}
|
|
2872
|
+
const idOrAlias = readFlag19(args, "--id") || readFlag19(args, "--alias") || readFirstPositional14(args);
|
|
2873
|
+
const from = readFlag19(args, "--from");
|
|
2874
|
+
const to = readFlag19(args, "--to");
|
|
2875
|
+
if (!idOrAlias || !from || !to) {
|
|
2876
|
+
return {
|
|
2877
|
+
exitCode: 1,
|
|
2878
|
+
error: "Usage: groundmail templates test-send --id <id|alias> --from <email> --to <email>"
|
|
2879
|
+
};
|
|
2880
|
+
}
|
|
2881
|
+
try {
|
|
2882
|
+
const client = await createTemplatesClient(apiKey, options.baseUrl);
|
|
2883
|
+
console.log(JSON.stringify(await client.templates.testSend(idOrAlias, { from, to })));
|
|
2884
|
+
return { exitCode: 0 };
|
|
2885
|
+
} catch (error) {
|
|
2886
|
+
return { exitCode: 1, error: error instanceof Error ? error.message : String(error) };
|
|
2887
|
+
}
|
|
2888
|
+
}
|
|
2889
|
+
|
|
2890
|
+
// src/commands/integrations/shared.ts
|
|
2891
|
+
function readFlag20(args, flag) {
|
|
2892
|
+
const index = args.indexOf(flag);
|
|
2893
|
+
if (index === -1) return void 0;
|
|
2894
|
+
return args[index + 1];
|
|
2895
|
+
}
|
|
2896
|
+
function parseIntegrationsCommonArgs(args) {
|
|
2897
|
+
return {
|
|
2898
|
+
apiKey: readFlag20(args, "--api-key"),
|
|
2899
|
+
baseUrl: readFlag20(args, "--base-url")
|
|
2900
|
+
};
|
|
2901
|
+
}
|
|
2902
|
+
function resolveApiKey8(options, env) {
|
|
2903
|
+
return options.apiKey || env.GROUNDMAIL_API_KEY || null;
|
|
2904
|
+
}
|
|
2905
|
+
async function createIntegrationsClient(apiKey, baseUrl) {
|
|
2906
|
+
const { Groundmail } = await import("groundmail");
|
|
2907
|
+
return new Groundmail(apiKey, baseUrl ? { baseUrl } : void 0);
|
|
2908
|
+
}
|
|
2909
|
+
|
|
2910
|
+
// src/commands/integrations/twilio.ts
|
|
2911
|
+
async function runTwilioStatusCommand(args, env = process.env) {
|
|
2912
|
+
const options = parseIntegrationsCommonArgs(args);
|
|
2913
|
+
const apiKey = resolveApiKey8(options, env);
|
|
2914
|
+
if (!apiKey) {
|
|
2915
|
+
return { exitCode: 1, error: "Missing API key. Set GROUNDMAIL_API_KEY or pass --api-key." };
|
|
2916
|
+
}
|
|
2917
|
+
try {
|
|
2918
|
+
const client = await createIntegrationsClient(apiKey, options.baseUrl);
|
|
2919
|
+
const result = await client.integrations.getTwilio();
|
|
2920
|
+
console.log(JSON.stringify(result));
|
|
2921
|
+
return { exitCode: 0 };
|
|
2922
|
+
} catch (error) {
|
|
2923
|
+
return { exitCode: 1, error: error instanceof Error ? error.message : String(error) };
|
|
2924
|
+
}
|
|
2925
|
+
}
|
|
2926
|
+
async function runTwilioConnectCommand(args, env = process.env) {
|
|
2927
|
+
const options = parseIntegrationsCommonArgs(args);
|
|
2928
|
+
const apiKey = resolveApiKey8(options, env);
|
|
2929
|
+
if (!apiKey) {
|
|
2930
|
+
return { exitCode: 1, error: "Missing API key. Set GROUNDMAIL_API_KEY or pass --api-key." };
|
|
2931
|
+
}
|
|
2932
|
+
const accountSid = readFlag20(args, "--account-sid") || env.TWILIO_ACCOUNT_SID || env.TWILIO_SID || "";
|
|
2933
|
+
const authToken = readFlag20(args, "--auth-token") || env.TWILIO_AUTH_TOKEN || env.TWILIO_TOKEN || "";
|
|
2934
|
+
const defaultFrom = readFlag20(args, "--from") || readFlag20(args, "--default-from") || env.TWILIO_DEFAULT_FROM || "";
|
|
2935
|
+
const messagingServiceSid = readFlag20(args, "--messaging-service-sid") || readFlag20(args, "--messaging-sid") || env.TWILIO_MESSAGING_SERVICE_SID || "";
|
|
2936
|
+
if (!accountSid.trim() || !authToken.trim()) {
|
|
2937
|
+
return {
|
|
2938
|
+
exitCode: 1,
|
|
2939
|
+
error: "Required: --account-sid and --auth-token (or TWILIO_ACCOUNT_SID and TWILIO_AUTH_TOKEN)."
|
|
2940
|
+
};
|
|
2941
|
+
}
|
|
2942
|
+
if (!defaultFrom.trim() && !messagingServiceSid.trim()) {
|
|
2943
|
+
return {
|
|
2944
|
+
exitCode: 1,
|
|
2945
|
+
error: "Required: --from or --messaging-service-sid (or TWILIO_DEFAULT_FROM / TWILIO_MESSAGING_SERVICE_SID)."
|
|
2946
|
+
};
|
|
2947
|
+
}
|
|
2948
|
+
try {
|
|
2949
|
+
const client = await createIntegrationsClient(apiKey, options.baseUrl);
|
|
2950
|
+
const result = await client.integrations.connectTwilio({
|
|
2951
|
+
accountSid: accountSid.trim(),
|
|
2952
|
+
authToken: authToken.trim(),
|
|
2953
|
+
...defaultFrom.trim() ? { defaultFrom: defaultFrom.trim() } : {},
|
|
2954
|
+
...messagingServiceSid.trim() ? { messagingServiceSid: messagingServiceSid.trim() } : {}
|
|
2955
|
+
});
|
|
2956
|
+
console.log(JSON.stringify(result));
|
|
2957
|
+
return { exitCode: 0 };
|
|
2958
|
+
} catch (error) {
|
|
2959
|
+
return { exitCode: 1, error: error instanceof Error ? error.message : String(error) };
|
|
2960
|
+
}
|
|
2961
|
+
}
|
|
2962
|
+
async function runTwilioDisconnectCommand(args, env = process.env) {
|
|
2963
|
+
const options = parseIntegrationsCommonArgs(args);
|
|
2964
|
+
const apiKey = resolveApiKey8(options, env);
|
|
2965
|
+
if (!apiKey) {
|
|
2966
|
+
return { exitCode: 1, error: "Missing API key. Set GROUNDMAIL_API_KEY or pass --api-key." };
|
|
2967
|
+
}
|
|
2968
|
+
try {
|
|
2969
|
+
const client = await createIntegrationsClient(apiKey, options.baseUrl);
|
|
2970
|
+
const result = await client.integrations.disconnectTwilio();
|
|
2971
|
+
console.log(JSON.stringify(result));
|
|
2972
|
+
return { exitCode: 0 };
|
|
2973
|
+
} catch (error) {
|
|
2974
|
+
return { exitCode: 1, error: error instanceof Error ? error.message : String(error) };
|
|
2975
|
+
}
|
|
2976
|
+
}
|
|
2977
|
+
|
|
2978
|
+
// src/commands/auth.ts
|
|
2979
|
+
async function runAuthCommand(subcommand, argv, deps = {}) {
|
|
2980
|
+
const env = deps.env ?? process.env;
|
|
2981
|
+
const brand = cliBrand(env);
|
|
2982
|
+
const usage = `Usage: ${brand.bin} auth token [--scope <scopes>] [--workspace <name>] [--json]`;
|
|
2983
|
+
if (subcommand === "token") return printToken(argv, deps, usage);
|
|
2984
|
+
return { exitCode: EXIT_USAGE, error: subcommand ? `Unknown: auth ${subcommand}.
|
|
2985
|
+
${usage}` : usage };
|
|
2986
|
+
}
|
|
2987
|
+
async function printToken(argv, deps, usage) {
|
|
2988
|
+
const io = deps.io ?? processIo;
|
|
2989
|
+
const env = deps.env ?? process.env;
|
|
2990
|
+
const brand = cliBrand(env);
|
|
2991
|
+
const parsed = parseArgs(argv, { values: ["--scope", "--workspace"], booleans: ["--json"] });
|
|
2992
|
+
if (!parsed.ok) return { exitCode: EXIT_USAGE, error: `${parsed.error}
|
|
2993
|
+
${usage}` };
|
|
2994
|
+
const { args } = parsed;
|
|
2995
|
+
if (args.positionals.length) return { exitCode: EXIT_USAGE, error: usage };
|
|
2996
|
+
const scopeFlag = args.values["--scope"] ? parseScopeFlag(args.values["--scope"]) : null;
|
|
2997
|
+
if (scopeFlag && !scopeFlag.ok) return { exitCode: EXIT_USAGE, error: `${scopeFlag.error}
|
|
2998
|
+
${usage}` };
|
|
2999
|
+
const config = loadConfig(env);
|
|
3000
|
+
const picked = args.values["--workspace"] ? findSavedWorkspace(config, args.values["--workspace"]) : currentSavedWorkspace(config, env);
|
|
3001
|
+
if (!picked.ok) {
|
|
3002
|
+
const reason = "error" in picked ? picked.error : `Not signed in to "${args.values["--workspace"]}" on this computer. Run ${brand.bin} login --workspace "${args.values["--workspace"]}".`;
|
|
3003
|
+
return { exitCode: EXIT_FAILED, error: reason };
|
|
3004
|
+
}
|
|
3005
|
+
const workspace = picked.workspace;
|
|
3006
|
+
if (!workspace.oauth) {
|
|
3007
|
+
const envNote = envApiKey(env) ? ` ${brand.keyEnvVar} is an API key already; use it as the Bearer token directly.` : "";
|
|
3008
|
+
return {
|
|
3009
|
+
exitCode: EXIT_FAILED,
|
|
3010
|
+
error: `The saved sign-in for ${workspace.name} is an API key from an older ${brand.bin} login, not OAuth. Run ${brand.bin} login to switch to tokens.${envNote}`
|
|
3011
|
+
};
|
|
3012
|
+
}
|
|
3013
|
+
try {
|
|
3014
|
+
const apiUrl = trimUrl(workspace.apiUrl) || defaultApiUrl(env);
|
|
3015
|
+
const result = await accessTokenFor(workspace, apiUrl, {
|
|
3016
|
+
scope: scopeFlag?.ok ? scopeFlag.scope : null,
|
|
3017
|
+
env,
|
|
3018
|
+
now: deps.now
|
|
3019
|
+
});
|
|
3020
|
+
if (!result.ok) return { exitCode: EXIT_FAILED, error: result.error };
|
|
3021
|
+
if (args.booleans["--json"]) {
|
|
3022
|
+
printJson(io, { access_token: result.token, token_type: "Bearer", scope: result.scope, workspace: { id: workspace.id, name: workspace.name } });
|
|
3023
|
+
} else {
|
|
3024
|
+
io.out(result.token);
|
|
3025
|
+
}
|
|
3026
|
+
return { exitCode: EXIT_OK };
|
|
3027
|
+
} catch (error) {
|
|
3028
|
+
if (error instanceof CliRequestError) return { exitCode: EXIT_FAILED, error: error.message };
|
|
3029
|
+
throw error;
|
|
3030
|
+
}
|
|
3031
|
+
}
|
|
3032
|
+
|
|
3033
|
+
// src/commands/workspaces.ts
|
|
3034
|
+
async function runWorkspacesCommand(subcommand, argv, deps = {}) {
|
|
3035
|
+
const env = deps.env ?? process.env;
|
|
3036
|
+
const brand = cliBrand(env);
|
|
3037
|
+
const usage = `Usage:
|
|
3038
|
+
${brand.bin} workspaces list
|
|
3039
|
+
${brand.bin} workspaces use <name|id>`;
|
|
3040
|
+
if (subcommand === "list" || subcommand === "ls") return listWorkspaces(argv, deps);
|
|
3041
|
+
if (subcommand === "use" || subcommand === "switch") return switchWorkspace(argv, deps);
|
|
3042
|
+
return { exitCode: EXIT_USAGE, error: subcommand ? `Unknown: workspaces ${subcommand}.
|
|
3043
|
+
${usage}` : usage };
|
|
3044
|
+
}
|
|
3045
|
+
async function listWorkspaces(argv, deps) {
|
|
3046
|
+
const io = deps.io ?? processIo;
|
|
3047
|
+
const env = deps.env ?? process.env;
|
|
3048
|
+
const brand = cliBrand(env);
|
|
3049
|
+
const parsed = parseArgs(argv, { booleans: ["--json"] });
|
|
3050
|
+
if (!parsed.ok) return { exitCode: EXIT_USAGE, error: parsed.error };
|
|
3051
|
+
if (parsed.args.positionals.length) return { exitCode: EXIT_USAGE, error: `${brand.bin} workspaces list takes no arguments.` };
|
|
3052
|
+
const config = loadConfig(env);
|
|
3053
|
+
const all = savedWorkspaces(config);
|
|
3054
|
+
const current = currentSavedWorkspace(config, env);
|
|
3055
|
+
const currentId = current.ok ? current.workspace.id : null;
|
|
3056
|
+
const envNote = envApiKey(env) ? "An API key is set in this environment; commands use it instead of these until you unset it." : null;
|
|
3057
|
+
const addHint = `To add a workspace: ${brand.bin} login --workspace "<name>", and pick it in the browser.`;
|
|
3058
|
+
if (wantsJson(parsed.args, io)) {
|
|
3059
|
+
printJson(io, {
|
|
3060
|
+
workspaces: all.map((workspace) => ({
|
|
3061
|
+
id: workspace.id,
|
|
3062
|
+
name: workspace.name,
|
|
3063
|
+
current: workspace.id === currentId,
|
|
3064
|
+
email: workspace.email ?? null,
|
|
3065
|
+
...workspace.apiUrl && workspace.apiUrl !== defaultApiUrl(env) ? { api_url: workspace.apiUrl } : {}
|
|
3066
|
+
})),
|
|
3067
|
+
current: currentId,
|
|
3068
|
+
...current.ok || current.reason === "none" ? {} : { next: current.error },
|
|
3069
|
+
config_path: configPath(env),
|
|
3070
|
+
...envNote ? { note: envNote } : {}
|
|
3071
|
+
});
|
|
3072
|
+
return { exitCode: EXIT_OK };
|
|
3073
|
+
}
|
|
3074
|
+
if (!all.length) {
|
|
3075
|
+
io.out(`No workspaces signed in on this computer. Run: ${brand.bin} login${envNote ? `
|
|
3076
|
+
${envNote}` : ""}`);
|
|
3077
|
+
return { exitCode: EXIT_OK };
|
|
3078
|
+
}
|
|
3079
|
+
const lines = all.map(
|
|
3080
|
+
(workspace) => `${workspace.id === currentId ? "*" : " "} ${workspace.name} (${workspace.id})${workspace.email ? ` ${workspace.email}` : ""}${workspace.apiUrl && workspace.apiUrl !== defaultApiUrl(env) ? ` ${workspace.apiUrl}` : ""}`
|
|
3081
|
+
);
|
|
3082
|
+
if (!current.ok) lines.push("", current.error);
|
|
3083
|
+
lines.push("", addHint);
|
|
3084
|
+
if (envNote) lines.push(envNote);
|
|
3085
|
+
io.out(lines.join("\n"));
|
|
3086
|
+
return { exitCode: EXIT_OK };
|
|
3087
|
+
}
|
|
3088
|
+
async function switchWorkspace(argv, deps) {
|
|
3089
|
+
const io = deps.io ?? processIo;
|
|
3090
|
+
const env = deps.env ?? process.env;
|
|
3091
|
+
const brand = cliBrand(env);
|
|
3092
|
+
const parsed = parseArgs(argv, { booleans: ["--json"] });
|
|
3093
|
+
if (!parsed.ok) return { exitCode: EXIT_USAGE, error: parsed.error };
|
|
3094
|
+
const wanted = parsed.args.positionals.join(" ").trim();
|
|
3095
|
+
if (!wanted) return { exitCode: EXIT_USAGE, error: `Name the workspace: ${brand.bin} workspaces use <name|id> (see ${brand.bin} workspaces list).` };
|
|
3096
|
+
const config = loadConfig(env);
|
|
3097
|
+
const match = findSavedWorkspace(config, wanted);
|
|
3098
|
+
if (!match.ok) {
|
|
3099
|
+
const saved = savedWorkspaces(config);
|
|
3100
|
+
return {
|
|
3101
|
+
exitCode: EXIT_FAILED,
|
|
3102
|
+
error: match.reason === "ambiguous" ? `More than one saved workspace is called "${wanted}". Use its id: ${match.matches.map((w) => w.id).join(", ")}.` : `Not signed in to "${wanted}" on this computer.${saved.length ? ` Signed in: ${saved.map((w) => `"${w.name}"`).join(", ")}.` : ""} To add it: ${brand.bin} login --workspace "${wanted}".`
|
|
3103
|
+
};
|
|
3104
|
+
}
|
|
3105
|
+
updateConfig((current) => ({ ...current, current: match.workspace.id }), env);
|
|
3106
|
+
if (wantsJson(parsed.args, io)) {
|
|
3107
|
+
printJson(io, { current: { id: match.workspace.id, name: match.workspace.name } });
|
|
3108
|
+
} else {
|
|
3109
|
+
io.out(`Now working in ${match.workspace.name}.`);
|
|
3110
|
+
}
|
|
3111
|
+
return { exitCode: EXIT_OK };
|
|
3112
|
+
}
|
|
3113
|
+
|
|
3114
|
+
// src/cli.ts
|
|
3115
|
+
function printUsage() {
|
|
3116
|
+
console.error("Usage:");
|
|
3117
|
+
console.error(" groundmail login [--scope <scopes>] [--workspace <name>] sign in through the browser (OAuth; no key to copy)");
|
|
3118
|
+
console.error(" groundmail logout [--workspace <name>|--all] revokes the sign-in server-side");
|
|
3119
|
+
console.error(" groundmail whoami");
|
|
3120
|
+
console.error(" groundmail auth token [--scope <scopes>] print a short-lived access token (for agents and scripts)");
|
|
3121
|
+
console.error(" groundmail workspaces list");
|
|
3122
|
+
console.error(" groundmail workspaces use <name|id>");
|
|
3123
|
+
console.error(" groundmail domains add example.com");
|
|
3124
|
+
console.error(" groundmail domains apply example.com --provider cloudflare");
|
|
3125
|
+
console.error(" groundmail domains apply example.com --provider vercel");
|
|
3126
|
+
console.error(" groundmail domains apply example.com --provider godaddy");
|
|
3127
|
+
console.error(" groundmail domains list");
|
|
3128
|
+
console.error(" groundmail domains get example.com");
|
|
3129
|
+
console.error(" groundmail domains update example.com --inbound true");
|
|
3130
|
+
console.error(" groundmail domains verify example.com");
|
|
3131
|
+
console.error(" groundmail domains delete example.com");
|
|
3132
|
+
console.error(" groundmail received-emails list [--domain inbound.example.com]");
|
|
3133
|
+
console.error(" groundmail received-emails get inb_...");
|
|
3134
|
+
console.error(' groundmail emails send --from you@domain.com --to user@example.com --subject "Hello" --html "<p>Hi</p>" [--category transactional|product|newsletter] [--unsubscribe]');
|
|
3135
|
+
console.error(" groundmail emails batch --file ./emails.json");
|
|
3136
|
+
console.error(" groundmail emails list [--limit 25] [--cursor \u2026]");
|
|
3137
|
+
console.error(" groundmail emails insights [--window 7d|30d]");
|
|
3138
|
+
console.error(" groundmail emails get --id msg_\u2026");
|
|
3139
|
+
console.error(" groundmail emails cancel --id msg_\u2026");
|
|
3140
|
+
console.error(" groundmail emails resend --id msg_\u2026");
|
|
3141
|
+
console.error(` groundmail events send --name user.created --email user@example.com --data '{"name":"Ada"}'`);
|
|
3142
|
+
console.error(" groundmail automations list");
|
|
3143
|
+
console.error(" groundmail automations create --file ./welcome.json");
|
|
3144
|
+
console.error(` groundmail automations create --name Welcome --trigger user.created --steps '[{"type":"send_email","email":{...}}]'`);
|
|
3145
|
+
console.error(" groundmail automations create --file ./otp-sms.json");
|
|
3146
|
+
console.error(" groundmail automations get --id <automation-id>");
|
|
3147
|
+
console.error(" groundmail automations update --id <automation-id> --file ./welcome.json");
|
|
3148
|
+
console.error(" groundmail automations activate --id <automation-id>");
|
|
3149
|
+
console.error(" groundmail automations pause --id <automation-id>");
|
|
3150
|
+
console.error(" groundmail automations delete --id <automation-id>");
|
|
3151
|
+
console.error(" groundmail webhooks list");
|
|
3152
|
+
console.error(" groundmail webhooks create --url https://yourapp.com/hooks");
|
|
3153
|
+
console.error(" groundmail webhooks get --id wh_\u2026");
|
|
3154
|
+
console.error(" groundmail webhooks update --id wh_\u2026 [--url \u2026] [--event email.sent] [--enabled true|false]");
|
|
3155
|
+
console.error(" groundmail webhooks delete --id <webhook-id>");
|
|
3156
|
+
console.error(" groundmail suppressions list");
|
|
3157
|
+
console.error(" groundmail suppressions add --email user@example.com [--reason \u2026]");
|
|
3158
|
+
console.error(" groundmail suppressions remove --id <id> | --email user@example.com");
|
|
3159
|
+
console.error(" groundmail templates list [--status draft|published]");
|
|
3160
|
+
console.error(" groundmail templates starters");
|
|
3161
|
+
console.error(" groundmail templates from-starter password-reset");
|
|
3162
|
+
console.error(' groundmail templates create --name Welcome --subject "Hi" --html "<p>Hi</p>" [--category transactional|product|newsletter] [--unsubscribe-enabled]');
|
|
3163
|
+
console.error(" groundmail templates get --id tpl_\u2026");
|
|
3164
|
+
console.error(" groundmail templates update --id tpl_\u2026 [--name \u2026] [--subject \u2026] [--html \u2026] [--text \u2026] [--set-alias \u2026] [--category transactional|product|newsletter] [--unsubscribe-enabled|--no-unsubscribe-enabled]");
|
|
3165
|
+
console.error(" groundmail templates publish --id tpl_\u2026");
|
|
3166
|
+
console.error(
|
|
3167
|
+
` groundmail templates push ./emails/welcome.tsx --alias welcome [--subject "Welcome"] [--props '{"name":"Ada"}'] [--publish]`
|
|
3168
|
+
);
|
|
3169
|
+
console.error(" groundmail templates versions --id tpl_\u2026");
|
|
3170
|
+
console.error(" groundmail templates restore --id tpl_\u2026 --version 2");
|
|
3171
|
+
console.error(" groundmail templates test-send --id tpl_\u2026 --from you@domain.com --to you@domain.com");
|
|
3172
|
+
console.error(' groundmail templates duplicate --id tpl_\u2026 [--name "Copy"]');
|
|
3173
|
+
console.error(" groundmail templates delete --id tpl_\u2026");
|
|
3174
|
+
console.error(" groundmail keys list");
|
|
3175
|
+
console.error(" groundmail keys create [--name Default] [--scopes emails:send,domains:read] [--allowed-domains example.com]");
|
|
3176
|
+
console.error(" groundmail keys delete --id <api-key-id>");
|
|
3177
|
+
console.error(" groundmail integrations twilio status");
|
|
3178
|
+
console.error(" groundmail integrations twilio connect --account-sid AC\u2026 --auth-token \u2026 --from +15551234567");
|
|
3179
|
+
console.error(" groundmail integrations twilio disconnect");
|
|
3180
|
+
console.error("");
|
|
3181
|
+
console.error("Environment:");
|
|
3182
|
+
console.error(" GROUNDMAIL_API_KEY=gm_\u2026 optional (CI): wins over the `groundmail login` sign-in");
|
|
3183
|
+
console.error(" GROUNDMAIL_NO_KEYCHAIN=1 keep the refresh token in the config file (0600) instead of the OS keychain");
|
|
3184
|
+
console.error("");
|
|
3185
|
+
console.error("Scopes: full, emails:send, emails:read, domains:read, domains:write, webhooks:write, templates:write,");
|
|
3186
|
+
console.error(" suppressions:write, account:read");
|
|
3187
|
+
console.error(" TWILIO_ACCOUNT_SID=AC\u2026 TWILIO_AUTH_TOKEN=\u2026 TWILIO_DEFAULT_FROM=+1\u2026 (integrations twilio connect)");
|
|
3188
|
+
console.error(" CLOUDFLARE_API_TOKEN=\u2026 (optional local apply; else uses dashboard Integrations)");
|
|
3189
|
+
console.error(" VERCEL_API_TOKEN=\u2026 VERCEL_TEAM_ID=\u2026 (optional local apply; else uses dashboard Integrations)");
|
|
3190
|
+
console.error(" GODADDY_API_KEY=\u2026 GODADDY_API_SECRET=\u2026 (domains apply --provider godaddy)");
|
|
3191
|
+
}
|
|
3192
|
+
async function finish(result) {
|
|
3193
|
+
if (result.error) console.error(result.error);
|
|
3194
|
+
process.exit(result.exitCode);
|
|
3195
|
+
}
|
|
3196
|
+
async function main() {
|
|
3197
|
+
const [, , command, subcommand] = process.argv;
|
|
3198
|
+
const args = process.argv.slice(3);
|
|
3199
|
+
if (command === "help" || command === "--help" || command === "-h") {
|
|
3200
|
+
printUsage();
|
|
3201
|
+
process.exit(0);
|
|
3202
|
+
}
|
|
3203
|
+
if (command === "login") await finish(await runLoginCommand(args));
|
|
3204
|
+
if (command === "logout") await finish(await runLogoutCommand(args));
|
|
3205
|
+
if (command === "whoami") await finish(await runWhoamiCommand(args));
|
|
3206
|
+
if (command === "workspaces" || command === "workspace") await finish(await runWorkspacesCommand(subcommand, args.slice(1)));
|
|
3207
|
+
if (command === "auth") await finish(await runAuthCommand(subcommand, args.slice(1)));
|
|
3208
|
+
const session = await applySavedSession(process.argv.slice(4));
|
|
3209
|
+
if (session.notice) console.error(session.notice);
|
|
3210
|
+
const rest = session.argv;
|
|
3211
|
+
if (command === "domains") {
|
|
3212
|
+
if (subcommand === "add" || subcommand === "create") {
|
|
3213
|
+
const result = await runAddCommand(rest);
|
|
3214
|
+
if (result.error) console.error(result.error);
|
|
3215
|
+
process.exit(result.exitCode);
|
|
3216
|
+
}
|
|
3217
|
+
if (subcommand === "apply") {
|
|
3218
|
+
const result = await runApplyCommand(rest);
|
|
3219
|
+
if (result.error) console.error(result.error);
|
|
3220
|
+
process.exit(result.exitCode);
|
|
3221
|
+
}
|
|
3222
|
+
if (subcommand === "get") {
|
|
3223
|
+
const result = await runGetCommand3(rest);
|
|
3224
|
+
if (result.error) console.error(result.error);
|
|
3225
|
+
process.exit(result.exitCode);
|
|
3226
|
+
}
|
|
3227
|
+
if (subcommand === "list") {
|
|
3228
|
+
const result = await runListCommand3(rest);
|
|
3229
|
+
if (result.error) console.error(result.error);
|
|
3230
|
+
process.exit(result.exitCode);
|
|
3231
|
+
}
|
|
3232
|
+
if (subcommand === "delete" || subcommand === "remove") {
|
|
3233
|
+
const result = await runDeleteCommand2(rest);
|
|
3234
|
+
if (result.error) console.error(result.error);
|
|
3235
|
+
process.exit(result.exitCode);
|
|
3236
|
+
}
|
|
3237
|
+
if (subcommand === "update") {
|
|
3238
|
+
const result = await runUpdateCommand2(rest);
|
|
3239
|
+
if (result.error) console.error(result.error);
|
|
3240
|
+
process.exit(result.exitCode);
|
|
3241
|
+
}
|
|
3242
|
+
if (subcommand === "verify") {
|
|
3243
|
+
const result = await runVerifyCommand(rest);
|
|
3244
|
+
if (result.error) console.error(result.error);
|
|
3245
|
+
process.exit(result.exitCode);
|
|
3246
|
+
}
|
|
3247
|
+
}
|
|
3248
|
+
if (command === "received-emails") {
|
|
3249
|
+
if (subcommand === "list") {
|
|
3250
|
+
const result = await runListCommand4(rest);
|
|
3251
|
+
if (result.error) console.error(result.error);
|
|
3252
|
+
process.exit(result.exitCode);
|
|
3253
|
+
}
|
|
3254
|
+
if (subcommand === "get") {
|
|
3255
|
+
const result = await runGetCommand4(rest);
|
|
3256
|
+
if (result.error) console.error(result.error);
|
|
3257
|
+
process.exit(result.exitCode);
|
|
3258
|
+
}
|
|
3259
|
+
}
|
|
3260
|
+
if (command === "emails") {
|
|
3261
|
+
if (subcommand === "send") {
|
|
3262
|
+
const result = await runSendCommandWithClient(rest);
|
|
3263
|
+
process.exit(result.exitCode);
|
|
3264
|
+
}
|
|
3265
|
+
if (subcommand === "batch") {
|
|
3266
|
+
const result = await runBatchCommandWithClient(rest);
|
|
3267
|
+
if (result.error) console.error(result.error);
|
|
3268
|
+
process.exit(result.exitCode);
|
|
3269
|
+
}
|
|
3270
|
+
if (subcommand === "cancel") {
|
|
3271
|
+
const result = await runCancelCommandWithClient(rest);
|
|
3272
|
+
if (result.error) console.error(result.error);
|
|
3273
|
+
process.exit(result.exitCode);
|
|
3274
|
+
}
|
|
3275
|
+
if (subcommand === "resend") {
|
|
3276
|
+
const result = await runResendCommandWithClient(rest);
|
|
3277
|
+
if (result.error) console.error(result.error);
|
|
3278
|
+
process.exit(result.exitCode);
|
|
3279
|
+
}
|
|
3280
|
+
if (subcommand === "list") {
|
|
3281
|
+
const result = await runListCommandWithClient(rest);
|
|
3282
|
+
if (result.error) console.error(result.error);
|
|
3283
|
+
process.exit(result.exitCode);
|
|
3284
|
+
}
|
|
3285
|
+
if (subcommand === "insights") {
|
|
3286
|
+
const result = await runInsightsCommandWithClient(rest);
|
|
3287
|
+
if (result.error) console.error(result.error);
|
|
3288
|
+
process.exit(result.exitCode);
|
|
3289
|
+
}
|
|
3290
|
+
if (subcommand === "get") {
|
|
3291
|
+
const result = await runGetCommandWithClient(rest);
|
|
3292
|
+
if (result.error) console.error(result.error);
|
|
3293
|
+
process.exit(result.exitCode);
|
|
3294
|
+
}
|
|
3295
|
+
}
|
|
3296
|
+
if (command === "events") {
|
|
3297
|
+
if (subcommand === "send" || subcommand === "trigger") {
|
|
3298
|
+
const result = await runSendEventCommand(rest);
|
|
3299
|
+
if (result.error) console.error(result.error);
|
|
3300
|
+
process.exit(result.exitCode);
|
|
3301
|
+
}
|
|
3302
|
+
}
|
|
3303
|
+
if (command === "automations") {
|
|
3304
|
+
if (subcommand === "list") {
|
|
3305
|
+
const result = await runListCommand2(rest);
|
|
3306
|
+
if (result.error) console.error(result.error);
|
|
3307
|
+
process.exit(result.exitCode);
|
|
3308
|
+
}
|
|
3309
|
+
if (subcommand === "get") {
|
|
3310
|
+
const result = await runGetCommand2(rest);
|
|
3311
|
+
if (result.error) console.error(result.error);
|
|
3312
|
+
process.exit(result.exitCode);
|
|
3313
|
+
}
|
|
3314
|
+
if (subcommand === "create" || subcommand === "add") {
|
|
3315
|
+
const result = await runCreateCommand(rest);
|
|
3316
|
+
if (result.error) console.error(result.error);
|
|
3317
|
+
process.exit(result.exitCode);
|
|
3318
|
+
}
|
|
3319
|
+
if (subcommand === "update") {
|
|
3320
|
+
const result = await runUpdateCommand(rest);
|
|
3321
|
+
if (result.error) console.error(result.error);
|
|
3322
|
+
process.exit(result.exitCode);
|
|
3323
|
+
}
|
|
3324
|
+
if (subcommand === "activate") {
|
|
3325
|
+
const result = await runActivateCommand(rest);
|
|
3326
|
+
if (result.error) console.error(result.error);
|
|
3327
|
+
process.exit(result.exitCode);
|
|
3328
|
+
}
|
|
3329
|
+
if (subcommand === "pause") {
|
|
3330
|
+
const result = await runPauseCommand(rest);
|
|
3331
|
+
if (result.error) console.error(result.error);
|
|
3332
|
+
process.exit(result.exitCode);
|
|
3333
|
+
}
|
|
3334
|
+
if (subcommand === "delete" || subcommand === "remove") {
|
|
3335
|
+
const result = await runDeleteCommand(rest);
|
|
3336
|
+
if (result.error) console.error(result.error);
|
|
3337
|
+
process.exit(result.exitCode);
|
|
3338
|
+
}
|
|
3339
|
+
}
|
|
3340
|
+
if (command === "webhooks") {
|
|
3341
|
+
if (subcommand === "list") {
|
|
3342
|
+
const result = await runListCommand5(rest);
|
|
3343
|
+
if (result.error) console.error(result.error);
|
|
3344
|
+
process.exit(result.exitCode);
|
|
3345
|
+
}
|
|
3346
|
+
if (subcommand === "get") {
|
|
3347
|
+
const result = await runGetCommand5(rest);
|
|
3348
|
+
if (result.error) console.error(result.error);
|
|
3349
|
+
process.exit(result.exitCode);
|
|
3350
|
+
}
|
|
3351
|
+
if (subcommand === "create") {
|
|
3352
|
+
const result = await runCreateCommand2(rest);
|
|
3353
|
+
if (result.error) console.error(result.error);
|
|
3354
|
+
process.exit(result.exitCode);
|
|
3355
|
+
}
|
|
3356
|
+
if (subcommand === "update") {
|
|
3357
|
+
const result = await runUpdateCommand3(rest);
|
|
3358
|
+
if (result.error) console.error(result.error);
|
|
3359
|
+
process.exit(result.exitCode);
|
|
3360
|
+
}
|
|
3361
|
+
if (subcommand === "delete") {
|
|
3362
|
+
const result = await runDeleteCommand3(rest);
|
|
3363
|
+
if (result.error) console.error(result.error);
|
|
3364
|
+
process.exit(result.exitCode);
|
|
3365
|
+
}
|
|
3366
|
+
}
|
|
3367
|
+
if (command === "suppressions") {
|
|
3368
|
+
if (subcommand === "list") {
|
|
3369
|
+
const result = await runListCommand6(rest);
|
|
3370
|
+
if (result.error) console.error(result.error);
|
|
3371
|
+
process.exit(result.exitCode);
|
|
3372
|
+
}
|
|
3373
|
+
if (subcommand === "add" || subcommand === "create") {
|
|
3374
|
+
const result = await runAddCommand2(rest);
|
|
3375
|
+
if (result.error) console.error(result.error);
|
|
3376
|
+
process.exit(result.exitCode);
|
|
3377
|
+
}
|
|
3378
|
+
if (subcommand === "remove" || subcommand === "delete") {
|
|
3379
|
+
const result = await runRemoveCommand(rest);
|
|
3380
|
+
if (result.error) console.error(result.error);
|
|
3381
|
+
process.exit(result.exitCode);
|
|
3382
|
+
}
|
|
3383
|
+
}
|
|
3384
|
+
if (command === "templates") {
|
|
3385
|
+
if (subcommand === "list") {
|
|
3386
|
+
const result = await runListCommand7(rest);
|
|
3387
|
+
if (result.error) console.error(result.error);
|
|
3388
|
+
process.exit(result.exitCode);
|
|
3389
|
+
}
|
|
3390
|
+
if (subcommand === "starters") {
|
|
3391
|
+
const result = await runListStartersCommand(rest);
|
|
3392
|
+
if (result.error) console.error(result.error);
|
|
3393
|
+
process.exit(result.exitCode);
|
|
3394
|
+
}
|
|
3395
|
+
if (subcommand === "from-starter") {
|
|
3396
|
+
const result = await runFromStarterCommand(rest);
|
|
3397
|
+
if (result.error) console.error(result.error);
|
|
3398
|
+
process.exit(result.exitCode);
|
|
3399
|
+
}
|
|
3400
|
+
if (subcommand === "create" || subcommand === "add") {
|
|
3401
|
+
const result = await runCreateCommand3(rest);
|
|
3402
|
+
if (result.error) console.error(result.error);
|
|
3403
|
+
process.exit(result.exitCode);
|
|
3404
|
+
}
|
|
3405
|
+
if (subcommand === "get") {
|
|
3406
|
+
const result = await runGetCommand6(rest);
|
|
3407
|
+
if (result.error) console.error(result.error);
|
|
3408
|
+
process.exit(result.exitCode);
|
|
3409
|
+
}
|
|
3410
|
+
if (subcommand === "update") {
|
|
3411
|
+
const result = await runUpdateCommand4(rest);
|
|
3412
|
+
if (result.error) console.error(result.error);
|
|
3413
|
+
process.exit(result.exitCode);
|
|
3414
|
+
}
|
|
3415
|
+
if (subcommand === "publish") {
|
|
3416
|
+
const result = await runPublishCommand(rest);
|
|
3417
|
+
if (result.error) console.error(result.error);
|
|
3418
|
+
process.exit(result.exitCode);
|
|
3419
|
+
}
|
|
3420
|
+
if (subcommand === "push") {
|
|
3421
|
+
const result = await runPushCommand(rest);
|
|
3422
|
+
if (result.error) console.error(result.error);
|
|
3423
|
+
process.exit(result.exitCode);
|
|
3424
|
+
}
|
|
3425
|
+
if (subcommand === "versions") {
|
|
3426
|
+
const result = await runVersionsCommand(rest);
|
|
3427
|
+
if (result.error) console.error(result.error);
|
|
3428
|
+
process.exit(result.exitCode);
|
|
3429
|
+
}
|
|
3430
|
+
if (subcommand === "restore") {
|
|
3431
|
+
const result = await runRestoreCommand(rest);
|
|
3432
|
+
if (result.error) console.error(result.error);
|
|
3433
|
+
process.exit(result.exitCode);
|
|
3434
|
+
}
|
|
3435
|
+
if (subcommand === "test-send") {
|
|
3436
|
+
const result = await runTestSendCommand(rest);
|
|
3437
|
+
if (result.error) console.error(result.error);
|
|
3438
|
+
process.exit(result.exitCode);
|
|
3439
|
+
}
|
|
3440
|
+
if (subcommand === "duplicate") {
|
|
3441
|
+
const result = await runDuplicateCommand(rest);
|
|
3442
|
+
if (result.error) console.error(result.error);
|
|
3443
|
+
process.exit(result.exitCode);
|
|
3444
|
+
}
|
|
3445
|
+
if (subcommand === "delete" || subcommand === "remove") {
|
|
3446
|
+
const result = await runDeleteCommand4(rest);
|
|
3447
|
+
if (result.error) console.error(result.error);
|
|
3448
|
+
process.exit(result.exitCode);
|
|
3449
|
+
}
|
|
3450
|
+
}
|
|
3451
|
+
if (command === "integrations") {
|
|
3452
|
+
if (subcommand === "twilio") {
|
|
3453
|
+
const action = rest[0];
|
|
3454
|
+
const actionArgs = rest.slice(1);
|
|
3455
|
+
if (action === "status" || action === "get") {
|
|
3456
|
+
const result = await runTwilioStatusCommand(actionArgs);
|
|
3457
|
+
if (result.error) console.error(result.error);
|
|
3458
|
+
process.exit(result.exitCode);
|
|
3459
|
+
}
|
|
3460
|
+
if (action === "connect" || action === "set") {
|
|
3461
|
+
const result = await runTwilioConnectCommand(actionArgs);
|
|
3462
|
+
if (result.error) console.error(result.error);
|
|
3463
|
+
process.exit(result.exitCode);
|
|
3464
|
+
}
|
|
3465
|
+
if (action === "disconnect" || action === "delete" || action === "remove") {
|
|
3466
|
+
const result = await runTwilioDisconnectCommand(actionArgs);
|
|
3467
|
+
if (result.error) console.error(result.error);
|
|
3468
|
+
process.exit(result.exitCode);
|
|
3469
|
+
}
|
|
3470
|
+
}
|
|
3471
|
+
}
|
|
3472
|
+
if (command === "keys") {
|
|
3473
|
+
if (subcommand === "list") {
|
|
3474
|
+
const result = await runListKeysCommandWithClient(rest);
|
|
3475
|
+
if (result.error) console.error(result.error);
|
|
3476
|
+
process.exit(result.exitCode);
|
|
3477
|
+
}
|
|
3478
|
+
if (subcommand === "create") {
|
|
3479
|
+
const result = await runCreateKeyCommandWithClient(rest);
|
|
3480
|
+
if (result.error) console.error(result.error);
|
|
3481
|
+
process.exit(result.exitCode);
|
|
3482
|
+
}
|
|
3483
|
+
if (subcommand === "delete") {
|
|
3484
|
+
const result = await runDeleteKeyCommandWithClient(rest);
|
|
3485
|
+
if (result.error) console.error(result.error);
|
|
3486
|
+
process.exit(result.exitCode);
|
|
3487
|
+
}
|
|
3488
|
+
}
|
|
3489
|
+
printUsage();
|
|
3490
|
+
process.exit(1);
|
|
3491
|
+
}
|
|
3492
|
+
main().catch((error) => {
|
|
3493
|
+
console.error(error instanceof Error ? error.message : String(error));
|
|
3494
|
+
process.exit(1);
|
|
3495
|
+
});
|