@apex-inc/sdk 0.25.0 → 0.28.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 +51 -7
- package/dist/esm/management.js +321 -2
- package/dist/index.d.ts +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js.map +1 -1
- package/dist/management.d.ts +464 -1
- package/dist/management.d.ts.map +1 -1
- package/dist/management.js +321 -2
- package/dist/management.js.map +1 -1
- package/package.json +1 -1
- package/skills/apex-communications/SKILL.md +9 -5
- package/skills/apex-experimentation/SKILL.md +48 -14
- package/skills/apex-growth-tracking/SKILL.md +25 -0
- package/skills/apex-integration-cookbook/SKILL.md +27 -0
- package/skills/apex-partner-network/SKILL.md +1 -0
package/dist/management.js
CHANGED
|
@@ -45,7 +45,7 @@ function communicationPipeline(commType) {
|
|
|
45
45
|
function createManagementClient(cfg) {
|
|
46
46
|
const root = baseUrl(cfg);
|
|
47
47
|
const headers = keyHeaders(cfg);
|
|
48
|
-
|
|
48
|
+
const client = {
|
|
49
49
|
async listExperiments() {
|
|
50
50
|
const res = await fetch(`${root}/api/experiments?unified=true`, { headers });
|
|
51
51
|
if (!res.ok) {
|
|
@@ -228,6 +228,29 @@ function createManagementClient(cfg) {
|
|
|
228
228
|
* Will this edit hit a live test? Advisory only — does not stamp or mail.
|
|
229
229
|
* Call before editing a page or committing.
|
|
230
230
|
*/
|
|
231
|
+
/**
|
|
232
|
+
* Who else is live on this page? Overlap lists plus same-page neighbors.
|
|
233
|
+
* Advisory only. Neighbors are different named parts on the same URL.
|
|
234
|
+
*/
|
|
235
|
+
async checkConflicts(opts) {
|
|
236
|
+
const res = await fetch(`${root}/api/experiments/check-conflicts`, {
|
|
237
|
+
method: "POST",
|
|
238
|
+
headers,
|
|
239
|
+
body: JSON.stringify({
|
|
240
|
+
experiment_id: opts.experimentId,
|
|
241
|
+
surface: opts.surface,
|
|
242
|
+
target_url: opts.targetUrl,
|
|
243
|
+
target_anchor: opts.targetAnchor,
|
|
244
|
+
target_component: opts.targetComponent,
|
|
245
|
+
data_source_id: opts.dataSourceId,
|
|
246
|
+
primary_metric_event: opts.primaryMetricEvent,
|
|
247
|
+
}),
|
|
248
|
+
});
|
|
249
|
+
if (!res.ok) {
|
|
250
|
+
throw new Error(`Apex checkConflicts failed (${res.status}): ${await res.text().catch(() => "")}`);
|
|
251
|
+
}
|
|
252
|
+
return res.json();
|
|
253
|
+
},
|
|
231
254
|
async checkLiveTests(opts) {
|
|
232
255
|
const res = await fetch(`${root}/api/experiments/check-live-tests`, {
|
|
233
256
|
method: "POST",
|
|
@@ -248,7 +271,7 @@ function createManagementClient(cfg) {
|
|
|
248
271
|
},
|
|
249
272
|
/**
|
|
250
273
|
* The shop says the change was not the thing under test. Restores a
|
|
251
|
-
* fair comparison.
|
|
274
|
+
* fair comparison. Silence also leaves the numbers fair.
|
|
252
275
|
*/
|
|
253
276
|
async markTreatmentOrthogonal(id) {
|
|
254
277
|
const res = await fetch(`${root}/api/experiments/${encodeURIComponent(id)}`, {
|
|
@@ -261,6 +284,21 @@ function createManagementClient(cfg) {
|
|
|
261
284
|
}
|
|
262
285
|
return res.json();
|
|
263
286
|
},
|
|
287
|
+
/**
|
|
288
|
+
* The shop says that change was the test. Marks the run Contaminated.
|
|
289
|
+
* Apex will not call a winner or learn from it. Offer a new run.
|
|
290
|
+
*/
|
|
291
|
+
async markTreatmentContaminated(id) {
|
|
292
|
+
const res = await fetch(`${root}/api/experiments/${encodeURIComponent(id)}`, {
|
|
293
|
+
method: "PATCH",
|
|
294
|
+
headers,
|
|
295
|
+
body: JSON.stringify({ treatment_touch: { status: "touched" } }),
|
|
296
|
+
});
|
|
297
|
+
if (!res.ok) {
|
|
298
|
+
throw new Error(`Apex markTreatmentContaminated failed (${res.status}): ${await res.text().catch(() => "")}`);
|
|
299
|
+
}
|
|
300
|
+
return res.json();
|
|
301
|
+
},
|
|
264
302
|
async activateExperiment(id) {
|
|
265
303
|
const res = await fetch(`${root}/api/experiments`, {
|
|
266
304
|
method: "PATCH",
|
|
@@ -1525,5 +1563,286 @@ function createManagementClient(cfg) {
|
|
|
1525
1563
|
},
|
|
1526
1564
|
},
|
|
1527
1565
|
};
|
|
1566
|
+
// ─── Experiments (Mobile Measurement P1 regroup, 2026-09-14) ─────────
|
|
1567
|
+
//
|
|
1568
|
+
// The experiment methods used to sit flat on the client next to
|
|
1569
|
+
// `adoption.*`, `communications.*`, `conversions.*`. They now live under
|
|
1570
|
+
// `experiments.*` like every other surface. The flat names stay as
|
|
1571
|
+
// deprecated aliases so nothing that already calls
|
|
1572
|
+
// `apexClient.listExperiments()` breaks on a minor bump.
|
|
1573
|
+
const experiments = {
|
|
1574
|
+
/** List the workspace's experiments (unified: web, code, comm, journey). */
|
|
1575
|
+
list: client.listExperiments,
|
|
1576
|
+
/** One experiment by id. */
|
|
1577
|
+
get: client.getExperiment,
|
|
1578
|
+
/** Per-arm results for an experiment. */
|
|
1579
|
+
getResults: client.getExperimentResults,
|
|
1580
|
+
/** Start serving a draft. */
|
|
1581
|
+
activate: client.activateExperiment,
|
|
1582
|
+
/** Make the winner permanent. */
|
|
1583
|
+
promote: client.promoteExperiment,
|
|
1584
|
+
/** Server-side arm for a visitor. */
|
|
1585
|
+
assignVariant: client.assignVariant,
|
|
1586
|
+
/** Would a new treatment collide with a running test? */
|
|
1587
|
+
checkConflicts: client.checkConflicts,
|
|
1588
|
+
/** Is anything live on these files / comms / journeys / milestones? */
|
|
1589
|
+
checkLiveTests: client.checkLiveTests,
|
|
1590
|
+
markTreatmentOrthogonal: client.markTreatmentOrthogonal,
|
|
1591
|
+
markTreatmentContaminated: client.markTreatmentContaminated,
|
|
1592
|
+
/**
|
|
1593
|
+
* Ad holdout (Mobile Measurement P3a): hold a share of the workspace's
|
|
1594
|
+
* known people back from the ads on one network's ad sets and measure
|
|
1595
|
+
* the installs the ads actually cause. Creates a DRAFT; call
|
|
1596
|
+
* `activate` to draw the list. Nothing counts until Apex confirms the
|
|
1597
|
+
* do-not-show list is on every ad set (`verifyAdExclusion`).
|
|
1598
|
+
*
|
|
1599
|
+
* Wraps `POST /api/experiments` with surface `paid_channel`, the same
|
|
1600
|
+
* route the dashboard wizard and the MCP `create_experiment` use.
|
|
1601
|
+
* Admin-only, and the workspace must have Ad holdouts switched on.
|
|
1602
|
+
*/
|
|
1603
|
+
async createAdHoldout(params) {
|
|
1604
|
+
const { name, hypothesis, runtimeDays, beliefId, predictionMagnitude, ...paidChannel } = params;
|
|
1605
|
+
const attachMode = paidChannel.attachMode ?? (paidChannel.network === "meta" ? "apex" : "manual");
|
|
1606
|
+
const days = runtimeDays ?? 28;
|
|
1607
|
+
const res = await fetch(`${root}/api/experiments`, {
|
|
1608
|
+
method: "POST",
|
|
1609
|
+
headers,
|
|
1610
|
+
body: JSON.stringify({
|
|
1611
|
+
surface: "paid_channel",
|
|
1612
|
+
name,
|
|
1613
|
+
hypothesis: hypothesis ??
|
|
1614
|
+
`The ads on ${paidChannel.adSetIds.length === 1 ? "this ad set" : `these ${paidChannel.adSetIds.length} ad sets`} cause installs that would not have happened without them.`,
|
|
1615
|
+
createdFrom: "sdk",
|
|
1616
|
+
status: "draft",
|
|
1617
|
+
paidChannel: {
|
|
1618
|
+
network: paidChannel.network,
|
|
1619
|
+
adAccountId: paidChannel.adAccountId,
|
|
1620
|
+
adSetIds: paidChannel.adSetIds,
|
|
1621
|
+
holdoutPercent: paidChannel.holdoutPercent,
|
|
1622
|
+
attachMode,
|
|
1623
|
+
washoutDays: paidChannel.washoutDays ?? 3,
|
|
1624
|
+
...(paidChannel.populationSegmentId ? { populationSegmentId: paidChannel.populationSegmentId } : {}),
|
|
1625
|
+
...(paidChannel.campaignNames?.length ? { campaignNames: paidChannel.campaignNames } : {}),
|
|
1626
|
+
},
|
|
1627
|
+
decisionWindow: { unit: "days", value: days, startFrom: "assignment" },
|
|
1628
|
+
...(beliefId ? { beliefId } : {}),
|
|
1629
|
+
...(predictionMagnitude
|
|
1630
|
+
? { prediction: { direction: "increase", magnitude: predictionMagnitude, provenance: "merchant" } }
|
|
1631
|
+
: {}),
|
|
1632
|
+
}),
|
|
1633
|
+
});
|
|
1634
|
+
if (!res.ok)
|
|
1635
|
+
throw new Error(`Apex experiments.createAdHoldout failed (${res.status}): ${await res.text().catch(() => "")}`);
|
|
1636
|
+
return res.json();
|
|
1637
|
+
},
|
|
1638
|
+
/**
|
|
1639
|
+
* Ask Apex to check right now whether the do-not-show list is on every
|
|
1640
|
+
* ad set the holdout covers, and record the answer. Rate-limited: a
|
|
1641
|
+
* recent check returns the last reading (`skipped: true`) instead of
|
|
1642
|
+
* re-reading the network. Wraps
|
|
1643
|
+
* `GET /api/experiments/{id}/ad-exclusion/verify`.
|
|
1644
|
+
*/
|
|
1645
|
+
async verifyAdExclusion(experimentId) {
|
|
1646
|
+
const res = await fetch(`${root}/api/experiments/${encodeURIComponent(experimentId)}/ad-exclusion/verify`, { headers });
|
|
1647
|
+
const json = (await res.json().catch(() => null));
|
|
1648
|
+
if (res.status === 429 && json?.data) {
|
|
1649
|
+
return { ...json.data, skipped: true, retryAfterSeconds: json.retryAfterSeconds };
|
|
1650
|
+
}
|
|
1651
|
+
if (!res.ok || !json?.data) {
|
|
1652
|
+
throw new Error(`Apex experiments.verifyAdExclusion failed (${res.status}): ${json?.error ?? ""}`);
|
|
1653
|
+
}
|
|
1654
|
+
return json.data;
|
|
1655
|
+
},
|
|
1656
|
+
/**
|
|
1657
|
+
* What Apex would change on the holdout's ad sets, without changing it:
|
|
1658
|
+
* each ad set's exclusion list before and after, the one id added, and
|
|
1659
|
+
* whether the workspace's standing allowance for Apex to edit ad-set
|
|
1660
|
+
* targeting is on file. Any member may call it; it writes nothing.
|
|
1661
|
+
* Meta only. Wraps `GET /api/experiments/{id}/ad-exclusion/attach`.
|
|
1662
|
+
*/
|
|
1663
|
+
async previewAdExclusion(experimentId) {
|
|
1664
|
+
const res = await fetch(`${root}/api/experiments/${encodeURIComponent(experimentId)}/ad-exclusion/attach`, { headers });
|
|
1665
|
+
const json = (await res.json().catch(() => null));
|
|
1666
|
+
if (!res.ok || !json?.data) {
|
|
1667
|
+
throw new Error(`Apex experiments.previewAdExclusion failed (${res.status}): ${json?.error ?? ""}`);
|
|
1668
|
+
}
|
|
1669
|
+
return json.data;
|
|
1670
|
+
},
|
|
1671
|
+
/**
|
|
1672
|
+
* Apex puts the held-back list on the holdout's ad sets (Mobile
|
|
1673
|
+
* Measurement P3b, Meta only). Adds exactly one exclusion per ad set;
|
|
1674
|
+
* every other targeting key is left as read, and Apex reads back to
|
|
1675
|
+
* confirm. Admin-only. The workspace must have allowed Apex to edit
|
|
1676
|
+
* ad-set targeting once: pass `acknowledge: true` on the first call,
|
|
1677
|
+
* after showing the preview; later calls ride on the allowance. Without
|
|
1678
|
+
* it the call fails with 409 `needs_targeting_ack` and nothing is edited.
|
|
1679
|
+
* Not billed. Wraps `POST /api/experiments/{id}/ad-exclusion/attach`.
|
|
1680
|
+
*/
|
|
1681
|
+
async attachAdExclusion(experimentId, opts = {}) {
|
|
1682
|
+
const res = await fetch(`${root}/api/experiments/${encodeURIComponent(experimentId)}/ad-exclusion/attach`, { method: "POST", headers, body: JSON.stringify(opts.acknowledge ? { acknowledge: true } : {}) });
|
|
1683
|
+
const json = (await res.json().catch(() => null));
|
|
1684
|
+
if (!res.ok || !json?.data) {
|
|
1685
|
+
throw new Error(`Apex experiments.attachAdExclusion failed (${res.status}${json?.code ? ` ${json.code}` : ""}): ${json?.error ?? ""}`);
|
|
1686
|
+
}
|
|
1687
|
+
return json.data;
|
|
1688
|
+
},
|
|
1689
|
+
/**
|
|
1690
|
+
* Take Apex's exclusion back off the ad sets and delete the held-back
|
|
1691
|
+
* list. Only removes what Apex added; a list the shop attached by hand
|
|
1692
|
+
* is refused with 409 `not_apex_attached`. Admin-only. Ending, pausing
|
|
1693
|
+
* or archiving the holdout does this on its own; call it to undo an
|
|
1694
|
+
* attach early. Wraps `POST /api/experiments/{id}/ad-exclusion/detach`.
|
|
1695
|
+
*/
|
|
1696
|
+
async detachAdExclusion(experimentId) {
|
|
1697
|
+
const res = await fetch(`${root}/api/experiments/${encodeURIComponent(experimentId)}/ad-exclusion/detach`, { method: "POST", headers });
|
|
1698
|
+
const json = (await res.json().catch(() => null));
|
|
1699
|
+
if (!res.ok || !json?.data) {
|
|
1700
|
+
throw new Error(`Apex experiments.detachAdExclusion failed (${res.status}${json?.code ? ` ${json.code}` : ""}): ${json?.error ?? ""}`);
|
|
1701
|
+
}
|
|
1702
|
+
return json.data;
|
|
1703
|
+
},
|
|
1704
|
+
};
|
|
1705
|
+
// ─── Apex Links ───────────────────────────────────────────────────────
|
|
1706
|
+
//
|
|
1707
|
+
// Trackable links (short URL, store routing, deferred deep link, UTMs).
|
|
1708
|
+
// Wraps `/api/mobile/links`, the same route the dashboard and MCP use.
|
|
1709
|
+
const links = {
|
|
1710
|
+
/** List the workspace's Apex Links. */
|
|
1711
|
+
async list(opts) {
|
|
1712
|
+
const qs = new URLSearchParams();
|
|
1713
|
+
if (opts?.ownerType)
|
|
1714
|
+
qs.set("ownerType", opts.ownerType);
|
|
1715
|
+
if (opts?.ownerId)
|
|
1716
|
+
qs.set("ownerId", opts.ownerId);
|
|
1717
|
+
if (opts?.enabled !== undefined)
|
|
1718
|
+
qs.set("enabled", String(opts.enabled));
|
|
1719
|
+
if (opts?.archived !== undefined)
|
|
1720
|
+
qs.set("archived", String(opts.archived));
|
|
1721
|
+
if (opts?.limit !== undefined)
|
|
1722
|
+
qs.set("limit", String(opts.limit));
|
|
1723
|
+
const q = qs.toString();
|
|
1724
|
+
const res = await fetch(`${root}/api/mobile/links${q ? `?${q}` : ""}`, { headers });
|
|
1725
|
+
if (!res.ok)
|
|
1726
|
+
throw new Error(`Apex links.list failed (${res.status}): ${await res.text().catch(() => "")}`);
|
|
1727
|
+
const json = (await res.json());
|
|
1728
|
+
return json.links ?? [];
|
|
1729
|
+
},
|
|
1730
|
+
/**
|
|
1731
|
+
* Create an Apex Link. `slug` becomes the short path; `destinationUrl`
|
|
1732
|
+
* is where the web falls back. Store fallbacks are derived from the
|
|
1733
|
+
* workspace's app config unless given.
|
|
1734
|
+
*/
|
|
1735
|
+
async create(params) {
|
|
1736
|
+
const res = await fetch(`${root}/api/mobile/links`, {
|
|
1737
|
+
method: "POST", headers, body: JSON.stringify(params),
|
|
1738
|
+
});
|
|
1739
|
+
if (!res.ok)
|
|
1740
|
+
throw new Error(`Apex links.create failed (${res.status}): ${await res.text().catch(() => "")}`);
|
|
1741
|
+
return res.json();
|
|
1742
|
+
},
|
|
1743
|
+
};
|
|
1744
|
+
// ─── Mobile Measurement ──────────────────────────────────────────────
|
|
1745
|
+
//
|
|
1746
|
+
// Install attribution, SKAN values and Ad holdouts land here as the
|
|
1747
|
+
// Mobile Measurement plan ships (P2–P3a).
|
|
1748
|
+
const mobile = {
|
|
1749
|
+
/**
|
|
1750
|
+
* SKAdNetwork conversion values: what Apple's 0–63 numbers mean for
|
|
1751
|
+
* this app. Wraps `/api/mobile/skan/schema`, the same route the
|
|
1752
|
+
* dashboard's "Say what Apple's numbers mean" step saves through.
|
|
1753
|
+
* The Capacitor plugin reads the anonymous `/resolve` twin of this
|
|
1754
|
+
* route on the device.
|
|
1755
|
+
*/
|
|
1756
|
+
skan: {
|
|
1757
|
+
/**
|
|
1758
|
+
* Read the schema. `mobileAppId` picks an app; omit it for the
|
|
1759
|
+
* workspace's first app. `schema` is null until the shop names its
|
|
1760
|
+
* values.
|
|
1761
|
+
*/
|
|
1762
|
+
async getSchema(opts) {
|
|
1763
|
+
const q = opts?.mobileAppId ? `?mobileAppId=${encodeURIComponent(opts.mobileAppId)}` : "";
|
|
1764
|
+
const res = await fetch(`${root}/api/mobile/skan/schema${q}`, { headers });
|
|
1765
|
+
if (!res.ok)
|
|
1766
|
+
throw new Error(`Apex mobile.skan.getSchema failed (${res.status}): ${await res.text().catch(() => "")}`);
|
|
1767
|
+
const json = (await res.json());
|
|
1768
|
+
return json.data;
|
|
1769
|
+
},
|
|
1770
|
+
/**
|
|
1771
|
+
* Replace the schema. Workspace admin (or an `apex_sk_` key). Range,
|
|
1772
|
+
* enum and one-value-per-event-per-window checks run server-side;
|
|
1773
|
+
* a 400 carries `problems` with a path per fault. `updatedAt` is
|
|
1774
|
+
* stamped by Apex, so you may omit it.
|
|
1775
|
+
*/
|
|
1776
|
+
async setSchema(params) {
|
|
1777
|
+
const res = await fetch(`${root}/api/mobile/skan/schema`, {
|
|
1778
|
+
method: "PUT", headers, body: JSON.stringify(params),
|
|
1779
|
+
});
|
|
1780
|
+
if (!res.ok)
|
|
1781
|
+
throw new Error(`Apex mobile.skan.setSchema failed (${res.status}): ${await res.text().catch(() => "")}`);
|
|
1782
|
+
const json = (await res.json());
|
|
1783
|
+
return json.data;
|
|
1784
|
+
},
|
|
1785
|
+
},
|
|
1786
|
+
};
|
|
1787
|
+
// ─── Beliefs ─────────────────────────────────────────────────────────
|
|
1788
|
+
//
|
|
1789
|
+
// What the shop believes and what its experiments have said about it.
|
|
1790
|
+
// `suggestions` is the "Worth writing down" rail: settled experiments
|
|
1791
|
+
// whose verdict passes the belief gate (decisive, exclusion attached the
|
|
1792
|
+
// whole window, no SRM, no guardrail breach, both arms over the events
|
|
1793
|
+
// floor) and have not been written down yet. Statistical only.
|
|
1794
|
+
const beliefs = {
|
|
1795
|
+
/** The workspace's beliefs. */
|
|
1796
|
+
async list() {
|
|
1797
|
+
const res = await fetch(`${root}/api/beliefs`, { headers });
|
|
1798
|
+
if (!res.ok)
|
|
1799
|
+
throw new Error(`Apex beliefs.list failed (${res.status}): ${await res.text().catch(() => "")}`);
|
|
1800
|
+
const json = (await res.json());
|
|
1801
|
+
return Array.isArray(json) ? json : json.data;
|
|
1802
|
+
},
|
|
1803
|
+
suggestions: {
|
|
1804
|
+
/**
|
|
1805
|
+
* Settled experiments worth writing down as a belief. Pass
|
|
1806
|
+
* `experimentId` for one experiment; then `blocker` says in one word
|
|
1807
|
+
* why it is not offered (`not_decisive`, `srm`, `guardrail_breach`,
|
|
1808
|
+
* `exclusion_not_attached`, `results_frozen`, `events_floor`,
|
|
1809
|
+
* `already_written`).
|
|
1810
|
+
*/
|
|
1811
|
+
async list(opts) {
|
|
1812
|
+
const qs = new URLSearchParams();
|
|
1813
|
+
if (opts?.experimentId)
|
|
1814
|
+
qs.set("experiment_id", opts.experimentId);
|
|
1815
|
+
if (opts?.max !== undefined)
|
|
1816
|
+
qs.set("max", String(opts.max));
|
|
1817
|
+
const q = qs.toString();
|
|
1818
|
+
const res = await fetch(`${root}/api/experiments/belief-suggestions${q ? `?${q}` : ""}`, { headers });
|
|
1819
|
+
if (!res.ok)
|
|
1820
|
+
throw new Error(`Apex beliefs.suggestions.list failed (${res.status}): ${await res.text().catch(() => "")}`);
|
|
1821
|
+
const json = (await res.json());
|
|
1822
|
+
return json.data;
|
|
1823
|
+
},
|
|
1824
|
+
/**
|
|
1825
|
+
* Write one down. Creates a Belief with the experiment as its
|
|
1826
|
+
* evidence and `generatedFromExperimentId` set; the evidence state
|
|
1827
|
+
* starts at `one_study`. `statement` is your edit of the sentence;
|
|
1828
|
+
* the numbers stay Apex's. Workspace admin (or an `apex_sk_` key).
|
|
1829
|
+
* The gate re-runs here: a 422 carries `blocker`; a 409 means it was
|
|
1830
|
+
* already written and hands back that belief.
|
|
1831
|
+
*/
|
|
1832
|
+
async accept(params) {
|
|
1833
|
+
const res = await fetch(`${root}/api/experiments/belief-suggestions/accept`, {
|
|
1834
|
+
method: "POST", headers, body: JSON.stringify(params),
|
|
1835
|
+
});
|
|
1836
|
+
const json = (await res.json().catch(() => null));
|
|
1837
|
+
if (res.status === 409 && json?.data)
|
|
1838
|
+
return json.data;
|
|
1839
|
+
if (!res.ok || !json?.data) {
|
|
1840
|
+
throw new Error(`Apex beliefs.suggestions.accept failed (${res.status}${json?.blocker ? ` ${json.blocker}` : ""}): ${json?.error ?? ""}`);
|
|
1841
|
+
}
|
|
1842
|
+
return json.data;
|
|
1843
|
+
},
|
|
1844
|
+
},
|
|
1845
|
+
};
|
|
1846
|
+
return { ...client, experiments, links, mobile, beliefs };
|
|
1528
1847
|
}
|
|
1529
1848
|
//# sourceMappingURL=management.js.map
|