@carrierllc/mcp 0.2.16 → 0.2.17

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.
Files changed (79) hide show
  1. package/dist/cli.js +644 -0
  2. package/dist/cli.js.map +1 -0
  3. package/dist/index.js +1263 -233
  4. package/dist/index.js.map +1 -1
  5. package/package.json +21 -14
  6. package/plugin/.claude-plugin/marketplace.json +31 -0
  7. package/plugin/carrier/.claude-plugin/plugin.json +19 -0
  8. package/plugin/carrier/.mcp.json +8 -0
  9. package/plugin/carrier/README.md +48 -0
  10. package/plugin/carrier/agents/carrier-billing-auditor.md +16 -0
  11. package/plugin/carrier/agents/carrier-fleet-ops.md +15 -0
  12. package/plugin/carrier/agents/carrier-storefront-builder.md +17 -0
  13. package/plugin/carrier/commands/billing.md +16 -0
  14. package/plugin/carrier/commands/churn.md +15 -0
  15. package/plugin/carrier/commands/credits.md +15 -0
  16. package/plugin/carrier/commands/esim-status.md +15 -0
  17. package/plugin/carrier/commands/fleet.md +17 -0
  18. package/plugin/carrier/commands/greenzone.md +16 -0
  19. package/plugin/carrier/commands/onboard.md +17 -0
  20. package/plugin/carrier/commands/packages.md +18 -0
  21. package/plugin/carrier/commands/provision.md +20 -0
  22. package/plugin/carrier/commands/sms.md +18 -0
  23. package/plugin/carrier/commands/status.md +15 -0
  24. package/plugin/carrier/commands/storefront.md +21 -0
  25. package/plugin/carrier/commands/subscribers.md +18 -0
  26. package/plugin/carrier/commands/usage.md +16 -0
  27. package/plugin/carrier/skills/carrier-operations/SKILL.md +36 -0
  28. package/templates/storefront/eslint.config.mjs +15 -0
  29. package/templates/storefront/next.config.ts +22 -0
  30. package/templates/storefront/open-next.config.ts +3 -0
  31. package/templates/storefront/package.json +38 -0
  32. package/templates/storefront/pnpm-lock.yaml +4020 -0
  33. package/templates/storefront/postcss.config.mjs +7 -0
  34. package/templates/storefront/src/app/activate/[orderId]/page.tsx +28 -0
  35. package/templates/storefront/src/app/apple-icon.tsx +29 -0
  36. package/templates/storefront/src/app/checkout/[templateId]/CheckoutClient.tsx +52 -0
  37. package/templates/storefront/src/app/checkout/[templateId]/page.tsx +24 -0
  38. package/templates/storefront/src/app/contact/page.tsx +35 -0
  39. package/templates/storefront/src/app/dashboard/page.tsx +26 -0
  40. package/templates/storefront/src/app/globals.css +99 -0
  41. package/templates/storefront/src/app/help/page.tsx +25 -0
  42. package/templates/storefront/src/app/icon.tsx +29 -0
  43. package/templates/storefront/src/app/layout.tsx +56 -0
  44. package/templates/storefront/src/app/legal/acceptable-use/page.tsx +21 -0
  45. package/templates/storefront/src/app/legal/privacy/page.tsx +27 -0
  46. package/templates/storefront/src/app/legal/terms/page.tsx +25 -0
  47. package/templates/storefront/src/app/manifest.ts +18 -0
  48. package/templates/storefront/src/app/page.tsx +31 -0
  49. package/templates/storefront/src/app/robots.ts +9 -0
  50. package/templates/storefront/src/app/shop/ShopClient.tsx +27 -0
  51. package/templates/storefront/src/app/shop/page.tsx +9 -0
  52. package/templates/storefront/src/app/sign-in/[[...sign-in]]/page.tsx +24 -0
  53. package/templates/storefront/src/app/sign-up/[[...sign-up]]/page.tsx +13 -0
  54. package/templates/storefront/src/app/sitemap.ts +14 -0
  55. package/templates/storefront/src/brand.config.ts +28 -0
  56. package/templates/storefront/src/components/Providers.tsx +16 -0
  57. package/templates/storefront/src/components/faq/FaqSection.tsx +80 -0
  58. package/templates/storefront/src/components/footer/StorefrontFooter.tsx +61 -0
  59. package/templates/storefront/src/components/landing/HeroSection.tsx +52 -0
  60. package/templates/storefront/src/components/landing/PlanCard.tsx +61 -0
  61. package/templates/storefront/src/components/nav/StorefrontNav.tsx +69 -0
  62. package/templates/storefront/src/components/theme/clerk-appearance.ts +51 -0
  63. package/templates/storefront/src/components/theme/theme-provider.tsx +87 -0
  64. package/templates/storefront/src/components/theme/theme-script.tsx +24 -0
  65. package/templates/storefront/src/lib/conversion-events.ts +36 -0
  66. package/templates/storefront/src/lib/sanitize-auth-redirect.ts +16 -0
  67. package/templates/storefront/src/middleware.ts +25 -0
  68. package/templates/storefront/src/vendor/carrier/client.ts +95 -0
  69. package/templates/storefront/src/vendor/carrier/index.ts +2 -0
  70. package/templates/storefront/src/vendor/carrier/types.ts +21 -0
  71. package/templates/storefront/src/vendor/config/index.ts +2 -0
  72. package/templates/storefront/src/vendor/geo/index.ts +32 -0
  73. package/templates/storefront/src/vendor/ui/brand.ts +68 -0
  74. package/templates/storefront/src/vendor/ui/cn.ts +7 -0
  75. package/templates/storefront/src/vendor/ui/countryImagery.ts +46 -0
  76. package/templates/storefront/src/vendor/ui/index.ts +3 -0
  77. package/templates/storefront/tsconfig.json +23 -0
  78. package/templates/storefront/wrangler.jsonc +11 -0
  79. package/dist/.metadata_never_index +0 -0
