@nuntly/cli 1.0.0-alpha.1

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/dist/index.js ADDED
@@ -0,0 +1,1254 @@
1
+ #!/usr/bin/env node
2
+
3
+ // src/index.ts
4
+ import { Command as Command11 } from "@commander-js/extra-typings";
5
+ import pc4 from "picocolors";
6
+
7
+ // src/commands/emails.ts
8
+ import { Command } from "@commander-js/extra-typings";
9
+ import { Nuntly } from "@nuntly/sdk";
10
+
11
+ // src/auth.ts
12
+ import { execSync } from "child_process";
13
+ import {
14
+ chmodSync,
15
+ existsSync,
16
+ mkdirSync,
17
+ readFileSync,
18
+ writeFileSync
19
+ } from "fs";
20
+ import { homedir, platform } from "os";
21
+ import { resolve } from "path";
22
+ import * as p from "@clack/prompts";
23
+ import pc from "picocolors";
24
+ var CONFIG_DIR = resolve(homedir(), ".nuntly");
25
+ var CONFIG_PATH = resolve(CONFIG_DIR, "config.json");
26
+ function loadRawConfig() {
27
+ if (!existsSync(CONFIG_PATH)) return {};
28
+ try {
29
+ return JSON.parse(readFileSync(CONFIG_PATH, "utf-8"));
30
+ } catch {
31
+ return {};
32
+ }
33
+ }
34
+ function resolveProfile(config, profileName) {
35
+ if (!config.profiles && config.apiKey) {
36
+ return {
37
+ apiKey: config.apiKey,
38
+ baseUrl: config.baseUrl,
39
+ outputFormat: config.outputFormat
40
+ };
41
+ }
42
+ const name = profileName ?? config.defaultProfile ?? "default";
43
+ return config.profiles?.[name] ?? {};
44
+ }
45
+ function restrictFilePermissions(filePath) {
46
+ if (platform() === "win32") {
47
+ try {
48
+ execSync(
49
+ `icacls "${filePath}" /inheritance:r /grant:r "%USERNAME%:(R,W)" /q`,
50
+ { stdio: "ignore" }
51
+ );
52
+ } catch {
53
+ }
54
+ } else {
55
+ chmodSync(filePath, 384);
56
+ }
57
+ }
58
+ function saveProfile(profileName, profile) {
59
+ mkdirSync(CONFIG_DIR, { recursive: true });
60
+ const config = loadRawConfig();
61
+ if (!config.profiles) config.profiles = {};
62
+ config.profiles[profileName] = {
63
+ ...config.profiles[profileName],
64
+ ...profile
65
+ };
66
+ if (!config.defaultProfile) config.defaultProfile = profileName;
67
+ delete config.apiKey;
68
+ delete config.baseUrl;
69
+ delete config.outputFormat;
70
+ writeFileSync(CONFIG_PATH, JSON.stringify(config, null, 2) + "\n", {
71
+ mode: 384
72
+ });
73
+ restrictFilePermissions(CONFIG_PATH);
74
+ }
75
+ function listProfiles() {
76
+ const config = loadRawConfig();
77
+ return Object.keys(config.profiles ?? {});
78
+ }
79
+ function getGlobalProfile() {
80
+ const idx = process.argv.indexOf("--profile");
81
+ if (idx >= 0 && idx + 1 < process.argv.length) return process.argv[idx + 1];
82
+ return process.env["NUNTLY_PROFILE"] ?? void 0;
83
+ }
84
+ function resolveApiKey(flagKey, profileName) {
85
+ const config = loadRawConfig();
86
+ const profile = resolveProfile(config, profileName ?? getGlobalProfile());
87
+ const key = flagKey ?? process.env["NUNTLY_API_KEY"] ?? profile.apiKey;
88
+ if (!key) {
89
+ console.error(pc.red("Error: API key not found."));
90
+ console.error(
91
+ `Run ${pc.bold("nuntly login")}, set ${pc.bold("NUNTLY_API_KEY")} env var, or use ${pc.bold("--api-key")}`
92
+ );
93
+ if (profileName) console.error(pc.dim(`Profile: ${profileName}`));
94
+ process.exit(1);
95
+ }
96
+ if (flagKey) {
97
+ console.error(
98
+ pc.yellow(
99
+ "Warning: passing API key via flag is less secure than env var."
100
+ )
101
+ );
102
+ }
103
+ return key;
104
+ }
105
+ function resolveBaseUrl(flagUrl, profileName) {
106
+ const config = loadRawConfig();
107
+ const profile = resolveProfile(config, profileName ?? getGlobalProfile());
108
+ return flagUrl ?? process.env["NUNTLY_BASE_URL"] ?? profile.baseUrl;
109
+ }
110
+ async function login(profileName) {
111
+ const name = profileName ?? "default";
112
+ p.intro(
113
+ pc.bold(`Nuntly Login${name !== "default" ? ` (profile: ${name})` : ""}`)
114
+ );
115
+ const apiKey = await p.text({
116
+ message: "Enter your API key",
117
+ placeholder: "nuntly_sk_...",
118
+ validate: (value) => {
119
+ if (!value || value.trim().length === 0) return "API key is required";
120
+ return void 0;
121
+ }
122
+ });
123
+ if (p.isCancel(apiKey)) {
124
+ p.cancel("Login cancelled.");
125
+ process.exit(0);
126
+ }
127
+ const baseUrl = await p.text({
128
+ message: "API base URL (leave empty for default)",
129
+ placeholder: "https://api.nuntly.com"
130
+ });
131
+ if (p.isCancel(baseUrl)) {
132
+ p.cancel("Login cancelled.");
133
+ process.exit(0);
134
+ }
135
+ const profile = { apiKey: apiKey.trim() };
136
+ if (baseUrl && baseUrl.trim()) profile.baseUrl = baseUrl.trim();
137
+ saveProfile(name, profile);
138
+ p.note(`Profile "${name}" saved to ${pc.dim(CONFIG_PATH)}`, "Saved");
139
+ p.outro(pc.green("Logged in successfully."));
140
+ }
141
+ async function confirmDelete(resource, id) {
142
+ const result = await p.confirm({
143
+ message: `Delete ${resource} ${pc.bold(id)}?`
144
+ });
145
+ if (p.isCancel(result)) return false;
146
+ return result;
147
+ }
148
+
149
+ // src/version.ts
150
+ var CLI_VERSION = "0.0.1";
151
+
152
+ // src/output.ts
153
+ import pc2 from "picocolors";
154
+ function extractItems(data) {
155
+ if (data && typeof data === "object" && "data" in data && Array.isArray(data.data))
156
+ return data.data;
157
+ if (Array.isArray(data)) return data;
158
+ return null;
159
+ }
160
+ function resolveKeys(items, fields) {
161
+ const allKeys = Object.keys(items[0]);
162
+ if (!fields) return allKeys;
163
+ return fields.filter((f) => allKeys.includes(f));
164
+ }
165
+ function formatOutput(data, format, opts) {
166
+ const fields = opts?.fields;
167
+ const noHeader = opts?.noHeader ?? false;
168
+ if (format === "quiet") {
169
+ if (data && typeof data === "object" && "id" in data) {
170
+ return String(data.id);
171
+ }
172
+ return "";
173
+ }
174
+ if (format === "table") {
175
+ const items = extractItems(data);
176
+ if (items) {
177
+ if (items.length === 0) return pc2.dim("No results.");
178
+ const keys = resolveKeys(items, fields);
179
+ const widths = keys.map(
180
+ (k) => Math.max(
181
+ k.length,
182
+ ...items.map((r) => String(r[k] ?? "").slice(0, 40).length)
183
+ )
184
+ );
185
+ const rows = items.map(
186
+ (r) => keys.map(
187
+ (k, i) => String(r[k] ?? "").slice(0, 40).padEnd(widths[i])
188
+ ).join(" ")
189
+ );
190
+ const parts = [];
191
+ if (!noHeader) {
192
+ const header = keys.map((k, i) => pc2.bold(k.padEnd(widths[i]))).join(" ");
193
+ parts.push(header, pc2.dim("-".repeat(header.length)));
194
+ }
195
+ parts.push(...rows);
196
+ const result = parts.join("\n");
197
+ const nextCursor = data?.nextCursor;
198
+ if (nextCursor)
199
+ return result + "\n" + pc2.dim(`next: --cursor ${nextCursor}`);
200
+ return result;
201
+ }
202
+ return JSON.stringify(data, null, 2);
203
+ }
204
+ if (format === "raw") {
205
+ return JSON.stringify(data);
206
+ }
207
+ if (format === "yaml") {
208
+ return toYaml(data);
209
+ }
210
+ if (format === "csv") {
211
+ const items = extractItems(data);
212
+ const escapeCsv = (v) => {
213
+ const s = typeof v === "object" && v !== null ? JSON.stringify(v) : String(v ?? "");
214
+ return s.includes(",") || s.includes('"') || s.includes("\n") ? `"${s.replace(/"/g, '""')}"` : s;
215
+ };
216
+ if (items && items.length > 0) {
217
+ const keys = resolveKeys(items, fields);
218
+ const rows = items.map(
219
+ (r) => keys.map((k) => escapeCsv(r[k])).join(",")
220
+ );
221
+ if (noHeader) return rows.join("\r\n");
222
+ return [keys.join(","), ...rows].join("\r\n");
223
+ }
224
+ if (data && typeof data === "object") {
225
+ const entries = Object.entries(data);
226
+ if (noHeader)
227
+ return entries.map(([k, v]) => `${k},${escapeCsv(v)}`).join("\r\n");
228
+ return [
229
+ "field,value",
230
+ ...entries.map(([k, v]) => `${k},${escapeCsv(v)}`)
231
+ ].join("\r\n");
232
+ }
233
+ return String(data);
234
+ }
235
+ if (format === "markdown") {
236
+ const items = extractItems(data);
237
+ if (items && items.length > 0) {
238
+ const keys = resolveKeys(items, fields);
239
+ const header = "| " + keys.join(" | ") + " |";
240
+ const sep = "| " + keys.map(() => "---").join(" | ") + " |";
241
+ const rows = items.map(
242
+ (r) => "| " + keys.map((k) => String(r[k] ?? "").slice(0, 60)).join(" | ") + " |"
243
+ );
244
+ if (noHeader) return rows.join("\n");
245
+ return [header, sep, ...rows].join("\n");
246
+ }
247
+ if (data && typeof data === "object") {
248
+ const entries = Object.entries(data);
249
+ const header = "| Field | Value |";
250
+ const sep = "| --- | --- |";
251
+ const rows = entries.map(
252
+ ([k, v]) => `| ${k} | ${typeof v === "object" ? JSON.stringify(v) : String(v)} |`
253
+ );
254
+ if (noHeader) return rows.join("\n");
255
+ return [header, sep, ...rows].join("\n");
256
+ }
257
+ return String(data);
258
+ }
259
+ return JSON.stringify(data, null, 2);
260
+ }
261
+ function toYaml(data, indent = 0) {
262
+ const prefix = " ".repeat(indent);
263
+ if (data === null || data === void 0) return `${prefix}null`;
264
+ if (typeof data === "string")
265
+ return data.includes("\n") ? `${prefix}|\\n${data}` : `${prefix}${data}`;
266
+ if (typeof data === "number" || typeof data === "boolean")
267
+ return `${prefix}${data}`;
268
+ if (Array.isArray(data)) {
269
+ if (data.length === 0) return `${prefix}[]`;
270
+ return data.map((item) => {
271
+ if (typeof item === "object" && item !== null) {
272
+ const inner = toYaml(item, indent + 1).trim();
273
+ return `${prefix}- ${inner.replace(/\n/g, "\n" + prefix + " ")}`;
274
+ }
275
+ return `${prefix}- ${item}`;
276
+ }).join("\n");
277
+ }
278
+ if (typeof data === "object") {
279
+ return Object.entries(data).map(([key, value]) => {
280
+ if (value === null || value === void 0)
281
+ return `${prefix}${key}: null`;
282
+ if (typeof value === "object")
283
+ return `${prefix}${key}:
284
+ ${toYaml(value, indent + 1)}`;
285
+ return `${prefix}${key}: ${value}`;
286
+ }).join("\n");
287
+ }
288
+ return `${prefix}${String(data)}`;
289
+ }
290
+ function printResult(data, opts) {
291
+ let format;
292
+ if (opts.quiet) format = "quiet";
293
+ else if (opts.raw) format = "raw";
294
+ else if (opts.format) format = opts.format;
295
+ else if (!process.stdout.isTTY) format = "json";
296
+ else {
297
+ const isArray = Array.isArray(data) || data && typeof data === "object" && "data" in data && Array.isArray(data.data);
298
+ format = isArray ? "table" : "json";
299
+ }
300
+ const fields = opts.fields?.split(",").map((f) => f.trim());
301
+ const noHeader = opts.header === false;
302
+ const output = formatOutput(data, format, { fields, noHeader });
303
+ if (output) console.log(output);
304
+ }
305
+ function isJsonMode(opts) {
306
+ if (opts?.format === "json" || opts?.raw) return true;
307
+ if (!opts?.format && !opts?.quiet && !process.stdout.isTTY) return true;
308
+ return false;
309
+ }
310
+ function printError(error, opts) {
311
+ const e = error;
312
+ const isApi = !!e?.status;
313
+ if (isJsonMode(opts)) {
314
+ const envelope = {
315
+ error: {
316
+ code: isApi ? `HTTP_${e.status}` : "CLIENT_ERROR",
317
+ message: e?.body?.message ?? e?.message ?? String(error),
318
+ ...e?.requestId && { requestId: e.requestId },
319
+ ...e?.body?.errors && { fields: e.body.errors }
320
+ }
321
+ };
322
+ console.error(JSON.stringify(envelope));
323
+ } else {
324
+ if (isApi) {
325
+ console.error(
326
+ pc2.red(`HTTP ${e.status}: ${e.body?.message ?? e.message}`)
327
+ );
328
+ if (e.requestId) console.error(pc2.dim(`request: ${e.requestId}`));
329
+ if (e.body?.errors) {
330
+ for (const err of e.body.errors) {
331
+ console.error(pc2.yellow(` ${err.field}: ${err.message}`));
332
+ }
333
+ }
334
+ } else {
335
+ console.error(pc2.red(e?.message ?? String(error)));
336
+ }
337
+ }
338
+ process.exit(isApi ? 1 : 2);
339
+ }
340
+
341
+ // src/spinner.ts
342
+ import pc3 from "picocolors";
343
+ var isLegacyWindows = process.platform === "win32" && !process.env["WT_SESSION"] && !process.env["TERM_PROGRAM"];
344
+ var FRAMES = isLegacyWindows ? ["|", "/", "-", "\\"] : [
345
+ "\u280B",
346
+ "\u2819",
347
+ "\u2839",
348
+ "\u2838",
349
+ "\u283C",
350
+ "\u2834",
351
+ "\u2826",
352
+ "\u2827",
353
+ "\u2807",
354
+ "\u280F"
355
+ ];
356
+ var INTERVAL = 80;
357
+ async function withSpinner(message, fn) {
358
+ if (!process.stderr.isTTY) return fn();
359
+ let i = 0;
360
+ const timer = setInterval(() => {
361
+ const frame = FRAMES[i++ % FRAMES.length];
362
+ process.stderr.write(`\r${pc3.cyan(frame)} ${pc3.dim(message)}`);
363
+ }, INTERVAL);
364
+ try {
365
+ return await fn();
366
+ } finally {
367
+ clearInterval(timer);
368
+ process.stderr.write(`\r${" ".repeat(message.length + 4)}\r`);
369
+ }
370
+ }
371
+
372
+ // src/files.ts
373
+ import { readFileSync as readFileSync2, readSync } from "fs";
374
+ function readStdin() {
375
+ const chunks = [];
376
+ const buf = Buffer.alloc(4096);
377
+ try {
378
+ let bytesRead;
379
+ while ((bytesRead = readSync(0, buf, 0, buf.length, null)) > 0) {
380
+ chunks.push(buf.subarray(0, bytesRead));
381
+ }
382
+ } catch (e) {
383
+ if (e.code !== "EOF" && e.code !== "EAGAIN") throw e;
384
+ }
385
+ return Buffer.concat(chunks).toString("utf-8");
386
+ }
387
+ function readInput(filePath) {
388
+ const raw = filePath === "-" ? readStdin() : readFileSync2(filePath, "utf-8");
389
+ return JSON.parse(raw);
390
+ }
391
+
392
+ // src/commands/emails.ts
393
+ var emailsCommand = new Command("emails").description("Emails resource.");
394
+ var statsSub = new Command("stats");
395
+ emailsCommand.addCommand(statsSub);
396
+ statsSub.command("retrieve").description("Returns aggregated daily sending statistics for the current period.").option("--format <fmt>", "Output format: json, raw, yaml, csv, markdown, table, quiet").option("-q, --quiet", "Shorthand for --format quiet").option("--raw", "Shorthand for --format raw").option("--fields <fields>", "Comma-separated list of fields to display").option("--no-header", "Omit column headers in table/csv output").addHelpText("after", "\nExample:\n $ nuntly emails stats retrieve").action(async (opts) => {
397
+ try {
398
+ const nuntly = new Nuntly({ apiKey: resolveApiKey(), baseUrl: resolveBaseUrl(), appInfo: { name: "@nuntly/cli", version: CLI_VERSION } });
399
+ const result = await withSpinner("Loading...", () => nuntly.emails.stats.retrieve());
400
+ printResult(result, opts);
401
+ } catch (error) {
402
+ printError(error, opts);
403
+ }
404
+ });
405
+ var eventsSub = new Command("events");
406
+ emailsCommand.addCommand(eventsSub);
407
+ eventsSub.command("list").description("Returns the full delivery event history for an email (sent, delivered, opened, bounced, etc.).").argument("<id>", "The id").option("--format <fmt>", "Output format: json, raw, yaml, csv, markdown, table, quiet").option("-q, --quiet", "Shorthand for --format quiet").option("--raw", "Shorthand for --format raw").option("--fields <fields>", "Comma-separated list of fields to display").option("--no-header", "Omit column headers in table/csv output").addHelpText("after", "\nExample:\n $ nuntly emails events list em_1234abcd").action(async (id, opts) => {
408
+ try {
409
+ const nuntly = new Nuntly({ apiKey: resolveApiKey(), baseUrl: resolveBaseUrl(), appInfo: { name: "@nuntly/cli", version: CLI_VERSION } });
410
+ const result = await withSpinner("Loading...", () => nuntly.emails.events.list(id));
411
+ printResult(result, opts);
412
+ } catch (error) {
413
+ printError(error, opts);
414
+ }
415
+ });
416
+ var contentSub = new Command("content");
417
+ emailsCommand.addCommand(contentSub);
418
+ contentSub.command("retrieve").description("Returns presigned URLs to download the HTML, plain-text, and raw MIME source of a sent email.").argument("<id>", "The id").option("--format <fmt>", "Output format: json, raw, yaml, csv, markdown, table, quiet").option("-q, --quiet", "Shorthand for --format quiet").option("--raw", "Shorthand for --format raw").option("--fields <fields>", "Comma-separated list of fields to display").option("--no-header", "Omit column headers in table/csv output").addHelpText("after", "\nExample:\n $ nuntly emails content retrieve em_1234abcd").action(async (id, opts) => {
419
+ try {
420
+ const nuntly = new Nuntly({ apiKey: resolveApiKey(), baseUrl: resolveBaseUrl(), appInfo: { name: "@nuntly/cli", version: CLI_VERSION } });
421
+ const result = await withSpinner("Loading...", () => nuntly.emails.content.retrieve(id));
422
+ printResult(result, opts);
423
+ } catch (error) {
424
+ printError(error, opts);
425
+ }
426
+ });
427
+ var bulkSub = new Command("bulk");
428
+ emailsCommand.addCommand(bulkSub);
429
+ bulkSub.command("send").description("Send up to 20 emails in a single request. Use `fallback` to set default values shared across all messages.").option("--fallback <value>", "Used as a fallback field email value if no value is present in emails").option("--emails <value>", "The bulk emails to send (required)").option("--file <path>", "Read JSON body from file (use - for stdin)").option("--format <fmt>", "Output format: json, raw, yaml, csv, markdown, table, quiet").option("-q, --quiet", "Shorthand for --format quiet").option("--raw", "Shorthand for --format raw").option("--fields <fields>", "Comma-separated list of fields to display").option("--no-header", "Omit column headers in table/csv output").addHelpText("after", '\nExample:\n $ nuntly emails bulk send --emails "value"\n $ cat payload.json | nuntly emails bulk send\n $ nuntly emails bulk send --file payload.json').action(async (opts) => {
430
+ try {
431
+ const nuntly = new Nuntly({ apiKey: resolveApiKey(), baseUrl: resolveBaseUrl(), appInfo: { name: "@nuntly/cli", version: CLI_VERSION } });
432
+ const body = opts.file ? readInput(opts.file) : !process.stdin.isTTY ? readInput("-") : {
433
+ fallback: opts.fallback != null ? JSON.parse(opts.fallback) : void 0,
434
+ emails: JSON.parse(opts.emails)
435
+ };
436
+ const result = await withSpinner("Creating...", () => nuntly.emails.bulk.send(body));
437
+ printResult(result, opts);
438
+ } catch (error) {
439
+ printError(error, opts);
440
+ }
441
+ });
442
+ bulkSub.command("list").description("Returns the delivery status of all emails submitted in a bulk request.").argument("<bulk-id>", "The bulkId").option("--format <fmt>", "Output format: json, raw, yaml, csv, markdown, table, quiet").option("-q, --quiet", "Shorthand for --format quiet").option("--raw", "Shorthand for --format raw").option("--fields <fields>", "Comma-separated list of fields to display").option("--no-header", "Omit column headers in table/csv output").addHelpText("after", "\nExample:\n $ nuntly emails bulk list em_1234abcd").action(async (bulkId, opts) => {
443
+ try {
444
+ const nuntly = new Nuntly({ apiKey: resolveApiKey(), baseUrl: resolveBaseUrl(), appInfo: { name: "@nuntly/cli", version: CLI_VERSION } });
445
+ const result = await withSpinner("Loading...", () => nuntly.emails.bulk.list(bulkId));
446
+ printResult(result, opts);
447
+ } catch (error) {
448
+ printError(error, opts);
449
+ }
450
+ });
451
+ emailsCommand.command("retrieve").description("Returns an email with its current delivery status and metadata.").argument("<id>", "The id").option("--format <fmt>", "Output format: json, raw, yaml, csv, markdown, table, quiet").option("-q, --quiet", "Shorthand for --format quiet").option("--raw", "Shorthand for --format raw").option("--fields <fields>", "Comma-separated list of fields to display").option("--no-header", "Omit column headers in table/csv output").addHelpText("after", "\nExample:\n $ nuntly emails retrieve em_1234abcd").action(async (id, opts) => {
452
+ try {
453
+ const nuntly = new Nuntly({ apiKey: resolveApiKey(), baseUrl: resolveBaseUrl(), appInfo: { name: "@nuntly/cli", version: CLI_VERSION } });
454
+ const result = await withSpinner("Loading...", () => nuntly.emails.retrieve(id));
455
+ printResult(result, opts);
456
+ } catch (error) {
457
+ printError(error, opts);
458
+ }
459
+ });
460
+ emailsCommand.command("list").description("Returns sent emails ordered by submission date, newest first.").option("--cursor <cursor>", "Pagination cursor").option("--limit <limit>", "Max items to return").option("--format <fmt>", "Output format: json, raw, yaml, csv, markdown, table, quiet").option("-q, --quiet", "Shorthand for --format quiet").option("--raw", "Shorthand for --format raw").option("--fields <fields>", "Comma-separated list of fields to display").option("--no-header", "Omit column headers in table/csv output").addHelpText("after", "\nExample:\n $ nuntly emails list\n $ nuntly emails list --format json | jq '.data[].id'").action(async (opts) => {
461
+ try {
462
+ const nuntly = new Nuntly({ apiKey: resolveApiKey(), baseUrl: resolveBaseUrl(), appInfo: { name: "@nuntly/cli", version: CLI_VERSION } });
463
+ const page = await withSpinner("Loading...", () => nuntly.emails.list({ cursor: opts.cursor, limit: opts.limit ? Number(opts.limit) : void 0 }));
464
+ printResult({ data: page.data, nextCursor: page.nextCursor }, opts);
465
+ } catch (error) {
466
+ printError(error, opts);
467
+ }
468
+ });
469
+ emailsCommand.command("send").description("Send transactional emails through Nuntly platform. It supports HTML and plain-text emails, attachments, labels, custom headers and scheduling.").option("--from <value>", "The e-mail address of the sender (required)").option("--to <value>", "The primary recipient(s) of the email (required)").option("--cc <value>", "The carbon copy recipient(s) of the email").option("--bcc <value>", "The blind carbon copy recipient(s) of the email").option("--reply-to <value>", "The email address where replies should be sent. If a recipient replies, the response will go to this address instead of the sender's email address").option("--subject <value>", "The subject of the e-mail (required)").option("--text <value>", "The plaintext version of the email").option("--html <value>", "The HTML version of the email").option("--headers <value>", "The headers to add to the email").option("--tags <value>", "The tags to add to the email").option("--attachments <value>", "The attachements to add to the email").option("--variables <value>", "The variables for the template").option("--scheduled-at <value>", "The date at which the email is scheduled to be sent").option("--file <path>", "Read JSON body from file (use - for stdin)").option("--format <fmt>", "Output format: json, raw, yaml, csv, markdown, table, quiet").option("-q, --quiet", "Shorthand for --format quiet").option("--raw", "Shorthand for --format raw").option("--fields <fields>", "Comma-separated list of fields to display").option("--no-header", "Omit column headers in table/csv output").addHelpText("after", '\nExample:\n $ nuntly emails send --from hello@acme.com --to user@example.com --subject "Welcome aboard"\n $ cat payload.json | nuntly emails send\n $ nuntly emails send --file payload.json').action(async (opts) => {
470
+ try {
471
+ const nuntly = new Nuntly({ apiKey: resolveApiKey(), baseUrl: resolveBaseUrl(), appInfo: { name: "@nuntly/cli", version: CLI_VERSION } });
472
+ const body = opts.file ? readInput(opts.file) : !process.stdin.isTTY ? readInput("-") : {
473
+ from: opts.from,
474
+ to: opts.to.split(","),
475
+ cc: opts.cc != null ? opts.cc.split(",") : void 0,
476
+ bcc: opts.bcc != null ? opts.bcc.split(",") : void 0,
477
+ replyTo: opts.replyTo != null ? opts.replyTo.split(",") : void 0,
478
+ subject: opts.subject,
479
+ text: opts.text,
480
+ html: opts.html,
481
+ headers: opts.headers != null ? JSON.parse(opts.headers) : void 0,
482
+ tags: opts.tags != null ? JSON.parse(opts.tags) : void 0,
483
+ attachments: opts.attachments != null ? JSON.parse(opts.attachments) : void 0,
484
+ variables: opts.variables != null ? JSON.parse(opts.variables) : void 0,
485
+ scheduledAt: opts.scheduledAt
486
+ };
487
+ const result = await withSpinner("Creating...", () => nuntly.emails.send(body));
488
+ printResult(result, opts);
489
+ } catch (error) {
490
+ printError(error, opts);
491
+ }
492
+ });
493
+ emailsCommand.command("cancel").description("Cancel a scheduled email before delivery. Only emails with `scheduled` status can be cancelled.").argument("<id>", "The id").option("--format <fmt>", "Output format: json, raw, yaml, csv, markdown, table, quiet").option("-q, --quiet", "Shorthand for --format quiet").option("--raw", "Shorthand for --format raw").option("--fields <fields>", "Comma-separated list of fields to display").option("--no-header", "Omit column headers in table/csv output").addHelpText("after", "\nExample:\n $ nuntly emails cancel em_1234abcd").action(async (id, opts) => {
494
+ try {
495
+ if (!await confirmDelete("emails", id)) return;
496
+ const nuntly = new Nuntly({ apiKey: resolveApiKey(), baseUrl: resolveBaseUrl(), appInfo: { name: "@nuntly/cli", version: CLI_VERSION } });
497
+ const result = await withSpinner("Deleting...", () => nuntly.emails.cancel(id));
498
+ printResult(result, opts);
499
+ } catch (error) {
500
+ printError(error, opts);
501
+ }
502
+ });
503
+
504
+ // src/commands/domains.ts
505
+ import { Command as Command2 } from "@commander-js/extra-typings";
506
+ import { Nuntly as Nuntly2 } from "@nuntly/sdk";
507
+ var domainsCommand = new Command2("domains").description("Domains resource.");
508
+ domainsCommand.command("list").description("Returns all domains with their verification and capability status.").option("--cursor <cursor>", "Pagination cursor").option("--limit <limit>", "Max items to return").option("--format <fmt>", "Output format: json, raw, yaml, csv, markdown, table, quiet").option("-q, --quiet", "Shorthand for --format quiet").option("--raw", "Shorthand for --format raw").option("--fields <fields>", "Comma-separated list of fields to display").option("--no-header", "Omit column headers in table/csv output").addHelpText("after", "\nExample:\n $ nuntly domains list\n $ nuntly domains list --format json | jq '.data[].id'").action(async (opts) => {
509
+ try {
510
+ const nuntly = new Nuntly2({ apiKey: resolveApiKey(), baseUrl: resolveBaseUrl(), appInfo: { name: "@nuntly/cli", version: CLI_VERSION } });
511
+ const page = await withSpinner("Loading...", () => nuntly.domains.list({ cursor: opts.cursor, limit: opts.limit ? Number(opts.limit) : void 0 }));
512
+ printResult({ data: page.data, nextCursor: page.nextCursor }, opts);
513
+ } catch (error) {
514
+ printError(error, opts);
515
+ }
516
+ });
517
+ domainsCommand.command("retrieve").description("Returns a domain with its DNS record configuration and current verification status for each record.").argument("<id>", "The id").option("--format <fmt>", "Output format: json, raw, yaml, csv, markdown, table, quiet").option("-q, --quiet", "Shorthand for --format quiet").option("--raw", "Shorthand for --format raw").option("--fields <fields>", "Comma-separated list of fields to display").option("--no-header", "Omit column headers in table/csv output").addHelpText("after", "\nExample:\n $ nuntly domains retrieve dm_5678efgh").action(async (id, opts) => {
518
+ try {
519
+ const nuntly = new Nuntly2({ apiKey: resolveApiKey(), baseUrl: resolveBaseUrl(), appInfo: { name: "@nuntly/cli", version: CLI_VERSION } });
520
+ const result = await withSpinner("Loading...", () => nuntly.domains.retrieve(id));
521
+ printResult(result, opts);
522
+ } catch (error) {
523
+ printError(error, opts);
524
+ }
525
+ });
526
+ domainsCommand.command("delete").description("Permanently deletes a domain along with its inboxes, received messages, attachments, and sending configuration. This action is irreversible.").argument("<id>", "The id").option("--format <fmt>", "Output format: json, raw, yaml, csv, markdown, table, quiet").option("-q, --quiet", "Shorthand for --format quiet").option("--raw", "Shorthand for --format raw").option("--fields <fields>", "Comma-separated list of fields to display").option("--no-header", "Omit column headers in table/csv output").addHelpText("after", "\nExample:\n $ nuntly domains delete dm_5678efgh").action(async (id, opts) => {
527
+ try {
528
+ if (!await confirmDelete("domains", id)) return;
529
+ const nuntly = new Nuntly2({ apiKey: resolveApiKey(), baseUrl: resolveBaseUrl(), appInfo: { name: "@nuntly/cli", version: CLI_VERSION } });
530
+ const result = await withSpinner("Deleting...", () => nuntly.domains.delete(id));
531
+ printResult(result, opts);
532
+ } catch (error) {
533
+ printError(error, opts);
534
+ }
535
+ });
536
+ domainsCommand.command("create").description("Add a domain to start configuring DNS records for sending or receiving emails.").option("--name <value>", "The name of the domain to send e-mails' (required)").option("--sending", "Enable sending").option("--receiving", "Enable receiving").option("--file <path>", "Read JSON body from file (use - for stdin)").option("--format <fmt>", "Output format: json, raw, yaml, csv, markdown, table, quiet").option("-q, --quiet", "Shorthand for --format quiet").option("--raw", "Shorthand for --format raw").option("--fields <fields>", "Comma-separated list of fields to display").option("--no-header", "Omit column headers in table/csv output").addHelpText("after", "\nExample:\n $ nuntly domains create --name my-resource\n $ cat payload.json | nuntly domains create\n $ nuntly domains create --file payload.json").action(async (opts) => {
537
+ try {
538
+ const nuntly = new Nuntly2({ apiKey: resolveApiKey(), baseUrl: resolveBaseUrl(), appInfo: { name: "@nuntly/cli", version: CLI_VERSION } });
539
+ const body = opts.file ? readInput(opts.file) : !process.stdin.isTTY ? readInput("-") : {
540
+ name: opts.name,
541
+ sending: opts.sending,
542
+ receiving: opts.receiving
543
+ };
544
+ const result = await withSpinner("Creating...", () => nuntly.domains.create(body));
545
+ printResult(result, opts);
546
+ } catch (error) {
547
+ printError(error, opts);
548
+ }
549
+ });
550
+ domainsCommand.command("update").description("Toggle sending, receiving, open tracking, or click tracking capabilities for a domain.").argument("<id>", "The id").option("--open-tracking", "Emit an event for each recipient opens an email their email client").option("--click-tracking", "Emit an event for each time the recipient clicks a link in the email").option("--sending", "Enable or disable sending").option("--receiving", "Enable or disable receiving").option("--file <path>", "Read JSON body from file (use - for stdin)").option("--format <fmt>", "Output format: json, raw, yaml, csv, markdown, table, quiet").option("-q, --quiet", "Shorthand for --format quiet").option("--raw", "Shorthand for --format raw").option("--fields <fields>", "Comma-separated list of fields to display").option("--no-header", "Omit column headers in table/csv output").addHelpText("after", "\nExample:\n $ nuntly domains update dm_5678efgh\n $ cat payload.json | nuntly domains update dm_5678efgh\n $ nuntly domains update dm_5678efgh --file payload.json").action(async (id, opts) => {
551
+ try {
552
+ const nuntly = new Nuntly2({ apiKey: resolveApiKey(), baseUrl: resolveBaseUrl(), appInfo: { name: "@nuntly/cli", version: CLI_VERSION } });
553
+ const body = opts.file ? readInput(opts.file) : !process.stdin.isTTY ? readInput("-") : {
554
+ openTracking: opts.openTracking,
555
+ clickTracking: opts.clickTracking,
556
+ sending: opts.sending,
557
+ receiving: opts.receiving
558
+ };
559
+ const result = await withSpinner("Updating...", () => nuntly.domains.update(id, body));
560
+ printResult(result, opts);
561
+ } catch (error) {
562
+ printError(error, opts);
563
+ }
564
+ });
565
+
566
+ // src/commands/webhooks.ts
567
+ import { Command as Command3 } from "@commander-js/extra-typings";
568
+ import { Nuntly as Nuntly3 } from "@nuntly/sdk";
569
+ var webhooksCommand = new Command3("webhooks").description("Webhooks resource.");
570
+ var eventsSub2 = new Command3("events");
571
+ webhooksCommand.addCommand(eventsSub2);
572
+ eventsSub2.command("list").description("Returns recent webhook events across all registered endpoints.").option("--cursor <cursor>", "Pagination cursor").option("--limit <limit>", "Max items to return").option("--format <fmt>", "Output format: json, raw, yaml, csv, markdown, table, quiet").option("-q, --quiet", "Shorthand for --format quiet").option("--raw", "Shorthand for --format raw").option("--fields <fields>", "Comma-separated list of fields to display").option("--no-header", "Omit column headers in table/csv output").addHelpText("after", "\nExample:\n $ nuntly webhooks events list\n $ nuntly webhooks events list --format json | jq '.data[].id'").action(async (opts) => {
573
+ try {
574
+ const nuntly = new Nuntly3({ apiKey: resolveApiKey(), baseUrl: resolveBaseUrl(), appInfo: { name: "@nuntly/cli", version: CLI_VERSION } });
575
+ const page = await withSpinner("Loading...", () => nuntly.webhooks.events.list({ cursor: opts.cursor, limit: opts.limit ? Number(opts.limit) : void 0 }));
576
+ printResult({ data: page.data, nextCursor: page.nextCursor }, opts);
577
+ } catch (error) {
578
+ printError(error, opts);
579
+ }
580
+ });
581
+ eventsSub2.command("replay").description("Re-deliver a webhook event to its endpoint. Useful for retrying failed deliveries.").argument("<id>", "The id").argument("<event-id>", "The eventId").option("--format <fmt>", "Output format: json, raw, yaml, csv, markdown, table, quiet").option("-q, --quiet", "Shorthand for --format quiet").option("--raw", "Shorthand for --format raw").option("--fields <fields>", "Comma-separated list of fields to display").option("--no-header", "Omit column headers in table/csv output").addHelpText("after", "\nExample:\n $ nuntly webhooks events replay wh_9012ijkl wh_9012ijkl").action(async (id, eventId, opts) => {
582
+ try {
583
+ const nuntly = new Nuntly3({ apiKey: resolveApiKey(), baseUrl: resolveBaseUrl(), appInfo: { name: "@nuntly/cli", version: CLI_VERSION } });
584
+ const result = await withSpinner("Creating...", () => nuntly.webhooks.events.replay(id, eventId));
585
+ printResult(result, opts);
586
+ } catch (error) {
587
+ printError(error, opts);
588
+ }
589
+ });
590
+ eventsSub2.command("deliveries").description("Returns all delivery attempts for a webhook event, including HTTP status codes and response times.").argument("<id>", "The id").argument("<event-id>", "The eventId").option("--format <fmt>", "Output format: json, raw, yaml, csv, markdown, table, quiet").option("-q, --quiet", "Shorthand for --format quiet").option("--raw", "Shorthand for --format raw").option("--fields <fields>", "Comma-separated list of fields to display").option("--no-header", "Omit column headers in table/csv output").addHelpText("after", "\nExample:\n $ nuntly webhooks events deliveries wh_9012ijkl wh_9012ijkl").action(async (id, eventId, opts) => {
591
+ try {
592
+ const nuntly = new Nuntly3({ apiKey: resolveApiKey(), baseUrl: resolveBaseUrl(), appInfo: { name: "@nuntly/cli", version: CLI_VERSION } });
593
+ const result = await withSpinner("Loading...", () => nuntly.webhooks.events.deliveries(id, eventId));
594
+ printResult(result, opts);
595
+ } catch (error) {
596
+ printError(error, opts);
597
+ }
598
+ });
599
+ webhooksCommand.command("retrieve").description("Returns a webhook endpoint with its URL, subscribed events, and configuration.").argument("<id>", "The id").option("--format <fmt>", "Output format: json, raw, yaml, csv, markdown, table, quiet").option("-q, --quiet", "Shorthand for --format quiet").option("--raw", "Shorthand for --format raw").option("--fields <fields>", "Comma-separated list of fields to display").option("--no-header", "Omit column headers in table/csv output").addHelpText("after", "\nExample:\n $ nuntly webhooks retrieve wh_9012ijkl").action(async (id, opts) => {
600
+ try {
601
+ const nuntly = new Nuntly3({ apiKey: resolveApiKey(), baseUrl: resolveBaseUrl(), appInfo: { name: "@nuntly/cli", version: CLI_VERSION } });
602
+ const result = await withSpinner("Loading...", () => nuntly.webhooks.retrieve(id));
603
+ printResult(result, opts);
604
+ } catch (error) {
605
+ printError(error, opts);
606
+ }
607
+ });
608
+ webhooksCommand.command("update").description("Update the endpoint URL, subscribed event types, or rotate the signing secret.").argument("<id>", "The id").option("--name <value>", "The name of the webhook").option("--endpoint-url <value>", "The endpoint URL of the webhook").option("--events <value>", "The event types to subscribe to").option("--status <value>", "The status of the webhook.").option("--rotate-secret", "If true, a new signing secret will be generated").option("--file <path>", "Read JSON body from file (use - for stdin)").option("--format <fmt>", "Output format: json, raw, yaml, csv, markdown, table, quiet").option("-q, --quiet", "Shorthand for --format quiet").option("--raw", "Shorthand for --format raw").option("--fields <fields>", "Comma-separated list of fields to display").option("--no-header", "Omit column headers in table/csv output").addHelpText("after", "\nExample:\n $ nuntly webhooks update wh_9012ijkl\n $ cat payload.json | nuntly webhooks update wh_9012ijkl\n $ nuntly webhooks update wh_9012ijkl --file payload.json").action(async (id, opts) => {
609
+ try {
610
+ const nuntly = new Nuntly3({ apiKey: resolveApiKey(), baseUrl: resolveBaseUrl(), appInfo: { name: "@nuntly/cli", version: CLI_VERSION } });
611
+ const body = opts.file ? readInput(opts.file) : !process.stdin.isTTY ? readInput("-") : {
612
+ name: opts.name,
613
+ endpointUrl: opts.endpointUrl,
614
+ events: opts.events != null ? opts.events.split(",") : void 0,
615
+ status: opts.status,
616
+ rotateSecret: opts.rotateSecret
617
+ };
618
+ const result = await withSpinner("Updating...", () => nuntly.webhooks.update(id, body));
619
+ printResult(result, opts);
620
+ } catch (error) {
621
+ printError(error, opts);
622
+ }
623
+ });
624
+ webhooksCommand.command("delete").description("Remove a webhook endpoint. No further events will be delivered to this URL.").argument("<id>", "The id").option("--format <fmt>", "Output format: json, raw, yaml, csv, markdown, table, quiet").option("-q, --quiet", "Shorthand for --format quiet").option("--raw", "Shorthand for --format raw").option("--fields <fields>", "Comma-separated list of fields to display").option("--no-header", "Omit column headers in table/csv output").addHelpText("after", "\nExample:\n $ nuntly webhooks delete wh_9012ijkl").action(async (id, opts) => {
625
+ try {
626
+ if (!await confirmDelete("webhooks", id)) return;
627
+ const nuntly = new Nuntly3({ apiKey: resolveApiKey(), baseUrl: resolveBaseUrl(), appInfo: { name: "@nuntly/cli", version: CLI_VERSION } });
628
+ const result = await withSpinner("Deleting...", () => nuntly.webhooks.delete(id));
629
+ printResult(result, opts);
630
+ } catch (error) {
631
+ printError(error, opts);
632
+ }
633
+ });
634
+ webhooksCommand.command("create").description("Register an endpoint to start receiving webhook events for your organization.").option("--name <value>", "The name of the webhook").option("--endpoint-url <value>", "The endpoint URL of the webhook (required)").option("--status <value>", "The status of the webhook.").option("--events <value>", "The event types to subscribe to (required)").option("--file <path>", "Read JSON body from file (use - for stdin)").option("--format <fmt>", "Output format: json, raw, yaml, csv, markdown, table, quiet").option("-q, --quiet", "Shorthand for --format quiet").option("--raw", "Shorthand for --format raw").option("--fields <fields>", "Comma-separated list of fields to display").option("--no-header", "Omit column headers in table/csv output").addHelpText("after", "\nExample:\n $ nuntly webhooks create --endpoint-url https://acme.com/webhooks --events email.sent,email.delivered\n $ cat payload.json | nuntly webhooks create\n $ nuntly webhooks create --file payload.json").action(async (opts) => {
635
+ try {
636
+ const nuntly = new Nuntly3({ apiKey: resolveApiKey(), baseUrl: resolveBaseUrl(), appInfo: { name: "@nuntly/cli", version: CLI_VERSION } });
637
+ const body = opts.file ? readInput(opts.file) : !process.stdin.isTTY ? readInput("-") : {
638
+ name: opts.name,
639
+ endpointUrl: opts.endpointUrl,
640
+ status: opts.status,
641
+ events: opts.events.split(",")
642
+ };
643
+ const result = await withSpinner("Creating...", () => nuntly.webhooks.create(body));
644
+ printResult(result, opts);
645
+ } catch (error) {
646
+ printError(error, opts);
647
+ }
648
+ });
649
+ webhooksCommand.command("list").description("Returns all registered webhook endpoints for the organization.").option("--cursor <cursor>", "Pagination cursor").option("--limit <limit>", "Max items to return").option("--format <fmt>", "Output format: json, raw, yaml, csv, markdown, table, quiet").option("-q, --quiet", "Shorthand for --format quiet").option("--raw", "Shorthand for --format raw").option("--fields <fields>", "Comma-separated list of fields to display").option("--no-header", "Omit column headers in table/csv output").addHelpText("after", "\nExample:\n $ nuntly webhooks list\n $ nuntly webhooks list --format json | jq '.data[].id'").action(async (opts) => {
650
+ try {
651
+ const nuntly = new Nuntly3({ apiKey: resolveApiKey(), baseUrl: resolveBaseUrl(), appInfo: { name: "@nuntly/cli", version: CLI_VERSION } });
652
+ const page = await withSpinner("Loading...", () => nuntly.webhooks.list({ cursor: opts.cursor, limit: opts.limit ? Number(opts.limit) : void 0 }));
653
+ printResult({ data: page.data, nextCursor: page.nextCursor }, opts);
654
+ } catch (error) {
655
+ printError(error, opts);
656
+ }
657
+ });
658
+
659
+ // src/commands/organizations.ts
660
+ import { Command as Command4 } from "@commander-js/extra-typings";
661
+ import { Nuntly as Nuntly4 } from "@nuntly/sdk";
662
+ var organizationsCommand = new Command4("organizations").description("Organizations resource.");
663
+ var usageSub = new Command4("usage");
664
+ organizationsCommand.addCommand(usageSub);
665
+ usageSub.command("retrieve").description("Returns current period usage metrics (daily and monthly) for sending and receiving, against your plan limits.").argument("<id>", "The id").option("--format <fmt>", "Output format: json, raw, yaml, csv, markdown, table, quiet").option("-q, --quiet", "Shorthand for --format quiet").option("--raw", "Shorthand for --format raw").option("--fields <fields>", "Comma-separated list of fields to display").option("--no-header", "Omit column headers in table/csv output").addHelpText("after", "\nExample:\n $ nuntly organizations usage retrieve org_8901klmn").action(async (id, opts) => {
666
+ try {
667
+ const nuntly = new Nuntly4({ apiKey: resolveApiKey(), baseUrl: resolveBaseUrl(), appInfo: { name: "@nuntly/cli", version: CLI_VERSION } });
668
+ const result = await withSpinner("Loading...", () => nuntly.organizations.usage.retrieve(id));
669
+ printResult(result, opts);
670
+ } catch (error) {
671
+ printError(error, opts);
672
+ }
673
+ });
674
+ organizationsCommand.command("list").description("Returns all organizations the authenticated user belongs to.").option("--cursor <cursor>", "Pagination cursor").option("--limit <limit>", "Max items to return").option("--format <fmt>", "Output format: json, raw, yaml, csv, markdown, table, quiet").option("-q, --quiet", "Shorthand for --format quiet").option("--raw", "Shorthand for --format raw").option("--fields <fields>", "Comma-separated list of fields to display").option("--no-header", "Omit column headers in table/csv output").addHelpText("after", "\nExample:\n $ nuntly organizations list\n $ nuntly organizations list --format json | jq '.data[].id'").action(async (opts) => {
675
+ try {
676
+ const nuntly = new Nuntly4({ apiKey: resolveApiKey(), baseUrl: resolveBaseUrl(), appInfo: { name: "@nuntly/cli", version: CLI_VERSION } });
677
+ const page = await withSpinner("Loading...", () => nuntly.organizations.list({ cursor: opts.cursor, limit: opts.limit ? Number(opts.limit) : void 0 }));
678
+ printResult({ data: page.data, nextCursor: page.nextCursor }, opts);
679
+ } catch (error) {
680
+ printError(error, opts);
681
+ }
682
+ });
683
+ organizationsCommand.command("retrieve").description("Returns the organization's profile, plan, region, and account status.").argument("<id>", "The id").option("--format <fmt>", "Output format: json, raw, yaml, csv, markdown, table, quiet").option("-q, --quiet", "Shorthand for --format quiet").option("--raw", "Shorthand for --format raw").option("--fields <fields>", "Comma-separated list of fields to display").option("--no-header", "Omit column headers in table/csv output").addHelpText("after", "\nExample:\n $ nuntly organizations retrieve org_8901klmn").action(async (id, opts) => {
684
+ try {
685
+ const nuntly = new Nuntly4({ apiKey: resolveApiKey(), baseUrl: resolveBaseUrl(), appInfo: { name: "@nuntly/cli", version: CLI_VERSION } });
686
+ const result = await withSpinner("Loading...", () => nuntly.organizations.retrieve(id));
687
+ printResult(result, opts);
688
+ } catch (error) {
689
+ printError(error, opts);
690
+ }
691
+ });
692
+
693
+ // src/commands/inboxes.ts
694
+ import { Command as Command5 } from "@commander-js/extra-typings";
695
+ import { Nuntly as Nuntly5 } from "@nuntly/sdk";
696
+ var inboxesCommand = new Command5("inboxes").description("Inboxes resource.");
697
+ var threadsSub = new Command5("threads");
698
+ inboxesCommand.addCommand(threadsSub);
699
+ threadsSub.command("list").description("List threads in an inbox.").argument("<inbox-id>", "The inboxId").option("--cursor <cursor>", "Pagination cursor").option("--limit <limit>", "Max items to return").option("--format <fmt>", "Output format: json, raw, yaml, csv, markdown, table, quiet").option("-q, --quiet", "Shorthand for --format quiet").option("--raw", "Shorthand for --format raw").option("--fields <fields>", "Comma-separated list of fields to display").option("--no-header", "Omit column headers in table/csv output").addHelpText("after", "\nExample:\n $ nuntly inboxes threads list ib_7890qrst\n $ nuntly inboxes threads list ib_7890qrst --format json | jq '.data[].id'").action(async (inboxId, opts) => {
700
+ try {
701
+ const nuntly = new Nuntly5({ apiKey: resolveApiKey(), baseUrl: resolveBaseUrl(), appInfo: { name: "@nuntly/cli", version: CLI_VERSION } });
702
+ const page = await withSpinner("Loading...", () => nuntly.inboxes.threads.list(inboxId, { cursor: opts.cursor, limit: opts.limit ? Number(opts.limit) : void 0 }));
703
+ printResult({ data: page.data, nextCursor: page.nextCursor }, opts);
704
+ } catch (error) {
705
+ printError(error, opts);
706
+ }
707
+ });
708
+ var messagesSub = new Command5("messages");
709
+ inboxesCommand.addCommand(messagesSub);
710
+ messagesSub.command("send").description("Send a new message from an inbox.").argument("<inbox-id>", "The inboxId").option("--to <value>", "The recipient addresses. (required)").option("--cc <value>", "The CC addresses.").option("--bcc <value>", "The BCC addresses.").option("--subject <value>", "The message subject. (required)").option("--text <value>", "The plain text body.").option("--html <value>", "The HTML body.").option("--file <path>", "Read JSON body from file (use - for stdin)").option("--format <fmt>", "Output format: json, raw, yaml, csv, markdown, table, quiet").option("-q, --quiet", "Shorthand for --format quiet").option("--raw", "Shorthand for --format raw").option("--fields <fields>", "Comma-separated list of fields to display").option("--no-header", "Omit column headers in table/csv output").addHelpText("after", '\nExample:\n $ nuntly inboxes messages send ib_7890qrst --to user@example.com --subject "Welcome aboard"\n $ cat payload.json | nuntly inboxes messages send ib_7890qrst\n $ nuntly inboxes messages send ib_7890qrst --file payload.json').action(async (inboxId, opts) => {
711
+ try {
712
+ const nuntly = new Nuntly5({ apiKey: resolveApiKey(), baseUrl: resolveBaseUrl(), appInfo: { name: "@nuntly/cli", version: CLI_VERSION } });
713
+ const body = opts.file ? readInput(opts.file) : !process.stdin.isTTY ? readInput("-") : {
714
+ to: opts.to.split(","),
715
+ cc: opts.cc != null ? opts.cc.split(",") : void 0,
716
+ bcc: opts.bcc != null ? opts.bcc.split(",") : void 0,
717
+ subject: opts.subject,
718
+ text: opts.text,
719
+ html: opts.html
720
+ };
721
+ const result = await withSpinner("Creating...", () => nuntly.inboxes.messages.send(inboxId, body));
722
+ printResult(result, opts);
723
+ } catch (error) {
724
+ printError(error, opts);
725
+ }
726
+ });
727
+ inboxesCommand.command("create").description("Create a new inbox on a verified domain.").option("--domain-id <value>", "The id of the domain for this inbox. Defaults to your provided domain when omitted.").option("--address <value>", "The local-part of the email address (before the @). (required)").option("--name <value>", "The display name of the inbox.").option("--namespace-id <value>", "The id of the namespace to assign the inbox to.").option("--agent-id <value>", "The external AI agent identifier.").option("--file <path>", "Read JSON body from file (use - for stdin)").option("--format <fmt>", "Output format: json, raw, yaml, csv, markdown, table, quiet").option("-q, --quiet", "Shorthand for --format quiet").option("--raw", "Shorthand for --format raw").option("--fields <fields>", "Comma-separated list of fields to display").option("--no-header", "Omit column headers in table/csv output").addHelpText("after", "\nExample:\n $ nuntly inboxes create --address support\n $ cat payload.json | nuntly inboxes create\n $ nuntly inboxes create --file payload.json").action(async (opts) => {
728
+ try {
729
+ const nuntly = new Nuntly5({ apiKey: resolveApiKey(), baseUrl: resolveBaseUrl(), appInfo: { name: "@nuntly/cli", version: CLI_VERSION } });
730
+ const body = opts.file ? readInput(opts.file) : !process.stdin.isTTY ? readInput("-") : {
731
+ domainId: opts.domainId,
732
+ address: opts.address,
733
+ name: opts.name,
734
+ namespaceId: opts.namespaceId,
735
+ agentId: opts.agentId
736
+ };
737
+ const result = await withSpinner("Creating...", () => nuntly.inboxes.create(body));
738
+ printResult(result, opts);
739
+ } catch (error) {
740
+ printError(error, opts);
741
+ }
742
+ });
743
+ inboxesCommand.command("list").description("List all inboxes.").option("--cursor <cursor>", "Pagination cursor").option("--limit <limit>", "Max items to return").option("--format <fmt>", "Output format: json, raw, yaml, csv, markdown, table, quiet").option("-q, --quiet", "Shorthand for --format quiet").option("--raw", "Shorthand for --format raw").option("--fields <fields>", "Comma-separated list of fields to display").option("--no-header", "Omit column headers in table/csv output").addHelpText("after", "\nExample:\n $ nuntly inboxes list\n $ nuntly inboxes list --format json | jq '.data[].id'").action(async (opts) => {
744
+ try {
745
+ const nuntly = new Nuntly5({ apiKey: resolveApiKey(), baseUrl: resolveBaseUrl(), appInfo: { name: "@nuntly/cli", version: CLI_VERSION } });
746
+ const page = await withSpinner("Loading...", () => nuntly.inboxes.list({ cursor: opts.cursor, limit: opts.limit ? Number(opts.limit) : void 0 }));
747
+ printResult({ data: page.data, nextCursor: page.nextCursor }, opts);
748
+ } catch (error) {
749
+ printError(error, opts);
750
+ }
751
+ });
752
+ inboxesCommand.command("retrieve").description("Retrieve an inbox with thread stats.").argument("<inbox-id>", "The inboxId").option("--format <fmt>", "Output format: json, raw, yaml, csv, markdown, table, quiet").option("-q, --quiet", "Shorthand for --format quiet").option("--raw", "Shorthand for --format raw").option("--fields <fields>", "Comma-separated list of fields to display").option("--no-header", "Omit column headers in table/csv output").addHelpText("after", "\nExample:\n $ nuntly inboxes retrieve ib_7890qrst").action(async (inboxId, opts) => {
753
+ try {
754
+ const nuntly = new Nuntly5({ apiKey: resolveApiKey(), baseUrl: resolveBaseUrl(), appInfo: { name: "@nuntly/cli", version: CLI_VERSION } });
755
+ const result = await withSpinner("Loading...", () => nuntly.inboxes.retrieve(inboxId));
756
+ printResult(result, opts);
757
+ } catch (error) {
758
+ printError(error, opts);
759
+ }
760
+ });
761
+ inboxesCommand.command("update").description("Update an inbox.").argument("<inbox-id>", "The inboxId").option("--name <value>", "The display name of the inbox.").option("--file <path>", "Read JSON body from file (use - for stdin)").option("--format <fmt>", "Output format: json, raw, yaml, csv, markdown, table, quiet").option("-q, --quiet", "Shorthand for --format quiet").option("--raw", "Shorthand for --format raw").option("--fields <fields>", "Comma-separated list of fields to display").option("--no-header", "Omit column headers in table/csv output").addHelpText("after", "\nExample:\n $ nuntly inboxes update ib_7890qrst\n $ cat payload.json | nuntly inboxes update ib_7890qrst\n $ nuntly inboxes update ib_7890qrst --file payload.json").action(async (inboxId, opts) => {
762
+ try {
763
+ const nuntly = new Nuntly5({ apiKey: resolveApiKey(), baseUrl: resolveBaseUrl(), appInfo: { name: "@nuntly/cli", version: CLI_VERSION } });
764
+ const body = opts.file ? readInput(opts.file) : !process.stdin.isTTY ? readInput("-") : {
765
+ name: opts.name
766
+ };
767
+ const result = await withSpinner("Updating...", () => nuntly.inboxes.update(inboxId, body));
768
+ printResult(result, opts);
769
+ } catch (error) {
770
+ printError(error, opts);
771
+ }
772
+ });
773
+ inboxesCommand.command("delete").description("Soft-delete an inbox.").argument("<inbox-id>", "The inboxId").option("--format <fmt>", "Output format: json, raw, yaml, csv, markdown, table, quiet").option("-q, --quiet", "Shorthand for --format quiet").option("--raw", "Shorthand for --format raw").option("--fields <fields>", "Comma-separated list of fields to display").option("--no-header", "Omit column headers in table/csv output").addHelpText("after", "\nExample:\n $ nuntly inboxes delete ib_7890qrst").action(async (inboxId, opts) => {
774
+ try {
775
+ if (!await confirmDelete("inboxes", inboxId)) return;
776
+ const nuntly = new Nuntly5({ apiKey: resolveApiKey(), baseUrl: resolveBaseUrl(), appInfo: { name: "@nuntly/cli", version: CLI_VERSION } });
777
+ const result = await withSpinner("Deleting...", () => nuntly.inboxes.delete(inboxId));
778
+ printResult(result, opts);
779
+ } catch (error) {
780
+ printError(error, opts);
781
+ }
782
+ });
783
+
784
+ // src/commands/agents.ts
785
+ import { Command as Command6 } from "@commander-js/extra-typings";
786
+ import { Nuntly as Nuntly6 } from "@nuntly/sdk";
787
+ var agentsCommand = new Command6("agents").description("Agents resource.");
788
+ var memorySub = new Command6("memory");
789
+ agentsCommand.addCommand(memorySub);
790
+ memorySub.command("retrieve").description("Retrieve the memory for an AI agent.").argument("<agent-id>", "The agentId").option("--format <fmt>", "Output format: json, raw, yaml, csv, markdown, table, quiet").option("-q, --quiet", "Shorthand for --format quiet").option("--raw", "Shorthand for --format raw").option("--fields <fields>", "Comma-separated list of fields to display").option("--no-header", "Omit column headers in table/csv output").addHelpText("after", "\nExample:\n $ nuntly agents memory retrieve ag_6789yzab").action(async (agentId, opts) => {
791
+ try {
792
+ const nuntly = new Nuntly6({ apiKey: resolveApiKey(), baseUrl: resolveBaseUrl(), appInfo: { name: "@nuntly/cli", version: CLI_VERSION } });
793
+ const result = await withSpinner("Loading...", () => nuntly.agents.memory.retrieve(agentId));
794
+ printResult(result, opts);
795
+ } catch (error) {
796
+ printError(error, opts);
797
+ }
798
+ });
799
+ memorySub.command("upsert").description("Create or update the memory for an AI agent.").argument("<agent-id>", "The agentId").option("--inbox-id <value>", "The inbox id to scope the memory to.").option("--thread-id <value>", "The thread id to scope the memory to.").option("--memory <value>", "The agent memory key-value data. (required)").option("--summary <value>", "A human-readable conversation summary.").option("--file <path>", "Read JSON body from file (use - for stdin)").option("--format <fmt>", "Output format: json, raw, yaml, csv, markdown, table, quiet").option("-q, --quiet", "Shorthand for --format quiet").option("--raw", "Shorthand for --format raw").option("--fields <fields>", "Comma-separated list of fields to display").option("--no-header", "Omit column headers in table/csv output").addHelpText("after", '\nExample:\n $ nuntly agents memory upsert ag_6789yzab --memory "value"\n $ cat payload.json | nuntly agents memory upsert ag_6789yzab\n $ nuntly agents memory upsert ag_6789yzab --file payload.json').action(async (agentId, opts) => {
800
+ try {
801
+ const nuntly = new Nuntly6({ apiKey: resolveApiKey(), baseUrl: resolveBaseUrl(), appInfo: { name: "@nuntly/cli", version: CLI_VERSION } });
802
+ const body = opts.file ? readInput(opts.file) : !process.stdin.isTTY ? readInput("-") : {
803
+ inboxId: opts.inboxId,
804
+ threadId: opts.threadId,
805
+ memory: JSON.parse(opts.memory),
806
+ summary: opts.summary
807
+ };
808
+ const result = await withSpinner("Updating...", () => nuntly.agents.memory.upsert(agentId, body));
809
+ printResult(result, opts);
810
+ } catch (error) {
811
+ printError(error, opts);
812
+ }
813
+ });
814
+
815
+ // src/commands/threads.ts
816
+ import { Command as Command7 } from "@commander-js/extra-typings";
817
+ import { Nuntly as Nuntly7 } from "@nuntly/sdk";
818
+ var threadsCommand = new Command7("threads").description("Threads resource.");
819
+ var messagesSub2 = new Command7("messages");
820
+ threadsCommand.addCommand(messagesSub2);
821
+ messagesSub2.command("list").description("List messages in a thread (chronological order).").argument("<thread-id>", "The threadId").option("--cursor <cursor>", "Pagination cursor").option("--limit <limit>", "Max items to return").option("--format <fmt>", "Output format: json, raw, yaml, csv, markdown, table, quiet").option("-q, --quiet", "Shorthand for --format quiet").option("--raw", "Shorthand for --format raw").option("--fields <fields>", "Comma-separated list of fields to display").option("--no-header", "Omit column headers in table/csv output").addHelpText("after", "\nExample:\n $ nuntly threads messages list th_0123cdef\n $ nuntly threads messages list th_0123cdef --format json | jq '.data[].id'").action(async (threadId, opts) => {
822
+ try {
823
+ const nuntly = new Nuntly7({ apiKey: resolveApiKey(), baseUrl: resolveBaseUrl(), appInfo: { name: "@nuntly/cli", version: CLI_VERSION } });
824
+ const page = await withSpinner("Loading...", () => nuntly.threads.messages.list(threadId, { cursor: opts.cursor, limit: opts.limit ? Number(opts.limit) : void 0 }));
825
+ printResult({ data: page.data, nextCursor: page.nextCursor }, opts);
826
+ } catch (error) {
827
+ printError(error, opts);
828
+ }
829
+ });
830
+ threadsCommand.command("retrieve").description("Retrieve a thread. Pass ?markRead=true to automatically remove the unread label from all messages.").argument("<thread-id>", "The threadId").option("--format <fmt>", "Output format: json, raw, yaml, csv, markdown, table, quiet").option("-q, --quiet", "Shorthand for --format quiet").option("--raw", "Shorthand for --format raw").option("--fields <fields>", "Comma-separated list of fields to display").option("--no-header", "Omit column headers in table/csv output").addHelpText("after", "\nExample:\n $ nuntly threads retrieve th_0123cdef").action(async (threadId, opts) => {
831
+ try {
832
+ const nuntly = new Nuntly7({ apiKey: resolveApiKey(), baseUrl: resolveBaseUrl(), appInfo: { name: "@nuntly/cli", version: CLI_VERSION } });
833
+ const result = await withSpinner("Loading...", () => nuntly.threads.retrieve(threadId));
834
+ printResult(result, opts);
835
+ } catch (error) {
836
+ printError(error, opts);
837
+ }
838
+ });
839
+ threadsCommand.command("update").description("Update thread labels and agent assignment. Label operations apply to all messages in the thread.").argument("<thread-id>", "The threadId").option("--add-labels <value>", "Labels to add to all messages in the thread.").option("--remove-labels <value>", "Labels to remove from all messages in the thread.").option("--agent-id <value>", "The AI agent identifier.").option("--file <path>", "Read JSON body from file (use - for stdin)").option("--format <fmt>", "Output format: json, raw, yaml, csv, markdown, table, quiet").option("-q, --quiet", "Shorthand for --format quiet").option("--raw", "Shorthand for --format raw").option("--fields <fields>", "Comma-separated list of fields to display").option("--no-header", "Omit column headers in table/csv output").addHelpText("after", "\nExample:\n $ nuntly threads update th_0123cdef\n $ cat payload.json | nuntly threads update th_0123cdef\n $ nuntly threads update th_0123cdef --file payload.json").action(async (threadId, opts) => {
840
+ try {
841
+ const nuntly = new Nuntly7({ apiKey: resolveApiKey(), baseUrl: resolveBaseUrl(), appInfo: { name: "@nuntly/cli", version: CLI_VERSION } });
842
+ const body = opts.file ? readInput(opts.file) : !process.stdin.isTTY ? readInput("-") : {
843
+ addLabels: opts.addLabels != null ? opts.addLabels.split(",") : void 0,
844
+ removeLabels: opts.removeLabels != null ? opts.removeLabels.split(",") : void 0,
845
+ agentId: opts.agentId
846
+ };
847
+ const result = await withSpinner("Updating...", () => nuntly.threads.update(threadId, body));
848
+ printResult(result, opts);
849
+ } catch (error) {
850
+ printError(error, opts);
851
+ }
852
+ });
853
+
854
+ // src/commands/messages.ts
855
+ import { Command as Command8 } from "@commander-js/extra-typings";
856
+ import { Nuntly as Nuntly8 } from "@nuntly/sdk";
857
+ var messagesCommand = new Command8("messages").description("Messages resource.");
858
+ var contentSub2 = new Command8("content");
859
+ messagesCommand.addCommand(contentSub2);
860
+ contentSub2.command("retrieve").description("Returns presigned URLs to download the HTML, plain-text, and raw MIME source of a received message.").argument("<message-id>", "The messageId").option("--format <fmt>", "Output format: json, raw, yaml, csv, markdown, table, quiet").option("-q, --quiet", "Shorthand for --format quiet").option("--raw", "Shorthand for --format raw").option("--fields <fields>", "Comma-separated list of fields to display").option("--no-header", "Omit column headers in table/csv output").addHelpText("after", "\nExample:\n $ nuntly messages content retrieve mg_4567ghij").action(async (messageId, opts) => {
861
+ try {
862
+ const nuntly = new Nuntly8({ apiKey: resolveApiKey(), baseUrl: resolveBaseUrl(), appInfo: { name: "@nuntly/cli", version: CLI_VERSION } });
863
+ const result = await withSpinner("Loading...", () => nuntly.messages.content.retrieve(messageId));
864
+ printResult(result, opts);
865
+ } catch (error) {
866
+ printError(error, opts);
867
+ }
868
+ });
869
+ var attachmentsSub = new Command8("attachments");
870
+ messagesCommand.addCommand(attachmentsSub);
871
+ attachmentsSub.command("list").description("List all attachments for a message.").argument("<message-id>", "The messageId").option("--format <fmt>", "Output format: json, raw, yaml, csv, markdown, table, quiet").option("-q, --quiet", "Shorthand for --format quiet").option("--raw", "Shorthand for --format raw").option("--fields <fields>", "Comma-separated list of fields to display").option("--no-header", "Omit column headers in table/csv output").addHelpText("after", "\nExample:\n $ nuntly messages attachments list mg_4567ghij").action(async (messageId, opts) => {
872
+ try {
873
+ const nuntly = new Nuntly8({ apiKey: resolveApiKey(), baseUrl: resolveBaseUrl(), appInfo: { name: "@nuntly/cli", version: CLI_VERSION } });
874
+ const result = await withSpinner("Loading...", () => nuntly.messages.attachments.list(messageId));
875
+ printResult(result, opts);
876
+ } catch (error) {
877
+ printError(error, opts);
878
+ }
879
+ });
880
+ attachmentsSub.command("retrieve").description("Retrieve an attachment with a presigned download URL.").argument("<message-id>", "The messageId").argument("<attachment-id>", "The attachmentId").option("--format <fmt>", "Output format: json, raw, yaml, csv, markdown, table, quiet").option("-q, --quiet", "Shorthand for --format quiet").option("--raw", "Shorthand for --format raw").option("--fields <fields>", "Comma-separated list of fields to display").option("--no-header", "Omit column headers in table/csv output").addHelpText("after", "\nExample:\n $ nuntly messages attachments retrieve mg_4567ghij mg_4567ghij").action(async (messageId, attachmentId, opts) => {
881
+ try {
882
+ const nuntly = new Nuntly8({ apiKey: resolveApiKey(), baseUrl: resolveBaseUrl(), appInfo: { name: "@nuntly/cli", version: CLI_VERSION } });
883
+ const result = await withSpinner("Loading...", () => nuntly.messages.attachments.retrieve(messageId, attachmentId));
884
+ printResult(result, opts);
885
+ } catch (error) {
886
+ printError(error, opts);
887
+ }
888
+ });
889
+ messagesCommand.command("list").description("List all received messages across inboxes.").option("--cursor <cursor>", "Pagination cursor").option("--limit <limit>", "Max items to return").option("--format <fmt>", "Output format: json, raw, yaml, csv, markdown, table, quiet").option("-q, --quiet", "Shorthand for --format quiet").option("--raw", "Shorthand for --format raw").option("--fields <fields>", "Comma-separated list of fields to display").option("--no-header", "Omit column headers in table/csv output").addHelpText("after", "\nExample:\n $ nuntly messages list\n $ nuntly messages list --format json | jq '.data[].id'").action(async (opts) => {
890
+ try {
891
+ const nuntly = new Nuntly8({ apiKey: resolveApiKey(), baseUrl: resolveBaseUrl(), appInfo: { name: "@nuntly/cli", version: CLI_VERSION } });
892
+ const page = await withSpinner("Loading...", () => nuntly.messages.list({ cursor: opts.cursor, limit: opts.limit ? Number(opts.limit) : void 0 }));
893
+ printResult({ data: page.data, nextCursor: page.nextCursor }, opts);
894
+ } catch (error) {
895
+ printError(error, opts);
896
+ }
897
+ });
898
+ messagesCommand.command("retrieve").description("Retrieve a single message with inbox enrichment.").argument("<message-id>", "The messageId").option("--format <fmt>", "Output format: json, raw, yaml, csv, markdown, table, quiet").option("-q, --quiet", "Shorthand for --format quiet").option("--raw", "Shorthand for --format raw").option("--fields <fields>", "Comma-separated list of fields to display").option("--no-header", "Omit column headers in table/csv output").addHelpText("after", "\nExample:\n $ nuntly messages retrieve mg_4567ghij").action(async (messageId, opts) => {
899
+ try {
900
+ const nuntly = new Nuntly8({ apiKey: resolveApiKey(), baseUrl: resolveBaseUrl(), appInfo: { name: "@nuntly/cli", version: CLI_VERSION } });
901
+ const result = await withSpinner("Loading...", () => nuntly.messages.retrieve(messageId));
902
+ printResult(result, opts);
903
+ } catch (error) {
904
+ printError(error, opts);
905
+ }
906
+ });
907
+ messagesCommand.command("update").description("Update message labels. Only available for messages in user-created inboxes.").argument("<message-id>", "The messageId").option("--add-labels <value>", "Labels to add to the message.").option("--remove-labels <value>", "Labels to remove from the message.").option("--file <path>", "Read JSON body from file (use - for stdin)").option("--format <fmt>", "Output format: json, raw, yaml, csv, markdown, table, quiet").option("-q, --quiet", "Shorthand for --format quiet").option("--raw", "Shorthand for --format raw").option("--fields <fields>", "Comma-separated list of fields to display").option("--no-header", "Omit column headers in table/csv output").addHelpText("after", "\nExample:\n $ nuntly messages update mg_4567ghij\n $ cat payload.json | nuntly messages update mg_4567ghij\n $ nuntly messages update mg_4567ghij --file payload.json").action(async (messageId, opts) => {
908
+ try {
909
+ const nuntly = new Nuntly8({ apiKey: resolveApiKey(), baseUrl: resolveBaseUrl(), appInfo: { name: "@nuntly/cli", version: CLI_VERSION } });
910
+ const body = opts.file ? readInput(opts.file) : !process.stdin.isTTY ? readInput("-") : {
911
+ addLabels: opts.addLabels != null ? opts.addLabels.split(",") : void 0,
912
+ removeLabels: opts.removeLabels != null ? opts.removeLabels.split(",") : void 0
913
+ };
914
+ const result = await withSpinner("Updating...", () => nuntly.messages.update(messageId, body));
915
+ printResult(result, opts);
916
+ } catch (error) {
917
+ printError(error, opts);
918
+ }
919
+ });
920
+ messagesCommand.command("reply").description("Reply to a message. Set replyAll to true to reply to all recipients.").argument("<message-id>", "The messageId").option("--text <value>", "The plain text body.").option("--html <value>", "The HTML body.").option("--reply-all", "Whether to reply to all recipients. (required)").option("--file <path>", "Read JSON body from file (use - for stdin)").option("--format <fmt>", "Output format: json, raw, yaml, csv, markdown, table, quiet").option("-q, --quiet", "Shorthand for --format quiet").option("--raw", "Shorthand for --format raw").option("--fields <fields>", "Comma-separated list of fields to display").option("--no-header", "Omit column headers in table/csv output").addHelpText("after", "\nExample:\n $ nuntly messages reply mg_4567ghij --reply-all\n $ cat payload.json | nuntly messages reply mg_4567ghij\n $ nuntly messages reply mg_4567ghij --file payload.json").action(async (messageId, opts) => {
921
+ try {
922
+ const nuntly = new Nuntly8({ apiKey: resolveApiKey(), baseUrl: resolveBaseUrl(), appInfo: { name: "@nuntly/cli", version: CLI_VERSION } });
923
+ const body = opts.file ? readInput(opts.file) : !process.stdin.isTTY ? readInput("-") : {
924
+ text: opts.text,
925
+ html: opts.html,
926
+ replyAll: opts.replyAll
927
+ };
928
+ const result = await withSpinner("Creating...", () => nuntly.messages.reply(messageId, body));
929
+ printResult(result, opts);
930
+ } catch (error) {
931
+ printError(error, opts);
932
+ }
933
+ });
934
+ messagesCommand.command("forward").description("Forward a message to new recipients.").argument("<message-id>", "The messageId").option("--to <value>", "The recipient addresses to forward to. (required)").option("--text <value>", "An optional comment to prepend.").option("--file <path>", "Read JSON body from file (use - for stdin)").option("--format <fmt>", "Output format: json, raw, yaml, csv, markdown, table, quiet").option("-q, --quiet", "Shorthand for --format quiet").option("--raw", "Shorthand for --format raw").option("--fields <fields>", "Comma-separated list of fields to display").option("--no-header", "Omit column headers in table/csv output").addHelpText("after", "\nExample:\n $ nuntly messages forward mg_4567ghij --to user@example.com\n $ cat payload.json | nuntly messages forward mg_4567ghij\n $ nuntly messages forward mg_4567ghij --file payload.json").action(async (messageId, opts) => {
935
+ try {
936
+ const nuntly = new Nuntly8({ apiKey: resolveApiKey(), baseUrl: resolveBaseUrl(), appInfo: { name: "@nuntly/cli", version: CLI_VERSION } });
937
+ const body = opts.file ? readInput(opts.file) : !process.stdin.isTTY ? readInput("-") : {
938
+ to: opts.to.split(","),
939
+ text: opts.text
940
+ };
941
+ const result = await withSpinner("Creating...", () => nuntly.messages.forward(messageId, body));
942
+ printResult(result, opts);
943
+ } catch (error) {
944
+ printError(error, opts);
945
+ }
946
+ });
947
+
948
+ // src/commands/namespaces.ts
949
+ import { Command as Command9 } from "@commander-js/extra-typings";
950
+ import { Nuntly as Nuntly9 } from "@nuntly/sdk";
951
+ var namespacesCommand = new Command9("namespaces").description("Namespaces resource.");
952
+ var inboxesSub = new Command9("inboxes");
953
+ namespacesCommand.addCommand(inboxesSub);
954
+ inboxesSub.command("list").description("List inboxes in a namespace.").argument("<namespace-id>", "The namespaceId").option("--cursor <cursor>", "Pagination cursor").option("--limit <limit>", "Max items to return").option("--format <fmt>", "Output format: json, raw, yaml, csv, markdown, table, quiet").option("-q, --quiet", "Shorthand for --format quiet").option("--raw", "Shorthand for --format raw").option("--fields <fields>", "Comma-separated list of fields to display").option("--no-header", "Omit column headers in table/csv output").addHelpText("after", "\nExample:\n $ nuntly namespaces inboxes list ib_7890qrst\n $ nuntly namespaces inboxes list ib_7890qrst --format json | jq '.data[].id'").action(async (namespaceId, opts) => {
955
+ try {
956
+ const nuntly = new Nuntly9({ apiKey: resolveApiKey(), baseUrl: resolveBaseUrl(), appInfo: { name: "@nuntly/cli", version: CLI_VERSION } });
957
+ const page = await withSpinner("Loading...", () => nuntly.namespaces.inboxes.list(namespaceId, { cursor: opts.cursor, limit: opts.limit ? Number(opts.limit) : void 0 }));
958
+ printResult({ data: page.data, nextCursor: page.nextCursor }, opts);
959
+ } catch (error) {
960
+ printError(error, opts);
961
+ }
962
+ });
963
+ namespacesCommand.command("create").description("Create a new namespace.").option("--name <value>", "The display name of the namespace. (required)").option("--external-id <value>", "An optional external identifier for the namespace.").option("--file <path>", "Read JSON body from file (use - for stdin)").option("--format <fmt>", "Output format: json, raw, yaml, csv, markdown, table, quiet").option("-q, --quiet", "Shorthand for --format quiet").option("--raw", "Shorthand for --format raw").option("--fields <fields>", "Comma-separated list of fields to display").option("--no-header", "Omit column headers in table/csv output").addHelpText("after", "\nExample:\n $ nuntly namespaces create --name my-resource\n $ cat payload.json | nuntly namespaces create\n $ nuntly namespaces create --file payload.json").action(async (opts) => {
964
+ try {
965
+ const nuntly = new Nuntly9({ apiKey: resolveApiKey(), baseUrl: resolveBaseUrl(), appInfo: { name: "@nuntly/cli", version: CLI_VERSION } });
966
+ const body = opts.file ? readInput(opts.file) : !process.stdin.isTTY ? readInput("-") : {
967
+ name: opts.name,
968
+ externalId: opts.externalId
969
+ };
970
+ const result = await withSpinner("Creating...", () => nuntly.namespaces.create(body));
971
+ printResult(result, opts);
972
+ } catch (error) {
973
+ printError(error, opts);
974
+ }
975
+ });
976
+ namespacesCommand.command("list").description("List all namespaces.").option("--cursor <cursor>", "Pagination cursor").option("--limit <limit>", "Max items to return").option("--format <fmt>", "Output format: json, raw, yaml, csv, markdown, table, quiet").option("-q, --quiet", "Shorthand for --format quiet").option("--raw", "Shorthand for --format raw").option("--fields <fields>", "Comma-separated list of fields to display").option("--no-header", "Omit column headers in table/csv output").addHelpText("after", "\nExample:\n $ nuntly namespaces list\n $ nuntly namespaces list --format json | jq '.data[].id'").action(async (opts) => {
977
+ try {
978
+ const nuntly = new Nuntly9({ apiKey: resolveApiKey(), baseUrl: resolveBaseUrl(), appInfo: { name: "@nuntly/cli", version: CLI_VERSION } });
979
+ const page = await withSpinner("Loading...", () => nuntly.namespaces.list({ cursor: opts.cursor, limit: opts.limit ? Number(opts.limit) : void 0 }));
980
+ printResult({ data: page.data, nextCursor: page.nextCursor }, opts);
981
+ } catch (error) {
982
+ printError(error, opts);
983
+ }
984
+ });
985
+ namespacesCommand.command("retrieve").description("Retrieve a namespace with inbox stats.").argument("<namespace-id>", "The namespaceId").option("--format <fmt>", "Output format: json, raw, yaml, csv, markdown, table, quiet").option("-q, --quiet", "Shorthand for --format quiet").option("--raw", "Shorthand for --format raw").option("--fields <fields>", "Comma-separated list of fields to display").option("--no-header", "Omit column headers in table/csv output").addHelpText("after", "\nExample:\n $ nuntly namespaces retrieve ns_2345uvwx").action(async (namespaceId, opts) => {
986
+ try {
987
+ const nuntly = new Nuntly9({ apiKey: resolveApiKey(), baseUrl: resolveBaseUrl(), appInfo: { name: "@nuntly/cli", version: CLI_VERSION } });
988
+ const result = await withSpinner("Loading...", () => nuntly.namespaces.retrieve(namespaceId));
989
+ printResult(result, opts);
990
+ } catch (error) {
991
+ printError(error, opts);
992
+ }
993
+ });
994
+ namespacesCommand.command("update").description("Update a namespace.").argument("<namespace-id>", "The namespaceId").option("--name <value>", "The display name of the namespace.").option("--external-id <value>", "An optional external identifier for the namespace.").option("--file <path>", "Read JSON body from file (use - for stdin)").option("--format <fmt>", "Output format: json, raw, yaml, csv, markdown, table, quiet").option("-q, --quiet", "Shorthand for --format quiet").option("--raw", "Shorthand for --format raw").option("--fields <fields>", "Comma-separated list of fields to display").option("--no-header", "Omit column headers in table/csv output").addHelpText("after", "\nExample:\n $ nuntly namespaces update ns_2345uvwx\n $ cat payload.json | nuntly namespaces update ns_2345uvwx\n $ nuntly namespaces update ns_2345uvwx --file payload.json").action(async (namespaceId, opts) => {
995
+ try {
996
+ const nuntly = new Nuntly9({ apiKey: resolveApiKey(), baseUrl: resolveBaseUrl(), appInfo: { name: "@nuntly/cli", version: CLI_VERSION } });
997
+ const body = opts.file ? readInput(opts.file) : !process.stdin.isTTY ? readInput("-") : {
998
+ name: opts.name,
999
+ externalId: opts.externalId
1000
+ };
1001
+ const result = await withSpinner("Updating...", () => nuntly.namespaces.update(namespaceId, body));
1002
+ printResult(result, opts);
1003
+ } catch (error) {
1004
+ printError(error, opts);
1005
+ }
1006
+ });
1007
+ namespacesCommand.command("delete").description("Soft-delete a namespace. Rejects if it has active inboxes.").argument("<namespace-id>", "The namespaceId").option("--format <fmt>", "Output format: json, raw, yaml, csv, markdown, table, quiet").option("-q, --quiet", "Shorthand for --format quiet").option("--raw", "Shorthand for --format raw").option("--fields <fields>", "Comma-separated list of fields to display").option("--no-header", "Omit column headers in table/csv output").addHelpText("after", "\nExample:\n $ nuntly namespaces delete ns_2345uvwx").action(async (namespaceId, opts) => {
1008
+ try {
1009
+ if (!await confirmDelete("namespaces", namespaceId)) return;
1010
+ const nuntly = new Nuntly9({ apiKey: resolveApiKey(), baseUrl: resolveBaseUrl(), appInfo: { name: "@nuntly/cli", version: CLI_VERSION } });
1011
+ const result = await withSpinner("Deleting...", () => nuntly.namespaces.delete(namespaceId));
1012
+ printResult(result, opts);
1013
+ } catch (error) {
1014
+ printError(error, opts);
1015
+ }
1016
+ });
1017
+
1018
+ // src/commands/api-keys.ts
1019
+ import { Command as Command10 } from "@commander-js/extra-typings";
1020
+ import { Nuntly as Nuntly10 } from "@nuntly/sdk";
1021
+ var apiKeysCommand = new Command10("api-keys").description("ApiKeys resource.");
1022
+ apiKeysCommand.command("retrieve").description("Returns API key metadata. The key value is never returned after creation.").argument("<id>", "The id").option("--format <fmt>", "Output format: json, raw, yaml, csv, markdown, table, quiet").option("-q, --quiet", "Shorthand for --format quiet").option("--raw", "Shorthand for --format raw").option("--fields <fields>", "Comma-separated list of fields to display").option("--no-header", "Omit column headers in table/csv output").addHelpText("after", "\nExample:\n $ nuntly api-keys retrieve id_example").action(async (id, opts) => {
1023
+ try {
1024
+ const nuntly = new Nuntly10({ apiKey: resolveApiKey(), baseUrl: resolveBaseUrl(), appInfo: { name: "@nuntly/cli", version: CLI_VERSION } });
1025
+ const result = await withSpinner("Loading...", () => nuntly.apiKeys.retrieve(id));
1026
+ printResult(result, opts);
1027
+ } catch (error) {
1028
+ printError(error, opts);
1029
+ }
1030
+ });
1031
+ apiKeysCommand.command("update").description("Update the key name, permissions, or restrict it to specific sending domains.").argument("<id>", "The id").option("--name <value>", "The name of the api key").option("--status <value>", "status").option("--permission <value>", "The permission type for the api key (required)").option("--domain-ids <value>", "The domain ids to restrict the api key to (only for sendingAccess)").option("--file <path>", "Read JSON body from file (use - for stdin)").option("--format <fmt>", "Output format: json, raw, yaml, csv, markdown, table, quiet").option("-q, --quiet", "Shorthand for --format quiet").option("--raw", "Shorthand for --format raw").option("--fields <fields>", "Comma-separated list of fields to display").option("--no-header", "Omit column headers in table/csv output").addHelpText("after", "\nExample:\n $ nuntly api-keys update id_example --permission sendingAccess\n $ cat payload.json | nuntly api-keys update id_example\n $ nuntly api-keys update id_example --file payload.json").action(async (id, opts) => {
1032
+ try {
1033
+ const nuntly = new Nuntly10({ apiKey: resolveApiKey(), baseUrl: resolveBaseUrl(), appInfo: { name: "@nuntly/cli", version: CLI_VERSION } });
1034
+ const body = opts.file ? readInput(opts.file) : !process.stdin.isTTY ? readInput("-") : {
1035
+ name: opts.name,
1036
+ status: opts.status,
1037
+ permission: opts.permission,
1038
+ domainIds: opts.domainIds != null ? opts.domainIds.split(",") : void 0
1039
+ };
1040
+ const result = await withSpinner("Updating...", () => nuntly.apiKeys.update(id, body));
1041
+ printResult(result, opts);
1042
+ } catch (error) {
1043
+ printError(error, opts);
1044
+ }
1045
+ });
1046
+ apiKeysCommand.command("delete").description("Revoke an API key. Requests authenticating with this key will be rejected immediately.").argument("<id>", "The id").option("--format <fmt>", "Output format: json, raw, yaml, csv, markdown, table, quiet").option("-q, --quiet", "Shorthand for --format quiet").option("--raw", "Shorthand for --format raw").option("--fields <fields>", "Comma-separated list of fields to display").option("--no-header", "Omit column headers in table/csv output").addHelpText("after", "\nExample:\n $ nuntly api-keys delete id_example").action(async (id, opts) => {
1047
+ try {
1048
+ if (!await confirmDelete("api keys", id)) return;
1049
+ const nuntly = new Nuntly10({ apiKey: resolveApiKey(), baseUrl: resolveBaseUrl(), appInfo: { name: "@nuntly/cli", version: CLI_VERSION } });
1050
+ const result = await withSpinner("Deleting...", () => nuntly.apiKeys.delete(id));
1051
+ printResult(result, opts);
1052
+ } catch (error) {
1053
+ printError(error, opts);
1054
+ }
1055
+ });
1056
+ apiKeysCommand.command("create").description("Generate a new API key. The key value is only returned once \u2014 store it securely.").option("--name <value>", "The name of the api key").option("--status <value>", "The status for the api key").option("--permission <value>", "The permission type for the api key (required)").option("--domain-ids <value>", "The domain ids to restrict the api key to (only for sendingAccess)").option("--file <path>", "Read JSON body from file (use - for stdin)").option("--format <fmt>", "Output format: json, raw, yaml, csv, markdown, table, quiet").option("-q, --quiet", "Shorthand for --format quiet").option("--raw", "Shorthand for --format raw").option("--fields <fields>", "Comma-separated list of fields to display").option("--no-header", "Omit column headers in table/csv output").addHelpText("after", "\nExample:\n $ nuntly api-keys create --permission sendingAccess\n $ cat payload.json | nuntly api-keys create\n $ nuntly api-keys create --file payload.json").action(async (opts) => {
1057
+ try {
1058
+ const nuntly = new Nuntly10({ apiKey: resolveApiKey(), baseUrl: resolveBaseUrl(), appInfo: { name: "@nuntly/cli", version: CLI_VERSION } });
1059
+ const body = opts.file ? readInput(opts.file) : !process.stdin.isTTY ? readInput("-") : {
1060
+ name: opts.name,
1061
+ status: opts.status,
1062
+ permission: opts.permission,
1063
+ domainIds: opts.domainIds != null ? opts.domainIds.split(",") : void 0
1064
+ };
1065
+ const result = await withSpinner("Creating...", () => nuntly.apiKeys.create(body));
1066
+ printResult(result, opts);
1067
+ } catch (error) {
1068
+ printError(error, opts);
1069
+ }
1070
+ });
1071
+ apiKeysCommand.command("list").description("Returns all API keys for the organization. Key values are never included in list responses.").option("--cursor <cursor>", "Pagination cursor").option("--limit <limit>", "Max items to return").option("--format <fmt>", "Output format: json, raw, yaml, csv, markdown, table, quiet").option("-q, --quiet", "Shorthand for --format quiet").option("--raw", "Shorthand for --format raw").option("--fields <fields>", "Comma-separated list of fields to display").option("--no-header", "Omit column headers in table/csv output").addHelpText("after", "\nExample:\n $ nuntly api-keys list\n $ nuntly api-keys list --format json | jq '.data[].id'").action(async (opts) => {
1072
+ try {
1073
+ const nuntly = new Nuntly10({ apiKey: resolveApiKey(), baseUrl: resolveBaseUrl(), appInfo: { name: "@nuntly/cli", version: CLI_VERSION } });
1074
+ const page = await withSpinner("Loading...", () => nuntly.apiKeys.list({ cursor: opts.cursor, limit: opts.limit ? Number(opts.limit) : void 0 }));
1075
+ printResult({ data: page.data, nextCursor: page.nextCursor }, opts);
1076
+ } catch (error) {
1077
+ printError(error, opts);
1078
+ }
1079
+ });
1080
+
1081
+ // src/completion.ts
1082
+ var COMMANDS = [
1083
+ "login",
1084
+ "profiles",
1085
+ "completion",
1086
+ "emails",
1087
+ "domains",
1088
+ "webhooks",
1089
+ "organizations",
1090
+ "inboxes",
1091
+ "agents",
1092
+ "threads",
1093
+ "messages",
1094
+ "namespaces",
1095
+ "api-keys"
1096
+ ];
1097
+ var SUBCOMMANDS = {
1098
+ emails: [
1099
+ "send",
1100
+ "list",
1101
+ "retrieve",
1102
+ "cancel",
1103
+ "stats",
1104
+ "events",
1105
+ "content",
1106
+ "bulk"
1107
+ ],
1108
+ domains: ["create", "list", "retrieve", "update", "delete"],
1109
+ webhooks: ["create", "list", "retrieve", "update", "delete", "events"],
1110
+ organizations: ["list", "retrieve", "usage"],
1111
+ inboxes: [
1112
+ "create",
1113
+ "list",
1114
+ "retrieve",
1115
+ "update",
1116
+ "delete",
1117
+ "threads",
1118
+ "messages"
1119
+ ],
1120
+ agents: ["memory"],
1121
+ threads: ["retrieve", "update", "messages"],
1122
+ messages: ["list", "retrieve", "reply", "forward", "content", "attachments"],
1123
+ namespaces: ["create", "list", "retrieve", "update", "delete", "inboxes"],
1124
+ "api-keys": ["create", "list", "retrieve", "update", "delete"]
1125
+ };
1126
+ function bashCompletion() {
1127
+ return `#!/bin/bash
1128
+ _nuntly_completions() {
1129
+ local cur prev commands
1130
+ cur="\${COMP_WORDS[COMP_CWORD]}"
1131
+ prev="\${COMP_WORDS[COMP_CWORD-1]}"
1132
+
1133
+ commands="${COMMANDS.join(" ")}"
1134
+
1135
+ case "\${COMP_CWORD}" in
1136
+ 1)
1137
+ COMPREPLY=( $(compgen -W "\${commands}" -- "\${cur}") )
1138
+ ;;
1139
+ 2)
1140
+ case "\${prev}" in
1141
+ ${Object.entries(SUBCOMMANDS).map(
1142
+ ([cmd, subs]) => ` ${cmd}) COMPREPLY=( $(compgen -W "${subs.join(" ")}" -- "\${cur}") ) ;;`
1143
+ ).join("\n")}
1144
+ *) ;;
1145
+ esac
1146
+ ;;
1147
+ *)
1148
+ COMPREPLY=( $(compgen -W "--json --yaml --markdown --table --quiet --profile --help" -- "\${cur}") )
1149
+ ;;
1150
+ esac
1151
+ }
1152
+ complete -F _nuntly_completions nuntly`;
1153
+ }
1154
+ function zshCompletion() {
1155
+ return `#compdef nuntly
1156
+
1157
+ _nuntly() {
1158
+ local -a commands
1159
+ commands=(
1160
+ ${COMMANDS.map((c) => ` '${c}:${c} command'`).join("\n")}
1161
+ )
1162
+
1163
+ _arguments -C \\
1164
+ '1:command:->command' \\
1165
+ '*::arg:->args'
1166
+
1167
+ case $state in
1168
+ command)
1169
+ _describe 'command' commands
1170
+ ;;
1171
+ args)
1172
+ case $words[1] in
1173
+ ${Object.entries(SUBCOMMANDS).map(
1174
+ ([cmd, subs]) => ` ${cmd}) _values 'subcommand' ${subs.map((s) => `'${s}'`).join(" ")} ;;`
1175
+ ).join("\n")}
1176
+ esac
1177
+ ;;
1178
+ esac
1179
+ }
1180
+
1181
+ _nuntly`;
1182
+ }
1183
+ function fishCompletion() {
1184
+ const lines = COMMANDS.map(
1185
+ (c) => `complete -c nuntly -n '__fish_use_subcommand' -a '${c}'`
1186
+ );
1187
+ for (const [cmd, subs] of Object.entries(SUBCOMMANDS)) {
1188
+ for (const sub of subs) {
1189
+ lines.push(
1190
+ `complete -c nuntly -n '__fish_seen_subcommand_from ${cmd}' -a '${sub}'`
1191
+ );
1192
+ }
1193
+ }
1194
+ return lines.join("\n");
1195
+ }
1196
+ function powershellCompletion() {
1197
+ const subBlocks = Object.entries(SUBCOMMANDS).map(
1198
+ ([cmd, subs]) => ` '${cmd}' { @(${subs.map((s) => `'${s}'`).join(", ")}) | Where-Object { $_ -like "$wordToComplete*" } | ForEach-Object { [System.Management.Automation.CompletionResult]::new($_, $_, 'ParameterValue', $_) } }`
1199
+ ).join("\n");
1200
+ return `Register-ArgumentCompleter -CommandName nuntly -ScriptBlock {
1201
+ param($wordToComplete, $commandAst, $cursorPosition)
1202
+ $elements = $commandAst.CommandElements
1203
+ if ($elements.Count -le 2) {
1204
+ @(${COMMANDS.map((c) => `'${c}'`).join(", ")}) | Where-Object { $_ -like "$wordToComplete*" } | ForEach-Object { [System.Management.Automation.CompletionResult]::new($_, $_, 'ParameterValue', $_) }
1205
+ } else {
1206
+ $cmd = $elements[1].ToString()
1207
+ switch ($cmd) {
1208
+ ${subBlocks}
1209
+ }
1210
+ }
1211
+ }`;
1212
+ }
1213
+
1214
+ // src/index.ts
1215
+ var program = new Command11().name("nuntly").description(pc4.bold("Nuntly CLI") + " - Developer-first email platform").version("0.0.1").option("--profile <name>", "Use a specific profile from ~/.nuntly/config.json");
1216
+ program.command("login").description("Save your API key to ~/.nuntly/config.json").argument("[profile]", 'Profile name (default: "default")').action(async (profile) => {
1217
+ await login(profile);
1218
+ });
1219
+ program.command("profiles").description("List configured profiles").action(() => {
1220
+ const profiles = listProfiles();
1221
+ if (profiles.length === 0) console.log(pc4.dim("No profiles configured. Run: nuntly login"));
1222
+ else profiles.forEach((n) => console.log(n));
1223
+ });
1224
+ program.command("completion").description("Output shell completion script").argument("<shell>", "Shell type: bash, zsh, fish, or powershell").action((shell) => {
1225
+ switch (shell) {
1226
+ case "bash":
1227
+ console.log(bashCompletion());
1228
+ break;
1229
+ case "zsh":
1230
+ console.log(zshCompletion());
1231
+ break;
1232
+ case "fish":
1233
+ console.log(fishCompletion());
1234
+ break;
1235
+ case "powershell":
1236
+ console.log(powershellCompletion());
1237
+ break;
1238
+ default:
1239
+ console.error(pc4.red("Unknown shell: " + shell));
1240
+ process.exit(1);
1241
+ }
1242
+ });
1243
+ program.addCommand(emailsCommand);
1244
+ program.addCommand(domainsCommand);
1245
+ program.addCommand(webhooksCommand);
1246
+ program.addCommand(organizationsCommand);
1247
+ program.addCommand(inboxesCommand);
1248
+ program.addCommand(agentsCommand);
1249
+ program.addCommand(threadsCommand);
1250
+ program.addCommand(messagesCommand);
1251
+ program.addCommand(namespacesCommand);
1252
+ program.addCommand(apiKeysCommand);
1253
+ program.parse();
1254
+ //# sourceMappingURL=index.js.map