carlyemail 0.1.0 → 0.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +58 -5
- package/carlyemail.js +279 -8
- package/package.json +23 -5
- package/sdk.d.ts +777 -0
- package/sdk.js +791 -0
package/README.md
CHANGED
|
@@ -1,13 +1,57 @@
|
|
|
1
1
|
# carlyemail
|
|
2
2
|
|
|
3
|
-
Real email inboxes your agent can send, receive and reply from.
|
|
3
|
+
Real email inboxes your agent can send, receive and reply from. One package,
|
|
4
|
+
two things: a typed client and a command line.
|
|
5
|
+
|
|
6
|
+
## The client
|
|
7
|
+
|
|
8
|
+
```bash
|
|
9
|
+
npm install carlyemail
|
|
10
|
+
```
|
|
11
|
+
|
|
12
|
+
```javascript
|
|
13
|
+
import { CarlyEmail } from "carlyemail";
|
|
14
|
+
|
|
15
|
+
const carly = new CarlyEmail(); // reads CARLYEMAIL_API_KEY
|
|
16
|
+
|
|
17
|
+
const inbox = await carly.inboxes.create({ username: "hello" });
|
|
18
|
+
|
|
19
|
+
await carly.messages.send(inbox.email, {
|
|
20
|
+
to: ["you@example.com"],
|
|
21
|
+
subject: "Hello",
|
|
22
|
+
text: "From an agent.",
|
|
23
|
+
});
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
Generated from the [OpenAPI spec](https://docs.carlyemail.com/openapi.json), so
|
|
27
|
+
it cannot describe an endpoint the API does not serve. One `fetch` call per
|
|
28
|
+
method and no dependencies, so it runs in Node, the browser, a worker and on the
|
|
29
|
+
edge with no build step. Types ship with it.
|
|
30
|
+
|
|
31
|
+
Errors carry the message, the fix and a documentation link:
|
|
32
|
+
|
|
33
|
+
```javascript
|
|
34
|
+
import { CarlyEmailError } from "carlyemail";
|
|
35
|
+
|
|
36
|
+
try {
|
|
37
|
+
await carly.inboxes.create({ username: "hello" });
|
|
38
|
+
} catch (error) {
|
|
39
|
+
if (error instanceof CarlyEmailError) {
|
|
40
|
+
console.log(error.status, error.code, error.fix, error.docs);
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
There is a Python client too: `pip install carlyemail`.
|
|
46
|
+
|
|
47
|
+
## The command line
|
|
4
48
|
|
|
5
49
|
```bash
|
|
6
|
-
npx carlyemail signup
|
|
50
|
+
npx carlyemail signup
|
|
7
51
|
```
|
|
8
52
|
|
|
9
|
-
|
|
10
|
-
can send:
|
|
53
|
+
It asks for your email, offers an inbox name, and writes the key to
|
|
54
|
+
`~/.carlyemail/config.json`. Confirm the emailed code and it can send:
|
|
11
55
|
|
|
12
56
|
```bash
|
|
13
57
|
npx carlyemail verify 123456
|
|
@@ -18,7 +62,7 @@ npx carlyemail send \
|
|
|
18
62
|
--text "Sent by an agent with its own address."
|
|
19
63
|
```
|
|
20
64
|
|
|
21
|
-
Install it
|
|
65
|
+
Install it if you use it often:
|
|
22
66
|
|
|
23
67
|
```bash
|
|
24
68
|
npm install -g carlyemail
|
|
@@ -36,6 +80,15 @@ npm install -g carlyemail
|
|
|
36
80
|
| `delete <inbox> --yes` | Delete an inbox and its mail |
|
|
37
81
|
| `send --from <i> --to <a> --subject <s> --text <t>` | Send an email |
|
|
38
82
|
| `messages <inbox>` | List messages |
|
|
83
|
+
| `read <inbox> <id>` | Read one message in full |
|
|
84
|
+
| `search <inbox> <text>` | Search messages |
|
|
85
|
+
| `threads <inbox>` | List conversations |
|
|
86
|
+
| `reply <inbox> <id> --text <t>` | Reply to the sender (`--all` to copy everyone) |
|
|
87
|
+
| `drafts <inbox>` | List drafts |
|
|
88
|
+
| `draft <inbox> --to <a> --text <t>` | Write a draft without sending |
|
|
89
|
+
| `send-draft <inbox> <id>` | Send a draft written earlier |
|
|
90
|
+
| `webhooks` | List webhook endpoints |
|
|
91
|
+
| `domains` | List custom domains, with their DNS records |
|
|
39
92
|
| `plan` | Current plan, with usage against every limit |
|
|
40
93
|
| `upgrade <plan>` | A Stripe checkout link |
|
|
41
94
|
| `billing` | Invoices, card changes, cancellation |
|
package/carlyemail.js
CHANGED
|
@@ -16,8 +16,9 @@ import { readFileSync, writeFileSync, mkdirSync, existsSync, chmodSync, realpath
|
|
|
16
16
|
import { homedir } from "node:os";
|
|
17
17
|
import { join } from "node:path";
|
|
18
18
|
import { fileURLToPath } from "node:url";
|
|
19
|
+
import { randomBytes } from "node:crypto";
|
|
19
20
|
|
|
20
|
-
export const VERSION = "0.
|
|
21
|
+
export const VERSION = "0.3.0";
|
|
21
22
|
|
|
22
23
|
const DEFAULT_API = "https://api.carlyemail.com";
|
|
23
24
|
const CONFIG_DIR = join(homedir(), ".carlyemail");
|
|
@@ -211,13 +212,34 @@ function emit(ctx, payload, render) {
|
|
|
211
212
|
render();
|
|
212
213
|
}
|
|
213
214
|
|
|
215
|
+
/** A name to offer when somebody has no preference. Short enough to type again. */
|
|
216
|
+
function suggestUsername() {
|
|
217
|
+
return `agent-${randomBytes(2).toString("hex")}`;
|
|
218
|
+
}
|
|
219
|
+
|
|
214
220
|
define(
|
|
215
221
|
"signup",
|
|
216
222
|
"Create an account and an inbox",
|
|
217
|
-
"carlyemail signup --human-email you@example.com --username my-agent",
|
|
223
|
+
"carlyemail signup (or: --human-email you@example.com --username my-agent)",
|
|
218
224
|
async (ctx) => {
|
|
219
|
-
|
|
220
|
-
|
|
225
|
+
// `signup` is the first command anybody runs, and refusing it with a usage
|
|
226
|
+
// error is a bad way to say hello. At a terminal, ask.
|
|
227
|
+
let human_email = ctx.flags["human-email"];
|
|
228
|
+
if (typeof human_email !== "string") {
|
|
229
|
+
const answer = await ctx.ask("Your email (for the confirmation code): ");
|
|
230
|
+
if (answer) human_email = answer;
|
|
231
|
+
}
|
|
232
|
+
if (!human_email) throw new UsageError("--human-email is required");
|
|
233
|
+
|
|
234
|
+
let username = ctx.flags.username;
|
|
235
|
+
if (typeof username !== "string") {
|
|
236
|
+
const suggested = suggestUsername();
|
|
237
|
+
const answer = await ctx.ask(`Inbox name [${suggested}]: `);
|
|
238
|
+
// null is "no terminal", which still has to fail. "" is a bare Enter,
|
|
239
|
+
// which means take the suggestion.
|
|
240
|
+
if (answer !== null) username = answer || suggested;
|
|
241
|
+
}
|
|
242
|
+
if (!username) throw new UsageError("--username is required");
|
|
221
243
|
|
|
222
244
|
const out = await request(ctx.config, "POST", "/v0/agent/sign-up", {
|
|
223
245
|
auth: false,
|
|
@@ -336,7 +358,236 @@ define("messages", "List messages in an inbox", "carlyemail messages me@carlyema
|
|
|
336
358
|
for (const m of out.messages) {
|
|
337
359
|
const when = (m.timestamp || "").slice(0, 16).replace("T", " ");
|
|
338
360
|
ctx.print(`${dim(when)} ${bold(m.subject || "(no subject)")}`);
|
|
339
|
-
|
|
361
|
+
// The id goes on screen because every other command takes one. Listing
|
|
362
|
+
// messages and then having to go to the API to find out what to call
|
|
363
|
+
// `read` or `reply` with was the gap people kept falling into.
|
|
364
|
+
const id = m.message_id ? ` ${m.message_id}` : "";
|
|
365
|
+
ctx.print(`${" ".repeat(18)}${dim(`from ${m.from ?? m.from_address ?? "?"}${id}`)}`);
|
|
366
|
+
}
|
|
367
|
+
});
|
|
368
|
+
});
|
|
369
|
+
|
|
370
|
+
define("read", "Read one message in full", "carlyemail read me@carlyemail.com '<id@host>'", async (ctx) => {
|
|
371
|
+
const [inbox, id] = ctx.positional;
|
|
372
|
+
if (!inbox || !id) throw new UsageError("carlyemail read <inbox> <message-id>");
|
|
373
|
+
const m = await request(
|
|
374
|
+
ctx.config,
|
|
375
|
+
"GET",
|
|
376
|
+
`/v0/inboxes/${encodeURIComponent(inbox)}/messages/${encodeURIComponent(id)}`
|
|
377
|
+
);
|
|
378
|
+
emit(ctx, m, () => {
|
|
379
|
+
ctx.print(`${dim("from")} ${m.from ?? "?"}`);
|
|
380
|
+
ctx.print(`${dim("to")} ${(m.to || []).join(", ")}`);
|
|
381
|
+
ctx.print(`${dim("date")} ${(m.timestamp || "").replace("T", " ").slice(0, 19)}`);
|
|
382
|
+
ctx.print(`${dim("subject")} ${bold(m.subject || "(no subject)")}`);
|
|
383
|
+
if (m.labels?.length) ctx.print(`${dim("labels")} ${m.labels.join(", ")}`);
|
|
384
|
+
if (m.attachments?.length) {
|
|
385
|
+
ctx.print(`${dim("files")} ${m.attachments.map((a) => a.filename ?? a.attachment_id).join(", ")}`);
|
|
386
|
+
}
|
|
387
|
+
ctx.print("");
|
|
388
|
+
// `text` is the body as sent; `extracted_text` has the quoted reply chain
|
|
389
|
+
// stripped. Print the full one — a person reading a single message asked
|
|
390
|
+
// for the message, not our opinion of the interesting part of it.
|
|
391
|
+
ctx.print(m.text || m.preview || dim("(no text body)"));
|
|
392
|
+
});
|
|
393
|
+
});
|
|
394
|
+
|
|
395
|
+
define("search", "Search messages in an inbox", 'carlyemail search me@carlyemail.com "invoice"', async (ctx) => {
|
|
396
|
+
const [inbox, ...terms] = ctx.positional;
|
|
397
|
+
const query = terms.join(" ") || ctx.flags.query;
|
|
398
|
+
if (!inbox || !query) throw new UsageError('carlyemail search <inbox> "what to look for"');
|
|
399
|
+
const out = await request(
|
|
400
|
+
ctx.config,
|
|
401
|
+
"GET",
|
|
402
|
+
// `q`, not `query` — the parameter name comes from the OpenAPI spec, and
|
|
403
|
+
// guessing it produced a 422 that looked like a CLI bug.
|
|
404
|
+
`/v0/inboxes/${encodeURIComponent(inbox)}/messages/search?q=${encodeURIComponent(query)}`
|
|
405
|
+
);
|
|
406
|
+
emit(ctx, out, () => {
|
|
407
|
+
if (!out.messages?.length) {
|
|
408
|
+
ctx.print(dim(`nothing matching ${JSON.stringify(query)}`));
|
|
409
|
+
return;
|
|
410
|
+
}
|
|
411
|
+
for (const m of out.messages) {
|
|
412
|
+
ctx.print(`${dim((m.timestamp || "").slice(0, 10))} ${bold(m.subject || "(no subject)")}`);
|
|
413
|
+
ctx.print(`${" ".repeat(12)}${dim(m.message_id ?? "")}`);
|
|
414
|
+
}
|
|
415
|
+
});
|
|
416
|
+
});
|
|
417
|
+
|
|
418
|
+
define("threads", "List conversations in an inbox", "carlyemail threads me@carlyemail.com", async (ctx) => {
|
|
419
|
+
const inbox = ctx.positional[0];
|
|
420
|
+
if (!inbox) throw new UsageError("carlyemail threads <inbox>");
|
|
421
|
+
const out = await request(ctx.config, "GET", `/v0/inboxes/${encodeURIComponent(inbox)}/threads`);
|
|
422
|
+
emit(ctx, out, () => {
|
|
423
|
+
if (!out.threads?.length) {
|
|
424
|
+
ctx.print(dim("no conversations"));
|
|
425
|
+
return;
|
|
426
|
+
}
|
|
427
|
+
for (const t of out.threads) {
|
|
428
|
+
const count = t.message_count ? dim(` (${t.message_count})`) : "";
|
|
429
|
+
ctx.print(`${bold(t.subject || "(no subject)")}${count}`);
|
|
430
|
+
ctx.print(` ${dim(t.thread_id)} ${dim((t.senders || []).join(", "))}`);
|
|
431
|
+
}
|
|
432
|
+
});
|
|
433
|
+
});
|
|
434
|
+
|
|
435
|
+
/** The newest message in an inbox that the inbox did not itself send. */
|
|
436
|
+
async function lastReceived(ctx, inbox) {
|
|
437
|
+
const out = await request(
|
|
438
|
+
ctx.config,
|
|
439
|
+
"GET",
|
|
440
|
+
`/v0/inboxes/${encodeURIComponent(inbox)}/messages?limit=10`
|
|
441
|
+
);
|
|
442
|
+
// Sent mail is stored in the inbox alongside what arrived, so the newest
|
|
443
|
+
// message is often the agent's own last reply. Replying to yourself is never
|
|
444
|
+
// what `--last` means.
|
|
445
|
+
const mine = inbox.toLowerCase();
|
|
446
|
+
const found = (out.messages || []).find(
|
|
447
|
+
(m) => !String(m.from ?? m.from_address ?? "").toLowerCase().includes(mine)
|
|
448
|
+
);
|
|
449
|
+
if (!found) throw new UsageError(`nothing to reply to in ${inbox}`);
|
|
450
|
+
return found.message_id;
|
|
451
|
+
}
|
|
452
|
+
|
|
453
|
+
define("reply", "Reply to a message", 'carlyemail reply me@x.com --last --text "On it"', async (ctx) => {
|
|
454
|
+
const [inbox, given] = ctx.positional;
|
|
455
|
+
if (!inbox) throw new UsageError("carlyemail reply <inbox> <message-id> --text ...");
|
|
456
|
+
if (!given && !ctx.flags.last) {
|
|
457
|
+
throw new UsageError("carlyemail reply <inbox> <message-id> --text ... (or --last)");
|
|
458
|
+
}
|
|
459
|
+
const body = {
|
|
460
|
+
text: typeof ctx.flags.text === "string" ? ctx.flags.text : undefined,
|
|
461
|
+
html: typeof ctx.flags.html === "string" ? ctx.flags.html : undefined,
|
|
462
|
+
};
|
|
463
|
+
// Checked before `--last` costs a request, and long before anything is sent.
|
|
464
|
+
if (body.text === undefined && body.html === undefined) {
|
|
465
|
+
throw new UsageError("--text or --html is required");
|
|
466
|
+
}
|
|
467
|
+
const id = given || (await lastReceived(ctx, inbox));
|
|
468
|
+
// reply-all is opt-in. Quietly copying everyone on the original thread is
|
|
469
|
+
// the kind of default that sends an agent's message to people the caller
|
|
470
|
+
// never saw.
|
|
471
|
+
const route = ctx.flags["all"] ? "reply-all" : "reply";
|
|
472
|
+
const sent = await request(
|
|
473
|
+
ctx.config,
|
|
474
|
+
"POST",
|
|
475
|
+
`/v0/inboxes/${encodeURIComponent(inbox)}/messages/${encodeURIComponent(id)}/${route}`,
|
|
476
|
+
{ body }
|
|
477
|
+
);
|
|
478
|
+
emit(ctx, sent, () => ctx.print(ok(`replied ${dim(sent.message_id ?? "")}`)));
|
|
479
|
+
});
|
|
480
|
+
|
|
481
|
+
define("drafts", "List drafts in an inbox", "carlyemail drafts me@carlyemail.com", async (ctx) => {
|
|
482
|
+
const inbox = ctx.positional[0];
|
|
483
|
+
if (!inbox) throw new UsageError("carlyemail drafts <inbox>");
|
|
484
|
+
const out = await request(ctx.config, "GET", `/v0/inboxes/${encodeURIComponent(inbox)}/drafts`);
|
|
485
|
+
emit(ctx, out, () => {
|
|
486
|
+
if (!out.drafts?.length) {
|
|
487
|
+
ctx.print(dim("no drafts"));
|
|
488
|
+
return;
|
|
489
|
+
}
|
|
490
|
+
for (const d of out.drafts) {
|
|
491
|
+
ctx.print(`${bold(d.subject || "(no subject)")} ${dim(`to ${(d.to || []).join(", ")}`)}`);
|
|
492
|
+
ctx.print(` ${dim(d.draft_id)}`);
|
|
493
|
+
}
|
|
494
|
+
});
|
|
495
|
+
});
|
|
496
|
+
|
|
497
|
+
define(
|
|
498
|
+
"draft",
|
|
499
|
+
"Write a draft without sending it",
|
|
500
|
+
'carlyemail draft me@x.com --to you@y.com --subject Hi --text "..."',
|
|
501
|
+
async (ctx) => {
|
|
502
|
+
const inbox = ctx.positional[0];
|
|
503
|
+
if (!inbox) throw new UsageError("carlyemail draft <inbox> --to ... --text ...");
|
|
504
|
+
const body = {
|
|
505
|
+
to: required(ctx.flags, "to").split(",").map((s) => s.trim()),
|
|
506
|
+
subject: typeof ctx.flags.subject === "string" ? ctx.flags.subject : undefined,
|
|
507
|
+
text: typeof ctx.flags.text === "string" ? ctx.flags.text : undefined,
|
|
508
|
+
};
|
|
509
|
+
const d = await request(
|
|
510
|
+
ctx.config,
|
|
511
|
+
"POST",
|
|
512
|
+
`/v0/inboxes/${encodeURIComponent(inbox)}/drafts`,
|
|
513
|
+
{ body }
|
|
514
|
+
);
|
|
515
|
+
emit(ctx, d, () => {
|
|
516
|
+
ctx.print(ok(`draft ${d.draft_id}`));
|
|
517
|
+
ctx.print(arrow(`send it with: carlyemail send-draft ${inbox} ${d.draft_id}`));
|
|
518
|
+
});
|
|
519
|
+
}
|
|
520
|
+
);
|
|
521
|
+
|
|
522
|
+
define("send-draft", "Send a draft that was written earlier", "carlyemail send-draft me@x.com dft_123", async (ctx) => {
|
|
523
|
+
const [inbox, id] = ctx.positional;
|
|
524
|
+
if (!inbox || !id) throw new UsageError("carlyemail send-draft <inbox> <draft-id>");
|
|
525
|
+
const sent = await request(
|
|
526
|
+
ctx.config,
|
|
527
|
+
"POST",
|
|
528
|
+
`/v0/inboxes/${encodeURIComponent(inbox)}/drafts/${encodeURIComponent(id)}/send`
|
|
529
|
+
);
|
|
530
|
+
emit(ctx, sent, () => ctx.print(ok(`sent ${dim(sent.message_id ?? "")}`)));
|
|
531
|
+
});
|
|
532
|
+
|
|
533
|
+
define(
|
|
534
|
+
"webhook",
|
|
535
|
+
"Create a webhook endpoint",
|
|
536
|
+
"carlyemail webhook https://you.example/mail --events message.received",
|
|
537
|
+
async (ctx) => {
|
|
538
|
+
const url = ctx.positional[0];
|
|
539
|
+
if (!url) throw new UsageError("carlyemail webhook <url> --events message.received");
|
|
540
|
+
const events =
|
|
541
|
+
typeof ctx.flags.events === "string"
|
|
542
|
+
? ctx.flags.events.split(",").map((e) => e.trim()).filter(Boolean)
|
|
543
|
+
: ["message.received"];
|
|
544
|
+
|
|
545
|
+
const made = await request(ctx.config, "POST", "/v0/webhooks", {
|
|
546
|
+
body: { url, event_types: events },
|
|
547
|
+
});
|
|
548
|
+
emit(ctx, made, () => {
|
|
549
|
+
ctx.print(ok(`webhook ${dim(made.webhook_id ?? "")}`));
|
|
550
|
+
// Returned once and never again. Printing it without saying so is how
|
|
551
|
+
// people end up recreating the webhook to get a secret back.
|
|
552
|
+
if (made.secret) {
|
|
553
|
+
ctx.print(` secret ${made.secret}`);
|
|
554
|
+
ctx.print(dim(" shown once — store it, you need it to verify deliveries"));
|
|
555
|
+
}
|
|
556
|
+
});
|
|
557
|
+
}
|
|
558
|
+
);
|
|
559
|
+
|
|
560
|
+
define("webhooks", "List webhook endpoints", "carlyemail webhooks", async (ctx) => {
|
|
561
|
+
const out = await request(ctx.config, "GET", "/v0/webhooks");
|
|
562
|
+
emit(ctx, out, () => {
|
|
563
|
+
if (!out.webhooks?.length) {
|
|
564
|
+
ctx.print(dim("no webhooks"));
|
|
565
|
+
return;
|
|
566
|
+
}
|
|
567
|
+
for (const w of out.webhooks) {
|
|
568
|
+
ctx.print(`${w.enabled === false ? dim("(disabled) ") : ""}${bold(w.url)}`);
|
|
569
|
+
ctx.print(` ${dim(w.webhook_id)} ${dim((w.event_types || []).join(", "))}`);
|
|
570
|
+
}
|
|
571
|
+
});
|
|
572
|
+
});
|
|
573
|
+
|
|
574
|
+
define("domains", "List custom sending domains", "carlyemail domains", async (ctx) => {
|
|
575
|
+
const out = await request(ctx.config, "GET", "/v0/domains");
|
|
576
|
+
emit(ctx, out, () => {
|
|
577
|
+
if (!out.domains?.length) {
|
|
578
|
+
ctx.print(dim("no custom domains — mail sends from carlyemail.com"));
|
|
579
|
+
return;
|
|
580
|
+
}
|
|
581
|
+
for (const d of out.domains) {
|
|
582
|
+
const mark = d.status === "VERIFIED" ? paint(32, "✓") : dim("…");
|
|
583
|
+
ctx.print(`${mark} ${bold(d.domain)} ${dim(d.status)}`);
|
|
584
|
+
// Unverified domains are the common case people get stuck on, and the
|
|
585
|
+
// records are the whole answer, so print them rather than a doc link.
|
|
586
|
+
if (d.status !== "VERIFIED") {
|
|
587
|
+
for (const r of d.records || []) {
|
|
588
|
+
ctx.print(` ${dim(r.type)} ${r.name} ${dim("→")} ${r.value}`);
|
|
589
|
+
}
|
|
590
|
+
}
|
|
340
591
|
}
|
|
341
592
|
});
|
|
342
593
|
});
|
|
@@ -408,7 +659,7 @@ export function helpText() {
|
|
|
408
659
|
lines.push(
|
|
409
660
|
"",
|
|
410
661
|
bold("Getting started"),
|
|
411
|
-
" carlyemail signup
|
|
662
|
+
" carlyemail signup",
|
|
412
663
|
" carlyemail verify 123456",
|
|
413
664
|
" carlyemail send --from my-agent@agents.carlyemail.com \\",
|
|
414
665
|
' --to someone@example.com --subject Hi --text "Hello from an agent"',
|
|
@@ -428,7 +679,27 @@ export function helpText() {
|
|
|
428
679
|
|
|
429
680
|
// ---------------------------------------------------------------- entry
|
|
430
681
|
|
|
431
|
-
|
|
682
|
+
/**
|
|
683
|
+
* Read one line from the terminal.
|
|
684
|
+
*
|
|
685
|
+
* Returns null when there is no terminal — a CI job or a piped script must get
|
|
686
|
+
* the usage error it can act on, not a prompt nobody will ever answer.
|
|
687
|
+
*/
|
|
688
|
+
async function askTerminal(question) {
|
|
689
|
+
if (!process.stdin.isTTY) return null;
|
|
690
|
+
const { createInterface } = await import("node:readline/promises");
|
|
691
|
+
const rl = createInterface({ input: process.stdin, output: process.stdout });
|
|
692
|
+
try {
|
|
693
|
+
return (await rl.question(question)).trim();
|
|
694
|
+
} finally {
|
|
695
|
+
rl.close();
|
|
696
|
+
}
|
|
697
|
+
}
|
|
698
|
+
|
|
699
|
+
export async function main(
|
|
700
|
+
argv,
|
|
701
|
+
{ env = process.env, print = console.log, ask = askTerminal, configFile, configDir } = {}
|
|
702
|
+
) {
|
|
432
703
|
const { flags, positional } = parseArgs(argv);
|
|
433
704
|
const name = positional.shift();
|
|
434
705
|
|
|
@@ -454,7 +725,7 @@ export async function main(argv, { env = process.env, print = console.log, confi
|
|
|
454
725
|
|
|
455
726
|
const config = loadConfig(env, configFile);
|
|
456
727
|
try {
|
|
457
|
-
await command.run({ config, flags, positional, print, configFile, configDir });
|
|
728
|
+
await command.run({ config, flags, positional, print, ask, configFile, configDir });
|
|
458
729
|
return 0;
|
|
459
730
|
} catch (error) {
|
|
460
731
|
if (error instanceof ApiError) {
|
package/package.json
CHANGED
|
@@ -1,8 +1,16 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "carlyemail",
|
|
3
|
-
"version": "0.
|
|
4
|
-
"description": "Real email inboxes your agent can send, receive and reply from.",
|
|
5
|
-
"keywords": [
|
|
3
|
+
"version": "0.3.0",
|
|
4
|
+
"description": "Real email inboxes your agent can send, receive and reply from. SDK and CLI.",
|
|
5
|
+
"keywords": [
|
|
6
|
+
"email",
|
|
7
|
+
"agent",
|
|
8
|
+
"ai",
|
|
9
|
+
"inbox",
|
|
10
|
+
"smtp",
|
|
11
|
+
"mcp",
|
|
12
|
+
"cli"
|
|
13
|
+
],
|
|
6
14
|
"homepage": "https://carlyemail.com",
|
|
7
15
|
"bugs": "https://docs.carlyemail.com/support",
|
|
8
16
|
"repository": {
|
|
@@ -16,8 +24,18 @@
|
|
|
16
24
|
"bin": {
|
|
17
25
|
"carlyemail": "./carlyemail.js"
|
|
18
26
|
},
|
|
19
|
-
"
|
|
20
|
-
"
|
|
27
|
+
"types": "./sdk.d.ts",
|
|
28
|
+
"exports": {
|
|
29
|
+
".": "./sdk.js",
|
|
30
|
+
"./cli": "./carlyemail.js"
|
|
31
|
+
},
|
|
32
|
+
"files": [
|
|
33
|
+
"carlyemail.js",
|
|
34
|
+
"sdk.js",
|
|
35
|
+
"sdk.d.ts",
|
|
36
|
+
"README.md",
|
|
37
|
+
"LICENSE"
|
|
38
|
+
],
|
|
21
39
|
"engines": {
|
|
22
40
|
"node": ">=18"
|
|
23
41
|
},
|