@carrierllc/mcp 0.9.2 → 0.10.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 CHANGED
@@ -12,7 +12,7 @@ Natural language control of your MVNO/eSIM fleet. 43 OCS tools + 8 intelligence
12
12
  ### Interactive CLI (non-developers)
13
13
 
14
14
  ```bash
15
- npx -y @carrierllc/mcp
15
+ npx -y -p @carrierllc/mcp carrier-mcp
16
16
  # opens the Carrier TUI: status, sign-up/sign-in, install MCP, fleet NL examples
17
17
  ```
18
18
 
@@ -51,7 +51,7 @@ flags. Two rules apply everywhere:
51
51
  ### Stdio server (token env)
52
52
 
53
53
  ```bash
54
- ESIMVAULT_API_TOKEN=your_key npx -y @carrierllc/mcp
54
+ ESIMVAULT_API_TOKEN=your_key npx -y -p @carrierllc/mcp carrier-mcp
55
55
  ```
56
56
 
57
57
  Get a key at [mcp.carrier.llc](https://mcp.carrier.llc) or an org API key from the console.
@@ -67,7 +67,7 @@ Add to `~/Library/Application Support/Claude/claude_desktop_config.json` (macOS)
67
67
  "mcpServers": {
68
68
  "carrier": {
69
69
  "command": "npx",
70
- "args": ["-y", "@carrierllc/mcp"],
70
+ "args": ["-y", "-p", "@carrierllc/mcp", "carrier-mcp"],
71
71
  "env": {
72
72
  "ESIMVAULT_API_TOKEN": "YOUR_KEY"
73
73
  }
@@ -85,7 +85,7 @@ Add to `~/.cursor/mcp.json`:
85
85
  "mcpServers": {
86
86
  "carrier": {
87
87
  "command": "npx",
88
- "args": ["-y", "@carrierllc/mcp"],
88
+ "args": ["-y", "-p", "@carrierllc/mcp", "carrier-mcp"],
89
89
  "env": {
90
90
  "ESIMVAULT_API_TOKEN": "YOUR_KEY"
91
91
  }
@@ -103,7 +103,7 @@ Add to `~/.codeium/windsurf/mcp_config.json`:
103
103
  "mcpServers": {
104
104
  "carrier": {
105
105
  "command": "npx",
106
- "args": ["-y", "@carrierllc/mcp"],
106
+ "args": ["-y", "-p", "@carrierllc/mcp", "carrier-mcp"],
107
107
  "env": {
108
108
  "ESIMVAULT_API_TOKEN": "YOUR_KEY"
109
109
  }
@@ -136,6 +136,64 @@ For multi-user deployments with OAuth 2.1 + PKCE, use the remote endpoint:
136
136
  | `ESIMVAULT_API_TOKEN` | Yes | — | Your eSIMVault OCS API token |
137
137
  | `CARRIER_OCS_API_TOKEN` | Alternative | — | Alias for `ESIMVAULT_API_TOKEN` |
138
138
  | `CARRIER_OCS_BASE_URL` | No | `https://ocs.esimvault.cloud` | OCS base URL (no trailing slash) |
139
+ | `BROWSER_USE_API_KEY` | No | — | Browser Use Cloud key. Enables the browsing agent (`ui_agent_ask`). Runs are billed to this key at roughly **$0.20–1.00 each** |
140
+ | `CARRIER_BROWSER_USE_MAX_RUNS` | No | `10` | Browser-agent runs allowed per CLI process. `0` disables the agent. See the caveats below |
141
+ | `CARRIER_ALLOW_PORTAL_PASSWORD` | No | unset | Set to `1` to allow the ten OCS-dashboard tools to use a portal login from this machine. See below |
142
+ | `CARRIER_OCS_PORTAL_USERNAME` | No | — | OCS portal username. Only used when the flag above is set |
143
+ | `CARRIER_OCS_PORTAL_PASSWORD` | No | — | OCS portal password. Only used when the flag above is set |
144
+ | `CARRIER_OCS_DASHBOARD_URL` | No | `https://ocs.esimvault.cloud` | Dashboard the portal agent logs into |
145
+
146
+ ### The browser agent costs real money
147
+
148
+ `ui_agent_ask` and the OCS-dashboard tools dispatch a hosted browser agent.
149
+ Each run costs roughly **$0.20–1.00**, billed to your own `BROWSER_USE_API_KEY`.
150
+
151
+ `CARRIER_BROWSER_USE_MAX_RUNS` caps how many runs one CLI process may start.
152
+ Read what it does not do:
153
+
154
+ - It caps **runs, not dollars**. The vendor prices a run after it finishes.
155
+ - It does **not survive a restart**. Each new CLI process starts a fresh count,
156
+ so a caller that respawns the CLI between runs is not bounded by it.
157
+
158
+ The only hard financial ceiling is a spend limit set in your Browser Use
159
+ account. Set one.
160
+
161
+ ### Opting in to the OCS portal password
162
+
163
+ Ten `ui_*` tools — steering lists, sub-account creation, destination lists,
164
+ package-template deletion, location-zone edits — have no OCS REST equivalent.
165
+ They work by driving the OCS web dashboard, which means logging into it. By
166
+ default the CLI has no portal login and these tools refuse.
167
+
168
+ You can supply one. Understand what you are supplying first:
169
+
170
+ - **This is a portal password, not a scoped API token.** It grants full
171
+ dashboard access to anyone who holds it. `CARRIER_OCS_API_TOKEN` is a
172
+ different kind of secret and is not a precedent for this.
173
+ - **An environment variable is a poor place for one.** It is readable from shell
174
+ history, from `ps` output by any process on the machine, and from CI logs.
175
+ - **It is sent to Browser Use Cloud**, which types it into the OCS login form.
176
+
177
+ Carrier never logs it, never returns it in tool output, and never writes it to
178
+ an audit row — but the exposure above is yours to accept.
179
+
180
+ Two independent things must both be true, and neither alone does anything:
181
+
182
+ ```bash
183
+ export CARRIER_ALLOW_PORTAL_PASSWORD=1
184
+ export CARRIER_OCS_PORTAL_USERNAME='...'
185
+ export CARRIER_OCS_PORTAL_PASSWORD='...'
186
+ ```
187
+
188
+ With the flag set, the CLI prints the warning to stderr at startup. Without both
189
+ halves, the ten tools return the same "requires the remote Worker" refusal they
190
+ returned before this option existed.
191
+
192
+ Every one of the ten accepts `dry_run: true`, which returns the agent prompt
193
+ with the credentials redacted and dispatches nothing. Use it first.
194
+
195
+ `ui_request_reseller_relay_change` is not one of the ten and is never gated: it
196
+ drafts text and touches no portal.
139
197
 
140
198
  ---
141
199
 
@@ -1792,7 +1792,7 @@ function storefrontScreen(input) {
1792
1792
  // package.json
1793
1793
  var package_default = {
1794
1794
  name: "@carrierllc/mcp",
1795
- version: "0.9.2",
1795
+ version: "0.10.0",
1796
1796
  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).",
1797
1797
  license: "MIT",
1798
1798
  author: "Carrier (Lifecycle Innovations Limited)",
@@ -1840,7 +1840,7 @@ var package_default = {
1840
1840
  zod: "^4.4.3"
1841
1841
  },
1842
1842
  devDependencies: {
1843
- "@cloudflare/workers-types": "^4.20260521.1",
1843
+ "@cloudflare/workers-types": "^5.20260819.1",
1844
1844
  "@types/node": "^26.1.2",
1845
1845
  "@typescript-eslint/eslint-plugin": "^8.59.4",
1846
1846
  "@typescript-eslint/parser": "^8.67.0",
@@ -1876,7 +1876,113 @@ var package_default = {
1876
1876
  // src/version.ts
1877
1877
  var CARRIER_VERSION = package_default.version;
1878
1878
 
1879
- // ../../packages/ocs-client/dist/chunk-EUYDFZ33.js
1879
+ // ../../packages/ocs-client/dist/chunk-CGMK4FTD.js
1880
+ var DEFAULT_OCS_BUDGET = {
1881
+ attemptMs: 3e4,
1882
+ maxAttempts: 3,
1883
+ totalMs: 75e3
1884
+ };
1885
+ var OCS_METHOD_BUDGETS = {
1886
+ // 3.4 MB, 88-128 s measured. 180 s is 1.4x the slowest observation. One
1887
+ // attempt: payload size does not vary, so a retry only doubles the wait.
1888
+ getCustomerTariff: { attemptMs: 18e4, maxAttempts: 1, totalMs: 18e4 }
1889
+ };
1890
+ var MIN_ATTEMPT_BUDGET_MS = 1e3;
1891
+ var BACKOFF_BASE_MS = 500;
1892
+ var OcsTimeoutError = class extends Error {
1893
+ constructor(method, timeoutMs, budgetMs = timeoutMs, attempts = 1) {
1894
+ super(
1895
+ `[${method}] OCS request timed out after ${timeoutMs}ms` + (attempts > 1 ? ` on each of ${attempts} attempts` : "") + (budgetMs > timeoutMs ? ` (budget ${budgetMs}ms)` : "") + `. The upstream did not deliver a complete response in time \u2014 usually an oversized payload or a wedged upstream. Narrow the request, or raise OCS_TIMEOUT_MS / OCS_TOTAL_TIMEOUT_MS if this method is legitimately slower here.`
1896
+ );
1897
+ this.method = method;
1898
+ this.timeoutMs = timeoutMs;
1899
+ this.budgetMs = budgetMs;
1900
+ this.attempts = attempts;
1901
+ this.name = "OcsTimeoutError";
1902
+ }
1903
+ method;
1904
+ timeoutMs;
1905
+ budgetMs;
1906
+ attempts;
1907
+ };
1908
+ function envInt(name) {
1909
+ const proc = globalThis["process"];
1910
+ const parsed = Number(proc?.env?.[name]);
1911
+ return Number.isFinite(parsed) && parsed > 0 ? parsed : void 0;
1912
+ }
1913
+ function ocsBudgetFor(method) {
1914
+ const base = OCS_METHOD_BUDGETS[method] ?? DEFAULT_OCS_BUDGET;
1915
+ const attemptMs = envInt("OCS_TIMEOUT_MS") ?? base.attemptMs;
1916
+ const totalMs = Math.max(envInt("OCS_TOTAL_TIMEOUT_MS") ?? base.totalMs, attemptMs);
1917
+ return { attemptMs, maxAttempts: base.maxAttempts, totalMs };
1918
+ }
1919
+ function sleep2(ms, signal) {
1920
+ return new Promise((resolve) => {
1921
+ const timer = setTimeout(finish, ms);
1922
+ function finish() {
1923
+ clearTimeout(timer);
1924
+ signal.removeEventListener("abort", finish);
1925
+ resolve();
1926
+ }
1927
+ signal.addEventListener("abort", finish, { once: true });
1928
+ });
1929
+ }
1930
+ async function withDeadline(work, deadline, ms) {
1931
+ let timer;
1932
+ try {
1933
+ return await Promise.race([
1934
+ work,
1935
+ new Promise((_resolve, reject) => {
1936
+ timer = setTimeout(() => reject(deadline), ms);
1937
+ })
1938
+ ]);
1939
+ } finally {
1940
+ if (timer !== void 0) clearTimeout(timer);
1941
+ }
1942
+ }
1943
+ async function runWithBudget(method, attempt, budget = ocsBudgetFor(method)) {
1944
+ const deadlineAt = Date.now() + budget.totalMs;
1945
+ const budgetController = new AbortController();
1946
+ const budgetTimer = setTimeout(() => budgetController.abort(), budget.totalMs);
1947
+ let attempts = 0;
1948
+ let lastTimeout;
1949
+ try {
1950
+ for (let i = 0; i < budget.maxAttempts; i++) {
1951
+ const remaining = deadlineAt - Date.now();
1952
+ if (remaining < MIN_ATTEMPT_BUDGET_MS) break;
1953
+ const attemptMs = Math.min(budget.attemptMs, remaining);
1954
+ attempts++;
1955
+ const attemptController = new AbortController();
1956
+ const onBudgetAbort = () => attemptController.abort();
1957
+ budgetController.signal.addEventListener("abort", onBudgetAbort, { once: true });
1958
+ const attemptTimer = setTimeout(() => attemptController.abort(), attemptMs);
1959
+ const breach = new OcsTimeoutError(method, attemptMs, budget.totalMs, attempts);
1960
+ try {
1961
+ return await withDeadline(
1962
+ attempt(attemptController.signal, attemptMs),
1963
+ breach,
1964
+ attemptMs
1965
+ );
1966
+ } catch (err) {
1967
+ if (!(err instanceof OcsTimeoutError)) throw err;
1968
+ lastTimeout = new OcsTimeoutError(method, attemptMs, budget.totalMs, attempts);
1969
+ } finally {
1970
+ clearTimeout(attemptTimer);
1971
+ budgetController.signal.removeEventListener("abort", onBudgetAbort);
1972
+ attemptController.abort();
1973
+ }
1974
+ if (i === budget.maxAttempts - 1) break;
1975
+ const backoff = Math.random() * BACKOFF_BASE_MS * 2 ** i;
1976
+ if (Date.now() + backoff + MIN_ATTEMPT_BUDGET_MS > deadlineAt) break;
1977
+ await sleep2(backoff, budgetController.signal);
1978
+ }
1979
+ } finally {
1980
+ clearTimeout(budgetTimer);
1981
+ }
1982
+ throw lastTimeout ?? new OcsTimeoutError(method, budget.attemptMs, budget.totalMs, Math.max(attempts, 1));
1983
+ }
1984
+
1985
+ // ../../packages/ocs-client/dist/chunk-OPUTHPZF.js
1880
1986
  var ENDPOINT_LIMITS_PER_MIN = {
1881
1987
  // Global sentinel (keyed as "__global__")
1882
1988
  __global__: 600,
@@ -2036,6 +2142,24 @@ function getRateLimitWindowCounts(resellerKey, endpoint) {
2036
2142
  }
2037
2143
  return { calls_in_window, batch_calls_in_window };
2038
2144
  }
2145
+ var OCS_PARAM_RENAMES = {
2146
+ getResellerInfo: { resellerId: "id" }
2147
+ };
2148
+ function applyParamRenames(method, params) {
2149
+ const renames = OCS_PARAM_RENAMES[method];
2150
+ if (!renames || !params || typeof params !== "object" || Array.isArray(params)) {
2151
+ return params;
2152
+ }
2153
+ const record = params;
2154
+ let out;
2155
+ for (const [from, to] of Object.entries(renames)) {
2156
+ if (!Object.prototype.hasOwnProperty.call(record, from)) continue;
2157
+ out ??= { ...record };
2158
+ if (!Object.prototype.hasOwnProperty.call(record, to)) out[to] = record[from];
2159
+ delete out[from];
2160
+ }
2161
+ return out ?? params;
2162
+ }
2039
2163
 
2040
2164
  // ../../packages/ocs-client/dist/chunk-JWSVJVAF.js
2041
2165
  var OCS_MAX_USAGE_WINDOW_DAYS = 7;
@@ -2388,6 +2512,195 @@ var ROUTER_RULES = `Rules:
2388
2512
  7. A tool answers the question only if its description says it does. Do not infer capability from the tool's NAME: several names share words with unrelated questions, and picking on the name alone has produced confidently wrong answers.
2389
2513
  8. Read the "Do NOT use this to \u2026" steers in a description as hard exclusions. They exist because that tool is the common wrong answer for a neighbouring question, and they name the tool to use instead.
2390
2514
  9. Prefer carrier_clarify over a tool that would return real data about a different question. Answering "which subscribers erode margin" when the user asked "what do my users' megabytes cost in total" is worse than admitting the gap, because the output looks like an answer.`;
2515
+ var OCS_LIST_KEYS = {
2516
+ subscribers: ["subscriberList"],
2517
+ templates: ["template", "prepaidPackageTemplate"],
2518
+ zones: ["listDetailedLocationZone", "locationZone", "zone", "zones"],
2519
+ resellers: ["reseller"],
2520
+ packages: ["prepaidPackage", "package"]
2521
+ };
2522
+ function ocsArray(data, keys) {
2523
+ if (Array.isArray(data)) return data;
2524
+ if (data === null || typeof data !== "object") return [];
2525
+ const candidates = typeof keys === "string" ? OCS_LIST_KEYS[keys] : keys;
2526
+ const obj = data;
2527
+ for (const key of candidates) {
2528
+ const v = obj[key];
2529
+ if (Array.isArray(v)) return v;
2530
+ }
2531
+ return [];
2532
+ }
2533
+ var FACET_TALLY_MAX = 25;
2534
+ var OCS_LIST_FACETS = {
2535
+ templates: [
2536
+ { label: "recurring", paths: ["recurring"] },
2537
+ {
2538
+ label: "locationZone",
2539
+ paths: [
2540
+ "rdbLocationZones.locationzonename",
2541
+ "locationzonename",
2542
+ "locationzoneid"
2543
+ ]
2544
+ }
2545
+ ],
2546
+ subscribers: [{ label: "status", paths: ["status"] }],
2547
+ packages: [
2548
+ { label: "status", paths: ["status"] },
2549
+ { label: "recurring", paths: ["recurring"] }
2550
+ ],
2551
+ zones: [],
2552
+ resellers: []
2553
+ };
2554
+ var OCS_LIST_NESTED = {
2555
+ resellers: { label: "accounts", path: "account" }
2556
+ };
2557
+ function pagingHints(data) {
2558
+ if (data === null || typeof data !== "object" || Array.isArray(data)) {
2559
+ return { hasMore: false, nbFound: void 0 };
2560
+ }
2561
+ const obj = data;
2562
+ return {
2563
+ hasMore: obj.hasMore === true,
2564
+ nbFound: typeof obj.nbFound === "number" ? obj.nbFound : void 0
2565
+ };
2566
+ }
2567
+ function readPath(row, path) {
2568
+ let cur = row;
2569
+ for (const seg of path.split(".")) {
2570
+ if (cur === null || typeof cur !== "object") return void 0;
2571
+ cur = cur[seg];
2572
+ }
2573
+ return cur;
2574
+ }
2575
+ function facetKey(v) {
2576
+ if (v === null || v === void 0) return null;
2577
+ if (typeof v === "boolean" || typeof v === "number") return String(v);
2578
+ if (typeof v === "string") return v;
2579
+ return null;
2580
+ }
2581
+ function ocsListSummary(data, kind, facets = OCS_LIST_FACETS[kind]) {
2582
+ const rows = ocsArray(data, kind);
2583
+ const breakdown = {};
2584
+ for (const facet of facets) {
2585
+ const counts = /* @__PURE__ */ new Map();
2586
+ let missing = 0;
2587
+ for (const row of rows) {
2588
+ if (row === null || typeof row !== "object") {
2589
+ missing += 1;
2590
+ continue;
2591
+ }
2592
+ let key = null;
2593
+ for (const path of facet.paths) {
2594
+ key = facetKey(readPath(row, path));
2595
+ if (key !== null) break;
2596
+ }
2597
+ if (key === null) {
2598
+ missing += 1;
2599
+ continue;
2600
+ }
2601
+ counts.set(key, (counts.get(key) ?? 0) + 1);
2602
+ }
2603
+ if (counts.size === 0 && missing === 0) continue;
2604
+ const entry = { distinct: counts.size };
2605
+ if (counts.size > 0 && counts.size <= FACET_TALLY_MAX) {
2606
+ entry.counts = Object.fromEntries(
2607
+ [...counts].sort(([a], [b]) => a < b ? -1 : a > b ? 1 : 0)
2608
+ );
2609
+ }
2610
+ if (missing > 0) entry.missing = missing;
2611
+ breakdown[facet.label] = entry;
2612
+ }
2613
+ const { hasMore, nbFound } = pagingHints(data);
2614
+ const more = hasMore || nbFound !== void 0 && nbFound > rows.length;
2615
+ const summary = {
2616
+ total: rows.length,
2617
+ note: `Counted server-side: ${rows.length} row${rows.length === 1 ? "" : "s"} in this result. Use these figures directly rather than recounting the rows below. ` + (more ? "This is ONE PAGE \u2014 OCS holds more rows than are returned here" + (nbFound !== void 0 ? ` (${nbFound} match upstream)` : "") + ", so do not report `total` as a fleet-wide figure; narrow the filters or page for the rest." : "Nothing was truncated: these are all the rows OCS returned.")
2618
+ };
2619
+ if (more) {
2620
+ summary.moreAvailable = true;
2621
+ if (nbFound !== void 0) summary.upstreamTotal = nbFound;
2622
+ }
2623
+ const nest = OCS_LIST_NESTED[kind];
2624
+ if (nest) {
2625
+ const n = rows.reduce((acc, row) => {
2626
+ const child = row && typeof row === "object" ? readPath(row, nest.path) : void 0;
2627
+ return acc + (Array.isArray(child) ? child.length : 0);
2628
+ }, 0);
2629
+ summary.nested = { [nest.label]: n };
2630
+ summary.note += ` This envelope NESTS: \`total\` counts the outer rows, while there are ${n} ${nest.label} inside them under \`${nest.path}\`. A question about ${nest.label} is answered by that figure, not by \`total\`.`;
2631
+ }
2632
+ if (Object.keys(breakdown).length > 0) {
2633
+ summary.breakdown = breakdown;
2634
+ summary.note += " `breakdown` gives, per field, how many distinct values occur and how many rows carry each (`missing` counts rows where the field is absent).";
2635
+ }
2636
+ return summary;
2637
+ }
2638
+ function withOcsListSummary(data, kind, facets) {
2639
+ const summary = ocsListSummary(data, kind, facets);
2640
+ if (data !== null && typeof data === "object" && !Array.isArray(data)) {
2641
+ return { summary, ...data };
2642
+ }
2643
+ return { summary, [OCS_LIST_KEYS[kind][0]]: data };
2644
+ }
2645
+ var OCS_DATA_QUANTITY_TYPE = "33";
2646
+ function readBytes(entry) {
2647
+ const total = entry.total;
2648
+ if (total !== null && typeof total === "object") {
2649
+ const perType = total.quantityPerType;
2650
+ if (perType !== null && typeof perType === "object") {
2651
+ const v = perType[OCS_DATA_QUANTITY_TYPE];
2652
+ if (v !== void 0) return Number(v) || 0;
2653
+ }
2654
+ }
2655
+ return Number(entry.dataBytes ?? entry.dataVolume ?? entry.totalData ?? 0) || 0;
2656
+ }
2657
+ function extractDailyUsage(data) {
2658
+ const out = [];
2659
+ if (data === null || typeof data !== "object") return out;
2660
+ if (Array.isArray(data)) {
2661
+ for (const entry of data) {
2662
+ if (entry === null || typeof entry !== "object") continue;
2663
+ const e = entry;
2664
+ out.push({ date: String(e.date ?? e.day ?? "?"), bytes: readBytes(e) });
2665
+ }
2666
+ return out;
2667
+ }
2668
+ const usages = data.usages;
2669
+ if (!Array.isArray(usages)) return out;
2670
+ for (const usage of usages) {
2671
+ if (usage === null || typeof usage !== "object") continue;
2672
+ const periods = usage.subsPeriodUsages;
2673
+ if (!Array.isArray(periods)) continue;
2674
+ for (const period of periods) {
2675
+ if (period === null || typeof period !== "object") continue;
2676
+ const p = period;
2677
+ out.push({ date: String(p.day ?? p.date ?? "?"), bytes: readBytes(p) });
2678
+ }
2679
+ }
2680
+ return out;
2681
+ }
2682
+ function daysUntil(dateStr) {
2683
+ const now = /* @__PURE__ */ new Date();
2684
+ const target = new Date(dateStr);
2685
+ return Math.ceil((target.getTime() - now.getTime()) / (1e3 * 60 * 60 * 24));
2686
+ }
2687
+ function formatBytes(bytes) {
2688
+ if (bytes === 0) return "0 B";
2689
+ const units = ["B", "KB", "MB", "GB", "TB"];
2690
+ const i = Math.floor(Math.log(bytes) / Math.log(1024));
2691
+ return `${(bytes / Math.pow(1024, i)).toFixed(2)} ${units[i]}`;
2692
+ }
2693
+ function isActiveSubscriber(row) {
2694
+ return (extractSubscriberStatus(row) ?? "").toUpperCase() === "ACTIVE";
2695
+ }
2696
+ function filterActiveSubscribers(rows) {
2697
+ return rows.filter((row) => isActiveSubscriber(row));
2698
+ }
2699
+ function subscriberRows(raw) {
2700
+ if (Array.isArray(raw)) return raw;
2701
+ const list = raw?.subscriberList;
2702
+ return Array.isArray(list) ? list : [];
2703
+ }
2391
2704
 
2392
2705
  // src/lib/storefront-logo.ts
2393
2706
  var OPENAI_KEY_NAMES = [
@@ -2510,9 +2823,11 @@ async function generateStorefrontLogo(input) {
2510
2823
  }
2511
2824
 
2512
2825
  export {
2826
+ runWithBudget,
2513
2827
  acquireEndpointSlot,
2514
2828
  getLimitForEndpoint,
2515
2829
  getRateLimitWindowCounts,
2830
+ applyParamRenames,
2516
2831
  OCS_MAX_USAGE_WINDOW_DAYS,
2517
2832
  clampUsagePeriod,
2518
2833
  lastNDaysPeriod,
@@ -2531,6 +2846,12 @@ export {
2531
2846
  recordToolDescription,
2532
2847
  buildRouterCatalog,
2533
2848
  ROUTER_RULES,
2849
+ withOcsListSummary,
2850
+ extractDailyUsage,
2851
+ daysUntil,
2852
+ formatBytes,
2853
+ filterActiveSubscribers,
2854
+ subscriberRows,
2534
2855
  generateStorefrontLogo,
2535
2856
  run,
2536
2857
  which,
@@ -2569,4 +2890,4 @@ export {
2569
2890
  storefrontScreen,
2570
2891
  CARRIER_VERSION
2571
2892
  };
2572
- //# sourceMappingURL=chunk-4XHSOF62.js.map
2893
+ //# sourceMappingURL=chunk-DUAENMJE.js.map