@carrierllc/mcp 0.2.18 → 0.2.20
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +22 -1
- package/dist/chunk-NHQDO2E7.js +461 -0
- package/dist/chunk-NHQDO2E7.js.map +1 -0
- package/dist/cli.js +877 -148
- package/dist/cli.js.map +1 -1
- package/dist/index.js +148 -297
- package/dist/index.js.map +1 -1
- package/package.json +6 -6
- package/plugin/carrier/commands/storefront.md +24 -12
- package/plugin/carrier/skills/carrier-operations/SKILL.md +4 -1
- package/templates/storefront/{next.config.ts → next.config.mjs} +2 -6
- package/templates/storefront/package-lock.json +12722 -0
- package/templates/storefront/package.json +3 -3
- package/templates/storefront/public/brand/logo.svg +13 -0
- package/templates/storefront/src/app/activate/[orderId]/ActivateClient.tsx +12 -12
- package/templates/storefront/src/app/checkout/[templateId]/CheckoutClient.tsx +5 -5
- package/templates/storefront/src/app/checkout/success/CheckoutSuccessClient.tsx +29 -29
- package/templates/storefront/src/app/checkout/success/page.tsx +1 -1
- package/templates/storefront/src/app/contact/page.tsx +33 -25
- package/templates/storefront/src/app/dashboard/page.tsx +7 -4
- package/templates/storefront/src/app/globals.css +72 -82
- package/templates/storefront/src/app/help/page.tsx +22 -17
- package/templates/storefront/src/app/layout.tsx +15 -4
- package/templates/storefront/src/app/page.tsx +16 -7
- package/templates/storefront/src/app/shop/ShopClient.tsx +7 -3
- package/templates/storefront/src/app/sign-in/[[...sign-in]]/page.tsx +1 -1
- package/templates/storefront/src/app/sign-up/[[...sign-up]]/StorefrontSignUpClient.tsx +16 -16
- package/templates/storefront/src/app/sign-up/[[...sign-up]]/page.tsx +1 -1
- package/templates/storefront/src/brand.config.ts +3 -3
- package/templates/storefront/src/components/faq/FaqSection.tsx +23 -26
- package/templates/storefront/src/components/footer/StorefrontFooter.tsx +37 -22
- package/templates/storefront/src/components/landing/FinalCTA.tsx +28 -0
- package/templates/storefront/src/components/landing/HeroSection.tsx +57 -24
- package/templates/storefront/src/components/landing/HowItWorks.tsx +44 -0
- package/templates/storefront/src/components/landing/PlanCard.tsx +58 -28
- package/templates/storefront/src/components/nav/StorefrontNav.tsx +59 -24
- package/templates/storefront/src/components/theme/BrandStyles.tsx +8 -0
- package/templates/storefront/src/components/theme/ThemeToggle.tsx +27 -0
- package/templates/storefront/src/components/theme/clerk-appearance.ts +33 -35
- package/templates/storefront/src/components/theme/theme-provider.tsx +25 -32
- package/templates/storefront/src/components/theme/theme-script.tsx +4 -7
- package/templates/storefront/src/lib/checkout-order-claim.ts +16 -2
- package/templates/storefront/src/lib/complete-email-sign-up.ts +3 -3
- package/templates/storefront/src/lib/verify-checkout-session.ts +19 -2
- package/templates/storefront/src/vendor/ui/brand.ts +6 -6
package/dist/cli.js
CHANGED
|
@@ -1,9 +1,16 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
+
import {
|
|
3
|
+
CARRIER_VERSION,
|
|
4
|
+
OCS_MAX_USAGE_WINDOW_DAYS,
|
|
5
|
+
clampUsagePeriod,
|
|
6
|
+
generateStorefrontLogo,
|
|
7
|
+
lastNDaysPeriod
|
|
8
|
+
} from "./chunk-NHQDO2E7.js";
|
|
2
9
|
|
|
3
10
|
// src/cli/index.ts
|
|
4
11
|
import { Command } from "commander";
|
|
5
|
-
import * as
|
|
6
|
-
import
|
|
12
|
+
import * as p3 from "@clack/prompts";
|
|
13
|
+
import pc4 from "picocolors";
|
|
7
14
|
import { resolve as resolve2 } from "path";
|
|
8
15
|
|
|
9
16
|
// src/cli/lib/brand.ts
|
|
@@ -224,12 +231,12 @@ async function replaceInTree(dir, subs) {
|
|
|
224
231
|
}
|
|
225
232
|
return touched;
|
|
226
233
|
}
|
|
227
|
-
async function exists(
|
|
228
|
-
return existsSync2(
|
|
234
|
+
async function exists(p4) {
|
|
235
|
+
return existsSync2(p4);
|
|
229
236
|
}
|
|
230
|
-
async function isDir(
|
|
237
|
+
async function isDir(p4) {
|
|
231
238
|
try {
|
|
232
|
-
return (await stat(
|
|
239
|
+
return (await stat(p4)).isDirectory();
|
|
233
240
|
} catch {
|
|
234
241
|
return false;
|
|
235
242
|
}
|
|
@@ -446,8 +453,27 @@ ${socialBody}
|
|
|
446
453
|
await writeFile(path, src);
|
|
447
454
|
}
|
|
448
455
|
|
|
449
|
-
// src/cli/lib/
|
|
456
|
+
// src/cli/lib/logo.ts
|
|
457
|
+
import { mkdir as mkdir2, writeFile as writeFile2 } from "fs/promises";
|
|
450
458
|
import { join as join5 } from "path";
|
|
459
|
+
async function writeStorefrontLogo(target, brand) {
|
|
460
|
+
const logo = await generateStorefrontLogo({
|
|
461
|
+
name: brand.name,
|
|
462
|
+
accent: brand.colors.accent,
|
|
463
|
+
tagline: brand.tagline
|
|
464
|
+
});
|
|
465
|
+
const dir = join5(target, "public", "brand");
|
|
466
|
+
await mkdir2(dir, { recursive: true });
|
|
467
|
+
const svgPath = join5(dir, "logo.svg");
|
|
468
|
+
await writeFile2(svgPath, logo.svg, "utf8");
|
|
469
|
+
if (logo.pngBase64) {
|
|
470
|
+
await writeFile2(join5(dir, "logo.png"), Buffer.from(logo.pngBase64, "base64"));
|
|
471
|
+
}
|
|
472
|
+
return { path: svgPath, source: logo.source };
|
|
473
|
+
}
|
|
474
|
+
|
|
475
|
+
// src/cli/lib/site.ts
|
|
476
|
+
import { join as join6 } from "path";
|
|
451
477
|
function slug(s) {
|
|
452
478
|
return s.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "").slice(0, 40) || "storefront";
|
|
453
479
|
}
|
|
@@ -462,7 +488,7 @@ async function buildSite(target) {
|
|
|
462
488
|
return r.ok;
|
|
463
489
|
}
|
|
464
490
|
async function loadStorefrontBrand(target, overrides) {
|
|
465
|
-
const configPath =
|
|
491
|
+
const configPath = join6(target, "src", "brand.config.ts");
|
|
466
492
|
if (!await exists(configPath)) {
|
|
467
493
|
return { ...CARRIER_BRAND, ...overrides };
|
|
468
494
|
}
|
|
@@ -491,7 +517,7 @@ async function loadStorefrontBrand(target, overrides) {
|
|
|
491
517
|
}
|
|
492
518
|
async function deploySite(target, brand) {
|
|
493
519
|
const projectName = slug(brand.name);
|
|
494
|
-
const workerBundle =
|
|
520
|
+
const workerBundle = join6(target, ".open-next", "worker.js");
|
|
495
521
|
if (!await exists(workerBundle)) {
|
|
496
522
|
return {
|
|
497
523
|
ok: false,
|
|
@@ -510,7 +536,7 @@ async function deploySite(target, brand) {
|
|
|
510
536
|
|
|
511
537
|
// src/cli/lib/status.ts
|
|
512
538
|
import { homedir as homedir2 } from "os";
|
|
513
|
-
import { join as
|
|
539
|
+
import { join as join7 } from "path";
|
|
514
540
|
function detectEnvToken() {
|
|
515
541
|
const api = process.env.CARRIER_API_KEY?.trim() || process.env.CARRIER_ORG_API_KEY?.trim() || "";
|
|
516
542
|
if (api.startsWith("ak_") || api.length > 0) {
|
|
@@ -555,10 +581,10 @@ function scanMcpServers(servers) {
|
|
|
555
581
|
}
|
|
556
582
|
async function probeMcpFromConfig() {
|
|
557
583
|
const candidates = [
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
584
|
+
join7(homedir2(), ".claude.json"),
|
|
585
|
+
join7(claudeHome(), "settings.json"),
|
|
586
|
+
join7(claudeHome(), ".mcp.json"),
|
|
587
|
+
join7(process.cwd(), ".mcp.json")
|
|
562
588
|
];
|
|
563
589
|
let best = null;
|
|
564
590
|
let configSeen = false;
|
|
@@ -743,7 +769,21 @@ import * as p from "@clack/prompts";
|
|
|
743
769
|
import pc2 from "picocolors";
|
|
744
770
|
|
|
745
771
|
// src/cli/lib/ask.ts
|
|
746
|
-
|
|
772
|
+
var DEFAULT_ERROR_HINTS = [
|
|
773
|
+
"Re-check the arguments: carrier <domain> <command> --help",
|
|
774
|
+
"Or open Claude and ask there with full tool routing"
|
|
775
|
+
];
|
|
776
|
+
function parseRpcBody(raw) {
|
|
777
|
+
const trimmed = raw.trim();
|
|
778
|
+
if (!trimmed) return {};
|
|
779
|
+
if (trimmed.startsWith("{") || trimmed.startsWith("[")) {
|
|
780
|
+
return JSON.parse(trimmed);
|
|
781
|
+
}
|
|
782
|
+
const dataLine = trimmed.split("\n").map((l) => l.trim()).find((l) => l.startsWith("data:"));
|
|
783
|
+
if (!dataLine) return {};
|
|
784
|
+
return JSON.parse(dataLine.slice(5).trim());
|
|
785
|
+
}
|
|
786
|
+
async function callMcpTool(tool, args, opts = {}) {
|
|
747
787
|
const token = resolveCliToken();
|
|
748
788
|
if (!token) {
|
|
749
789
|
return {
|
|
@@ -751,39 +791,68 @@ async function carrierAsk(intent) {
|
|
|
751
791
|
text: withNextStep("No headless token in the environment.", [
|
|
752
792
|
"Interactive path: open Claude and say your intent \u2014 OAuth runs on first use.",
|
|
753
793
|
"Or export CARRIER_API_KEY=ak_\u2026 from Console \u2192 Settings \u2192 API Keys.",
|
|
754
|
-
'Then retry: carrier ask "show fleet health"'
|
|
794
|
+
'Then retry: carrier ask "show fleet health"',
|
|
795
|
+
"Or run a discrete command: carrier subscribers list --account-id 123"
|
|
755
796
|
])
|
|
756
797
|
};
|
|
757
798
|
}
|
|
758
|
-
const
|
|
759
|
-
|
|
760
|
-
|
|
761
|
-
|
|
762
|
-
params: {
|
|
763
|
-
name: "carrier_ask",
|
|
764
|
-
arguments: { intent }
|
|
765
|
-
}
|
|
799
|
+
const headers = {
|
|
800
|
+
Authorization: `Bearer ${token}`,
|
|
801
|
+
"Content-Type": "application/json",
|
|
802
|
+
Accept: "application/json, text/event-stream"
|
|
766
803
|
};
|
|
767
804
|
try {
|
|
768
|
-
const
|
|
805
|
+
const initRes = await fetch(MCP_URL, {
|
|
769
806
|
method: "POST",
|
|
770
|
-
headers
|
|
771
|
-
|
|
772
|
-
|
|
773
|
-
|
|
774
|
-
|
|
775
|
-
|
|
807
|
+
headers,
|
|
808
|
+
body: JSON.stringify({
|
|
809
|
+
jsonrpc: "2.0",
|
|
810
|
+
id: 1,
|
|
811
|
+
method: "initialize",
|
|
812
|
+
params: {
|
|
813
|
+
protocolVersion: "2024-11-05",
|
|
814
|
+
capabilities: {},
|
|
815
|
+
clientInfo: { name: "carrier-cli", version: CARRIER_VERSION }
|
|
816
|
+
}
|
|
817
|
+
})
|
|
776
818
|
});
|
|
777
|
-
if (
|
|
819
|
+
if (initRes.status === 401 || initRes.status === 403) {
|
|
778
820
|
return {
|
|
779
821
|
ok: false,
|
|
780
|
-
text: withNextStep(`MCP returned ${
|
|
822
|
+
text: withNextStep(`MCP returned ${initRes.status} (auth rejected).`, [
|
|
781
823
|
"Confirm CARRIER_API_KEY is a valid org key (ak_\u2026) from app.carrier.llc",
|
|
782
824
|
"Or complete OCS onboarding: https://app.carrier.llc/onboarding",
|
|
783
825
|
"Interactive: use Claude + OAuth instead of a headless key"
|
|
784
826
|
])
|
|
785
827
|
};
|
|
786
828
|
}
|
|
829
|
+
const sessionId = initRes.headers.get("mcp-session-id") ?? initRes.headers.get("Mcp-Session-Id");
|
|
830
|
+
await initRes.arrayBuffer().catch(() => void 0);
|
|
831
|
+
if (!sessionId) {
|
|
832
|
+
return {
|
|
833
|
+
ok: false,
|
|
834
|
+
text: withNextStep("MCP initialize did not return a session id.", [
|
|
835
|
+
"Check network access to https://mcp.carrier.llc/mcp",
|
|
836
|
+
"Retry later, or use Claude with the registered MCP (OAuth path)"
|
|
837
|
+
])
|
|
838
|
+
};
|
|
839
|
+
}
|
|
840
|
+
const sessionHeaders = { ...headers, "Mcp-Session-Id": sessionId };
|
|
841
|
+
await fetch(MCP_URL, {
|
|
842
|
+
method: "POST",
|
|
843
|
+
headers: sessionHeaders,
|
|
844
|
+
body: JSON.stringify({ jsonrpc: "2.0", method: "notifications/initialized" })
|
|
845
|
+
}).catch(() => void 0);
|
|
846
|
+
const res = await fetch(MCP_URL, {
|
|
847
|
+
method: "POST",
|
|
848
|
+
headers: sessionHeaders,
|
|
849
|
+
body: JSON.stringify({
|
|
850
|
+
jsonrpc: "2.0",
|
|
851
|
+
id: 2,
|
|
852
|
+
method: "tools/call",
|
|
853
|
+
params: { name: tool, arguments: args }
|
|
854
|
+
})
|
|
855
|
+
});
|
|
787
856
|
if (!res.ok) {
|
|
788
857
|
const snippet = (await res.text().catch(() => "")).slice(0, 240);
|
|
789
858
|
return {
|
|
@@ -794,18 +863,15 @@ async function carrierAsk(intent) {
|
|
|
794
863
|
])
|
|
795
864
|
};
|
|
796
865
|
}
|
|
797
|
-
const json = await res.
|
|
866
|
+
const json = parseRpcBody(await res.text());
|
|
798
867
|
if (json.error?.message) {
|
|
799
868
|
return {
|
|
800
869
|
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
|
-
])
|
|
870
|
+
text: withNextStep(json.error.message, opts.errorHints ?? DEFAULT_ERROR_HINTS)
|
|
805
871
|
};
|
|
806
872
|
}
|
|
807
873
|
const parts = json.result?.content ?? [];
|
|
808
|
-
const text3 = parts.map((
|
|
874
|
+
const text3 = parts.map((p4) => p4.text).filter(Boolean).join("\n").trim() || JSON.stringify(json.result ?? json, null, 2);
|
|
809
875
|
return { ok: !json.result?.isError, text: text3 };
|
|
810
876
|
} catch (e) {
|
|
811
877
|
const msg = e instanceof Error ? e.message : String(e);
|
|
@@ -818,6 +884,18 @@ async function carrierAsk(intent) {
|
|
|
818
884
|
};
|
|
819
885
|
}
|
|
820
886
|
}
|
|
887
|
+
async function carrierAsk(intent) {
|
|
888
|
+
return callMcpTool(
|
|
889
|
+
"carrier_ask",
|
|
890
|
+
{ intent },
|
|
891
|
+
{
|
|
892
|
+
errorHints: [
|
|
893
|
+
"Rephrase the intent more specifically (include ICCID / MSISDN if relevant)",
|
|
894
|
+
"Or use a discrete command: carrier --help lists every domain"
|
|
895
|
+
]
|
|
896
|
+
}
|
|
897
|
+
);
|
|
898
|
+
}
|
|
821
899
|
|
|
822
900
|
// src/cli/lib/home.ts
|
|
823
901
|
function mark(ok2) {
|
|
@@ -881,11 +959,6 @@ async function runHome(opts) {
|
|
|
881
959
|
label: "Open onboarding (link OCS / managed setup)",
|
|
882
960
|
hint: ONBOARDING_URL
|
|
883
961
|
},
|
|
884
|
-
{
|
|
885
|
-
value: "examples",
|
|
886
|
-
label: "Talk to fleet \u2014 NL examples for Claude",
|
|
887
|
-
hint: "Copy-paste prompts"
|
|
888
|
-
},
|
|
889
962
|
{
|
|
890
963
|
value: "ask",
|
|
891
964
|
label: hasToken ? "Ask the fleet (carrier ask)" : "Ask the fleet (needs CARRIER_API_KEY)",
|
|
@@ -926,27 +999,6 @@ async function runHome(opts) {
|
|
|
926
999
|
await openAndReport(ONBOARDING_URL, "Onboarding");
|
|
927
1000
|
p.log.info("Link OCS credentials (BYO) or finish managed setup, then use Claude MCP.");
|
|
928
1001
|
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
1002
|
case "ask": {
|
|
951
1003
|
if (!resolveCliToken()) {
|
|
952
1004
|
p.log.warn("No CARRIER_API_KEY / OCS token in env.");
|
|
@@ -984,46 +1036,666 @@ async function runHome(opts) {
|
|
|
984
1036
|
}
|
|
985
1037
|
}
|
|
986
1038
|
|
|
1039
|
+
// src/cli/lib/domains.ts
|
|
1040
|
+
var ICCID = {
|
|
1041
|
+
flags: "--iccid <iccid>",
|
|
1042
|
+
description: "Subscriber ICCID",
|
|
1043
|
+
arg: "iccid",
|
|
1044
|
+
type: "string",
|
|
1045
|
+
required: true
|
|
1046
|
+
};
|
|
1047
|
+
var ACCOUNT_ID = (description) => ({
|
|
1048
|
+
flags: "--account-id <id>",
|
|
1049
|
+
description,
|
|
1050
|
+
arg: "accountId",
|
|
1051
|
+
type: "number"
|
|
1052
|
+
});
|
|
1053
|
+
var RESELLER_ID = {
|
|
1054
|
+
flags: "--reseller-id <id>",
|
|
1055
|
+
description: "Reseller ID (omit for the token owner's reseller)",
|
|
1056
|
+
arg: "resellerId",
|
|
1057
|
+
type: "number"
|
|
1058
|
+
};
|
|
1059
|
+
var PACKAGE_ID = {
|
|
1060
|
+
flags: "--package-id <id>",
|
|
1061
|
+
description: "Package ID from `carrier packages list`",
|
|
1062
|
+
arg: "packageId",
|
|
1063
|
+
type: "number",
|
|
1064
|
+
required: true
|
|
1065
|
+
};
|
|
1066
|
+
var TEMPLATE_ID = {
|
|
1067
|
+
flags: "--template-id <id>",
|
|
1068
|
+
description: "Template ID from `carrier templates list`",
|
|
1069
|
+
arg: "templateId",
|
|
1070
|
+
type: "number",
|
|
1071
|
+
required: true
|
|
1072
|
+
};
|
|
1073
|
+
var PERIOD = [
|
|
1074
|
+
{
|
|
1075
|
+
flags: "--start <date>",
|
|
1076
|
+
description: "Start date YYYY-MM-DD (default: 7 days ago)",
|
|
1077
|
+
arg: "startDate",
|
|
1078
|
+
type: "string"
|
|
1079
|
+
},
|
|
1080
|
+
{
|
|
1081
|
+
flags: "--end <date>",
|
|
1082
|
+
description: "End date YYYY-MM-DD, max 7 days from start (default: today)",
|
|
1083
|
+
arg: "endDate",
|
|
1084
|
+
type: "string"
|
|
1085
|
+
}
|
|
1086
|
+
];
|
|
1087
|
+
var CLI_DOMAINS = [
|
|
1088
|
+
{
|
|
1089
|
+
name: "subscribers",
|
|
1090
|
+
summary: "Look up, inspect and change subscribers.",
|
|
1091
|
+
commands: [
|
|
1092
|
+
{
|
|
1093
|
+
name: "list",
|
|
1094
|
+
tool: "list_subscribers",
|
|
1095
|
+
summary: "List subscribers. OCS needs at least one of account/iccid/msisdn/imsi.",
|
|
1096
|
+
options: [
|
|
1097
|
+
ACCOUNT_ID("Filter by account ID"),
|
|
1098
|
+
{ flags: "--iccid <iccid>", description: "Filter by ICCID", arg: "iccid", type: "string" },
|
|
1099
|
+
{ flags: "--msisdn <msisdn>", description: "Filter by MSISDN", arg: "msisdn", type: "string" },
|
|
1100
|
+
{ flags: "--imsi <imsi>", description: "Filter by IMSI", arg: "imsi", type: "string" },
|
|
1101
|
+
{ flags: "--status <status>", description: "Filter by status, e.g. ACTIVE", arg: "status", type: "string" },
|
|
1102
|
+
{ flags: "--limit <n>", description: "Max rows to return", arg: "limit", type: "number" },
|
|
1103
|
+
{ flags: "--offset <n>", description: "Pagination offset", arg: "offset", type: "number" }
|
|
1104
|
+
]
|
|
1105
|
+
},
|
|
1106
|
+
{
|
|
1107
|
+
name: "get",
|
|
1108
|
+
tool: "get_subscriber",
|
|
1109
|
+
summary: "Full record for one subscriber by ICCID or MSISDN.",
|
|
1110
|
+
options: [
|
|
1111
|
+
{ flags: "--iccid <iccid>", description: "Subscriber ICCID", arg: "iccid", type: "string" },
|
|
1112
|
+
{ flags: "--msisdn <msisdn>", description: "Subscriber MSISDN", arg: "msisdn", type: "string" },
|
|
1113
|
+
{
|
|
1114
|
+
flags: "--gz-counter",
|
|
1115
|
+
description: "Include the green-zone byte counter",
|
|
1116
|
+
arg: "with_gz_counter",
|
|
1117
|
+
type: "boolean"
|
|
1118
|
+
}
|
|
1119
|
+
]
|
|
1120
|
+
},
|
|
1121
|
+
{
|
|
1122
|
+
name: "location",
|
|
1123
|
+
tool: "get_subscriber_location",
|
|
1124
|
+
summary: "Last-known location from the most recent cell tower usage.",
|
|
1125
|
+
options: [ICCID]
|
|
1126
|
+
},
|
|
1127
|
+
{
|
|
1128
|
+
name: "usage",
|
|
1129
|
+
tool: "subscriber_usage",
|
|
1130
|
+
summary: "Daily data, voice and SMS usage over a window of up to 7 days.",
|
|
1131
|
+
options: [ICCID, ...PERIOD],
|
|
1132
|
+
usageWindow: true
|
|
1133
|
+
},
|
|
1134
|
+
{
|
|
1135
|
+
name: "events",
|
|
1136
|
+
tool: "subscriber_network_events",
|
|
1137
|
+
summary: "Network attach/detach events over a window of up to 7 days.",
|
|
1138
|
+
options: [ICCID, ...PERIOD],
|
|
1139
|
+
usageWindow: true
|
|
1140
|
+
},
|
|
1141
|
+
{
|
|
1142
|
+
name: "active-period",
|
|
1143
|
+
tool: "subscriber_active_period",
|
|
1144
|
+
summary: "First and last usage dates for one subscriber.",
|
|
1145
|
+
options: [ICCID]
|
|
1146
|
+
},
|
|
1147
|
+
{
|
|
1148
|
+
name: "esim-status",
|
|
1149
|
+
tool: "esim_status_per_account",
|
|
1150
|
+
summary: "eSIM state counts per account: active, suspended, inventory.",
|
|
1151
|
+
options: [ACCOUNT_ID("Report on a single account"), RESELLER_ID]
|
|
1152
|
+
},
|
|
1153
|
+
{
|
|
1154
|
+
name: "set-status",
|
|
1155
|
+
tool: "modify_subscriber_status",
|
|
1156
|
+
summary: "Change a subscriber's status.",
|
|
1157
|
+
options: [
|
|
1158
|
+
ICCID,
|
|
1159
|
+
{
|
|
1160
|
+
flags: "--status <status>",
|
|
1161
|
+
description: "New status, e.g. ACTIVE or SUSPENDED",
|
|
1162
|
+
arg: "status",
|
|
1163
|
+
type: "string",
|
|
1164
|
+
required: true
|
|
1165
|
+
}
|
|
1166
|
+
],
|
|
1167
|
+
write: true
|
|
1168
|
+
},
|
|
1169
|
+
{
|
|
1170
|
+
name: "set-balance",
|
|
1171
|
+
tool: "modify_subscriber_balance",
|
|
1172
|
+
summary: "Add to or replace a subscriber's balance.",
|
|
1173
|
+
options: [
|
|
1174
|
+
ICCID,
|
|
1175
|
+
{
|
|
1176
|
+
flags: "--amount <amount>",
|
|
1177
|
+
description: "Amount to add (adapt) or set to (set)",
|
|
1178
|
+
arg: "amount",
|
|
1179
|
+
type: "number",
|
|
1180
|
+
required: true
|
|
1181
|
+
},
|
|
1182
|
+
{
|
|
1183
|
+
flags: "--mode <mode>",
|
|
1184
|
+
description: "'adapt' adds or subtracts, 'set' replaces",
|
|
1185
|
+
arg: "mode",
|
|
1186
|
+
type: "string",
|
|
1187
|
+
required: true
|
|
1188
|
+
}
|
|
1189
|
+
],
|
|
1190
|
+
write: true
|
|
1191
|
+
}
|
|
1192
|
+
]
|
|
1193
|
+
},
|
|
1194
|
+
{
|
|
1195
|
+
name: "packages",
|
|
1196
|
+
summary: "Packages assigned to a subscriber.",
|
|
1197
|
+
commands: [
|
|
1198
|
+
{
|
|
1199
|
+
name: "list",
|
|
1200
|
+
tool: "list_subscriber_packages",
|
|
1201
|
+
summary: "Packages on one subscriber, with allowance and expiry.",
|
|
1202
|
+
options: [ICCID]
|
|
1203
|
+
},
|
|
1204
|
+
{
|
|
1205
|
+
name: "assign",
|
|
1206
|
+
tool: "assign_package",
|
|
1207
|
+
summary: "Assign a one-time package from a template.",
|
|
1208
|
+
options: [
|
|
1209
|
+
{ ...ICCID, required: false },
|
|
1210
|
+
{
|
|
1211
|
+
flags: "--template-id <id>",
|
|
1212
|
+
description: "Package template ID from `carrier templates list`",
|
|
1213
|
+
arg: "packageTemplateId",
|
|
1214
|
+
type: "number",
|
|
1215
|
+
required: true
|
|
1216
|
+
},
|
|
1217
|
+
{
|
|
1218
|
+
flags: "--account-id <id>",
|
|
1219
|
+
description: "Auto-pick a free eSIM from this account instead of using --iccid",
|
|
1220
|
+
arg: "account_for_subs",
|
|
1221
|
+
type: "number"
|
|
1222
|
+
}
|
|
1223
|
+
],
|
|
1224
|
+
write: true,
|
|
1225
|
+
requireOneOf: ["iccid", "account_for_subs"]
|
|
1226
|
+
},
|
|
1227
|
+
{
|
|
1228
|
+
name: "set-status",
|
|
1229
|
+
tool: "modify_package_status",
|
|
1230
|
+
summary: "Activate or deactivate one package without deleting it.",
|
|
1231
|
+
options: [
|
|
1232
|
+
ICCID,
|
|
1233
|
+
PACKAGE_ID,
|
|
1234
|
+
{
|
|
1235
|
+
flags: "--status <status>",
|
|
1236
|
+
description: "New package status, e.g. ACTIVE or INACTIVE",
|
|
1237
|
+
arg: "status",
|
|
1238
|
+
type: "string",
|
|
1239
|
+
required: true
|
|
1240
|
+
}
|
|
1241
|
+
],
|
|
1242
|
+
write: true
|
|
1243
|
+
},
|
|
1244
|
+
{
|
|
1245
|
+
name: "set-expiry",
|
|
1246
|
+
tool: "modify_package_expiry",
|
|
1247
|
+
summary: "Move a package's expiry date. Pass an absolute date or a day count.",
|
|
1248
|
+
options: [
|
|
1249
|
+
ICCID,
|
|
1250
|
+
PACKAGE_ID,
|
|
1251
|
+
{
|
|
1252
|
+
flags: "--expires <date>",
|
|
1253
|
+
description: "Absolute expiry, ISO 8601 (e.g. 2026-06-01)",
|
|
1254
|
+
arg: "expirationDate",
|
|
1255
|
+
type: "string"
|
|
1256
|
+
},
|
|
1257
|
+
{
|
|
1258
|
+
flags: "--validity-days <n>",
|
|
1259
|
+
description: "Days from now until expiry (instead of --expires)",
|
|
1260
|
+
arg: "validity_days",
|
|
1261
|
+
type: "number"
|
|
1262
|
+
}
|
|
1263
|
+
],
|
|
1264
|
+
write: true
|
|
1265
|
+
},
|
|
1266
|
+
{
|
|
1267
|
+
name: "set-limits",
|
|
1268
|
+
tool: "modify_package_limits",
|
|
1269
|
+
summary: "Change data, voice or SMS ceilings on an assigned package.",
|
|
1270
|
+
options: [
|
|
1271
|
+
ICCID,
|
|
1272
|
+
PACKAGE_ID,
|
|
1273
|
+
{
|
|
1274
|
+
flags: "--limits <json>",
|
|
1275
|
+
description: `New limits as JSON, e.g. '{"dataLimit":5368709120}'`,
|
|
1276
|
+
arg: "limits",
|
|
1277
|
+
type: "string",
|
|
1278
|
+
required: true
|
|
1279
|
+
}
|
|
1280
|
+
],
|
|
1281
|
+
write: true
|
|
1282
|
+
},
|
|
1283
|
+
{
|
|
1284
|
+
name: "delete",
|
|
1285
|
+
tool: "delete_subscriber_package",
|
|
1286
|
+
summary: "Permanently remove one package from a subscriber.",
|
|
1287
|
+
options: [ICCID, PACKAGE_ID],
|
|
1288
|
+
write: true
|
|
1289
|
+
}
|
|
1290
|
+
]
|
|
1291
|
+
},
|
|
1292
|
+
{
|
|
1293
|
+
name: "templates",
|
|
1294
|
+
summary: "The package template catalog.",
|
|
1295
|
+
commands: [
|
|
1296
|
+
{
|
|
1297
|
+
name: "list",
|
|
1298
|
+
tool: "list_package_templates",
|
|
1299
|
+
summary: "Browse package templates available for assignment.",
|
|
1300
|
+
options: [ACCOUNT_ID("Filter templates visible to one account")]
|
|
1301
|
+
},
|
|
1302
|
+
{
|
|
1303
|
+
name: "create",
|
|
1304
|
+
tool: "create_package_template",
|
|
1305
|
+
summary: "Create a package template from a JSON configuration.",
|
|
1306
|
+
options: [
|
|
1307
|
+
{
|
|
1308
|
+
flags: "--template <json>",
|
|
1309
|
+
description: `Template config as JSON, e.g. '{"name":"Europe 5GB","dataLimit":5368709120}'`,
|
|
1310
|
+
arg: "template",
|
|
1311
|
+
type: "string",
|
|
1312
|
+
required: true
|
|
1313
|
+
}
|
|
1314
|
+
],
|
|
1315
|
+
write: true
|
|
1316
|
+
},
|
|
1317
|
+
{
|
|
1318
|
+
name: "set-core",
|
|
1319
|
+
tool: "modify_template_core",
|
|
1320
|
+
summary: "Change name, limits, price, validity or zone on a template.",
|
|
1321
|
+
options: [
|
|
1322
|
+
TEMPLATE_ID,
|
|
1323
|
+
{
|
|
1324
|
+
flags: "--changes <json>",
|
|
1325
|
+
description: `Core fields as JSON, e.g. '{"name":"Europe 5GB"}'`,
|
|
1326
|
+
arg: "changes",
|
|
1327
|
+
type: "string",
|
|
1328
|
+
required: true
|
|
1329
|
+
}
|
|
1330
|
+
],
|
|
1331
|
+
write: true
|
|
1332
|
+
},
|
|
1333
|
+
{
|
|
1334
|
+
name: "set-recurring",
|
|
1335
|
+
tool: "modify_template_recurring",
|
|
1336
|
+
summary: "Change auto-renewal settings on a template.",
|
|
1337
|
+
options: [
|
|
1338
|
+
TEMPLATE_ID,
|
|
1339
|
+
{
|
|
1340
|
+
flags: "--changes <json>",
|
|
1341
|
+
description: `Recurring fields as JSON, e.g. '{"periodicity":"monthly"}'`,
|
|
1342
|
+
arg: "changes",
|
|
1343
|
+
type: "string",
|
|
1344
|
+
required: true
|
|
1345
|
+
}
|
|
1346
|
+
],
|
|
1347
|
+
write: true
|
|
1348
|
+
},
|
|
1349
|
+
{
|
|
1350
|
+
name: "set-throttling",
|
|
1351
|
+
tool: "modify_template_throttling",
|
|
1352
|
+
summary: "Change throttling thresholds. Applies to existing packages too.",
|
|
1353
|
+
options: [
|
|
1354
|
+
TEMPLATE_ID,
|
|
1355
|
+
{
|
|
1356
|
+
flags: "--changes <json>",
|
|
1357
|
+
description: `Throttling fields as JSON, e.g. '{"throttlingActive":true}'`,
|
|
1358
|
+
arg: "changes",
|
|
1359
|
+
type: "string",
|
|
1360
|
+
required: true
|
|
1361
|
+
}
|
|
1362
|
+
],
|
|
1363
|
+
write: true
|
|
1364
|
+
}
|
|
1365
|
+
]
|
|
1366
|
+
},
|
|
1367
|
+
{
|
|
1368
|
+
name: "zones",
|
|
1369
|
+
summary: "Location zones and destination lists.",
|
|
1370
|
+
commands: [
|
|
1371
|
+
{
|
|
1372
|
+
name: "list",
|
|
1373
|
+
tool: "list_detailed_location_zones",
|
|
1374
|
+
summary: "Location zones with countries and operators. Prefer this over `elements`.",
|
|
1375
|
+
options: [RESELLER_ID]
|
|
1376
|
+
},
|
|
1377
|
+
{
|
|
1378
|
+
name: "elements",
|
|
1379
|
+
tool: "list_location_zones",
|
|
1380
|
+
summary: "Raw zone elements. Upstream OCS returns malformed rows for some zones.",
|
|
1381
|
+
options: [
|
|
1382
|
+
{
|
|
1383
|
+
flags: "--zone-id <id>",
|
|
1384
|
+
description: "Filter to one location zone",
|
|
1385
|
+
arg: "locationZoneId",
|
|
1386
|
+
type: "number"
|
|
1387
|
+
}
|
|
1388
|
+
]
|
|
1389
|
+
},
|
|
1390
|
+
{
|
|
1391
|
+
name: "create",
|
|
1392
|
+
tool: "create_location_zone",
|
|
1393
|
+
summary: "Create a location zone from a JSON configuration.",
|
|
1394
|
+
options: [
|
|
1395
|
+
{
|
|
1396
|
+
flags: "--zone <json>",
|
|
1397
|
+
description: `Zone config as JSON, e.g. '{"name":"Europe","countries":["NL","DE"]}'`,
|
|
1398
|
+
arg: "zone",
|
|
1399
|
+
type: "string",
|
|
1400
|
+
required: true
|
|
1401
|
+
}
|
|
1402
|
+
],
|
|
1403
|
+
write: true
|
|
1404
|
+
},
|
|
1405
|
+
{
|
|
1406
|
+
name: "destinations",
|
|
1407
|
+
tool: "list_destination_lists",
|
|
1408
|
+
summary: "Destination list catalog for voice and SMS packages.",
|
|
1409
|
+
options: [RESELLER_ID]
|
|
1410
|
+
},
|
|
1411
|
+
{
|
|
1412
|
+
name: "prefixes",
|
|
1413
|
+
tool: "list_destination_prefixes",
|
|
1414
|
+
summary: "Dialling prefixes inside one destination list.",
|
|
1415
|
+
options: [
|
|
1416
|
+
{
|
|
1417
|
+
flags: "--destination-list-id <id>",
|
|
1418
|
+
description: "Destination list ID from `carrier zones destinations`",
|
|
1419
|
+
arg: "destinationListId",
|
|
1420
|
+
type: "number"
|
|
1421
|
+
}
|
|
1422
|
+
]
|
|
1423
|
+
}
|
|
1424
|
+
]
|
|
1425
|
+
},
|
|
1426
|
+
{
|
|
1427
|
+
name: "steering",
|
|
1428
|
+
summary: "Network steering lists and operator preference.",
|
|
1429
|
+
commands: [
|
|
1430
|
+
{
|
|
1431
|
+
name: "list",
|
|
1432
|
+
tool: "list_steering_lists",
|
|
1433
|
+
summary: "Steering lists configured for this reseller.",
|
|
1434
|
+
options: [RESELLER_ID]
|
|
1435
|
+
},
|
|
1436
|
+
{
|
|
1437
|
+
name: "assign",
|
|
1438
|
+
tool: "modify_subscriber_steering_list",
|
|
1439
|
+
summary: "Assign a steering list to a subscriber. Follow with `push`.",
|
|
1440
|
+
options: [
|
|
1441
|
+
ICCID,
|
|
1442
|
+
{
|
|
1443
|
+
flags: "--list-id <id>",
|
|
1444
|
+
description: "Steering list ID from `carrier steering list`",
|
|
1445
|
+
arg: "steeringListId",
|
|
1446
|
+
type: "number",
|
|
1447
|
+
required: true
|
|
1448
|
+
}
|
|
1449
|
+
],
|
|
1450
|
+
write: true
|
|
1451
|
+
},
|
|
1452
|
+
{
|
|
1453
|
+
name: "push",
|
|
1454
|
+
tool: "push_steering_to_subscriber",
|
|
1455
|
+
summary: "Push the assigned operator preference list to the device now.",
|
|
1456
|
+
options: [ICCID],
|
|
1457
|
+
write: true
|
|
1458
|
+
}
|
|
1459
|
+
]
|
|
1460
|
+
},
|
|
1461
|
+
{
|
|
1462
|
+
name: "intelligence",
|
|
1463
|
+
summary: "Composite reports over the fleet.",
|
|
1464
|
+
commands: [
|
|
1465
|
+
{
|
|
1466
|
+
name: "fleet-health",
|
|
1467
|
+
tool: "fleet_health",
|
|
1468
|
+
summary: "eSIM counts, low-balance accounts and what needs attention.",
|
|
1469
|
+
options: [ACCOUNT_ID("Report on a single account")]
|
|
1470
|
+
},
|
|
1471
|
+
{
|
|
1472
|
+
name: "diagnose",
|
|
1473
|
+
tool: "diagnose_subscriber",
|
|
1474
|
+
summary: "Why one subscriber is offline, throttled or failing to attach.",
|
|
1475
|
+
options: [ICCID]
|
|
1476
|
+
},
|
|
1477
|
+
{
|
|
1478
|
+
name: "anomalies",
|
|
1479
|
+
tool: "detect_usage_anomalies",
|
|
1480
|
+
summary: "Usage spikes and burn rates that exhaust the package early.",
|
|
1481
|
+
options: [ICCID]
|
|
1482
|
+
},
|
|
1483
|
+
{
|
|
1484
|
+
name: "churn",
|
|
1485
|
+
tool: "churn_risk",
|
|
1486
|
+
summary: "Churn risk score with contributing factors.",
|
|
1487
|
+
options: [ICCID]
|
|
1488
|
+
},
|
|
1489
|
+
{
|
|
1490
|
+
name: "optimize",
|
|
1491
|
+
tool: "optimize_package",
|
|
1492
|
+
summary: "Better-fitting package for one subscriber, with the saving.",
|
|
1493
|
+
options: [ICCID]
|
|
1494
|
+
},
|
|
1495
|
+
{
|
|
1496
|
+
name: "coverage",
|
|
1497
|
+
tool: "audit_network_coverage",
|
|
1498
|
+
summary: "Networks subscribers actually attach to, against your steering lists.",
|
|
1499
|
+
options: [
|
|
1500
|
+
ACCOUNT_ID("Report on a single account"),
|
|
1501
|
+
{ flags: "--limit <n>", description: "Subscribers to sample (default 50)", arg: "limit", type: "number" }
|
|
1502
|
+
]
|
|
1503
|
+
},
|
|
1504
|
+
{
|
|
1505
|
+
name: "high-cost",
|
|
1506
|
+
tool: "high_cost_subscribers",
|
|
1507
|
+
summary: "Subscribers whose consumption erodes the margin.",
|
|
1508
|
+
options: [
|
|
1509
|
+
ACCOUNT_ID("Report on a single account"),
|
|
1510
|
+
{ flags: "--limit <n>", description: "Subscribers to analyse (default 100)", arg: "limit", type: "number" },
|
|
1511
|
+
{
|
|
1512
|
+
flags: "--threshold-pct <n>",
|
|
1513
|
+
description: "Usage percentage that flags a subscriber (default 80)",
|
|
1514
|
+
arg: "thresholdPct",
|
|
1515
|
+
type: "number"
|
|
1516
|
+
}
|
|
1517
|
+
]
|
|
1518
|
+
},
|
|
1519
|
+
{
|
|
1520
|
+
name: "marketing",
|
|
1521
|
+
tool: "marketing_intelligence",
|
|
1522
|
+
summary: "Where subscribers and revenue concentrate, and which markets grow.",
|
|
1523
|
+
options: [ACCOUNT_ID("Report on a single account")]
|
|
1524
|
+
},
|
|
1525
|
+
{
|
|
1526
|
+
name: "country-entry",
|
|
1527
|
+
tool: "detect_country_entry",
|
|
1528
|
+
summary: "Country a subscriber is in now, optionally diffed against the last known one.",
|
|
1529
|
+
options: [
|
|
1530
|
+
{ ...ICCID, arg: "subscriber.iccid" },
|
|
1531
|
+
{
|
|
1532
|
+
flags: "--expected-country <iso2>",
|
|
1533
|
+
description: "Last known ISO 3166-1 alpha-2 country, e.g. NL",
|
|
1534
|
+
arg: "expectedCountry",
|
|
1535
|
+
type: "string"
|
|
1536
|
+
}
|
|
1537
|
+
]
|
|
1538
|
+
}
|
|
1539
|
+
]
|
|
1540
|
+
}
|
|
1541
|
+
];
|
|
1542
|
+
|
|
1543
|
+
// src/cli/lib/capability.ts
|
|
1544
|
+
import * as p2 from "@clack/prompts";
|
|
1545
|
+
import pc3 from "picocolors";
|
|
1546
|
+
var MAX_PRETTY_CHARS = 8e3;
|
|
1547
|
+
function optionKey(flags) {
|
|
1548
|
+
const long = flags.split(/[ ,|]+/).find((t) => t.startsWith("--"));
|
|
1549
|
+
if (!long) throw new Error(`Option "${flags}" has no long flag`);
|
|
1550
|
+
return long.replace(/^--/, "").split("-").map((part, i) => i === 0 ? part : part.charAt(0).toUpperCase() + part.slice(1)).join("");
|
|
1551
|
+
}
|
|
1552
|
+
function setArg(target, path, value) {
|
|
1553
|
+
const parts = path.split(".");
|
|
1554
|
+
let node = target;
|
|
1555
|
+
for (const part of parts.slice(0, -1)) {
|
|
1556
|
+
const next = node[part];
|
|
1557
|
+
if (typeof next !== "object" || next === null) node[part] = {};
|
|
1558
|
+
node = node[part];
|
|
1559
|
+
}
|
|
1560
|
+
node[parts[parts.length - 1]] = value;
|
|
1561
|
+
}
|
|
1562
|
+
function coerce(opt, raw) {
|
|
1563
|
+
if (opt.type === "boolean") return { value: raw === true };
|
|
1564
|
+
if (opt.type === "number") {
|
|
1565
|
+
const n = Number(raw);
|
|
1566
|
+
if (!Number.isFinite(n)) return { error: `${opt.flags} expects a number, got "${String(raw)}"` };
|
|
1567
|
+
return { value: n };
|
|
1568
|
+
}
|
|
1569
|
+
return { value: String(raw) };
|
|
1570
|
+
}
|
|
1571
|
+
function buildToolArgs(cmd, opts) {
|
|
1572
|
+
const args = {};
|
|
1573
|
+
const errors = [];
|
|
1574
|
+
const notes = [];
|
|
1575
|
+
for (const opt of cmd.options) {
|
|
1576
|
+
const raw = opts[optionKey(opt.flags)];
|
|
1577
|
+
if (raw === void 0 || raw === false) {
|
|
1578
|
+
if (opt.required) errors.push(`Missing required ${opt.flags}`);
|
|
1579
|
+
continue;
|
|
1580
|
+
}
|
|
1581
|
+
const { value, error } = coerce(opt, raw);
|
|
1582
|
+
if (error) errors.push(error);
|
|
1583
|
+
else setArg(args, opt.arg, value);
|
|
1584
|
+
}
|
|
1585
|
+
if (cmd.requireOneOf && !cmd.requireOneOf.some((name) => args[name] !== void 0)) {
|
|
1586
|
+
const flags = cmd.options.filter((opt) => cmd.requireOneOf.includes(opt.arg)).map((opt) => opt.flags);
|
|
1587
|
+
errors.push(`Pass one of: ${flags.join(", ")}`);
|
|
1588
|
+
}
|
|
1589
|
+
if (cmd.usageWindow) {
|
|
1590
|
+
const start = args.startDate;
|
|
1591
|
+
const end = args.endDate;
|
|
1592
|
+
if (!start && !end) {
|
|
1593
|
+
const window = lastNDaysPeriod();
|
|
1594
|
+
args.startDate = window.start;
|
|
1595
|
+
args.endDate = window.end;
|
|
1596
|
+
notes.push(`Window defaulted to ${window.start} \u2026 ${window.end} (last ${OCS_MAX_USAGE_WINDOW_DAYS} days).`);
|
|
1597
|
+
} else if (!start || !end) {
|
|
1598
|
+
errors.push("Pass both --start and --end, or neither for the last 7 days");
|
|
1599
|
+
} else {
|
|
1600
|
+
const clamped = clampUsagePeriod(start, end);
|
|
1601
|
+
if (clamped.start !== start) {
|
|
1602
|
+
notes.push(
|
|
1603
|
+
`Start moved to ${clamped.start} \u2014 OCS rejects windows wider than ${OCS_MAX_USAGE_WINDOW_DAYS} days.`
|
|
1604
|
+
);
|
|
1605
|
+
}
|
|
1606
|
+
args.startDate = clamped.start;
|
|
1607
|
+
args.endDate = clamped.end;
|
|
1608
|
+
}
|
|
1609
|
+
}
|
|
1610
|
+
if (cmd.write) args.dry_run = true;
|
|
1611
|
+
return { args, errors, notes };
|
|
1612
|
+
}
|
|
1613
|
+
async function runCapability(domain, cmd, opts) {
|
|
1614
|
+
const label = `${domain.name} ${cmd.name}`;
|
|
1615
|
+
const quiet = opts.json === true;
|
|
1616
|
+
const { args, errors, notes } = buildToolArgs(cmd, opts);
|
|
1617
|
+
if (cmd.write && opts.commit === true) delete args.dry_run;
|
|
1618
|
+
if (errors.length) {
|
|
1619
|
+
const message = withNextStep(`Cannot run \`carrier ${label}\`.`, [
|
|
1620
|
+
...errors,
|
|
1621
|
+
`See every flag: carrier ${label} --help`
|
|
1622
|
+
]);
|
|
1623
|
+
if (quiet) console.error(message);
|
|
1624
|
+
else p2.log.error(message);
|
|
1625
|
+
process.exitCode = 1;
|
|
1626
|
+
return;
|
|
1627
|
+
}
|
|
1628
|
+
const dryRun = cmd.write === true && args.dry_run === true;
|
|
1629
|
+
const warnings = dryRun ? [...notes, "Dry run \u2014 nothing changes. Re-run with --commit to apply."] : notes;
|
|
1630
|
+
for (const note4 of warnings) {
|
|
1631
|
+
if (quiet) console.error(note4);
|
|
1632
|
+
else p2.log.info(pc3.dim(note4));
|
|
1633
|
+
}
|
|
1634
|
+
const s = quiet ? null : p2.spinner();
|
|
1635
|
+
s?.start(`Calling ${cmd.tool}`);
|
|
1636
|
+
const result = await callMcpTool(cmd.tool, args, {
|
|
1637
|
+
errorHints: [
|
|
1638
|
+
`Check the arguments: carrier ${label} --help`,
|
|
1639
|
+
'Or describe the task in words: carrier ask "\u2026"'
|
|
1640
|
+
]
|
|
1641
|
+
});
|
|
1642
|
+
s?.stop(result.ok ? "Done" : "Failed");
|
|
1643
|
+
if (!result.ok) {
|
|
1644
|
+
if (quiet) console.error(result.text);
|
|
1645
|
+
else p2.log.error(result.text);
|
|
1646
|
+
process.exitCode = 1;
|
|
1647
|
+
return;
|
|
1648
|
+
}
|
|
1649
|
+
if (quiet) {
|
|
1650
|
+
process.stdout.write(`${result.text}
|
|
1651
|
+
`);
|
|
1652
|
+
return;
|
|
1653
|
+
}
|
|
1654
|
+
const body = result.text.length > MAX_PRETTY_CHARS ? `${result.text.slice(0, MAX_PRETTY_CHARS)}
|
|
1655
|
+
\u2026truncated. Re-run with --json for the whole response.` : result.text;
|
|
1656
|
+
p2.note(body, cmd.tool);
|
|
1657
|
+
}
|
|
1658
|
+
|
|
987
1659
|
// src/cli/index.ts
|
|
988
|
-
var VERSION =
|
|
1660
|
+
var VERSION = CARRIER_VERSION;
|
|
989
1661
|
function header() {
|
|
990
|
-
|
|
1662
|
+
p3.intro(`${pc4.bold(pc4.yellow("\u25C6 carrier"))} ${pc4.dim("\xB7 the Stripe of telecom \u2014 CLI v" + VERSION)}`);
|
|
991
1663
|
}
|
|
992
1664
|
function ok(msg) {
|
|
993
|
-
|
|
1665
|
+
p3.log.success(pc4.green(msg));
|
|
994
1666
|
}
|
|
995
1667
|
function info(msg) {
|
|
996
|
-
|
|
1668
|
+
p3.log.info(msg);
|
|
997
1669
|
}
|
|
998
1670
|
function fail(msg, next) {
|
|
999
|
-
|
|
1000
|
-
|
|
1671
|
+
p3.log.error(msg);
|
|
1672
|
+
p3.note(next.map((s) => `\u2192 ${s}`).join("\n"), "What to do next");
|
|
1001
1673
|
}
|
|
1002
1674
|
async function promptBrand(seed) {
|
|
1003
|
-
const name = await
|
|
1675
|
+
const name = await p3.text({
|
|
1004
1676
|
message: "Brand name",
|
|
1005
1677
|
placeholder: seed.name,
|
|
1006
1678
|
defaultValue: seed.name
|
|
1007
1679
|
});
|
|
1008
|
-
if (
|
|
1009
|
-
const domain = await
|
|
1680
|
+
if (p3.isCancel(name)) process.exit(0);
|
|
1681
|
+
const domain = await p3.text({
|
|
1010
1682
|
message: "Domain",
|
|
1011
1683
|
placeholder: seed.domain,
|
|
1012
1684
|
defaultValue: seed.domain
|
|
1013
1685
|
});
|
|
1014
|
-
if (
|
|
1015
|
-
const accent = await
|
|
1686
|
+
if (p3.isCancel(domain)) process.exit(0);
|
|
1687
|
+
const accent = await p3.text({
|
|
1016
1688
|
message: "Accent color (hex)",
|
|
1017
1689
|
placeholder: seed.colors.accent,
|
|
1018
1690
|
defaultValue: seed.colors.accent
|
|
1019
1691
|
});
|
|
1020
|
-
if (
|
|
1021
|
-
const supportEmail = await
|
|
1692
|
+
if (p3.isCancel(accent)) process.exit(0);
|
|
1693
|
+
const supportEmail = await p3.text({
|
|
1022
1694
|
message: "Support email",
|
|
1023
1695
|
placeholder: `support@${domain}`,
|
|
1024
1696
|
defaultValue: `support@${domain}`
|
|
1025
1697
|
});
|
|
1026
|
-
if (
|
|
1698
|
+
if (p3.isCancel(supportEmail)) process.exit(0);
|
|
1027
1699
|
const accentDark = deriveAccentDark(accent, seed);
|
|
1028
1700
|
const isCarrier = name === CARRIER_BRAND.name && domain === CARRIER_BRAND.domain;
|
|
1029
1701
|
return {
|
|
@@ -1040,7 +1712,7 @@ async function promptBrand(seed) {
|
|
|
1040
1712
|
};
|
|
1041
1713
|
}
|
|
1042
1714
|
async function doPluginInstall() {
|
|
1043
|
-
const s =
|
|
1715
|
+
const s = p3.spinner();
|
|
1044
1716
|
s.start("Installing Carrier Claude Code plugin + zero-cred MCP");
|
|
1045
1717
|
let r;
|
|
1046
1718
|
try {
|
|
@@ -1057,13 +1729,13 @@ async function doPluginInstall() {
|
|
|
1057
1729
|
s.stop("Plugin staged");
|
|
1058
1730
|
ok(`Plugin \u2192 ${r.copiedTo}`);
|
|
1059
1731
|
info(
|
|
1060
|
-
`MCP: ${r.mcpAdded ?
|
|
1732
|
+
`MCP: ${r.mcpAdded ? pc4.green("registered") : pc4.yellow("manual")} \xB7 marketplace: ${r.marketplaceAdded ? pc4.green("added") : pc4.yellow("manual")} \xB7 install: ${r.pluginInstalled ? pc4.green("done") : pc4.yellow("manual")}`
|
|
1061
1733
|
);
|
|
1062
|
-
|
|
1734
|
+
p3.note(installAuthGuidance().join("\n"), "OAuth on first use (Entry A)");
|
|
1063
1735
|
if (!r.claudeFound || r.notes.length) {
|
|
1064
|
-
|
|
1065
|
-
if (r.notes.length) info(
|
|
1066
|
-
|
|
1736
|
+
p3.note(manualCommands().join("\n"), "Finish wiring (run in your terminal)");
|
|
1737
|
+
if (r.notes.length) info(pc4.dim(r.notes.join("\n")));
|
|
1738
|
+
p3.note(
|
|
1067
1739
|
[
|
|
1068
1740
|
"You can still create an account now:",
|
|
1069
1741
|
` carrier open signup`,
|
|
@@ -1072,20 +1744,20 @@ async function doPluginInstall() {
|
|
|
1072
1744
|
"Next"
|
|
1073
1745
|
);
|
|
1074
1746
|
} else {
|
|
1075
|
-
|
|
1747
|
+
p3.note(
|
|
1076
1748
|
[
|
|
1077
1749
|
"Open Claude Code and say something like:",
|
|
1078
1750
|
' "Show my fleet health"',
|
|
1079
1751
|
"First call opens the browser for sign-in / sign-up. No token paste.",
|
|
1080
1752
|
"",
|
|
1081
|
-
"More prompts: carrier
|
|
1753
|
+
"More prompts: carrier examples"
|
|
1082
1754
|
].join("\n"),
|
|
1083
1755
|
"Talk to your fleet"
|
|
1084
1756
|
);
|
|
1085
1757
|
}
|
|
1086
1758
|
}
|
|
1087
1759
|
async function doSiteCreate(target, brand) {
|
|
1088
|
-
const s =
|
|
1760
|
+
const s = p3.spinner();
|
|
1089
1761
|
s.start(`Scaffolding ${brand.name} storefront \u2192 ${target}`);
|
|
1090
1762
|
try {
|
|
1091
1763
|
await scaffoldStorefront(target, brand);
|
|
@@ -1100,15 +1772,25 @@ async function doSiteCreate(target, brand) {
|
|
|
1100
1772
|
}
|
|
1101
1773
|
s.stop("Storefront scaffolded");
|
|
1102
1774
|
ok(`Created ${target} (white-labeled: ${brand.name}, accent ${brand.colors.accent})`);
|
|
1775
|
+
const logoSpin = p3.spinner();
|
|
1776
|
+
logoSpin.start("Generating storefront logo");
|
|
1777
|
+
try {
|
|
1778
|
+
const logo = await writeStorefrontLogo(target, brand);
|
|
1779
|
+
logoSpin.stop(`Logo ready (${logo.source})`);
|
|
1780
|
+
ok(logo.path);
|
|
1781
|
+
} catch (e) {
|
|
1782
|
+
logoSpin.stop("Logo skipped");
|
|
1783
|
+
info(pc4.yellow(String(e instanceof Error ? e.message : e)));
|
|
1784
|
+
}
|
|
1103
1785
|
}
|
|
1104
1786
|
async function maybeBuildDeploy(target, brand, opts) {
|
|
1105
1787
|
if (opts.install) {
|
|
1106
|
-
const s =
|
|
1788
|
+
const s = p3.spinner();
|
|
1107
1789
|
s.start("Installing storefront dependencies");
|
|
1108
1790
|
const oki = await installDeps(target);
|
|
1109
1791
|
s.stop(oki ? "Dependencies installed" : "Dependency install reported errors");
|
|
1110
1792
|
if (!oki) {
|
|
1111
|
-
|
|
1793
|
+
p3.note(
|
|
1112
1794
|
[
|
|
1113
1795
|
`cd ${target} && pnpm install`,
|
|
1114
1796
|
"Fix any Node/pnpm version issues, then retry build"
|
|
@@ -1119,12 +1801,12 @@ async function maybeBuildDeploy(target, brand, opts) {
|
|
|
1119
1801
|
}
|
|
1120
1802
|
}
|
|
1121
1803
|
if (opts.build) {
|
|
1122
|
-
const s =
|
|
1804
|
+
const s = p3.spinner();
|
|
1123
1805
|
s.start("Building storefront (next build)");
|
|
1124
1806
|
const okb = await buildSite(target);
|
|
1125
1807
|
s.stop(okb ? "Build succeeded" : "Build failed \u2014 see output above");
|
|
1126
1808
|
if (!okb) {
|
|
1127
|
-
|
|
1809
|
+
p3.note(
|
|
1128
1810
|
[`cd ${target}`, "pnpm build", "Check env keys in .env.local if the build mentions Clerk"].join("\n"),
|
|
1129
1811
|
"Next"
|
|
1130
1812
|
);
|
|
@@ -1132,13 +1814,13 @@ async function maybeBuildDeploy(target, brand, opts) {
|
|
|
1132
1814
|
}
|
|
1133
1815
|
}
|
|
1134
1816
|
if (opts.deploy) {
|
|
1135
|
-
const s =
|
|
1817
|
+
const s = p3.spinner();
|
|
1136
1818
|
s.start("Deploying to Cloudflare Workers");
|
|
1137
1819
|
const r = await deploySite(target, brand);
|
|
1138
1820
|
s.stop(r.ok ? `Deployed: ${r.projectName}` : "Deploy skipped");
|
|
1139
1821
|
if (!r.ok && r.reason) {
|
|
1140
|
-
info(
|
|
1141
|
-
|
|
1822
|
+
info(pc4.yellow(r.reason));
|
|
1823
|
+
p3.note(
|
|
1142
1824
|
[
|
|
1143
1825
|
"Install wrangler and login: npx wrangler login",
|
|
1144
1826
|
`Then: carrier site deploy ${target}`
|
|
@@ -1150,26 +1832,31 @@ async function maybeBuildDeploy(target, brand, opts) {
|
|
|
1150
1832
|
}
|
|
1151
1833
|
async function interactiveSiteCreate() {
|
|
1152
1834
|
const brand = await promptBrand(CARRIER_BRAND);
|
|
1153
|
-
const dir = await
|
|
1835
|
+
const dir = await p3.text({
|
|
1154
1836
|
message: "Output directory",
|
|
1155
1837
|
placeholder: "./storefront",
|
|
1156
1838
|
defaultValue: "./storefront"
|
|
1157
1839
|
});
|
|
1158
|
-
if (
|
|
1840
|
+
if (p3.isCancel(dir)) return;
|
|
1159
1841
|
const target = resolve2(dir);
|
|
1160
1842
|
if (await exists(target)) {
|
|
1161
|
-
const go = await
|
|
1162
|
-
if (
|
|
1163
|
-
|
|
1843
|
+
const go = await p3.confirm({ message: `${target} exists \u2014 write into it anyway?`, initialValue: false });
|
|
1844
|
+
if (p3.isCancel(go) || !go) {
|
|
1845
|
+
p3.outro("Stopped. Re-run with a different directory.");
|
|
1164
1846
|
return;
|
|
1165
1847
|
}
|
|
1166
1848
|
}
|
|
1167
1849
|
await doSiteCreate(target, brand);
|
|
1168
|
-
|
|
1850
|
+
p3.outro(pc4.green(`Scaffolded. cd ${dir} && pnpm install && pnpm dev`));
|
|
1169
1851
|
}
|
|
1170
1852
|
var program = new Command();
|
|
1171
1853
|
program.name("carrier").description(
|
|
1172
|
-
|
|
1854
|
+
[
|
|
1855
|
+
"Carrier CLI \u2014 status, OAuth-ready MCP install, storefront scaffold, and fleet control.",
|
|
1856
|
+
"",
|
|
1857
|
+
"Domains: " + CLI_DOMAINS.map((d) => d.name).join(", "),
|
|
1858
|
+
'Anything they do not cover: carrier ask "\u2026"'
|
|
1859
|
+
].join("\n")
|
|
1173
1860
|
).version(VERSION).action(async () => {
|
|
1174
1861
|
header();
|
|
1175
1862
|
await runHome({
|
|
@@ -1184,12 +1871,12 @@ program.command("init").description("Interactive home: status, install plugin+MC
|
|
|
1184
1871
|
const brand = CARRIER_BRAND;
|
|
1185
1872
|
const target = resolve2(o.dir);
|
|
1186
1873
|
if (await exists(target)) {
|
|
1187
|
-
info(
|
|
1874
|
+
info(pc4.yellow(`${target} exists \u2014 writing into it (--yes).`));
|
|
1188
1875
|
}
|
|
1189
1876
|
await doSiteCreate(target, brand);
|
|
1190
1877
|
await maybeBuildDeploy(target, brand, { install: true, build: true, deploy: false });
|
|
1191
|
-
|
|
1192
|
-
|
|
1878
|
+
p3.note(oauthFirstUseNote(), "Auth");
|
|
1879
|
+
p3.note(
|
|
1193
1880
|
[
|
|
1194
1881
|
`cd ${o.dir}`,
|
|
1195
1882
|
`Edit src/brand.config.ts to re-brand anytime`,
|
|
@@ -1199,7 +1886,7 @@ program.command("init").description("Interactive home: status, install plugin+MC
|
|
|
1199
1886
|
].join("\n"),
|
|
1200
1887
|
"Next"
|
|
1201
1888
|
);
|
|
1202
|
-
|
|
1889
|
+
p3.outro(pc4.green("Done. Your connectivity business is wired."));
|
|
1203
1890
|
return;
|
|
1204
1891
|
}
|
|
1205
1892
|
if (o.full) {
|
|
@@ -1209,14 +1896,14 @@ program.command("init").description("Interactive home: status, install plugin+MC
|
|
|
1209
1896
|
const brand = await promptBrand(CARRIER_BRAND);
|
|
1210
1897
|
const target = resolve2(o.dir);
|
|
1211
1898
|
if (await exists(target)) {
|
|
1212
|
-
const go = await
|
|
1213
|
-
if (
|
|
1214
|
-
|
|
1899
|
+
const go = await p3.confirm({ message: `${target} exists \u2014 write into it anyway?`, initialValue: false });
|
|
1900
|
+
if (p3.isCancel(go) || !go) {
|
|
1901
|
+
p3.outro("Stopped. Re-run with --dir <new path>.");
|
|
1215
1902
|
return;
|
|
1216
1903
|
}
|
|
1217
1904
|
}
|
|
1218
1905
|
await doSiteCreate(target, brand);
|
|
1219
|
-
const next = await
|
|
1906
|
+
const next = await p3.select({
|
|
1220
1907
|
message: "Roll it out now?",
|
|
1221
1908
|
options: [
|
|
1222
1909
|
{ value: "build", label: "Install deps + build" },
|
|
@@ -1225,14 +1912,14 @@ program.command("init").description("Interactive home: status, install plugin+MC
|
|
|
1225
1912
|
],
|
|
1226
1913
|
initialValue: "build"
|
|
1227
1914
|
});
|
|
1228
|
-
if (
|
|
1915
|
+
if (p3.isCancel(next)) process.exit(0);
|
|
1229
1916
|
await maybeBuildDeploy(target, brand, {
|
|
1230
1917
|
install: next !== "none",
|
|
1231
1918
|
build: next !== "none",
|
|
1232
1919
|
deploy: next === "deploy"
|
|
1233
1920
|
});
|
|
1234
|
-
|
|
1235
|
-
|
|
1921
|
+
p3.note(oauthFirstUseNote(), "Auth");
|
|
1922
|
+
p3.note(
|
|
1236
1923
|
[
|
|
1237
1924
|
`cd ${o.dir}`,
|
|
1238
1925
|
`Edit src/brand.config.ts to re-brand anytime`,
|
|
@@ -1241,7 +1928,7 @@ program.command("init").description("Interactive home: status, install plugin+MC
|
|
|
1241
1928
|
].join("\n"),
|
|
1242
1929
|
"Next"
|
|
1243
1930
|
);
|
|
1244
|
-
|
|
1931
|
+
p3.outro(pc4.green("Done. Your connectivity business is wired."));
|
|
1245
1932
|
return;
|
|
1246
1933
|
}
|
|
1247
1934
|
await runHome({
|
|
@@ -1250,14 +1937,14 @@ program.command("init").description("Interactive home: status, install plugin+MC
|
|
|
1250
1937
|
const brand = await promptBrand(CARRIER_BRAND);
|
|
1251
1938
|
const target = resolve2(o.dir);
|
|
1252
1939
|
if (await exists(target)) {
|
|
1253
|
-
const go = await
|
|
1254
|
-
if (
|
|
1255
|
-
|
|
1940
|
+
const go = await p3.confirm({ message: `${target} exists \u2014 write into it anyway?`, initialValue: false });
|
|
1941
|
+
if (p3.isCancel(go) || !go) {
|
|
1942
|
+
p3.log.info("Skipped storefront. Pick Install or Exit from the menu, or re-run with --dir.");
|
|
1256
1943
|
return;
|
|
1257
1944
|
}
|
|
1258
1945
|
}
|
|
1259
1946
|
await doSiteCreate(target, brand);
|
|
1260
|
-
const next = await
|
|
1947
|
+
const next = await p3.select({
|
|
1261
1948
|
message: "Roll it out now?",
|
|
1262
1949
|
options: [
|
|
1263
1950
|
{ value: "build", label: "Install deps + build" },
|
|
@@ -1266,13 +1953,13 @@ program.command("init").description("Interactive home: status, install plugin+MC
|
|
|
1266
1953
|
],
|
|
1267
1954
|
initialValue: "build"
|
|
1268
1955
|
});
|
|
1269
|
-
if (
|
|
1956
|
+
if (p3.isCancel(next)) return;
|
|
1270
1957
|
await maybeBuildDeploy(target, brand, {
|
|
1271
1958
|
install: next !== "none",
|
|
1272
1959
|
build: next !== "none",
|
|
1273
1960
|
deploy: next === "deploy"
|
|
1274
1961
|
});
|
|
1275
|
-
|
|
1962
|
+
p3.note(
|
|
1276
1963
|
[
|
|
1277
1964
|
`cd ${o.dir}`,
|
|
1278
1965
|
`pnpm dev`,
|
|
@@ -1287,25 +1974,25 @@ var plugin = program.command("plugin").description("Manage the Carrier Claude Co
|
|
|
1287
1974
|
plugin.command("install").description("Install/register the Carrier plugin + zero-cred MCP (OAuth on first use).").action(async () => {
|
|
1288
1975
|
header();
|
|
1289
1976
|
await doPluginInstall();
|
|
1290
|
-
|
|
1977
|
+
p3.outro(pc4.green("Plugin ready. Restart Claude Code, then talk to your fleet."));
|
|
1291
1978
|
});
|
|
1292
1979
|
plugin.command("status").description("Show plugin + MCP registration + auth next steps.").action(async () => {
|
|
1293
1980
|
header();
|
|
1294
1981
|
const st = await gatherStatus();
|
|
1295
1982
|
printStatus(st);
|
|
1296
|
-
|
|
1297
|
-
|
|
1983
|
+
p3.note(oauthFirstUseNote(), "Auth");
|
|
1984
|
+
p3.outro("");
|
|
1298
1985
|
});
|
|
1299
1986
|
program.command("status").description("Show MCP / plugin / auth status and next steps.").action(async () => {
|
|
1300
1987
|
header();
|
|
1301
|
-
const s =
|
|
1988
|
+
const s = p3.spinner();
|
|
1302
1989
|
s.start("Checking status");
|
|
1303
1990
|
const st = await gatherStatus();
|
|
1304
1991
|
s.stop("Done");
|
|
1305
|
-
|
|
1306
|
-
|
|
1307
|
-
|
|
1308
|
-
|
|
1992
|
+
p3.note(formatStatusBlock(st), "Status");
|
|
1993
|
+
p3.note(formatNextSteps(st), "Next steps");
|
|
1994
|
+
p3.note(oauthFirstUseNote(), "Auth");
|
|
1995
|
+
p3.outro("");
|
|
1309
1996
|
});
|
|
1310
1997
|
var openCmd = program.command("open").description("Open Carrier account / product URLs in your browser.");
|
|
1311
1998
|
for (const [name, url, desc] of [
|
|
@@ -1320,17 +2007,17 @@ for (const [name, url, desc] of [
|
|
|
1320
2007
|
const r = await openUrl(url);
|
|
1321
2008
|
if (r.ok) ok(r.hint);
|
|
1322
2009
|
else {
|
|
1323
|
-
|
|
1324
|
-
|
|
2010
|
+
p3.log.warn(r.hint);
|
|
2011
|
+
p3.note(url, "Open this URL");
|
|
1325
2012
|
}
|
|
1326
|
-
|
|
1327
|
-
|
|
2013
|
+
p3.note(accountLinksNote(), "Account links");
|
|
2014
|
+
p3.outro("");
|
|
1328
2015
|
});
|
|
1329
2016
|
}
|
|
1330
2017
|
program.command("examples").description("Print natural-language fleet prompts for Claude / MCP.").action(async () => {
|
|
1331
2018
|
header();
|
|
1332
|
-
|
|
1333
|
-
|
|
2019
|
+
p3.note(formatNlExamples(), "Talk to fleet \u2014 paste into Claude");
|
|
2020
|
+
p3.note(
|
|
1334
2021
|
[
|
|
1335
2022
|
"After `carrier plugin install` (or this menu \u2192 Install):",
|
|
1336
2023
|
" 1. Open Claude Code",
|
|
@@ -1341,9 +2028,21 @@ program.command("examples").description("Print natural-language fleet prompts fo
|
|
|
1341
2028
|
].join("\n"),
|
|
1342
2029
|
"How"
|
|
1343
2030
|
);
|
|
1344
|
-
|
|
2031
|
+
p3.note(
|
|
2032
|
+
[
|
|
2033
|
+
"With CARRIER_API_KEY set, every capability also has its own command:",
|
|
2034
|
+
...CLI_DOMAINS.map((d) => ` carrier ${d.name} --help`.padEnd(34) + pc4.dim(d.summary)),
|
|
2035
|
+
"",
|
|
2036
|
+
" --json raw response on stdout, ready for jq",
|
|
2037
|
+
" --commit apply a change instead of previewing it"
|
|
2038
|
+
].join("\n"),
|
|
2039
|
+
"Domain commands"
|
|
2040
|
+
);
|
|
2041
|
+
p3.outro("");
|
|
1345
2042
|
});
|
|
1346
|
-
program.command("ask").description(
|
|
2043
|
+
program.command("ask").description(
|
|
2044
|
+
'Natural-language fallback for whatever the domain commands miss: carrier ask "show fleet health" (needs CARRIER_API_KEY or OCS token).'
|
|
2045
|
+
).argument("<intent...>", "Natural-language intent").action(async (parts) => {
|
|
1347
2046
|
header();
|
|
1348
2047
|
const intent = parts.join(" ").trim();
|
|
1349
2048
|
if (!intent) {
|
|
@@ -1354,19 +2053,33 @@ program.command("ask").description('Optional headless NL: carrier ask "show flee
|
|
|
1354
2053
|
process.exitCode = 1;
|
|
1355
2054
|
return;
|
|
1356
2055
|
}
|
|
1357
|
-
const s =
|
|
2056
|
+
const s = p3.spinner();
|
|
1358
2057
|
s.start("Asking Carrier MCP\u2026");
|
|
1359
2058
|
const result = await carrierAsk(intent);
|
|
1360
2059
|
s.stop(result.ok ? "Done" : "Failed");
|
|
1361
2060
|
if (result.ok) {
|
|
1362
|
-
|
|
1363
|
-
|
|
2061
|
+
p3.note(result.text.slice(0, 6e3), "Answer");
|
|
2062
|
+
p3.outro("");
|
|
1364
2063
|
} else {
|
|
1365
|
-
|
|
1366
|
-
|
|
2064
|
+
p3.log.error(result.text);
|
|
2065
|
+
p3.outro(pc4.yellow("See next steps above."));
|
|
1367
2066
|
process.exitCode = 1;
|
|
1368
2067
|
}
|
|
1369
2068
|
});
|
|
2069
|
+
for (const domain of CLI_DOMAINS) {
|
|
2070
|
+
const group = program.command(domain.name).description(domain.summary);
|
|
2071
|
+
for (const cmd of domain.commands) {
|
|
2072
|
+
const sub = group.command(cmd.name).description(cmd.write ? `${cmd.summary} Dry run unless --commit.` : cmd.summary);
|
|
2073
|
+
for (const opt of cmd.options) sub.option(opt.flags, opt.description);
|
|
2074
|
+
sub.option("--json", "Print the raw tool response on stdout and nothing else");
|
|
2075
|
+
if (cmd.write) sub.option("--commit", "Apply the change instead of previewing it");
|
|
2076
|
+
sub.action(async (opts) => {
|
|
2077
|
+
if (opts.json !== true) header();
|
|
2078
|
+
await runCapability(domain, cmd, opts);
|
|
2079
|
+
if (opts.json !== true) p3.outro("");
|
|
2080
|
+
});
|
|
2081
|
+
}
|
|
2082
|
+
}
|
|
1370
2083
|
var site = program.command("site").description("Scaffold and deploy a white-labeled storefront.");
|
|
1371
2084
|
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
2085
|
header();
|
|
@@ -1396,9 +2109,9 @@ site.command("create [dir]").description("Scaffold a white-labeled storefront fr
|
|
|
1396
2109
|
}
|
|
1397
2110
|
const target = resolve2(dir ?? "./storefront");
|
|
1398
2111
|
if (await exists(target)) {
|
|
1399
|
-
const go = await
|
|
1400
|
-
if (
|
|
1401
|
-
|
|
2112
|
+
const go = await p3.confirm({ message: `${target} exists \u2014 write into it anyway?`, initialValue: false });
|
|
2113
|
+
if (p3.isCancel(go) || !go) {
|
|
2114
|
+
p3.outro("Stopped. Re-run with a different directory.");
|
|
1402
2115
|
return;
|
|
1403
2116
|
}
|
|
1404
2117
|
}
|
|
@@ -1408,7 +2121,7 @@ site.command("create [dir]").description("Scaffold a white-labeled storefront fr
|
|
|
1408
2121
|
process.exitCode = 1;
|
|
1409
2122
|
return;
|
|
1410
2123
|
}
|
|
1411
|
-
|
|
2124
|
+
p3.outro(pc4.green(`Scaffolded. cd ${dir ?? "storefront"} && pnpm install && pnpm dev`));
|
|
1412
2125
|
});
|
|
1413
2126
|
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
2127
|
header();
|
|
@@ -1424,12 +2137,28 @@ site.command("deploy [dir]").description("Build + deploy a storefront to Cloudfl
|
|
|
1424
2137
|
process.exitCode = 1;
|
|
1425
2138
|
return;
|
|
1426
2139
|
}
|
|
1427
|
-
|
|
2140
|
+
p3.outro("");
|
|
2141
|
+
});
|
|
2142
|
+
site.command("logo [dir]").description("Generate a storefront logo (SVG always; PNG if an image key is on this box or Carrier).").action(async (dir) => {
|
|
2143
|
+
header();
|
|
2144
|
+
const target = resolve2(dir ?? "./storefront");
|
|
2145
|
+
try {
|
|
2146
|
+
const brand = await loadStorefrontBrand(target);
|
|
2147
|
+
const logo = await writeStorefrontLogo(target, brand);
|
|
2148
|
+
ok(`Logo (${logo.source}): ${logo.path}`);
|
|
2149
|
+
} catch (e) {
|
|
2150
|
+
fail(String(e instanceof Error ? e.message : e), [
|
|
2151
|
+
"Scaffold first: carrier site create"
|
|
2152
|
+
]);
|
|
2153
|
+
process.exitCode = 1;
|
|
2154
|
+
return;
|
|
2155
|
+
}
|
|
2156
|
+
p3.outro("");
|
|
1428
2157
|
});
|
|
1429
2158
|
program.parseAsync(process.argv).catch((e) => {
|
|
1430
|
-
console.error(
|
|
2159
|
+
console.error(pc4.red(String(e instanceof Error ? e.message : e)));
|
|
1431
2160
|
console.error(
|
|
1432
|
-
|
|
2161
|
+
pc4.dim(
|
|
1433
2162
|
[
|
|
1434
2163
|
"",
|
|
1435
2164
|
"What to do next:",
|