@carrierllc/mcp 0.2.16 → 0.2.18

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 (91) hide show
  1. package/README.md +21 -1
  2. package/dist/cli.js +1445 -0
  3. package/dist/cli.js.map +1 -0
  4. package/dist/index.js +1437 -252
  5. package/dist/index.js.map +1 -1
  6. package/package.json +23 -15
  7. package/plugin/.claude-plugin/marketplace.json +31 -0
  8. package/plugin/carrier/.claude-plugin/plugin.json +19 -0
  9. package/plugin/carrier/.mcp.json +8 -0
  10. package/plugin/carrier/README.md +75 -0
  11. package/plugin/carrier/agents/carrier-billing-auditor.md +16 -0
  12. package/plugin/carrier/agents/carrier-fleet-ops.md +15 -0
  13. package/plugin/carrier/agents/carrier-storefront-builder.md +17 -0
  14. package/plugin/carrier/commands/billing.md +23 -0
  15. package/plugin/carrier/commands/churn.md +15 -0
  16. package/plugin/carrier/commands/credits.md +15 -0
  17. package/plugin/carrier/commands/esim-status.md +15 -0
  18. package/plugin/carrier/commands/fleet.md +17 -0
  19. package/plugin/carrier/commands/greenzone.md +16 -0
  20. package/plugin/carrier/commands/onboard.md +17 -0
  21. package/plugin/carrier/commands/packages.md +18 -0
  22. package/plugin/carrier/commands/provision.md +30 -0
  23. package/plugin/carrier/commands/sms.md +18 -0
  24. package/plugin/carrier/commands/status.md +15 -0
  25. package/plugin/carrier/commands/storefront.md +21 -0
  26. package/plugin/carrier/commands/subscribers.md +18 -0
  27. package/plugin/carrier/commands/usage.md +16 -0
  28. package/plugin/carrier/commands/wallet.md +31 -0
  29. package/plugin/carrier/skills/carrier-operations/SKILL.md +43 -0
  30. package/templates/storefront/eslint.config.mjs +15 -0
  31. package/templates/storefront/next.config.ts +22 -0
  32. package/templates/storefront/open-next.config.ts +3 -0
  33. package/templates/storefront/package.json +38 -0
  34. package/templates/storefront/pnpm-lock.yaml +4020 -0
  35. package/templates/storefront/postcss.config.mjs +7 -0
  36. package/templates/storefront/src/app/activate/[orderId]/ActivateClient.tsx +125 -0
  37. package/templates/storefront/src/app/activate/[orderId]/page.tsx +32 -0
  38. package/templates/storefront/src/app/api/checkout/claim/route.ts +30 -0
  39. package/templates/storefront/src/app/api/checkout/guest/route.ts +91 -0
  40. package/templates/storefront/src/app/api/profile/phone/route.ts +40 -0
  41. package/templates/storefront/src/app/apple-icon.tsx +29 -0
  42. package/templates/storefront/src/app/checkout/[templateId]/CheckoutClient.tsx +110 -0
  43. package/templates/storefront/src/app/checkout/[templateId]/page.tsx +24 -0
  44. package/templates/storefront/src/app/checkout/success/CheckoutSuccessClient.tsx +415 -0
  45. package/templates/storefront/src/app/checkout/success/page.tsx +59 -0
  46. package/templates/storefront/src/app/contact/page.tsx +35 -0
  47. package/templates/storefront/src/app/dashboard/page.tsx +26 -0
  48. package/templates/storefront/src/app/globals.css +99 -0
  49. package/templates/storefront/src/app/help/page.tsx +25 -0
  50. package/templates/storefront/src/app/icon.tsx +29 -0
  51. package/templates/storefront/src/app/layout.tsx +56 -0
  52. package/templates/storefront/src/app/legal/acceptable-use/page.tsx +21 -0
  53. package/templates/storefront/src/app/legal/privacy/page.tsx +27 -0
  54. package/templates/storefront/src/app/legal/terms/page.tsx +25 -0
  55. package/templates/storefront/src/app/manifest.ts +18 -0
  56. package/templates/storefront/src/app/page.tsx +31 -0
  57. package/templates/storefront/src/app/robots.ts +9 -0
  58. package/templates/storefront/src/app/shop/ShopClient.tsx +27 -0
  59. package/templates/storefront/src/app/shop/page.tsx +9 -0
  60. package/templates/storefront/src/app/sign-in/[[...sign-in]]/page.tsx +24 -0
  61. package/templates/storefront/src/app/sign-up/[[...sign-up]]/StorefrontSignUpClient.tsx +199 -0
  62. package/templates/storefront/src/app/sign-up/[[...sign-up]]/page.tsx +28 -0
  63. package/templates/storefront/src/app/sitemap.ts +14 -0
  64. package/templates/storefront/src/brand.config.ts +28 -0
  65. package/templates/storefront/src/components/Providers.tsx +16 -0
  66. package/templates/storefront/src/components/faq/FaqSection.tsx +80 -0
  67. package/templates/storefront/src/components/footer/StorefrontFooter.tsx +61 -0
  68. package/templates/storefront/src/components/landing/HeroSection.tsx +52 -0
  69. package/templates/storefront/src/components/landing/PlanCard.tsx +61 -0
  70. package/templates/storefront/src/components/nav/StorefrontNav.tsx +69 -0
  71. package/templates/storefront/src/components/theme/clerk-appearance.ts +51 -0
  72. package/templates/storefront/src/components/theme/theme-provider.tsx +87 -0
  73. package/templates/storefront/src/components/theme/theme-script.tsx +24 -0
  74. package/templates/storefront/src/lib/checkout-order-claim.ts +127 -0
  75. package/templates/storefront/src/lib/complete-email-sign-up.ts +50 -0
  76. package/templates/storefront/src/lib/conversion-events.ts +36 -0
  77. package/templates/storefront/src/lib/sanitize-auth-redirect.ts +16 -0
  78. package/templates/storefront/src/lib/verify-checkout-session.ts +53 -0
  79. package/templates/storefront/src/middleware.ts +31 -0
  80. package/templates/storefront/src/vendor/carrier/client.ts +95 -0
  81. package/templates/storefront/src/vendor/carrier/index.ts +2 -0
  82. package/templates/storefront/src/vendor/carrier/types.ts +21 -0
  83. package/templates/storefront/src/vendor/config/index.ts +2 -0
  84. package/templates/storefront/src/vendor/geo/index.ts +32 -0
  85. package/templates/storefront/src/vendor/ui/brand.ts +68 -0
  86. package/templates/storefront/src/vendor/ui/cn.ts +7 -0
  87. package/templates/storefront/src/vendor/ui/countryImagery.ts +46 -0
  88. package/templates/storefront/src/vendor/ui/index.ts +3 -0
  89. package/templates/storefront/tsconfig.json +23 -0
  90. package/templates/storefront/wrangler.jsonc +11 -0
  91. package/dist/.metadata_never_index +0 -0
