carlyemail 0.4.0 → 0.6.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/carlyemail.js +113 -14
- package/package.json +2 -7
- package/sdk.d.ts +1 -0
package/carlyemail.js
CHANGED
|
@@ -18,7 +18,7 @@ import { join } from "node:path";
|
|
|
18
18
|
import { fileURLToPath } from "node:url";
|
|
19
19
|
import { randomBytes } from "node:crypto";
|
|
20
20
|
|
|
21
|
-
export const VERSION = "0.
|
|
21
|
+
export const VERSION = "0.6.0";
|
|
22
22
|
|
|
23
23
|
const DEFAULT_API = "https://api.carlyemail.com";
|
|
24
24
|
const CONFIG_DIR = join(homedir(), ".carlyemail");
|
|
@@ -229,6 +229,28 @@ function suggestUsername() {
|
|
|
229
229
|
return `agent-${randomBytes(2).toString("hex")}`;
|
|
230
230
|
}
|
|
231
231
|
|
|
232
|
+
/**
|
|
233
|
+
* Ask the server to email a sign-in code and remember which address it went to,
|
|
234
|
+
* so a later `verify` knows to exchange the code for a key rather than confirm
|
|
235
|
+
* a sign-up. `known` distinguishes "you told me this account exists" (signup
|
|
236
|
+
* got `account_exists`) from the server's deliberately non-committal answer.
|
|
237
|
+
*/
|
|
238
|
+
async function startSignin(ctx, human_email, { known = false } = {}) {
|
|
239
|
+
await request(ctx.config, "POST", "/v0/agent/sign-in", {
|
|
240
|
+
auth: false,
|
|
241
|
+
body: { human_email },
|
|
242
|
+
});
|
|
243
|
+
saveConfig({ ...ctx.config, pending_signin: human_email }, ctx.configFile, ctx.configDir);
|
|
244
|
+
ctx.print(
|
|
245
|
+
arrow(
|
|
246
|
+
known
|
|
247
|
+
? `a sign-in code is on its way to ${human_email} (good for 10 minutes), then:`
|
|
248
|
+
: `if ${human_email} has an account, a code is on its way (good for 10 minutes), then:`
|
|
249
|
+
)
|
|
250
|
+
);
|
|
251
|
+
ctx.print(` carlyemail verify <code>`);
|
|
252
|
+
}
|
|
253
|
+
|
|
232
254
|
define(
|
|
233
255
|
"signup",
|
|
234
256
|
"Create an account and an inbox",
|
|
@@ -253,19 +275,38 @@ define(
|
|
|
253
275
|
}
|
|
254
276
|
if (!username) throw new UsageError("--username is required");
|
|
255
277
|
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
278
|
+
// Not prompted for. Two questions is a sign-up; three is a form, and this
|
|
279
|
+
// one has a default that works and a `carlyemail name` to change it later.
|
|
280
|
+
const display_name =
|
|
281
|
+
typeof ctx.flags["display-name"] === "string" ? ctx.flags["display-name"] : undefined;
|
|
282
|
+
|
|
283
|
+
let out;
|
|
284
|
+
try {
|
|
285
|
+
out = await request(ctx.config, "POST", "/v0/agent/sign-up", {
|
|
286
|
+
auth: false,
|
|
287
|
+
body: { human_email, username, source: "cli", display_name },
|
|
288
|
+
});
|
|
289
|
+
} catch (error) {
|
|
290
|
+
// An existing account is not a dead end. Fall through to the sign-in
|
|
291
|
+
// flow the same way the console does, without being asked — the person
|
|
292
|
+
// typed their address wanting a working key, not an error about which
|
|
293
|
+
// command would have produced one.
|
|
294
|
+
if (error instanceof ApiError && error.code === "account_exists") {
|
|
295
|
+
ctx.print(`${human_email} already has an account — signing in instead.`);
|
|
296
|
+
await startSignin(ctx, human_email, { known: true });
|
|
297
|
+
return;
|
|
298
|
+
}
|
|
299
|
+
throw error;
|
|
300
|
+
}
|
|
260
301
|
|
|
261
302
|
// Saved before anything else is printed: the key is returned exactly once
|
|
262
303
|
// and is unrecoverable afterwards, so losing it to a later crash would
|
|
263
304
|
// cost the account.
|
|
264
|
-
const
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
);
|
|
305
|
+
const config = { ...ctx.config, api_key: out.api_key, organization_id: out.organization_id };
|
|
306
|
+
// A sign-in abandoned before its code arrived must not re-route the
|
|
307
|
+
// `verify` that belongs to this fresh sign-up.
|
|
308
|
+
delete config.pending_signin;
|
|
309
|
+
const saved = saveConfig(config, ctx.configFile, ctx.configDir);
|
|
269
310
|
|
|
270
311
|
ctx.print(ok(bold(out.inbox_id)));
|
|
271
312
|
ctx.print(ok(`key saved to ${saved}`));
|
|
@@ -273,7 +314,22 @@ define(
|
|
|
273
314
|
ctx.print(arrow(`check ${human_email} for a 6-digit code, then:`));
|
|
274
315
|
ctx.print(` carlyemail verify <code>`);
|
|
275
316
|
ctx.print("");
|
|
276
|
-
ctx.print(dim(
|
|
317
|
+
ctx.print(dim(`Until the code is confirmed the account can only email ${human_email}.`));
|
|
318
|
+
}
|
|
319
|
+
);
|
|
320
|
+
|
|
321
|
+
define(
|
|
322
|
+
"signin",
|
|
323
|
+
"Sign in to an existing account with an emailed code",
|
|
324
|
+
"carlyemail signin you@example.com",
|
|
325
|
+
async (ctx) => {
|
|
326
|
+
let human_email = ctx.positional[0] || ctx.flags["human-email"];
|
|
327
|
+
if (typeof human_email !== "string" || !human_email) {
|
|
328
|
+
const answer = await ctx.ask("The account's owner email: ");
|
|
329
|
+
if (answer) human_email = answer;
|
|
330
|
+
}
|
|
331
|
+
if (!human_email) throw new UsageError("carlyemail signin you@example.com");
|
|
332
|
+
await startSignin(ctx, human_email);
|
|
277
333
|
}
|
|
278
334
|
);
|
|
279
335
|
|
|
@@ -281,8 +337,24 @@ define("verify", "Confirm the owner email with the code", "carlyemail verify 123
|
|
|
281
337
|
const code = ctx.positional[0] || ctx.flags.code;
|
|
282
338
|
if (!code) throw new UsageError("the 6-digit code is required: carlyemail verify 123456");
|
|
283
339
|
|
|
340
|
+
// A pending sign-in and a sign-up confirmation are the same step to the
|
|
341
|
+
// person typing the code, but different calls: one trades the code for a new
|
|
342
|
+
// key, the other upgrades the key sign-up already saved. The config
|
|
343
|
+
// remembers which one is open.
|
|
344
|
+
if (ctx.config.pending_signin) {
|
|
345
|
+
const out = await request(ctx.config, "POST", "/v0/agent/sign-in/verify", {
|
|
346
|
+
auth: false,
|
|
347
|
+
body: { human_email: ctx.config.pending_signin, otp_code: String(code) },
|
|
348
|
+
});
|
|
349
|
+
const config = { ...ctx.config, api_key: out.api_key, organization_id: out.organization_id };
|
|
350
|
+
delete config.pending_signin;
|
|
351
|
+
const saved = saveConfig(config, ctx.configFile, ctx.configDir);
|
|
352
|
+
ctx.print(ok(`signed in — key saved to ${saved}`));
|
|
353
|
+
return;
|
|
354
|
+
}
|
|
355
|
+
|
|
284
356
|
await request(ctx.config, "POST", "/v0/agent/verify", { body: { otp_code: String(code) } });
|
|
285
|
-
ctx.print(ok("verified — this account can send now"));
|
|
357
|
+
ctx.print(ok("verified — this account can send anywhere now"));
|
|
286
358
|
});
|
|
287
359
|
|
|
288
360
|
define("whoami", "Show the key's identity and scope", "carlyemail whoami", async (ctx) => {
|
|
@@ -316,10 +388,37 @@ define("create", "Create an inbox", "carlyemail create --username support", asyn
|
|
|
316
388
|
const username = ctx.positional[0] || required(ctx.flags, "username");
|
|
317
389
|
const body = { username };
|
|
318
390
|
if (typeof ctx.flags.domain === "string") body.domain = ctx.flags.domain;
|
|
391
|
+
if (typeof ctx.flags["display-name"] === "string") body.display_name = ctx.flags["display-name"];
|
|
319
392
|
const inbox = await request(ctx.config, "POST", "/v0/inboxes", { body });
|
|
320
393
|
emit(ctx, inbox, () => ctx.print(ok(bold(inbox.email))));
|
|
321
394
|
});
|
|
322
395
|
|
|
396
|
+
define(
|
|
397
|
+
"name",
|
|
398
|
+
"Set the name recipients see beside an address",
|
|
399
|
+
'carlyemail name support@carlyemail.com "Support Bot"',
|
|
400
|
+
async (ctx) => {
|
|
401
|
+
const inbox = ctx.positional[0];
|
|
402
|
+
if (!inbox) throw new UsageError('which inbox? carlyemail name <inbox> "Display Name"');
|
|
403
|
+
// Joined, not `positional[1]`: an unquoted `carlyemail name x@y.com Support
|
|
404
|
+
// Bot` is the obvious way to type this and would otherwise silently set
|
|
405
|
+
// "Support". Nothing after the address clears the name rather than being a
|
|
406
|
+
// usage error, so there is a way to take one off.
|
|
407
|
+
const display_name = ctx.positional.slice(1).join(" ");
|
|
408
|
+
|
|
409
|
+
const out = await request(ctx.config, "PATCH", `/v0/inboxes/${encodeURIComponent(inbox)}`, {
|
|
410
|
+
body: { display_name },
|
|
411
|
+
});
|
|
412
|
+
emit(ctx, out, () =>
|
|
413
|
+
ctx.print(
|
|
414
|
+
out.display_name
|
|
415
|
+
? ok(`${bold(out.email)} now sends as ${bold(out.display_name)}`)
|
|
416
|
+
: ok(`${bold(out.email)} now sends with no display name`)
|
|
417
|
+
)
|
|
418
|
+
);
|
|
419
|
+
}
|
|
420
|
+
);
|
|
421
|
+
|
|
323
422
|
define("delete", "Delete an inbox and its mail", "carlyemail delete support@carlyemail.com", async (ctx) => {
|
|
324
423
|
const inbox = ctx.positional[0];
|
|
325
424
|
if (!inbox) throw new UsageError("which inbox? carlyemail delete <inbox>");
|
|
@@ -694,9 +793,9 @@ define("plan", "Show the current plan and its limits", "carlyemail plan", async
|
|
|
694
793
|
ctx.print(` ${dim("email")} ${cap(billing.monthly_emails)} a month`);
|
|
695
794
|
});
|
|
696
795
|
|
|
697
|
-
define("upgrade", "Get a checkout link for a paid plan", "carlyemail upgrade
|
|
796
|
+
define("upgrade", "Get a checkout link for a paid plan", "carlyemail upgrade startup", async (ctx) => {
|
|
698
797
|
const plan = ctx.positional[0] || ctx.flags.plan;
|
|
699
|
-
if (!plan) throw new UsageError("which plan? carlyemail upgrade
|
|
798
|
+
if (!plan) throw new UsageError("which plan? carlyemail upgrade startup|business");
|
|
700
799
|
const out = await request(ctx.config, "POST", "/v0/billing/checkout", { body: { plan } });
|
|
701
800
|
emit(ctx, out, () => {
|
|
702
801
|
ctx.print(arrow("open this to upgrade:"));
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "carlyemail",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.6.0",
|
|
4
4
|
"description": "Real email inboxes your agent can send, receive and reply from. SDK and CLI.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"email",
|
|
@@ -11,13 +11,8 @@
|
|
|
11
11
|
"mcp",
|
|
12
12
|
"cli"
|
|
13
13
|
],
|
|
14
|
-
"homepage": "https://carlyemail.com",
|
|
14
|
+
"homepage": "https://docs.carlyemail.com",
|
|
15
15
|
"bugs": "https://docs.carlyemail.com/support",
|
|
16
|
-
"repository": {
|
|
17
|
-
"type": "git",
|
|
18
|
-
"url": "git+https://github.com/shirschfield/carlyemail.git",
|
|
19
|
-
"directory": "cli"
|
|
20
|
-
},
|
|
21
16
|
"license": "MIT",
|
|
22
17
|
"author": "SWH Labs LLC",
|
|
23
18
|
"type": "module",
|