@carrierllc/mcp 0.7.0 → 0.8.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/dist/cli.js CHANGED
@@ -39,7 +39,7 @@ import {
39
39
  verifyStorefront,
40
40
  walletScreen,
41
41
  which
42
- } from "./chunk-CQ6EOLA7.js";
42
+ } from "./chunk-JROD4YAU.js";
43
43
  import {
44
44
  copyTree,
45
45
  exists,
@@ -97,6 +97,7 @@ var MCP_URL = "https://mcp.carrier.llc/mcp";
97
97
  var API_URL = "https://api.carrier.llc";
98
98
  var MCP_HOME = "https://mcp.carrier.llc";
99
99
  var SIGN_UP_URL = "https://accounts.carrier.llc/sign-up";
100
+ var CLERK_FAPI_URL = "https://clerk.carrier.llc";
100
101
  var SIGN_IN_URL = "https://accounts.carrier.llc/sign-in";
101
102
  var CONSOLE_URL = "https://app.carrier.llc";
102
103
  var ONBOARDING_URL = "https://app.carrier.llc/onboarding";
@@ -1287,6 +1288,214 @@ async function runHome(opts) {
1287
1288
  }
1288
1289
  }
1289
1290
 
1291
+ // src/cli/lib/signup.ts
1292
+ var DEFAULT_MAX_CODE_ATTEMPTS = 3;
1293
+ function parseEnvironment(raw) {
1294
+ const env = raw ?? {};
1295
+ const signUp = env.user_settings?.sign_up ?? {};
1296
+ const display = env.display_config ?? {};
1297
+ const result = {
1298
+ legalConsentRequired: signUp.legal_consent_enabled === true,
1299
+ captchaEnabled: signUp.captcha_enabled === true
1300
+ };
1301
+ if (display.terms_url) result.termsUrl = display.terms_url;
1302
+ if (display.privacy_policy_url) result.privacyUrl = display.privacy_policy_url;
1303
+ return result;
1304
+ }
1305
+ async function readSignupEnvironment(fetchImpl, clerkUrl) {
1306
+ const url = `${clerkUrl}/v1/environment?__clerk_api_version=2025-04-10&_clerk_js_version=5.0.0`;
1307
+ const res = await fetchImpl(url, { headers: { Accept: "application/json" } });
1308
+ if (!res.ok) throw new Error(`Clerk environment returned ${res.status}`);
1309
+ return parseEnvironment(await res.json());
1310
+ }
1311
+ function looksLikeEmail(value) {
1312
+ return /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(value);
1313
+ }
1314
+ async function apiError(res) {
1315
+ let body = {};
1316
+ try {
1317
+ body = await res.json();
1318
+ } catch {
1319
+ }
1320
+ const out = {
1321
+ message: body.error ?? `Carrier API returned ${res.status}`
1322
+ };
1323
+ if (body.code) out.code = body.code;
1324
+ if (res.status === 429 && body.retry_after_seconds) {
1325
+ const hours = Math.ceil(body.retry_after_seconds / 3600);
1326
+ out.message += ` Try again in about ${hours}h, or sign up in a browser: carrier open signup`;
1327
+ }
1328
+ return out;
1329
+ }
1330
+ function cancelled() {
1331
+ return { ok: false, cancelled: true, message: "Sign-up cancelled." };
1332
+ }
1333
+ async function signup(opts) {
1334
+ const io = opts.io;
1335
+ const fetchImpl = opts.fetchImpl ?? fetch;
1336
+ const apiUrl = opts.apiUrl ?? API_URL;
1337
+ const clerkUrl = opts.clerkUrl ?? CLERK_FAPI_URL;
1338
+ const maxAttempts = opts.maxCodeAttempts ?? DEFAULT_MAX_CODE_ATTEMPTS;
1339
+ let environment;
1340
+ try {
1341
+ environment = await readSignupEnvironment(fetchImpl, clerkUrl);
1342
+ } catch (e) {
1343
+ return {
1344
+ ok: false,
1345
+ message: withNextStep(
1346
+ `Could not read sign-up settings: ${e instanceof Error ? e.message : String(e)}`,
1347
+ [
1348
+ "Verify outbound HTTPS to clerk.carrier.llc",
1349
+ "Sign up in a browser instead: carrier open signup"
1350
+ ]
1351
+ )
1352
+ };
1353
+ }
1354
+ if (environment.captchaEnabled) {
1355
+ return {
1356
+ ok: false,
1357
+ message: withNextStep("Sign-up on this instance requires a CAPTCHA, which needs a browser.", [
1358
+ "Sign up in a browser: carrier open signup",
1359
+ "Then come back and run: carrier login"
1360
+ ])
1361
+ };
1362
+ }
1363
+ const email = await io.text("Email address");
1364
+ if (email === null) return cancelled();
1365
+ if (!looksLikeEmail(email.trim())) {
1366
+ return { ok: false, message: `"${email.trim()}" does not look like an email address.` };
1367
+ }
1368
+ const firstName = await io.text("First name");
1369
+ if (firstName === null) return cancelled();
1370
+ const lastName = await io.text("Last name");
1371
+ if (lastName === null) return cancelled();
1372
+ const password2 = await io.password("Password");
1373
+ if (password2 === null) return cancelled();
1374
+ let legalAccepted = false;
1375
+ if (environment.legalConsentRequired) {
1376
+ io.note?.(
1377
+ [
1378
+ `Terms: ${environment.termsUrl ?? "https://carrier.llc/terms"}`,
1379
+ `Privacy: ${environment.privacyUrl ?? "https://carrier.llc/privacy"}`
1380
+ ].join("\n"),
1381
+ "Before you continue"
1382
+ );
1383
+ const accepted = await io.confirm("Do you accept the terms and privacy policy?");
1384
+ if (accepted === null) return cancelled();
1385
+ if (!accepted) {
1386
+ return { ok: false, message: "An account cannot be created without accepting the terms." };
1387
+ }
1388
+ legalAccepted = true;
1389
+ }
1390
+ const headers = {
1391
+ "Content-Type": "application/json",
1392
+ Accept: "application/json",
1393
+ "User-Agent": `carrier-cli/${CARRIER_VERSION}`
1394
+ };
1395
+ io.step?.("Creating your account");
1396
+ let started;
1397
+ try {
1398
+ const res = await fetchImpl(`${apiUrl}/v1/signup`, {
1399
+ method: "POST",
1400
+ headers,
1401
+ body: JSON.stringify({
1402
+ email_address: email.trim(),
1403
+ password: password2,
1404
+ first_name: firstName.trim(),
1405
+ last_name: lastName.trim(),
1406
+ ...legalAccepted ? { legal_accepted: true } : {}
1407
+ })
1408
+ });
1409
+ if (!res.ok) {
1410
+ const err = await apiError(res);
1411
+ return {
1412
+ ok: false,
1413
+ message: withNextStep(err.message, [
1414
+ "Already have an account? carrier login",
1415
+ "Or sign up in a browser: carrier open signup"
1416
+ ])
1417
+ };
1418
+ }
1419
+ const body = await res.json();
1420
+ if (!body.data?.sign_up_id || !body.data.client_token) {
1421
+ return { ok: false, message: "Sign-up started but the response was incomplete." };
1422
+ }
1423
+ started = {
1424
+ sign_up_id: body.data.sign_up_id,
1425
+ client_token: body.data.client_token,
1426
+ verification_required: body.data.verification_required !== false
1427
+ };
1428
+ } catch (e) {
1429
+ return {
1430
+ ok: false,
1431
+ message: withNextStep(
1432
+ `Could not reach the Carrier API: ${e instanceof Error ? e.message : String(e)}`,
1433
+ ["Verify outbound HTTPS to api.carrier.llc"]
1434
+ )
1435
+ };
1436
+ }
1437
+ if (started.verification_required) {
1438
+ io.note?.(`We sent a code to ${email.trim()}.`, "Check your email");
1439
+ }
1440
+ let lastError = "Verification failed.";
1441
+ for (let attempt = 1; attempt <= maxAttempts; attempt++) {
1442
+ const code = await io.text("Verification code", { placeholder: "123456" });
1443
+ if (code === null) return cancelled();
1444
+ io.step?.("Verifying");
1445
+ let res;
1446
+ try {
1447
+ res = await fetchImpl(`${apiUrl}/v1/signup/verify`, {
1448
+ method: "POST",
1449
+ headers,
1450
+ body: JSON.stringify({
1451
+ sign_up_id: started.sign_up_id,
1452
+ client_token: started.client_token,
1453
+ code: code.trim()
1454
+ })
1455
+ });
1456
+ } catch (e) {
1457
+ return {
1458
+ ok: false,
1459
+ message: withNextStep(
1460
+ `Could not reach the Carrier API: ${e instanceof Error ? e.message : String(e)}`,
1461
+ ["Your account may exist \u2014 try: carrier login"]
1462
+ )
1463
+ };
1464
+ }
1465
+ if (res.ok) {
1466
+ const body = await res.json();
1467
+ const result = {
1468
+ ok: true,
1469
+ message: "Account created.",
1470
+ email: body.data?.email_address ?? email.trim()
1471
+ };
1472
+ if (body.data?.organization_id) result.organizationId = body.data.organization_id;
1473
+ if (body.data?.organization_name) result.organizationName = body.data.organization_name;
1474
+ return result;
1475
+ }
1476
+ const err = await apiError(res);
1477
+ lastError = err.message;
1478
+ if (err.code !== "form_code_incorrect") {
1479
+ return {
1480
+ ok: false,
1481
+ message: withNextStep(err.message, [
1482
+ "Your account may already exist \u2014 try: carrier login",
1483
+ "Or finish in a browser: carrier open signup"
1484
+ ])
1485
+ };
1486
+ }
1487
+ if (attempt < maxAttempts) io.note?.("That code was not right. Try again.", "Incorrect code");
1488
+ }
1489
+ return {
1490
+ ok: false,
1491
+ message: withNextStep(lastError, [
1492
+ `No attempts left after ${maxAttempts} tries.`,
1493
+ "Start over: carrier signup",
1494
+ "Or finish in a browser: carrier open signup"
1495
+ ])
1496
+ };
1497
+ }
1498
+
1290
1499
  // src/cli/lib/domains.ts