@@ -0,0 +1,95 @@
1
+ /**
2
+ * Carrier API client stub.
3
+ * Set NEXT_PUBLIC_CARRIER_API_URL to point at a live Carrier API.
4
+ * When absent, all methods return mock data so the storefront builds without credentials.
5
+ */
6
+ import type { SkuTemplate } from "./types";
7
+
8
+ const MOCK_TEMPLATES: SkuTemplate[] = [
9
+ {
10
+ id: "visit-calm",
11
+ name: "Visit — Calm",
12
+ description: "Perfect for short trips. Light browsing & maps.",
13
+ dataGb: 3,
14
+ validityDays: 7,
15
+ price_cents: 499,
16
+ currency: "EUR",
17
+ regions: ["Europe"],
18
+ features: ["3 GB data", "7 days validity", "40+ countries", "Throttled after limit"],
19
+ },
20
+ {
21
+ id: "visit-cruise",
22
+ name: "Visit — Cruise",
23
+ description: "Comfortable speed for a week away.",
24
+ dataGb: 10,
25
+ validityDays: 7,
26
+ price_cents: 799,
27
+ currency: "EUR",
28
+ regions: ["Europe"],
29
+ features: ["10 GB data", "7 days validity", "40+ countries", "1 Mbps after limit"],
30
+ },
31
+ {
32
+ id: "nomad-calm",
33
+ name: "Nomad — Calm",
34
+ description: "Long-term coverage for slow travellers.",
35
+ dataGb: 30,
36
+ validityDays: 30,
37
+ price_cents: 1999,
38
+ currency: "EUR",
39
+ regions: ["Global"],
40
+ features: ["30 GB data", "30 days validity", "120+ countries", "Throttled after limit"],
41
+ },
42
+ {
43
+ id: "nomad-cruise",
44
+ name: "Nomad — Cruise",
45
+ description: "30 days of comfortable global data.",
46
+ dataGb: 50,
47
+ validityDays: 30,
48
+ price_cents: 2499,
49
+ currency: "EUR",
50
+ regions: ["Global"],
51
+ features: ["50 GB data", "30 days validity", "120+ countries", "1 Mbps after limit"],
52
+ },
53
+ {
54
+ id: "schengen-cruise",
55
+ name: "Schengen Loop — Cruise",
56
+ description: "Schengen zone coverage for multi-country trips.",
57
+ dataGb: 20,
58
+ validityDays: 14,
59
+ price_cents: 1499,
60
+ currency: "EUR",
61
+ regions: ["Schengen"],
62
+ features: ["20 GB data", "14 days validity", "26 Schengen countries", "1 Mbps after limit"],
63
+ },
64
+ ];
65
+
66
+ const API_BASE = process.env.NEXT_PUBLIC_CARRIER_API_URL ?? "";
67
+
68
+ export async function fetchTemplates(): Promise<SkuTemplate[]> {
69
+ if (!API_BASE) return MOCK_TEMPLATES;
70
+ try {
71
+ const res = await fetch(`${API_BASE}/api/templates`, { next: { revalidate: 3600 } });
72
+ if (!res.ok) return MOCK_TEMPLATES;
73
+ return (await res.json()) as SkuTemplate[];
74
+ } catch {
75
+ return MOCK_TEMPLATES;
76
+ }
77
+ }
78
+
79
+ export async function createCheckoutSession(
80
+ templateId: string,
81
+ userId: string,
82
+ ): Promise<{ url: string } | null> {
83
+ if (!API_BASE) return null;
84
+ try {
85
+ const res = await fetch(`${API_BASE}/api/checkout`, {
86
+ method: "POST",
87
+ headers: { "Content-Type": "application/json", Authorization: `Bearer ${userId}` },
88
+ body: JSON.stringify({ templateId }),
89
+ });
90
+ if (!res.ok) return null;
91
+ return (await res.json()) as { url: string };
92
+ } catch {
93
+ return null;
94
+ }
95
+ }
@@ -0,0 +1,2 @@
1
+ export * from "./types";
2
+ export * from "./client";
@@ -0,0 +1,21 @@
1
+ export type ICCID = string & { __brand: "ICCID" };
2
+ export type TemplateId = string;
3
+
4
+ export interface SkuTemplate {
5
+ id: TemplateId;
6
+ name: string;
7
+ description: string;
8
+ dataGb: number;
9
+ validityDays: number;
10
+ price_cents: number;
11
+ currency: string;
12
+ regions: string[];
13
+ features: string[];
14
+ }
15
+
16
+ export interface ProvisionEsimResult {
17
+ iccid: ICCID;
18
+ qrCode: string;
19
+ activationUrl: string;
20
+ expiresAt: string;
21
+ }
@@ -0,0 +1,2 @@
1
+ // Carrier brand tokens (re-exported from brand.config for convenience)
2
+ export { default as brand } from "@/brand.config";
@@ -0,0 +1,32 @@
1
+ export function getCountryName(iso: string, locale = "en"): string {
2
+ try {
3
+ const dn = new Intl.DisplayNames([locale], { type: "region" });
4
+ return dn.of(iso.toUpperCase()) ?? iso;
5
+ } catch {
6
+ return iso;
7
+ }
8
+ }
9
+
10
+ export function formatPrice(cents: number, currency = "EUR", locale = "en"): string {
11
+ try {
12
+ return new Intl.NumberFormat(locale, {
13
+ style: "currency",
14
+ currency,
15
+ minimumFractionDigits: 2,
16
+ }).format(cents / 100);
17
+ } catch {
18
+ return `${currency} ${(cents / 100).toFixed(2)}`;
19
+ }
20
+ }
21
+
22
+ export type DisplayCurrency = "EUR" | "USD" | "GBP";
23
+
24
+ export const SUPPORTED_CURRENCIES: DisplayCurrency[] = ["EUR", "USD", "GBP"];
25
+
26
+ export function getDisplayCurrency(countryIso: string): DisplayCurrency {
27
+ const map: Record<string, DisplayCurrency> = {
28
+ US: "USD",
29
+ GB: "GBP",
30
+ };
31
+ return map[countryIso.toUpperCase()] ?? "EUR";
32
+ }
@@ -0,0 +1,68 @@
1
+ import brand from "@/brand.config";
2
+
3
+ /**
4
+ * BRAND constants — driven by brand.config.ts (white-label driven).
5
+ */
6
+ export const BRAND = {
7
+ name: brand.name,
8
+ legalName: brand.legalName,
9
+ tagline: brand.tagline,
10
+ description: `Unlimited data in 200+ countries. Instant QR activation. Real support.`,
11
+ domain: brand.domain,
12
+ support: {
13
+ email: brand.supportEmail,
14
+ whatsapp: brand.supportWhatsapp,
15
+ sms: brand.supportWhatsapp,
16
+ statusUrl: brand.supportUrl,
17
+ hoursLocal: "16h/day · Mon-Sun · EU + Americas + Asia-Pac",
18
+ responseTimeMinutes: 5,
19
+ },
20
+ social: brand.social,
21
+ } as const;
22
+
23
+ export const GRADIENTS = {
24
+ hero: "bg-gradient-to-br from-[#FF7A45] via-[#FF6B35] to-[#D9461C]",
25
+ heroText: "bg-gradient-to-br from-[#FF6B35] to-[#D9461C] bg-clip-text text-transparent",
26
+ card: "bg-gradient-to-br from-[#0F1422] to-[#1F2638]",
27
+ glow: "bg-gradient-to-r from-[#FF6B35]/30 via-[#FF6B35]/40 to-[#FFB088]/30",
28
+ } as const;
29
+
30
+ export type SupportChannel = "whatsapp" | "sms" | "email";
31
+
32
+ export interface SupportLinkContext {
33
+ message?: string;
34
+ orderId?: string;
35
+ iccid?: string;
36
+ skuName?: string;
37
+ subject?: string;
38
+ }
39
+
40
+ export function buildSupportLink(
41
+ channel: SupportChannel,
42
+ context: SupportLinkContext = {},
43
+ ): string {
44
+ const parts: string[] = [];
45
+ if (context.message?.trim()) parts.push(context.message.trim());
46
+ if (context.orderId) parts.push(`Order: ${context.orderId}`);
47
+ if (context.iccid) parts.push(`ICCID: ${context.iccid}`);
48
+ if (context.skuName) parts.push(`Plan: ${context.skuName}`);
49
+ const body = parts.join("\n");
50
+
51
+ if (channel === "whatsapp") {
52
+ const digits = BRAND.support.whatsapp.replace(/\D/g, "");
53
+ return body
54
+ ? `https://wa.me/${digits}?text=${encodeURIComponent(body)}`
55
+ : `https://wa.me/${digits}`;
56
+ }
57
+
58
+ if (channel === "sms") {
59
+ return body
60
+ ? `sms:${BRAND.support.sms}?&body=${encodeURIComponent(body)}`
61
+ : `sms:${BRAND.support.sms}`;
62
+ }
63
+
64
+ const subject = context.subject?.trim() || `${BRAND.name} support`;
65
+ const qs = new URLSearchParams({ subject });
66
+ if (body) qs.set("body", body);
67
+ return `mailto:${BRAND.support.email}?${qs.toString()}`;
68
+ }
@@ -0,0 +1,7 @@
1
+ import { clsx, type ClassValue } from "clsx";
2
+ import { twMerge } from "tailwind-merge";
3
+
4
+ /** Class-name combiner with Tailwind-merge dedup. */
5
+ export function cn(...inputs: ClassValue[]): string {
6
+ return twMerge(clsx(inputs));
7
+ }
@@ -0,0 +1,46 @@
1
+ export type CountryImage = {
2
+ url: string;
3
+ thumbUrl: string;
4
+ blurhash: string;
5
+ };
6
+
7
+ function ix(id: string, blurhash: string): CountryImage {
8
+ const base = `https://images.unsplash.com/${id}`;
9
+ return {
10
+ url: `${base}?w=1080&q=70&auto=format&fit=crop`,
11
+ thumbUrl: `${base}?w=480&q=60&auto=format&fit=crop`,
12
+ blurhash,
13
+ };
14
+ }
15
+
16
+ const FALLBACK: CountryImage = ix(
17
+ "photo-1500835556837-99ac94a94552",
18
+ "LJG]bjxu0Lt600WB_3M{0LWBxujt",
19
+ );
20
+
21
+ const CATALOG: Readonly<Record<string, CountryImage>> = {
22
+ FR: ix("photo-1502602898657-3e91760cbb34", "LCC@b800WB00j[~qof?bj[00of-;j["),
23
+ DE: ix("photo-1467269204594-9661b134dd2b", "LEC@$$Rj00j[~qof?bWB00j[?bj["),
24
+ IT: ix("photo-1531572753322-ad063cecc140", "LFC@b800WB00j[~qof?bj[00of-;j["),
25
+ ES: ix("photo-1543783207-ec64e4d95325", "LBE3KAj]00WB~qj[?bWB00WB-;j["),
26
+ GB: ix("photo-1513635269975-59663e0ac1ad", "LCE.~WM{00WB~qof?bj[00j[-;j["),
27
+ JP: ix("photo-1492571350019-22de08371fd3", "LDD]bjxu00j[~qWB?bof00j[%MWB"),
28
+ US: ix("photo-1485871981521-5b1fd3805eee", "LCD]bjxu00j[~qWB?bof00j[%MWB"),
29
+ AU: ix("photo-1506973035872-a4ec16b8e8d9", "LEE.~xRj00WB~qof?bof00WB-;j["),
30
+ SG: ix("photo-1525625293386-3f8f99389edd", "LFC@b800WB00j[~qof?bj[00of-;j["),
31
+ AE: ix("photo-1512453979798-5ea266f8880c", "LEE.~xRj00WB~qof?bof00WB-;j["),
32
+ TH: ix("photo-1528181304800-259b08848526", "LFE.~xRj00j[~qof?bj[00j[-;j["),
33
+ IN: ix("photo-1524492412937-b28074a5d7da", "LEE.~xRj00WB~qof?bof00WB-;j["),
34
+ BR: ix("photo-1483729558449-99ef09a8c325", "LFE.~xRj00j[~qof?bj[00j[-;j["),
35
+ CA: ix("photo-1503614472-8c93d56e92ce", "LBE.~Wf600M{~qof?bWB00j[-;of"),
36
+ MX: ix("photo-1518105779142-d975f22f1b0a", "LFC@b800WB00j[~qof?bj[00of-;j["),
37
+ NL: ix("photo-1534351590666-13e3e96c5017", "LCE.~Wf6IUM{_3xu%2Rj4mM{xuM{"),
38
+ PT: ix("photo-1555881400-74d7acaacd8b", "LFE.~xRj00j[~qof?bj[00j[-;j["),
39
+ GR: ix("photo-1530841377377-3ff06c0ca713", "LFE.~xRj00j[~qof?bj[00j[-;j["),
40
+ };
41
+
42
+ export function imageForIso(iso: string): CountryImage {
43
+ return CATALOG[iso.toUpperCase()] ?? FALLBACK;
44
+ }
45
+
46
+ export const FALLBACK_COUNTRY_IMAGE = FALLBACK;
@@ -0,0 +1,3 @@
1
+ export * from "./cn";
2
+ export * from "./brand";
3
+ export * from "./countryImagery";
@@ -0,0 +1,23 @@
1
+ {
2
+ "compilerOptions": {
3
+ "target": "ES2017",
4
+ "lib": ["dom", "dom.iterable", "esnext"],
5
+ "allowJs": true,
6
+ "skipLibCheck": true,
7
+ "strict": true,
8
+ "noEmit": true,
9
+ "esModuleInterop": true,
10
+ "module": "esnext",
11
+ "moduleResolution": "Bundler",
12
+ "resolveJsonModule": true,
13
+ "isolatedModules": true,
14
+ "jsx": "preserve",
15
+ "incremental": true,
16
+ "plugins": [{ "name": "next" }],
17
+ "paths": {
18
+ "@/*": ["./src/*"]
19
+ }
20
+ },
21
+ "include": ["next-env.d.ts", "**/*.ts", "**/*.tsx", ".next/types/**/*.ts"],
22
+ "exclude": ["node_modules"]
23
+ }
@@ -0,0 +1,11 @@
1
+ {
2
+ "$schema": "node_modules/wrangler/config-schema.json",
3
+ "main": ".open-next/worker.js",
4
+ "name": "storefront",
5
+ "compatibility_date": "2025-05-01",
6
+ "compatibility_flags": ["nodejs_compat"],
7
+ "assets": {
8
+ "directory": ".open-next/assets",
9
+ "binding": "ASSETS"
10
+ }
11
+ }
File without changes