@carrierllc/mcp 0.2.19 → 0.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +22 -1
- package/dist/chunk-ZII4CD4U.js +461 -0
- package/dist/chunk-ZII4CD4U.js.map +1 -0
- package/dist/cli.js +1294 -105
- package/dist/cli.js.map +1 -1
- package/dist/index.js +144 -315
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
- 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/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/complete-email-sign-up.ts +3 -3
- 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-ZII4CD4U.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,6 +769,10 @@ import * as p from "@clack/prompts";
|
|
|
743
769
|
import pc2 from "picocolors";
|
|
744
770
|
|
|
745
771
|
// src/cli/lib/ask.ts
|
|
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
|
+
];
|
|
746
776
|
function parseRpcBody(raw) {
|
|
747
777
|
const trimmed = raw.trim();
|
|
748
778
|
if (!trimmed) return {};
|
|
@@ -753,7 +783,7 @@ function parseRpcBody(raw) {
|
|
|
753
783
|
if (!dataLine) return {};
|
|
754
784
|
return JSON.parse(dataLine.slice(5).trim());
|
|
755
785
|
}
|
|
756
|
-
async function
|
|
786
|
+
async function callMcpTool(tool, args, opts = {}) {
|
|
757
787
|
const token = resolveCliToken();
|
|
758
788
|
if (!token) {
|
|
759
789
|
return {
|
|
@@ -761,7 +791,8 @@ async function carrierAsk(intent) {
|
|
|
761
791
|
text: withNextStep("No headless token in the environment.", [
|
|
762
792
|
"Interactive path: open Claude and say your intent \u2014 OAuth runs on first use.",
|
|
763
793
|
"Or export CARRIER_API_KEY=ak_\u2026 from Console \u2192 Settings \u2192 API Keys.",
|
|
764
|
-
'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"
|
|
765
796
|
])
|
|
766
797
|
};
|
|
767
798
|
}
|
|
@@ -781,7 +812,7 @@ async function carrierAsk(intent) {
|
|
|
781
812
|
params: {
|
|
782
813
|
protocolVersion: "2024-11-05",
|
|
783
814
|
capabilities: {},
|
|
784
|
-
clientInfo: { name: "carrier-cli", version:
|
|
815
|
+
clientInfo: { name: "carrier-cli", version: CARRIER_VERSION }
|
|
785
816
|
}
|
|
786
817
|
})
|
|
787
818
|
});
|
|
@@ -819,7 +850,7 @@ async function carrierAsk(intent) {
|
|
|
819
850
|
jsonrpc: "2.0",
|
|
820
851
|
id: 2,
|
|
821
852
|
method: "tools/call",
|
|
822
|
-
params: { name:
|
|
853
|
+
params: { name: tool, arguments: args }
|
|
823
854
|
})
|
|
824
855
|
});
|
|
825
856
|
if (!res.ok) {
|
|
@@ -836,14 +867,11 @@ async function carrierAsk(intent) {
|
|
|
836
867
|
if (json.error?.message) {
|
|
837
868
|
return {
|
|
838
869
|
ok: false,
|
|
839
|
-
text: withNextStep(json.error.message,
|
|
840
|
-
"Rephrase the intent more specifically (include ICCID / MSISDN if relevant)",
|
|
841
|
-
"Or open Claude and ask there with full tool routing"
|
|
842
|
-
])
|
|
870
|
+
text: withNextStep(json.error.message, opts.errorHints ?? DEFAULT_ERROR_HINTS)
|
|
843
871
|
};
|
|
844
872
|
}
|
|
845
873
|
const parts = json.result?.content ?? [];
|
|
846
|
-
const text3 = parts.map((
|
|
874
|
+
const text3 = parts.map((p4) => p4.text).filter(Boolean).join("\n").trim() || JSON.stringify(json.result ?? json, null, 2);
|
|
847
875
|
return { ok: !json.result?.isError, text: text3 };
|
|
848
876
|
} catch (e) {
|
|
849
877
|
const msg = e instanceof Error ? e.message : String(e);
|
|
@@ -856,6 +884,18 @@ async function carrierAsk(intent) {
|
|
|
856
884
|
};
|
|
857
885
|
}
|
|
858
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
|
+
}
|
|
859
899
|
|
|
860
900
|
// src/cli/lib/home.ts
|
|
861
901
|
function mark(ok2) {
|
|
@@ -996,46 +1036,1125 @@ async function runHome(opts) {
|
|
|
996
1036
|
}
|
|
997
1037
|
}
|
|
998
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
|
+
name: "sim-status show",
|
|
1194
|
+
tool: "get_sim_provider_status",
|
|
1195
|
+
summary: "SIM/eSIM status at the SIM provider level, distinct from OCS status.",
|
|
1196
|
+
options: [ICCID]
|
|
1197
|
+
},
|
|
1198
|
+
{
|
|
1199
|
+
name: "sim-status set",
|
|
1200
|
+
tool: "change_sim_status",
|
|
1201
|
+
summary: "Change the SIM/eSIM status at the SIM provider level. DELETED is irreversible.",
|
|
1202
|
+
options: [
|
|
1203
|
+
ICCID,
|
|
1204
|
+
{
|
|
1205
|
+
flags: "--status <status>",
|
|
1206
|
+
description: "New SIM status: ENABLED, DISABLED or DELETED (irreversible)",
|
|
1207
|
+
arg: "simStatus",
|
|
1208
|
+
type: "string",
|
|
1209
|
+
required: true
|
|
1210
|
+
}
|
|
1211
|
+
],
|
|
1212
|
+
write: true
|
|
1213
|
+
},
|
|
1214
|
+
{
|
|
1215
|
+
name: "phone-number set",
|
|
1216
|
+
tool: "affect_subscriber_phone_number",
|
|
1217
|
+
summary: "Assign a phone number (MSISDN) to a subscriber.",
|
|
1218
|
+
options: [
|
|
1219
|
+
ICCID,
|
|
1220
|
+
{
|
|
1221
|
+
flags: "--phone-number <e164>",
|
|
1222
|
+
description: "E.164 phone number to assign, e.g. +31612345678",
|
|
1223
|
+
arg: "phone_number",
|
|
1224
|
+
type: "string",
|
|
1225
|
+
required: true
|
|
1226
|
+
},
|
|
1227
|
+
{
|
|
1228
|
+
flags: "--phone-type <type>",
|
|
1229
|
+
description: "'fake' for test/dev MSISDNs, 'real' for production",
|
|
1230
|
+
arg: "phone_type",
|
|
1231
|
+
type: "string",
|
|
1232
|
+
required: true
|
|
1233
|
+
}
|
|
1234
|
+
],
|
|
1235
|
+
write: true
|
|
1236
|
+
},
|
|
1237
|
+
{
|
|
1238
|
+
name: "contact set",
|
|
1239
|
+
tool: "modify_subscriber_contact_info",
|
|
1240
|
+
summary: "Update contact details on a subscriber. Omitted fields stay unchanged.",
|
|
1241
|
+
options: [
|
|
1242
|
+
ICCID,
|
|
1243
|
+
{ flags: "--first-name <name>", description: "First name", arg: "firstName", type: "string" },
|
|
1244
|
+
{ flags: "--last-name <name>", description: "Last name", arg: "lastName", type: "string" },
|
|
1245
|
+
{ flags: "--company <name>", description: "Company name", arg: "company", type: "string" },
|
|
1246
|
+
{ flags: "--email <email>", description: "Email address", arg: "email", type: "string" },
|
|
1247
|
+
{ flags: "--phone-number <number>", description: "Phone number", arg: "phoneNumber", type: "string" }
|
|
1248
|
+
],
|
|
1249
|
+
write: true
|
|
1250
|
+
},
|
|
1251
|
+
{
|
|
1252
|
+
name: "mobile-plan set",
|
|
1253
|
+
tool: "modify_subscriber_mobile_plan",
|
|
1254
|
+
summary: "Change the mobile pricing plan a subscriber is billed under.",
|
|
1255
|
+
options: [
|
|
1256
|
+
ICCID,
|
|
1257
|
+
{
|
|
1258
|
+
flags: "--plan-id <id>",
|
|
1259
|
+
description: "Mobile pricing plan ID",
|
|
1260
|
+
arg: "mobile_plan_id",
|
|
1261
|
+
type: "number",
|
|
1262
|
+
required: true
|
|
1263
|
+
}
|
|
1264
|
+
],
|
|
1265
|
+
write: true
|
|
1266
|
+
},
|
|
1267
|
+
{
|
|
1268
|
+
name: "voip-plan set",
|
|
1269
|
+
tool: "modify_subscriber_voip_plan",
|
|
1270
|
+
summary: "Change the VoIP pricing plan assigned to a subscriber.",
|
|
1271
|
+
options: [
|
|
1272
|
+
ICCID,
|
|
1273
|
+
{
|
|
1274
|
+
flags: "--plan-id <id>",
|
|
1275
|
+
description: "VoIP pricing plan ID",
|
|
1276
|
+
arg: "voip_plan_id",
|
|
1277
|
+
type: "number",
|
|
1278
|
+
required: true
|
|
1279
|
+
}
|
|
1280
|
+
],
|
|
1281
|
+
write: true
|
|
1282
|
+
},
|
|
1283
|
+
{
|
|
1284
|
+
name: "traffic set",
|
|
1285
|
+
tool: "set_subscriber_traffic_restrictions",
|
|
1286
|
+
summary: "Enable or disable data, calls and SMS per traffic type. Omit a flag to leave it unchanged.",
|
|
1287
|
+
options: [
|
|
1288
|
+
ICCID,
|
|
1289
|
+
{ flags: "--data <bool>", description: "Allow data traffic (true or false)", arg: "dataAllowed", type: "boolean" },
|
|
1290
|
+
{ flags: "--moc <bool>", description: "Allow outbound calls (true or false)", arg: "mocAllowed", type: "boolean" },
|
|
1291
|
+
{ flags: "--mtc <bool>", description: "Allow inbound calls (true or false)", arg: "mtcAllowed", type: "boolean" },
|
|
1292
|
+
{ flags: "--sms <bool>", description: "Allow outbound SMS (true or false)", arg: "smsMoAllowed", type: "boolean" }
|
|
1293
|
+
],
|
|
1294
|
+
write: true
|
|
1295
|
+
},
|
|
1296
|
+
{
|
|
1297
|
+
name: "bitrate show",
|
|
1298
|
+
tool: "hlr_get_bitrate",
|
|
1299
|
+
summary: "Current HLR-level bandwidth cap for a subscriber.",
|
|
1300
|
+
options: [ICCID]
|
|
1301
|
+
},
|
|
1302
|
+
{
|
|
1303
|
+
name: "bitrate set",
|
|
1304
|
+
tool: "hlr_set_bitrate",
|
|
1305
|
+
summary: "Set an HLR-level bandwidth cap. Pass a numeric bps value or an OCS level.",
|
|
1306
|
+
options: [
|
|
1307
|
+
ICCID,
|
|
1308
|
+
{
|
|
1309
|
+
flags: "--bitrate <bps>",
|
|
1310
|
+
description: "Max bitrate in bits per second (0 removes the limit)",
|
|
1311
|
+
arg: "bitrate",
|
|
1312
|
+
type: "number"
|
|
1313
|
+
},
|
|
1314
|
+
{
|
|
1315
|
+
flags: "--bitrate-string <level>",
|
|
1316
|
+
description: "OCS bitrate level, e.g. KB_256, KB_1024, UNLIMITED (instead of --bitrate)",
|
|
1317
|
+
arg: "bitrate_string",
|
|
1318
|
+
type: "string"
|
|
1319
|
+
}
|
|
1320
|
+
],
|
|
1321
|
+
write: true,
|
|
1322
|
+
requireOneOf: ["bitrate", "bitrate_string"]
|
|
1323
|
+
},
|
|
1324
|
+
{
|
|
1325
|
+
name: "greenzone reset",
|
|
1326
|
+
tool: "reset_subscriber_gz_counter",
|
|
1327
|
+
summary: "Reset the Green Zone byte counter to zero. Irreversible.",
|
|
1328
|
+
options: [ICCID],
|
|
1329
|
+
write: true
|
|
1330
|
+
},
|
|
1331
|
+
{
|
|
1332
|
+
name: "cell-location",
|
|
1333
|
+
tool: "get_subscriber_location_by_cell_id",
|
|
1334
|
+
summary: "Resolve a raw cell tower tuple to coordinates (Bridge4IP GeoSense).",
|
|
1335
|
+
options: [
|
|
1336
|
+
{
|
|
1337
|
+
flags: "--radio-type <type>",
|
|
1338
|
+
description: "Radio type: 2G, 3G, 4G, 5G or NB-IoT",
|
|
1339
|
+
arg: "radio_type",
|
|
1340
|
+
type: "string",
|
|
1341
|
+
required: true
|
|
1342
|
+
},
|
|
1343
|
+
{ flags: "--mcc <mcc>", description: "Mobile Country Code", arg: "mcc", type: "number", required: true },
|
|
1344
|
+
{ flags: "--mnc <mnc>", description: "Mobile Network Code", arg: "mnc", type: "number", required: true },
|
|
1345
|
+
{ flags: "--lac <lac>", description: "Location Area Code", arg: "lac", type: "number", required: true },
|
|
1346
|
+
{ flags: "--cell-id <id>", description: "Cell tower ID (strongly recommended)", arg: "cell_id", type: "number" },
|
|
1347
|
+
{
|
|
1348
|
+
flags: "--signal-strength <dbm>",
|
|
1349
|
+
description: "Signal strength in dBm, e.g. -89 (improves accuracy)",
|
|
1350
|
+
arg: "signal_strength",
|
|
1351
|
+
type: "number"
|
|
1352
|
+
}
|
|
1353
|
+
]
|
|
1354
|
+
}
|
|
1355
|
+
]
|
|
1356
|
+
},
|
|
1357
|
+
{
|
|
1358
|
+
name: "packages",
|
|
1359
|
+
summary: "Packages assigned to a subscriber.",
|
|
1360
|
+
commands: [
|
|
1361
|
+
{
|
|
1362
|
+
name: "list",
|
|
1363
|
+
tool: "list_subscriber_packages",
|
|
1364
|
+
summary: "Packages on one subscriber, with allowance and expiry.",
|
|
1365
|
+
options: [ICCID]
|
|
1366
|
+
},
|
|
1367
|
+
{
|
|
1368
|
+
name: "assign",
|
|
1369
|
+
tool: "assign_package",
|
|
1370
|
+
summary: "Assign a one-time package from a template.",
|
|
1371
|
+
options: [
|
|
1372
|
+
{ ...ICCID, required: false },
|
|
1373
|
+
{
|
|
1374
|
+
flags: "--template-id <id>",
|
|
1375
|
+
description: "Package template ID from `carrier templates list`",
|
|
1376
|
+
arg: "packageTemplateId",
|
|
1377
|
+
type: "number",
|
|
1378
|
+
required: true
|
|
1379
|
+
},
|
|
1380
|
+
{
|
|
1381
|
+
flags: "--account-id <id>",
|
|
1382
|
+
description: "Auto-pick a free eSIM from this account instead of using --iccid",
|
|
1383
|
+
arg: "account_for_subs",
|
|
1384
|
+
type: "number"
|
|
1385
|
+
}
|
|
1386
|
+
],
|
|
1387
|
+
write: true,
|
|
1388
|
+
requireOneOf: ["iccid", "account_for_subs"]
|
|
1389
|
+
},
|
|
1390
|
+
{
|
|
1391
|
+
name: "set-status",
|
|
1392
|
+
tool: "modify_package_status",
|
|
1393
|
+
summary: "Activate or deactivate one package without deleting it.",
|
|
1394
|
+
options: [
|
|
1395
|
+
ICCID,
|
|
1396
|
+
PACKAGE_ID,
|
|
1397
|
+
{
|
|
1398
|
+
flags: "--status <status>",
|
|
1399
|
+
description: "New package status, e.g. ACTIVE or INACTIVE",
|
|
1400
|
+
arg: "status",
|
|
1401
|
+
type: "string",
|
|
1402
|
+
required: true
|
|
1403
|
+
}
|
|
1404
|
+
],
|
|
1405
|
+
write: true
|
|
1406
|
+
},
|
|
1407
|
+
{
|
|
1408
|
+
name: "set-expiry",
|
|
1409
|
+
tool: "modify_package_expiry",
|
|
1410
|
+
summary: "Move a package's expiry date. Pass an absolute date or a day count.",
|
|
1411
|
+
options: [
|
|
1412
|
+
ICCID,
|
|
1413
|
+
PACKAGE_ID,
|
|
1414
|
+
{
|
|
1415
|
+
flags: "--expires <date>",
|
|
1416
|
+
description: "Absolute expiry, ISO 8601 (e.g. 2026-06-01)",
|
|
1417
|
+
arg: "expirationDate",
|
|
1418
|
+
type: "string"
|
|
1419
|
+
},
|
|
1420
|
+
{
|
|
1421
|
+
flags: "--validity-days <n>",
|
|
1422
|
+
description: "Days from now until expiry (instead of --expires)",
|
|
1423
|
+
arg: "validity_days",
|
|
1424
|
+
type: "number"
|
|
1425
|
+
}
|
|
1426
|
+
],
|
|
1427
|
+
write: true
|
|
1428
|
+
},
|
|
1429
|
+
{
|
|
1430
|
+
name: "set-limits",
|
|
1431
|
+
tool: "modify_package_limits",
|
|
1432
|
+
summary: "Change data, voice or SMS ceilings on an assigned package.",
|
|
1433
|
+
options: [
|
|
1434
|
+
ICCID,
|
|
1435
|
+
PACKAGE_ID,
|
|
1436
|
+
{
|
|
1437
|
+
flags: "--limits <json>",
|
|
1438
|
+
description: `New limits as JSON, e.g. '{"dataLimit":5368709120}'`,
|
|
1439
|
+
arg: "limits",
|
|
1440
|
+
type: "string",
|
|
1441
|
+
required: true
|
|
1442
|
+
}
|
|
1443
|
+
],
|
|
1444
|
+
write: true
|
|
1445
|
+
},
|
|
1446
|
+
{
|
|
1447
|
+
name: "assign-recurring",
|
|
1448
|
+
tool: "assign_recurring_package",
|
|
1449
|
+
summary: "Assign an auto-renewing package from a recurring-enabled template.",
|
|
1450
|
+
options: [
|
|
1451
|
+
ICCID,
|
|
1452
|
+
{
|
|
1453
|
+
flags: "--template-id <id>",
|
|
1454
|
+
description: "Recurring package template ID from `carrier templates list`",
|
|
1455
|
+
arg: "packageTemplateId",
|
|
1456
|
+
type: "number",
|
|
1457
|
+
required: true
|
|
1458
|
+
},
|
|
1459
|
+
{
|
|
1460
|
+
flags: "--activate-at-first-use",
|
|
1461
|
+
description: "Activate on first network usage (instead of --start-time)",
|
|
1462
|
+
arg: "activation_at_first_use",
|
|
1463
|
+
type: "boolean"
|
|
1464
|
+
},
|
|
1465
|
+
{
|
|
1466
|
+
flags: "--start-time <iso>",
|
|
1467
|
+
description: "Scheduled activation, ISO 8601 UTC (instead of --activate-at-first-use)",
|
|
1468
|
+
arg: "start_time_utc",
|
|
1469
|
+
type: "string"
|
|
1470
|
+
}
|
|
1471
|
+
],
|
|
1472
|
+
write: true
|
|
1473
|
+
},
|
|
1474
|
+
{
|
|
1475
|
+
name: "recurring set",
|
|
1476
|
+
tool: "stop_resume_recurring_package",
|
|
1477
|
+
summary: "Pause or restart the auto-renewal of a recurring package.",
|
|
1478
|
+
options: [
|
|
1479
|
+
ICCID,
|
|
1480
|
+
PACKAGE_ID,
|
|
1481
|
+
{
|
|
1482
|
+
flags: "--action <action>",
|
|
1483
|
+
description: "'stop' halts future renewals, 'resume' re-enables them",
|
|
1484
|
+
arg: "action",
|
|
1485
|
+
type: "string",
|
|
1486
|
+
required: true
|
|
1487
|
+
}
|
|
1488
|
+
],
|
|
1489
|
+
write: true
|
|
1490
|
+
},
|
|
1491
|
+
{
|
|
1492
|
+
name: "active-period set",
|
|
1493
|
+
tool: "modify_subscriber_package_active_period",
|
|
1494
|
+
summary: "Move the start and/or end of a package's active period.",
|
|
1495
|
+
options: [
|
|
1496
|
+
ICCID,
|
|
1497
|
+
{
|
|
1498
|
+
flags: "--package-id <id>",
|
|
1499
|
+
description: "Package ID from `carrier packages list`",
|
|
1500
|
+
arg: "package_id",
|
|
1501
|
+
type: "number",
|
|
1502
|
+
required: true
|
|
1503
|
+
},
|
|
1504
|
+
{
|
|
1505
|
+
flags: "--start <date>",
|
|
1506
|
+
description: "New start date, ISO 8601 (e.g. 2026-09-01)",
|
|
1507
|
+
arg: "start_date",
|
|
1508
|
+
type: "string"
|
|
1509
|
+
},
|
|
1510
|
+
{
|
|
1511
|
+
flags: "--end <date>",
|
|
1512
|
+
description: "New end date, ISO 8601 (e.g. 2026-09-30)",
|
|
1513
|
+
arg: "end_date",
|
|
1514
|
+
type: "string"
|
|
1515
|
+
}
|
|
1516
|
+
],
|
|
1517
|
+
write: true,
|
|
1518
|
+
requireOneOf: ["start_date", "end_date"]
|
|
1519
|
+
},
|
|
1520
|
+
{
|
|
1521
|
+
name: "clean",
|
|
1522
|
+
tool: "clean_all_packages",
|
|
1523
|
+
summary: "Remove ALL packages from a subscriber. Irreversible.",
|
|
1524
|
+
options: [ICCID],
|
|
1525
|
+
write: true
|
|
1526
|
+
},
|
|
1527
|
+
{
|
|
1528
|
+
name: "delete",
|
|
1529
|
+
tool: "delete_subscriber_package",
|
|
1530
|
+
summary: "Permanently remove one package from a subscriber.",
|
|
1531
|
+
options: [ICCID, PACKAGE_ID],
|
|
1532
|
+
write: true
|
|
1533
|
+
}
|
|
1534
|
+
]
|
|
1535
|
+
},
|
|
1536
|
+
{
|
|
1537
|
+
name: "templates",
|
|
1538
|
+
summary: "The package template catalog.",
|
|
1539
|
+
commands: [
|
|
1540
|
+
{
|
|
1541
|
+
name: "list",
|
|
1542
|
+
tool: "list_package_templates",
|
|
1543
|
+
summary: "Browse package templates available for assignment.",
|
|
1544
|
+
options: [ACCOUNT_ID("Filter templates visible to one account")]
|
|
1545
|
+
},
|
|
1546
|
+
{
|
|
1547
|
+
name: "create",
|
|
1548
|
+
tool: "create_package_template",
|
|
1549
|
+
summary: "Create a package template from a JSON configuration.",
|
|
1550
|
+
options: [
|
|
1551
|
+
{
|
|
1552
|
+
flags: "--template <json>",
|
|
1553
|
+
description: `Template config as JSON, e.g. '{"name":"Europe 5GB","dataLimit":5368709120}'`,
|
|
1554
|
+
arg: "template",
|
|
1555
|
+
type: "string",
|
|
1556
|
+
required: true
|
|
1557
|
+
}
|
|
1558
|
+
],
|
|
1559
|
+
write: true
|
|
1560
|
+
},
|
|
1561
|
+
{
|
|
1562
|
+
name: "set-core",
|
|
1563
|
+
tool: "modify_template_core",
|
|
1564
|
+
summary: "Change name, limits, price, validity or zone on a template.",
|
|
1565
|
+
options: [
|
|
1566
|
+
TEMPLATE_ID,
|
|
1567
|
+
{
|
|
1568
|
+
flags: "--changes <json>",
|
|
1569
|
+
description: `Core fields as JSON, e.g. '{"name":"Europe 5GB"}'`,
|
|
1570
|
+
arg: "changes",
|
|
1571
|
+
type: "string",
|
|
1572
|
+
required: true
|
|
1573
|
+
}
|
|
1574
|
+
],
|
|
1575
|
+
write: true
|
|
1576
|
+
},
|
|
1577
|
+
{
|
|
1578
|
+
name: "set-recurring",
|
|
1579
|
+
tool: "modify_template_recurring",
|
|
1580
|
+
summary: "Change auto-renewal settings on a template.",
|
|
1581
|
+
options: [
|
|
1582
|
+
TEMPLATE_ID,
|
|
1583
|
+
{
|
|
1584
|
+
flags: "--changes <json>",
|
|
1585
|
+
description: `Recurring fields as JSON, e.g. '{"periodicity":"monthly"}'`,
|
|
1586
|
+
arg: "changes",
|
|
1587
|
+
type: "string",
|
|
1588
|
+
required: true
|
|
1589
|
+
}
|
|
1590
|
+
],
|
|
1591
|
+
write: true
|
|
1592
|
+
},
|
|
1593
|
+
{
|
|
1594
|
+
name: "set-throttling",
|
|
1595
|
+
tool: "modify_template_throttling",
|
|
1596
|
+
summary: "Change throttling thresholds. Applies to existing packages too.",
|
|
1597
|
+
options: [
|
|
1598
|
+
TEMPLATE_ID,
|
|
1599
|
+
{
|
|
1600
|
+
flags: "--changes <json>",
|
|
1601
|
+
description: `Throttling fields as JSON, e.g. '{"throttlingActive":true}'`,
|
|
1602
|
+
arg: "changes",
|
|
1603
|
+
type: "string",
|
|
1604
|
+
required: true
|
|
1605
|
+
}
|
|
1606
|
+
],
|
|
1607
|
+
write: true
|
|
1608
|
+
}
|
|
1609
|
+
]
|
|
1610
|
+
},
|
|
1611
|
+
{
|
|
1612
|
+
name: "zones",
|
|
1613
|
+
summary: "Location zones and destination lists.",
|
|
1614
|
+
commands: [
|
|
1615
|
+
{
|
|
1616
|
+
name: "list",
|
|
1617
|
+
tool: "list_detailed_location_zones",
|
|
1618
|
+
summary: "Location zones with countries and operators. Prefer this over `elements`.",
|
|
1619
|
+
options: [RESELLER_ID]
|
|
1620
|
+
},
|
|
1621
|
+
{
|
|
1622
|
+
name: "elements",
|
|
1623
|
+
tool: "list_location_zones",
|
|
1624
|
+
summary: "Raw zone elements. Upstream OCS returns malformed rows for some zones.",
|
|
1625
|
+
options: [
|
|
1626
|
+
{
|
|
1627
|
+
flags: "--zone-id <id>",
|
|
1628
|
+
description: "Filter to one location zone",
|
|
1629
|
+
arg: "locationZoneId",
|
|
1630
|
+
type: "number"
|
|
1631
|
+
}
|
|
1632
|
+
]
|
|
1633
|
+
},
|
|
1634
|
+
{
|
|
1635
|
+
name: "create",
|
|
1636
|
+
tool: "create_location_zone",
|
|
1637
|
+
summary: "Create a location zone from a JSON configuration.",
|
|
1638
|
+
options: [
|
|
1639
|
+
{
|
|
1640
|
+
flags: "--zone <json>",
|
|
1641
|
+
description: `Zone config as JSON, e.g. '{"name":"Europe","countries":["NL","DE"]}'`,
|
|
1642
|
+
arg: "zone",
|
|
1643
|
+
type: "string",
|
|
1644
|
+
required: true
|
|
1645
|
+
}
|
|
1646
|
+
],
|
|
1647
|
+
write: true
|
|
1648
|
+
},
|
|
1649
|
+
{
|
|
1650
|
+
name: "network-profile set",
|
|
1651
|
+
tool: "change_network_profile_of_location_zone",
|
|
1652
|
+
summary: "Attach or change the network profile on an existing location zone.",
|
|
1653
|
+
options: [
|
|
1654
|
+
{
|
|
1655
|
+
flags: "--zone-id <id>",
|
|
1656
|
+
description: "Location zone ID from `carrier zones list`",
|
|
1657
|
+
arg: "location_zone_id",
|
|
1658
|
+
type: "number",
|
|
1659
|
+
required: true
|
|
1660
|
+
},
|
|
1661
|
+
{
|
|
1662
|
+
flags: "--network-profile-id <id>",
|
|
1663
|
+
description: "Network profile ID from `carrier network-profiles list`",
|
|
1664
|
+
arg: "network_profile_id",
|
|
1665
|
+
type: "number",
|
|
1666
|
+
required: true
|
|
1667
|
+
}
|
|
1668
|
+
],
|
|
1669
|
+
write: true
|
|
1670
|
+
},
|
|
1671
|
+
{
|
|
1672
|
+
name: "destinations",
|
|
1673
|
+
tool: "list_destination_lists",
|
|
1674
|
+
summary: "Destination list catalog for voice and SMS packages.",
|
|
1675
|
+
options: [RESELLER_ID]
|
|
1676
|
+
},
|
|
1677
|
+
{
|
|
1678
|
+
name: "prefixes",
|
|
1679
|
+
tool: "list_destination_prefixes",
|
|
1680
|
+
summary: "Dialling prefixes inside one destination list.",
|
|
1681
|
+
options: [
|
|
1682
|
+
{
|
|
1683
|
+
flags: "--destination-list-id <id>",
|
|
1684
|
+
description: "Destination list ID from `carrier zones destinations`",
|
|
1685
|
+
arg: "destinationListId",
|
|
1686
|
+
type: "number"
|
|
1687
|
+
}
|
|
1688
|
+
]
|
|
1689
|
+
}
|
|
1690
|
+
]
|
|
1691
|
+
},
|
|
1692
|
+
{
|
|
1693
|
+
name: "steering",
|
|
1694
|
+
summary: "Network steering lists and operator preference.",
|
|
1695
|
+
commands: [
|
|
1696
|
+
{
|
|
1697
|
+
name: "list",
|
|
1698
|
+
tool: "list_steering_lists",
|
|
1699
|
+
summary: "Steering lists configured for this reseller.",
|
|
1700
|
+
options: [RESELLER_ID]
|
|
1701
|
+
},
|
|
1702
|
+
{
|
|
1703
|
+
name: "assign",
|
|
1704
|
+
tool: "modify_subscriber_steering_list",
|
|
1705
|
+
summary: "Assign a steering list to a subscriber. Follow with `push`.",
|
|
1706
|
+
options: [
|
|
1707
|
+
ICCID,
|
|
1708
|
+
{
|
|
1709
|
+
flags: "--list-id <id>",
|
|
1710
|
+
description: "Steering list ID from `carrier steering list`",
|
|
1711
|
+
arg: "steeringListId",
|
|
1712
|
+
type: "number",
|
|
1713
|
+
required: true
|
|
1714
|
+
}
|
|
1715
|
+
],
|
|
1716
|
+
write: true
|
|
1717
|
+
},
|
|
1718
|
+
{
|
|
1719
|
+
name: "push",
|
|
1720
|
+
tool: "push_steering_to_subscriber",
|
|
1721
|
+
summary: "Push the assigned operator preference list to the device now.",
|
|
1722
|
+
options: [ICCID],
|
|
1723
|
+
write: true
|
|
1724
|
+
}
|
|
1725
|
+
]
|
|
1726
|
+
},
|
|
1727
|
+
{
|
|
1728
|
+
name: "accounts",
|
|
1729
|
+
summary: "Reseller accounts: balances and subscriber moves.",
|
|
1730
|
+
commands: [
|
|
1731
|
+
{
|
|
1732
|
+
name: "list",
|
|
1733
|
+
tool: "list_reseller_accounts",
|
|
1734
|
+
summary: "Accounts under a reseller with name, balance and type.",
|
|
1735
|
+
options: [RESELLER_ID]
|
|
1736
|
+
},
|
|
1737
|
+
{
|
|
1738
|
+
name: "balance set",
|
|
1739
|
+
tool: "modify_account_balance",
|
|
1740
|
+
summary: "Add to or replace an account's monetary balance.",
|
|
1741
|
+
options: [
|
|
1742
|
+
{ ...ACCOUNT_ID("Account ID from `carrier accounts list`"), required: true },
|
|
1743
|
+
{
|
|
1744
|
+
flags: "--amount <amount>",
|
|
1745
|
+
description: "Amount to add (adapt) or set to (set)",
|
|
1746
|
+
arg: "amount",
|
|
1747
|
+
type: "number",
|
|
1748
|
+
required: true
|
|
1749
|
+
},
|
|
1750
|
+
{
|
|
1751
|
+
flags: "--mode <mode>",
|
|
1752
|
+
description: "'adapt' adds or subtracts, 'set' replaces",
|
|
1753
|
+
arg: "mode",
|
|
1754
|
+
type: "string",
|
|
1755
|
+
required: true
|
|
1756
|
+
}
|
|
1757
|
+
],
|
|
1758
|
+
write: true
|
|
1759
|
+
},
|
|
1760
|
+
{
|
|
1761
|
+
name: "move-subscribers",
|
|
1762
|
+
tool: "move_subscriber_range_to_account",
|
|
1763
|
+
summary: "Move a contiguous ICCID range of subscribers to another account.",
|
|
1764
|
+
options: [
|
|
1765
|
+
{
|
|
1766
|
+
flags: "--iccid-from <iccid>",
|
|
1767
|
+
description: "Start ICCID of the range (inclusive)",
|
|
1768
|
+
arg: "iccidFrom",
|
|
1769
|
+
type: "string",
|
|
1770
|
+
required: true
|
|
1771
|
+
},
|
|
1772
|
+
{
|
|
1773
|
+
flags: "--iccid-to <iccid>",
|
|
1774
|
+
description: "End ICCID of the range (inclusive)",
|
|
1775
|
+
arg: "iccidTo",
|
|
1776
|
+
type: "string",
|
|
1777
|
+
required: true
|
|
1778
|
+
},
|
|
1779
|
+
{ ...ACCOUNT_ID("Target account ID from `carrier accounts list`"), required: true }
|
|
1780
|
+
],
|
|
1781
|
+
write: true
|
|
1782
|
+
}
|
|
1783
|
+
]
|
|
1784
|
+
},
|
|
1785
|
+
{
|
|
1786
|
+
name: "reseller",
|
|
1787
|
+
summary: "The reseller the token belongs to.",
|
|
1788
|
+
commands: [
|
|
1789
|
+
{
|
|
1790
|
+
name: "show",
|
|
1791
|
+
tool: "get_reseller_info",
|
|
1792
|
+
summary: "Full reseller record: balance, pricing plans, contact and traffic config.",
|
|
1793
|
+
options: [RESELLER_ID]
|
|
1794
|
+
}
|
|
1795
|
+
]
|
|
1796
|
+
},
|
|
1797
|
+
{
|
|
1798
|
+
name: "tariff",
|
|
1799
|
+
summary: "Wholesale tariffs: mobile and VoIP rates.",
|
|
1800
|
+
commands: [
|
|
1801
|
+
{
|
|
1802
|
+
name: "show",
|
|
1803
|
+
tool: "get_tariff",
|
|
1804
|
+
summary: "Per-country wholesale rates for data, voice and SMS.",
|
|
1805
|
+
options: [RESELLER_ID]
|
|
1806
|
+
},
|
|
1807
|
+
{
|
|
1808
|
+
name: "voip",
|
|
1809
|
+
tool: "list_subscriber_voip_tariff",
|
|
1810
|
+
summary: "VoIP tariff catalog for a reseller.",
|
|
1811
|
+
options: [
|
|
1812
|
+
{
|
|
1813
|
+
flags: "--reseller-id <id>",
|
|
1814
|
+
description: "Reseller ID (omit for the token owner's reseller)",
|
|
1815
|
+
arg: "reseller_id",
|
|
1816
|
+
type: "number"
|
|
1817
|
+
}
|
|
1818
|
+
]
|
|
1819
|
+
},
|
|
1820
|
+
{
|
|
1821
|
+
name: "voip-rules",
|
|
1822
|
+
tool: "list_voip_tariff_rule",
|
|
1823
|
+
summary: "Rate rules inside one VoIP plan.",
|
|
1824
|
+
options: [
|
|
1825
|
+
{
|
|
1826
|
+
flags: "--voip-plan-id <id>",
|
|
1827
|
+
description: "VoIP plan ID from `carrier tariff voip`",
|
|
1828
|
+
arg: "voip_plan_id",
|
|
1829
|
+
type: "number",
|
|
1830
|
+
required: true
|
|
1831
|
+
}
|
|
1832
|
+
]
|
|
1833
|
+
}
|
|
1834
|
+
]
|
|
1835
|
+
},
|
|
1836
|
+
{
|
|
1837
|
+
name: "sms",
|
|
1838
|
+
summary: "Mobile-terminated SMS to subscribers.",
|
|
1839
|
+
commands: [
|
|
1840
|
+
{
|
|
1841
|
+
name: "send",
|
|
1842
|
+
tool: "send_sms",
|
|
1843
|
+
summary: "Send an MT SMS to one subscriber.",
|
|
1844
|
+
options: [
|
|
1845
|
+
ICCID,
|
|
1846
|
+
{
|
|
1847
|
+
flags: "--msisdn <msisdn>",
|
|
1848
|
+
description: "Target MSISDN in E.164 format",
|
|
1849
|
+
arg: "msisdn",
|
|
1850
|
+
type: "string",
|
|
1851
|
+
required: true
|
|
1852
|
+
},
|
|
1853
|
+
{
|
|
1854
|
+
flags: "--message <text>",
|
|
1855
|
+
description: "SMS text (160 chars GSM-7; 70 with emoji or non-Latin scripts)",
|
|
1856
|
+
arg: "message",
|
|
1857
|
+
type: "string",
|
|
1858
|
+
required: true
|
|
1859
|
+
},
|
|
1860
|
+
{
|
|
1861
|
+
flags: "--sender <sender>",
|
|
1862
|
+
description: "Sender ID or number shown on the device",
|
|
1863
|
+
arg: "sender",
|
|
1864
|
+
type: "string"
|
|
1865
|
+
}
|
|
1866
|
+
],
|
|
1867
|
+
write: true
|
|
1868
|
+
}
|
|
1869
|
+
]
|
|
1870
|
+
},
|
|
1871
|
+
{
|
|
1872
|
+
name: "sponsors",
|
|
1873
|
+
summary: "Sponsor networks backing the eSIM profiles.",
|
|
1874
|
+
commands: [
|
|
1875
|
+
{
|
|
1876
|
+
name: "list",
|
|
1877
|
+
tool: "list_sponsors",
|
|
1878
|
+
summary: "Sponsor carriers available to this reseller.",
|
|
1879
|
+
options: [RESELLER_ID]
|
|
1880
|
+
}
|
|
1881
|
+
]
|
|
1882
|
+
},
|
|
1883
|
+
{
|
|
1884
|
+
name: "network-profiles",
|
|
1885
|
+
summary: "Roaming configuration profiles for zones and provisioning.",
|
|
1886
|
+
commands: [
|
|
1887
|
+
{
|
|
1888
|
+
name: "list",
|
|
1889
|
+
tool: "list_network_profiles",
|
|
1890
|
+
summary: "Network profiles available to this reseller.",
|
|
1891
|
+
options: []
|
|
1892
|
+
}
|
|
1893
|
+
]
|
|
1894
|
+
},
|
|
1895
|
+
{
|
|
1896
|
+
name: "webhooks",
|
|
1897
|
+
summary: "Bridge4IP webhook and relay flag status.",
|
|
1898
|
+
commands: [
|
|
1899
|
+
{
|
|
1900
|
+
name: "show",
|
|
1901
|
+
tool: "carrier_webhook_config",
|
|
1902
|
+
summary: "Current relay flags (LU, Gy, VoIP, calls+SMS) and notification webhooks.",
|
|
1903
|
+
options: [
|
|
1904
|
+
{
|
|
1905
|
+
flags: "--reseller-id <id>",
|
|
1906
|
+
description: "Reseller ID (omit for the token owner's reseller)",
|
|
1907
|
+
arg: "reseller_id",
|
|
1908
|
+
type: "number"
|
|
1909
|
+
}
|
|
1910
|
+
]
|
|
1911
|
+
}
|
|
1912
|
+
]
|
|
1913
|
+
},
|
|
1914
|
+
{
|
|
1915
|
+
name: "intelligence",
|
|
1916
|
+
summary: "Composite reports over the fleet.",
|
|
1917
|
+
commands: [
|
|
1918
|
+
{
|
|
1919
|
+
name: "fleet-health",
|
|
1920
|
+
tool: "fleet_health",
|
|
1921
|
+
summary: "eSIM counts, low-balance accounts and what needs attention.",
|
|
1922
|
+
options: [ACCOUNT_ID("Report on a single account")]
|
|
1923
|
+
},
|
|
1924
|
+
{
|
|
1925
|
+
name: "diagnose",
|
|
1926
|
+
tool: "diagnose_subscriber",
|
|
1927
|
+
summary: "Why one subscriber is offline, throttled or failing to attach.",
|
|
1928
|
+
options: [ICCID]
|
|
1929
|
+
},
|
|
1930
|
+
{
|
|
1931
|
+
name: "anomalies",
|
|
1932
|
+
tool: "detect_usage_anomalies",
|
|
1933
|
+
summary: "Usage spikes and burn rates that exhaust the package early.",
|
|
1934
|
+
options: [ICCID]
|
|
1935
|
+
},
|
|
1936
|
+
{
|
|
1937
|
+
name: "churn",
|
|
1938
|
+
tool: "churn_risk",
|
|
1939
|
+
summary: "Churn risk score with contributing factors.",
|
|
1940
|
+
options: [ICCID]
|
|
1941
|
+
},
|
|
1942
|
+
{
|
|
1943
|
+
name: "optimize",
|
|
1944
|
+
tool: "optimize_package",
|
|
1945
|
+
summary: "Better-fitting package for one subscriber, with the saving.",
|
|
1946
|
+
options: [ICCID]
|
|
1947
|
+
},
|
|
1948
|
+
{
|
|
1949
|
+
name: "coverage",
|
|
1950
|
+
tool: "audit_network_coverage",
|
|
1951
|
+
summary: "Networks subscribers actually attach to, against your steering lists.",
|
|
1952
|
+
options: [
|
|
1953
|
+
ACCOUNT_ID("Report on a single account"),
|
|
1954
|
+
{ flags: "--limit <n>", description: "Subscribers to sample (default 50)", arg: "limit", type: "number" }
|
|
1955
|
+
]
|
|
1956
|
+
},
|
|
1957
|
+
{
|
|
1958
|
+
name: "high-cost",
|
|
1959
|
+
tool: "high_cost_subscribers",
|
|
1960
|
+
summary: "Subscribers whose consumption erodes the margin.",
|
|
1961
|
+
options: [
|
|
1962
|
+
ACCOUNT_ID("Report on a single account"),
|
|
1963
|
+
{ flags: "--limit <n>", description: "Subscribers to analyse (default 100)", arg: "limit", type: "number" },
|
|
1964
|
+
{
|
|
1965
|
+
flags: "--threshold-pct <n>",
|
|
1966
|
+
description: "Usage percentage that flags a subscriber (default 80)",
|
|
1967
|
+
arg: "thresholdPct",
|
|
1968
|
+
type: "number"
|
|
1969
|
+
}
|
|
1970
|
+
]
|
|
1971
|
+
},
|
|
1972
|
+
{
|
|
1973
|
+
name: "marketing",
|
|
1974
|
+
tool: "marketing_intelligence",
|
|
1975
|
+
summary: "Where subscribers and revenue concentrate, and which markets grow.",
|
|
1976
|
+
options: [ACCOUNT_ID("Report on a single account")]
|
|
1977
|
+
},
|
|
1978
|
+
{
|
|
1979
|
+
name: "country-entry",
|
|
1980
|
+
tool: "detect_country_entry",
|
|
1981
|
+
summary: "Country a subscriber is in now, optionally diffed against the last known one.",
|
|
1982
|
+
options: [
|
|
1983
|
+
{ ...ICCID, arg: "subscriber.iccid" },
|
|
1984
|
+
{
|
|
1985
|
+
flags: "--expected-country <iso2>",
|
|
1986
|
+
description: "Last known ISO 3166-1 alpha-2 country, e.g. NL",
|
|
1987
|
+
arg: "expectedCountry",
|
|
1988
|
+
type: "string"
|
|
1989
|
+
}
|
|
1990
|
+
]
|
|
1991
|
+
}
|
|
1992
|
+
]
|
|
1993
|
+
}
|
|
1994
|
+
];
|
|
1995
|
+
|
|
1996
|
+
// src/cli/lib/capability.ts
|
|
1997
|
+
import * as p2 from "@clack/prompts";
|
|
1998
|
+
import pc3 from "picocolors";
|
|
1999
|
+
var MAX_PRETTY_CHARS = 8e3;
|
|
2000
|
+
function optionKey(flags) {
|
|
2001
|
+
const long = flags.split(/[ ,|]+/).find((t) => t.startsWith("--"));
|
|
2002
|
+
if (!long) throw new Error(`Option "${flags}" has no long flag`);
|
|
2003
|
+
return long.replace(/^--/, "").split("-").map((part, i) => i === 0 ? part : part.charAt(0).toUpperCase() + part.slice(1)).join("");
|
|
2004
|
+
}
|
|
2005
|
+
function setArg(target, path, value) {
|
|
2006
|
+
const parts = path.split(".");
|
|
2007
|
+
let node = target;
|
|
2008
|
+
for (const part of parts.slice(0, -1)) {
|
|
2009
|
+
const next = node[part];
|
|
2010
|
+
if (typeof next !== "object" || next === null) node[part] = {};
|
|
2011
|
+
node = node[part];
|
|
2012
|
+
}
|
|
2013
|
+
node[parts[parts.length - 1]] = value;
|
|
2014
|
+
}
|
|
2015
|
+
function coerce(opt, raw) {
|
|
2016
|
+
if (opt.type === "boolean") {
|
|
2017
|
+
if (raw === true) return { value: true };
|
|
2018
|
+
const text3 = String(raw).toLowerCase();
|
|
2019
|
+
if (text3 === "true") return { value: true };
|
|
2020
|
+
if (text3 === "false") return { value: false };
|
|
2021
|
+
return { error: `${opt.flags} expects true or false, got "${String(raw)}"` };
|
|
2022
|
+
}
|
|
2023
|
+
if (opt.type === "number") {
|
|
2024
|
+
const n = Number(raw);
|
|
2025
|
+
if (!Number.isFinite(n)) return { error: `${opt.flags} expects a number, got "${String(raw)}"` };
|
|
2026
|
+
return { value: n };
|
|
2027
|
+
}
|
|
2028
|
+
return { value: String(raw) };
|
|
2029
|
+
}
|
|
2030
|
+
function buildToolArgs(cmd, opts) {
|
|
2031
|
+
const args = {};
|
|
2032
|
+
const errors = [];
|
|
2033
|
+
const notes = [];
|
|
2034
|
+
for (const opt of cmd.options) {
|
|
2035
|
+
const raw = opts[optionKey(opt.flags)];
|
|
2036
|
+
if (raw === void 0 || raw === false) {
|
|
2037
|
+
if (opt.required) errors.push(`Missing required ${opt.flags}`);
|
|
2038
|
+
continue;
|
|
2039
|
+
}
|
|
2040
|
+
const { value, error } = coerce(opt, raw);
|
|
2041
|
+
if (error) errors.push(error);
|
|
2042
|
+
else setArg(args, opt.arg, value);
|
|
2043
|
+
}
|
|
2044
|
+
if (cmd.requireOneOf && !cmd.requireOneOf.some((name) => args[name] !== void 0)) {
|
|
2045
|
+
const flags = cmd.options.filter((opt) => cmd.requireOneOf.includes(opt.arg)).map((opt) => opt.flags);
|
|
2046
|
+
errors.push(`Pass one of: ${flags.join(", ")}`);
|
|
2047
|
+
}
|
|
2048
|
+
if (cmd.usageWindow) {
|
|
2049
|
+
const start = args.startDate;
|
|
2050
|
+
const end = args.endDate;
|
|
2051
|
+
if (!start && !end) {
|
|
2052
|
+
const window = lastNDaysPeriod();
|
|
2053
|
+
args.startDate = window.start;
|
|
2054
|
+
args.endDate = window.end;
|
|
2055
|
+
notes.push(`Window defaulted to ${window.start} \u2026 ${window.end} (last ${OCS_MAX_USAGE_WINDOW_DAYS} days).`);
|
|
2056
|
+
} else if (!start || !end) {
|
|
2057
|
+
errors.push("Pass both --start and --end, or neither for the last 7 days");
|
|
2058
|
+
} else {
|
|
2059
|
+
const clamped = clampUsagePeriod(start, end);
|
|
2060
|
+
if (clamped.start !== start) {
|
|
2061
|
+
notes.push(
|
|
2062
|
+
`Start moved to ${clamped.start} \u2014 OCS rejects windows wider than ${OCS_MAX_USAGE_WINDOW_DAYS} days.`
|
|
2063
|
+
);
|
|
2064
|
+
}
|
|
2065
|
+
args.startDate = clamped.start;
|
|
2066
|
+
args.endDate = clamped.end;
|
|
2067
|
+
}
|
|
2068
|
+
}
|
|
2069
|
+
if (cmd.write) args.dry_run = true;
|
|
2070
|
+
return { args, errors, notes };
|
|
2071
|
+
}
|
|
2072
|
+
async function runCapability(domain, cmd, opts) {
|
|
2073
|
+
const label = `${domain.name} ${cmd.name}`;
|
|
2074
|
+
const quiet = opts.json === true;
|
|
2075
|
+
const { args, errors, notes } = buildToolArgs(cmd, opts);
|
|
2076
|
+
if (cmd.write && opts.commit === true) delete args.dry_run;
|
|
2077
|
+
if (errors.length) {
|
|
2078
|
+
const message = withNextStep(`Cannot run \`carrier ${label}\`.`, [
|
|
2079
|
+
...errors,
|
|
2080
|
+
`See every flag: carrier ${label} --help`
|
|
2081
|
+
]);
|
|
2082
|
+
if (quiet) console.error(message);
|
|
2083
|
+
else p2.log.error(message);
|
|
2084
|
+
process.exitCode = 1;
|
|
2085
|
+
return;
|
|
2086
|
+
}
|
|
2087
|
+
const dryRun = cmd.write === true && args.dry_run === true;
|
|
2088
|
+
const warnings = dryRun ? [...notes, "Dry run \u2014 nothing changes. Re-run with --commit to apply."] : notes;
|
|
2089
|
+
for (const note4 of warnings) {
|
|
2090
|
+
if (quiet) console.error(note4);
|
|
2091
|
+
else p2.log.info(pc3.dim(note4));
|
|
2092
|
+
}
|
|
2093
|
+
const s = quiet ? null : p2.spinner();
|
|
2094
|
+
s?.start(`Calling ${cmd.tool}`);
|
|
2095
|
+
const result = await callMcpTool(cmd.tool, args, {
|
|
2096
|
+
errorHints: [
|
|
2097
|
+
`Check the arguments: carrier ${label} --help`,
|
|
2098
|
+
'Or describe the task in words: carrier ask "\u2026"'
|
|
2099
|
+
]
|
|
2100
|
+
});
|
|
2101
|
+
s?.stop(result.ok ? "Done" : "Failed");
|
|
2102
|
+
if (!result.ok) {
|
|
2103
|
+
if (quiet) console.error(result.text);
|
|
2104
|
+
else p2.log.error(result.text);
|
|
2105
|
+
process.exitCode = 1;
|
|
2106
|
+
return;
|
|
2107
|
+
}
|
|
2108
|
+
if (quiet) {
|
|
2109
|
+
process.stdout.write(`${result.text}
|
|
2110
|
+
`);
|
|
2111
|
+
return;
|
|
2112
|
+
}
|
|
2113
|
+
const body = result.text.length > MAX_PRETTY_CHARS ? `${result.text.slice(0, MAX_PRETTY_CHARS)}
|
|
2114
|
+
\u2026truncated. Re-run with --json for the whole response.` : result.text;
|
|
2115
|
+
p2.note(body, cmd.tool);
|
|
2116
|
+
}
|
|
2117
|
+
|
|
999
2118
|
// src/cli/index.ts
|
|
1000
|
-
var VERSION =
|
|
2119
|
+
var VERSION = CARRIER_VERSION;
|
|
1001
2120
|
function header() {
|
|
1002
|
-
|
|
2121
|
+
p3.intro(`${pc4.bold(pc4.yellow("\u25C6 carrier"))} ${pc4.dim("\xB7 the Stripe of telecom \u2014 CLI v" + VERSION)}`);
|
|
1003
2122
|
}
|
|
1004
2123
|
function ok(msg) {
|
|
1005
|
-
|
|
2124
|
+
p3.log.success(pc4.green(msg));
|
|
1006
2125
|
}
|
|
1007
2126
|
function info(msg) {
|
|
1008
|
-
|
|
2127
|
+
p3.log.info(msg);
|
|
1009
2128
|
}
|
|
1010
2129
|
function fail(msg, next) {
|
|
1011
|
-
|
|
1012
|
-
|
|
2130
|
+
p3.log.error(msg);
|
|
2131
|
+
p3.note(next.map((s) => `\u2192 ${s}`).join("\n"), "What to do next");
|
|
1013
2132
|
}
|
|
1014
2133
|
async function promptBrand(seed) {
|
|
1015
|
-
const name = await
|
|
2134
|
+
const name = await p3.text({
|
|
1016
2135
|
message: "Brand name",
|
|
1017
2136
|
placeholder: seed.name,
|
|
1018
2137
|
defaultValue: seed.name
|
|
1019
2138
|
});
|
|
1020
|
-
if (
|
|
1021
|
-
const domain = await
|
|
2139
|
+
if (p3.isCancel(name)) process.exit(0);
|
|
2140
|
+
const domain = await p3.text({
|
|
1022
2141
|
message: "Domain",
|
|
1023
2142
|
placeholder: seed.domain,
|
|
1024
2143
|
defaultValue: seed.domain
|
|
1025
2144
|
});
|
|
1026
|
-
if (
|
|
1027
|
-
const accent = await
|
|
2145
|
+
if (p3.isCancel(domain)) process.exit(0);
|
|
2146
|
+
const accent = await p3.text({
|
|
1028
2147
|
message: "Accent color (hex)",
|
|
1029
2148
|
placeholder: seed.colors.accent,
|
|
1030
2149
|
defaultValue: seed.colors.accent
|
|
1031
2150
|
});
|
|
1032
|
-
if (
|
|
1033
|
-
const supportEmail = await
|
|
2151
|
+
if (p3.isCancel(accent)) process.exit(0);
|
|
2152
|
+
const supportEmail = await p3.text({
|
|
1034
2153
|
message: "Support email",
|
|
1035
2154
|
placeholder: `support@${domain}`,
|
|
1036
2155
|
defaultValue: `support@${domain}`
|
|
1037
2156
|
});
|
|
1038
|
-
if (
|
|
2157
|
+
if (p3.isCancel(supportEmail)) process.exit(0);
|
|
1039
2158
|
const accentDark = deriveAccentDark(accent, seed);
|
|
1040
2159
|
const isCarrier = name === CARRIER_BRAND.name && domain === CARRIER_BRAND.domain;
|
|
1041
2160
|
return {
|
|
@@ -1052,7 +2171,7 @@ async function promptBrand(seed) {
|
|
|
1052
2171
|
};
|
|
1053
2172
|
}
|
|
1054
2173
|
async function doPluginInstall() {
|
|
1055
|
-
const s =
|
|
2174
|
+
const s = p3.spinner();
|
|
1056
2175
|
s.start("Installing Carrier Claude Code plugin + zero-cred MCP");
|
|
1057
2176
|
let r;
|
|
1058
2177
|
try {
|
|
@@ -1069,13 +2188,13 @@ async function doPluginInstall() {
|
|
|
1069
2188
|
s.stop("Plugin staged");
|
|
1070
2189
|
ok(`Plugin \u2192 ${r.copiedTo}`);
|
|
1071
2190
|
info(
|
|
1072
|
-
`MCP: ${r.mcpAdded ?
|
|
2191
|
+
`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")}`
|
|
1073
2192
|
);
|
|
1074
|
-
|
|
2193
|
+
p3.note(installAuthGuidance().join("\n"), "OAuth on first use (Entry A)");
|
|
1075
2194
|
if (!r.claudeFound || r.notes.length) {
|
|
1076
|
-
|
|
1077
|
-
if (r.notes.length) info(
|
|
1078
|
-
|
|
2195
|
+
p3.note(manualCommands().join("\n"), "Finish wiring (run in your terminal)");
|
|
2196
|
+
if (r.notes.length) info(pc4.dim(r.notes.join("\n")));
|
|
2197
|
+
p3.note(
|
|
1079
2198
|
[
|
|
1080
2199
|
"You can still create an account now:",
|
|
1081
2200
|
` carrier open signup`,
|
|
@@ -1084,7 +2203,7 @@ async function doPluginInstall() {
|
|
|
1084
2203
|
"Next"
|
|
1085
2204
|
);
|
|
1086
2205
|
} else {
|
|
1087
|
-
|
|
2206
|
+
p3.note(
|
|
1088
2207
|
[
|
|
1089
2208
|
"Open Claude Code and say something like:",
|
|
1090
2209
|
' "Show my fleet health"',
|
|
@@ -1097,7 +2216,7 @@ async function doPluginInstall() {
|
|
|
1097
2216
|
}
|
|
1098
2217
|
}
|
|
1099
2218
|
async function doSiteCreate(target, brand) {
|
|
1100
|
-
const s =
|
|
2219
|
+
const s = p3.spinner();
|
|
1101
2220
|
s.start(`Scaffolding ${brand.name} storefront \u2192 ${target}`);
|
|
1102
2221
|
try {
|
|
1103
2222
|
await scaffoldStorefront(target, brand);
|
|
@@ -1112,15 +2231,25 @@ async function doSiteCreate(target, brand) {
|
|
|
1112
2231
|
}
|
|
1113
2232
|
s.stop("Storefront scaffolded");
|
|
1114
2233
|
ok(`Created ${target} (white-labeled: ${brand.name}, accent ${brand.colors.accent})`);
|
|
2234
|
+
const logoSpin = p3.spinner();
|
|
2235
|
+
logoSpin.start("Generating storefront logo");
|
|
2236
|
+
try {
|
|
2237
|
+
const logo = await writeStorefrontLogo(target, brand);
|
|
2238
|
+
logoSpin.stop(`Logo ready (${logo.source})`);
|
|
2239
|
+
ok(logo.path);
|
|
2240
|
+
} catch (e) {
|
|
2241
|
+
logoSpin.stop("Logo skipped");
|
|
2242
|
+
info(pc4.yellow(String(e instanceof Error ? e.message : e)));
|
|
2243
|
+
}
|
|
1115
2244
|
}
|
|
1116
2245
|
async function maybeBuildDeploy(target, brand, opts) {
|
|
1117
2246
|
if (opts.install) {
|
|
1118
|
-
const s =
|
|
2247
|
+
const s = p3.spinner();
|
|
1119
2248
|
s.start("Installing storefront dependencies");
|
|
1120
2249
|
const oki = await installDeps(target);
|
|
1121
2250
|
s.stop(oki ? "Dependencies installed" : "Dependency install reported errors");
|
|
1122
2251
|
if (!oki) {
|
|
1123
|
-
|
|
2252
|
+
p3.note(
|
|
1124
2253
|
[
|
|
1125
2254
|
`cd ${target} && pnpm install`,
|
|
1126
2255
|
"Fix any Node/pnpm version issues, then retry build"
|
|
@@ -1131,12 +2260,12 @@ async function maybeBuildDeploy(target, brand, opts) {
|
|
|
1131
2260
|
}
|
|
1132
2261
|
}
|
|
1133
2262
|
if (opts.build) {
|
|
1134
|
-
const s =
|
|
2263
|
+
const s = p3.spinner();
|
|
1135
2264
|
s.start("Building storefront (next build)");
|
|
1136
2265
|
const okb = await buildSite(target);
|
|
1137
2266
|
s.stop(okb ? "Build succeeded" : "Build failed \u2014 see output above");
|
|
1138
2267
|
if (!okb) {
|
|
1139
|
-
|
|
2268
|
+
p3.note(
|
|
1140
2269
|
[`cd ${target}`, "pnpm build", "Check env keys in .env.local if the build mentions Clerk"].join("\n"),
|
|
1141
2270
|
"Next"
|
|
1142
2271
|
);
|
|
@@ -1144,13 +2273,13 @@ async function maybeBuildDeploy(target, brand, opts) {
|
|
|
1144
2273
|
}
|
|
1145
2274
|
}
|
|
1146
2275
|
if (opts.deploy) {
|
|
1147
|
-
const s =
|
|
2276
|
+
const s = p3.spinner();
|
|
1148
2277
|
s.start("Deploying to Cloudflare Workers");
|
|
1149
2278
|
const r = await deploySite(target, brand);
|
|
1150
2279
|
s.stop(r.ok ? `Deployed: ${r.projectName}` : "Deploy skipped");
|
|
1151
2280
|
if (!r.ok && r.reason) {
|
|
1152
|
-
info(
|
|
1153
|
-
|
|
2281
|
+
info(pc4.yellow(r.reason));
|
|
2282
|
+
p3.note(
|
|
1154
2283
|
[
|
|
1155
2284
|
"Install wrangler and login: npx wrangler login",
|
|
1156
2285
|
`Then: carrier site deploy ${target}`
|
|
@@ -1162,26 +2291,31 @@ async function maybeBuildDeploy(target, brand, opts) {
|
|
|
1162
2291
|
}
|
|
1163
2292
|
async function interactiveSiteCreate() {
|
|
1164
2293
|
const brand = await promptBrand(CARRIER_BRAND);
|
|
1165
|
-
const dir = await
|
|
2294
|
+
const dir = await p3.text({
|
|
1166
2295
|
message: "Output directory",
|
|
1167
2296
|
placeholder: "./storefront",
|
|
1168
2297
|
defaultValue: "./storefront"
|
|
1169
2298
|
});
|
|
1170
|
-
if (
|
|
2299
|
+
if (p3.isCancel(dir)) return;
|
|
1171
2300
|
const target = resolve2(dir);
|
|
1172
2301
|
if (await exists(target)) {
|
|
1173
|
-
const go = await
|
|
1174
|
-
if (
|
|
1175
|
-
|
|
2302
|
+
const go = await p3.confirm({ message: `${target} exists \u2014 write into it anyway?`, initialValue: false });
|
|
2303
|
+
if (p3.isCancel(go) || !go) {
|
|
2304
|
+
p3.outro("Stopped. Re-run with a different directory.");
|
|
1176
2305
|
return;
|
|
1177
2306
|
}
|
|
1178
2307
|
}
|
|
1179
2308
|
await doSiteCreate(target, brand);
|
|
1180
|
-
|
|
2309
|
+
p3.outro(pc4.green(`Scaffolded. cd ${dir} && pnpm install && pnpm dev`));
|
|
1181
2310
|
}
|
|
1182
2311
|
var program = new Command();
|
|
1183
2312
|
program.name("carrier").description(
|
|
1184
|
-
|
|
2313
|
+
[
|
|
2314
|
+
"Carrier CLI \u2014 status, OAuth-ready MCP install, storefront scaffold, and fleet control.",
|
|
2315
|
+
"",
|
|
2316
|
+
"Domains: " + CLI_DOMAINS.map((d) => d.name).join(", "),
|
|
2317
|
+
'Anything they do not cover: carrier ask "\u2026"'
|
|
2318
|
+
].join("\n")
|
|
1185
2319
|
).version(VERSION).action(async () => {
|
|
1186
2320
|
header();
|
|
1187
2321
|
await runHome({
|
|
@@ -1196,12 +2330,12 @@ program.command("init").description("Interactive home: status, install plugin+MC
|
|
|
1196
2330
|
const brand = CARRIER_BRAND;
|
|
1197
2331
|
const target = resolve2(o.dir);
|
|
1198
2332
|
if (await exists(target)) {
|
|
1199
|
-
info(
|
|
2333
|
+
info(pc4.yellow(`${target} exists \u2014 writing into it (--yes).`));
|
|
1200
2334
|
}
|
|
1201
2335
|
await doSiteCreate(target, brand);
|
|
1202
2336
|
await maybeBuildDeploy(target, brand, { install: true, build: true, deploy: false });
|
|
1203
|
-
|
|
1204
|
-
|
|
2337
|
+
p3.note(oauthFirstUseNote(), "Auth");
|
|
2338
|
+
p3.note(
|
|
1205
2339
|
[
|
|
1206
2340
|
`cd ${o.dir}`,
|
|
1207
2341
|
`Edit src/brand.config.ts to re-brand anytime`,
|
|
@@ -1211,7 +2345,7 @@ program.command("init").description("Interactive home: status, install plugin+MC
|
|
|
1211
2345
|
].join("\n"),
|
|
1212
2346
|
"Next"
|
|
1213
2347
|
);
|
|
1214
|
-
|
|
2348
|
+
p3.outro(pc4.green("Done. Your connectivity business is wired."));
|
|
1215
2349
|
return;
|
|
1216
2350
|
}
|
|
1217
2351
|
if (o.full) {
|
|
@@ -1221,14 +2355,14 @@ program.command("init").description("Interactive home: status, install plugin+MC
|
|
|
1221
2355
|
const brand = await promptBrand(CARRIER_BRAND);
|
|
1222
2356
|
const target = resolve2(o.dir);
|
|
1223
2357
|
if (await exists(target)) {
|
|
1224
|
-
const go = await
|
|
1225
|
-
if (
|
|
1226
|
-
|
|
2358
|
+
const go = await p3.confirm({ message: `${target} exists \u2014 write into it anyway?`, initialValue: false });
|
|
2359
|
+
if (p3.isCancel(go) || !go) {
|
|
2360
|
+
p3.outro("Stopped. Re-run with --dir <new path>.");
|
|
1227
2361
|
return;
|
|
1228
2362
|
}
|
|
1229
2363
|
}
|
|
1230
2364
|
await doSiteCreate(target, brand);
|
|
1231
|
-
const next = await
|
|
2365
|
+
const next = await p3.select({
|
|
1232
2366
|
message: "Roll it out now?",
|
|
1233
2367
|
options: [
|
|
1234
2368
|
{ value: "build", label: "Install deps + build" },
|
|
@@ -1237,14 +2371,14 @@ program.command("init").description("Interactive home: status, install plugin+MC
|
|
|
1237
2371
|
],
|
|
1238
2372
|
initialValue: "build"
|
|
1239
2373
|
});
|
|
1240
|
-
if (
|
|
2374
|
+
if (p3.isCancel(next)) process.exit(0);
|
|
1241
2375
|
await maybeBuildDeploy(target, brand, {
|
|
1242
2376
|
install: next !== "none",
|
|
1243
2377
|
build: next !== "none",
|
|
1244
2378
|
deploy: next === "deploy"
|
|
1245
2379
|
});
|
|
1246
|
-
|
|
1247
|
-
|
|
2380
|
+
p3.note(oauthFirstUseNote(), "Auth");
|
|
2381
|
+
p3.note(
|
|
1248
2382
|
[
|
|
1249
2383
|
`cd ${o.dir}`,
|
|
1250
2384
|
`Edit src/brand.config.ts to re-brand anytime`,
|
|
@@ -1253,7 +2387,7 @@ program.command("init").description("Interactive home: status, install plugin+MC
|
|
|
1253
2387
|
].join("\n"),
|
|
1254
2388
|
"Next"
|
|
1255
2389
|
);
|
|
1256
|
-
|
|
2390
|
+
p3.outro(pc4.green("Done. Your connectivity business is wired."));
|
|
1257
2391
|
return;
|
|
1258
2392
|
}
|
|
1259
2393
|
await runHome({
|
|
@@ -1262,14 +2396,14 @@ program.command("init").description("Interactive home: status, install plugin+MC
|
|
|
1262
2396
|
const brand = await promptBrand(CARRIER_BRAND);
|
|
1263
2397
|
const target = resolve2(o.dir);
|
|
1264
2398
|
if (await exists(target)) {
|
|
1265
|
-
const go = await
|
|
1266
|
-
if (
|
|
1267
|
-
|
|
2399
|
+
const go = await p3.confirm({ message: `${target} exists \u2014 write into it anyway?`, initialValue: false });
|
|
2400
|
+
if (p3.isCancel(go) || !go) {
|
|
2401
|
+
p3.log.info("Skipped storefront. Pick Install or Exit from the menu, or re-run with --dir.");
|
|
1268
2402
|
return;
|
|
1269
2403
|
}
|
|
1270
2404
|
}
|
|
1271
2405
|
await doSiteCreate(target, brand);
|
|
1272
|
-
const next = await
|
|
2406
|
+
const next = await p3.select({
|
|
1273
2407
|
message: "Roll it out now?",
|
|
1274
2408
|
options: [
|
|
1275
2409
|
{ value: "build", label: "Install deps + build" },
|
|
@@ -1278,13 +2412,13 @@ program.command("init").description("Interactive home: status, install plugin+MC
|
|
|
1278
2412
|
],
|
|
1279
2413
|
initialValue: "build"
|
|
1280
2414
|
});
|
|
1281
|
-
if (
|
|
2415
|
+
if (p3.isCancel(next)) return;
|
|
1282
2416
|
await maybeBuildDeploy(target, brand, {
|
|
1283
2417
|
install: next !== "none",
|
|
1284
2418
|
build: next !== "none",
|
|
1285
2419
|
deploy: next === "deploy"
|
|
1286
2420
|
});
|
|
1287
|
-
|
|
2421
|
+
p3.note(
|
|
1288
2422
|
[
|
|
1289
2423
|
`cd ${o.dir}`,
|
|
1290
2424
|
`pnpm dev`,
|
|
@@ -1299,25 +2433,25 @@ var plugin = program.command("plugin").description("Manage the Carrier Claude Co
|
|
|
1299
2433
|
plugin.command("install").description("Install/register the Carrier plugin + zero-cred MCP (OAuth on first use).").action(async () => {
|
|
1300
2434
|
header();
|
|
1301
2435
|
await doPluginInstall();
|
|
1302
|
-
|
|
2436
|
+
p3.outro(pc4.green("Plugin ready. Restart Claude Code, then talk to your fleet."));
|
|
1303
2437
|
});
|
|
1304
2438
|
plugin.command("status").description("Show plugin + MCP registration + auth next steps.").action(async () => {
|
|
1305
2439
|
header();
|
|
1306
2440
|
const st = await gatherStatus();
|
|
1307
2441
|
printStatus(st);
|
|
1308
|
-
|
|
1309
|
-
|
|
2442
|
+
p3.note(oauthFirstUseNote(), "Auth");
|
|
2443
|
+
p3.outro("");
|
|
1310
2444
|
});
|
|
1311
2445
|
program.command("status").description("Show MCP / plugin / auth status and next steps.").action(async () => {
|
|
1312
2446
|
header();
|
|
1313
|
-
const s =
|
|
2447
|
+
const s = p3.spinner();
|
|
1314
2448
|
s.start("Checking status");
|
|
1315
2449
|
const st = await gatherStatus();
|
|
1316
2450
|
s.stop("Done");
|
|
1317
|
-
|
|
1318
|
-
|
|
1319
|
-
|
|
1320
|
-
|
|
2451
|
+
p3.note(formatStatusBlock(st), "Status");
|
|
2452
|
+
p3.note(formatNextSteps(st), "Next steps");
|
|
2453
|
+
p3.note(oauthFirstUseNote(), "Auth");
|
|
2454
|
+
p3.outro("");
|
|
1321
2455
|
});
|
|
1322
2456
|
var openCmd = program.command("open").description("Open Carrier account / product URLs in your browser.");
|
|
1323
2457
|
for (const [name, url, desc] of [
|
|
@@ -1332,17 +2466,17 @@ for (const [name, url, desc] of [
|
|
|
1332
2466
|
const r = await openUrl(url);
|
|
1333
2467
|
if (r.ok) ok(r.hint);
|
|
1334
2468
|
else {
|
|
1335
|
-
|
|
1336
|
-
|
|
2469
|
+
p3.log.warn(r.hint);
|
|
2470
|
+
p3.note(url, "Open this URL");
|
|
1337
2471
|
}
|
|
1338
|
-
|
|
1339
|
-
|
|
2472
|
+
p3.note(accountLinksNote(), "Account links");
|
|
2473
|
+
p3.outro("");
|
|
1340
2474
|
});
|
|
1341
2475
|
}
|
|
1342
2476
|
program.command("examples").description("Print natural-language fleet prompts for Claude / MCP.").action(async () => {
|
|
1343
2477
|
header();
|
|
1344
|
-
|
|
1345
|
-
|
|
2478
|
+
p3.note(formatNlExamples(), "Talk to fleet \u2014 paste into Claude");
|
|
2479
|
+
p3.note(
|
|
1346
2480
|
[
|
|
1347
2481
|
"After `carrier plugin install` (or this menu \u2192 Install):",
|
|
1348
2482
|
" 1. Open Claude Code",
|
|
@@ -1353,9 +2487,21 @@ program.command("examples").description("Print natural-language fleet prompts fo
|
|
|
1353
2487
|
].join("\n"),
|
|
1354
2488
|
"How"
|
|
1355
2489
|
);
|
|
1356
|
-
|
|
2490
|
+
p3.note(
|
|
2491
|
+
[
|
|
2492
|
+
"With CARRIER_API_KEY set, every capability also has its own command:",
|
|
2493
|
+
...CLI_DOMAINS.map((d) => ` carrier ${d.name} --help`.padEnd(34) + pc4.dim(d.summary)),
|
|
2494
|
+
"",
|
|
2495
|
+
" --json raw response on stdout, ready for jq",
|
|
2496
|
+
" --commit apply a change instead of previewing it"
|
|
2497
|
+
].join("\n"),
|
|
2498
|
+
"Domain commands"
|
|
2499
|
+
);
|
|
2500
|
+
p3.outro("");
|
|
1357
2501
|
});
|
|
1358
|
-
program.command("ask").description(
|
|
2502
|
+
program.command("ask").description(
|
|
2503
|
+
'Natural-language fallback for whatever the domain commands miss: carrier ask "show fleet health" (needs CARRIER_API_KEY or OCS token).'
|
|
2504
|
+
).argument("<intent...>", "Natural-language intent").action(async (parts) => {
|
|
1359
2505
|
header();
|
|
1360
2506
|
const intent = parts.join(" ").trim();
|
|
1361
2507
|
if (!intent) {
|
|
@@ -1366,19 +2512,46 @@ program.command("ask").description('Optional headless NL: carrier ask "show flee
|
|
|
1366
2512
|
process.exitCode = 1;
|
|
1367
2513
|
return;
|
|
1368
2514
|
}
|
|
1369
|
-
const s =
|
|
2515
|
+
const s = p3.spinner();
|
|
1370
2516
|
s.start("Asking Carrier MCP\u2026");
|
|
1371
2517
|
const result = await carrierAsk(intent);
|
|
1372
2518
|
s.stop(result.ok ? "Done" : "Failed");
|
|
1373
2519
|
if (result.ok) {
|
|
1374
|
-
|
|
1375
|
-
|
|
2520
|
+
p3.note(result.text.slice(0, 6e3), "Answer");
|
|
2521
|
+
p3.outro("");
|
|
1376
2522
|
} else {
|
|
1377
|
-
|
|
1378
|
-
|
|
2523
|
+
p3.log.error(result.text);
|
|
2524
|
+
p3.outro(pc4.yellow("See next steps above."));
|
|
1379
2525
|
process.exitCode = 1;
|
|
1380
2526
|
}
|
|
1381
2527
|
});
|
|
2528
|
+
for (const domain of CLI_DOMAINS) {
|
|
2529
|
+
const group = program.command(domain.name).description(domain.summary);
|
|
2530
|
+
const subgroups = /* @__PURE__ */ new Map();
|
|
2531
|
+
for (const cmd of domain.commands) {
|
|
2532
|
+
const parts = cmd.name.split(" ");
|
|
2533
|
+
let parent = group;
|
|
2534
|
+
let prefix = "";
|
|
2535
|
+
for (const part of parts.slice(0, -1)) {
|
|
2536
|
+
prefix = prefix ? `${prefix} ${part}` : part;
|
|
2537
|
+
let nested = subgroups.get(prefix);
|
|
2538
|
+
if (!nested) {
|
|
2539
|
+
nested = parent.command(part).description(`${part} subcommands`);
|
|
2540
|
+
subgroups.set(prefix, nested);
|
|
2541
|
+
}
|
|
2542
|
+
parent = nested;
|
|
2543
|
+
}
|
|
2544
|
+
const sub = parent.command(parts[parts.length - 1]).description(cmd.write ? `${cmd.summary} Dry run unless --commit.` : cmd.summary);
|
|
2545
|
+
for (const opt of cmd.options) sub.option(opt.flags, opt.description);
|
|
2546
|
+
sub.option("--json", "Print the raw tool response on stdout and nothing else");
|
|
2547
|
+
if (cmd.write) sub.option("--commit", "Apply the change instead of previewing it");
|
|
2548
|
+
sub.action(async (opts) => {
|
|
2549
|
+
if (opts.json !== true) header();
|
|
2550
|
+
await runCapability(domain, cmd, opts);
|
|
2551
|
+
if (opts.json !== true) p3.outro("");
|
|
2552
|
+
});
|
|
2553
|
+
}
|
|
2554
|
+
}
|
|
1382
2555
|
var site = program.command("site").description("Scaffold and deploy a white-labeled storefront.");
|
|
1383
2556
|
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) => {
|
|
1384
2557
|
header();
|
|
@@ -1408,9 +2581,9 @@ site.command("create [dir]").description("Scaffold a white-labeled storefront fr
|
|
|
1408
2581
|
}
|
|
1409
2582
|
const target = resolve2(dir ?? "./storefront");
|
|
1410
2583
|
if (await exists(target)) {
|
|
1411
|
-
const go = await
|
|
1412
|
-
if (
|
|
1413
|
-
|
|
2584
|
+
const go = await p3.confirm({ message: `${target} exists \u2014 write into it anyway?`, initialValue: false });
|
|
2585
|
+
if (p3.isCancel(go) || !go) {
|
|
2586
|
+
p3.outro("Stopped. Re-run with a different directory.");
|
|
1414
2587
|
return;
|
|
1415
2588
|
}
|
|
1416
2589
|
}
|
|
@@ -1420,7 +2593,7 @@ site.command("create [dir]").description("Scaffold a white-labeled storefront fr
|
|
|
1420
2593
|
process.exitCode = 1;
|
|
1421
2594
|
return;
|
|
1422
2595
|
}
|
|
1423
|
-
|
|
2596
|
+
p3.outro(pc4.green(`Scaffolded. cd ${dir ?? "storefront"} && pnpm install && pnpm dev`));
|
|
1424
2597
|
});
|
|
1425
2598
|
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) => {
|
|
1426
2599
|
header();
|
|
@@ -1436,12 +2609,28 @@ site.command("deploy [dir]").description("Build + deploy a storefront to Cloudfl
|
|
|
1436
2609
|
process.exitCode = 1;
|
|
1437
2610
|
return;
|
|
1438
2611
|
}
|
|
1439
|
-
|
|
2612
|
+
p3.outro("");
|
|
2613
|
+
});
|
|
2614
|
+
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) => {
|
|
2615
|
+
header();
|
|
2616
|
+
const target = resolve2(dir ?? "./storefront");
|
|
2617
|
+
try {
|
|
2618
|
+
const brand = await loadStorefrontBrand(target);
|
|
2619
|
+
const logo = await writeStorefrontLogo(target, brand);
|
|
2620
|
+
ok(`Logo (${logo.source}): ${logo.path}`);
|
|
2621
|
+
} catch (e) {
|
|
2622
|
+
fail(String(e instanceof Error ? e.message : e), [
|
|
2623
|
+
"Scaffold first: carrier site create"
|
|
2624
|
+
]);
|
|
2625
|
+
process.exitCode = 1;
|
|
2626
|
+
return;
|
|
2627
|
+
}
|
|
2628
|
+
p3.outro("");
|
|
1440
2629
|
});
|
|
1441
2630
|
program.parseAsync(process.argv).catch((e) => {
|
|
1442
|
-
console.error(
|
|
2631
|
+
console.error(pc4.red(String(e instanceof Error ? e.message : e)));
|
|
1443
2632
|
console.error(
|
|
1444
|
-
|
|
2633
|
+
pc4.dim(
|
|
1445
2634
|
[
|
|
1446
2635
|
"",
|
|
1447
2636
|
"What to do next:",
|