@carrierllc/mcp 0.13.30 → 0.13.32
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 +35 -0
- package/dist/{chunk-ZJY7H2QW.js → chunk-2P76KIOS.js} +30 -2
- package/dist/{chunk-ZJY7H2QW.js.map → chunk-2P76KIOS.js.map} +1 -1
- package/dist/cli.js +432 -44
- package/dist/cli.js.map +1 -1
- package/dist/index.js +1 -1
- package/package.json +1 -1
package/dist/cli.js
CHANGED
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
import {
|
|
3
3
|
CARRIER_BRAND,
|
|
4
4
|
CARRIER_VERSION,
|
|
5
|
+
OCS_LIST_KEYS,
|
|
5
6
|
OCS_MAX_USAGE_WINDOW_DAYS,
|
|
6
7
|
TARGET_IDS,
|
|
7
8
|
billingScreen,
|
|
@@ -13,6 +14,7 @@ import {
|
|
|
13
14
|
deriveAccentDark,
|
|
14
15
|
deriveAccentLight,
|
|
15
16
|
deriveAccentPaletteSubs,
|
|
17
|
+
extractSubscriberStatus,
|
|
16
18
|
fleetScreen,
|
|
17
19
|
formatProbes,
|
|
18
20
|
generateStorefrontLogo,
|
|
@@ -36,12 +38,13 @@ import {
|
|
|
36
38
|
slug,
|
|
37
39
|
storefrontClerkUrls,
|
|
38
40
|
storefrontScreen,
|
|
41
|
+
subscriberIccid,
|
|
39
42
|
subscribersScreen,
|
|
40
43
|
usageScreen,
|
|
41
44
|
verifyStorefront,
|
|
42
45
|
walletScreen,
|
|
43
46
|
which
|
|
44
|
-
} from "./chunk-
|
|
47
|
+
} from "./chunk-2P76KIOS.js";
|
|
45
48
|
import {
|
|
46
49
|
copyTree,
|
|
47
50
|
exists,
|
|
@@ -552,14 +555,14 @@ async function claudeResolvedConfig(server) {
|
|
|
552
555
|
}
|
|
553
556
|
const json = await privateJson(file);
|
|
554
557
|
if (!json) return null;
|
|
555
|
-
const
|
|
558
|
+
const object2 = (value) => !!value && typeof value === "object" && !Array.isArray(value);
|
|
556
559
|
const readOptional = async (path) => {
|
|
557
560
|
try {
|
|
558
561
|
await lstat(path);
|
|
559
562
|
const text3 = await privateText(path, false);
|
|
560
563
|
if (text3 === null) return null;
|
|
561
564
|
const value = JSON.parse(text3);
|
|
562
|
-
return
|
|
565
|
+
return object2(value) ? value : null;
|
|
563
566
|
} catch (e) {
|
|
564
567
|
return e.code === "ENOENT" ? void 0 : null;
|
|
565
568
|
}
|
|
@@ -600,15 +603,15 @@ async function claudeResolvedConfig(server) {
|
|
|
600
603
|
if (settings === null || settings && policyFields.some((k) => Object.hasOwn(settings, k))) return null;
|
|
601
604
|
}
|
|
602
605
|
const projects = json.projects;
|
|
603
|
-
if (projects !== void 0 && !
|
|
604
|
-
const local =
|
|
605
|
-
if (local !== void 0 && !
|
|
606
|
+
if (projects !== void 0 && !object2(projects)) return null;
|
|
607
|
+
const local = object2(projects) ? projects[workspace] : void 0;
|
|
608
|
+
if (local !== void 0 && !object2(local)) return null;
|
|
606
609
|
const userServers = json.mcpServers;
|
|
607
|
-
const localServers =
|
|
608
|
-
if (userServers !== void 0 && !
|
|
609
|
-
let config =
|
|
610
|
+
const localServers = object2(local) ? local.mcpServers : void 0;
|
|
611
|
+
if (userServers !== void 0 && !object2(userServers) || localServers !== void 0 && !object2(localServers)) return null;
|
|
612
|
+
let config = object2(userServers) ? userServers[server] : void 0;
|
|
610
613
|
let scope = "user", selectedFile = file;
|
|
611
|
-
const hasLocal =
|
|
614
|
+
const hasLocal = object2(localServers) && Object.hasOwn(localServers, server);
|
|
612
615
|
let project, projectFile;
|
|
613
616
|
const dirs = [];
|
|
614
617
|
for (let dir = cwd; ; ) {
|
|
@@ -621,7 +624,7 @@ async function claudeResolvedConfig(server) {
|
|
|
621
624
|
const path = join6(dir, ".mcp.json"), value = await readOptional(path);
|
|
622
625
|
if (value === null) return null;
|
|
623
626
|
if (!value) continue;
|
|
624
|
-
if (!
|
|
627
|
+
if (!object2(value.mcpServers)) return null;
|
|
625
628
|
if (Object.hasOwn(value.mcpServers, server)) {
|
|
626
629
|
project = value.mcpServers[server];
|
|
627
630
|
projectFile = path;
|
|
@@ -629,7 +632,7 @@ async function claudeResolvedConfig(server) {
|
|
|
629
632
|
}
|
|
630
633
|
if (projectFile && !hasLocal) {
|
|
631
634
|
const settings = await readOptional(join6(configDir, "settings.json"));
|
|
632
|
-
if (settings === null || !
|
|
635
|
+
if (settings === null || !object2(local) || local.hasTrustDialogAccepted !== true) return null;
|
|
633
636
|
const disabled = settings?.disabledMcpjsonServers;
|
|
634
637
|
const enabled = settings?.enabledMcpjsonServers;
|
|
635
638
|
if (disabled !== void 0 && (!Array.isArray(disabled) || disabled.some((v) => typeof v !== "string"))) return null;
|
|
@@ -648,8 +651,8 @@ async function claudeResolvedConfig(server) {
|
|
|
648
651
|
config = localServers[server];
|
|
649
652
|
scope = "local";
|
|
650
653
|
}
|
|
651
|
-
if (!
|
|
652
|
-
if (config.headers !== void 0 && !
|
|
654
|
+
if (!object2(config) || config.url !== MCP_URL || config.command || config.type !== void 0 && config.type !== "http" && config.type !== "sse") return null;
|
|
655
|
+
if (config.headers !== void 0 && !object2(config.headers)) return null;
|
|
653
656
|
for (const [key, value] of Object.entries(config.headers ?? {})) {
|
|
654
657
|
if (typeof value !== "string" || /authorization|api[-_]?key|token|cookie/i.test(key) || /\$\{|[\r\n\0]/.test(value)) return null;
|
|
655
658
|
}
|
|
@@ -877,7 +880,7 @@ async function startCallbackServer() {
|
|
|
877
880
|
res.end(page("You're signed in", "Close this tab and return to your terminal."));
|
|
878
881
|
resolveCode?.(code);
|
|
879
882
|
});
|
|
880
|
-
let
|
|
883
|
+
let bound2 = false;
|
|
881
884
|
let lastErr;
|
|
882
885
|
for (const port2 of CALLBACK_PORTS) {
|
|
883
886
|
try {
|
|
@@ -888,13 +891,13 @@ async function startCallbackServer() {
|
|
|
888
891
|
resolve5();
|
|
889
892
|
});
|
|
890
893
|
});
|
|
891
|
-
|
|
894
|
+
bound2 = true;
|
|
892
895
|
break;
|
|
893
896
|
} catch (e) {
|
|
894
897
|
lastErr = e;
|
|
895
898
|
}
|
|
896
899
|
}
|
|
897
|
-
if (!
|
|
900
|
+
if (!bound2) {
|
|
898
901
|
throw new Error(
|
|
899
902
|
`Could not bind a local callback port (tried ${CALLBACK_PORTS.filter(Boolean).join(", ")}): ${lastErr instanceof Error ? lastErr.message : String(lastErr)}`
|
|
900
903
|
);
|
|
@@ -1153,6 +1156,360 @@ function withNextStep(errorMsg, next) {
|
|
|
1153
1156
|
return [errorMsg, "", "What to do next:", ...next.map((s) => ` \u2192 ${s}`)].join("\n");
|
|
1154
1157
|
}
|
|
1155
1158
|
|
|
1159
|
+
// src/cli/lib/ask-answer.ts
|
|
1160
|
+
var MAX_CHARS = 3800;
|
|
1161
|
+
var MAX_ROWS = 10;
|
|
1162
|
+
var RAW_HINT = 'Use carrier ask "your question" --json for the complete raw result (may contain sensitive data).';
|
|
1163
|
+
function object(value) {
|
|
1164
|
+
return value !== null && typeof value === "object" && !Array.isArray(value) ? value : void 0;
|
|
1165
|
+
}
|
|
1166
|
+
function measured(value) {
|
|
1167
|
+
return typeof value === "number" && Number.isFinite(value) && value >= 0;
|
|
1168
|
+
}
|
|
1169
|
+
function bound(text3, prose = false) {
|
|
1170
|
+
text3 = sanitizeAskText(text3);
|
|
1171
|
+
if (text3.length <= MAX_CHARS) return text3;
|
|
1172
|
+
const notice = `Answer shortened; complete rows or sentences omitted. ${RAW_HINT}`;
|
|
1173
|
+
const budget = MAX_CHARS - notice.length - 1;
|
|
1174
|
+
const units = prose ? text3.match(/[^\n]+?(?:[.!?](?=\s|$)|(?=\n|$))/g) ?? [] : text3.split("\n");
|
|
1175
|
+
const kept = [];
|
|
1176
|
+
let size = 0;
|
|
1177
|
+
for (const unit of units) {
|
|
1178
|
+
const clean = unit.trim();
|
|
1179
|
+
if (size + clean.length + 1 > budget) break;
|
|
1180
|
+
kept.push(clean);
|
|
1181
|
+
size += clean.length + 1;
|
|
1182
|
+
}
|
|
1183
|
+
return [...kept, notice].join("\n");
|
|
1184
|
+
}
|
|
1185
|
+
function safeFacet(key, value) {
|
|
1186
|
+
if (key === "recurring") return value === "true" || value === "false";
|
|
1187
|
+
if (key === "status") return ["active", "inactive", "inventory", "manually suspended", "end of life", "free", "expired", "terminated"].includes(value.toLowerCase());
|
|
1188
|
+
return false;
|
|
1189
|
+
}
|
|
1190
|
+
function unsupportedJudgement(intent) {
|
|
1191
|
+
return /\b(predict\w*|most likely|least likely|likelihood|probability|forecast\w*|voorspel\w*|waarschijnlijk)\b/i.test(intent) || /\b(who|which|what|wie|welke)\b.*\b(best|worst|beste|slechtste)\b(?=\s*(?:[?!.]|$))/i.test(intent) || /\b(who|which|what|wie|welke)\s+(?:(?:is|are|has|the|subscriber|package|action)\s+)*(best|worst|beste|slechtste)\b/i.test(intent) || /\b(?:recommend\w*|advise)\b/i.test(intent) || /^\s*should\b/i.test(intent) || /\b(will|going to)\b.*\b(cancel|churn|leave|fail)\b/i.test(intent);
|
|
1192
|
+
}
|
|
1193
|
+
function subscriberLine(value) {
|
|
1194
|
+
const row = object(value);
|
|
1195
|
+
if (!row) return void 0;
|
|
1196
|
+
const status = extractSubscriberStatus(row);
|
|
1197
|
+
const sim = object(row.sim);
|
|
1198
|
+
const id = typeof row.subscriberId === "string" || typeof row.subscriberId === "number" ? row.subscriberId : "(identifier not reported)";
|
|
1199
|
+
return `Subscriber ${inline(id)}. Subscriber status: ${inline(status || "not reported")}. SIM inventory status: ${typeof sim?.status === "string" ? inline(sim.status) : "not reported"}.`;
|
|
1200
|
+
}
|
|
1201
|
+
function subscriberStatusAnswer(rows2, summary, intent) {
|
|
1202
|
+
const identities = [...intent.matchAll(/\b(subscriberId|subscriber\s+id|subscriber|ICCID)(?:\s*[:=]\s*|\s+)(?:"([^"\r\n]+)"|'([^'\r\n]+)'|([^\s"'?!,;]+))/gi)].map((m) => {
|
|
1203
|
+
if (m[4]) m[4] = m[4].replace(/[.:)]+$/, "");
|
|
1204
|
+
return m;
|
|
1205
|
+
}).filter((m) => !/^(?:status|statuses|current|is|are|the|list|count|in|for|of|from|their|my|our|all|each|every|now|today|currently|and|or)$/i.test(m[4] ?? ""));
|
|
1206
|
+
if (identities.length) {
|
|
1207
|
+
const tail = intent.slice((identities[0].index ?? 0) + identities[0][0].length);
|
|
1208
|
+
const extraIdentity = /\b(?:and|or)\b/i.test(tail) && !/^\s*[.:)]*\s+and\s+(?:(?:its|their|the)\s+)?(?:ICCID|subscriberId|subscriber\s+id|(?:current\s+)?status)\s*[.?!]*$/i.test(tail);
|
|
1209
|
+
if (identities.length !== 1 || /\b(named|with|whose|and|or)\b/i.test(identities[0][4] ?? "") || extraIdentity) {
|
|
1210
|
+
return "Carrier cannot identify a single subscriber from this unclear request. Specify one exact subscriberId or ICCID; no subscriber was selected.";
|
|
1211
|
+
}
|
|
1212
|
+
const match = identities[0];
|
|
1213
|
+
const id = match[2] ?? match[3] ?? match[4];
|
|
1214
|
+
if (!rows2) return "Subscriber row details were not returned; Carrier cannot determine the requested current status.";
|
|
1215
|
+
const selected = rows2.filter((value) => {
|
|
1216
|
+
const row = object(value);
|
|
1217
|
+
if (!row) return false;
|
|
1218
|
+
return match[1].toLowerCase() === "iccid" ? subscriberIccid(row) === id : (typeof row.subscriberId === "string" || typeof row.subscriberId === "number" && Number.isFinite(row.subscriberId)) && String(row.subscriberId) === id;
|
|
1219
|
+
});
|
|
1220
|
+
if (!selected.length) return "The requested identifier is not present in the returned rows. This does not establish fleet-wide absence.";
|
|
1221
|
+
if (selected.length !== 1) return "Carrier cannot select a single subscriber: that identifier is ambiguous in the returned rows.";
|
|
1222
|
+
return bound(`Current status from the returned row:
|
|
1223
|
+
${subscriberLine(selected[0])}`);
|
|
1224
|
+
}
|
|
1225
|
+
const fieldList = /^\s*(?:list|show)\b/i.test(intent) && /\b(?:all|each|every)\s+subscribers\b/i.test(intent);
|
|
1226
|
+
if (!fieldList && /\b(subscriberId|subscriber\s+id|ICCID)\b|\b(?:of|for)\s+(?:the\s+)?subscriber\b/i.test(intent)) return "Carrier cannot identify a single subscriber from this unclear request. Specify one exact subscriberId or ICCID; no subscriber was selected.";
|
|
1227
|
+
if (!rows2) return "Subscriber row details were not returned; current statuses are unavailable.";
|
|
1228
|
+
const lines = ["Current subscriber statuses from the returned rows only:"];
|
|
1229
|
+
if (/\b(how many|count|number|total|hoeveel)\b/i.test(intent)) {
|
|
1230
|
+
lines.push(`Carrier reports ${summary.total} subscribers in this result.`);
|
|
1231
|
+
if (rows2.length < summary.total) lines.push(`Only ${rows2.length} of the reported ${summary.total} rows are present in the response; the remaining details are unavailable.`);
|
|
1232
|
+
}
|
|
1233
|
+
if (rows2.length === 0) lines.push("No subscriber rows were returned; no current statuses can be determined.");
|
|
1234
|
+
for (const row of rows2.slice(0, MAX_ROWS)) {
|
|
1235
|
+
const line = subscriberLine(row);
|
|
1236
|
+
if (line) lines.push(line);
|
|
1237
|
+
else lines.push("A returned row has an unsupported shape; its current status is unavailable.");
|
|
1238
|
+
}
|
|
1239
|
+
if (rows2.length < summary.total || summary.moreAvailable === true) lines.push("Current statuses for remaining rows are unavailable; these returned statuses are not a fleet-wide status breakdown.");
|
|
1240
|
+
if (rows2.length > MAX_ROWS) lines.push(`Showing ${MAX_ROWS} of ${rows2.length} returned rows. ${RAW_HINT}`);
|
|
1241
|
+
return bound(lines.join("\n"));
|
|
1242
|
+
}
|
|
1243
|
+
function formatAskAnswer(text3, tool, structured, intent = "") {
|
|
1244
|
+
const countOnly = /\b(how many|count|number of|hoeveel|aantal)\b/i.test(intent) && !/\b(status|list|details?|which|their|lijst)\b/i.test(intent);
|
|
1245
|
+
if (unsupportedJudgement(intent) && !INTELLIGENCE_TOOLS.has(tool)) return `Carrier cannot determine or predict that judgement from this tool result. No such conclusion is supported by the returned records. ${RAW_HINT}`;
|
|
1246
|
+
let value = structured;
|
|
1247
|
+
try {
|
|
1248
|
+
const parsed = JSON.parse(text3);
|
|
1249
|
+
if (structured === void 0) value = parsed;
|
|
1250
|
+
} catch {
|
|
1251
|
+
if (text3.trim() && !/^[\[{]/.test(text3.trim())) return bound(text3, true);
|
|
1252
|
+
if (structured === void 0) return `The tool result could not be read as complete JSON. ${RAW_HINT}`;
|
|
1253
|
+
}
|
|
1254
|
+
const fallback = object(value);
|
|
1255
|
+
if (structured === void 0 && Array.isArray(fallback?.content) && fallback.content.length === 0) {
|
|
1256
|
+
return "No result data was returned.";
|
|
1257
|
+
}
|
|
1258
|
+
const body = object(value);
|
|
1259
|
+
if (tool === "get_subscriber" && /\b(status|statuses)\b/i.test(intent)) {
|
|
1260
|
+
const row = object(body?.subscriber) ?? object(object(body?.data)?.subscriber) ?? object(body?.data) ?? body;
|
|
1261
|
+
if (row && (Object.hasOwn(row, "subscriberId") || Object.hasOwn(row, "status") || Object.hasOwn(row, "sim"))) {
|
|
1262
|
+
return subscriberStatusAnswer([row], { total: 1 }, intent);
|
|
1263
|
+
}
|
|
1264
|
+
}
|
|
1265
|
+
const summary = object(body?.summary);
|
|
1266
|
+
if (measured(summary?.total)) {
|
|
1267
|
+
const data = object(body?.data) ?? body;
|
|
1268
|
+
const collections = [["subscriberList", "subscribers"], ["template", "package templates"], ["prepaidPackageTemplate", "package templates"], ["reseller", "resellers"], ...OCS_LIST_KEYS.packages.map((key) => [key, "packages"]), ["zone", "zones"]];
|
|
1269
|
+
const found = collections.find(([key]) => Array.isArray(data?.[key]));
|
|
1270
|
+
const noun = found?.[1] ?? (tool === "list_subscribers" ? "subscribers" : tool === "list_package_templates" ? "package templates" : tool === "list_subscriber_packages" ? "packages" : "items");
|
|
1271
|
+
const rows2 = found ? data?.[found[0]] : void 0;
|
|
1272
|
+
if (noun === "subscribers" && !countOnly && /\b(status|statuses)\b/i.test(intent)) return subscriberStatusAnswer(rows2, summary, intent);
|
|
1273
|
+
const lines = [`Carrier reports ${summary.total} ${noun} in this result.`];
|
|
1274
|
+
for (const [key, count] of Object.entries(object(summary.nested) ?? {})) {
|
|
1275
|
+
if (key === "accounts" && measured(count)) lines.push(`${count} accounts reported inside the result.`);
|
|
1276
|
+
}
|
|
1277
|
+
if (summary.moreAvailable === true) lines.push("More results are available; this is not a complete fleet total.");
|
|
1278
|
+
if (measured(summary.upstreamTotal)) lines.push(`Upstream reports ${summary.upstreamTotal} matches before paging.`);
|
|
1279
|
+
for (const [key, value2] of Object.entries(object(summary.breakdown) ?? {})) {
|
|
1280
|
+
const counts = object(object(value2)?.counts);
|
|
1281
|
+
const entries = Object.entries(counts ?? {}).filter(([name, n]) => measured(n) && safeFacet(key, name));
|
|
1282
|
+
if (entries.length) lines.push(`${label(key)} (server-reported): ${entries.map(([k, n]) => `${k}: ${n}`).join(", ")}.`);
|
|
1283
|
+
}
|
|
1284
|
+
if (rows2) {
|
|
1285
|
+
if (rows2.length < summary.total) lines.push(`Only ${rows2.length} of the reported ${summary.total} rows are present in the response; the remaining details are unavailable.`);
|
|
1286
|
+
if (countOnly) return bound(lines.join("\n"));
|
|
1287
|
+
if (noun === "subscribers") {
|
|
1288
|
+
for (const value2 of rows2.slice(0, MAX_ROWS)) {
|
|
1289
|
+
const line = subscriberLine(value2);
|
|
1290
|
+
if (line) lines.push(line);
|
|
1291
|
+
}
|
|
1292
|
+
} else if (tool === "list_reseller_accounts" && found?.[0] === "reseller" && rows2.length) {
|
|
1293
|
+
const nested = object(summary.nested)?.accounts;
|
|
1294
|
+
lines.push(...resellerAccountLines(rows2, measured(nested) ? nested : void 0));
|
|
1295
|
+
} else if (tool === "list_package_templates" && (found?.[0] === "template" || found?.[0] === "prepaidPackageTemplate") && rows2.length) {
|
|
1296
|
+
lines.push(...templateLines(rows2));
|
|
1297
|
+
} else lines.push(...facts(rows2));
|
|
1298
|
+
if (rows2.length > MAX_ROWS && noun === "subscribers") lines.push(`Showing ${MAX_ROWS} of ${rows2.length} returned rows. ${RAW_HINT}`);
|
|
1299
|
+
} else lines.push("Row details were not returned.");
|
|
1300
|
+
return bound(lines.join("\n"));
|
|
1301
|
+
}
|
|
1302
|
+
if (value === null) return "No result data was returned.";
|
|
1303
|
+
if (typeof value !== "object") return bound(`Carrier returned: ${String(value)}`);
|
|
1304
|
+
if (tool === "fleet_health_app" && body) {
|
|
1305
|
+
return bound(["Carrier returned these fleet facts:", ...recordFacts(body, FLEET_FIELDS, false)].join("\n"));
|
|
1306
|
+
}
|
|
1307
|
+
return bound(["Carrier returned these facts (not an inferred answer):", ...facts(value)].join("\n"));
|
|
1308
|
+
}
|
|
1309
|
+
var UNSUPPORTED = `Carrier cannot safely interpret unsupported result fields or shapes. ${RAW_HINT}`;
|
|
1310
|
+
var INTELLIGENCE_TOOLS = /* @__PURE__ */ new Set(["optimize_package", "churn_risk", "fleet_health", "fleet_health_app"]);
|
|
1311
|
+
var FLEET_FIELDS = {
|
|
1312
|
+
utilization: "number",
|
|
1313
|
+
totalActive: "number",
|
|
1314
|
+
totalSuspended: "number",
|
|
1315
|
+
totalInventory: "number",
|
|
1316
|
+
totalOther: "number",
|
|
1317
|
+
totalAccounts: "number",
|
|
1318
|
+
lowBalanceCount: "number"
|
|
1319
|
+
};
|
|
1320
|
+
var FIELD_TYPES = {
|
|
1321
|
+
name: "string",
|
|
1322
|
+
description: "string",
|
|
1323
|
+
detail: "string",
|
|
1324
|
+
currency: "string",
|
|
1325
|
+
status: "string",
|
|
1326
|
+
balance: "money",
|
|
1327
|
+
amount: "money",
|
|
1328
|
+
price: "money",
|
|
1329
|
+
dataLimit: "number",
|
|
1330
|
+
dataUsed: "number",
|
|
1331
|
+
expirationDate: "string",
|
|
1332
|
+
startDate: "string",
|
|
1333
|
+
endDate: "string",
|
|
1334
|
+
useddatabyte: "number",
|
|
1335
|
+
pckdatabyte: "number",
|
|
1336
|
+
tsexpirationutc: "string",
|
|
1337
|
+
recurring: "boolean",
|
|
1338
|
+
active: "boolean",
|
|
1339
|
+
enabled: "boolean",
|
|
1340
|
+
id: "scalar",
|
|
1341
|
+
accountId: "scalar",
|
|
1342
|
+
resellerId: "scalar",
|
|
1343
|
+
templateId: "scalar",
|
|
1344
|
+
packageId: "scalar",
|
|
1345
|
+
zoneId: "scalar",
|
|
1346
|
+
subscriberId: "scalar",
|
|
1347
|
+
// packageOnly is a boolean on the live listResellerAccount account rows.
|
|
1348
|
+
packageOnly: "boolean"
|
|
1349
|
+
};
|
|
1350
|
+
var TEMPLATE_FIELD_TYPES = {
|
|
1351
|
+
prepaidpackagetemplateid: "scalar",
|
|
1352
|
+
prepaidpackagetemplatename: "string",
|
|
1353
|
+
resellerid: "scalar",
|
|
1354
|
+
locationzoneid: "scalar"
|
|
1355
|
+
};
|
|
1356
|
+
var ACCOUNT_FIELD_TYPES = {
|
|
1357
|
+
id: "scalar",
|
|
1358
|
+
name: "string",
|
|
1359
|
+
balance: "money",
|
|
1360
|
+
packageOnly: "boolean"
|
|
1361
|
+
};
|
|
1362
|
+
function inline(value) {
|
|
1363
|
+
return sanitizeAskText(String(value)).replace(/[\r\n\t]+/g, " ");
|
|
1364
|
+
}
|
|
1365
|
+
function sanitizeAskText(text3) {
|
|
1366
|
+
return text3.replace(/(?:\x1b\]|\x9d)[\s\S]*?(?:\x07|\x1b\\|\x9c|$)/g, "").replace(/(?:\x1b[P^_X]|[\x90\x98\x9e\x9f])[\s\S]*?(?:\x1b\\|\x9c|$)/g, "").replace(/(?:\x1b\[|\x9b)[0-?]*[ -/]*[@-~]/g, "").replace(/\x1b[ -/]*[@-~]/g, "").replace(/[\x00-\x08\x0b-\x1f\x7f-\x9f]/g, "");
|
|
1367
|
+
}
|
|
1368
|
+
function label(key) {
|
|
1369
|
+
const words = key.replace(/([a-z])([A-Z])/g, "$1 $2").replace(/[_-]/g, " ");
|
|
1370
|
+
return words.charAt(0).toUpperCase() + words.slice(1);
|
|
1371
|
+
}
|
|
1372
|
+
function validField(expected, value) {
|
|
1373
|
+
if (expected === "scalar") return typeof value === "string" || typeof value === "number" && Number.isFinite(value);
|
|
1374
|
+
if (expected === "money") return typeof value === "number" && Number.isFinite(value) || typeof value === "string" && /^[+-]?\d+(?:\.\d+)?$/.test(value) && Number.isFinite(Number(value));
|
|
1375
|
+
if (!expected) return false;
|
|
1376
|
+
return typeof value === expected && (expected !== "number" || Number.isFinite(value));
|
|
1377
|
+
}
|
|
1378
|
+
function recordFacts(record2, fields = FIELD_TYPES, warnUnknown = true) {
|
|
1379
|
+
const lines = [];
|
|
1380
|
+
let unsupported = false;
|
|
1381
|
+
for (const [key, value] of Object.entries(record2)) {
|
|
1382
|
+
const expected = Object.hasOwn(fields, key) ? fields[key] : void 0;
|
|
1383
|
+
if (!expected || !validField(expected, value)) {
|
|
1384
|
+
if (warnUnknown || expected) unsupported = true;
|
|
1385
|
+
continue;
|
|
1386
|
+
}
|
|
1387
|
+
lines.push(`${label(key)}: ${typeof value === "boolean" ? String(value) : inline(value)}`);
|
|
1388
|
+
}
|
|
1389
|
+
if (unsupported) lines.push(UNSUPPORTED);
|
|
1390
|
+
return lines.length ? lines : ["No result fields were returned."];
|
|
1391
|
+
}
|
|
1392
|
+
var TEMPLATE_WIRE_FIELDS = ["prepaidpackagetemplateid", "prepaidpackagetemplatename", "resellerid", "locationzoneid"];
|
|
1393
|
+
var ACCOUNT_FIELD_ORDER = ["name", "balance", "packageOnly"];
|
|
1394
|
+
function own(record2, key) {
|
|
1395
|
+
return Object.hasOwn(record2, key) ? record2[key] : void 0;
|
|
1396
|
+
}
|
|
1397
|
+
function identifier(value, state) {
|
|
1398
|
+
if (value === void 0) return void 0;
|
|
1399
|
+
if (!validField("scalar", value)) {
|
|
1400
|
+
state.unsupported++;
|
|
1401
|
+
return void 0;
|
|
1402
|
+
}
|
|
1403
|
+
return inline(value) || void 0;
|
|
1404
|
+
}
|
|
1405
|
+
function resellerAccountLines(rows2, nestedAccounts) {
|
|
1406
|
+
const state = { unsupported: 0 };
|
|
1407
|
+
const accountLines = [];
|
|
1408
|
+
let listed = 0;
|
|
1409
|
+
let empty = 0;
|
|
1410
|
+
for (const entry of rows2) {
|
|
1411
|
+
const reseller = object(entry);
|
|
1412
|
+
if (!reseller) {
|
|
1413
|
+
state.unsupported++;
|
|
1414
|
+
continue;
|
|
1415
|
+
}
|
|
1416
|
+
const accounts = own(reseller, "account");
|
|
1417
|
+
if (!Array.isArray(accounts)) {
|
|
1418
|
+
state.unsupported++;
|
|
1419
|
+
continue;
|
|
1420
|
+
}
|
|
1421
|
+
if (accounts.length === 0) {
|
|
1422
|
+
empty++;
|
|
1423
|
+
continue;
|
|
1424
|
+
}
|
|
1425
|
+
const resellerId = identifier(own(reseller, "id"), state);
|
|
1426
|
+
const resellerName = own(reseller, "name");
|
|
1427
|
+
if (resellerName !== void 0 && typeof resellerName !== "string") state.unsupported++;
|
|
1428
|
+
const association = `reseller ${resellerId ?? "(identifier not reported)"}${typeof resellerName === "string" && inline(resellerName) ? ` (${inline(resellerName)})` : ""}`;
|
|
1429
|
+
for (const item of accounts) {
|
|
1430
|
+
const account = object(item);
|
|
1431
|
+
if (!account) {
|
|
1432
|
+
state.unsupported++;
|
|
1433
|
+
continue;
|
|
1434
|
+
}
|
|
1435
|
+
listed++;
|
|
1436
|
+
if (accountLines.length >= MAX_ROWS) continue;
|
|
1437
|
+
const parts = [`Account ${identifier(own(account, "id"), state) ?? "(identifier not reported)"}`, association];
|
|
1438
|
+
for (const key of ACCOUNT_FIELD_ORDER) {
|
|
1439
|
+
const value = own(account, key);
|
|
1440
|
+
if (value === void 0) continue;
|
|
1441
|
+
if (!validField(ACCOUNT_FIELD_TYPES[key], value)) {
|
|
1442
|
+
state.unsupported++;
|
|
1443
|
+
continue;
|
|
1444
|
+
}
|
|
1445
|
+
parts.push(`${label(key)}: ${typeof value === "boolean" ? String(value) : inline(value)}`);
|
|
1446
|
+
}
|
|
1447
|
+
accountLines.push(parts.join("; "));
|
|
1448
|
+
}
|
|
1449
|
+
}
|
|
1450
|
+
const lines = [];
|
|
1451
|
+
if (nestedAccounts !== void 0 && listed < nestedAccounts) {
|
|
1452
|
+
lines.push(`Only ${listed} of the reported ${nestedAccounts} accounts are present in the response; the remaining account details are unavailable.`);
|
|
1453
|
+
}
|
|
1454
|
+
if (state.unsupported) lines.push(UNSUPPORTED);
|
|
1455
|
+
if (empty) lines.push(`No accounts are listed under ${empty} ${empty === 1 ? "reseller" : "resellers"} in the returned rows.`);
|
|
1456
|
+
if (listed > MAX_ROWS) lines.push(`Showing ${accountLines.length} of ${listed} returned accounts. ${RAW_HINT}`);
|
|
1457
|
+
return [...lines, ...accountLines];
|
|
1458
|
+
}
|
|
1459
|
+
function templateLines(rows2) {
|
|
1460
|
+
const state = { unsupported: 0 };
|
|
1461
|
+
const itemLines = [];
|
|
1462
|
+
rows2.forEach((row, i) => {
|
|
1463
|
+
const record2 = object(row);
|
|
1464
|
+
if (!record2) {
|
|
1465
|
+
state.unsupported++;
|
|
1466
|
+
return;
|
|
1467
|
+
}
|
|
1468
|
+
const wire = Object.hasOwn(record2, "prepaidpackagetemplateid") || Object.hasOwn(record2, "prepaidpackagetemplatename");
|
|
1469
|
+
const types = wire ? TEMPLATE_FIELD_TYPES : FIELD_TYPES;
|
|
1470
|
+
const entries = wire ? TEMPLATE_WIRE_FIELDS.filter((key) => Object.hasOwn(record2, key)).map((key) => [key, record2[key]]) : Object.entries(record2);
|
|
1471
|
+
const parts = [];
|
|
1472
|
+
for (const [key, value] of entries) {
|
|
1473
|
+
const expected = Object.hasOwn(types, key) ? types[key] : void 0;
|
|
1474
|
+
if (!expected || !validField(expected, value)) {
|
|
1475
|
+
state.unsupported++;
|
|
1476
|
+
continue;
|
|
1477
|
+
}
|
|
1478
|
+
parts.push(`${label(key)}: ${typeof value === "boolean" ? String(value) : inline(value)}`);
|
|
1479
|
+
}
|
|
1480
|
+
if (itemLines.length < MAX_ROWS) itemLines.push(`Item ${i + 1}: ${parts.length ? parts.join("; ") : "No result fields were returned."}`);
|
|
1481
|
+
});
|
|
1482
|
+
const lines = [];
|
|
1483
|
+
if (state.unsupported) lines.push(UNSUPPORTED);
|
|
1484
|
+
if (rows2.length > MAX_ROWS) lines.push(`Showing ${MAX_ROWS} of ${rows2.length} returned rows. ${RAW_HINT}`);
|
|
1485
|
+
return [...lines, ...itemLines];
|
|
1486
|
+
}
|
|
1487
|
+
function facts(value) {
|
|
1488
|
+
if (Array.isArray(value)) {
|
|
1489
|
+
if (!value.length) return ["No items were returned."];
|
|
1490
|
+
const lines2 = value.slice(0, MAX_ROWS).flatMap((row, i) => {
|
|
1491
|
+
const record3 = object(row);
|
|
1492
|
+
return record3 ? [`Item ${i + 1}: ${recordFacts(record3).join("; ")}`] : [UNSUPPORTED];
|
|
1493
|
+
});
|
|
1494
|
+
if (value.length > MAX_ROWS) lines2.push(`Showing ${MAX_ROWS} of ${value.length} returned rows. ${RAW_HINT}`);
|
|
1495
|
+
return lines2;
|
|
1496
|
+
}
|
|
1497
|
+
const record2 = object(value);
|
|
1498
|
+
if (!record2) return [UNSUPPORTED];
|
|
1499
|
+
const outer = Object.fromEntries(Object.entries(record2).filter(([key]) => !["account", "result", "data"].includes(key)));
|
|
1500
|
+
const lines = Object.keys(outer).length ? recordFacts(outer) : [];
|
|
1501
|
+
for (const key of ["account", "result", "data"]) {
|
|
1502
|
+
if (!Object.hasOwn(record2, key)) continue;
|
|
1503
|
+
const inner = object(record2[key]);
|
|
1504
|
+
if (inner) lines.push(`${label(key)}:`, ...recordFacts(inner));
|
|
1505
|
+
else if (key === "account" && Array.isArray(record2[key])) {
|
|
1506
|
+
const accounts = record2[key];
|
|
1507
|
+
lines.push(...facts(accounts));
|
|
1508
|
+
} else lines.push(UNSUPPORTED);
|
|
1509
|
+
}
|
|
1510
|
+
return lines.length ? lines : ["No result fields were returned."];
|
|
1511
|
+
}
|
|
1512
|
+
|
|
1156
1513
|
// src/cli/lib/ask.ts
|
|
1157
1514
|
var DEFAULT_ERROR_HINTS = [
|
|
1158
1515
|
"Re-check the arguments: carrier <domain> <command> --help",
|
|
@@ -1342,11 +1699,24 @@ function parseRouting(text3) {
|
|
|
1342
1699
|
return void 0;
|
|
1343
1700
|
}
|
|
1344
1701
|
}
|
|
1345
|
-
|
|
1702
|
+
function readableAskError(result) {
|
|
1703
|
+
try {
|
|
1704
|
+
const body = JSON.parse(result.text);
|
|
1705
|
+
const message = body.error ?? body.message;
|
|
1706
|
+
if (typeof message === "string") return { ...result, text: message };
|
|
1707
|
+
} catch {
|
|
1708
|
+
}
|
|
1709
|
+
return result;
|
|
1710
|
+
}
|
|
1711
|
+
async function carrierAsk(intent, opts = {}) {
|
|
1712
|
+
const result = await routeAsk(intent, opts);
|
|
1713
|
+
return opts.json ? result : { ...result, text: sanitizeAskText(result.text) };
|
|
1714
|
+
}
|
|
1715
|
+
async function routeAsk(intent, opts) {
|
|
1346
1716
|
const routed = await callMcpTool("carrier_ask", { intent }, { errorHints: ASK_ERROR_HINTS });
|
|
1347
1717
|
if (!routed.ok) return routed;
|
|
1348
1718
|
const routing = parseRouting(routed.text);
|
|
1349
|
-
if (!routing) return routed;
|
|
1719
|
+
if (!routing) return opts.json ? routed : { ...routed, text: formatAskAnswer(routed.text, "carrier_ask", routed.structured, intent) };
|
|
1350
1720
|
if (!routing.resolved_tool) {
|
|
1351
1721
|
const candidates = (routing.candidates ?? []).map((c) => c.tool ? `Candidate: ${c.tool}${c.reason ? ` \u2014 ${c.reason}` : ""}` : void 0).filter((s) => Boolean(s));
|
|
1352
1722
|
return {
|
|
@@ -1372,13 +1742,16 @@ async function carrierAsk(intent) {
|
|
|
1372
1742
|
return {
|
|
1373
1743
|
ok: false,
|
|
1374
1744
|
text: withNextStep(`"${intent}" resolves to ${tool}, which changes live service or billing.`, [
|
|
1375
|
-
`Params: ${JSON.stringify(args)}`,
|
|
1745
|
+
`Params: ${JSON.stringify(args, (_k, v) => typeof v === "string" ? sanitizeAskText(v) : v)}`,
|
|
1376
1746
|
"Run the matching discrete command so every flag is explicit and reviewable",
|
|
1377
1747
|
"carrier --help lists every domain"
|
|
1378
1748
|
])
|
|
1379
1749
|
};
|
|
1380
1750
|
}
|
|
1381
|
-
|
|
1751
|
+
const result = await callMcpTool(tool, args, { errorHints: ASK_ERROR_HINTS });
|
|
1752
|
+
if (opts.json) return result;
|
|
1753
|
+
if (!result.ok) return readableAskError(result);
|
|
1754
|
+
return { ...result, text: formatAskAnswer(result.text, tool, result.structured, intent) };
|
|
1382
1755
|
}
|
|
1383
1756
|
|
|
1384
1757
|
// src/cli/lib/rest.ts
|
|
@@ -1568,9 +1941,9 @@ var NAME_TO_CODE = /* @__PURE__ */ new Map();
|
|
|
1568
1941
|
for (let first = 65; first <= 90; first += 1) {
|
|
1569
1942
|
for (let second = 65; second <= 90; second += 1) {
|
|
1570
1943
|
const code = String.fromCharCode(first, second);
|
|
1571
|
-
const
|
|
1572
|
-
if (!
|
|
1573
|
-
const key =
|
|
1944
|
+
const label2 = regionLabel(code);
|
|
1945
|
+
if (!label2) continue;
|
|
1946
|
+
const key = label2.toLowerCase();
|
|
1574
1947
|
const current = NAME_TO_CODE.get(key);
|
|
1575
1948
|
if (!current || PREFERRED.has(code)) NAME_TO_CODE.set(key, code);
|
|
1576
1949
|
}
|
|
@@ -1702,9 +2075,9 @@ function logoMime(bytes) {
|
|
|
1702
2075
|
function regionLabel(code) {
|
|
1703
2076
|
if (!/^[A-Z]{2}$/.test(code)) return void 0;
|
|
1704
2077
|
try {
|
|
1705
|
-
const
|
|
1706
|
-
if (!
|
|
1707
|
-
return
|
|
2078
|
+
const label2 = regionNames.of(code);
|
|
2079
|
+
if (!label2 || label2.toUpperCase() === code) return void 0;
|
|
2080
|
+
return label2;
|
|
1708
2081
|
} catch {
|
|
1709
2082
|
return void 0;
|
|
1710
2083
|
}
|
|
@@ -1825,9 +2198,9 @@ function storefrontPlanBody(input) {
|
|
|
1825
2198
|
function isStorefrontPlanError(value) {
|
|
1826
2199
|
return value !== null && "error" in value;
|
|
1827
2200
|
}
|
|
1828
|
-
function parseWholeNumber(value,
|
|
2201
|
+
function parseWholeNumber(value, label2) {
|
|
1829
2202
|
if (value === void 0) return { ok: true, value: void 0 };
|
|
1830
|
-
if (!/^\d+$/.test(value)) return { ok: false, message: `${
|
|
2203
|
+
if (!/^\d+$/.test(value)) return { ok: false, message: `${label2} must be a whole number.` };
|
|
1831
2204
|
return { ok: true, value: Number(value) };
|
|
1832
2205
|
}
|
|
1833
2206
|
function overlay(base, input, fields) {
|
|
@@ -2738,12 +3111,12 @@ async function showHomeStatus() {
|
|
|
2738
3111
|
printStatus(st);
|
|
2739
3112
|
return st;
|
|
2740
3113
|
}
|
|
2741
|
-
async function openAndReport(url,
|
|
3114
|
+
async function openAndReport(url, label2) {
|
|
2742
3115
|
const r = await openUrl(url);
|
|
2743
3116
|
if (r.ok) p.log.success(r.hint);
|
|
2744
3117
|
else {
|
|
2745
3118
|
p.log.warn(r.hint);
|
|
2746
|
-
p.note(url,
|
|
3119
|
+
p.note(url, label2);
|
|
2747
3120
|
}
|
|
2748
3121
|
}
|
|
2749
3122
|
async function runHome(opts) {
|
|
@@ -2848,7 +3221,7 @@ async function runHome(opts) {
|
|
|
2848
3221
|
spin.start("Asking Carrier MCP\u2026");
|
|
2849
3222
|
const result = await carrierAsk(String(intent).trim());
|
|
2850
3223
|
spin.stop(result.ok ? "Answer" : "Could not complete ask");
|
|
2851
|
-
if (result.ok) p.note(result.text
|
|
3224
|
+
if (result.ok) p.note(result.text, "carrier ask");
|
|
2852
3225
|
else p.log.error(result.text);
|
|
2853
3226
|
break;
|
|
2854
3227
|
}
|
|
@@ -5736,7 +6109,7 @@ function formatJsonOutput(text3) {
|
|
|
5736
6109
|
}
|
|
5737
6110
|
}
|
|
5738
6111
|
async function runCapability(domain, cmd, opts) {
|
|
5739
|
-
const
|
|
6112
|
+
const label2 = `${domain.name} ${cmd.name}`;
|
|
5740
6113
|
const quiet = opts.json === true;
|
|
5741
6114
|
const { args, errors, notes } = buildToolArgs(cmd, opts);
|
|
5742
6115
|
if (cmd.write && opts.commit === true) delete args.dry_run;
|
|
@@ -5744,9 +6117,9 @@ async function runCapability(domain, cmd, opts) {
|
|
|
5744
6117
|
errors.push(`${capabilityTarget(cmd)} has no preview mode. Pass --confirm to run it for real.`);
|
|
5745
6118
|
}
|
|
5746
6119
|
if (errors.length) {
|
|
5747
|
-
const message = withNextStep(`Cannot run \`carrier ${
|
|
6120
|
+
const message = withNextStep(`Cannot run \`carrier ${label2}\`.`, [
|
|
5748
6121
|
...errors,
|
|
5749
|
-
`See every flag: carrier ${
|
|
6122
|
+
`See every flag: carrier ${label2} --help`
|
|
5750
6123
|
]);
|
|
5751
6124
|
if (quiet) console.error(message);
|
|
5752
6125
|
else p2.log.error(message);
|
|
@@ -5762,7 +6135,7 @@ async function runCapability(domain, cmd, opts) {
|
|
|
5762
6135
|
].join("\n");
|
|
5763
6136
|
if (quiet) process.stdout.write(`${formatJsonOutput(preview)}
|
|
5764
6137
|
`);
|
|
5765
|
-
else p2.note(preview, `carrier ${
|
|
6138
|
+
else p2.note(preview, `carrier ${label2}`);
|
|
5766
6139
|
return;
|
|
5767
6140
|
}
|
|
5768
6141
|
const dryRun = cmd.write === true && args.dry_run === true;
|
|
@@ -5773,7 +6146,7 @@ async function runCapability(domain, cmd, opts) {
|
|
|
5773
6146
|
}
|
|
5774
6147
|
const target = capabilityTarget(cmd);
|
|
5775
6148
|
const errorHints = [
|
|
5776
|
-
`Check the arguments: carrier ${
|
|
6149
|
+
`Check the arguments: carrier ${label2} --help`,
|
|
5777
6150
|
'Or describe the task in words: carrier ask "\u2026"'
|
|
5778
6151
|
];
|
|
5779
6152
|
const s = quiet ? null : p2.spinner();
|
|
@@ -6587,8 +6960,8 @@ program.command("examples").description("Print natural-language fleet prompts fo
|
|
|
6587
6960
|
});
|
|
6588
6961
|
program.command("ask").description(
|
|
6589
6962
|
'Natural-language fallback for whatever the domain commands miss: carrier ask "show fleet health" (needs CARRIER_API_KEY or OCS token).'
|
|
6590
|
-
).argument("<intent...>", "Natural-language intent").action(async (parts) => {
|
|
6591
|
-
header();
|
|
6963
|
+
).argument("<intent...>", "Natural-language intent").option("--json", "Print the complete raw tool result (may contain sensitive data)").action(async (parts, opts) => {
|
|
6964
|
+
if (!opts.json) header();
|
|
6592
6965
|
const intent = parts.join(" ").trim();
|
|
6593
6966
|
if (!intent) {
|
|
6594
6967
|
fail("Missing intent.", [
|
|
@@ -6598,12 +6971,27 @@ program.command("ask").description(
|
|
|
6598
6971
|
process.exitCode = 1;
|
|
6599
6972
|
return;
|
|
6600
6973
|
}
|
|
6601
|
-
const s = p3.spinner();
|
|
6602
|
-
s
|
|
6603
|
-
const result = await carrierAsk(intent);
|
|
6604
|
-
s
|
|
6974
|
+
const s = opts.json ? void 0 : p3.spinner();
|
|
6975
|
+
s?.start("Asking Carrier MCP\u2026");
|
|
6976
|
+
const result = await carrierAsk(intent, opts);
|
|
6977
|
+
s?.stop(result.ok ? "Done" : "Failed");
|
|
6978
|
+
if (opts.json) {
|
|
6979
|
+
if (result.ok) {
|
|
6980
|
+
let raw = result.text;
|
|
6981
|
+
try {
|
|
6982
|
+
JSON.parse(raw);
|
|
6983
|
+
} catch {
|
|
6984
|
+
raw = JSON.stringify(raw);
|
|
6985
|
+
}
|
|
6986
|
+
process.stdout.write(raw + "\n");
|
|
6987
|
+
} else {
|
|
6988
|
+
process.stderr.write(result.text + "\n");
|
|
6989
|
+
process.exitCode = 1;
|
|
6990
|
+
}
|
|
6991
|
+
return;
|
|
6992
|
+
}
|
|
6605
6993
|
if (result.ok) {
|
|
6606
|
-
p3.note(result.text
|
|
6994
|
+
p3.note(result.text, "Answer");
|
|
6607
6995
|
p3.outro("");
|
|
6608
6996
|
} else {
|
|
6609
6997
|
p3.log.error(result.text);
|