1291
1500
  var ICCID = {
1292
1501
  flags: "--iccid <iccid>",
@@ -2315,6 +2524,125 @@ var CLI_DOMAINS = [
2315
2524
  }
2316
2525
  ]
2317
2526
  },
2527
+ {
2528
+ name: "onboard",
2529
+ summary: "Connect a new account to OCS without opening a browser",
2530
+ commands: [
2531
+ {
2532
+ name: "status",
2533
+ transport: "rest",
2534
+ restMethod: "GET",
2535
+ restPath: "/v1/onboarding",
2536
+ summary: "Show how far onboarding has got. Same state the console renders.",
2537
+ options: []
2538
+ },
2539
+ {
2540
+ name: "org",
2541
+ transport: "rest",
2542
+ restMethod: "POST",
2543
+ restPath: "/v1/onboarding/org",
2544
+ summary: "Record who the operator is (step 1).",
2545
+ options: [
2546
+ {
2547
+ flags: "--name <name>",
2548
+ description: "Operator name, max 128 characters",
2549
+ arg: "name",
2550
+ type: "string",
2551
+ required: true
2552
+ },
2553
+ {
2554
+ flags: "--website <url>",
2555
+ description: "Public site, if there is one",
2556
+ arg: "website",
2557
+ type: "string"
2558
+ },
2559
+ {
2560
+ flags: "--country <iso2>",
2561
+ description: "ISO 3166-1 alpha-2 country, e.g. NL",
2562
+ arg: "country",
2563
+ type: "string"
2564
+ },
2565
+ {
2566
+ flags: "--operator-type <type>",
2567
+ description: "How the operator describes itself, e.g. mvno",
2568
+ arg: "operatorType",
2569
+ type: "string"
2570
+ }
2571
+ ]
2572
+ },
2573
+ {
2574
+ name: "connect",
2575
+ transport: "rest",
2576
+ restMethod: "POST",
2577
+ restPath: "/v1/onboarding/ocs-connect",
2578
+ summary: "Connect your own eSIMVault token (step 2, BYO). Verified against OCS, then stored encrypted; the token is never echoed back.",
2579
+ options: [
2580
+ {
2581
+ flags: "--token <token>",
2582
+ description: "eSIMVault API token. A flag lands in shell history \u2014 prefer a leading space, or run this once from a fresh shell.",
2583
+ arg: "token",
2584
+ type: "string",
2585
+ required: true
2586
+ }
2587
+ ]
2588
+ },
2589
+ {
2590
+ name: "managed",
2591
+ transport: "rest",
2592
+ restMethod: "POST",
2593
+ restPath: "/v1/onboarding/managed-account",
2594
+ summary: "Let Carrier provision the OCS sub-account instead (step 2, the primary path). Idempotent, and clears any BYO token.",
2595
+ options: []
2596
+ },
2597
+ {
2598
+ name: "plan",
2599
+ transport: "rest",
2600
+ restMethod: "POST",
2601
+ restPath: "/v1/onboarding/plan",
2602
+ summary: "Record the starting plan template (step 3).",
2603
+ options: [
2604
+ {
2605
+ flags: "--template-id <id>",
2606
+ description: "Package template to start from",
2607
+ arg: "templateId",
2608
+ type: "string",
2609
+ required: true
2610
+ },
2611
+ {
2612
+ flags: "--display-name <name>",
2613
+ description: "What customers will see the plan called",
2614
+ arg: "displayName",
2615
+ type: "string",
2616
+ required: true
2617
+ }
2618
+ ]
2619
+ },
2620
+ {
2621
+ name: "plan-skip",
2622
+ transport: "rest",
2623
+ restMethod: "POST",
2624
+ restPath: "/v1/onboarding/plan/skip",
2625
+ summary: "Skip the plan step and come back to it later (step 3).",
2626
+ options: []
2627
+ },
2628
+ {
2629
+ name: "test-esim",
2630
+ transport: "rest",
2631
+ restMethod: "POST",
2632
+ restPath: "/v1/onboarding/test-esim",
2633
+ summary: "Read back one eSIM from the connected fleet (step 4). Nothing is provisioned; a demo ICCID with a reason comes back if OCS has nothing to show.",
2634
+ options: []
2635
+ },
2636
+ {
2637
+ name: "complete",
2638
+ transport: "rest",
2639
+ restMethod: "POST",
2640
+ restPath: "/v1/onboarding/complete",
2641
+ summary: "Mark onboarding done (step 5).",
2642
+ options: []
2643
+ }
2644
+ ]
2645
+ },
2318
2646
  // <generated:catalog-domains>
