@carrierllc/mcp 0.13.12 → 0.13.14
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/dist/{chunk-IQDAU7WA.js → chunk-DIHA6TVT.js} +602 -5
- package/dist/chunk-DIHA6TVT.js.map +1 -0
- package/dist/cli.js +97 -10
- package/dist/cli.js.map +1 -1
- package/dist/index.js +202 -74
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
- package/plugin/carrier/.claude-plugin/plugin.json +1 -1
- package/plugin/carrier/skills/publish-storefront/SKILL.md +2 -2
- package/dist/chunk-IQDAU7WA.js.map +0 -1
|
@@ -1820,7 +1820,7 @@ function storefrontScreen(input) {
|
|
|
1820
1820
|
// package.json
|
|
1821
1821
|
var package_default = {
|
|
1822
1822
|
name: "@carrierllc/mcp",
|
|
1823
|
-
version: "0.13.
|
|
1823
|
+
version: "0.13.14",
|
|
1824
1824
|
description: "Carrier MCP \u2014 natural-language control of MVNO/eSIM fleets via eSIMVault OCS. Stdio mode for direct integration with Claude Desktop, Cursor, Windsurf, and MCP-compatible clients. Ships the `carrier` CLI (plugin install + white-label eSIM storefront scaffold).",
|
|
1825
1825
|
license: "MIT",
|
|
1826
1826
|
author: "Carrier (Lifecycle Innovations Limited)",
|
|
@@ -2705,7 +2705,7 @@ function packageTemplateModifyFromToolJson(changes, method) {
|
|
|
2705
2705
|
return packageTemplateModifyParams(body, method);
|
|
2706
2706
|
}
|
|
2707
2707
|
|
|
2708
|
-
// ../../packages/ocs-client/dist/chunk-
|
|
2708
|
+
// ../../packages/ocs-client/dist/chunk-MNVQV5ST.js
|
|
2709
2709
|
import { z } from "zod";
|
|
2710
2710
|
var OCS_V1_METHODS = /* @__PURE__ */ new Set([
|
|
2711
2711
|
"getResellerInfo",
|
|
@@ -2749,6 +2749,10 @@ var OCS_V1_METHODS = /* @__PURE__ */ new Set([
|
|
|
2749
2749
|
"resetSubsGzCounter",
|
|
2750
2750
|
"sendMtSms"
|
|
2751
2751
|
]);
|
|
2752
|
+
var OCS_FORBIDDEN_METHODS = /* @__PURE__ */ new Set([
|
|
2753
|
+
"createAccount",
|
|
2754
|
+
"createSubscriber"
|
|
2755
|
+
]);
|
|
2752
2756
|
var OcsResponseEnvelope = z.object({
|
|
2753
2757
|
status: z.object({
|
|
2754
2758
|
code: z.number(),
|
|
@@ -3050,7 +3054,7 @@ async function runWithBudget(method, attempt, budget = ocsBudgetFor(method)) {
|
|
|
3050
3054
|
throw lastTimeout ?? new OcsTimeoutError(method, budget.attemptMs, budget.totalMs, Math.max(attempts, 1));
|
|
3051
3055
|
}
|
|
3052
3056
|
|
|
3053
|
-
// ../../packages/ocs-client/dist/chunk-
|
|
3057
|
+
// ../../packages/ocs-client/dist/chunk-4F7VPWOS.js
|
|
3054
3058
|
var RATE_LIMIT = 10;
|
|
3055
3059
|
var WINDOW_MS = 1e3;
|
|
3056
3060
|
var windows = /* @__PURE__ */ new Map();
|
|
@@ -3331,7 +3335,7 @@ var OcsApiError = class extends Error {
|
|
|
3331
3335
|
var OcsUnknownMethodError = class extends Error {
|
|
3332
3336
|
constructor(method) {
|
|
3333
3337
|
super(
|
|
3334
|
-
`[${method}] is not a known OCS v1 method. It is not in the verified set of ${OCS_V1_METHODS.size} methods (createAccount/createSubscriber do not exist on the OCS). If this is a genuinely new method, add it to OCS_V1_METHODS or construct the client without { strictMethods: true }.`
|
|
3338
|
+
`[${method}] is not a known OCS v1 method. It is not in the verified set of ${OCS_V1_METHODS.size} methods (createAccount/createSubscriber do not exist on the OCS). ` + (OCS_FORBIDDEN_METHODS.has(method) ? `${method} is a proven phantom method and is always rejected regardless of client options.` : `If this is a genuinely new method, add it to OCS_V1_METHODS or construct the client without { strictMethods: true }.`)
|
|
3335
3339
|
);
|
|
3336
3340
|
this.method = method;
|
|
3337
3341
|
this.name = "OcsUnknownMethodError";
|
|
@@ -3456,6 +3460,9 @@ var OcsClient = class {
|
|
|
3456
3460
|
_warnedMethods = /* @__PURE__ */ new Set();
|
|
3457
3461
|
_warnedValidation = /* @__PURE__ */ new Set();
|
|
3458
3462
|
async call(method, params = {}, token) {
|
|
3463
|
+
if (OCS_FORBIDDEN_METHODS.has(method)) {
|
|
3464
|
+
throw new OcsUnknownMethodError(method);
|
|
3465
|
+
}
|
|
3459
3466
|
if (!OCS_V1_METHODS.has(method)) {
|
|
3460
3467
|
if (this._strictMethods) throw new OcsUnknownMethodError(method);
|
|
3461
3468
|
if (!this._warnedMethods.has(method)) {
|
|
@@ -4133,6 +4140,113 @@ function applySubscriberFilters(raw, args) {
|
|
|
4133
4140
|
}
|
|
4134
4141
|
return rewrapSubscriberList(raw, envelope, rows, filtered, matched);
|
|
4135
4142
|
}
|
|
4143
|
+
var OCS_SUBSCRIBER_SEARCH_KEYS = [
|
|
4144
|
+
"accountId",
|
|
4145
|
+
"iccid",
|
|
4146
|
+
"imsi",
|
|
4147
|
+
"msisdn",
|
|
4148
|
+
"activationCode",
|
|
4149
|
+
"iccidPrefix",
|
|
4150
|
+
"imsiPrefix",
|
|
4151
|
+
"msisdnPrefix"
|
|
4152
|
+
];
|
|
4153
|
+
function hasSubscriberSearchKey(params) {
|
|
4154
|
+
return OCS_SUBSCRIBER_SEARCH_KEYS.some((key) => {
|
|
4155
|
+
const v = params[key];
|
|
4156
|
+
return v !== void 0 && v !== null && String(v).trim() !== "";
|
|
4157
|
+
});
|
|
4158
|
+
}
|
|
4159
|
+
function flattenResellerAccounts(raw) {
|
|
4160
|
+
const records = [];
|
|
4161
|
+
if (Array.isArray(raw)) {
|
|
4162
|
+
records.push(...raw);
|
|
4163
|
+
} else if (raw !== null && typeof raw === "object") {
|
|
4164
|
+
const obj = raw;
|
|
4165
|
+
if (Array.isArray(obj["reseller"])) {
|
|
4166
|
+
for (const entry of obj["reseller"]) {
|
|
4167
|
+
const accounts = entry?.["account"];
|
|
4168
|
+
if (Array.isArray(accounts)) records.push(...accounts);
|
|
4169
|
+
}
|
|
4170
|
+
}
|
|
4171
|
+
if (Array.isArray(obj["account"])) records.push(...obj["account"]);
|
|
4172
|
+
}
|
|
4173
|
+
const seen = /* @__PURE__ */ new Set();
|
|
4174
|
+
const out = [];
|
|
4175
|
+
for (const rec of records) {
|
|
4176
|
+
if (rec === null || typeof rec !== "object") continue;
|
|
4177
|
+
const r = rec;
|
|
4178
|
+
const id = Number(r["id"] ?? r["accountId"]);
|
|
4179
|
+
if (!Number.isFinite(id) || seen.has(id)) continue;
|
|
4180
|
+
seen.add(id);
|
|
4181
|
+
out.push(typeof r["name"] === "string" ? { id, name: r["name"] } : { id });
|
|
4182
|
+
}
|
|
4183
|
+
return out;
|
|
4184
|
+
}
|
|
4185
|
+
function pickDefaultAccount(accounts) {
|
|
4186
|
+
if (accounts.length === 0) return void 0;
|
|
4187
|
+
const first = [...accounts].sort((a, b) => a.id - b.id)[0];
|
|
4188
|
+
return {
|
|
4189
|
+
accountId: first.id,
|
|
4190
|
+
...first.name !== void 0 ? { name: first.name } : {},
|
|
4191
|
+
accountCount: accounts.length
|
|
4192
|
+
};
|
|
4193
|
+
}
|
|
4194
|
+
var NO_ACCOUNTS_MESSAGE = "OCS requires a subscriber search key, and no default account could be chosen because this reseller has no accounts yet. Create an account in OCS, or pass accountId / set preferred_account_id once one exists.";
|
|
4195
|
+
function lookupFailedMessage(detail) {
|
|
4196
|
+
return `OCS requires a subscriber search key, and this organization's default account could not be looked up (${detail}). Pass accountId, or set preferred_account_id, or retry.`;
|
|
4197
|
+
}
|
|
4198
|
+
function errorText(err) {
|
|
4199
|
+
return err instanceof Error ? err.message : String(err);
|
|
4200
|
+
}
|
|
4201
|
+
async function resolveDefaultAccount(opts) {
|
|
4202
|
+
const cached = await opts.cache?.get().catch(() => null);
|
|
4203
|
+
if (cached && Number.isFinite(cached.accountId)) return { ok: true, ...cached };
|
|
4204
|
+
let resellerId;
|
|
4205
|
+
const given = Number(opts.resellerId);
|
|
4206
|
+
if (opts.resellerId !== void 0 && opts.resellerId !== "" && Number.isFinite(given)) {
|
|
4207
|
+
resellerId = given;
|
|
4208
|
+
} else {
|
|
4209
|
+
try {
|
|
4210
|
+
const info = await opts.call("getResellerInfo", {});
|
|
4211
|
+
const id = Number(info?.id);
|
|
4212
|
+
if (Number.isFinite(id)) resellerId = id;
|
|
4213
|
+
} catch {
|
|
4214
|
+
}
|
|
4215
|
+
}
|
|
4216
|
+
let raw;
|
|
4217
|
+
try {
|
|
4218
|
+
raw = await opts.call("listResellerAccount", resellerId !== void 0 ? { resellerId } : {});
|
|
4219
|
+
} catch (err) {
|
|
4220
|
+
return { ok: false, reason: "lookup_failed", message: lookupFailedMessage(errorText(err)) };
|
|
4221
|
+
}
|
|
4222
|
+
const pick = pickDefaultAccount(flattenResellerAccounts(raw));
|
|
4223
|
+
if (!pick) return { ok: false, reason: "no_accounts", message: NO_ACCOUNTS_MESSAGE };
|
|
4224
|
+
await opts.cache?.put(pick).catch(() => void 0);
|
|
4225
|
+
return { ok: true, ...pick };
|
|
4226
|
+
}
|
|
4227
|
+
function defaultAccountNote(pick) {
|
|
4228
|
+
const label = pick.name ? `${pick.accountId} (${pick.name})` : String(pick.accountId);
|
|
4229
|
+
return pick.accountCount > 1 ? `No account was specified, so this shows account ${label} only \u2014 the lowest of ${pick.accountCount} accounts under this reseller. Pass accountId to see another account.` : `No account was specified, so this shows account ${label}, the only account under this reseller.`;
|
|
4230
|
+
}
|
|
4231
|
+
async function scopeListSubscriberParams(opts) {
|
|
4232
|
+
if (opts.managed || hasSubscriberSearchKey(opts.params)) return { ok: true, params: opts.params };
|
|
4233
|
+
const preferred = Number(opts.preferredAccountId);
|
|
4234
|
+
if (opts.preferredAccountId !== void 0 && opts.preferredAccountId.trim() !== "" && Number.isFinite(preferred)) {
|
|
4235
|
+
return { ok: true, params: { ...opts.params, accountId: preferred } };
|
|
4236
|
+
}
|
|
4237
|
+
const resolved = await resolveDefaultAccount({
|
|
4238
|
+
call: opts.call,
|
|
4239
|
+
...opts.resellerId !== void 0 ? { resellerId: opts.resellerId } : {},
|
|
4240
|
+
...opts.cache ? { cache: opts.cache } : {}
|
|
4241
|
+
});
|
|
4242
|
+
if (!resolved.ok) return resolved;
|
|
4243
|
+
const { ok: _ok, ...pick } = resolved;
|
|
4244
|
+
return {
|
|
4245
|
+
ok: true,
|
|
4246
|
+
params: { ...opts.params, accountId: pick.accountId },
|
|
4247
|
+
defaultAccount: { ...pick, note: defaultAccountNote(pick) }
|
|
4248
|
+
};
|
|
4249
|
+
}
|
|
4136
4250
|
var descriptions = /* @__PURE__ */ new Map();
|
|
4137
4251
|
function recordToolDescription(name, description) {
|
|
4138
4252
|
if (description) descriptions.set(name, description);
|
|
@@ -4683,6 +4797,482 @@ function collapseGyStays(events) {
|
|
|
4683
4797
|
function gyCountryHistoryFromResponse(response) {
|
|
4684
4798
|
return collapseGyStays(extractGyEvents(response));
|
|
4685
4799
|
}
|
|
4800
|
+
var NETWORK_DEFAULT_MODES = ["cheapest_available", "all", "custom"];
|
|
4801
|
+
var SYSTEM_NETWORK_DEFAULT = "cheapest_available";
|
|
4802
|
+
var isoOf = (r) => String(r.operator?.countryIso2 ?? "").trim().toUpperCase();
|
|
4803
|
+
var networkIdOf = (r) => {
|
|
4804
|
+
const n = r.operator?.networkId ?? r.networkid;
|
|
4805
|
+
return typeof n === "number" && Number.isFinite(n) ? n : void 0;
|
|
4806
|
+
};
|
|
4807
|
+
function normaliseCountries(countries) {
|
|
4808
|
+
const out = /* @__PURE__ */ new Set();
|
|
4809
|
+
for (const c of countries) {
|
|
4810
|
+
const iso = String(c ?? "").trim().toUpperCase();
|
|
4811
|
+
if (/^[A-Z]{2}$/.test(iso)) out.add(iso);
|
|
4812
|
+
}
|
|
4813
|
+
return [...out].sort();
|
|
4814
|
+
}
|
|
4815
|
+
function isRuleAvailable(rule, now) {
|
|
4816
|
+
if (rule.active === false) return false;
|
|
4817
|
+
if (rule.hidden === true) return false;
|
|
4818
|
+
const t = now.getTime();
|
|
4819
|
+
if (rule.startdate) {
|
|
4820
|
+
const s = Date.parse(rule.startdate);
|
|
4821
|
+
if (Number.isFinite(s) && s > t) return false;
|
|
4822
|
+
}
|
|
4823
|
+
if (rule.enddate) {
|
|
4824
|
+
const e = Date.parse(rule.enddate);
|
|
4825
|
+
if (Number.isFinite(e) && e <= t) return false;
|
|
4826
|
+
}
|
|
4827
|
+
return true;
|
|
4828
|
+
}
|
|
4829
|
+
function cheapestAvailableForCountry(rules, country, now) {
|
|
4830
|
+
const iso = String(country ?? "").trim().toUpperCase();
|
|
4831
|
+
if (!/^[A-Z]{2}$/.test(iso)) {
|
|
4832
|
+
return {
|
|
4833
|
+
country: iso,
|
|
4834
|
+
networks: [],
|
|
4835
|
+
candidateCount: 0,
|
|
4836
|
+
warnings: [
|
|
4837
|
+
{ code: "NO_COUNTRY", message: `"${country}" is not an ISO-3166 alpha-2 country code.` }
|
|
4838
|
+
]
|
|
4839
|
+
};
|
|
4840
|
+
}
|
|
4841
|
+
if (!rules || rules.length === 0) {
|
|
4842
|
+
return {
|
|
4843
|
+
country: iso,
|
|
4844
|
+
networks: [],
|
|
4845
|
+
candidateCount: 0,
|
|
4846
|
+
warnings: [
|
|
4847
|
+
{
|
|
4848
|
+
code: "NO_TARIFF_DATA",
|
|
4849
|
+
country: iso,
|
|
4850
|
+
message: `No tariff data was available, so ${iso} stays open to all networks. It was not narrowed to the cheapest.`
|
|
4851
|
+
}
|
|
4852
|
+
]
|
|
4853
|
+
};
|
|
4854
|
+
}
|
|
4855
|
+
const best = /* @__PURE__ */ new Map();
|
|
4856
|
+
const currencies = /* @__PURE__ */ new Set();
|
|
4857
|
+
for (const r of rules) {
|
|
4858
|
+
if (isoOf(r) !== iso) continue;
|
|
4859
|
+
if (!isRuleAvailable(r, now)) continue;
|
|
4860
|
+
const rate = r.datarate;
|
|
4861
|
+
if (typeof rate !== "number" || !Number.isFinite(rate) || rate <= 0) continue;
|
|
4862
|
+
const id = networkIdOf(r);
|
|
4863
|
+
if (id === void 0) continue;
|
|
4864
|
+
const currency = String(
|
|
4865
|
+
r.currency?.currencycode ?? (r.currencyid !== void 0 ? `id:${r.currencyid}` : "?")
|
|
4866
|
+
);
|
|
4867
|
+
currencies.add(currency);
|
|
4868
|
+
const cur = best.get(id);
|
|
4869
|
+
if (!cur || rate < cur.datarate) {
|
|
4870
|
+
best.set(id, {
|
|
4871
|
+
networkId: id,
|
|
4872
|
+
operatorName: String(r.operator?.operatorName ?? `network ${id}`),
|
|
4873
|
+
datarate: rate,
|
|
4874
|
+
currency
|
|
4875
|
+
});
|
|
4876
|
+
}
|
|
4877
|
+
}
|
|
4878
|
+
if (best.size === 0) {
|
|
4879
|
+
return {
|
|
4880
|
+
country: iso,
|
|
4881
|
+
networks: [],
|
|
4882
|
+
candidateCount: 0,
|
|
4883
|
+
warnings: [
|
|
4884
|
+
{
|
|
4885
|
+
code: "NO_AVAILABLE_NETWORK",
|
|
4886
|
+
country: iso,
|
|
4887
|
+
message: `No network with a usable data rate was found for ${iso}, so it stays open to all networks. It was not narrowed to the cheapest.`
|
|
4888
|
+
}
|
|
4889
|
+
]
|
|
4890
|
+
};
|
|
4891
|
+
}
|
|
4892
|
+
if (currencies.size > 1) {
|
|
4893
|
+
return {
|
|
4894
|
+
country: iso,
|
|
4895
|
+
networks: [],
|
|
4896
|
+
candidateCount: best.size,
|
|
4897
|
+
warnings: [
|
|
4898
|
+
{
|
|
4899
|
+
code: "MIXED_CURRENCY",
|
|
4900
|
+
country: iso,
|
|
4901
|
+
message: `Rates for ${iso} are in more than one currency (${[...currencies].sort().join(", ")}), so they cannot be compared. It stays open to all networks.`
|
|
4902
|
+
}
|
|
4903
|
+
]
|
|
4904
|
+
};
|
|
4905
|
+
}
|
|
4906
|
+
const min = Math.min(...[...best.values()].map((n) => n.datarate));
|
|
4907
|
+
const networks = [...best.values()].filter((n) => n.datarate === min).sort((a, b) => a.networkId - b.networkId);
|
|
4908
|
+
const warnings = [];
|
|
4909
|
+
if (best.size === 1) {
|
|
4910
|
+
warnings.push({
|
|
4911
|
+
code: "SINGLE_NETWORK",
|
|
4912
|
+
country: iso,
|
|
4913
|
+
message: `Only one network is available for ${iso}, so "cheapest" and "all" are the same there.`
|
|
4914
|
+
});
|
|
4915
|
+
}
|
|
4916
|
+
return { country: iso, networks, candidateCount: best.size, warnings };
|
|
4917
|
+
}
|
|
4918
|
+
function validateNetworkChoice(choice) {
|
|
4919
|
+
if (!choice || typeof choice !== "object") return "A network choice must be an object.";
|
|
4920
|
+
const c = choice;
|
|
4921
|
+
if (!NETWORK_DEFAULT_MODES.includes(c.mode)) {
|
|
4922
|
+
return `mode must be one of: ${NETWORK_DEFAULT_MODES.join(", ")}.`;
|
|
4923
|
+
}
|
|
4924
|
+
if (c.mode === "custom") {
|
|
4925
|
+
const id = c.steeringListId;
|
|
4926
|
+
if (typeof id !== "number" || !Number.isInteger(id) || id <= 0) {
|
|
4927
|
+
return "mode custom needs steeringListId, a positive integer from list_steering_lists.";
|
|
4928
|
+
}
|
|
4929
|
+
}
|
|
4930
|
+
return null;
|
|
4931
|
+
}
|
|
4932
|
+
function pickChoice(input) {
|
|
4933
|
+
const layers = [
|
|
4934
|
+
["sim_override", input.simOverride],
|
|
4935
|
+
["package_override", input.packageOverride],
|
|
4936
|
+
["org_default", input.orgDefault]
|
|
4937
|
+
];
|
|
4938
|
+
const skipped = [];
|
|
4939
|
+
for (const [source, choice] of layers) {
|
|
4940
|
+
if (choice === null || choice === void 0) continue;
|
|
4941
|
+
const problem = validateNetworkChoice(choice);
|
|
4942
|
+
if (problem) {
|
|
4943
|
+
skipped.push({
|
|
4944
|
+
code: "INVALID_CHOICE",
|
|
4945
|
+
message: `The ${source.replace("_", " ")} is invalid and was ignored: ${problem}`
|
|
4946
|
+
});
|
|
4947
|
+
continue;
|
|
4948
|
+
}
|
|
4949
|
+
return { choice, source, skipped };
|
|
4950
|
+
}
|
|
4951
|
+
return { choice: { mode: SYSTEM_NETWORK_DEFAULT }, source: "system_default", skipped };
|
|
4952
|
+
}
|
|
4953
|
+
function resolveNetworkPlan(input) {
|
|
4954
|
+
const { choice, source, skipped } = pickChoice(input);
|
|
4955
|
+
const countries = normaliseCountries(input.countries);
|
|
4956
|
+
const warnings = [...skipped];
|
|
4957
|
+
if (choice.mode === "custom") {
|
|
4958
|
+
return {
|
|
4959
|
+
source,
|
|
4960
|
+
mode: "custom",
|
|
4961
|
+
outcome: "use_list",
|
|
4962
|
+
steeringListId: choice.steeringListId,
|
|
4963
|
+
// validated by pickChoice
|
|
4964
|
+
networks: [],
|
|
4965
|
+
perCountry: [],
|
|
4966
|
+
fellBackToAll: false,
|
|
4967
|
+
warnings,
|
|
4968
|
+
priceBasis: null
|
|
4969
|
+
};
|
|
4970
|
+
}
|
|
4971
|
+
if (choice.mode === "all") {
|
|
4972
|
+
return {
|
|
4973
|
+
source,
|
|
4974
|
+
mode: "all",
|
|
4975
|
+
outcome: "allow_all",
|
|
4976
|
+
networks: [],
|
|
4977
|
+
perCountry: [],
|
|
4978
|
+
fellBackToAll: false,
|
|
4979
|
+
warnings,
|
|
4980
|
+
priceBasis: null
|
|
4981
|
+
};
|
|
4982
|
+
}
|
|
4983
|
+
if (countries.length === 0) {
|
|
4984
|
+
warnings.push({
|
|
4985
|
+
code: "NO_COUNTRY",
|
|
4986
|
+
message: "No valid country was given, so nothing could be narrowed. The SIM stays open to all networks."
|
|
4987
|
+
});
|
|
4988
|
+
return {
|
|
4989
|
+
source,
|
|
4990
|
+
mode: "cheapest_available",
|
|
4991
|
+
outcome: "allow_all",
|
|
4992
|
+
networks: [],
|
|
4993
|
+
perCountry: [],
|
|
4994
|
+
fellBackToAll: true,
|
|
4995
|
+
warnings,
|
|
4996
|
+
priceBasis: null
|
|
4997
|
+
};
|
|
4998
|
+
}
|
|
4999
|
+
const perCountry = countries.map((c) => cheapestAvailableForCountry(input.tariffRules, c, input.now));
|
|
5000
|
+
for (const pc of perCountry) warnings.push(...pc.warnings);
|
|
5001
|
+
const byId = /* @__PURE__ */ new Map();
|
|
5002
|
+
for (const pc of perCountry) for (const n of pc.networks) byId.set(n.networkId, n);
|
|
5003
|
+
const networks = [...byId.values()].sort((a, b) => a.networkId - b.networkId);
|
|
5004
|
+
const unresolved = perCountry.some((pc) => pc.networks.length === 0);
|
|
5005
|
+
if (networks.length === 0) {
|
|
5006
|
+
return {
|
|
5007
|
+
source,
|
|
5008
|
+
mode: "cheapest_available",
|
|
5009
|
+
outcome: "allow_all",
|
|
5010
|
+
networks: [],
|
|
5011
|
+
perCountry,
|
|
5012
|
+
fellBackToAll: true,
|
|
5013
|
+
warnings,
|
|
5014
|
+
priceBasis: null
|
|
5015
|
+
};
|
|
5016
|
+
}
|
|
5017
|
+
return {
|
|
5018
|
+
source,
|
|
5019
|
+
mode: "cheapest_available",
|
|
5020
|
+
outcome: "restrict",
|
|
5021
|
+
networks,
|
|
5022
|
+
perCountry,
|
|
5023
|
+
fellBackToAll: unresolved,
|
|
5024
|
+
warnings,
|
|
5025
|
+
priceBasis: "wholesale_datarate_per_mb"
|
|
5026
|
+
};
|
|
5027
|
+
}
|
|
5028
|
+
function steeringListsFromRaw(raw) {
|
|
5029
|
+
const arr = Array.isArray(raw) ? raw : raw && typeof raw === "object" ? raw.data ?? raw.steeringLists ?? raw.list ?? [] : [];
|
|
5030
|
+
if (!Array.isArray(arr)) return [];
|
|
5031
|
+
const out = [];
|
|
5032
|
+
for (const item of arr) {
|
|
5033
|
+
if (!item || typeof item !== "object") continue;
|
|
5034
|
+
const o = item;
|
|
5035
|
+
const id = Number(o.id ?? o.steeringListId ?? o.steeringlistid);
|
|
5036
|
+
if (!Number.isInteger(id) || id <= 0) continue;
|
|
5037
|
+
const nets = o.networks ?? o.networkList ?? o.operators ?? [];
|
|
5038
|
+
const networkIds = Array.isArray(nets) ? nets.map((n) => {
|
|
5039
|
+
if (typeof n === "number") return n;
|
|
5040
|
+
if (n && typeof n === "object") {
|
|
5041
|
+
const r = n;
|
|
5042
|
+
return Number(r.networkId ?? r.networkid ?? r.id);
|
|
5043
|
+
}
|
|
5044
|
+
return NaN;
|
|
5045
|
+
}).filter((n) => Number.isInteger(n)) : [];
|
|
5046
|
+
out.push({ id, name: String(o.name ?? o.steeringListName ?? `list ${id}`), networkIds });
|
|
5047
|
+
}
|
|
5048
|
+
return out;
|
|
5049
|
+
}
|
|
5050
|
+
function planAssignment(plan, lists) {
|
|
5051
|
+
if (plan.outcome === "allow_all") {
|
|
5052
|
+
return {
|
|
5053
|
+
action: "leave_open",
|
|
5054
|
+
reason: plan.fellBackToAll ? "Fell back to all networks; see warnings. No steering list is set." : "The choice is all networks. No steering list is set."
|
|
5055
|
+
};
|
|
5056
|
+
}
|
|
5057
|
+
if (plan.outcome === "use_list") {
|
|
5058
|
+
const id = plan.steeringListId;
|
|
5059
|
+
const known = lists.some((l) => l.id === id);
|
|
5060
|
+
return known ? { action: "assign", steeringListId: id, reason: "Chosen custom list exists." } : {
|
|
5061
|
+
action: "leave_open",
|
|
5062
|
+
reason: `Steering list ${id} was not found in this reseller's lists, so nothing is assigned.`
|
|
5063
|
+
};
|
|
5064
|
+
}
|
|
5065
|
+
const want = new Set(plan.networks.map((n) => n.networkId));
|
|
5066
|
+
const match = lists.find(
|
|
5067
|
+
(l) => l.networkIds.length === want.size && l.networkIds.every((id) => want.has(id))
|
|
5068
|
+
);
|
|
5069
|
+
if (match) {
|
|
5070
|
+
return {
|
|
5071
|
+
action: "assign",
|
|
5072
|
+
steeringListId: match.id,
|
|
5073
|
+
reason: `List "${match.name}" holds exactly the cheapest networks.`
|
|
5074
|
+
};
|
|
5075
|
+
}
|
|
5076
|
+
const countries = plan.perCountry.filter((p) => p.networks.length > 0).map((p) => p.country);
|
|
5077
|
+
return {
|
|
5078
|
+
action: "needs_list",
|
|
5079
|
+
networks: plan.networks,
|
|
5080
|
+
suggestedName: `cheapest-${countries.join("-").toLowerCase()}`,
|
|
5081
|
+
reason: "No existing list holds exactly these networks. Creating one has no REST method; it needs the browser agent or the OCS dashboard."
|
|
5082
|
+
};
|
|
5083
|
+
}
|
|
5084
|
+
function tariffRulesFromRaw(raw) {
|
|
5085
|
+
if (Array.isArray(raw)) return raw;
|
|
5086
|
+
if (raw && typeof raw === "object") {
|
|
5087
|
+
const obj = raw;
|
|
5088
|
+
for (const key of ["rule", "listTariffRule", "rules", "data"]) {
|
|
5089
|
+
const v = obj[key];
|
|
5090
|
+
if (Array.isArray(v)) return v;
|
|
5091
|
+
if (v && typeof v === "object") {
|
|
5092
|
+
const nested = v.rule;
|
|
5093
|
+
if (Array.isArray(nested)) return nested;
|
|
5094
|
+
}
|
|
5095
|
+
}
|
|
5096
|
+
}
|
|
5097
|
+
return null;
|
|
5098
|
+
}
|
|
5099
|
+
var NETWORK_DEFAULT_KEY_PREFIX = "netdefault:";
|
|
5100
|
+
function networkDefaultKey(partition) {
|
|
5101
|
+
return `${NETWORK_DEFAULT_KEY_PREFIX}${partition}`;
|
|
5102
|
+
}
|
|
5103
|
+
function parseStoredNetworkDefault(raw) {
|
|
5104
|
+
if (!raw || typeof raw !== "object") return null;
|
|
5105
|
+
const r = raw;
|
|
5106
|
+
if (validateNetworkChoice(r) !== null) return null;
|
|
5107
|
+
const choice = {
|
|
5108
|
+
mode: r.mode,
|
|
5109
|
+
updatedAt: typeof r.updatedAt === "string" ? r.updatedAt : ""
|
|
5110
|
+
};
|
|
5111
|
+
if (r.mode === "custom") choice.steeringListId = r.steeringListId;
|
|
5112
|
+
if (typeof r.updatedBy === "string") choice.updatedBy = r.updatedBy;
|
|
5113
|
+
return choice;
|
|
5114
|
+
}
|
|
5115
|
+
var listNetworks = (ns) => ns.map((n) => `${n.operatorName} (${n.datarate} ${n.currency}/MB)`).join(", ");
|
|
5116
|
+
function buildNetworkPlanReport(input) {
|
|
5117
|
+
const plan = resolveNetworkPlan({
|
|
5118
|
+
countries: input.countries,
|
|
5119
|
+
tariffRules: input.tariffError ? null : tariffRulesFromRaw(input.tariffRaw),
|
|
5120
|
+
orgDefault: input.orgDefault ?? null,
|
|
5121
|
+
packageOverride: input.packageOverride ?? null,
|
|
5122
|
+
simOverride: input.simOverride ?? null,
|
|
5123
|
+
now: input.now
|
|
5124
|
+
});
|
|
5125
|
+
if (input.tariffError && plan.mode === "cheapest_available") {
|
|
5126
|
+
plan.warnings.push({
|
|
5127
|
+
code: "FETCH_FAILED",
|
|
5128
|
+
message: `The tariff could not be read (${input.tariffError}).`
|
|
5129
|
+
});
|
|
5130
|
+
}
|
|
5131
|
+
if (input.steeringListsError) {
|
|
5132
|
+
plan.warnings.push({
|
|
5133
|
+
code: "FETCH_FAILED",
|
|
5134
|
+
message: `The steering lists could not be read (${input.steeringListsError}).`
|
|
5135
|
+
});
|
|
5136
|
+
}
|
|
5137
|
+
let assignment;
|
|
5138
|
+
if (input.steeringListsError && (plan.outcome === "restrict" || plan.outcome === "use_list")) {
|
|
5139
|
+
assignment = {
|
|
5140
|
+
action: "leave_open",
|
|
5141
|
+
reason: "The steering lists could not be read, so no list can be matched or checked. Nothing is assigned."
|
|
5142
|
+
};
|
|
5143
|
+
} else {
|
|
5144
|
+
assignment = planAssignment(plan, steeringListsFromRaw(input.steeringListsRaw));
|
|
5145
|
+
}
|
|
5146
|
+
let summary;
|
|
5147
|
+
if (plan.outcome === "restrict") {
|
|
5148
|
+
summary = `Cheapest available: ${listNetworks(plan.networks)}.`;
|
|
5149
|
+
if (plan.fellBackToAll) summary += " Some countries could not be narrowed and stay open to all networks.";
|
|
5150
|
+
} else if (plan.outcome === "use_list") {
|
|
5151
|
+
summary = `Custom steering list ${plan.steeringListId}.`;
|
|
5152
|
+
} else if (plan.fellBackToAll) {
|
|
5153
|
+
summary = "FELL BACK TO ALL NETWORKS: the cheapest network could not be determined. See warnings.";
|
|
5154
|
+
} else {
|
|
5155
|
+
summary = "All networks are allowed (chosen).";
|
|
5156
|
+
}
|
|
5157
|
+
return { plan, assignment, applied: false, summary };
|
|
5158
|
+
}
|
|
5159
|
+
function networkDefaultPartition(identity) {
|
|
5160
|
+
if (identity.orgId) return identity.orgId;
|
|
5161
|
+
return `user:${identity.sub.replace(/^clerk_/, "")}`;
|
|
5162
|
+
}
|
|
5163
|
+
var fail = (status, code, error) => ({
|
|
5164
|
+
ok: false,
|
|
5165
|
+
status,
|
|
5166
|
+
error,
|
|
5167
|
+
code
|
|
5168
|
+
});
|
|
5169
|
+
var msg = (e) => e instanceof Error ? e.message : String(e);
|
|
5170
|
+
function parseOptionalChoice(value, label) {
|
|
5171
|
+
if (value === void 0 || value === null) return { choice: null };
|
|
5172
|
+
const problem = validateNetworkChoice(value);
|
|
5173
|
+
if (problem) return { error: `${label}: ${problem}` };
|
|
5174
|
+
const v = value;
|
|
5175
|
+
return { choice: v.mode === "custom" ? { mode: "custom", steeringListId: v.steeringListId } : { mode: v.mode } };
|
|
5176
|
+
}
|
|
5177
|
+
var NO_STORE_WARNING = {
|
|
5178
|
+
code: "INVALID_CHOICE",
|
|
5179
|
+
message: "This surface has no stored org default (local stdio mode), so it could not be read. Only the system default and the overrides you pass apply here."
|
|
5180
|
+
};
|
|
5181
|
+
async function getNetworkDefault(io) {
|
|
5182
|
+
const pending = io.readStored();
|
|
5183
|
+
const warnings = [];
|
|
5184
|
+
let stored = null;
|
|
5185
|
+
if (pending === void 0) {
|
|
5186
|
+
warnings.push(NO_STORE_WARNING);
|
|
5187
|
+
} else {
|
|
5188
|
+
const raw = await Promise.resolve(pending).catch(() => null);
|
|
5189
|
+
stored = parseStoredNetworkDefault(raw);
|
|
5190
|
+
if (raw !== null && raw !== void 0 && stored === null) {
|
|
5191
|
+
warnings.push({
|
|
5192
|
+
code: "INVALID_CHOICE",
|
|
5193
|
+
message: "The stored org default is unreadable and was ignored; the system default applies."
|
|
5194
|
+
});
|
|
5195
|
+
}
|
|
5196
|
+
}
|
|
5197
|
+
return {
|
|
5198
|
+
ok: true,
|
|
5199
|
+
data: {
|
|
5200
|
+
modes: NETWORK_DEFAULT_MODES,
|
|
5201
|
+
systemDefault: SYSTEM_NETWORK_DEFAULT,
|
|
5202
|
+
orgDefault: stored,
|
|
5203
|
+
effectiveMode: stored?.mode ?? SYSTEM_NETWORK_DEFAULT,
|
|
5204
|
+
warnings,
|
|
5205
|
+
appliesTo: "new_assignments_only"
|
|
5206
|
+
}
|
|
5207
|
+
};
|
|
5208
|
+
}
|
|
5209
|
+
async function setNetworkDefault(io, body, actor, now) {
|
|
5210
|
+
const parsed = parseOptionalChoice(body, "body");
|
|
5211
|
+
if ("error" in parsed) return fail(400, "VALIDATION_ERROR", parsed.error);
|
|
5212
|
+
if (parsed.choice === null) return fail(400, "VALIDATION_ERROR", "body: A network choice must be an object.");
|
|
5213
|
+
const choice = parsed.choice;
|
|
5214
|
+
if (!io.writeStored) {
|
|
5215
|
+
return fail(501, "NO_STORE", "This surface has no store for the org default. Set it through the REST API, the Console or the hosted MCP server.");
|
|
5216
|
+
}
|
|
5217
|
+
if (choice.mode === "custom") {
|
|
5218
|
+
let listsRaw;
|
|
5219
|
+
try {
|
|
5220
|
+
listsRaw = await io.callOcs("listSteeringList", await io.resolveResellerId());
|
|
5221
|
+
} catch (e) {
|
|
5222
|
+
return fail(502, "OCS_ERROR", msg(e));
|
|
5223
|
+
}
|
|
5224
|
+
if (!steeringListsFromRaw(listsRaw).some((l) => l.id === choice.steeringListId)) {
|
|
5225
|
+
return fail(400, "VALIDATION_ERROR", `Steering list ${choice.steeringListId} was not found. Pick one from list_steering_lists.`);
|
|
5226
|
+
}
|
|
5227
|
+
}
|
|
5228
|
+
const record = { ...choice, updatedAt: now.toISOString(), updatedBy: actor };
|
|
5229
|
+
await io.writeStored(record);
|
|
5230
|
+
return { ok: true, data: { orgDefault: record, appliesTo: "new_assignments_only" } };
|
|
5231
|
+
}
|
|
5232
|
+
async function planNetworkDefault(io, req, now) {
|
|
5233
|
+
if (!req || !Array.isArray(req.countries) || req.countries.length === 0) {
|
|
5234
|
+
return fail(400, "VALIDATION_ERROR", "countries required: a non-empty array of ISO-3166 alpha-2 codes");
|
|
5235
|
+
}
|
|
5236
|
+
const sim = parseOptionalChoice(req.simOverride, "simOverride");
|
|
5237
|
+
if ("error" in sim) return fail(400, "VALIDATION_ERROR", sim.error);
|
|
5238
|
+
const pkg = parseOptionalChoice(req.packageOverride, "packageOverride");
|
|
5239
|
+
if ("error" in pkg) return fail(400, "VALIDATION_ERROR", pkg.error);
|
|
5240
|
+
let resellerId;
|
|
5241
|
+
if (req.resellerId !== void 0 && req.resellerId !== null) {
|
|
5242
|
+
resellerId = Number(req.resellerId);
|
|
5243
|
+
if (!Number.isInteger(resellerId) || resellerId <= 0) {
|
|
5244
|
+
return fail(400, "VALIDATION_ERROR", "resellerId must be a positive integer");
|
|
5245
|
+
}
|
|
5246
|
+
} else {
|
|
5247
|
+
try {
|
|
5248
|
+
resellerId = await io.resolveResellerId();
|
|
5249
|
+
} catch (e) {
|
|
5250
|
+
return fail(502, "OCS_ERROR", msg(e));
|
|
5251
|
+
}
|
|
5252
|
+
}
|
|
5253
|
+
const settle = async (method) => {
|
|
5254
|
+
try {
|
|
5255
|
+
return { data: await io.callOcs(method, resellerId), error: null };
|
|
5256
|
+
} catch (e) {
|
|
5257
|
+
return { data: void 0, error: msg(e) };
|
|
5258
|
+
}
|
|
5259
|
+
};
|
|
5260
|
+
const [tariff, lists, view] = await Promise.all([settle("getCustomerTariff"), settle("listSteeringList"), getNetworkDefault(io)]);
|
|
5261
|
+
const orgDefault = view.ok && view.data.orgDefault ? view.data.orgDefault.mode === "custom" ? { mode: "custom", steeringListId: view.data.orgDefault.steeringListId } : { mode: view.data.orgDefault.mode } : null;
|
|
5262
|
+
const report = buildNetworkPlanReport({
|
|
5263
|
+
countries: req.countries.map((x) => String(x)),
|
|
5264
|
+
tariffRaw: tariff.data,
|
|
5265
|
+
tariffError: tariff.error,
|
|
5266
|
+
steeringListsRaw: lists.data,
|
|
5267
|
+
steeringListsError: lists.error,
|
|
5268
|
+
orgDefault,
|
|
5269
|
+
packageOverride: pkg.choice,
|
|
5270
|
+
simOverride: sim.choice,
|
|
5271
|
+
now
|
|
5272
|
+
});
|
|
5273
|
+
if (view.ok) report.plan.warnings.push(...view.data.warnings);
|
|
5274
|
+
return { ok: true, data: report };
|
|
5275
|
+
}
|
|
4686
5276
|
|
|
4687
5277
|
// src/lib/storefront-logo.ts
|
|
4688
5278
|
function clampHex(hex, fallback) {
|
|
@@ -4786,6 +5376,7 @@ export {
|
|
|
4786
5376
|
buildListSubscriberParams,
|
|
4787
5377
|
extractSubscriberStatus,
|
|
4788
5378
|
applySubscriberFilters,
|
|
5379
|
+
scopeListSubscriberParams,
|
|
4789
5380
|
recordToolDescription,
|
|
4790
5381
|
buildRouterCatalog,
|
|
4791
5382
|
ROUTER_RULES,
|
|
@@ -4808,6 +5399,12 @@ export {
|
|
|
4808
5399
|
networkEventRows,
|
|
4809
5400
|
summarizeNetworkEvents,
|
|
4810
5401
|
gyCountryHistoryFromResponse,
|
|
5402
|
+
NETWORK_DEFAULT_MODES,
|
|
5403
|
+
networkDefaultKey,
|
|
5404
|
+
networkDefaultPartition,
|
|
5405
|
+
getNetworkDefault,
|
|
5406
|
+
setNetworkDefault,
|
|
5407
|
+
planNetworkDefault,
|
|
4811
5408
|
generateStorefrontLogo,
|
|
4812
5409
|
run,
|
|
4813
5410
|
which,
|
|
@@ -4846,4 +5443,4 @@ export {
|
|
|
4846
5443
|
storefrontScreen,
|
|
4847
5444
|
CARRIER_VERSION
|
|
4848
5445
|
};
|
|
4849
|
-
//# sourceMappingURL=chunk-
|
|
5446
|
+
//# sourceMappingURL=chunk-DIHA6TVT.js.map
|