package/dist/cli.js ADDED
@@ -0,0 +1,1445 @@
1
+ #!/usr/bin/env node
2
+
3
+ // src/cli/index.ts
4
+ import { Command } from "commander";
5
+ import * as p2 from "@clack/prompts";
6
+ import pc3 from "picocolors";
7
+ import { resolve as resolve2 } from "path";
8
+
9
+ // src/cli/lib/brand.ts
10
+ var CARRIER_BRAND = {
11
+ name: "Carrier",
12
+ legalName: "Lifecycle Innovations Limited",
13
+ tagline: "Programmable connectivity, on demand.",
14
+ domain: "carrier.llc",
15
+ supportEmail: "support@carrier.llc",
16
+ supportUrl: "https://carrier.llc/help",
17
+ supportWhatsapp: "+17864604829",
18
+ colors: {
19
+ bg: "#080C16",
20
+ accent: "#FF6B35",
21
+ accentDark: "#D9461C",
22
+ text: "#F5F1EA"
23
+ },
24
+ social: {
25
+ x: "@carrier_llc",
26
+ instagram: "@carrier.llc",
27
+ tiktok: "@carrier.llc"
28
+ },
29
+ carrierApiUrl: "https://api.carrier.llc"
30
+ };
31
+ var CARRIER_ACCENT_LIGHT = "#FFB088";
32
+ var CARRIER_ACCENT_GRADIENT_START = "#FF7A45";
33
+ function hexToRgb(hex) {
34
+ const h = hex.replace(/^#/, "");
35
+ if (!/^[0-9a-fA-F]{6}$/.test(h)) return null;
36
+ return [parseInt(h.slice(0, 2), 16), parseInt(h.slice(2, 4), 16), parseInt(h.slice(4, 6), 16)];
37
+ }
38
+ function rgbToHex(r, g, b, lower = false) {
39
+ const fmt = (n) => Math.max(0, Math.min(255, Math.round(n))).toString(16).padStart(2, "0");
40
+ const out = `#${fmt(r)}${fmt(g)}${fmt(b)}`;
41
+ return lower ? out : out.toUpperCase();
42
+ }
43
+ function mixHexWithWhite(hex, whiteRatio) {
44
+ const rgb = hexToRgb(hex);
45
+ if (!rgb) return hex;
46
+ const mix = (n) => n + (255 - n) * whiteRatio;
47
+ return rgbToHex(mix(rgb[0]), mix(rgb[1]), mix(rgb[2]));
48
+ }
49
+ function darkenHex(hex, factor) {
50
+ const rgb = hexToRgb(hex);
51
+ if (!rgb) return hex;
52
+ const scale = (n) => n * factor;
53
+ return rgbToHex(scale(rgb[0]), scale(rgb[1]), scale(rgb[2]), true);
54
+ }
55
+ function deriveAccentDark(accent, seed = CARRIER_BRAND) {
56
+ if (accent.toUpperCase() === seed.colors.accent.toUpperCase()) return seed.colors.accentDark;
57
+ const rgb = hexToRgb(accent);
58
+ if (!rgb) return accent;
59
+ const scale = (n) => Math.max(0, Math.min(255, Math.round(n * 0.75)));
60
+ return rgbToHex(scale(rgb[0]), scale(rgb[1]), scale(rgb[2]));
61
+ }
62
+ function deriveAccentLight(accent, seed = CARRIER_BRAND) {
63
+ if (accent.toUpperCase() === seed.colors.accent.toUpperCase()) return CARRIER_ACCENT_LIGHT;
64
+ return mixHexWithWhite(accent, 0.45);
65
+ }
66
+ function deriveAccentGradientStart(accent, seed = CARRIER_BRAND) {
67
+ if (accent.toUpperCase() === seed.colors.accent.toUpperCase()) return CARRIER_ACCENT_GRADIENT_START;
68
+ return mixHexWithWhite(accent, 0.08);
69
+ }
70
+ function deriveAccentPaletteSubs(accent, accentDark) {
71
+ if (accent.toUpperCase() === CARRIER_BRAND.colors.accent.toUpperCase()) return [];
72
+ const accentLight = deriveAccentLight(accent);
73
+ const gradientStart = deriveAccentGradientStart(accent);
74
+ return [
75
+ ["#FF6B35", accent],
76
+ ["#D9461C", accentDark],
77
+ ["#FFB088", accentLight],
78
+ ["#FF7A45", gradientStart],
79
+ ["#fff4ef", mixHexWithWhite(accent, 0.94).toLowerCase()],
80
+ ["#ffe0d0", mixHexWithWhite(accent, 0.85).toLowerCase()],
81
+ ["#ffbfa0", mixHexWithWhite(accent, 0.7).toLowerCase()],
82
+ ["#ff9970", mixHexWithWhite(accent, 0.55).toLowerCase()],
83
+ ["#ff7d4d", mixHexWithWhite(accent, 0.4).toLowerCase()],
84
+ ["#b33a17", darkenHex(accentDark, 0.75)],
85
+ ["#8a2d12", darkenHex(accentDark, 0.58)],
86
+ ["#5e1e0c", darkenHex(accentDark, 0.4)],
87
+ ["#3a1107", darkenHex(accentDark, 0.25)]
88
+ ];
89
+ }
90
+ function renderEnv(brand) {
91
+ return [
92
+ `# Generated by @carrierllc/mcp`,
93
+ `NEXT_PUBLIC_BRAND_NAME=${JSON.stringify(brand.name)}`,
94
+ `NEXT_PUBLIC_CARRIER_API_URL=${JSON.stringify(brand.carrierApiUrl)}`,
95
+ `# Clerk (fill in from your Clerk dashboard to enable auth + checkout)`,
96
+ `NEXT_PUBLIC_CLERK_PUBLISHABLE_KEY=`,
97
+ `CLERK_SECRET_KEY=`,
98
+ ``
99
+ ].join("\n");
100
+ }
101
+
102
+ // src/cli/lib/plugin.ts
103
+ import { join as join3 } from "path";
104
+
105
+ // src/cli/lib/paths.ts
106
+ import { fileURLToPath } from "url";
107
+ import { dirname, join, resolve } from "path";
108
+ import { existsSync } from "fs";
109
+ import { homedir } from "os";
110
+ function packageRoot() {
111
+ const here = dirname(fileURLToPath(import.meta.url));
112
+ for (const candidate of [resolve(here, ".."), resolve(here, "..", "..")]) {
113
+ if (existsSync(join(candidate, "plugin")) && existsSync(join(candidate, "templates"))) {
114
+ return candidate;
115
+ }
116
+ }
117
+ return resolve(here, "..");
118
+ }
119
+ function pluginSourceDir() {
120
+ return join(packageRoot(), "plugin");
121
+ }
122
+ function pluginManifestDir() {
123
+ return join(packageRoot(), "plugin", "carrier");
124
+ }
125
+ function storefrontTemplateDir() {
126
+ return join(packageRoot(), "templates", "storefront");
127
+ }
128
+ function claudeHome() {
129
+ return process.env.CLAUDE_CONFIG_DIR ? resolve(process.env.CLAUDE_CONFIG_DIR) : join(homedir(), ".claude");
130
+ }
131
+ function claudePluginsDir() {
132
+ return join(claudeHome(), "plugins");
133
+ }
134
+ function installedPluginDir() {
135
+ return join(claudePluginsDir(), "carrier");
136
+ }
137
+
138
+ // src/cli/lib/fsx.ts
139
+ import { cp, mkdir, readdir, readFile, rm, stat, writeFile } from "fs/promises";
140
+ import { existsSync as existsSync2 } from "fs";
141
+ import { join as join2 } from "path";
142
+ var SKIP = /* @__PURE__ */ new Set([
143
+ "node_modules",
144
+ ".next",
145
+ ".turbo",
146
+ ".vercel",
147
+ "dist",
148
+ ".git",
149
+ "test-results",
150
+ "playwright-report"
151
+ ]);
152
+ var cpFilter = (s) => {
153
+ const base = s.split("/").pop() ?? "";
154
+ return !SKIP.has(base);
155
+ };
156
+ async function copyTree(src, dest) {
157
+ await mkdir(dest, { recursive: true });
158
+ for (const entry of await readdir(src, { withFileTypes: true })) {
159
+ if (SKIP.has(entry.name)) continue;
160
+ await cp(join2(src, entry.name), join2(dest, entry.name), {
161
+ recursive: true,
162
+ filter: cpFilter
163
+ });
164
+ }
165
+ }
166
+ async function pruneTree(src, dest) {
167
+ if (!await isDir(dest)) return;
168
+ for (const entry of await readdir(dest, { withFileTypes: true })) {
169
+ if (SKIP.has(entry.name)) continue;
170
+ const srcPath = join2(src, entry.name);
171
+ const destPath = join2(dest, entry.name);
172
+ if (!existsSync2(srcPath)) {
173
+ await rm(destPath, { recursive: true, force: true });
174
+ continue;
175
+ }
176
+ if (entry.isDirectory() && await isDir(srcPath)) await pruneTree(srcPath, destPath);
177
+ }
178
+ }
179
+ async function* walk(dir) {
180
+ for (const entry of await readdir(dir, { withFileTypes: true })) {
181
+ if (SKIP.has(entry.name)) continue;
182
+ const full = join2(dir, entry.name);
183
+ if (entry.isDirectory()) yield* walk(full);
184
+ else yield full;
185
+ }
186
+ }
187
+ var TEXT_EXT = /* @__PURE__ */ new Set([
188
+ ".ts",
189
+ ".tsx",
190
+ ".js",
191
+ ".jsx",
192
+ ".mjs",
193
+ ".cjs",
194
+ ".json",
195
+ ".css",
196
+ ".md",
197
+ ".mdx",
198
+ ".html",
199
+ ".txt",
200
+ ".env",
201
+ ".example",
202
+ ".yml",
203
+ ".yaml",
204
+ ".toml"
205
+ ]);
206
+ function isTextFile(path) {
207
+ const dot = path.lastIndexOf(".");
208
+ if (dot === -1) return false;
209
+ return TEXT_EXT.has(path.slice(dot));
210
+ }
211
+ async function replaceInTree(dir, subs) {
212
+ let touched = 0;
213
+ for await (const file of walk(dir)) {
214
+ if (!isTextFile(file)) continue;
215
+ const before = await readFile(file, "utf8");
216
+ let after = before;
217
+ for (const [from, to] of subs) {
218
+ after = typeof from === "string" ? after.split(from).join(to) : after.replace(from, to);
219
+ }
220
+ if (after !== before) {
221
+ await writeFile(file, after);
222
+ touched++;
223
+ }
224
+ }
225
+ return touched;
226
+ }
227
+ async function exists(p3) {
228
+ return existsSync2(p3);
229
+ }
230
+ async function isDir(p3) {
231
+ try {
232
+ return (await stat(p3)).isDirectory();
233
+ } catch {
234
+ return false;
235
+ }
236
+ }
237
+
238
+ // src/cli/lib/exec.ts
239
+ import { spawn } from "child_process";
240
+ function run(cmd, args, opts = {}) {
241
+ return new Promise((resolve3) => {
242
+ const child = spawn(cmd, args, { cwd: opts.cwd, shell: false });
243
+ let stdout = "";
244
+ let stderr = "";
245
+ let settled = false;
246
+ const finish = (result) => {
247
+ if (settled) return;
248
+ settled = true;
249
+ resolve3(result);
250
+ };
251
+ let timer;
252
+ if (opts.timeoutMs && opts.timeoutMs > 0) {
253
+ timer = setTimeout(() => {
254
+ try {
255
+ child.kill("SIGTERM");
256
+ } catch {
257
+ }
258
+ finish({
259
+ ok: false,
260
+ code: null,
261
+ stdout,
262
+ stderr: stderr || `timeout after ${opts.timeoutMs}ms`
263
+ });
264
+ }, opts.timeoutMs);
265
+ }
266
+ child.stdout?.on("data", (d) => stdout += d.toString());
267
+ child.stderr?.on("data", (d) => stderr += d.toString());
268
+ child.on("error", () => {
269
+ if (timer) clearTimeout(timer);
270
+ finish({ ok: false, code: null, stdout, stderr });
271
+ });
272
+ child.on("close", (code) => {
273
+ if (timer) clearTimeout(timer);
274
+ finish({ ok: code === 0, code, stdout, stderr });
275
+ });
276
+ });
277
+ }
278
+ function runInherit(cmd, args, opts = {}) {
279
+ return new Promise((resolve3) => {
280
+ const child = spawn(cmd, args, { cwd: opts.cwd, shell: false, stdio: "inherit" });
281
+ child.on("error", () => resolve3({ ok: false, code: null, stdout: "", stderr: "" }));
282
+ child.on("close", (code) => resolve3({ ok: code === 0, code, stdout: "", stderr: "" }));
283
+ });
284
+ }
285
+ async function which(bin) {
286
+ const probe = process.platform === "win32" ? "where" : "which";
287
+ const r = await run(probe, [bin]);
288
+ return r.ok && r.stdout.trim().length > 0;
289
+ }
290
+
291
+ // src/cli/lib/urls.ts
292
+ import { spawn as spawn2 } from "child_process";
293
+ var MCP_URL = "https://mcp.carrier.llc/mcp";
294
+ var MCP_HOME = "https://mcp.carrier.llc";
295
+ var SIGN_UP_URL = "https://accounts.carrier.llc/sign-up";
296
+ var SIGN_IN_URL = "https://accounts.carrier.llc/sign-in";
297
+ var CONSOLE_URL = "https://app.carrier.llc";
298
+ var ONBOARDING_URL = "https://app.carrier.llc/onboarding";
299
+ async function openUrl(url) {
300
+ const platform = process.platform;
301
+ if (platform === "darwin") {
302
+ const r = await run("open", [url]);
303
+ return r.ok ? { ok: true, hint: `Opened ${url}` } : { ok: false, hint: `Could not open browser. Visit:
304
+ ${url}` };
305
+ }
306
+ if (platform === "win32") {
307
+ const r = await run("cmd", ["/c", "start", "", url]);
308
+ return r.ok ? { ok: true, hint: `Opened ${url}` } : { ok: false, hint: `Could not open browser. Visit:
309
+ ${url}` };
310
+ }
311
+ for (const bin of ["xdg-open", "open"]) {
312
+ const r = await run(bin, [url]);
313
+ if (r.ok) return { ok: true, hint: `Opened ${url}` };
314
+ }
315
+ try {
316
+ spawn2("xdg-open", [url], { detached: true, stdio: "ignore" }).unref();
317
+ return { ok: true, hint: `Launched browser for ${url}` };
318
+ } catch {
319
+ return { ok: false, hint: `Could not open browser. Visit:
320
+ ${url}` };
321
+ }
322
+ }
323
+
324
+ // src/cli/lib/plugin.ts
325
+ var MCP_NAME = "carrier";
326
+ async function installPlugin() {
327
+ const marketplace = pluginSourceDir();
328
+ const pluginRoot = pluginManifestDir();
329
+ const dest = installedPluginDir();
330
+ const notes = [];
331
+ if (!await exists(pluginRoot)) {
332
+ throw new Error(`Bundled plugin not found at ${pluginRoot}. Reinstall @carrierllc/mcp.`);
333
+ }
334
+ await copyTree(pluginRoot, dest);
335
+ const claudeFound = await which("claude");
336
+ let marketplaceAdded = false;
337
+ let pluginInstalled = false;
338
+ let mcpAdded = false;
339
+ if (claudeFound) {
340
+ const mkt = await run("claude", ["plugin", "marketplace", "add", marketplace]);
341
+ marketplaceAdded = mkt.ok && !/✘|failed|error/i.test(mkt.stdout + mkt.stderr) || /already/i.test(mkt.stderr + mkt.stdout);
342
+ if (!marketplaceAdded) notes.push(`marketplace add: ${(mkt.stderr || mkt.stdout).trim().slice(0, 200)}`);
343
+ const inst = await run("claude", ["plugin", "install", "carrier@carrier"]);
344
+ const instOut = inst.stdout + inst.stderr;
345
+ pluginInstalled = /✔|success(fully)? installed|already (installed|enabled)/i.test(instOut) && !/✘|failed/i.test(instOut);
346
+ if (!pluginInstalled) notes.push(`plugin install: ${instOut.replace(/\s+/g, " ").trim().slice(0, 200)}`);
347
+ const mcp = await run("claude", ["mcp", "add", "--transport", "http", MCP_NAME, MCP_URL]);
348
+ mcpAdded = mcp.ok || /already|exists/i.test(mcp.stderr + mcp.stdout);
349
+ if (!mcpAdded) notes.push(`mcp add: ${(mcp.stderr || mcp.stdout).trim().slice(0, 200)}`);
350
+ } else {
351
+ notes.push("`claude` CLI not on PATH \u2014 plugin copied; finish wiring with the manual commands below.");
352
+ }
353
+ return { copiedTo: dest, marketplaceAdded, pluginInstalled, mcpAdded, claudeFound, notes };
354
+ }
355
+ async function pluginStatus() {
356
+ const dir = installedPluginDir();
357
+ const manifest = join3(dir, ".claude-plugin", "plugin.json");
358
+ if (!await exists(manifest)) return { installed: false, dir };
359
+ try {
360
+ const json = JSON.parse(await readFile(manifest, "utf8"));
361
+ return { installed: true, version: json.version, dir };
362
+ } catch {
363
+ return { installed: true, dir };
364
+ }
365
+ }
366
+ function manualCommands() {
367
+ return [
368
+ `claude plugin marketplace add ${pluginSourceDir()}`,
369
+ `claude plugin install carrier@carrier`,
370
+ `claude mcp add --transport http ${MCP_NAME} ${MCP_URL}`
371
+ ];
372
+ }
373
+ function installAuthGuidance() {
374
+ return [
375
+ `MCP URL (zero credentials): ${MCP_URL}`,
376
+ "OAuth on first use: when Claude first calls Carrier, your browser opens",
377
+ " Clerk sign-in / sign-up (Google, GitHub, or email). No API token to paste.",
378
+ "Stuck on auth? claude mcp auth carrier",
379
+ "Headless/CI only: org API key (ak_\u2026) from https://app.carrier.llc \u2192 Settings \u2192 API Keys"
380
+ ];
381
+ }
382
+
383
+ // src/cli/lib/whitelabel.ts
384
+ import { join as join4 } from "path";
385
+ function preserveClerkFromEnv(existing, fresh) {
386
+ let out = fresh;
387
+ for (const key of ["NEXT_PUBLIC_CLERK_PUBLISHABLE_KEY", "CLERK_SECRET_KEY"]) {
388
+ const line = existing.split("\n").find((l) => l.startsWith(`${key}=`));
389
+ if (!line) continue;
390
+ const value = line.slice(key.length + 1);
391
+ if (!value.trim()) continue;
392
+ out = out.replace(new RegExp(`^${key}=.*$`, "m"), `${key}=${value}`);
393
+ }
394
+ return out;
395
+ }
396
+ async function scaffoldStorefront(target, brand) {
397
+ const template = storefrontTemplateDir();
398
+ if (!await exists(template)) {
399
+ throw new Error(
400
+ `Storefront template not found at ${template}. The @carrierllc/mcp package may be corrupt \u2014 reinstall it.`
401
+ );
402
+ }
403
+ const envPath = join4(target, ".env.local");
404
+ const existingEnv = await exists(envPath) ? await readFile(envPath, "utf8") : void 0;
405
+ await copyTree(template, target);
406
+ await pruneTree(template, target);
407
+ const subs = [
408
+ ["Mango", brand.name],
409
+ ["\u{1F96D}", ""]
410
+ ];
411
+ subs.push(...deriveAccentPaletteSubs(brand.colors.accent, brand.colors.accentDark));
412
+ if (brand.colors.bg.toUpperCase() !== "#080C16") subs.push(["#080C16", brand.colors.bg]);
413
+ if (brand.colors.text.toUpperCase() !== "#F5F1EA") subs.push(["#F5F1EA", brand.colors.text]);
414
+ await replaceInTree(target, subs);
415
+ await patchBrandConfig(join4(target, "src", "brand.config.ts"), brand);
416
+ const envBody = existingEnv ? preserveClerkFromEnv(existingEnv, renderEnv(brand)) : renderEnv(brand);
417
+ await writeFile(envPath, envBody);
418
+ await writeFile(join4(target, ".npmrc"), "ignore-workspace-root-check=true\n");
419
+ }
420
+ async function patchBrandConfig(path, brand) {
421
+ if (!await exists(path)) return;
422
+ let src = await readFile(path, "utf8");
423
+ const setField = (field, value) => {
424
+ const re = new RegExp(`(\\b${field}:\\s*(?:[^"\\n]*\\?\\?\\s*)?)"[^"]*"`);
425
+ src = src.replace(re, `$1${JSON.stringify(value)}`);
426
+ };
427
+ setField("name", brand.name);
428
+ setField("legalName", brand.legalName);
429
+ setField("tagline", brand.tagline);
430
+ setField("domain", brand.domain);
431
+ setField("supportUrl", brand.supportUrl);
432
+ setField("supportEmail", brand.supportEmail);
433
+ setField("supportWhatsapp", brand.supportWhatsapp);
434
+ setField("accent", brand.colors.accent);
435
+ setField("accentDark", brand.colors.accentDark);
436
+ setField("accentLight", deriveAccentLight(brand.colors.accent));
437
+ setField("bg", brand.colors.bg);
438
+ const socialEntries = Object.entries(brand.social).filter(([, v]) => v);
439
+ const socialBody = socialEntries.map(([k, v]) => ` ${k}: ${JSON.stringify(v)},`).join("\n");
440
+ src = src.replace(
441
+ /social:\s*\{[^}]*\}/s,
442
+ socialEntries.length ? `social: {
443
+ ${socialBody}
444
+ }` : "social: {}"
445
+ );
446
+ await writeFile(path, src);
447
+ }
448
+
449
+ // src/cli/lib/site.ts
450
+ import { join as join5 } from "path";
451
+ function slug(s) {
452
+ return s.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "").slice(0, 40) || "storefront";
453
+ }
454
+ async function installDeps(target) {
455
+ const pkgMgr = await which("pnpm") ? "pnpm" : "npm";
456
+ const r = await runInherit(pkgMgr, ["install"], { cwd: target });
457
+ return r.ok;
458
+ }
459
+ async function buildSite(target) {
460
+ const pkgMgr = await which("pnpm") ? "pnpm" : "npm";
461
+ const r = await runInherit(pkgMgr, ["run", "cf:build"], { cwd: target });
462
+ return r.ok;
463
+ }
464
+ async function loadStorefrontBrand(target, overrides) {
465
+ const configPath = join5(target, "src", "brand.config.ts");
466
+ if (!await exists(configPath)) {
467
+ return { ...CARRIER_BRAND, ...overrides };
468
+ }
469
+ const src = await readFile(configPath, "utf8");
470
+ const pick = (field, fallback) => {
471
+ const m = src.match(new RegExp(`\\b${field}:\\s*(?:[^"\\n]*\\?\\?\\s*)?"([^"]*)"`));
472
+ return m?.[1] ?? fallback;
473
+ };
474
+ return {
475
+ ...CARRIER_BRAND,
476
+ name: overrides?.name ?? pick("name", CARRIER_BRAND.name),
477
+ domain: pick("domain", CARRIER_BRAND.domain),
478
+ supportEmail: pick("supportEmail", CARRIER_BRAND.supportEmail),
479
+ supportUrl: pick("supportUrl", CARRIER_BRAND.supportUrl),
480
+ tagline: pick("tagline", CARRIER_BRAND.tagline),
481
+ legalName: pick("legalName", CARRIER_BRAND.legalName),
482
+ colors: {
483
+ ...CARRIER_BRAND.colors,
484
+ accent: pick("accent", CARRIER_BRAND.colors.accent),
485
+ accentDark: pick("accentDark", CARRIER_BRAND.colors.accentDark),
486
+ bg: pick("bg", CARRIER_BRAND.colors.bg),
487
+ text: pick("text", CARRIER_BRAND.colors.text)
488
+ },
489
+ carrierApiUrl: CARRIER_BRAND.carrierApiUrl
490
+ };
491
+ }
492
+ async function deploySite(target, brand) {
493
+ const projectName = slug(brand.name);
494
+ const workerBundle = join5(target, ".open-next", "worker.js");
495
+ if (!await exists(workerBundle)) {
496
+ return {
497
+ ok: false,
498
+ projectName,
499
+ reason: "No OpenNext build found \u2014 run `carrier site deploy` (build step) or `pnpm run build` in the storefront first."
500
+ };
501
+ }
502
+ if (!await which("wrangler") && !await which("npx")) {
503
+ return { ok: false, projectName, reason: "wrangler/npx not found \u2014 install wrangler to deploy." };
504
+ }
505
+ const bin = await which("wrangler") ? "wrangler" : "npx";
506
+ const args = bin === "wrangler" ? ["deploy", "--name", projectName] : ["wrangler", "deploy", "--name", projectName];
507
+ const r = await runInherit(bin, args, { cwd: target });
508
+ return r.ok ? { ok: true, projectName } : { ok: false, projectName, reason: "wrangler deploy failed \u2014 run `wrangler login` then retry `carrier site deploy`." };
509
+ }
510
+
511
+ // src/cli/lib/status.ts
512
+ import { homedir as homedir2 } from "os";
513
+ import { join as join6 } from "path";
514
+ function detectEnvToken() {
515
+ const api = process.env.CARRIER_API_KEY?.trim() || process.env.CARRIER_ORG_API_KEY?.trim() || "";
516
+ if (api.startsWith("ak_") || api.length > 0) {
517
+ return { present: true, kind: "api-key" };
518
+ }
519
+ const ocs = process.env.ESIMVAULT_API_TOKEN?.trim() || process.env.CARRIER_OCS_API_TOKEN?.trim() || "";
520
+ if (ocs) return { present: true, kind: "ocs-token" };
521
+ return { present: false, kind: "none" };
522
+ }
523
+ function rankHit(name, url) {
524
+ let rank = 0;
525
+ if (name.toLowerCase() === MCP_NAME) rank += 100;
526
+ else if (/^carrier[-_]?/i.test(name) && !/test|staging|stg|dev|local/i.test(name)) rank += 40;
527
+ else if (/carrier/i.test(name) && !/test|staging|stg|dev|local/i.test(name)) rank += 20;
528
+ else if (/carrier/i.test(name)) rank += 5;
529
+ if (url === MCP_URL || url.includes("https://mcp.carrier.llc/mcp")) rank += 50;
530
+ else if (/mcp\.carrier\.llc/i.test(url)) rank += 25;
531
+ return rank;
532
+ }
533
+ function scanMcpServers(servers) {
534
+ if (!servers || typeof servers !== "object") return null;
535
+ let best = null;
536
+ for (const [name, entry] of Object.entries(servers)) {
537
+ if (!/carrier/i.test(name) && !(typeof entry?.url === "string" && /carrier\.llc/i.test(entry.url))) {
538
+ continue;
539
+ }
540
+ const url = typeof entry?.url === "string" ? entry.url : "";
541
+ const rank = rankHit(name, url);
542
+ if (rank < 20 && !url.includes("mcp.carrier.llc")) {
543
+ continue;
544
+ }
545
+ const urlMatches = url === MCP_URL || url.includes("https://mcp.carrier.llc/mcp");
546
+ const hit = {
547
+ registered: true,
548
+ urlMatches,
549
+ detail: url ? `${name}: ${url}` : `${name}: ${entry?.command ?? "configured"}`,
550
+ rank
551
+ };
552
+ if (!best || hit.rank > best.rank) best = hit;
553
+ }
554
+ return best;
555
+ }
556
+ async function probeMcpFromConfig() {
557
+ const candidates = [
558
+ join6(homedir2(), ".claude.json"),
559
+ join6(claudeHome(), "settings.json"),
560
+ join6(claudeHome(), ".mcp.json"),
561
+ join6(process.cwd(), ".mcp.json")
562
+ ];
563
+ let best = null;
564
+ let configSeen = false;
565
+ for (const file of candidates) {
566
+ if (!await exists(file)) continue;
567
+ try {
568
+ const json = JSON.parse(await readFile(file, "utf8"));
569
+ configSeen = true;
570
+ const direct = scanMcpServers(json.mcpServers);
571
+ if (direct && (!best || direct.rank > best.rank)) best = direct;
572
+ if (json.projects) {
573
+ for (const proj of Object.values(json.projects)) {
574
+ const hit = scanMcpServers(proj?.mcpServers);
575
+ if (hit && (!best || hit.rank > best.rank)) best = hit;
576
+ }
577
+ }
578
+ } catch {
579
+ }
580
+ }
581
+ if (!best) return { hit: null, configSeen };
582
+ return {
583
+ hit: { registered: best.registered, urlMatches: best.urlMatches, detail: best.detail },
584
+ configSeen
585
+ };
586
+ }
587
+ async function probeMcpRegistration() {
588
+ const { hit, configSeen } = await probeMcpFromConfig();
589
+ if (hit) return hit;
590
+ if (configSeen) {
591
+ return {
592
+ registered: false,
593
+ urlMatches: false,
594
+ detail: "carrier not registered (no production MCP URL in Claude config)"
595
+ };
596
+ }
597
+ const claudeFound = await which("claude");
598
+ if (!claudeFound) {
599
+ return {
600
+ registered: false,
601
+ urlMatches: false,
602
+ detail: "claude CLI not on PATH"
603
+ };
604
+ }
605
+ const r = await run("claude", ["mcp", "list"], { timeoutMs: 5e3 });
606
+ const out = `${r.stdout}
607
+ ${r.stderr}`;
608
+ if (!r.ok && !out.trim()) {
609
+ return { registered: false, urlMatches: false, detail: "claude mcp list failed or timed out" };
610
+ }
611
+ if (/timeout after/i.test(r.stderr) && !/carrier/i.test(out)) {
612
+ return {
613
+ registered: false,
614
+ urlMatches: false,
615
+ detail: "claude mcp list timed out \u2014 run `claude mcp list` manually"
616
+ };
617
+ }
618
+ const lines = out.split("\n").map((l) => l.trim()).filter(Boolean);
619
+ const carrierLine = lines.find((l) => new RegExp(`^${MCP_NAME}\\s*:`, "i").test(l)) ?? lines.find((l) => /carrier/i.test(l) && /mcp\.carrier\.llc/i.test(l));
620
+ if (!carrierLine) {
621
+ return { registered: false, urlMatches: false, detail: "carrier not in claude mcp list" };
622
+ }
623
+ const urlMatches = carrierLine.includes(MCP_URL) || /mcp\.carrier\.llc\/mcp/i.test(carrierLine);
624
+ return { registered: true, urlMatches, detail: carrierLine.slice(0, 200) };
625
+ }
626
+ function buildNextSteps(s) {
627
+ const steps = [];
628
+ if (!s.claudeFound) {
629
+ steps.push("Install Claude Code (https://claude.ai/code), then re-run: carrier plugin install");
630
+ } else if (!s.pluginInstalled) {
631
+ steps.push("Install the Carrier plugin: carrier plugin install");
632
+ } else if (!s.mcpRegistered) {
633
+ steps.push(`Register the zero-cred MCP URL: claude mcp add --transport http ${MCP_NAME} ${MCP_URL}`);
634
+ } else if (!s.mcpUrlMatches) {
635
+ steps.push(`Update MCP to production URL: claude mcp add --transport http ${MCP_NAME} ${MCP_URL}`);
636
+ }
637
+ if (s.mcpRegistered || s.pluginInstalled) {
638
+ steps.push(
639
+ "Talk to your fleet in Claude \u2014 first tool call opens Clerk OAuth (Google / GitHub / email). No API token needed."
640
+ );
641
+ } else {
642
+ steps.push("Create a free account (browser): carrier open signup");
643
+ steps.push("Or sign in: carrier open signin");
644
+ }
645
+ if (!s.tokenPresent) {
646
+ steps.push(
647
+ "Optional headless key: Console \u2192 Settings \u2192 API Keys (ak_\u2026) then export CARRIER_API_KEY=ak_\u2026 for `carrier ask`."
648
+ );
649
+ } else {
650
+ steps.push('Try a natural-language fleet query: carrier ask "show fleet health"');
651
+ }
652
+ steps.push("Scaffold a white-label storefront anytime: carrier site create");
653
+ return steps;
654
+ }
655
+ async function gatherStatus() {
656
+ const [plugin2, mcp, claudeFound] = await Promise.all([
657
+ pluginStatus(),
658
+ probeMcpRegistration(),
659
+ which("claude")
660
+ ]);
661
+ const token = detectEnvToken();
662
+ let authMode = "none";
663
+ if (token.kind === "api-key") authMode = "api-key";
664
+ else if (token.kind === "ocs-token") authMode = "ocs-token";
665
+ else if (mcp.registered || plugin2.installed) authMode = "oauth-first-use";
666
+ const base = {
667
+ pluginInstalled: plugin2.installed,
668
+ pluginVersion: plugin2.version,
669
+ pluginDir: plugin2.dir,
670
+ claudeFound,
671
+ mcpRegistered: mcp.registered,
672
+ mcpUrlMatches: mcp.urlMatches,
673
+ mcpDetail: mcp.detail,
674
+ tokenPresent: token.present,
675
+ tokenKind: token.kind,
676
+ authMode
677
+ };
678
+ return { ...base, nextSteps: buildNextSteps(base) };
679
+ }
680
+ function resolveCliToken() {
681
+ const api = process.env.CARRIER_API_KEY?.trim() || process.env.CARRIER_ORG_API_KEY?.trim() || "";
682
+ if (api) return api;
683
+ const ocs = process.env.ESIMVAULT_API_TOKEN?.trim() || process.env.CARRIER_OCS_API_TOKEN?.trim() || "";
684
+ return ocs || null;
685
+ }
686
+
687
+ // src/cli/lib/guidance.ts
688
+ import pc from "picocolors";
689
+ var FLEET_NL_EXAMPLES = [
690
+ { label: "Fleet health", prompt: "Show my fleet health \u2014 accounts, eSIMs, low-balance alerts" },
691
+ { label: "Subscriber lookup", prompt: "Look up subscriber ICCID 8944\u2026 and diagnose connectivity" },
692
+ { label: "Usage anomalies", prompt: "Detect usage anomalies and burn-rate risks this week" },
693
+ { label: "Issue from inventory", prompt: "Issue 3 free eSIMs from inventory on my account and assign the starter package" },
694
+ { label: "Billing check", prompt: "What's my platform credit balance and Stripe Connect payout status?" },
695
+ { label: "Wallet balance", prompt: "What's my managed prepaid wallet balance and auto-top-up status?" },
696
+ { label: "Churn risk", prompt: "List high churn-risk subscribers with retention ideas" }
697
+ ];
698
+ function oauthFirstUseNote() {
699
+ return [
700
+ "Entry A \u2014 zero credentials up front",
701
+ ` MCP URL: ${MCP_URL}`,
702
+ " Auth: OAuth on first use (Clerk \u2014 Google, GitHub, or email)",
703
+ " Token: none required in Claude / Cursor / Windsurf",
704
+ "",
705
+ "When you first talk to Carrier in your MCP client, the browser opens",
706
+ "sign-in/sign-up. After authorize, tools work. Headless? Use an org API key",
707
+ `(ak_\u2026) from ${CONSOLE_URL} \u2192 Settings \u2192 API Keys.`
708
+ ].join("\n");
709
+ }
710
+ function formatStatusBlock(st) {
711
+ const yn = (ok2, yes = "yes", no = "no") => ok2 ? pc.green(yes) : pc.yellow(no);
712
+ const authLabel = st.authMode === "oauth-first-use" ? pc.cyan("OAuth on first use (no token needed)") : st.authMode === "api-key" ? pc.green("org API key in env (CARRIER_API_KEY)") : st.authMode === "ocs-token" ? pc.green("OCS token in env") : pc.yellow("not ready \u2014 install MCP or open sign-up");
713
+ return [
714
+ `Claude Code: ${yn(st.claudeFound, "found", "not on PATH")}`,
715
+ `Plugin: ${st.pluginInstalled ? pc.green(`installed v${st.pluginVersion ?? "?"}`) : pc.yellow("not installed")}`,
716
+ `MCP registered:${st.mcpRegistered ? pc.green(" yes") : pc.yellow(" no")}${st.mcpDetail ? pc.dim(` (${st.mcpDetail})`) : ""}`,
717
+ `MCP URL: ${st.mcpUrlMatches ? pc.green(MCP_URL) : pc.yellow("not production / missing")}`,
718
+ `Auth path: ${authLabel}`,
719
+ `Headless token:${st.tokenPresent ? pc.green(` ${st.tokenKind}`) : pc.dim(" none (optional)")}`
720
+ ].join("\n");
721
+ }
722
+ function formatNextSteps(st) {
723
+ return st.nextSteps.map((s, i) => ` ${i + 1}. ${s}`).join("\n");
724
+ }
725
+ function formatNlExamples() {
726
+ return FLEET_NL_EXAMPLES.map((e) => ` \u2022 ${pc.bold(e.label)}: "${e.prompt}"`).join("\n");
727
+ }
728
+ function accountLinksNote() {
729
+ return [
730
+ `Sign up: ${SIGN_UP_URL}`,
731
+ `Sign in: ${SIGN_IN_URL}`,
732
+ `Console: ${CONSOLE_URL}`,
733
+ `Onboarding: ${ONBOARDING_URL}`,
734
+ `MCP home: https://mcp.carrier.llc`
735
+ ].join("\n");
736
+ }
737
+ function withNextStep(errorMsg, next) {
738
+ return [errorMsg, "", "What to do next:", ...next.map((s) => ` \u2192 ${s}`)].join("\n");
739
+ }
740
+
741
+ // src/cli/lib/home.ts
742
+ import * as p from "@clack/prompts";
743
+ import pc2 from "picocolors";
744
+
745
+ // src/cli/lib/ask.ts
746
+ async function carrierAsk(intent) {
747
+ const token = resolveCliToken();
748
+ if (!token) {
749
+ return {
750
+ ok: false,
751
+ text: withNextStep("No headless token in the environment.", [
752
+ "Interactive path: open Claude and say your intent \u2014 OAuth runs on first use.",
753
+ "Or export CARRIER_API_KEY=ak_\u2026 from Console \u2192 Settings \u2192 API Keys.",
754
+ 'Then retry: carrier ask "show fleet health"'
755
+ ])
756
+ };
757
+ }
758
+ const body = {
759
+ jsonrpc: "2.0",
760
+ id: 1,
761
+ method: "tools/call",
762
+ params: {
763
+ name: "carrier_ask",
764
+ arguments: { intent }
765
+ }
766
+ };
767
+ try {
768
+ const res = await fetch(MCP_URL, {
769
+ method: "POST",
770
+ headers: {
771
+ Authorization: `Bearer ${token}`,
772
+ "Content-Type": "application/json",
773
+ Accept: "application/json, text/event-stream"
774
+ },
775
+ body: JSON.stringify(body)
776
+ });
777
+ if (res.status === 401 || res.status === 403) {
778
+ return {
779
+ ok: false,
780
+ text: withNextStep(`MCP returned ${res.status} (auth rejected).`, [
781
+ "Confirm CARRIER_API_KEY is a valid org key (ak_\u2026) from app.carrier.llc",
782
+ "Or complete OCS onboarding: https://app.carrier.llc/onboarding",
783
+ "Interactive: use Claude + OAuth instead of a headless key"
784
+ ])
785
+ };
786
+ }
787
+ if (!res.ok) {
788
+ const snippet = (await res.text().catch(() => "")).slice(0, 240);
789
+ return {
790
+ ok: false,
791
+ text: withNextStep(`MCP call failed (${res.status}). ${snippet}`, [
792
+ "Check network access to https://mcp.carrier.llc/mcp",
793
+ "Retry later, or use Claude with the registered MCP (OAuth path)"
794
+ ])
795
+ };
796
+ }
797
+ const json = await res.json();
798
+ if (json.error?.message) {
799
+ return {
800
+ ok: false,
801
+ text: withNextStep(json.error.message, [
802
+ "Rephrase the intent more specifically (include ICCID / MSISDN if relevant)",
803
+ "Or open Claude and ask there with full tool routing"
804
+ ])
805
+ };
806
+ }
807
+ const parts = json.result?.content ?? [];
808
+ const text3 = parts.map((p3) => p3.text).filter(Boolean).join("\n").trim() || JSON.stringify(json.result ?? json, null, 2);
809
+ return { ok: !json.result?.isError, text: text3 };
810
+ } catch (e) {
811
+ const msg = e instanceof Error ? e.message : String(e);
812
+ return {
813
+ ok: false,
814
+ text: withNextStep(`Could not reach MCP: ${msg}`, [
815
+ "Verify outbound HTTPS to mcp.carrier.llc",
816
+ "Use the interactive Claude path while offline from this machine"
817
+ ])
818
+ };
819
+ }
820
+ }
821
+
822
+ // src/cli/lib/home.ts
823
+ function mark(ok2) {
824
+ return ok2 ? pc2.green("\u25CF") : pc2.yellow("\u25CB");
825
+ }
826
+ function printStatus(st) {
827
+ p.note(formatStatusBlock(st), "Status");
828
+ p.note(formatNextSteps(st), "Next steps");
829
+ }
830
+ async function showHomeStatus() {
831
+ const s = p.spinner();
832
+ s.start("Checking Claude plugin + MCP registration");
833
+ const st = await gatherStatus();
834
+ s.stop("Status ready");
835
+ printStatus(st);
836
+ return st;
837
+ }
838
+ async function openAndReport(url, label) {
839
+ const r = await openUrl(url);
840
+ if (r.ok) p.log.success(r.hint);
841
+ else {
842
+ p.log.warn(r.hint);
843
+ p.note(url, label);
844
+ }
845
+ }
846
+ async function runHome(opts) {
847
+ let st = await showHomeStatus();
848
+ p.note(oauthFirstUseNote(), "How auth works");
849
+ for (; ; ) {
850
+ const hasToken = !!resolveCliToken();
851
+ const choice = await p.select({
852
+ message: "What do you want to do?",
853
+ options: [
854
+ {
855
+ value: "refresh",
856
+ label: `${mark(st.mcpRegistered && st.pluginInstalled)} Refresh status`,
857
+ hint: "Re-check plugin + MCP"
858
+ },
859
+ {
860
+ value: "install",
861
+ label: `${mark(st.pluginInstalled && st.mcpRegistered)} Install / re-register plugin + MCP`,
862
+ hint: "Zero-cred URL \xB7 OAuth on first use"
863
+ },
864
+ {
865
+ value: "signup",
866
+ label: "Open sign-up",
867
+ hint: "Create a Carrier account (Google / GitHub / email)"
868
+ },
869
+ {
870
+ value: "signin",
871
+ label: "Open sign-in",
872
+ hint: "Existing account"
873
+ },
874
+ {
875
+ value: "console",
876
+ label: "Open console",
877
+ hint: CONSOLE_URL
878
+ },
879
+ {
880
+ value: "onboarding",
881
+ label: "Open onboarding (link OCS / managed setup)",
882
+ hint: ONBOARDING_URL
883
+ },
884
+ {
885
+ value: "examples",
886
+ label: "Talk to fleet \u2014 NL examples for Claude",
887
+ hint: "Copy-paste prompts"
888
+ },
889
+ {
890
+ value: "ask",
891
+ label: hasToken ? "Ask the fleet (carrier ask)" : "Ask the fleet (needs CARRIER_API_KEY)",
892
+ hint: hasToken ? "Uses env token" : "Optional headless path"
893
+ },
894
+ {
895
+ value: "site",
896
+ label: "Scaffold white-label storefront",
897
+ hint: "carrier site create"
898
+ },
899
+ { value: "exit", label: "Exit" }
900
+ ]
901
+ });
902
+ if (p.isCancel(choice) || choice === "exit") {
903
+ p.outro(pc2.dim("Run `carrier` anytime for this menu. Talk to your fleet in Claude."));
904
+ return;
905
+ }
906
+ switch (choice) {
907
+ case "refresh":
908
+ st = await showHomeStatus();
909
+ break;
910
+ case "install":
911
+ await opts.onInstall();
912
+ p.note(oauthFirstUseNote(), "OAuth on first use");
913
+ st = await showHomeStatus();
914
+ break;
915
+ case "signup":
916
+ await openAndReport(SIGN_UP_URL, "Sign-up URL");
917
+ p.log.info("After sign-up, return here or open Claude and talk to Carrier \u2014 OAuth may already be done.");
918
+ break;
919
+ case "signin":
920
+ await openAndReport(SIGN_IN_URL, "Sign-in URL");
921
+ break;
922
+ case "console":
923
+ await openAndReport(CONSOLE_URL, "Console");
924
+ break;
925
+ case "onboarding":
926
+ await openAndReport(ONBOARDING_URL, "Onboarding");
927
+ p.log.info("Link OCS credentials (BYO) or finish managed setup, then use Claude MCP.");
928
+ break;
929
+ case "examples":
930
+ p.note(formatNlExamples(), "Paste into Claude (with Carrier MCP connected)");
931
+ p.note(
932
+ [
933
+ "In Claude Code after install:",
934
+ ' "Show my fleet health"',
935
+ " /carrier:fleet",
936
+ " /carrier:status",
937
+ "",
938
+ `MCP home: ${MCP_HOME}`
939
+ ].join("\n"),
940
+ "Talk to fleet"
941
+ );
942
+ {
943
+ const go = await p.confirm({
944
+ message: "Open mcp.carrier.llc in the browser?",
945
+ initialValue: false
946
+ });
947
+ if (!p.isCancel(go) && go) await openAndReport(MCP_HOME, "MCP home");
948
+ }
949
+ break;
950
+ case "ask": {
951
+ if (!resolveCliToken()) {
952
+ p.log.warn("No CARRIER_API_KEY / OCS token in env.");
953
+ p.note(
954
+ [
955
+ "Interactive (recommended): install MCP, open Claude, ask in plain English.",
956
+ "Headless: Console \u2192 Settings \u2192 API Keys \u2192 export CARRIER_API_KEY=ak_\u2026",
957
+ "",
958
+ "Example prompts:",
959
+ ...FLEET_NL_EXAMPLES.slice(0, 3).map((e) => ` carrier ask "${e.prompt}"`)
960
+ ].join("\n"),
961
+ "How to ask"
962
+ );
963
+ break;
964
+ }
965
+ const intent = await p.text({
966
+ message: "What should Carrier do?",
967
+ placeholder: "show fleet health"
968
+ });
969
+ if (p.isCancel(intent) || !String(intent).trim()) break;
970
+ const spin = p.spinner();
971
+ spin.start("Asking Carrier MCP\u2026");
972
+ const result = await carrierAsk(String(intent).trim());
973
+ spin.stop(result.ok ? "Answer" : "Could not complete ask");
974
+ if (result.ok) p.note(result.text.slice(0, 4e3), "carrier ask");
975
+ else p.log.error(result.text);
976
+ break;
977
+ }
978
+ case "site":
979
+ await opts.onSiteCreate();
980
+ return;
981
+ default:
982
+ break;
983
+ }
984
+ }
985
+ }
986
+
987
+ // src/cli/index.ts
988
+ var VERSION = "0.2.18";
989
+ function header() {
990
+ p2.intro(`${pc3.bold(pc3.yellow("\u25C6 carrier"))} ${pc3.dim("\xB7 the Stripe of telecom \u2014 CLI v" + VERSION)}`);
991
+ }
992
+ function ok(msg) {
993
+ p2.log.success(pc3.green(msg));
994
+ }
995
+ function info(msg) {
996
+ p2.log.info(msg);
997
+ }
998
+ function fail(msg, next) {
999
+ p2.log.error(msg);
1000
+ p2.note(next.map((s) => `\u2192 ${s}`).join("\n"), "What to do next");
1001
+ }
1002
+ async function promptBrand(seed) {
1003
+ const name = await p2.text({
1004
+ message: "Brand name",
1005
+ placeholder: seed.name,
1006
+ defaultValue: seed.name
1007
+ });
1008
+ if (p2.isCancel(name)) process.exit(0);
1009
+ const domain = await p2.text({
1010
+ message: "Domain",
1011
+ placeholder: seed.domain,
1012
+ defaultValue: seed.domain
1013
+ });
1014
+ if (p2.isCancel(domain)) process.exit(0);
1015
+ const accent = await p2.text({
1016
+ message: "Accent color (hex)",
1017
+ placeholder: seed.colors.accent,
1018
+ defaultValue: seed.colors.accent
1019
+ });
1020
+ if (p2.isCancel(accent)) process.exit(0);
1021
+ const supportEmail = await p2.text({
1022
+ message: "Support email",
1023
+ placeholder: `support@${domain}`,
1024
+ defaultValue: `support@${domain}`
1025
+ });
1026
+ if (p2.isCancel(supportEmail)) process.exit(0);
1027
+ const accentDark = deriveAccentDark(accent, seed);
1028
+ const isCarrier = name === CARRIER_BRAND.name && domain === CARRIER_BRAND.domain;
1029
+ return {
1030
+ ...seed,
1031
+ name,
1032
+ legalName: name,
1033
+ tagline: isCarrier ? seed.tagline : `Mobile connectivity by ${name}.`,
1034
+ domain,
1035
+ supportEmail,
1036
+ supportUrl: `https://${domain}/help`,
1037
+ supportWhatsapp: isCarrier ? seed.supportWhatsapp : "",
1038
+ social: isCarrier ? seed.social : {},
1039
+ colors: { ...seed.colors, accent, accentDark }
1040
+ };
1041
+ }
1042
+ async function doPluginInstall() {
1043
+ const s = p2.spinner();
1044
+ s.start("Installing Carrier Claude Code plugin + zero-cred MCP");
1045
+ let r;
1046
+ try {
1047
+ r = await installPlugin();
1048
+ } catch (e) {
1049
+ s.stop("Install failed");
1050
+ fail(String(e instanceof Error ? e.message : e), [
1051
+ "Reinstall the package: npm i -g @carrierllc/mcp (or npx @carrierllc/mcp)",
1052
+ "Or finish manually with the commands under `carrier plugin install` help",
1053
+ `Sign up anytime: ${SIGN_UP_URL}`
1054
+ ]);
1055
+ return;
1056
+ }
1057
+ s.stop("Plugin staged");
1058
+ ok(`Plugin \u2192 ${r.copiedTo}`);
1059
+ info(
1060
+ `MCP: ${r.mcpAdded ? pc3.green("registered") : pc3.yellow("manual")} \xB7 marketplace: ${r.marketplaceAdded ? pc3.green("added") : pc3.yellow("manual")} \xB7 install: ${r.pluginInstalled ? pc3.green("done") : pc3.yellow("manual")}`
1061
+ );
1062
+ p2.note(installAuthGuidance().join("\n"), "OAuth on first use (Entry A)");
1063
+ if (!r.claudeFound || r.notes.length) {
1064
+ p2.note(manualCommands().join("\n"), "Finish wiring (run in your terminal)");
1065
+ if (r.notes.length) info(pc3.dim(r.notes.join("\n")));
1066
+ p2.note(
1067
+ [
1068
+ "You can still create an account now:",
1069
+ ` carrier open signup`,
1070
+ "Then open Claude and talk to your fleet \u2014 browser OAuth completes auth."
1071
+ ].join("\n"),
1072
+ "Next"
1073
+ );
1074
+ } else {
1075
+ p2.note(
1076
+ [
1077
+ "Open Claude Code and say something like:",
1078
+ ' "Show my fleet health"',
1079
+ "First call opens the browser for sign-in / sign-up. No token paste.",
1080
+ "",
1081
+ "More prompts: carrier \u2192 Talk to fleet"
1082
+ ].join("\n"),
1083
+ "Talk to your fleet"
1084
+ );
1085
+ }
1086
+ }
1087
+ async function doSiteCreate(target, brand) {
1088
+ const s = p2.spinner();
1089
+ s.start(`Scaffolding ${brand.name} storefront \u2192 ${target}`);
1090
+ try {
1091
+ await scaffoldStorefront(target, brand);
1092
+ } catch (e) {
1093
+ s.stop("Scaffold failed");
1094
+ fail(String(e instanceof Error ? e.message : e), [
1095
+ "Pick a free directory: carrier site create ./my-storefront",
1096
+ "Ensure the package templates shipped with @carrierllc/mcp",
1097
+ "Still stuck? carrier open console"
1098
+ ]);
1099
+ throw e;
1100
+ }
1101
+ s.stop("Storefront scaffolded");
1102
+ ok(`Created ${target} (white-labeled: ${brand.name}, accent ${brand.colors.accent})`);
1103
+ }
1104
+ async function maybeBuildDeploy(target, brand, opts) {
1105
+ if (opts.install) {
1106
+ const s = p2.spinner();
1107
+ s.start("Installing storefront dependencies");
1108
+ const oki = await installDeps(target);
1109
+ s.stop(oki ? "Dependencies installed" : "Dependency install reported errors");
1110
+ if (!oki) {
1111
+ p2.note(
1112
+ [
1113
+ `cd ${target} && pnpm install`,
1114
+ "Fix any Node/pnpm version issues, then retry build"
1115
+ ].join("\n"),
1116
+ "Next"
1117
+ );
1118
+ return;
1119
+ }
1120
+ }
1121
+ if (opts.build) {
1122
+ const s = p2.spinner();
1123
+ s.start("Building storefront (next build)");
1124
+ const okb = await buildSite(target);
1125
+ s.stop(okb ? "Build succeeded" : "Build failed \u2014 see output above");
1126
+ if (!okb) {
1127
+ p2.note(
1128
+ [`cd ${target}`, "pnpm build", "Check env keys in .env.local if the build mentions Clerk"].join("\n"),
1129
+ "Next"
1130
+ );
1131
+ return;
1132
+ }
1133
+ }
1134
+ if (opts.deploy) {
1135
+ const s = p2.spinner();
1136
+ s.start("Deploying to Cloudflare Workers");
1137
+ const r = await deploySite(target, brand);
1138
+ s.stop(r.ok ? `Deployed: ${r.projectName}` : "Deploy skipped");
1139
+ if (!r.ok && r.reason) {
1140
+ info(pc3.yellow(r.reason));
1141
+ p2.note(
1142
+ [
1143
+ "Install wrangler and login: npx wrangler login",
1144
+ `Then: carrier site deploy ${target}`
1145
+ ].join("\n"),
1146
+ "Next"
1147
+ );
1148
+ }
1149
+ }
1150
+ }
1151
+ async function interactiveSiteCreate() {
1152
+ const brand = await promptBrand(CARRIER_BRAND);
1153
+ const dir = await p2.text({
1154
+ message: "Output directory",
1155
+ placeholder: "./storefront",
1156
+ defaultValue: "./storefront"
1157
+ });
1158
+ if (p2.isCancel(dir)) return;
1159
+ const target = resolve2(dir);
1160
+ if (await exists(target)) {
1161
+ const go = await p2.confirm({ message: `${target} exists \u2014 write into it anyway?`, initialValue: false });
1162
+ if (p2.isCancel(go) || !go) {
1163
+ p2.outro("Stopped. Re-run with a different directory.");
1164
+ return;
1165
+ }
1166
+ }
1167
+ await doSiteCreate(target, brand);
1168
+ p2.outro(pc3.green(`Scaffolded. cd ${dir} && pnpm install && pnpm dev`));
1169
+ }
1170
+ var program = new Command();
1171
+ program.name("carrier").description(
1172
+ "Carrier CLI \u2014 clear TUI for non-developers: status, OAuth-ready MCP install, storefront scaffold, and fleet NL helpers."
1173
+ ).version(VERSION).action(async () => {
1174
+ header();
1175
+ await runHome({
1176
+ onInstall: doPluginInstall,
1177
+ onSiteCreate: interactiveSiteCreate
1178
+ });
1179
+ });
1180
+ program.command("init").description("Interactive home: status, install plugin+MCP (OAuth on first use), optional storefront.").option("--dir <path>", "Storefront output directory", "./storefront").option("--yes", "Use Carrier defaults, no prompts (plugin + scaffold + build)").option("--full", "After install, continue into storefront scaffold (default interactive path offers both)").action(async (o) => {
1181
+ header();
1182
+ if (o.yes) {
1183
+ await doPluginInstall();
1184
+ const brand = CARRIER_BRAND;
1185
+ const target = resolve2(o.dir);
1186
+ if (await exists(target)) {
1187
+ info(pc3.yellow(`${target} exists \u2014 writing into it (--yes).`));
1188
+ }
1189
+ await doSiteCreate(target, brand);
1190
+ await maybeBuildDeploy(target, brand, { install: true, build: true, deploy: false });
1191
+ p2.note(oauthFirstUseNote(), "Auth");
1192
+ p2.note(
1193
+ [
1194
+ `cd ${o.dir}`,
1195
+ `Edit src/brand.config.ts to re-brand anytime`,
1196
+ `pnpm dev # local preview`,
1197
+ `MCP endpoint: ${MCP_URL}`,
1198
+ 'In Claude: "Show my fleet health" (browser OAuth on first use)'
1199
+ ].join("\n"),
1200
+ "Next"
1201
+ );
1202
+ p2.outro(pc3.green("Done. Your connectivity business is wired."));
1203
+ return;
1204
+ }
1205
+ if (o.full) {
1206
+ const st = await gatherStatus();
1207
+ printStatus(st);
1208
+ await doPluginInstall();
1209
+ const brand = await promptBrand(CARRIER_BRAND);
1210
+ const target = resolve2(o.dir);
1211
+ if (await exists(target)) {
1212
+ const go = await p2.confirm({ message: `${target} exists \u2014 write into it anyway?`, initialValue: false });
1213
+ if (p2.isCancel(go) || !go) {
1214
+ p2.outro("Stopped. Re-run with --dir <new path>.");
1215
+ return;
1216
+ }
1217
+ }
1218
+ await doSiteCreate(target, brand);
1219
+ const next = await p2.select({
1220
+ message: "Roll it out now?",
1221
+ options: [
1222
+ { value: "build", label: "Install deps + build" },
1223
+ { value: "deploy", label: "Install + build + deploy to Cloudflare Workers" },
1224
+ { value: "none", label: "Just scaffold \u2014 I'll build later" }
1225
+ ],
1226
+ initialValue: "build"
1227
+ });
1228
+ if (p2.isCancel(next)) process.exit(0);
1229
+ await maybeBuildDeploy(target, brand, {
1230
+ install: next !== "none",
1231
+ build: next !== "none",
1232
+ deploy: next === "deploy"
1233
+ });
1234
+ p2.note(oauthFirstUseNote(), "Auth");
1235
+ p2.note(
1236
+ [
1237
+ `cd ${o.dir}`,
1238
+ `Edit src/brand.config.ts to re-brand anytime`,
1239
+ `pnpm dev # local preview`,
1240
+ `MCP endpoint: ${MCP_URL}`
1241
+ ].join("\n"),
1242
+ "Next"
1243
+ );
1244
+ p2.outro(pc3.green("Done. Your connectivity business is wired."));
1245
+ return;
1246
+ }
1247
+ await runHome({
1248
+ onInstall: doPluginInstall,
1249
+ onSiteCreate: async () => {
1250
+ const brand = await promptBrand(CARRIER_BRAND);
1251
+ const target = resolve2(o.dir);
1252
+ if (await exists(target)) {
1253
+ const go = await p2.confirm({ message: `${target} exists \u2014 write into it anyway?`, initialValue: false });
1254
+ if (p2.isCancel(go) || !go) {
1255
+ p2.log.info("Skipped storefront. Pick Install or Exit from the menu, or re-run with --dir.");
1256
+ return;
1257
+ }
1258
+ }
1259
+ await doSiteCreate(target, brand);
1260
+ const next = await p2.select({
1261
+ message: "Roll it out now?",
1262
+ options: [
1263
+ { value: "build", label: "Install deps + build" },
1264
+ { value: "deploy", label: "Install + build + deploy to Cloudflare Workers" },
1265
+ { value: "none", label: "Just scaffold \u2014 I'll build later" }
1266
+ ],
1267
+ initialValue: "build"
1268
+ });
1269
+ if (p2.isCancel(next)) return;
1270
+ await maybeBuildDeploy(target, brand, {
1271
+ install: next !== "none",
1272
+ build: next !== "none",
1273
+ deploy: next === "deploy"
1274
+ });
1275
+ p2.note(
1276
+ [
1277
+ `cd ${o.dir}`,
1278
+ `pnpm dev`,
1279
+ `MCP: ${MCP_URL} \xB7 OAuth on first use in Claude`
1280
+ ].join("\n"),
1281
+ "Next"
1282
+ );
1283
+ }
1284
+ });
1285
+ });
1286
+ var plugin = program.command("plugin").description("Manage the Carrier Claude Code plugin.");
1287
+ plugin.command("install").description("Install/register the Carrier plugin + zero-cred MCP (OAuth on first use).").action(async () => {
1288
+ header();
1289
+ await doPluginInstall();
1290
+ p2.outro(pc3.green("Plugin ready. Restart Claude Code, then talk to your fleet."));
1291
+ });
1292
+ plugin.command("status").description("Show plugin + MCP registration + auth next steps.").action(async () => {
1293
+ header();
1294
+ const st = await gatherStatus();
1295
+ printStatus(st);
1296
+ p2.note(oauthFirstUseNote(), "Auth");
1297
+ p2.outro("");
1298
+ });
1299
+ program.command("status").description("Show MCP / plugin / auth status and next steps.").action(async () => {
1300
+ header();
1301
+ const s = p2.spinner();
1302
+ s.start("Checking status");
1303
+ const st = await gatherStatus();
1304
+ s.stop("Done");
1305
+ p2.note(formatStatusBlock(st), "Status");
1306
+ p2.note(formatNextSteps(st), "Next steps");
1307
+ p2.note(oauthFirstUseNote(), "Auth");
1308
+ p2.outro("");
1309
+ });
1310
+ var openCmd = program.command("open").description("Open Carrier account / product URLs in your browser.");
1311
+ for (const [name, url, desc] of [
1312
+ ["signup", SIGN_UP_URL, "Create a Carrier account (Clerk sign-up)"],
1313
+ ["signin", SIGN_IN_URL, "Sign in to Carrier"],
1314
+ ["console", CONSOLE_URL, "Open the Carrier console"],
1315
+ ["onboarding", ONBOARDING_URL, "Open console onboarding (link OCS / managed)"],
1316
+ ["mcp", MCP_HOME, "Open mcp.carrier.llc product page"]
1317
+ ]) {
1318
+ openCmd.command(name).description(desc).action(async () => {
1319
+ header();
1320
+ const r = await openUrl(url);
1321
+ if (r.ok) ok(r.hint);
1322
+ else {
1323
+ p2.log.warn(r.hint);
1324
+ p2.note(url, "Open this URL");
1325
+ }
1326
+ p2.note(accountLinksNote(), "Account links");
1327
+ p2.outro("");
1328
+ });
1329
+ }
1330
+ program.command("examples").description("Print natural-language fleet prompts for Claude / MCP.").action(async () => {
1331
+ header();
1332
+ p2.note(formatNlExamples(), "Talk to fleet \u2014 paste into Claude");
1333
+ p2.note(
1334
+ [
1335
+ "After `carrier plugin install` (or this menu \u2192 Install):",
1336
+ " 1. Open Claude Code",
1337
+ ' 2. Say: "Show my fleet health"',
1338
+ " 3. Browser opens for sign-in/sign-up on first use",
1339
+ "",
1340
+ "Slash commands: /carrier:fleet /carrier:status /carrier:wallet /carrier:onboard"
1341
+ ].join("\n"),
1342
+ "How"
1343
+ );
1344
+ p2.outro("");
1345
+ });
1346
+ program.command("ask").description('Optional headless NL: carrier ask "show fleet health" (needs CARRIER_API_KEY or OCS token).').argument("<intent...>", "Natural-language intent").action(async (parts) => {
1347
+ header();
1348
+ const intent = parts.join(" ").trim();
1349
+ if (!intent) {
1350
+ fail("Missing intent.", [
1351
+ 'carrier ask "show fleet health"',
1352
+ "Or use Claude interactively (no token): carrier plugin install"
1353
+ ]);
1354
+ process.exitCode = 1;
1355
+ return;
1356
+ }
1357
+ const s = p2.spinner();
1358
+ s.start("Asking Carrier MCP\u2026");
1359
+ const result = await carrierAsk(intent);
1360
+ s.stop(result.ok ? "Done" : "Failed");
1361
+ if (result.ok) {
1362
+ p2.note(result.text.slice(0, 6e3), "Answer");
1363
+ p2.outro("");
1364
+ } else {
1365
+ p2.log.error(result.text);
1366
+ p2.outro(pc3.yellow("See next steps above."));
1367
+ process.exitCode = 1;
1368
+ }
1369
+ });
1370
+ var site = program.command("site").description("Scaffold and deploy a white-labeled storefront.");
1371
+ site.command("create [dir]").description("Scaffold a white-labeled storefront from the Mango template.").option("--name <name>", "Brand name").option("--domain <domain>", "Domain").option("--accent <hex>", "Accent color").option("--yes", "Carrier defaults, no prompts").action(async (dir, o) => {
1372
+ header();
1373
+ let brand = { ...CARRIER_BRAND };
1374
+ if (o.name || o.domain || o.accent) {
1375
+ const name = o.name ?? brand.name;
1376
+ const domain = o.domain ?? (o.name ? `${slug(o.name)}.com` : brand.domain);
1377
+ const isCarrier = name === CARRIER_BRAND.name && domain === CARRIER_BRAND.domain;
1378
+ brand = {
1379
+ ...brand,
1380
+ name,
1381
+ domain,
1382
+ legalName: o.name ?? brand.legalName,
1383
+ tagline: o.name ? `Mobile connectivity by ${o.name}.` : brand.tagline,
1384
+ supportEmail: `support@${domain}`,
1385
+ supportUrl: `https://${domain}/help`,
1386
+ supportWhatsapp: isCarrier ? brand.supportWhatsapp : "",
1387
+ social: isCarrier ? brand.social : {},
1388
+ colors: {
1389
+ ...brand.colors,
1390
+ accent: o.accent ?? brand.colors.accent,
1391
+ accentDark: o.accent ? deriveAccentDark(o.accent) : brand.colors.accentDark
1392
+ }
1393
+ };
1394
+ } else if (!o.yes) {
1395
+ brand = await promptBrand(CARRIER_BRAND);
1396
+ }
1397
+ const target = resolve2(dir ?? "./storefront");
1398
+ if (await exists(target)) {
1399
+ const go = await p2.confirm({ message: `${target} exists \u2014 write into it anyway?`, initialValue: false });
1400
+ if (p2.isCancel(go) || !go) {
1401
+ p2.outro("Stopped. Re-run with a different directory.");
1402
+ return;
1403
+ }
1404
+ }
1405
+ try {
1406
+ await doSiteCreate(target, brand);
1407
+ } catch {
1408
+ process.exitCode = 1;
1409
+ return;
1410
+ }
1411
+ p2.outro(pc3.green(`Scaffolded. cd ${dir ?? "storefront"} && pnpm install && pnpm dev`));
1412
+ });
1413
+ site.command("deploy [dir]").description("Build + deploy a storefront to Cloudflare Workers.").option("--name <name>", "Cloudflare Worker name (defaults from brand)").action(async (dir, o) => {
1414
+ header();
1415
+ const target = resolve2(dir ?? "./storefront");
1416
+ try {
1417
+ const brand = await loadStorefrontBrand(target, o.name ? { name: o.name } : void 0);
1418
+ await maybeBuildDeploy(target, brand, { install: true, build: true, deploy: true });
1419
+ } catch (e) {
1420
+ fail(String(e instanceof Error ? e.message : e), [
1421
+ "Scaffold first: carrier site create",
1422
+ "Ensure wrangler is logged in for deploy"
1423
+ ]);
1424
+ process.exitCode = 1;
1425
+ return;
1426
+ }
1427
+ p2.outro("");
1428
+ });
1429
+ program.parseAsync(process.argv).catch((e) => {
1430
+ console.error(pc3.red(String(e instanceof Error ? e.message : e)));
1431
+ console.error(
1432
+ pc3.dim(
1433
+ [
1434
+ "",
1435
+ "What to do next:",
1436
+ " \u2192 carrier status",
1437
+ " \u2192 carrier open signup",
1438
+ " \u2192 carrier plugin install",
1439
+ " \u2192 carrier --help"
1440
+ ].join("\n")
1441
+ )
1442
+ );
1443
+ process.exit(1);
1444
+ });
1445
+ //# sourceMappingURL=cli.js.map