2319
2647
  // Generated from the MCP catalog by scripts/generate-domains.mjs. Do not edit by hand:
2320
2648
  // rebuild the server, re-run the generator, and review the diff.
@@ -4296,6 +4624,71 @@ program.command("login").description("Sign in from this terminal. Opens a browse
4296
4624
  }
4297
4625
  p3.outro("");
4298
4626
  });
4627
+ function clackSignupIO() {
4628
+ return {
4629
+ async text(message, opts) {
4630
+ const value = await p3.text({ message, ...opts?.placeholder ? { placeholder: opts.placeholder } : {} });
4631
+ return p3.isCancel(value) ? null : String(value ?? "").trim();
4632
+ },
4633
+ async password(message) {
4634
+ const value = await p3.password({ message });
4635
+ return p3.isCancel(value) ? null : String(value ?? "");
4636
+ },
4637
+ async confirm(message) {
4638
+ const value = await p3.confirm({ message });
4639
+ return p3.isCancel(value) ? null : Boolean(value);
4640
+ },
4641
+ step(message) {
4642
+ p3.log.step(message);
4643
+ },
4644
+ note(body, title) {
4645
+ p3.note(body, title);
4646
+ }
4647
+ };
4648
+ }
4649
+ program.command("signup").description("Create a Carrier account from this terminal, then sign in.").action(async () => {
4650
+ header();
4651
+ const result = await signup({ io: clackSignupIO() });
4652
+ if (!result.ok) {
4653
+ if (result.cancelled) {
4654
+ p3.log.info(result.message);
4655
+ } else {
4656
+ p3.log.error(result.message);
4657
+ process.exitCode = 1;
4658
+ }
4659
+ p3.outro("");
4660
+ return;
4661
+ }
4662
+ p3.note(
4663
+ [
4664
+ `Account: ${result.email ?? "created"}`,
4665
+ `Organization: ${result.organizationName ?? result.organizationId ?? "created"}`
4666
+ ].join("\n"),
4667
+ "Signed up"
4668
+ );
4669
+ const s = p3.spinner();
4670
+ s.start("Signing in");
4671
+ try {
4672
+ const login2 = await login({
4673
+ openBrowser: (url) => openUrl(url),
4674
+ onUrl: (url) => s.message(`Finish sign-in at ${url.slice(0, 60)}\u2026`)
4675
+ });
4676
+ s.stop(login2.ok ? "Signed in" : "Sign-in did not finish");
4677
+ if (login2.ok) {
4678
+ p3.note("Try it: carrier intelligence fleet-health", "Next");
4679
+ } else {
4680
+ p3.log.warn(withNextStep(login2.message, ["Sign in when ready: carrier login"]));
4681
+ }
4682
+ } catch (e) {
4683
+ s.stop("Sign-in did not finish");
4684
+ p3.log.warn(
4685
+ withNextStep(e instanceof Error ? e.message : String(e), [
4686
+ "Your account exists \u2014 sign in when ready: carrier login"
4687
+ ])
4688
+ );
4689
+ }
4690
+ p3.outro("");
4691
+ });
4299
4692
  program.command("logout").description("Forget the stored sign-in on this machine.").action(async () => {
4300
4693
  header();
4301
4694
  await logout();
@@ -4385,7 +4778,7 @@ connectCmd.command("begin").description("Start or resume Stripe Connect onboardi
4385
4778
  });
4386
4779
  var openCmd = program.command("open").description("Open Carrier account / product URLs in your browser.");
4387
4780
  for (const [name, url, desc] of [
4388
- ["signup", SIGN_UP_URL, "Create a Carrier account (Clerk sign-up)"],
4781
+ ["signup", SIGN_UP_URL, "Create a Carrier account in a browser (headless: carrier signup)"],
4389
4782
  ["signin", SIGN_IN_URL, "Sign in to Carrier"],
4390
4783
  ["console", CONSOLE_URL, "Open the Carrier console"],
4391
4784
  ["onboarding", ONBOARDING_URL, "Open console onboarding (link OCS / managed)"],