@buoy-gg/mcp 7.0.33 → 7.0.34

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/index.js CHANGED
@@ -676,7 +676,7 @@ var delay = (ms) => new Promise((r) => setTimeout(r, ms));
676
676
 
677
677
  // src/server.ts
678
678
  var import_mcp = require("@modelcontextprotocol/sdk/server/mcp.js");
679
- var import_zod24 = require("zod");
679
+ var import_zod26 = require("zod");
680
680
 
681
681
  // src/tools/generic.ts
682
682
  var import_zod = require("zod");
@@ -687,7 +687,7 @@ var import_node_os = __toESM(require("os"));
687
687
  var import_node_path = __toESM(require("path"));
688
688
 
689
689
  // src/version.ts
690
- var VERSION = "7.0.33" ? "7.0.33" : "0.0.0-dev";
690
+ var VERSION = "7.0.34" ? "7.0.34" : "0.0.0-dev";
691
691
  var PACKAGE_NAME = "@buoy-gg/mcp";
692
692
 
693
693
  // src/update/checkForUpdate.ts
@@ -862,7 +862,40 @@ function errMsg(e) {
862
862
  }
863
863
 
864
864
  // src/tools/packs/perfMonitor.ts
865
+ var import_zod3 = require("zod");
866
+
867
+ // src/tools/packKit.ts
865
868
  var import_zod2 = require("zod");
869
+ var text = (s) => ({
870
+ content: [{ type: "text", text: s }]
871
+ });
872
+ var fail2 = (message) => ({
873
+ isError: true,
874
+ content: [{ type: "text", text: message }]
875
+ });
876
+ var errMsg2 = (e) => e instanceof Error ? e.message : String(e);
877
+ var deviceIdInput = import_zod2.z.string().optional().describe("Target device id (auto-selected if one is connected).");
878
+ function clampLimit(value, fallback) {
879
+ return typeof value === "number" && value > 0 ? value : fallback;
880
+ }
881
+ function staleHeader(stale) {
882
+ return stale ? `${stale}
883
+
884
+ ` : "";
885
+ }
886
+ function inactiveToolMessage(options) {
887
+ return `No ${options.toolId} result from the device. The ${options.toolId} tool may be inactive, or the app needs a newer Buoy (${options.mcpTool} requires the ${options.action} action).`;
888
+ }
889
+ function withDevice(sync, run2) {
890
+ return async (args) => {
891
+ try {
892
+ const target = sync.requireProDevice(args.deviceId);
893
+ return await run2(args, target);
894
+ } catch (e) {
895
+ return fail2(errMsg2(e));
896
+ }
897
+ };
898
+ }
866
899
 
867
900
  // src/tools/packs/perfMonitorTypes.ts
868
901
  var PERF_TOOL_ID = "perf-monitor";
@@ -1179,13 +1212,10 @@ function round(n, d) {
1179
1212
  var POLL_INTERVAL_MS = 1500;
1180
1213
  var delay2 = (ms) => new Promise((r) => setTimeout(r, ms));
1181
1214
  var json2 = (value) => ({ content: [{ type: "text", text: JSON.stringify(value, null, 2) }] });
1182
- var text = (s) => ({ content: [{ type: "text", text: s }] });
1183
- var fail2 = (message) => ({ isError: true, content: [{ type: "text", text: message }] });
1184
- var errMsg2 = (e) => e instanceof Error ? e.message : String(e);
1185
- var caseSchema = import_zod2.z.object({
1186
- name: import_zod2.z.string().describe("Display name for this variant (becomes the saved run's name)."),
1187
- params: import_zod2.z.record(import_zod2.z.string()).optional().describe('Query params applied to the route for this case, e.g. { "renderer": "v2", "count": "100" }.'),
1188
- route: import_zod2.z.string().optional().describe("Optional per-case route override; defaults to targetRoute.")
1215
+ var caseSchema = import_zod3.z.object({
1216
+ name: import_zod3.z.string().describe("Display name for this variant (becomes the saved run's name)."),
1217
+ params: import_zod3.z.record(import_zod3.z.string()).optional().describe('Query params applied to the route for this case, e.g. { "renderer": "v2", "count": "100" }.'),
1218
+ route: import_zod3.z.string().optional().describe("Optional per-case route override; defaults to targetRoute.")
1189
1219
  });
1190
1220
  async function readDeviceSettings(sync, deviceId) {
1191
1221
  try {
@@ -1304,22 +1334,22 @@ function registerPerfMonitorTools(server, sync) {
1304
1334
  {
1305
1335
  description: "Run a perf-monitor benchmark batch and return a RANKED comparison of all cases (winner by UI FPS). Give it a target route and a matrix of cases (each a name + query params that select a variant on that route); it navigates, records, and benchmarks each case on the device, then ranks them. This blocks until the batch completes (can be minutes for large batches). The app must have the target route render its variants from the params.",
1306
1336
  inputSchema: {
1307
- targetRoute: import_zod2.z.string().describe('The screen to benchmark, e.g. "/perf-test". Reads case params to pick a variant.'),
1308
- cases: import_zod2.z.array(caseSchema).min(1).describe("The variants to compare. Case #0 is the baseline for deltas."),
1309
- bounceRoute: import_zod2.z.string().optional().describe('Screen to bounce to between runs so the test screen fully remounts. Defaults to "/" (or a stub if target is "/").'),
1310
- runsPerCase: import_zod2.z.number().optional().describe("Runs per case; the median becomes the canonical result. Default 3 (1\u201310)."),
1311
- perCaseDurationMs: import_zod2.z.number().optional().describe("How long to record each run. Default 5000."),
1312
- settleMs: import_zod2.z.number().optional().describe("Idle gap after navigation before recording. Default 600."),
1313
- coolDownMs: import_zod2.z.number().optional().describe("Idle between runs/cases for thermal recovery. Default 2000."),
1314
- navTimeoutMs: import_zod2.z.number().optional().describe("Max wait for navigation per run. Default 5000."),
1315
- discardWarmupRuns: import_zod2.z.number().optional().describe("Discard first N runs of each case before taking the median. Default 0."),
1316
- shuffleCases: import_zod2.z.boolean().optional().describe("Interleave + shuffle case order to cancel thermal bias. Default on for >3 cases."),
1317
- reloadBetweenCases: import_zod2.z.boolean().optional().describe("Fully reload JS between cases (needs <AutomationResumer/> mounted). Default false."),
1318
- captureRenders: import_zod2.z.boolean().optional().describe(
1337
+ targetRoute: import_zod3.z.string().describe('The screen to benchmark, e.g. "/perf-test". Reads case params to pick a variant.'),
1338
+ cases: import_zod3.z.array(caseSchema).min(1).describe("The variants to compare. Case #0 is the baseline for deltas."),
1339
+ bounceRoute: import_zod3.z.string().optional().describe('Screen to bounce to between runs so the test screen fully remounts. Defaults to "/" (or a stub if target is "/").'),
1340
+ runsPerCase: import_zod3.z.number().optional().describe("Runs per case; the median becomes the canonical result. Default 3 (1\u201310)."),
1341
+ perCaseDurationMs: import_zod3.z.number().optional().describe("How long to record each run. Default 5000."),
1342
+ settleMs: import_zod3.z.number().optional().describe("Idle gap after navigation before recording. Default 600."),
1343
+ coolDownMs: import_zod3.z.number().optional().describe("Idle between runs/cases for thermal recovery. Default 2000."),
1344
+ navTimeoutMs: import_zod3.z.number().optional().describe("Max wait for navigation per run. Default 5000."),
1345
+ discardWarmupRuns: import_zod3.z.number().optional().describe("Discard first N runs of each case before taking the median. Default 0."),
1346
+ shuffleCases: import_zod3.z.boolean().optional().describe("Interleave + shuffle case order to cancel thermal bias. Default on for >3 cases."),
1347
+ reloadBetweenCases: import_zod3.z.boolean().optional().describe("Fully reload JS between cases (needs <AutomationResumer/> mounted). Default false."),
1348
+ captureRenders: import_zod3.z.boolean().optional().describe(
1319
1349
  "Capture per-component render counts/durations during each run (needs @buoy-gg/highlight-updates installed; dev builds only). Adds slight JS-thread overhead applied equally to every case. Default true."
1320
1350
  ),
1321
- deviceId: import_zod2.z.string().optional().describe("Target device id. Omit to auto-select when one device is connected."),
1322
- maxWaitMs: import_zod2.z.number().optional().describe("Safety cap for how long to wait for completion. Defaults to ~3\xD7 the estimate + 60s.")
1351
+ deviceId: import_zod3.z.string().optional().describe("Target device id. Omit to auto-select when one device is connected."),
1352
+ maxWaitMs: import_zod3.z.number().optional().describe("Safety cap for how long to wait for completion. Defaults to ~3\xD7 the estimate + 60s.")
1323
1353
  }
1324
1354
  },
1325
1355
  async (args) => {
@@ -1389,7 +1419,7 @@ ${JSON.stringify(summary, null, 2)}
1389
1419
  "get_automation_status",
1390
1420
  {
1391
1421
  description: "Get the perf-monitor automation batch status (current phase + last completed batch) without waiting.",
1392
- inputSchema: { deviceId: import_zod2.z.string().optional().describe("Target device id (auto-selected if one is connected).") }
1422
+ inputSchema: { deviceId: import_zod3.z.string().optional().describe("Target device id (auto-selected if one is connected).") }
1393
1423
  },
1394
1424
  async ({ deviceId }) => {
1395
1425
  try {
@@ -1410,8 +1440,8 @@ ${JSON.stringify(summary, null, 2)}
1410
1440
  {
1411
1441
  description: "Block until the in-flight perf-monitor batch finishes, then return its ranked comparison. Use this if a batch was started another way (e.g. on the device).",
1412
1442
  inputSchema: {
1413
- deviceId: import_zod2.z.string().optional().describe("Target device id (auto-selected if one is connected)."),
1414
- maxWaitMs: import_zod2.z.number().optional().describe("Max time to wait. Default 600000 (10 min).")
1443
+ deviceId: deviceIdInput,
1444
+ maxWaitMs: import_zod3.z.number().optional().describe("Max time to wait. Default 600000 (10 min).")
1415
1445
  }
1416
1446
  },
1417
1447
  async ({ deviceId, maxWaitMs }) => {
@@ -1484,8 +1514,8 @@ ${JSON.stringify(ranking, null, 2)}
1484
1514
  {
1485
1515
  description: "Re-rank a previously-run benchmark batch by its batchId (from the saved recordings index).",
1486
1516
  inputSchema: {
1487
- batchId: import_zod2.z.string().describe("The batch id to report on."),
1488
- deviceId: import_zod2.z.string().optional().describe("Target device id (auto-selected if one is connected).")
1517
+ batchId: import_zod3.z.string().describe("The batch id to report on."),
1518
+ deviceId: deviceIdInput
1489
1519
  }
1490
1520
  },
1491
1521
  async ({ batchId, deviceId }) => {
@@ -1512,9 +1542,9 @@ ${JSON.stringify(ranking, null, 2)}
1512
1542
  {
1513
1543
  description: "Compare two saved benchmark runs head-to-head: avg + p95 for FPS/CPU/memory, jank, AND render counts \u2014 total, commits, and wasted (parent cascades + identity-only prop churn) \u2014 each with deltas. Use this to prove a memoization or re-render fix actually landed: the wasted-render delta is the number that says so.",
1514
1544
  inputSchema: {
1515
- reportIdA: import_zod2.z.string().describe("First (baseline) report id."),
1516
- reportIdB: import_zod2.z.string().describe("Second report id to compare against the baseline."),
1517
- deviceId: import_zod2.z.string().optional().describe("Target device id (auto-selected if one is connected).")
1545
+ reportIdA: import_zod3.z.string().describe("First (baseline) report id."),
1546
+ reportIdB: import_zod3.z.string().describe("Second report id to compare against the baseline."),
1547
+ deviceId: deviceIdInput
1518
1548
  }
1519
1549
  },
1520
1550
  async ({ reportIdA, reportIdB, deviceId }) => {
@@ -1542,8 +1572,8 @@ ${JSON.stringify(ranking, null, 2)}
1542
1572
  {
1543
1573
  description: "Read the device's saved benchmark settings (per-page duration, runs, cooldown, reload, shuffle) in plain English with a time estimate. Call this before run_benchmark_batch so you can tell the user what the run will do \u2014 run_benchmark_batch inherits these unless you override them.",
1544
1574
  inputSchema: {
1545
- pages: import_zod2.z.number().optional().describe("How many pages/variants you plan to test, for a total-time estimate. Default 1."),
1546
- deviceId: import_zod2.z.string().optional().describe("Target device id (auto-selected if one is connected).")
1575
+ pages: import_zod3.z.number().optional().describe("How many pages/variants you plan to test, for a total-time estimate. Default 1."),
1576
+ deviceId: deviceIdInput
1547
1577
  }
1548
1578
  },
1549
1579
  async ({ pages, deviceId }) => {
@@ -1573,16 +1603,16 @@ ${JSON.stringify(settings, null, 2)}
1573
1603
  {
1574
1604
  description: `PERSIST benchmark settings on the device so they stick across runs. Only use when the user explicitly wants to save a change ("always", "save this"). For a one-off, pass overrides to run_benchmark_batch instead \u2014 don't change their saved profile.`,
1575
1605
  inputSchema: {
1576
- perCaseDurationMs: import_zod2.z.number().optional().describe("Recording length per page (ms)."),
1577
- settleMs: import_zod2.z.number().optional().describe("Idle after navigation before recording (ms)."),
1578
- coolDownMs: import_zod2.z.number().optional().describe("Idle between runs/cases (ms)."),
1579
- navTimeoutMs: import_zod2.z.number().optional().describe("Max wait for navigation per run (ms)."),
1580
- runsPerCase: import_zod2.z.number().optional().describe("Runs per page; median is the canonical result (1\u201310)."),
1581
- discardWarmupRuns: import_zod2.z.number().optional().describe("Discard first N runs of each page before the median."),
1582
- shuffleCases: import_zod2.z.boolean().optional().describe("Interleave + shuffle case order to cancel thermal bias."),
1583
- reloadBetweenCases: import_zod2.z.boolean().optional().describe("Fully reload JS between pages (needs <AutomationResumer/> mounted)."),
1584
- captureRenders: import_zod2.z.boolean().optional().describe("Capture per-component render counts/durations during each run."),
1585
- deviceId: import_zod2.z.string().optional().describe("Target device id (auto-selected if one is connected).")
1606
+ perCaseDurationMs: import_zod3.z.number().optional().describe("Recording length per page (ms)."),
1607
+ settleMs: import_zod3.z.number().optional().describe("Idle after navigation before recording (ms)."),
1608
+ coolDownMs: import_zod3.z.number().optional().describe("Idle between runs/cases (ms)."),
1609
+ navTimeoutMs: import_zod3.z.number().optional().describe("Max wait for navigation per run (ms)."),
1610
+ runsPerCase: import_zod3.z.number().optional().describe("Runs per page; median is the canonical result (1\u201310)."),
1611
+ discardWarmupRuns: import_zod3.z.number().optional().describe("Discard first N runs of each page before the median."),
1612
+ shuffleCases: import_zod3.z.boolean().optional().describe("Interleave + shuffle case order to cancel thermal bias."),
1613
+ reloadBetweenCases: import_zod3.z.boolean().optional().describe("Fully reload JS between pages (needs <AutomationResumer/> mounted)."),
1614
+ captureRenders: import_zod3.z.boolean().optional().describe("Capture per-component render counts/durations during each run."),
1615
+ deviceId: deviceIdInput
1586
1616
  }
1587
1617
  },
1588
1618
  async (args) => {
@@ -1630,11 +1660,8 @@ ${JSON.stringify(saved ?? settings, null, 2)}
1630
1660
  }
1631
1661
 
1632
1662
  // src/tools/packs/events.ts
1633
- var import_zod3 = require("zod");
1663
+ var import_zod4 = require("zod");
1634
1664
  var EVENTS_TOOL_ID = "events";
1635
- var text2 = (s) => ({ content: [{ type: "text", text: s }] });
1636
- var fail3 = (message) => ({ isError: true, content: [{ type: "text", text: message }] });
1637
- var errMsg3 = (e) => e instanceof Error ? e.message : String(e);
1638
1665
  var SOURCE_ALIASES = {
1639
1666
  storage: ["storage-async", "storage-mmkv"],
1640
1667
  "react-query": ["react-query", "react-query-query", "react-query-mutation"]
@@ -1649,13 +1676,13 @@ function registerEventsTools(server, sync) {
1649
1676
  {
1650
1677
  description: "The UNIVERSAL, token-bounded activity reader for what's happening in the app across ALL Buoy tools. Check this timeline BEFORE driving the UI to reproduce a bug \u2014 the failure is usually already recorded here. The events tool aggregates every source \u2014 network requests, redux/zustand/jotai actions, react-query query/mutation updates, storage writes, route changes, renders \u2014 so filter to one with `sources`: ['network'] lists HTTP requests, ['redux'] dispatched actions, ['react-query'] query updates, ['storage'] writes, ['route'] navigation. Returns a compact one-line summary per event (source \xB7 status \xB7 title \xB7 timing) by DEFAULT \u2014 not the heavy raw payloads \u2014 so it's cheap. Re-call with includeData:true, a bigger limit, or specific sources only when you need detail. PREFER this over get_snapshot (and over reading source code) for any 'what happened' / 'show me the requests/actions' question; get_snapshot is the raw heavy current-state escape hatch.",
1651
1678
  inputSchema: {
1652
- limit: import_zod3.z.number().optional().describe("Most-recent N events to include. Default 25."),
1653
- sources: import_zod3.z.array(import_zod3.z.string()).optional().describe('Only these sources. Coarse names accepted: "network", "redux", "react-query", "storage", "route", "render", "zustand", "jotai". Omit for all.'),
1654
- status: import_zod3.z.enum(["all", "errors", "success", "pending"]).optional().describe("Filter by status. Default all."),
1655
- includeData: import_zod3.z.boolean().optional().describe("Include each event's full raw payload (HEAVY \u2014 network bodies, state trees). Default false (compact)."),
1656
- format: import_zod3.z.enum(["markdown", "json", "text", "mermaid"]).optional().describe("Output format. Default markdown."),
1657
- preset: import_zod3.z.enum(["llm", "bugReport", "json", "errors", "minimal", "mermaid"]).optional().describe("Start from a named Copy-Settings preset (then your other args override it)."),
1658
- deviceId: import_zod3.z.string().optional().describe("Target device id (auto-selected if one is connected).")
1679
+ limit: import_zod4.z.number().optional().describe("Most-recent N events to include. Default 25."),
1680
+ sources: import_zod4.z.array(import_zod4.z.string()).optional().describe('Only these sources. Coarse names accepted: "network", "redux", "react-query", "storage", "route", "render", "zustand", "jotai". Omit for all.'),
1681
+ status: import_zod4.z.enum(["all", "errors", "success", "pending"]).optional().describe("Filter by status. Default all."),
1682
+ includeData: import_zod4.z.boolean().optional().describe("Include each event's full raw payload (HEAVY \u2014 network bodies, state trees). Default false (compact)."),
1683
+ format: import_zod4.z.enum(["markdown", "json", "text", "mermaid"]).optional().describe("Output format. Default markdown."),
1684
+ preset: import_zod4.z.enum(["llm", "bugReport", "json", "errors", "minimal", "mermaid"]).optional().describe("Start from a named Copy-Settings preset (then your other args override it)."),
1685
+ deviceId: deviceIdInput
1659
1686
  }
1660
1687
  },
1661
1688
  async (args) => {
@@ -1674,7 +1701,7 @@ function registerEventsTools(server, sync) {
1674
1701
  target
1675
1702
  );
1676
1703
  if (!res || typeof res.output !== "string") {
1677
- return fail3(
1704
+ return fail2(
1678
1705
  "No events result from the device. The events tool may be inactive, or the app needs a newer Buoy (get_events requires the exportEvents action)."
1679
1706
  );
1680
1707
  }
@@ -1682,20 +1709,17 @@ function registerEventsTools(server, sync) {
1682
1709
  const note = `
1683
1710
 
1684
1711
  _Showing ${res.returned} of ${res.totalAvailable} captured event(s)${more}._`;
1685
- return text2((res.output || "(no events captured)") + note);
1712
+ return text((res.output || "(no events captured)") + note);
1686
1713
  } catch (e) {
1687
- return fail3(errMsg3(e));
1714
+ return fail2(errMsg2(e));
1688
1715
  }
1689
1716
  }
1690
1717
  );
1691
1718
  }
1692
1719
 
1693
1720
  // src/tools/packs/network.ts
1694
- var import_zod4 = require("zod");
1721
+ var import_zod5 = require("zod");
1695
1722
  var NETWORK_TOOL_ID = "network";
1696
- var text3 = (s) => ({ content: [{ type: "text", text: s }] });
1697
- var fail4 = (message) => ({ isError: true, content: [{ type: "text", text: message }] });
1698
- var errMsg4 = (e) => e instanceof Error ? e.message : String(e);
1699
1723
  var masterNote = (state) => {
1700
1724
  if (state?.enabled !== false) return "";
1701
1725
  return state.autoPaused ? ' \u2014 AUTO-PAUSED after the app failed to settle on launch with overrides active; nothing is firing. Re-arm with network_override({action:"enable"}).' : " \u2014 MASTER SWITCH IS OFF, nothing is firing";
@@ -1821,12 +1845,12 @@ function registerNetworkTools(server, sync) {
1821
1845
  {
1822
1846
  description: "List captured HTTP requests WITH their request ids, plus which ones are pinned/saved. API data wrong or calls failing \u2192 read the actual request+response bodies here instead of guessing from code; capture starts at app launch, so boot-time requests (session bootstrap, first queries) are included. Use this (not get_events) when you need to ACT on a request \u2014 every network action is keyed by the id, and get_events does not emit ids. Compact by default: one line per request (id \xB7 status \xB7 method \xB7 path \xB7 timing \xB7 size). Set flagged:'pinned'|'saved'|'any' to read the developer's KEPT requests \u2014 those are snapshots that outlive Clear, the 500-request cap and app restarts, so treat a pinned request as 'a human flagged this as the problem'. Filter with status:'errors' to go straight to failures, or `pattern` to match a URL substring. includeBodies:true adds truncated request/response payloads (heavier).",
1823
1847
  inputSchema: {
1824
- limit: import_zod4.z.number().optional().describe("Most-recent N requests. Default 25."),
1825
- pattern: import_zod4.z.string().optional().describe("Only requests whose URL contains this substring (case-insensitive)."),
1826
- status: import_zod4.z.enum(["all", "errors", "success", "pending"]).optional().describe("Filter by outcome. Default all."),
1827
- flagged: import_zod4.z.enum(["pinned", "saved", "any"]).optional().describe("Read KEPT requests instead of the live list: 'pinned' (held at the top of the developer's list), 'saved' (their favorites), 'any' (either). These survive Clear and app restarts."),
1828
- includeBodies: import_zod4.z.boolean().optional().describe("Include truncated request/response bodies. Default false (compact)."),
1829
- deviceId: import_zod4.z.string().optional().describe("Target device id (auto-selected if one is connected).")
1848
+ limit: import_zod5.z.number().optional().describe("Most-recent N requests. Default 25."),
1849
+ pattern: import_zod5.z.string().optional().describe("Only requests whose URL contains this substring (case-insensitive)."),
1850
+ status: import_zod5.z.enum(["all", "errors", "success", "pending"]).optional().describe("Filter by outcome. Default all."),
1851
+ flagged: import_zod5.z.enum(["pinned", "saved", "any"]).optional().describe("Read KEPT requests instead of the live list: 'pinned' (held at the top of the developer's list), 'saved' (their favorites), 'any' (either). These survive Clear and app restarts."),
1852
+ includeBodies: import_zod5.z.boolean().optional().describe("Include truncated request/response bodies. Default false (compact)."),
1853
+ deviceId: deviceIdInput
1830
1854
  }
1831
1855
  },
1832
1856
  async (args) => {
@@ -1850,7 +1874,7 @@ function registerNetworkTools(server, sync) {
1850
1874
  );
1851
1875
  const rows2 = wanted.filter((r) => r.event && matchesStatus(r.event, args.status)).filter((r) => !pattern || (r.event.url ?? "").toLowerCase().includes(pattern)).slice(0, limit);
1852
1876
  if (rows2.length === 0) {
1853
- return text3(
1877
+ return text(
1854
1878
  `No ${args.flagged === "any" ? "pinned or saved" : args.flagged} requests on ${target}.
1855
1879
 
1856
1880
  The developer pins/saves a request from the Network tool (long-press a row, or the pin/bookmark buttons in the request detail header), or you can with network_action.`
@@ -1867,7 +1891,7 @@ The developer pins/saves a request from the Network tool (long-press a row, or t
1867
1891
  if (args.includeBodies) out += bodyLines(r.event, kept.bodies);
1868
1892
  return out;
1869
1893
  }).join("\n");
1870
- return text3(
1894
+ return text(
1871
1895
  `**Kept requests on ${target}** \u2014 ${rows2.length} of ${wanted.length}
1872
1896
 
1873
1897
  ${body2}
@@ -1878,7 +1902,7 @@ _These are snapshots: they survive Clear, the 500-request cap and app restarts.
1878
1902
  const rows = events.filter((e) => matchesStatus(e, args.status)).filter((e) => !pattern || (e.url ?? "").toLowerCase().includes(pattern)).slice(0, limit);
1879
1903
  if (rows.length === 0) {
1880
1904
  const why = events.length === 0 ? await captureDiagnosis(sync, target) : "No request matched the filter.";
1881
- return text3(`No requests on ${target}. ${why}`);
1905
+ return text(`No requests on ${target}. ${why}`);
1882
1906
  }
1883
1907
  const live = args.includeBodies ? await resolveBodies(sync, rows, target) : { bodies: /* @__PURE__ */ new Map(), skipped: 0 };
1884
1908
  const body = rows.map((e, i) => {
@@ -1888,7 +1912,7 @@ _These are snapshots: they survive Clear, the 500-request cap and app restarts.
1888
1912
  return out;
1889
1913
  }).join("\n");
1890
1914
  const keptNote = saved.length > 0 ? ` \xB7 ${saved.length} kept (flagged:'any' to list)` : "";
1891
- return text3(
1915
+ return text(
1892
1916
  `**Network requests on ${target}** \u2014 ${rows.length} of ${events.length}${keptNote}
1893
1917
 
1894
1918
  ${body}
@@ -1896,7 +1920,7 @@ ${body}
1896
1920
  _\u{1F4CC} pinned \xB7 \u{1F516} saved. Act on one with its id, e.g. network_action({action:"pin", id:"${rows[0].id}"})._${skippedNote(live.skipped)}`
1897
1921
  );
1898
1922
  } catch (e) {
1899
- return fail4(errMsg4(e));
1923
+ return fail2(errMsg2(e));
1900
1924
  }
1901
1925
  }
1902
1926
  );
@@ -1905,10 +1929,10 @@ _\u{1F4CC} pinned \xB7 \u{1F516} saved. Act on one with its id, e.g. network_act
1905
1929
  {
1906
1930
  description: "Act on captured HTTP requests: PIN one (holds it at the top of the developer's Network list, ignoring their filters), SAVE one to their favorites, or clear the list. Pinning and saving keep a full snapshot that survives Clear, the 500-request cap and app restarts \u2014 so this is how you hand a request to a human ('I pinned the failing call') or keep one you'll need after the log rolls over. Get ids from get_network_requests (get_events does not emit them). `remove` takes the record `key` from get_network_requests({flagged:'any'}), not a request id.",
1907
1931
  inputSchema: {
1908
- action: import_zod4.z.enum(["pin", "unpin", "save", "unsave", "remove", "clearPinned", "clearSaved", "clearRequests"]).describe("pin/unpin/save/unsave act on one request by `id`. remove drops one kept record by `key`. clearPinned/clearSaved drop all of that kind. clearRequests wipes the live capture list (kept requests survive)."),
1909
- id: import_zod4.z.string().optional().describe("Request id for pin/unpin/save/unsave \u2014 from get_network_requests."),
1910
- key: import_zod4.z.string().optional().describe("Kept-record key for `remove` \u2014 from get_network_requests({flagged:'any'})."),
1911
- deviceId: import_zod4.z.string().optional().describe("Target device id (auto-selected if one is connected).")
1932
+ action: import_zod5.z.enum(["pin", "unpin", "save", "unsave", "remove", "clearPinned", "clearSaved", "clearRequests"]).describe("pin/unpin/save/unsave act on one request by `id`. remove drops one kept record by `key`. clearPinned/clearSaved drop all of that kind. clearRequests wipes the live capture list (kept requests survive)."),
1933
+ id: import_zod5.z.string().optional().describe("Request id for pin/unpin/save/unsave \u2014 from get_network_requests."),
1934
+ key: import_zod5.z.string().optional().describe("Kept-record key for `remove` \u2014 from get_network_requests({flagged:'any'})."),
1935
+ deviceId: deviceIdInput
1912
1936
  }
1913
1937
  },
1914
1938
  async (args) => {
@@ -1916,10 +1940,10 @@ _\u{1F4CC} pinned \xB7 \u{1F516} saved. Act on one with its id, e.g. network_act
1916
1940
  const target = sync.requireProDevice(args.deviceId);
1917
1941
  const needsId = ["pin", "unpin", "save", "unsave"].includes(args.action);
1918
1942
  if (needsId && !args.id) {
1919
- return fail4(`"${args.action}" needs an \`id\`. List requests with get_network_requests to get one.`);
1943
+ return fail2(`"${args.action}" needs an \`id\`. List requests with get_network_requests to get one.`);
1920
1944
  }
1921
1945
  if (args.action === "remove" && !args.key) {
1922
- return fail4('"remove" needs a `key` (not a request id). Get it from get_network_requests({flagged:"any"}).');
1946
+ return fail2('"remove" needs a `key` (not a request id). Get it from get_network_requests({flagged:"any"}).');
1923
1947
  }
1924
1948
  switch (args.action) {
1925
1949
  case "pin":
@@ -1930,10 +1954,10 @@ _\u{1F4CC} pinned \xB7 \u{1F516} saved. Act on one with its id, e.g. network_act
1930
1954
  { id: args.id, pinned: args.action === "pin" },
1931
1955
  target
1932
1956
  );
1933
- if (res === null) return fail4(`No request "${args.id}" on ${target}. It may have been cleared or aged out \u2014 re-list with get_network_requests.`);
1957
+ if (res === null) return fail2(`No request "${args.id}" on ${target}. It may have been cleared or aged out \u2014 re-list with get_network_requests.`);
1934
1958
  const capped = typeof res === "object" && res !== null && res.reason === "pin-cap";
1935
- if (capped) return fail4(`Pin limit reached on ${target}. Unpin something first (network_action({action:"clearPinned"}) drops them all).`);
1936
- return text3(`${args.action === "pin" ? "\u{1F4CC} Pinned" : "Unpinned"} \`${args.id}\` on ${target}.`);
1959
+ if (capped) return fail2(`Pin limit reached on ${target}. Unpin something first (network_action({action:"clearPinned"}) drops them all).`);
1960
+ return text(`${args.action === "pin" ? "\u{1F4CC} Pinned" : "Unpinned"} \`${args.id}\` on ${target}.`);
1937
1961
  }
1938
1962
  case "save":
1939
1963
  case "unsave": {
@@ -1943,26 +1967,26 @@ _\u{1F4CC} pinned \xB7 \u{1F516} saved. Act on one with its id, e.g. network_act
1943
1967
  { id: args.id, saved: args.action === "save" },
1944
1968
  target
1945
1969
  );
1946
- if (res === null) return fail4(`No request "${args.id}" on ${target}. It may have been cleared or aged out \u2014 re-list with get_network_requests.`);
1970
+ if (res === null) return fail2(`No request "${args.id}" on ${target}. It may have been cleared or aged out \u2014 re-list with get_network_requests.`);
1947
1971
  const capped = typeof res === "object" && res !== null && res.reason === "saved-cap";
1948
- if (capped) return fail4(`Saved-request limit reached on ${target}. Remove one first, or network_action({action:"clearSaved"}).`);
1949
- return text3(`${args.action === "save" ? "\u{1F516} Saved" : "Unsaved"} \`${args.id}\` on ${target}.`);
1972
+ if (capped) return fail2(`Saved-request limit reached on ${target}. Remove one first, or network_action({action:"clearSaved"}).`);
1973
+ return text(`${args.action === "save" ? "\u{1F516} Saved" : "Unsaved"} \`${args.id}\` on ${target}.`);
1950
1974
  }
1951
1975
  case "remove":
1952
1976
  await sync.callAction(NETWORK_TOOL_ID, "removeSavedRecord", { key: args.key }, target);
1953
- return text3(`Removed kept record \`${args.key}\` on ${target}.`);
1977
+ return text(`Removed kept record \`${args.key}\` on ${target}.`);
1954
1978
  case "clearPinned":
1955
1979
  await sync.callAction(NETWORK_TOOL_ID, "clearPinnedRequests", {}, target);
1956
- return text3(`Unpinned all requests on ${target} (saved ones kept).`);
1980
+ return text(`Unpinned all requests on ${target} (saved ones kept).`);
1957
1981
  case "clearSaved":
1958
1982
  await sync.callAction(NETWORK_TOOL_ID, "clearSavedRequests", {}, target);
1959
- return text3(`Cleared saved requests on ${target} (pinned ones kept).`);
1983
+ return text(`Cleared saved requests on ${target} (pinned ones kept).`);
1960
1984
  case "clearRequests":
1961
1985
  await sync.callAction(NETWORK_TOOL_ID, "clearEvents", {}, target);
1962
- return text3(`Cleared the captured request list on ${target}. Pinned/saved requests are unaffected.`);
1986
+ return text(`Cleared the captured request list on ${target}. Pinned/saved requests are unaffected.`);
1963
1987
  }
1964
1988
  } catch (e) {
1965
- return fail4(errMsg4(e));
1989
+ return fail2(errMsg2(e));
1966
1990
  }
1967
1991
  }
1968
1992
  );
@@ -1971,48 +1995,48 @@ _\u{1F4CC} pinned \xB7 \u{1F516} saved. Act on one with its id, e.g. network_act
1971
1995
  {
1972
1996
  description: "Force matching HTTP requests to return a chosen status/body, to fail outright, or to arrive late \u2014 without touching the backend or the app's code. This is how you drive an app into a state it won't reach on its own: force a 500 and see whether the error UI appears, return [] and check the empty state, simulate offline or a slow connection. Rules run INSIDE the app and survive an app reload, so they also cover the boot path. After adding one, trigger the request (navigate/tap_element) and read the result with get_network_requests or describe_screen. ALWAYS remove your rules when done \u2014 a forgotten rule looks exactly like a real bug. Requires Buoy Pro.",
1973
1997
  inputSchema: {
1974
- action: import_zod4.z.enum(["list", "add", "remove", "clear", "enable", "disable", "debug"]).describe(
1998
+ action: import_zod5.z.enum(["list", "add", "remove", "clear", "enable", "disable", "debug"]).describe(
1975
1999
  "add creates a rule (or replaces one when `id` is given). remove drops one by `id`. clear drops all. enable/disable flip ONE rule when `id` is given, otherwise the master switch \u2014 rules are kept either way. list shows every rule with its hit counts; pass `id` to dump one rule in full, body included. debug reports why a rule is or isn't firing."
1976
2000
  ),
1977
- urlPattern: import_zod4.z.string().optional().describe(
2001
+ urlPattern: import_zod5.z.string().optional().describe(
1978
2002
  "For `add`: glob matched against the WHOLE request URL, query string included. `*` is the only wildcard, e.g. '*pokeapi.co*' or 'https://api.example.com/v1/users*'. Careful with an exact URL copied from get_network_requests \u2014 if the client cache-busts or pages, the next call won't match it and the rule sits at 0 hits. Prefer `fromRequestId`, which handles that for you."
1979
2003
  ),
1980
- fromRequestId: import_zod4.z.string().optional().describe(
2004
+ fromRequestId: import_zod5.z.string().optional().describe(
1981
2005
  "For `add`: build the rule from a captured request id (from get_network_requests) \u2014 the same 'Override this request' the developer gets in the UI. Seeds the pattern (query string replaced with `*` so it keeps matching), the method, the status, and the REAL response body, so you can change two fields of a payload you just saw instead of authoring one from scratch. Anything you pass explicitly overrides the prefill."
1982
2006
  ),
1983
- methods: import_zod4.z.array(import_zod4.z.string()).optional().describe("For `add`: restrict to these HTTP methods. Omit for any."),
1984
- kind: import_zod4.z.enum(["respond", "fail", "delay"]).optional().describe(
2007
+ methods: import_zod5.z.array(import_zod5.z.string()).optional().describe("For `add`: restrict to these HTTP methods. Omit for any."),
2008
+ kind: import_zod5.z.enum(["respond", "fail", "delay"]).optional().describe(
1985
2009
  "respond = return your status/body, never hitting the network (default). fail = a transport failure, as if the device were offline. delay = let the real request run, just late."
1986
2010
  ),
1987
- status: import_zod4.z.number().optional().describe(
2011
+ status: import_zod5.z.number().optional().describe(
1988
2012
  "For kind 'respond': HTTP status, 200\u2013599 (default 200). Outside that range fetch itself throws, so use kind 'fail' for connection errors rather than status 0."
1989
2013
  ),
1990
- body: import_zod4.z.union([import_zod4.z.string(), import_zod4.z.record(import_zod4.z.unknown()), import_zod4.z.array(import_zod4.z.unknown())]).optional().describe(
2014
+ body: import_zod5.z.union([import_zod5.z.string(), import_zod5.z.record(import_zod5.z.unknown()), import_zod5.z.array(import_zod5.z.unknown())]).optional().describe(
1991
2015
  "For kind 'respond': the response body. Pass JSON directly (an object or array) or a raw string; content-type is inferred."
1992
2016
  ),
1993
- statusText: import_zod4.z.string().optional().describe(
2017
+ statusText: import_zod5.z.string().optional().describe(
1994
2018
  "For kind 'respond': the status line's text, e.g. 'Service Unavailable'. Cosmetic \u2014 React Native's XMLHttpRequest has no statusText field, so the app never sees it; it only makes the developer's rule list read correctly."
1995
2019
  ),
1996
- headers: import_zod4.z.record(import_zod4.z.string()).optional().describe("For kind 'respond': response headers to send."),
1997
- failKind: import_zod4.z.enum(["network", "timeout"]).optional().describe("For kind 'fail': 'network' (default) or 'timeout'."),
1998
- delayMs: import_zod4.z.number().optional().describe("Latency before the outcome, in ms. Works with every kind."),
1999
- times: import_zod4.z.number().optional().describe(
2020
+ headers: import_zod5.z.record(import_zod5.z.string()).optional().describe("For kind 'respond': response headers to send."),
2021
+ failKind: import_zod5.z.enum(["network", "timeout"]).optional().describe("For kind 'fail': 'network' (default) or 'timeout'."),
2022
+ delayMs: import_zod5.z.number().optional().describe("Latency before the outcome, in ms. Works with every kind."),
2023
+ times: import_zod5.z.number().optional().describe(
2000
2024
  "Auto-disable the rule after N matches. Use 1 for 'make the next call fail' so it can't linger."
2001
2025
  ),
2002
- alternate: import_zod4.z.boolean().optional().describe(
2026
+ alternate: import_zod5.z.boolean().optional().describe(
2003
2027
  "Apply to every OTHER matching request, forever: the first goes through, the second is overridden, and so on. This is how you test retry logic and flaky endpoints \u2014 a rule that is always on or always off can't reach those paths."
2004
2028
  ),
2005
- name: import_zod4.z.string().optional().describe("Label shown in the developer's UI."),
2006
- enabled: import_zod4.z.boolean().optional().describe(
2029
+ name: import_zod5.z.string().optional().describe("Label shown in the developer's UI."),
2030
+ enabled: import_zod5.z.boolean().optional().describe(
2007
2031
  "For `add`: create the rule switched off (default true). Useful for staging a rule you'll arm later with enable({id})."
2008
2032
  ),
2009
- id: import_zod4.z.string().optional().describe(
2033
+ id: import_zod5.z.string().optional().describe(
2010
2034
  "Rule id. Required for `remove`. For `add` it replaces that rule. For `enable`/`disable` it targets ONE rule instead of the master switch. For `list` it dumps that rule in full."
2011
2035
  ),
2012
- url: import_zod4.z.string().optional().describe(
2036
+ url: import_zod5.z.string().optional().describe(
2013
2037
  "For `debug`: a concrete URL to test the rules against, so the report says which rule would match it and why."
2014
2038
  ),
2015
- deviceId: import_zod4.z.string().optional().describe("Target device id (auto-selected if one is connected).")
2039
+ deviceId: deviceIdInput
2016
2040
  }
2017
2041
  },
2018
2042
  async (args) => {
@@ -2021,7 +2045,7 @@ _\u{1F4CC} pinned \xB7 \u{1F516} saved. Act on one with its id, e.g. network_act
2021
2045
  switch (args.action) {
2022
2046
  case "add": {
2023
2047
  if (!args.urlPattern && !args.fromRequestId) {
2024
- return fail4(
2048
+ return fail2(
2025
2049
  "`add` needs a `urlPattern` or a `fromRequestId`. Take an id from get_network_requests to override a request you just saw, or pass a glob like '*pokeapi.co*'."
2026
2050
  );
2027
2051
  }
@@ -2053,13 +2077,13 @@ _\u{1F4CC} pinned \xB7 \u{1F516} saved. Act on one with its id, e.g. network_act
2053
2077
  );
2054
2078
  const shaped = res;
2055
2079
  if (!shaped?.ok) {
2056
- return fail4(shaped?.error ?? `Could not add the rule on ${target}.`);
2080
+ return fail2(shaped?.error ?? `Could not add the rule on ${target}.`);
2057
2081
  }
2058
2082
  const pattern = shaped.rule?.urlPattern ?? args.urlPattern;
2059
2083
  const armed = shaped.rule?.enabled === false ? `
2060
2084
 
2061
2085
  It is switched OFF \u2014 arm it with network_override({action:"enable", id:"${shaped.rule?.id}"}).` : "";
2062
- return text3(
2086
+ return text(
2063
2087
  `Override added on ${target} \u2014 \`${shaped.rule?.id}\` matching \`${pattern}\`.${armed}
2064
2088
 
2065
2089
  Trigger the request now (navigate / tap_element), then check it with get_network_requests or describe_screen. Remove it with network_override({action:"remove", id:"${shaped.rule?.id}"}) when you're done.`
@@ -2067,14 +2091,14 @@ Trigger the request now (navigate / tap_element), then check it with get_network
2067
2091
  }
2068
2092
  case "remove": {
2069
2093
  if (!args.id) {
2070
- return fail4('`remove` needs an `id`. List them with network_override({action:"list"}).');
2094
+ return fail2('`remove` needs an `id`. List them with network_override({action:"list"}).');
2071
2095
  }
2072
2096
  await sync.callAction(NETWORK_TOOL_ID, "deleteOverrideRule", { id: args.id }, target);
2073
- return text3(`Removed override \`${args.id}\` on ${target}.`);
2097
+ return text(`Removed override \`${args.id}\` on ${target}.`);
2074
2098
  }
2075
2099
  case "clear":
2076
2100
  await sync.callAction(NETWORK_TOOL_ID, "clearOverrideRules", {}, target);
2077
- return text3(`Removed all override rules on ${target}. Traffic is real again.`);
2101
+ return text(`Removed all override rules on ${target}. Traffic is real again.`);
2078
2102
  case "enable":
2079
2103
  case "disable": {
2080
2104
  const enabled = args.action === "enable";
@@ -2085,12 +2109,12 @@ Trigger the request now (navigate / tap_element), then check it with get_network
2085
2109
  { id: args.id, enabled },
2086
2110
  target
2087
2111
  );
2088
- return text3(
2112
+ return text(
2089
2113
  `Rule \`${args.id}\` is now ${enabled ? "ON" : "OFF"} on ${target}. The other rules and the master switch are unchanged.`
2090
2114
  );
2091
2115
  }
2092
2116
  await sync.callAction(NETWORK_TOOL_ID, "setOverridesEnabled", { enabled }, target);
2093
- return text3(
2117
+ return text(
2094
2118
  enabled ? `Overrides switched ON for ${target}.` : `Overrides switched OFF for ${target}. Rules are kept \u2014 re-enable with network_override({action:"enable"}).`
2095
2119
  );
2096
2120
  }
@@ -2105,11 +2129,11 @@ Trigger the request now (navigate / tap_element), then check it with get_network
2105
2129
  if (args.id) {
2106
2130
  const rule = rules.find((r) => r.id === args.id);
2107
2131
  if (!rule) {
2108
- return fail4(
2132
+ return fail2(
2109
2133
  `No override rule \`${args.id}\` on ${target}. List them with network_override({action:"list"}).`
2110
2134
  );
2111
2135
  }
2112
- return text3(
2136
+ return text(
2113
2137
  `**Override rule \`${rule.id}\`** on ${target}${masterNote(res)}
2114
2138
 
2115
2139
  \`\`\`json
@@ -2118,7 +2142,7 @@ ${JSON.stringify(rule, null, 2)}
2118
2142
  );
2119
2143
  }
2120
2144
  if (rules.length === 0) {
2121
- return text3(
2145
+ return text(
2122
2146
  `No override rules on ${target}.
2123
2147
 
2124
2148
  Add one with network_override({action:"add", urlPattern:"*", status:500}) to force an error state.`
@@ -2146,7 +2170,7 @@ Add one with network_override({action:"add", urlPattern:"*", status:500}) to for
2146
2170
  return `${i + 1}. \`${rule.id}\`${rule.name ? ` ${rule.name}` : ""}
2147
2171
  \u2514\u2500 \`${rule.urlPattern}\` \xB7 ${bits.join(" \xB7 ")}`;
2148
2172
  });
2149
- return text3(
2173
+ return text(
2150
2174
  `**Override rules on ${target}**${masterNote(res)}
2151
2175
 
2152
2176
  ${rows.join("\n")}
@@ -2162,11 +2186,11 @@ Full detail incl. body: network_override({action:"list", id:"${rules[0]?.id}"}).
2162
2186
  target
2163
2187
  );
2164
2188
  if (!res) {
2165
- return fail4(
2189
+ return fail2(
2166
2190
  `${target} did not answer the diagnostics call \u2014 the network tool may not be mounted.`
2167
2191
  );
2168
2192
  }
2169
- return text3(
2193
+ return text(
2170
2194
  `**Override diagnostics on ${target}**${args.url ? ` for \`${args.url}\`` : ""}
2171
2195
 
2172
2196
  \`\`\`json
@@ -2178,24 +2202,21 @@ A rule that never fires is usually one of: the master switch is off, the interce
2178
2202
  }
2179
2203
  }
2180
2204
  } catch (e) {
2181
- return fail4(errMsg4(e));
2205
+ return fail2(errMsg2(e));
2182
2206
  }
2183
2207
  }
2184
2208
  );
2185
2209
  }
2186
2210
 
2187
2211
  // src/tools/packs/reactQuery.ts
2188
- var import_zod5 = require("zod");
2212
+ var import_zod6 = require("zod");
2189
2213
  var RQ_TOOL_ID = "query";
2190
2214
  var RQ_TOOL_ID_LEGACY = "react-query";
2191
- var text4 = (s) => ({ content: [{ type: "text", text: s }] });
2192
- var fail5 = (message) => ({ isError: true, content: [{ type: "text", text: message }] });
2193
- var errMsg5 = (e) => e instanceof Error ? e.message : String(e);
2194
2215
  async function callQueryAction(sync, action, params, target) {
2195
2216
  try {
2196
2217
  return await sync.callAction(RQ_TOOL_ID, action, params, target);
2197
2218
  } catch (e) {
2198
- const msg = errMsg5(e);
2219
+ const msg = errMsg2(e);
2199
2220
  if (!/unknown tool/i.test(msg)) throw e;
2200
2221
  return await sync.callAction(RQ_TOOL_ID_LEGACY, action, params, target);
2201
2222
  }
@@ -2223,10 +2244,10 @@ function registerReactQueryTools(server, sync) {
2223
2244
  {
2224
2245
  description: "Read the app's CURRENT React Query cache \u2014 every query with its status (fresh/stale/fetching/error), staleness, observer count, last-updated time, and any error. Compact by DEFAULT (no cached payloads) so it's token-cheap; pass includeData:true for the actual cached data, or staleOnly:true to see just stale queries. Use this for 'what's in the cache / why is this stale / which queries errored'. (For the history of query updates over time, use get_events with sources:['react-query'].) Each row's hash is what you pass to react_query_action.",
2225
2246
  inputSchema: {
2226
- includeData: import_zod5.z.boolean().optional().describe("Include each query's full cached data payload (HEAVY). Default false."),
2227
- staleOnly: import_zod5.z.boolean().optional().describe("Only stale queries. Default false."),
2228
- limit: import_zod5.z.number().optional().describe("Most-recently-updated N queries. Default 25."),
2229
- deviceId: import_zod5.z.string().optional().describe("Target device id (auto-selected if one is connected).")
2247
+ includeData: import_zod6.z.boolean().optional().describe("Include each query's full cached data payload (HEAVY). Default false."),
2248
+ staleOnly: import_zod6.z.boolean().optional().describe("Only stale queries. Default false."),
2249
+ limit: import_zod6.z.number().optional().describe("Most-recently-updated N queries. Default 25."),
2250
+ deviceId: deviceIdInput
2230
2251
  }
2231
2252
  },
2232
2253
  async (args) => {
@@ -2239,12 +2260,12 @@ function registerReactQueryTools(server, sync) {
2239
2260
  target
2240
2261
  );
2241
2262
  if (!res || !Array.isArray(res.queries)) {
2242
- return fail5(
2263
+ return fail2(
2243
2264
  "No react-query result from the device. The react-query tool may be inactive, or the app needs a newer Buoy (get_react_query requires the listQueries action)."
2244
2265
  );
2245
2266
  }
2246
2267
  if (res.queries.length === 0) {
2247
- return text4(`React Query cache is empty (0 of ${res.total} queries${args.staleOnly ? " match staleOnly" : ""}).`);
2268
+ return text(`React Query cache is empty (0 of ${res.total} queries${args.staleOnly ? " match staleOnly" : ""}).`);
2248
2269
  }
2249
2270
  const lines = res.queries.map((q, i) => {
2250
2271
  const icon = STATUS_ICON[q.status] ?? "\u2022";
@@ -2257,11 +2278,11 @@ function registerReactQueryTools(server, sync) {
2257
2278
  const staleCount = res.queries.filter((q) => q.isStale).length;
2258
2279
  const header = `**React Query cache \u2014 ${res.total} queries (showing ${res.returned}, ${staleCount} stale):**`;
2259
2280
  const note = res.includedData ? "" : "\n\n_Compact view. Pass includeData:true for cached payloads, staleOnly:true to narrow. Act on one with react_query_action using the `hash` in backticks._";
2260
- return text4(`${header}
2281
+ return text(`${header}
2261
2282
 
2262
2283
  ${lines.join("\n")}${note}`);
2263
2284
  } catch (e) {
2264
- return fail5(errMsg5(e));
2285
+ return fail2(errMsg2(e));
2265
2286
  }
2266
2287
  }
2267
2288
  );
@@ -2270,9 +2291,9 @@ ${lines.join("\n")}${note}`);
2270
2291
  {
2271
2292
  description: "Mutate the app's React Query cache: invalidate / refetch / reset / remove a single query (by queryHash, from get_react_query), or clear the entire query cache. Use this to fix stale data, force a refetch, or reset state during debugging.",
2272
2293
  inputSchema: {
2273
- action: import_zod5.z.enum(["invalidate", "refetch", "reset", "remove", "clear"]).describe("invalidate/refetch/reset/remove a single query (needs queryHash); clear empties the whole query cache."),
2274
- queryHash: import_zod5.z.string().optional().describe("The target query's hash (from get_react_query, shown in backticks). Required for all actions except clear."),
2275
- deviceId: import_zod5.z.string().optional().describe("Target device id (auto-selected if one is connected).")
2294
+ action: import_zod6.z.enum(["invalidate", "refetch", "reset", "remove", "clear"]).describe("invalidate/refetch/reset/remove a single query (needs queryHash); clear empties the whole query cache."),
2295
+ queryHash: import_zod6.z.string().optional().describe("The target query's hash (from get_react_query, shown in backticks). Required for all actions except clear."),
2296
+ deviceId: deviceIdInput
2276
2297
  }
2277
2298
  },
2278
2299
  async (args) => {
@@ -2280,174 +2301,145 @@ ${lines.join("\n")}${note}`);
2280
2301
  const target = sync.requireProDevice(args.deviceId);
2281
2302
  if (args.action === "clear") {
2282
2303
  await callQueryAction(sync, "clearQueryCache", {}, target);
2283
- return text4("Cleared the entire React Query cache.");
2304
+ return text("Cleared the entire React Query cache.");
2284
2305
  }
2285
2306
  if (!args.queryHash) {
2286
- return fail5(`'${args.action}' needs a queryHash. Call get_react_query first and use a row's hash (in backticks).`);
2307
+ return fail2(`'${args.action}' needs a queryHash. Call get_react_query first and use a row's hash (in backticks).`);
2287
2308
  }
2288
2309
  await callQueryAction(sync, args.action, { queryHash: args.queryHash }, target);
2289
- return text4(`Ran ${args.action} on query \`${args.queryHash}\`.`);
2310
+ return text(`Ran ${args.action} on query \`${args.queryHash}\`.`);
2290
2311
  } catch (e) {
2291
- return fail5(errMsg5(e));
2312
+ return fail2(errMsg2(e));
2292
2313
  }
2293
2314
  }
2294
2315
  );
2295
2316
  }
2296
2317
 
2297
2318
  // src/tools/packs/zustand.ts
2298
- var import_zod6 = require("zod");
2319
+ var import_zod7 = require("zod");
2299
2320
  var ZUSTAND_TOOL_ID = "zustand";
2300
- var text5 = (s) => ({ content: [{ type: "text", text: s }] });
2301
- var fail6 = (message) => ({ isError: true, content: [{ type: "text", text: message }] });
2302
- var errMsg6 = (e) => e instanceof Error ? e.message : String(e);
2303
2321
  function registerZustandTools(server, sync) {
2304
2322
  server.registerTool(
2305
2323
  "get_zustand_state",
2306
2324
  {
2307
2325
  description: "Read the app's CURRENT Zustand store state \u2014 each registered store with its name, change count, persistence, and (by default) just its top-level keys so it's token-cheap. Pass includeValues:true for the full state objects. Use this for 'what's in my zustand stores / what's the current count'. (For the history of state changes over time, use get_events with sources:['zustand'].)",
2308
2326
  inputSchema: {
2309
- includeValues: import_zod6.z.boolean().optional().describe("Include each store's full current state object (HEAVY). Default false (keys only)."),
2310
- limit: import_zod6.z.number().optional().describe("First N stores. Default all."),
2311
- deviceId: import_zod6.z.string().optional().describe("Target device id (auto-selected if one is connected).")
2327
+ includeValues: import_zod7.z.boolean().optional().describe("Include each store's full current state object (HEAVY). Default false (keys only)."),
2328
+ limit: import_zod7.z.number().optional().describe("First N stores. Default all."),
2329
+ deviceId: deviceIdInput
2312
2330
  }
2313
2331
  },
2314
- async (args) => {
2315
- try {
2316
- const target = sync.requireProDevice(args.deviceId);
2317
- const res = await sync.callAction(
2318
- ZUSTAND_TOOL_ID,
2319
- "listStores",
2320
- { includeValues: args.includeValues ?? false, limit: args.limit },
2321
- target
2332
+ withDevice(sync, async (args, target) => {
2333
+ const res = await sync.callAction(
2334
+ ZUSTAND_TOOL_ID,
2335
+ "listStores",
2336
+ { includeValues: args.includeValues ?? false, limit: args.limit },
2337
+ target
2338
+ );
2339
+ if (!res || !Array.isArray(res.stores)) {
2340
+ return fail2(
2341
+ inactiveToolMessage({ toolId: ZUSTAND_TOOL_ID, mcpTool: "get_zustand_state", action: "listStores" })
2322
2342
  );
2323
- if (!res || !Array.isArray(res.stores)) {
2324
- return fail6(
2325
- "No zustand result from the device. The zustand tool may be inactive, or the app needs a newer Buoy (get_zustand_state requires the listStores action)."
2326
- );
2327
- }
2328
- if (res.stores.length === 0) {
2329
- return text5("No Zustand stores are registered (call watchStores to register them).");
2330
- }
2331
- const lines = res.stores.map((s, i) => {
2332
- const persist = s.isPersisted ? ` \xB7 persisted${s.persistName ? ` (${s.persistName})` : ""}` : "";
2333
- const body = res.includedValues ? `
2343
+ }
2344
+ if (res.stores.length === 0) {
2345
+ return text("No Zustand stores are registered (call watchStores to register them).");
2346
+ }
2347
+ const lines = res.stores.map((s, i) => {
2348
+ const persist = s.isPersisted ? ` \xB7 persisted${s.persistName ? ` (${s.persistName})` : ""}` : "";
2349
+ const body = res.includedValues ? `
2334
2350
  ${JSON.stringify(s.currentState)}` : ` \xB7 keys: ${(s.keys ?? []).join(", ") || "\u2014"}`;
2335
- return `${i + 1}. **${s.name}** \xB7 ${s.changes} changes${persist}${body}`;
2336
- });
2337
- const note = res.includedValues ? "" : "\n\n_Compact view (keys only). Pass includeValues:true for full state. Mutate with zustand_action({store, state})._";
2338
- return text5(`**Zustand stores \u2014 ${res.total} (showing ${res.returned}):**
2351
+ return `${i + 1}. **${s.name}** \xB7 ${s.changes} changes${persist}${body}`;
2352
+ });
2353
+ const note = res.includedValues ? "" : "\n\n_Compact view (keys only). Pass includeValues:true for full state. Mutate with zustand_action({store, state})._";
2354
+ return text(`**Zustand stores \u2014 ${res.total} (showing ${res.returned}):**
2339
2355
 
2340
2356
  ${lines.join("\n")}${note}`);
2341
- } catch (e) {
2342
- return fail6(errMsg6(e));
2343
- }
2344
- }
2357
+ })
2345
2358
  );
2346
2359
  server.registerTool(
2347
2360
  "zustand_action",
2348
2361
  {
2349
2362
  description: "Set the state of a Zustand store (time-travel / reset / tweak during debugging). Targets a store by name (from get_zustand_state). By default replaces the whole state; pass replace:false to merge a partial.",
2350
2363
  inputSchema: {
2351
- store: import_zod6.z.string().describe("Store name to target (from get_zustand_state)."),
2352
- state: import_zod6.z.record(import_zod6.z.string(), import_zod6.z.unknown()).describe("The new state object."),
2353
- replace: import_zod6.z.boolean().optional().describe("Replace the whole state (default true) or merge a partial (false)."),
2354
- deviceId: import_zod6.z.string().optional().describe("Target device id (auto-selected if one is connected).")
2364
+ store: import_zod7.z.string().describe("Store name to target (from get_zustand_state)."),
2365
+ state: import_zod7.z.record(import_zod7.z.string(), import_zod7.z.unknown()).describe("The new state object."),
2366
+ replace: import_zod7.z.boolean().optional().describe("Replace the whole state (default true) or merge a partial (false)."),
2367
+ deviceId: deviceIdInput
2355
2368
  }
2356
2369
  },
2357
- async (args) => {
2358
- try {
2359
- const target = sync.requireProDevice(args.deviceId);
2360
- await sync.callAction(
2361
- ZUSTAND_TOOL_ID,
2362
- "setState",
2363
- { storeName: args.store, state: args.state, replace: args.replace ?? true },
2364
- target
2365
- );
2366
- return text5(`Set state on Zustand store "${args.store}" (${args.replace === false ? "merged" : "replaced"}).`);
2367
- } catch (e) {
2368
- return fail6(errMsg6(e));
2369
- }
2370
- }
2370
+ withDevice(sync, async (args, target) => {
2371
+ await sync.callAction(
2372
+ ZUSTAND_TOOL_ID,
2373
+ "setState",
2374
+ { storeName: args.store, state: args.state, replace: args.replace ?? true },
2375
+ target
2376
+ );
2377
+ return text(`Set state on Zustand store "${args.store}" (${args.replace === false ? "merged" : "replaced"}).`);
2378
+ })
2371
2379
  );
2372
2380
  }
2373
2381
 
2374
2382
  // src/tools/packs/jotai.ts
2375
- var import_zod7 = require("zod");
2383
+ var import_zod8 = require("zod");
2376
2384
  var JOTAI_TOOL_ID = "jotai";
2377
- var text6 = (s) => ({ content: [{ type: "text", text: s }] });
2378
- var fail7 = (message) => ({ isError: true, content: [{ type: "text", text: message }] });
2379
- var errMsg7 = (e) => e instanceof Error ? e.message : String(e);
2380
2385
  function registerJotaiTools(server, sync) {
2381
2386
  server.registerTool(
2382
2387
  "get_jotai_state",
2383
2388
  {
2384
2389
  description: "Read the app's CURRENT Jotai atom values \u2014 each watched atom with its label, change count, and whether it's writable. Compact by DEFAULT (no values) so it's token-cheap; pass includeValues:true for the actual current values. Use this for 'what are my jotai atoms / what's the current value'. (For the history of atom changes over time, use get_events with sources:['jotai'].)",
2385
2390
  inputSchema: {
2386
- includeValues: import_zod7.z.boolean().optional().describe("Include each atom's current value (HEAVY). Default false."),
2387
- limit: import_zod7.z.number().optional().describe("First N atoms. Default all."),
2388
- deviceId: import_zod7.z.string().optional().describe("Target device id (auto-selected if one is connected).")
2391
+ includeValues: import_zod8.z.boolean().optional().describe("Include each atom's current value (HEAVY). Default false."),
2392
+ limit: import_zod8.z.number().optional().describe("First N atoms. Default all."),
2393
+ deviceId: deviceIdInput
2389
2394
  }
2390
2395
  },
2391
- async (args) => {
2392
- try {
2393
- const target = sync.requireProDevice(args.deviceId);
2394
- const res = await sync.callAction(
2395
- JOTAI_TOOL_ID,
2396
- "listAtoms",
2397
- { includeValues: args.includeValues ?? false, limit: args.limit },
2398
- target
2396
+ withDevice(sync, async (args, target) => {
2397
+ const res = await sync.callAction(
2398
+ JOTAI_TOOL_ID,
2399
+ "listAtoms",
2400
+ { includeValues: args.includeValues ?? false, limit: args.limit },
2401
+ target
2402
+ );
2403
+ if (!res || !Array.isArray(res.atoms)) {
2404
+ return fail2(
2405
+ inactiveToolMessage({ toolId: JOTAI_TOOL_ID, mcpTool: "get_jotai_state", action: "listAtoms" })
2399
2406
  );
2400
- if (!res || !Array.isArray(res.atoms)) {
2401
- return fail7(
2402
- "No jotai result from the device. The jotai tool may be inactive, or the app needs a newer Buoy (get_jotai_state requires the listAtoms action)."
2403
- );
2404
- }
2405
- if (res.atoms.length === 0) {
2406
- return text6("No Jotai atoms are being watched (call watchAtoms to register them).");
2407
- }
2408
- const lines = res.atoms.map((a, i) => {
2409
- const kind = a.writable ? "writable" : "read-only";
2410
- const value = res.includedValues ? `
2407
+ }
2408
+ if (res.atoms.length === 0) {
2409
+ return text("No Jotai atoms are being watched (call watchAtoms to register them).");
2410
+ }
2411
+ const lines = res.atoms.map((a, i) => {
2412
+ const kind = a.writable ? "writable" : "read-only";
2413
+ const value = res.includedValues ? `
2411
2414
  ${JSON.stringify(a.currentValue)}` : "";
2412
- return `${i + 1}. **${a.label}** \xB7 ${a.changes} changes \xB7 ${kind}${value}`;
2413
- });
2414
- const note = res.includedValues ? "" : "\n\n_Compact view. Pass includeValues:true for current values. Set a writable atom with jotai_action({atom, value})._";
2415
- return text6(`**Jotai atoms \u2014 ${res.total} (showing ${res.returned}):**
2415
+ return `${i + 1}. **${a.label}** \xB7 ${a.changes} changes \xB7 ${kind}${value}`;
2416
+ });
2417
+ const note = res.includedValues ? "" : "\n\n_Compact view. Pass includeValues:true for current values. Set a writable atom with jotai_action({atom, value})._";
2418
+ return text(`**Jotai atoms \u2014 ${res.total} (showing ${res.returned}):**
2416
2419
 
2417
2420
  ${lines.join("\n")}${note}`);
2418
- } catch (e) {
2419
- return fail7(errMsg7(e));
2420
- }
2421
- }
2421
+ })
2422
2422
  );
2423
2423
  server.registerTool(
2424
2424
  "jotai_action",
2425
2425
  {
2426
2426
  description: "Set the value of a writable Jotai atom (by label, from get_jotai_state). Read-only/derived atoms cannot be set (you'll get a clear error). Use for tweaking state during debugging.",
2427
2427
  inputSchema: {
2428
- atom: import_zod7.z.string().describe("Atom label to set (from get_jotai_state; must be writable)."),
2429
- value: import_zod7.z.unknown().describe("The new value (any JSON \u2014 number, string, object, etc.)."),
2430
- deviceId: import_zod7.z.string().optional().describe("Target device id (auto-selected if one is connected).")
2428
+ atom: import_zod8.z.string().describe("Atom label to set (from get_jotai_state; must be writable)."),
2429
+ value: import_zod8.z.unknown().describe("The new value (any JSON \u2014 number, string, object, etc.)."),
2430
+ deviceId: deviceIdInput
2431
2431
  }
2432
2432
  },
2433
- async (args) => {
2434
- try {
2435
- const target = sync.requireProDevice(args.deviceId);
2436
- await sync.callAction(JOTAI_TOOL_ID, "setAtom", { label: args.atom, value: args.value }, target);
2437
- return text6(`Set Jotai atom "${args.atom}" to ${JSON.stringify(args.value)}.`);
2438
- } catch (e) {
2439
- return fail7(errMsg7(e));
2440
- }
2441
- }
2433
+ withDevice(sync, async (args, target) => {
2434
+ await sync.callAction(JOTAI_TOOL_ID, "setAtom", { label: args.atom, value: args.value }, target);
2435
+ return text(`Set Jotai atom "${args.atom}" to ${JSON.stringify(args.value)}.`);
2436
+ })
2442
2437
  );
2443
2438
  }
2444
2439
 
2445
2440
  // src/tools/packs/redux.ts
2446
- var import_zod8 = require("zod");
2441
+ var import_zod9 = require("zod");
2447
2442
  var REDUX_TOOL_ID = "redux";
2448
- var text7 = (s) => ({ content: [{ type: "text", text: s }] });
2449
- var fail8 = (message) => ({ isError: true, content: [{ type: "text", text: message }] });
2450
- var errMsg8 = (e) => e instanceof Error ? e.message : String(e);
2451
2443
  function noStoreMessage(reason) {
2452
2444
  switch (reason) {
2453
2445
  case "no-react-redux":
@@ -2466,8 +2458,8 @@ function registerReduxTools(server, sync) {
2466
2458
  {
2467
2459
  description: "Read the app's CURRENT Redux state. Compact by DEFAULT \u2014 just the top-level slice names \u2014 so it's token-cheap; pass includeValues:true for the full state tree. Use this for 'what's in my redux store / current auth state'. (For the history of dispatched actions over time, use get_events with sources:['redux'].)",
2468
2460
  inputSchema: {
2469
- includeValues: import_zod8.z.boolean().optional().describe("Include the full current state tree (HEAVY). Default false (slice names only)."),
2470
- deviceId: import_zod8.z.string().optional().describe("Target device id (auto-selected if one is connected).")
2461
+ includeValues: import_zod9.z.boolean().optional().describe("Include the full current state tree (HEAVY). Default false (slice names only)."),
2462
+ deviceId: deviceIdInput
2471
2463
  }
2472
2464
  },
2473
2465
  async (args) => {
@@ -2480,15 +2472,15 @@ function registerReduxTools(server, sync) {
2480
2472
  target
2481
2473
  );
2482
2474
  if (!res) {
2483
- return fail8(
2475
+ return fail2(
2484
2476
  "No redux result from the device. The redux tool may be inactive, or the app needs a newer Buoy (get_redux_state requires the getState action)."
2485
2477
  );
2486
2478
  }
2487
- if (!res.available) return fail8(noStoreMessage(res.reason));
2479
+ if (!res.available) return fail2(noStoreMessage(res.reason));
2488
2480
  const slices = res.slices.length ? res.slices.join(", ") : "(none)";
2489
2481
  const captureNote = res.capture === "top-level-only" ? TOP_LEVEL_ONLY_NOTE : "";
2490
2482
  if (args.includeValues) {
2491
- return text7(
2483
+ return text(
2492
2484
  `**Redux store \u2014 slices: ${slices}**
2493
2485
 
2494
2486
  \`\`\`json
@@ -2496,13 +2488,13 @@ ${JSON.stringify(res.state, null, 2)}
2496
2488
  \`\`\`${captureNote}`
2497
2489
  );
2498
2490
  }
2499
- return text7(
2491
+ return text(
2500
2492
  `**Redux store \u2014 slices: ${slices}**
2501
2493
 
2502
2494
  _Compact view. Pass includeValues:true for the full state tree. Dispatch with redux_dispatch({type, payload})._${captureNote}`
2503
2495
  );
2504
2496
  } catch (e) {
2505
- return fail8(errMsg8(e));
2497
+ return fail2(errMsg2(e));
2506
2498
  }
2507
2499
  }
2508
2500
  );
@@ -2511,9 +2503,9 @@ _Compact view. Pass includeValues:true for the full state tree. Dispatch with re
2511
2503
  {
2512
2504
  description: "Dispatch a Redux action to the app's store (e.g. to trigger a state change while debugging). Provide the action type and optional payload \u2014 they're sent as { type, payload }.",
2513
2505
  inputSchema: {
2514
- type: import_zod8.z.string().describe('The action type, e.g. "counter/increment" or "auth/logout".'),
2515
- payload: import_zod8.z.unknown().optional().describe("Optional action payload (any JSON)."),
2516
- deviceId: import_zod8.z.string().optional().describe("Target device id (auto-selected if one is connected).")
2506
+ type: import_zod9.z.string().describe('The action type, e.g. "counter/increment" or "auth/logout".'),
2507
+ payload: import_zod9.z.unknown().optional().describe("Optional action payload (any JSON)."),
2508
+ deviceId: deviceIdInput
2517
2509
  }
2518
2510
  },
2519
2511
  async (args) => {
@@ -2522,21 +2514,18 @@ _Compact view. Pass includeValues:true for the full state tree. Dispatch with re
2522
2514
  const action = { type: args.type };
2523
2515
  if (args.payload !== void 0) action.payload = args.payload;
2524
2516
  const res = await sync.callAction(REDUX_TOOL_ID, "dispatch", { action }, target);
2525
- if (!res || !res.dispatched) return fail8(noStoreMessage(res?.reason));
2526
- return text7(`Dispatched \`${args.type}\`${args.payload !== void 0 ? ` with payload ${JSON.stringify(args.payload)}` : ""}.`);
2517
+ if (!res || !res.dispatched) return fail2(noStoreMessage(res?.reason));
2518
+ return text(`Dispatched \`${args.type}\`${args.payload !== void 0 ? ` with payload ${JSON.stringify(args.payload)}` : ""}.`);
2527
2519
  } catch (e) {
2528
- return fail8(errMsg8(e));
2520
+ return fail2(errMsg2(e));
2529
2521
  }
2530
2522
  }
2531
2523
  );
2532
2524
  }
2533
2525
 
2534
2526
  // src/tools/packs/storage.ts
2535
- var import_zod9 = require("zod");
2527
+ var import_zod10 = require("zod");
2536
2528
  var STORAGE_TOOL_ID = "storage";
2537
- var text8 = (s) => ({ content: [{ type: "text", text: s }] });
2538
- var fail9 = (message) => ({ isError: true, content: [{ type: "text", text: message }] });
2539
- var errMsg9 = (e) => e instanceof Error ? e.message : String(e);
2540
2529
  function truncate2(v, n = 200) {
2541
2530
  if (v === null || v === void 0) return "(null)";
2542
2531
  const s = typeof v === "string" ? v : JSON.stringify(v);
@@ -2548,10 +2537,10 @@ function registerStorageTools(server, sync) {
2548
2537
  {
2549
2538
  description: "Read the app's storage keys across ALL backends: AsyncStorage, MMKV (per instance), and Expo SecureStore. Data wrong or missing after an app restart \u2192 read the persisted values here FIRST \u2014 the bug is usually a stale, missing, or malformed stored value, and reading it beats hunting for it in code. Compact by DEFAULT \u2014 keys only \u2014 so it's token-cheap; pass includeValues:true for the values (truncated), or a `pattern` substring to narrow. Buoy's own internal keys are already filtered out.",
2550
2539
  inputSchema: {
2551
- pattern: import_zod9.z.string().optional().describe("Only keys containing this substring (case-insensitive)."),
2552
- includeValues: import_zod9.z.boolean().optional().describe("Include each key's value (truncated). Default false (keys only)."),
2553
- limit: import_zod9.z.number().optional().describe("Max keys per backend section. Default 100."),
2554
- deviceId: import_zod9.z.string().optional().describe("Target device id (auto-selected if one is connected).")
2540
+ pattern: import_zod10.z.string().optional().describe("Only keys containing this substring (case-insensitive)."),
2541
+ includeValues: import_zod10.z.boolean().optional().describe("Include each key's value (truncated). Default false (keys only)."),
2542
+ limit: import_zod10.z.number().optional().describe("Max keys per backend section. Default 100."),
2543
+ deviceId: deviceIdInput
2555
2544
  }
2556
2545
  },
2557
2546
  async (args) => {
@@ -2563,7 +2552,7 @@ function registerStorageTools(server, sync) {
2563
2552
  const sections = [];
2564
2553
  const allKeys = await sync.callAction(STORAGE_TOOL_ID, "async.getAllKeys", {}, target);
2565
2554
  if (!Array.isArray(allKeys)) {
2566
- return fail9("No storage result from the device. The storage tool may be inactive.");
2555
+ return fail2("No storage result from the device. The storage tool may be inactive.");
2567
2556
  }
2568
2557
  const asyncKeys = allKeys.filter(matches2).slice(0, limit);
2569
2558
  if (asyncKeys.length > 0) {
@@ -2604,12 +2593,12 @@ ${lines.join("\n")}`);
2604
2593
  ${lines.join("\n")}`);
2605
2594
  }
2606
2595
  if (sections.length === 0) {
2607
- return text8(`No storage keys${pattern ? ` matching "${args.pattern}"` : ""} in any backend.`);
2596
+ return text(`No storage keys${pattern ? ` matching "${args.pattern}"` : ""} in any backend.`);
2608
2597
  }
2609
2598
  const note = args.includeValues ? "" : "\n\n_Keys only. Pass includeValues:true for values, or a pattern to filter. Modify AsyncStorage with storage_action._";
2610
- return text8(`${sections.join("\n\n")}${note}`);
2599
+ return text(`${sections.join("\n\n")}${note}`);
2611
2600
  } catch (e) {
2612
- return fail9(errMsg9(e));
2601
+ return fail2(errMsg2(e));
2613
2602
  }
2614
2603
  }
2615
2604
  );
@@ -2618,10 +2607,10 @@ ${lines.join("\n")}`);
2618
2607
  {
2619
2608
  description: "Modify the app's AsyncStorage: set a key, remove a key, or clear all app storage (Buoy's own devtool keys are preserved). Values are stored as strings \u2014 JSON-encode objects yourself.",
2620
2609
  inputSchema: {
2621
- action: import_zod9.z.enum(["set", "remove", "clear"]).describe("set/remove a key, or clear all app storage."),
2622
- key: import_zod9.z.string().optional().describe("The key (required for set and remove)."),
2623
- value: import_zod9.z.string().optional().describe("The string value to store (required for set; JSON-encode objects)."),
2624
- deviceId: import_zod9.z.string().optional().describe("Target device id (auto-selected if one is connected).")
2610
+ action: import_zod10.z.enum(["set", "remove", "clear"]).describe("set/remove a key, or clear all app storage."),
2611
+ key: import_zod10.z.string().optional().describe("The key (required for set and remove)."),
2612
+ value: import_zod10.z.string().optional().describe("The string value to store (required for set; JSON-encode objects)."),
2613
+ deviceId: deviceIdInput
2625
2614
  }
2626
2615
  },
2627
2616
  async (args) => {
@@ -2629,33 +2618,30 @@ ${lines.join("\n")}`);
2629
2618
  const target = sync.requireProDevice(args.deviceId);
2630
2619
  if (args.action === "clear") {
2631
2620
  await sync.callAction(STORAGE_TOOL_ID, "clearAppStorage", {}, target);
2632
- return text8("Cleared all app storage (Buoy's own devtool keys preserved).");
2621
+ return text("Cleared all app storage (Buoy's own devtool keys preserved).");
2633
2622
  }
2634
2623
  if (!args.key) {
2635
- return fail9(`'${args.action}' needs a key.`);
2624
+ return fail2(`'${args.action}' needs a key.`);
2636
2625
  }
2637
2626
  if (args.action === "remove") {
2638
2627
  await sync.callAction(STORAGE_TOOL_ID, "async.removeItem", { key: args.key }, target);
2639
- return text8(`Removed storage key "${args.key}".`);
2628
+ return text(`Removed storage key "${args.key}".`);
2640
2629
  }
2641
2630
  if (args.value === void 0) {
2642
- return fail9("'set' needs a value (a string).");
2631
+ return fail2("'set' needs a value (a string).");
2643
2632
  }
2644
2633
  await sync.callAction(STORAGE_TOOL_ID, "async.setItem", { key: args.key, value: args.value }, target);
2645
- return text8(`Set storage key "${args.key}".`);
2634
+ return text(`Set storage key "${args.key}".`);
2646
2635
  } catch (e) {
2647
- return fail9(errMsg9(e));
2636
+ return fail2(errMsg2(e));
2648
2637
  }
2649
2638
  }
2650
2639
  );
2651
2640
  }
2652
2641
 
2653
2642
  // src/tools/packs/routes.ts
2654
- var import_zod10 = require("zod");
2643
+ var import_zod11 = require("zod");
2655
2644
  var ROUTES_TOOL_ID = "route-events";
2656
- var text9 = (s) => ({ content: [{ type: "text", text: s }] });
2657
- var fail10 = (message) => ({ isError: true, content: [{ type: "text", text: message }] });
2658
- var errMsg10 = (e) => e instanceof Error ? e.message : String(e);
2659
2645
  function flattenRoutes(routes, acc = []) {
2660
2646
  for (const r of routes ?? []) {
2661
2647
  if (r.path) acc.push(r.path);
@@ -2669,8 +2655,8 @@ function registerRoutesTools(server, sync) {
2669
2655
  {
2670
2656
  description: "Read the app's CURRENT route + navigation stack (where the user is and how they got there). Compact by DEFAULT; pass includeSitemap:true to also list every route path the app defines (useful before navigate). Use for 'what screen am I on / what's the nav stack / what routes exist'. (For navigation history over time, use get_events with sources:['route'].)",
2671
2657
  inputSchema: {
2672
- includeSitemap: import_zod10.z.boolean().optional().describe("Also list all defined route paths (can be long). Default false."),
2673
- deviceId: import_zod10.z.string().optional().describe("Target device id (auto-selected if one is connected).")
2658
+ includeSitemap: import_zod11.z.boolean().optional().describe("Also list all defined route paths (can be long). Default false."),
2659
+ deviceId: deviceIdInput
2674
2660
  }
2675
2661
  },
2676
2662
  async (args) => {
@@ -2703,9 +2689,9 @@ ${shown.map((r) => `- \`${r}\``).join("\n")}`
2703
2689
  **Sitemap:** ${allPaths.length} routes _(pass includeSitemap:true to list them)_`);
2704
2690
  }
2705
2691
  parts.push("\n_Navigate with navigate({ path })._");
2706
- return text9(parts.join("\n"));
2692
+ return text(parts.join("\n"));
2707
2693
  } catch (e) {
2708
- return fail10(errMsg10(e));
2694
+ return fail2(errMsg2(e));
2709
2695
  }
2710
2696
  }
2711
2697
  );
@@ -2714,9 +2700,9 @@ ${shown.map((r) => `- \`${r}\``).join("\n")}`
2714
2700
  {
2715
2701
  description: "Navigate the app to a route path (e.g. '/pokemon/25' or '/settings'). Use a concrete path \u2014 resolve any dynamic segments (like [id]) to real values first. See get_routes with includeSitemap:true for valid paths. Pushes by default (back still works); pass replace:true to leave the current screen instead of stacking on top of it (e.g. resetting to '/').",
2716
2702
  inputSchema: {
2717
- path: import_zod10.z.string().describe("The concrete path to navigate to, e.g. '/pokemon/25'."),
2718
- replace: import_zod10.z.boolean().optional().describe("Replace the current screen instead of pushing (use when resetting, e.g. to '/')."),
2719
- deviceId: import_zod10.z.string().optional().describe("Target device id (auto-selected if one is connected).")
2703
+ path: import_zod11.z.string().describe("The concrete path to navigate to, e.g. '/pokemon/25'."),
2704
+ replace: import_zod11.z.boolean().optional().describe("Replace the current screen instead of pushing (use when resetting, e.g. to '/')."),
2705
+ deviceId: deviceIdInput
2720
2706
  }
2721
2707
  },
2722
2708
  async (args) => {
@@ -2728,20 +2714,17 @@ ${shown.map((r) => `- \`${r}\``).join("\n")}`
2728
2714
  { path: args.path, replace: args.replace === true },
2729
2715
  target
2730
2716
  );
2731
- return text9(`Navigated to \`${args.path}\`${args.replace ? " (replaced current screen)" : ""}.`);
2717
+ return text(`Navigated to \`${args.path}\`${args.replace ? " (replaced current screen)" : ""}.`);
2732
2718
  } catch (e) {
2733
- return fail10(errMsg10(e));
2719
+ return fail2(errMsg2(e));
2734
2720
  }
2735
2721
  }
2736
2722
  );
2737
2723
  }
2738
2724
 
2739
2725
  // src/tools/packs/console.ts
2740
- var import_zod11 = require("zod");
2726
+ var import_zod12 = require("zod");
2741
2727
  var CONSOLE_TOOL_ID = "console";
2742
- var text10 = (s) => ({ content: [{ type: "text", text: s }] });
2743
- var fail11 = (message) => ({ isError: true, content: [{ type: "text", text: message }] });
2744
- var errMsg11 = (e) => e instanceof Error ? e.message : String(e);
2745
2728
  var SEVERITY = { verbose: 0, info: 1, warning: 2, error: 3 };
2746
2729
  var TAG = { verbose: "\xB7", info: "info", warning: "warn", error: "ERR" };
2747
2730
  function hhmmss(ts) {
@@ -2755,57 +2738,275 @@ function registerConsoleTools(server, sync) {
2755
2738
  {
2756
2739
  description: "Read the app's captured console logs (console.log/warn/error/\u2026) as a compact tail \u2014 time \xB7 level \xB7 message. App crashing or erroring \u2192 read this FIRST: it captures crashes and errors including FATAL uncaught JS errors (tagged [FATAL], hooked from RN's global error handler), so a dead app's last words are here, not in the code. Token-cheap (uses each entry's pre-rendered message, not raw args). Filter by minimum level (e.g. level:'warning' for warnings+errors) or a message substring. For full args/stack of an entry, fall back to get_snapshot('console').",
2757
2740
  inputSchema: {
2758
- level: import_zod11.z.enum(["verbose", "info", "warning", "error"]).optional().describe("Minimum level to include (severity threshold). e.g. 'warning' = warnings + errors. Default all."),
2759
- limit: import_zod11.z.number().optional().describe("Most-recent N entries. Default 50."),
2760
- pattern: import_zod11.z.string().optional().describe("Only entries whose message contains this substring (case-insensitive)."),
2761
- deviceId: import_zod11.z.string().optional().describe("Target device id (auto-selected if one is connected).")
2741
+ level: import_zod12.z.enum(["verbose", "info", "warning", "error"]).optional().describe("Minimum level to include (severity threshold). e.g. 'warning' = warnings + errors. Default all."),
2742
+ limit: import_zod12.z.number().optional().describe("Most-recent N entries. Default 50."),
2743
+ pattern: import_zod12.z.string().optional().describe("Only entries whose message contains this substring (case-insensitive)."),
2744
+ deviceId: deviceIdInput
2745
+ }
2746
+ },
2747
+ withDevice(sync, async (args, target) => {
2748
+ const { payload, stale } = await sync.getSnapshot(CONSOLE_TOOL_ID, target);
2749
+ const banner = staleHeader(stale);
2750
+ const p = Array.isArray(payload) ? { entries: payload } : payload ?? {};
2751
+ const all = Array.isArray(p.entries) ? p.entries : [];
2752
+ const minSev = args.level ? SEVERITY[args.level] : 0;
2753
+ const pattern = args.pattern?.toLowerCase();
2754
+ const filtered = all.filter(
2755
+ (e) => SEVERITY[e.level] >= minSev && (!pattern || (e.message ?? "").toLowerCase().includes(pattern))
2756
+ );
2757
+ const sorted = [...filtered].sort((a, b) => a.id - b.id);
2758
+ const limit = clampLimit(args.limit, 50);
2759
+ const shown = sorted.slice(-limit);
2760
+ if (shown.length === 0) {
2761
+ return text(`${banner}No console entries${args.level ? ` at level \u2265 ${args.level}` : ""}${pattern ? ` matching "${args.pattern}"` : ""} (${all.length} total captured).`);
2762
+ }
2763
+ const lines = shown.map((e) => `${hhmmss(e.timestamp)} [${TAG[e.level] ?? e.level}] ${e.message}`);
2764
+ const levelNote = args.level ? `, level \u2265 ${args.level}` : "";
2765
+ const header = `**Console \u2014 ${all.length} entries (showing ${shown.length}${levelNote}):**`;
2766
+ return text(`${banner}${header}
2767
+
2768
+ \`\`\`
2769
+ ${lines.join("\n")}
2770
+ \`\`\`
2771
+
2772
+ _Tail of recent logs. Use get_snapshot('console') for full args/stacks._`);
2773
+ })
2774
+ );
2775
+ }
2776
+
2777
+ // src/tools/packs/sentry.ts
2778
+ var import_zod13 = require("zod");
2779
+ var SENTRY_TOOL_ID = "sentry";
2780
+ var text2 = (s) => ({ content: [{ type: "text", text: s }] });
2781
+ var fail3 = (message) => ({
2782
+ isError: true,
2783
+ content: [{ type: "text", text: message }]
2784
+ });
2785
+ var errMsg3 = (e) => e instanceof Error ? e.message : String(e);
2786
+ function hhmmss2(ts) {
2787
+ const d = new Date(ts);
2788
+ const p = (n) => String(n).padStart(2, "0");
2789
+ return `${p(d.getHours())}:${p(d.getMinutes())}:${p(d.getSeconds())}`;
2790
+ }
2791
+ function bytes(n) {
2792
+ if (n < 1024) return `${n}B`;
2793
+ if (n < 1024 * 1024) return `${(n / 1024).toFixed(1)}KB`;
2794
+ return `${(n / (1024 * 1024)).toFixed(2)}MB`;
2795
+ }
2796
+ function units(n, format) {
2797
+ if (format === "bytes") return bytes(n);
2798
+ if (n >= 1e6) return `${(n / 1e6).toFixed(1)}M`;
2799
+ if (n >= 1e3) return `${(n / 1e3).toFixed(1)}k`;
2800
+ return String(n);
2801
+ }
2802
+ var FREE_QUOTA = {
2803
+ errors: 5e3,
2804
+ spans: 5e6,
2805
+ replays: 50,
2806
+ logBytes: 5 * 1024 ** 3,
2807
+ attachmentBytes: 1 * 1024 ** 3
2808
+ };
2809
+ var CATEGORY_FORMAT = {
2810
+ errors: "count",
2811
+ spans: "count",
2812
+ replays: "count",
2813
+ logBytes: "bytes",
2814
+ attachmentBytes: "bytes"
2815
+ };
2816
+ function statusNote(status) {
2817
+ switch (status) {
2818
+ case "attached":
2819
+ return "";
2820
+ case "sdk-not-found":
2821
+ return "\n\n\u26A0\uFE0F The tool never found @sentry/react-native \u2014 nothing is being captured. The app must pass `getClient` to SentryRoot, or the SDK isn't installed.";
2822
+ case "no-client":
2823
+ return "\n\n\u26A0\uFE0F The SDK is present but no client appeared \u2014 is Sentry.init() being called?";
2824
+ default:
2825
+ return "\n\n\u23F3 Still looking for the app's Sentry client.";
2826
+ }
2827
+ }
2828
+ function registerSentryTools(server, sync) {
2829
+ server.registerTool(
2830
+ "get_sentry_envelopes",
2831
+ {
2832
+ description: "Read what the app is actually sending to Sentry \u2014 every outgoing envelope with its item types, byte size, span count, and summary. Use this to answer 'is this error reaching Sentry', 'what does my app send at startup', or 'why is my Sentry bill high'. Debugging an error locally? These payloads are the final post-processing event, so you can read the exception, breadcrumbs and contexts here WITHOUT waiting for ingestion or spending quota. Filter by envelope item type (event / transaction / log / session / client_report / attachment / replay_event).",
2833
+ inputSchema: {
2834
+ type: import_zod13.z.string().optional().describe(
2835
+ "Only envelopes containing this item type, e.g. 'event' for errors or 'transaction' for traces."
2836
+ ),
2837
+ limit: import_zod13.z.number().optional().describe("Most-recent N envelopes. Default 25."),
2838
+ deviceId: import_zod13.z.string().optional().describe("Target device id (auto-selected if one is connected).")
2762
2839
  }
2763
2840
  },
2764
2841
  async (args) => {
2765
2842
  try {
2766
2843
  const target = sync.requireProDevice(args.deviceId);
2767
- const { payload, stale } = await sync.getSnapshot(CONSOLE_TOOL_ID, target);
2768
- const staleHeader = stale ? `${stale}
2844
+ const { payload, stale } = await sync.getSnapshot(SENTRY_TOOL_ID, target);
2845
+ const staleHeader2 = stale ? `${stale}
2769
2846
 
2770
2847
  ` : "";
2771
- const p = Array.isArray(payload) ? { entries: payload } : payload ?? {};
2772
- const all = Array.isArray(p.entries) ? p.entries : [];
2773
- const minSev = args.level ? SEVERITY[args.level] : 0;
2774
- const pattern = args.pattern?.toLowerCase();
2775
- const filtered = all.filter(
2776
- (e) => SEVERITY[e.level] >= minSev && (!pattern || (e.message ?? "").toLowerCase().includes(pattern))
2777
- );
2778
- const sorted = [...filtered].sort((a, b) => a.id - b.id);
2779
- const limit = typeof args.limit === "number" && args.limit > 0 ? args.limit : 50;
2780
- const shown = sorted.slice(-limit);
2848
+ const p = payload ?? {};
2849
+ const all = Array.isArray(p.envelopes) ? p.envelopes : [];
2850
+ const filtered = args.type ? all.filter((e) => e.items.some((i) => i.type === args.type)) : all;
2851
+ const limit = typeof args.limit === "number" && args.limit > 0 ? args.limit : 25;
2852
+ const shown = filtered.slice(-limit);
2781
2853
  if (shown.length === 0) {
2782
- return text10(`${staleHeader}No console entries${args.level ? ` at level \u2265 ${args.level}` : ""}${pattern ? ` matching "${args.pattern}"` : ""} (${all.length} total captured).`);
2854
+ return text2(
2855
+ `${staleHeader2}No Sentry envelopes${args.type ? ` containing a '${args.type}' item` : ""} (${all.length} captured this session).${statusNote(p.status)}`
2856
+ );
2783
2857
  }
2784
- const lines = shown.map((e) => `${hhmmss(e.timestamp)} [${TAG[e.level] ?? e.level}] ${e.message}`);
2785
- const levelNote = args.level ? `, level \u2265 ${args.level}` : "";
2786
- const header = `**Console \u2014 ${all.length} entries (showing ${shown.length}${levelNote}):**`;
2787
- return text10(`${staleHeader}${header}
2858
+ const lines = shown.map((e) => {
2859
+ const types = e.items.map((i) => i.type).join("+");
2860
+ const spans = e.items.reduce((sum, i) => sum + (i.spanCount ?? 0), 0);
2861
+ const spanNote = spans > 0 ? ` \xB7 ${spans} span${spans === 1 ? "" : "s"}` : "";
2862
+ const head = `${e.id}. ${hhmmss2(e.timestamp)} [${types}] ${bytes(e.totalBytes)}${spanNote}`;
2863
+ return `${head}
2864
+ \u2514\u2500 ${e.items[0]?.summary ?? "envelope"}`;
2865
+ });
2866
+ const header = `**Sentry \u2014 ${all.length} envelope${all.length === 1 ? "" : "s"} sent (showing ${shown.length}${args.type ? `, type '${args.type}'` : ""}):**`;
2867
+ return text2(
2868
+ `${staleHeader2}${header}${statusNote(p.status)}
2788
2869
 
2789
- \`\`\`
2790
2870
  ${lines.join("\n")}
2871
+
2872
+ _Full payloads (exception, breadcrumbs, contexts) via get_snapshot('sentry'). Billing units via get_sentry_cost._`
2873
+ );
2874
+ } catch (e) {
2875
+ return fail3(errMsg3(e));
2876
+ }
2877
+ }
2878
+ );
2879
+ server.registerTool(
2880
+ "get_sentry_cost",
2881
+ {
2882
+ description: "What this session would cost in Sentry's billing units, and what that rate implies for a month against the free plan. Sentry bills on RECEIPT \u2014 data its server-side sampling later discards is still charged \u2014 and spans are billed individually, so one transaction is many units. Use this before shipping a Sentry config, or when asked why a Sentry bill is high.",
2883
+ inputSchema: {
2884
+ deviceId: import_zod13.z.string().optional().describe("Target device id (auto-selected if one is connected).")
2885
+ }
2886
+ },
2887
+ async (args) => {
2888
+ try {
2889
+ const target = sync.requireProDevice(args.deviceId);
2890
+ const { payload, stale } = await sync.getSnapshot(SENTRY_TOOL_ID, target);
2891
+ const staleHeader2 = stale ? `${stale}
2892
+
2893
+ ` : "";
2894
+ const p = payload ?? {};
2895
+ const usage = p.usage;
2896
+ if (!usage?.session) {
2897
+ return text2(
2898
+ `${staleHeader2}No usage recorded yet \u2014 nothing has been sent to Sentry this session.${statusNote(p.status)}`
2899
+ );
2900
+ }
2901
+ const session = usage.session;
2902
+ const projected = usage.projectedMonthly;
2903
+ const keys = Object.keys(session);
2904
+ const rows = keys.map((key) => {
2905
+ const format = CATEGORY_FORMAT[key];
2906
+ const now = units(session[key], format);
2907
+ if (!projected) return `- ${key}: ${now} this session`;
2908
+ const month = projected[key];
2909
+ const quota = FREE_QUOTA[key];
2910
+ const pct = quota > 0 ? Math.round(month / quota * 100) : null;
2911
+ const pctNote = pct === null ? " (not on the free plan)" : ` \u2014 ${pct}% of the free plan's ${units(quota, format)}`;
2912
+ return `- ${key}: ${now} this session \u2192 ~${units(month, format)}/mo${pctNote}`;
2913
+ });
2914
+ const windowNote = usage.windowMs ? ` over ${Math.round(usage.windowMs / 1e3)}s` : "";
2915
+ const contributors = usage.topContributors?.length ? `
2916
+
2917
+ **What produced it:**
2918
+ ${usage.topContributors.map((c) => `- ${c.label} \u2014 ${c.units} ${c.category}`).join("\n")}` : "";
2919
+ const caveat = projected ? "\n\n_The monthly figure extrapolates one session on one device \u2014 it catches a config that is orders of magnitude off, it is not a forecast._" : "\n\n_Session too short to project a month from._";
2920
+ return text2(
2921
+ `${staleHeader2}**Sentry cost \u2014 ${p.envelopes?.length ?? 0} envelopes${windowNote}:**
2922
+
2923
+ ${rows.join("\n")}${contributors}${caveat}`
2924
+ );
2925
+ } catch (e) {
2926
+ return fail3(errMsg3(e));
2927
+ }
2928
+ }
2929
+ );
2930
+ server.registerTool(
2931
+ "get_sentry_drops",
2932
+ {
2933
+ description: "Why events never reached Sentry. Answers 'I called captureException but the event isn't in Sentry' \u2014 the single most common Sentry problem \u2014 by naming the layer that discarded it: a beforeSend callback returning null, a sample-rate roll, dedupe or another event processor, React Native tracing discarding an empty transaction, or a native rate limit.",
2934
+ inputSchema: {
2935
+ limit: import_zod13.z.number().optional().describe("Most-recent N drop records. Default 30."),
2936
+ deviceId: import_zod13.z.string().optional().describe("Target device id (auto-selected if one is connected).")
2937
+ }
2938
+ },
2939
+ async (args) => {
2940
+ try {
2941
+ const target = sync.requireProDevice(args.deviceId);
2942
+ const { payload, stale } = await sync.getSnapshot(SENTRY_TOOL_ID, target);
2943
+ const staleHeader2 = stale ? `${stale}
2944
+
2945
+ ` : "";
2946
+ const p = payload ?? {};
2947
+ const summary = p.drops?.summary ?? [];
2948
+ const recent = p.drops?.recent ?? [];
2949
+ if (summary.length === 0) {
2950
+ return text2(
2951
+ `${staleHeader2}Nothing dropped this session \u2014 every captured event reached the transport.${statusNote(p.status)}
2952
+
2953
+ _If an event is still missing from sentry.io, it left the device: check the DSN, the project, and Sentry's own inbound filters and quota._`
2954
+ );
2955
+ }
2956
+ const limit = typeof args.limit === "number" && args.limit > 0 ? args.limit : 30;
2957
+ const shown = recent.slice(-limit);
2958
+ const summaryLines = summary.map(
2959
+ (s) => `- ${s.reason}: ${s.units} unit${s.units === 1 ? "" : "s"} across ${s.records} record${s.records === 1 ? "" : "s"}`
2960
+ );
2961
+ const rows = shown.map(
2962
+ (d) => `${hhmmss2(d.timestamp)} [${d.reason}] ${d.category}${d.quantity > 1 ? ` \xD7${d.quantity}` : ""} \u2014 ${d.detail} (${d.source})`
2963
+ );
2964
+ return text2(
2965
+ `${staleHeader2}**Sentry drops \u2014 by reason:**
2966
+
2967
+ ${summaryLines.join("\n")}
2968
+
2969
+ **Recent (${shown.length}):**
2970
+
2971
+ \`\`\`
2972
+ ${rows.join("\n")}
2791
2973
  \`\`\`
2792
2974
 
2793
- _Tail of recent logs. Use get_snapshot('console') for full args/stacks._`);
2975
+ _"observed" means the tool watched it happen; "reported" means the SDK counted it afterwards. Native drops only appear when the SDK runs with debug enabled._`
2976
+ );
2977
+ } catch (e) {
2978
+ return fail3(errMsg3(e));
2979
+ }
2980
+ }
2981
+ );
2982
+ server.registerTool(
2983
+ "sentry_action",
2984
+ {
2985
+ description: "Act on the Sentry inspector. 'clear' drops every captured envelope and drop record \u2014 use it to get a clean window before driving a flow you want to measure.",
2986
+ inputSchema: {
2987
+ action: import_zod13.z.enum(["clear"]).describe("The action to perform."),
2988
+ deviceId: import_zod13.z.string().optional().describe("Target device id (auto-selected if one is connected).")
2989
+ }
2990
+ },
2991
+ async (args) => {
2992
+ try {
2993
+ const target = sync.requireProDevice(args.deviceId);
2994
+ await sync.callAction(SENTRY_TOOL_ID, "clearEnvelopes", {}, target);
2995
+ return text2(
2996
+ "Cleared the Sentry inspector \u2014 envelopes and drop records. Drive your flow, then read get_sentry_envelopes or get_sentry_cost."
2997
+ );
2794
2998
  } catch (e) {
2795
- return fail11(errMsg11(e));
2999
+ return fail3(errMsg3(e));
2796
3000
  }
2797
3001
  }
2798
3002
  );
2799
3003
  }
2800
3004
 
2801
3005
  // src/tools/packs/timeMachine.ts
2802
- var import_zod12 = require("zod");
3006
+ var import_zod14 = require("zod");
2803
3007
  var TIME_MACHINE_TOOL_ID = "time-machine";
2804
- var text11 = (s) => ({ content: [{ type: "text", text: s }] });
2805
- var fail12 = (message) => ({ isError: true, content: [{ type: "text", text: message }] });
2806
- var errMsg12 = (e) => e instanceof Error ? e.message : String(e);
2807
- function kb(bytes) {
2808
- return bytes < 1024 ? `${bytes} B` : `${(bytes / 1024).toFixed(1)} KB`;
3008
+ function kb(bytes2) {
3009
+ return bytes2 < 1024 ? `${bytes2} B` : `${(bytes2 / 1024).toFixed(1)} KB`;
2809
3010
  }
2810
3011
  function formatOutcome(outcome) {
2811
3012
  const lines = Object.entries(outcome.results).map(([sid, r2]) => {
@@ -2830,7 +3031,7 @@ function registerTimeMachineTools(server, sync) {
2830
3031
  {
2831
3032
  description: "List the app's Time Machine state snapshots (restore points) and which state sources (storage/redux/zustand/jotai/react-query) can currently be captured/restored. Each snapshot also records the route it was captured on (shown as `route`), which a restore can navigate back to. Use time_machine_action to capture a snapshot before driving a flow, then restore it to re-run the flow from the same state.",
2832
3033
  inputSchema: {
2833
- deviceId: import_zod12.z.string().optional().describe("Target device id (auto-selected if one is connected).")
3034
+ deviceId: deviceIdInput
2834
3035
  }
2835
3036
  },
2836
3037
  async (args) => {
@@ -2863,9 +3064,9 @@ ${providerLines.join("\n") || "_no providers registered_"}`
2863
3064
  ];
2864
3065
  if (p.lastOutcome) parts.push(`**Last restore:**
2865
3066
  ${formatOutcome(p.lastOutcome)}`);
2866
- return text11(parts.join("\n\n"));
3067
+ return text(parts.join("\n\n"));
2867
3068
  } catch (e) {
2868
- return fail12(errMsg12(e));
3069
+ return fail2(errMsg2(e));
2869
3070
  }
2870
3071
  }
2871
3072
  );
@@ -2874,14 +3075,14 @@ ${formatOutcome(p.lastOutcome)}`);
2874
3075
  {
2875
3076
  description: "Manage Time Machine state snapshots: capture the app's current state as a named restore point, preview what restoring one would change (per-item added/removed/changed/won't-apply verdicts \u2014 check the blast radius BEFORE restoring), restore one (mode 'live' replaces state in place; 'reload' restores persisted storage then reloads the JS bundle \u2014 baselines always reload; pass restoreRoute:true to also return to the screen the snapshot was captured on), capture_baseline (an empty wipe-to-fresh-install restore point), delete, or rename. The restore result reports per-source applied/skipped counts \u2014 check them rather than assuming success.",
2876
3077
  inputSchema: {
2877
- action: import_zod12.z.enum(["capture", "capture_baseline", "preview", "restore", "delete", "rename"]).describe("What to do."),
2878
- id: import_zod12.z.string().optional().describe("Snapshot id (required for restore/delete/rename; from get_time_machine)."),
2879
- name: import_zod12.z.string().optional().describe("Snapshot name (for capture and rename)."),
2880
- mode: import_zod12.z.enum(["live", "reload"]).optional().describe("Restore mode. Default 'live'."),
2881
- excludeKeys: import_zod12.z.array(import_zod12.z.string()).optional().describe('Restore only: item keys to LEAVE UNTOUCHED, as "sourceId::itemKey" (itemKey = the key shown by the preview action, e.g. "query::[\\"pokemon\\"]" or "storage::async:@cart").'),
2882
- restoreRoute: import_zod12.z.boolean().optional().describe("Restore only: also navigate back to the route the snapshot was captured on (shown as `route` by get_time_machine). With mode 'reload' the navigation happens automatically on the next boot. Default false \u2014 state is restored but the app stays on the current screen."),
2883
- sourceIds: import_zod12.z.array(import_zod12.z.string()).optional().describe("Capture/restore only these source ids (default all)."),
2884
- deviceId: import_zod12.z.string().optional().describe("Target device id (auto-selected if one is connected).")
3078
+ action: import_zod14.z.enum(["capture", "capture_baseline", "preview", "restore", "delete", "rename"]).describe("What to do."),
3079
+ id: import_zod14.z.string().optional().describe("Snapshot id (required for restore/delete/rename; from get_time_machine)."),
3080
+ name: import_zod14.z.string().optional().describe("Snapshot name (for capture and rename)."),
3081
+ mode: import_zod14.z.enum(["live", "reload"]).optional().describe("Restore mode. Default 'live'."),
3082
+ excludeKeys: import_zod14.z.array(import_zod14.z.string()).optional().describe('Restore only: item keys to LEAVE UNTOUCHED, as "sourceId::itemKey" (itemKey = the key shown by the preview action, e.g. "query::[\\"pokemon\\"]" or "storage::async:@cart").'),
3083
+ restoreRoute: import_zod14.z.boolean().optional().describe("Restore only: also navigate back to the route the snapshot was captured on (shown as `route` by get_time_machine). With mode 'reload' the navigation happens automatically on the next boot. Default false \u2014 state is restored but the app stays on the current screen."),
3084
+ sourceIds: import_zod14.z.array(import_zod14.z.string()).optional().describe("Capture/restore only these source ids (default all)."),
3085
+ deviceId: deviceIdInput
2885
3086
  }
2886
3087
  },
2887
3088
  async (args) => {
@@ -2894,14 +3095,14 @@ ${formatOutcome(p.lastOutcome)}`);
2894
3095
  const s = result.snapshot;
2895
3096
  const warnings = Object.entries(s.sources).flatMap(([sid, src]) => src.warnings.map((w) => `
2896
3097
  \u26A0 ${sid}: ${w}`)).join("");
2897
- return text11(`Captured \`${s.id}\` "${s.name}" \u2014 ${kb(s.sizeBytes)} \xB7 ${Object.keys(s.sources).join(", ")}${warnings}`);
3098
+ return text(`Captured \`${s.id}\` "${s.name}" \u2014 ${kb(s.sizeBytes)} \xB7 ${Object.keys(s.sources).join(", ")}${warnings}`);
2898
3099
  }
2899
3100
  case "capture_baseline": {
2900
3101
  const result = await call("captureBaseline", {});
2901
- return text11(`Captured baseline \`${result.snapshot.id}\` \u2014 restoring it wipes app storage and reloads.`);
3102
+ return text(`Captured baseline \`${result.snapshot.id}\` \u2014 restoring it wipes app storage and reloads.`);
2902
3103
  }
2903
3104
  case "preview": {
2904
- if (!args.id) return fail12("preview requires `id` (from get_time_machine).");
3105
+ if (!args.id) return fail2("preview requires `id` (from get_time_machine).");
2905
3106
  const result = await call("preview", { id: args.id });
2906
3107
  const lines = [];
2907
3108
  let totalApplies = 0;
@@ -2920,46 +3121,46 @@ ${formatOutcome(p.lastOutcome)}`);
2920
3121
  for (const note of src.notes) lines.push(` \xB7 ${note}`);
2921
3122
  }
2922
3123
  const head = totalApplies === 0 ? "**Restore would change NOTHING** (states identical or nothing applicable)." : `**Restore would apply ${totalApplies} change(s):**`;
2923
- return text11(`${head}
3124
+ return text(`${head}
2924
3125
  ${lines.join("\n")}`);
2925
3126
  }
2926
3127
  case "restore": {
2927
- if (!args.id) return fail12("restore requires `id` (from get_time_machine).");
3128
+ if (!args.id) return fail2("restore requires `id` (from get_time_machine).");
2928
3129
  const outcome = await call("restore", {
2929
3130
  id: args.id,
2930
3131
  mode: args.mode ?? "live",
2931
3132
  excludeKeys: args.excludeKeys,
2932
3133
  restoreRoute: args.restoreRoute
2933
3134
  });
2934
- return text11(formatOutcome(outcome));
3135
+ return text(formatOutcome(outcome));
2935
3136
  }
2936
3137
  case "delete": {
2937
- if (!args.id) return fail12("delete requires `id`.");
3138
+ if (!args.id) return fail2("delete requires `id`.");
2938
3139
  await call("delete", { id: args.id });
2939
- return text11(`Deleted snapshot \`${args.id}\`.`);
3140
+ return text(`Deleted snapshot \`${args.id}\`.`);
2940
3141
  }
2941
3142
  case "rename": {
2942
- if (!args.id || !args.name) return fail12("rename requires `id` and `name`.");
3143
+ if (!args.id || !args.name) return fail2("rename requires `id` and `name`.");
2943
3144
  await call("rename", { id: args.id, name: args.name });
2944
- return text11(`Renamed \`${args.id}\` to "${args.name}".`);
3145
+ return text(`Renamed \`${args.id}\` to "${args.name}".`);
2945
3146
  }
2946
3147
  }
2947
3148
  } catch (e) {
2948
- return fail12(errMsg12(e));
3149
+ return fail2(errMsg2(e));
2949
3150
  }
2950
3151
  }
2951
3152
  );
2952
3153
  }
2953
3154
 
2954
3155
  // src/tools/packs/scenarios.ts
2955
- var import_zod13 = require("zod");
3156
+ var import_zod15 = require("zod");
2956
3157
  var SCENARIOS_TOOL_ID = "scenarios";
2957
- var text12 = (s) => ({ content: [{ type: "text", text: s }] });
2958
- var fail13 = (message) => ({
3158
+ var text3 = (s) => ({ content: [{ type: "text", text: s }] });
3159
+ var fail4 = (message) => ({
2959
3160
  isError: true,
2960
3161
  content: [{ type: "text", text: message }]
2961
3162
  });
2962
- var errMsg13 = (e) => e instanceof Error ? e.message : String(e);
3163
+ var errMsg4 = (e) => e instanceof Error ? e.message : String(e);
2963
3164
  function varSpec(v) {
2964
3165
  const opts = v.options?.length ? ` one of ${v.options.join("|")}` : "";
2965
3166
  const def = v.default !== void 0 ? ` (default ${String(v.default)})` : "";
@@ -3021,7 +3222,7 @@ function registerScenariosTools(server, sync) {
3021
3222
  {
3022
3223
  description: "List the app's saved Scenarios \u2014 named, parameterized app states that can be applied with one call (override a response, write storage, impersonate a user, navigate). Shows what each one WILL do in plain English with a durability label per step, plus which scenario is currently active and what it changed. Use scenario_action to run one as a deterministic fixture BEFORE driving a flow, so the flow starts from a known state instead of whatever the app happened to be in. If a scenario is active, whatever you observe on the device is partly simulated \u2014 check here first when the app's data looks wrong.",
3023
3224
  inputSchema: {
3024
- deviceId: import_zod13.z.string().optional().describe("Target device id (auto-selected if one is connected).")
3225
+ deviceId: import_zod15.z.string().optional().describe("Target device id (auto-selected if one is connected).")
3025
3226
  }
3026
3227
  },
3027
3228
  async (args) => {
@@ -3051,9 +3252,9 @@ ${formatActive(p.active)}` : "**No scenario active** \u2014 the app is showing r
3051
3252
  ${drafts.map(summarize).join("\n")}`
3052
3253
  );
3053
3254
  }
3054
- return text12(parts.join("\n\n"));
3255
+ return text3(parts.join("\n\n"));
3055
3256
  } catch (e) {
3056
- return fail13(errMsg13(e));
3257
+ return fail4(errMsg4(e));
3057
3258
  }
3058
3259
  }
3059
3260
  );
@@ -3062,66 +3263,66 @@ ${drafts.map(summarize).join("\n")}`
3062
3263
  {
3063
3264
  description: `Drive Buoy Scenarios: 'preview' dry-runs one and reports exactly what it WOULD change (plus any conflict with the active scenario) without touching the app \u2014 do this before running an unfamiliar scenario; 'run' applies it and resolves only once every step has landed, which makes it a deterministic fixture to call before run_flow; 'deactivate' reverses it and reports what it could NOT restore (storage writes have no automatic inverse); 'save' writes a new scenario onto the device \u2014 it lands as a DRAFT that a human must accept on the device before it can run, so tell the user to go accept it; 'setFolder' files a saved scenario under a flow ("Checkout", "Login") or clears its folder, which is how a QA library stays navigable \u2014 check get_scenarios for the folders already in use and reuse one instead of inventing a near-duplicate; 'delete' removes a device scenario. Prefer a network override step over a query-cache poke: the override survives refetch and restart, the poke dies on the next fetch.`,
3064
3265
  inputSchema: {
3065
- action: import_zod13.z.enum(["preview", "run", "deactivate", "save", "setFolder", "delete"]).describe("What to do."),
3066
- folder: import_zod13.z.string().optional().describe(
3266
+ action: import_zod15.z.enum(["preview", "run", "deactivate", "save", "setFolder", "delete"]).describe("What to do."),
3267
+ folder: import_zod15.z.string().optional().describe(
3067
3268
  "Folder name for action 'setFolder' \u2014 omit to un-file the scenario. Matching an existing folder case-insensitively files it there rather than creating a second one."
3068
3269
  ),
3069
- id: import_zod13.z.string().optional().describe(
3270
+ id: import_zod15.z.string().optional().describe(
3070
3271
  "Scenario id (required for preview/run/delete; from get_scenarios)."
3071
3272
  ),
3072
- params: import_zod13.z.record(import_zod13.z.union([import_zod13.z.string(), import_zod13.z.number(), import_zod13.z.boolean()])).optional().describe(
3273
+ params: import_zod15.z.record(import_zod15.z.union([import_zod15.z.string(), import_zod15.z.number(), import_zod15.z.boolean()])).optional().describe(
3073
3274
  "Variable values for preview/run, e.g. {storeId:'220'}. Declared defaults fill in the rest."
3074
3275
  ),
3075
- scenario: import_zod13.z.object({
3076
- id: import_zod13.z.string().describe("Stable kebab-case id."),
3077
- name: import_zod13.z.string().describe("Human name; may contain {{variable}} placeholders."),
3078
- description: import_zod13.z.string().optional(),
3079
- expectedOutcome: import_zod13.z.string().optional().describe(
3276
+ scenario: import_zod15.z.object({
3277
+ id: import_zod15.z.string().describe("Stable kebab-case id."),
3278
+ name: import_zod15.z.string().describe("Human name; may contain {{variable}} placeholders."),
3279
+ description: import_zod15.z.string().optional(),
3280
+ expectedOutcome: import_zod15.z.string().optional().describe(
3080
3281
  "What SHOULD happen once applied \u2014 this is how a tester tells a bug from expected behavior without asking a developer. Write it."
3081
3282
  ),
3082
- author: import_zod13.z.string().optional(),
3083
- authoredAgainst: import_zod13.z.string().optional().describe("App version this was written against."),
3084
- folder: import_zod13.z.string().optional().describe(
3283
+ author: import_zod15.z.string().optional(),
3284
+ authoredAgainst: import_zod15.z.string().optional().describe("App version this was written against."),
3285
+ folder: import_zod15.z.string().optional().describe(
3085
3286
  'The flow this belongs to \u2014 "Checkout", "Login". Reuse a folder from get_scenarios; a scenario nobody can find is a scenario nobody runs.'
3086
3287
  ),
3087
- tags: import_zod13.z.array(import_zod13.z.string()).optional(),
3088
- vars: import_zod13.z.array(
3089
- import_zod13.z.object({
3090
- key: import_zod13.z.string(),
3091
- label: import_zod13.z.string().optional(),
3092
- type: import_zod13.z.enum(["string", "number", "boolean", "enum"]),
3093
- options: import_zod13.z.array(import_zod13.z.union([import_zod13.z.string(), import_zod13.z.number(), import_zod13.z.boolean()])).optional(),
3094
- default: import_zod13.z.union([import_zod13.z.string(), import_zod13.z.number(), import_zod13.z.boolean()]).optional()
3288
+ tags: import_zod15.z.array(import_zod15.z.string()).optional(),
3289
+ vars: import_zod15.z.array(
3290
+ import_zod15.z.object({
3291
+ key: import_zod15.z.string(),
3292
+ label: import_zod15.z.string().optional(),
3293
+ type: import_zod15.z.enum(["string", "number", "boolean", "enum"]),
3294
+ options: import_zod15.z.array(import_zod15.z.union([import_zod15.z.string(), import_zod15.z.number(), import_zod15.z.boolean()])).optional(),
3295
+ default: import_zod15.z.union([import_zod15.z.string(), import_zod15.z.number(), import_zod15.z.boolean()]).optional()
3095
3296
  })
3096
3297
  ).optional().describe(
3097
3298
  "Declare a variable for every value a tester might want to change (a store id, a user, a status code) instead of freezing it into the steps."
3098
3299
  ),
3099
- steps: import_zod13.z.array(
3100
- import_zod13.z.object({
3101
- id: import_zod13.z.string().optional(),
3102
- tool: import_zod13.z.string().describe(
3300
+ steps: import_zod15.z.array(
3301
+ import_zod15.z.object({
3302
+ id: import_zod15.z.string().optional(),
3303
+ tool: import_zod15.z.string().describe(
3103
3304
  "Tool id: network | storage | impersonate | route-events | query | time-machine"
3104
3305
  ),
3105
- action: import_zod13.z.string().describe(
3306
+ action: import_zod15.z.string().describe(
3106
3307
  "Action name on that tool, e.g. upsertOverrideRule, async.setItem, startImpersonation, navigate."
3107
3308
  ),
3108
- params: import_zod13.z.unknown().optional(),
3109
- label: import_zod13.z.string().optional()
3309
+ params: import_zod15.z.unknown().optional(),
3310
+ label: import_zod15.z.string().optional()
3110
3311
  })
3111
3312
  ).describe("Steps, in order."),
3112
- undoSteps: import_zod13.z.array(
3113
- import_zod13.z.object({
3114
- id: import_zod13.z.string().optional(),
3115
- tool: import_zod13.z.string(),
3116
- action: import_zod13.z.string(),
3117
- params: import_zod13.z.unknown().optional(),
3118
- label: import_zod13.z.string().optional()
3313
+ undoSteps: import_zod15.z.array(
3314
+ import_zod15.z.object({
3315
+ id: import_zod15.z.string().optional(),
3316
+ tool: import_zod15.z.string(),
3317
+ action: import_zod15.z.string(),
3318
+ params: import_zod15.z.unknown().optional(),
3319
+ label: import_zod15.z.string().optional()
3119
3320
  })
3120
3321
  ).optional().describe(
3121
3322
  "How to reverse effects that have no automatic inverse. Supply these whenever a step writes or removes storage, or deactivation will honestly report the key as unrestored."
3122
3323
  )
3123
3324
  }).optional().describe("The scenario to save (action 'save')."),
3124
- deviceId: import_zod13.z.string().optional().describe("Target device id (auto-selected if one is connected).")
3325
+ deviceId: import_zod15.z.string().optional().describe("Target device id (auto-selected if one is connected).")
3125
3326
  }
3126
3327
  },
3127
3328
  async (args) => {
@@ -3130,7 +3331,7 @@ ${drafts.map(summarize).join("\n")}`
3130
3331
  const call = (action, params) => sync.callAction(SCENARIOS_TOOL_ID, action, params, target);
3131
3332
  switch (args.action) {
3132
3333
  case "preview": {
3133
- if (!args.id) return fail13("preview requires `id` (from get_scenarios).");
3334
+ if (!args.id) return fail4("preview requires `id` (from get_scenarios).");
3134
3335
  const r = await call("preview", {
3135
3336
  id: args.id,
3136
3337
  params: args.params ?? {}
@@ -3153,16 +3354,16 @@ ${r.warnings.map((w) => ` \xB7 ${w}`).join("\n")}`);
3153
3354
  r.ok ? "\u2705 Pre-flight passed \u2014 safe to run." : `\u274C **Cannot run:**
3154
3355
  ${r.errors.map((e) => ` \xB7 ${e}`).join("\n")}`
3155
3356
  );
3156
- return text12(lines.join("\n\n"));
3357
+ return text3(lines.join("\n\n"));
3157
3358
  }
3158
3359
  case "run": {
3159
- if (!args.id) return fail13("run requires `id` (from get_scenarios).");
3360
+ if (!args.id) return fail4("run requires `id` (from get_scenarios).");
3160
3361
  const r = await call("run", {
3161
3362
  id: args.id,
3162
3363
  params: args.params ?? {}
3163
3364
  });
3164
3365
  if (r.preflightErrors?.length) {
3165
- return fail13(
3366
+ return fail4(
3166
3367
  `Pre-flight refused to run \`${args.id}\` \u2014 nothing was changed:
3167
3368
  ${r.preflightErrors.map((e) => ` \xB7 ${e}`).join("\n")}`
3168
3369
  );
@@ -3174,14 +3375,14 @@ ${r.preflightErrors.map((e) => ` \xB7 ${e}`).join("\n")}`
3174
3375
  return ` ${mark} ${s.label} (${s.ms}ms)${err}`;
3175
3376
  }).join("\n");
3176
3377
  if (!r.ok) {
3177
- return fail13(
3378
+ return fail4(
3178
3379
  `Run FAILED \u2014 it stopped at the failing step and nothing after it ran. The steps before it DID apply:
3179
3380
  ${steps}
3180
3381
 
3181
3382
  The device is now in a partial state. Deactivate to reverse what landed, or fix the step and run again.`
3182
3383
  );
3183
3384
  }
3184
- return text12(
3385
+ return text3(
3185
3386
  [
3186
3387
  `\u2705 **Scenario active \u2014 ${r.name ?? args.id}**`,
3187
3388
  steps,
@@ -3205,49 +3406,49 @@ ${(r.effects ?? []).map((e) => ` \xB7 ${e.label} [${e.kind}]`).join("\n")}`,
3205
3406
  const errs = rep.errors.length ? `
3206
3407
 
3207
3408
  Errors: ${rep.errors.join("; ")}` : "";
3208
- return text12(`**Deactivated.**
3409
+ return text3(`**Deactivated.**
3209
3410
  ${lines.map((l) => ` \xB7 ${l}`).join("\n")}${warn}${errs}`);
3210
3411
  }
3211
3412
  case "save": {
3212
- if (!args.scenario) return fail13("save requires `scenario`.");
3413
+ if (!args.scenario) return fail4("save requires `scenario`.");
3213
3414
  const r = await call("save", { scenario: args.scenario });
3214
3415
  if (!r.ok) {
3215
- return fail13(
3416
+ return fail4(
3216
3417
  `Save rejected:
3217
3418
  ${(r.errors ?? [r.error ?? "unknown error"]).map((e) => ` \xB7 ${e}`).join("\n")}`
3218
3419
  );
3219
3420
  }
3220
- return text12(
3421
+ return text3(
3221
3422
  `Saved \`${r.id}\` **as a draft**.
3222
3423
 
3223
3424
  It is INERT and cannot run yet. Ask the user to open Scenarios on the device, review what it will do, and tap **Accept to library** \u2014 a scenario never becomes runnable on someone's device without them seeing it first.`
3224
3425
  );
3225
3426
  }
3226
3427
  case "setFolder": {
3227
- if (!args.id) return fail13("setFolder requires `id` (from get_scenarios).");
3428
+ if (!args.id) return fail4("setFolder requires `id` (from get_scenarios).");
3228
3429
  const r = await call("setFolder", {
3229
3430
  id: args.id,
3230
3431
  folder: args.folder
3231
3432
  });
3232
- return text12(
3433
+ return text3(
3233
3434
  r.folder ? `Filed \`${r.id}\` under **${r.folder}**.` : `Removed \`${r.id}\` from its folder \u2014 it now shows under Ungrouped.`
3234
3435
  );
3235
3436
  }
3236
3437
  case "delete": {
3237
- if (!args.id) return fail13("delete requires `id`.");
3438
+ if (!args.id) return fail4("delete requires `id`.");
3238
3439
  await call("delete", { id: args.id });
3239
- return text12(`Deleted \`${args.id}\` from the device library.`);
3440
+ return text3(`Deleted \`${args.id}\` from the device library.`);
3240
3441
  }
3241
3442
  }
3242
3443
  } catch (e) {
3243
- return fail13(errMsg13(e));
3444
+ return fail4(errMsg4(e));
3244
3445
  }
3245
3446
  }
3246
3447
  );
3247
3448
  }
3248
3449
 
3249
3450
  // src/tools/packs/screenshot.ts
3250
- var import_zod14 = require("zod");
3451
+ var import_zod16 = require("zod");
3251
3452
 
3252
3453
  // src/capture/simctl.ts
3253
3454
  var import_node_child_process = require("child_process");
@@ -3361,8 +3562,8 @@ function frameToRect(frame, screen) {
3361
3562
  var TOOL_ID = "highlight-updates";
3362
3563
  var MARGIN_PT = 12;
3363
3564
  var CAPTURE_PADDING_PT = 6;
3364
- var fail14 = (message) => ({ isError: true, content: [{ type: "text", text: message }] });
3365
- var errMsg14 = (e) => e instanceof Error ? e.message : String(e);
3565
+ var fail5 = (message) => ({ isError: true, content: [{ type: "text", text: message }] });
3566
+ var errMsg5 = (e) => e instanceof Error ? e.message : String(e);
3366
3567
  function reasonMessage(res, query) {
3367
3568
  switch (res.reason) {
3368
3569
  case "no-renders":
@@ -3392,10 +3593,10 @@ function registerScreenshotTools(server, sync) {
3392
3593
  {
3393
3594
  description: "Take a tight, cropped screenshot of a single React Native component running in the iOS Simulator \u2014 no manual coordinates needed. Give a testID, nativeID, accessibilityLabel, or component name in `query` (or an exact `nativeTag` from a previous call's candidates) and it locates the live component on-device (scrolling it into view), then captures + crops the Simulator framebuffer to just that component. If highlight tracking hasn't seen a render yet, it falls back to a live fiber walk (same as describe_screen). iOS Simulator only, and the MCP server must run on the same Mac as the booted Simulator. Requires Buoy Pro.",
3394
3595
  inputSchema: {
3395
- query: import_zod14.z.string().optional().describe('Fuzzy match against testID, nativeID, accessibilityLabel, component name, or view type \u2014 e.g. "submit-button".'),
3396
- nativeTag: import_zod14.z.number().optional().describe("An exact nativeTag (from a previous call's candidates) to skip fuzzy matching."),
3397
- scrollIntoView: import_zod14.z.boolean().optional().describe("Scroll the component into view before capturing, if it's off-screen. Default true."),
3398
- deviceId: import_zod14.z.string().optional().describe("Target device id (auto-selected if one is connected).")
3596
+ query: import_zod16.z.string().optional().describe('Fuzzy match against testID, nativeID, accessibilityLabel, component name, or view type \u2014 e.g. "submit-button".'),
3597
+ nativeTag: import_zod16.z.number().optional().describe("An exact nativeTag (from a previous call's candidates) to skip fuzzy matching."),
3598
+ scrollIntoView: import_zod16.z.boolean().optional().describe("Scroll the component into view before capturing, if it's off-screen. Default true."),
3599
+ deviceId: deviceIdInput
3399
3600
  }
3400
3601
  },
3401
3602
  async (args) => {
@@ -3437,11 +3638,11 @@ function registerScreenshotTools(server, sync) {
3437
3638
  }
3438
3639
  }
3439
3640
  if (!located.matched || !located.rect || !located.screen) {
3440
- return fail14(reasonMessage(located, args.query) + candidatesText(located.candidates));
3641
+ return fail5(reasonMessage(located, args.query) + candidatesText(located.candidates));
3441
3642
  }
3442
3643
  const shot = await captureIosFramebuffer();
3443
3644
  if (!shot.ok || !shot.srcPath || !shot.width || !shot.height) {
3444
- return fail14(shot.error ?? "Screenshot capture failed.");
3645
+ return fail5(shot.error ?? "Screenshot capture failed.");
3445
3646
  }
3446
3647
  const pxScale = shot.width / located.screen.width;
3447
3648
  const r = located.rect;
@@ -3457,7 +3658,7 @@ function registerScreenshotTools(server, sync) {
3457
3658
  }
3458
3659
  );
3459
3660
  if (!crop.ok || !crop.buffer) {
3460
- return fail14(crop.error ?? "Crop failed.");
3661
+ return fail5(crop.error ?? "Crop failed.");
3461
3662
  }
3462
3663
  const label = located.componentName || "component";
3463
3664
  const idPart = located.testID ? ` (testID: "${located.testID}")` : "";
@@ -3470,14 +3671,14 @@ function registerScreenshotTools(server, sync) {
3470
3671
  ]
3471
3672
  };
3472
3673
  } catch (e) {
3473
- return fail14(errMsg14(e));
3674
+ return fail5(errMsg5(e));
3474
3675
  }
3475
3676
  }
3476
3677
  );
3477
3678
  }
3478
3679
 
3479
3680
  // src/tools/packs/camera.ts
3480
- var import_zod15 = require("zod");
3681
+ var import_zod17 = require("zod");
3481
3682
 
3482
3683
  // src/capture/camera.ts
3483
3684
  var import_node_child_process2 = require("child_process");
@@ -3633,9 +3834,9 @@ _Tell the user to enter a licence in Buoy Desktop, or see https://buoy.gg/pricin
3633
3834
  }
3634
3835
 
3635
3836
  // src/tools/packs/camera.ts
3636
- var text13 = (s) => ({ content: [{ type: "text", text: s }] });
3637
- var gate = (tool) => readEntitlement().isPro ? null : fail15(proRequired(tool));
3638
- var fail15 = (message) => ({
3837
+ var text4 = (s) => ({ content: [{ type: "text", text: s }] });
3838
+ var gate = (tool) => readEntitlement().isPro ? null : fail6(proRequired(tool));
3839
+ var fail6 = (message) => ({
3639
3840
  isError: true,
3640
3841
  content: [{ type: "text", text: message }]
3641
3842
  });
@@ -3645,11 +3846,11 @@ function registerCameraTools(server) {
3645
3846
  {
3646
3847
  description: "Give the iOS Simulator a camera, and choose what it shows. Use this before testing anything that opens a camera \u2014 a QR or barcode scanner, document capture, an avatar picker. `barcode` generates the code from text, so testing a scanner needs no printed code and no second device; pdf417 is the barcode on a driver's licence. `screen` points the phone's camera at this Mac's screen. Needs no Buoy integration in the app under test.",
3647
3848
  inputSchema: {
3648
- kind: import_zod15.z.enum(["barcode", "image", "video", "camera", "screen", "pattern"]).describe("What the camera should show."),
3649
- value: import_zod15.z.string().optional().describe(
3849
+ kind: import_zod17.z.enum(["barcode", "image", "video", "camera", "screen", "pattern"]).describe("What the camera should show."),
3850
+ value: import_zod17.z.string().optional().describe(
3650
3851
  "Text for `barcode`, a file path for `image`/`video`, a camera uniqueId for `camera` (see `camera_inputs`), or one of `behind` / `display` / `window:<id>` / `region:<id>[:<gutter>]` for `screen`."
3651
3852
  ),
3652
- format: import_zod15.z.enum(["qr", "pdf417", "aztec", "code128"]).optional().describe("Barcode symbology; defaults to qr.")
3853
+ format: import_zod17.z.enum(["qr", "pdf417", "aztec", "code128"]).optional().describe("Barcode symbology; defaults to qr.")
3653
3854
  }
3654
3855
  },
3655
3856
  async (args) => {
@@ -3660,12 +3861,12 @@ function registerCameraTools(server) {
3660
3861
  if (args.format) cmd.push("--format", args.format);
3661
3862
  const result = await run(cmd);
3662
3863
  if (!result.ok) {
3663
- return fail15(
3864
+ return fail6(
3664
3865
  result.error ?? result.data?.error ?? "could not set the camera source"
3665
3866
  );
3666
3867
  }
3667
3868
  const what = args.kind === "barcode" ? `a ${args.format ?? "qr"} code reading "${args.value}"` : args.kind === "screen" ? `this Mac's screen (${args.value ?? "behind the simulator window"})` : args.value ?? args.kind;
3668
- return text13(
3869
+ return text4(
3669
3870
  `The simulator camera is now showing ${what}, at ${result.data?.fps ?? 0} fps.
3670
3871
 
3671
3872
  _Apps already running do not pick up a camera they started without \u2014 use \`camera_launch\` to relaunch one, or \`camera_zero_setup\` so that anything the simulator launches gets it._`
@@ -3683,15 +3884,15 @@ _Apps already running do not pick up a camera they started without \u2014 use \`
3683
3884
  if (blocked) return blocked;
3684
3885
  const result = await run(["status"]);
3685
3886
  if (!result.ok || !result.data) {
3686
- return fail15(result.error ?? "could not read the simulator camera status");
3887
+ return fail6(result.error ?? "could not read the simulator camera status");
3687
3888
  }
3688
3889
  const { publishing, fps, codes } = result.data;
3689
3890
  if (!publishing) {
3690
- return text13(
3891
+ return text4(
3691
3892
  "The simulator camera is not publishing anything.\n\n_Set a source with `camera_source` first._"
3692
3893
  );
3693
3894
  }
3694
- return text13(
3895
+ return text4(
3695
3896
  [
3696
3897
  `Publishing at ${fps} fps.`,
3697
3898
  codes.length ? `Currently decoding: ${codes.map((c) => `\`${c}\``).join(", ")}` : "No barcode in the current frame.",
@@ -3706,8 +3907,8 @@ _Apps already running do not pick up a camera they started without \u2014 use \`
3706
3907
  {
3707
3908
  description: "Relaunch a simulator app with the camera attached. A camera can only be attached when a process starts, so an app that is already running cannot be given one \u2014 this terminates and relaunches it.",
3708
3909
  inputSchema: {
3709
- bundleId: import_zod15.z.string().describe("The app's bundle identifier."),
3710
- udid: import_zod15.z.string().optional().describe("Simulator udid, if more than one is booted.")
3910
+ bundleId: import_zod17.z.string().describe("The app's bundle identifier."),
3911
+ udid: import_zod17.z.string().optional().describe("Simulator udid, if more than one is booted.")
3711
3912
  }
3712
3913
  },
3713
3914
  async (args) => {
@@ -3717,9 +3918,9 @@ _Apps already running do not pick up a camera they started without \u2014 use \`
3717
3918
  if (args.udid) cmd.push("--udid", args.udid);
3718
3919
  const result = await run(cmd);
3719
3920
  if (!result.ok) {
3720
- return fail15(result.error ?? result.data?.error ?? "could not launch the app");
3921
+ return fail6(result.error ?? result.data?.error ?? "could not launch the app");
3721
3922
  }
3722
- return text13(
3923
+ return text4(
3723
3924
  `Relaunched \`${args.bundleId}\` with the camera attached${result.data?.pid ? ` (pid ${result.data.pid})` : ""}.`
3724
3925
  );
3725
3926
  }
@@ -3729,7 +3930,7 @@ _Apps already running do not pick up a camera they started without \u2014 use \`
3729
3930
  {
3730
3931
  description: "Work out why the simulator camera is not working. Checks that the pieces are present, a simulator is booted, something is publishing, SOMETHING IS READING IT, the library is loaded in the target app, no other tool owns the injection, and no camera library in the project disables simulator cameras at compile time.",
3731
3932
  inputSchema: {
3732
- bundleId: import_zod15.z.string().optional().describe("Check a specific app \u2014 whether the library is actually loaded in it.")
3933
+ bundleId: import_zod17.z.string().optional().describe("Check a specific app \u2014 whether the library is actually loaded in it.")
3733
3934
  }
3734
3935
  },
3735
3936
  async (args) => {
@@ -3737,11 +3938,11 @@ _Apps already running do not pick up a camera they started without \u2014 use \`
3737
3938
  if (args.bundleId) cmd.push(args.bundleId);
3738
3939
  const result = await run(cmd);
3739
3940
  if (!result.data?.checks) {
3740
- return fail15(result.error ?? "could not run the simulator camera diagnosis");
3941
+ return fail6(result.error ?? "could not run the simulator camera diagnosis");
3741
3942
  }
3742
3943
  const ent = readEntitlement();
3743
3944
  const tierNote = ent.isPro ? "" : "\n\n_This install is on the **free** tier, so the other `camera_*` tools are blocked. The camera still works by hand in the Buoy desktop panel._";
3744
- return text13(describeDiagnosis(result.data.checks) + tierNote);
3945
+ return text4(describeDiagnosis(result.data.checks) + tierNote);
3745
3946
  }
3746
3947
  );
3747
3948
  server.registerTool(
@@ -3755,9 +3956,9 @@ _Apps already running do not pick up a camera they started without \u2014 use \`
3755
3956
  if (blocked) return blocked;
3756
3957
  const result = await run(["stop"]);
3757
3958
  if (!result.ok) {
3758
- return fail15(result.error ?? result.data?.error ?? "could not stop the camera");
3959
+ return fail6(result.error ?? result.data?.error ?? "could not stop the camera");
3759
3960
  }
3760
- return text13("The simulator camera is off.");
3961
+ return text4("The simulator camera is off.");
3761
3962
  }
3762
3963
  );
3763
3964
  server.registerTool(
@@ -3765,7 +3966,7 @@ _Apps already running do not pick up a camera they started without \u2014 use \`
3765
3966
  {
3766
3967
  description: "List booted simulators and the apps installed on one. Use this to find the bundle id `camera_launch` needs, or the udid to pass when more than one simulator is booted. Lists every app on the simulator, not just Buoy-integrated ones \u2014 the camera works on any of them.",
3767
3968
  inputSchema: {
3768
- udid: import_zod15.z.string().optional().describe("Which simulator's apps to list. Only needed when several are booted.")
3969
+ udid: import_zod17.z.string().optional().describe("Which simulator's apps to list. Only needed when several are booted.")
3769
3970
  }
3770
3971
  },
3771
3972
  async (args) => {
@@ -3773,7 +3974,7 @@ _Apps already running do not pick up a camera they started without \u2014 use \`
3773
3974
  if (blocked) return blocked;
3774
3975
  const devicesResult = await run(["devices"]);
3775
3976
  if (!devicesResult.ok || !devicesResult.data) {
3776
- return fail15(devicesResult.error ?? "could not list booted simulators");
3977
+ return fail6(devicesResult.error ?? "could not list booted simulators");
3777
3978
  }
3778
3979
  const devices = devicesResult.data.devices ?? [];
3779
3980
  let apps = null;
@@ -3784,7 +3985,7 @@ _Apps already running do not pick up a camera they started without \u2014 use \`
3784
3985
  ]);
3785
3986
  apps = appsResult.ok ? appsResult.data?.apps ?? [] : null;
3786
3987
  }
3787
- return text13(describeDevices(devices, apps));
3988
+ return text4(describeDevices(devices, apps));
3788
3989
  }
3789
3990
  );
3790
3991
  server.registerTool(
@@ -3801,11 +4002,11 @@ _Apps already running do not pick up a camera they started without \u2014 use \`
3801
4002
  run(["windows"])
3802
4003
  ]);
3803
4004
  if (!cams.ok && !wins.ok) {
3804
- return fail15(
4005
+ return fail6(
3805
4006
  cams.error ?? wins.error ?? "could not list the Mac's cameras or windows"
3806
4007
  );
3807
4008
  }
3808
- return text13(
4009
+ return text4(
3809
4010
  describeInputs(cams.data?.cameras ?? [], wins.data?.windows ?? [])
3810
4011
  );
3811
4012
  }
@@ -3815,8 +4016,8 @@ _Apps already running do not pick up a camera they started without \u2014 use \`
3815
4016
  {
3816
4017
  description: "Make every app the simulator launches get a camera, however it was launched \u2014 `expo start`, `react-native run-ios`, Xcode, a test runner. Use this when you are not the one starting the app, so `camera_launch` is not an option. It loads Buoy's library into every process in that simulator, SpringBoard included, so turn it off when finished.",
3817
4018
  inputSchema: {
3818
- action: import_zod15.z.enum(["on", "off", "status"]).describe("Turn it on, off, or report the current state."),
3819
- udid: import_zod15.z.string().optional().describe("Which simulator. Only needed when several are booted.")
4019
+ action: import_zod17.z.enum(["on", "off", "status"]).describe("Turn it on, off, or report the current state."),
4020
+ udid: import_zod17.z.string().optional().describe("Which simulator. Only needed when several are booted.")
3820
4021
  }
3821
4022
  },
3822
4023
  async (args) => {
@@ -3824,36 +4025,36 @@ _Apps already running do not pick up a camera they started without \u2014 use \`
3824
4025
  if (blocked) return blocked;
3825
4026
  const result = await run(["zero-setup", args.action, ...args.udid ? ["--udid", args.udid] : []]);
3826
4027
  if (!result.ok) {
3827
- return fail15(
4028
+ return fail6(
3828
4029
  result.error ?? result.data?.error ?? "could not change zero-setup mode"
3829
4030
  );
3830
4031
  }
3831
4032
  const d = result.data ?? {};
3832
4033
  if (args.action === "status") {
3833
- if (!d.enabled) return text13("Zero-setup mode is **off**.");
3834
- return text13(
4034
+ if (!d.enabled) return text4("Zero-setup mode is **off**.");
4035
+ return text4(
3835
4036
  d.foreign ? `Zero-setup is on, but **another tool owns it** (\`${d.path}\`). Quit that tool before turning Buoy's on.` : "Zero-setup mode is **on** \u2014 anything this simulator launches gets the camera."
3836
4037
  );
3837
4038
  }
3838
4039
  if (args.action === "on") {
3839
- return text13(
4040
+ return text4(
3840
4041
  "Zero-setup mode is **on**. Anything this simulator launches from now on gets the camera \u2014 start your app however you normally would.\n\n_This loads the library into every process in the simulator. Turn it off with `camera_zero_setup` action `off` when you are done._"
3841
4042
  );
3842
4043
  }
3843
- return text13("Zero-setup mode is **off**.");
4044
+ return text4("Zero-setup mode is **off**.");
3844
4045
  }
3845
4046
  );
3846
4047
  }
3847
4048
 
3848
4049
  // src/tools/packs/interact.ts
3849
- var import_zod16 = require("zod");
4050
+ var import_zod18 = require("zod");
3850
4051
  var TOOL_ID2 = "highlight-updates";
3851
- var text14 = (s) => ({ content: [{ type: "text", text: s }] });
3852
- var fail16 = (message) => ({
4052
+ var text5 = (s) => ({ content: [{ type: "text", text: s }] });
4053
+ var fail7 = (message) => ({
3853
4054
  isError: true,
3854
4055
  content: [{ type: "text", text: message }]
3855
4056
  });
3856
- var errMsg15 = (e) => e instanceof Error ? e.message : String(e);
4057
+ var errMsg6 = (e) => e instanceof Error ? e.message : String(e);
3857
4058
  function formatElement(e) {
3858
4059
  let line = e.interactive ? "\u2022 " : " ";
3859
4060
  line += e.name;
@@ -3876,8 +4077,8 @@ function registerInteractTools(server, sync) {
3876
4077
  {
3877
4078
  description: "List the interactive / labeled elements currently on screen \u2014 the app's live accessibility-style tree walked from the React fiber tree. Each row shows the component name, visible text/label, role, testID, a normalized tap point (tap: x,y in 0\u20131), and its #nativeTag. Rows marked with \u2022 are tappable (have an onPress). Use this to see what's on screen and pick something to tap with tap_element \u2014 no screenshot needed. If you already know the sequence you want to drive, `run_flow` does the whole thing (taps, waits, assertions) in one call instead. Re-run after navigating or when the screen changes, since positions move. Requires Buoy Pro.",
3878
4079
  inputSchema: {
3879
- interactiveOnly: import_zod16.z.boolean().optional().describe("Only return tappable (onPress) elements. Default false (include labels/text for context)."),
3880
- deviceId: import_zod16.z.string().optional().describe("Target device id (auto-selected if one is connected).")
4080
+ interactiveOnly: import_zod18.z.boolean().optional().describe("Only return tappable (onPress) elements. Default false (include labels/text for context)."),
4081
+ deviceId: deviceIdInput
3881
4082
  }
3882
4083
  },
3883
4084
  async (args) => {
@@ -3887,16 +4088,16 @@ function registerInteractTools(server, sync) {
3887
4088
  const all = res?.elements ?? [];
3888
4089
  const elements = args.interactiveOnly ? all.filter((e) => e.interactive) : all;
3889
4090
  if (elements.length === 0) {
3890
- return text14(
4091
+ return text5(
3891
4092
  "No elements found on screen. If the app just navigated, wait a moment and retry; make sure highlight-updates is enabled in the app."
3892
4093
  );
3893
4094
  }
3894
4095
  const header = `Screen ${res.screen.width}\xD7${res.screen.height} \u2014 ${elements.length} element${elements.length === 1 ? "" : "s"} (\u2022 = tappable). Tap with tap_element({ testID | query | nativeTag }).`;
3895
- return text14(`${header}
4096
+ return text5(`${header}
3896
4097
 
3897
4098
  ${elements.map(formatElement).join("\n")}`);
3898
4099
  } catch (e) {
3899
- return fail16(errMsg15(e));
4100
+ return fail7(errMsg6(e));
3900
4101
  }
3901
4102
  }
3902
4103
  );
@@ -3905,20 +4106,20 @@ ${elements.map(formatElement).join("\n")}`);
3905
4106
  {
3906
4107
  description: "Interact with an on-screen element by invoking its handler directly in JS (no screenshot, no coordinate math, works on real devices). Handles buttons (onPress + onLongPress), toggles/switches and sliders (onValueChange), and text inputs (onChangeText) \u2014 see each element's `control` in describe_screen. Identify the target with an exact `nativeTag` or `testID`, or a fuzzy `query`. Pass `longPress:true` to invoke onLongPress instead of onPress (elements that support it show {long-press} in describe_screen). For a toggle, pass `value:true/false` (omit to flip); for a slider, pass a numeric `value` (omit to jump to the far end); for a text input, pass `text`. If the element is off-screen it's scrolled into view first (scrollIntoView:false to skip). Call describe_screen first to see options + current values. Driving MORE THAN ONE step (reproducing a bug, verifying a fix)? Use `run_flow` instead \u2014 it runs the whole sequence in one call. Requires Buoy Pro.",
3907
4108
  inputSchema: {
3908
- nativeTag: import_zod16.z.number().optional().describe("Exact nativeTag from describe_screen (most precise)."),
3909
- testID: import_zod16.z.string().optional().describe("Exact testID of the element."),
3910
- query: import_zod16.z.string().optional().describe('Fuzzy match against testID / label / visible text / component name \u2014 e.g. "Sign in".'),
3911
- value: import_zod16.z.union([import_zod16.z.number(), import_zod16.z.boolean()]).optional().describe("For a toggle: true/false (omit to flip). For a slider: the numeric value (omit to jump to the far end)."),
3912
- text: import_zod16.z.string().optional().describe("For a text input: the string to set via onChangeText."),
3913
- longPress: import_zod16.z.boolean().optional().describe("Invoke the element's onLongPress instead of onPress. Fails with a clear reason if the element has no onLongPress handler."),
3914
- scrollIntoView: import_zod16.z.boolean().optional().describe("Scroll the target into view first if it's off-screen. Default true."),
3915
- deviceId: import_zod16.z.string().optional().describe("Target device id (auto-selected if one is connected).")
4109
+ nativeTag: import_zod18.z.number().optional().describe("Exact nativeTag from describe_screen (most precise)."),
4110
+ testID: import_zod18.z.string().optional().describe("Exact testID of the element."),
4111
+ query: import_zod18.z.string().optional().describe('Fuzzy match against testID / label / visible text / component name \u2014 e.g. "Sign in".'),
4112
+ value: import_zod18.z.union([import_zod18.z.number(), import_zod18.z.boolean()]).optional().describe("For a toggle: true/false (omit to flip). For a slider: the numeric value (omit to jump to the far end)."),
4113
+ text: import_zod18.z.string().optional().describe("For a text input: the string to set via onChangeText."),
4114
+ longPress: import_zod18.z.boolean().optional().describe("Invoke the element's onLongPress instead of onPress. Fails with a clear reason if the element has no onLongPress handler."),
4115
+ scrollIntoView: import_zod18.z.boolean().optional().describe("Scroll the target into view first if it's off-screen. Default true."),
4116
+ deviceId: deviceIdInput
3916
4117
  }
3917
4118
  },
3918
4119
  async (args) => {
3919
4120
  try {
3920
4121
  if (args.nativeTag == null && !args.testID && !args.query) {
3921
- return fail16("Provide one of: nativeTag, testID, or query (from describe_screen).");
4122
+ return fail7("Provide one of: nativeTag, testID, or query (from describe_screen).");
3922
4123
  }
3923
4124
  const target = sync.requireProDevice(args.deviceId);
3924
4125
  const res = await sync.callAction(
@@ -3943,26 +4144,23 @@ ${elements.map(formatElement).join("\n")}`);
3943
4144
  const id = m.testID ? ` [testID=${m.testID}]` : m.nativeTag != null ? ` #${m.nativeTag}` : "";
3944
4145
  const scrolledNote = res.scrolled ? " (scrolled into view)" : "";
3945
4146
  const verb = m.via === "onValueChange" ? `Set "${label}"${id} \u2192 ${JSON.stringify(m.value)}` : m.via === "onChangeText" ? `Typed ${JSON.stringify(m.value)} into "${label}"${id}` : m.via === "onLongPress" ? `Long-pressed "${label}"${id}` : `Tapped "${label}"${id}`;
3946
- return text14(`${verb}${scrolledNote}.${flowTip}`);
4147
+ return text5(`${verb}${scrolledNote}.${flowTip}`);
3947
4148
  }
3948
4149
  let msg = res?.reason ?? "Tap failed.";
3949
4150
  if (res?.candidates?.length) {
3950
4151
  msg += "\n\nTappable candidates:\n" + res.candidates.map((c) => `- ${c.name}${c.text ? ` "${c.text}"` : ""}${c.testID ? ` [testID=${c.testID}]` : ""}${c.nativeTag != null ? ` #${c.nativeTag}` : ""}`).join("\n");
3951
4152
  }
3952
- return fail16(msg);
4153
+ return fail7(msg);
3953
4154
  } catch (e) {
3954
- return fail16(errMsg15(e));
4155
+ return fail7(errMsg6(e));
3955
4156
  }
3956
4157
  }
3957
4158
  );
3958
4159
  }
3959
4160
 
3960
4161
  // src/tools/packs/jsThread.ts
3961
- var import_zod17 = require("zod");
4162
+ var import_zod19 = require("zod");
3962
4163
  var JS_TOP_TOOL_ID = "js-top";
3963
- var text15 = (s) => ({ content: [{ type: "text", text: s }] });
3964
- var fail17 = (message) => ({ isError: true, content: [{ type: "text", text: message }] });
3965
- var errMsg16 = (e) => e instanceof Error ? e.message : String(e);
3966
4164
  var SPARK_CHARS = "\u2581\u2582\u2583\u2584\u2585\u2586\u2587\u2588";
3967
4165
  function sparkline(values) {
3968
4166
  const max = Math.max(1, ...values);
@@ -4033,9 +4231,9 @@ function registerJsThreadTools(server, sync) {
4033
4231
  {
4034
4232
  description: 'Task Manager for the React Native JS thread: sample for a few seconds and return a ranked table of which task origins (timers, Promise chains, rAF callbacks, bridge call-ins) consumed JS-thread time \u2014 plus thread busy% and freeze (50ms+ block) attribution. Use when JS FPS is low and you need to know WHAT is eating the thread. To profile one specific interaction, pass clearFirst:true, then drive the UI with tap_element while the sample runs. Drill into a row with get_js_thread_origin_detail. NOT the tool for React components re-rendering too often \u2014 this measures TASKS on the thread, not renders; for "the page re-renders constantly / typing lags / tapping one button re-renders everything" use measure_renders. Requires @buoy-gg/js-top on the device.',
4035
4233
  inputSchema: {
4036
- deviceId: import_zod17.z.string().optional().describe("Target device id (auto-selected if one is connected)."),
4037
- durationMs: import_zod17.z.number().optional().describe("How long to sample before reading the table. Default 3000, max 30000."),
4038
- clearFirst: import_zod17.z.boolean().optional().describe("Reset all stats before sampling, so the result attributes only what happens during this sample.")
4234
+ deviceId: deviceIdInput,
4235
+ durationMs: import_zod19.z.number().optional().describe("How long to sample before reading the table. Default 3000, max 30000."),
4236
+ clearFirst: import_zod19.z.boolean().optional().describe("Reset all stats before sampling, so the result attributes only what happens during this sample.")
4039
4237
  }
4040
4238
  },
4041
4239
  async ({ deviceId, durationMs, clearFirst }) => {
@@ -4048,7 +4246,7 @@ function registerJsThreadTools(server, sync) {
4048
4246
  { durationMs: duration, clearFirst: clearFirst === true },
4049
4247
  target
4050
4248
  );
4051
- if (!snap) return fail17("Device returned no snapshot \u2014 is @buoy-gg/js-top installed in the app?");
4249
+ if (!snap) return fail2("Device returned no snapshot \u2014 is @buoy-gg/js-top installed in the app?");
4052
4250
  const md = formatTopMarkdown(snap, duration);
4053
4251
  const slim = {
4054
4252
  busyPct: Math.round(snap.busyPct * 10) / 10,
@@ -4067,13 +4265,13 @@ function registerJsThreadTools(server, sync) {
4067
4265
  worstFreezeMs: r.worstFreezeMs
4068
4266
  }))
4069
4267
  };
4070
- return text15(`${md}
4268
+ return text(`${md}
4071
4269
 
4072
4270
  \`\`\`json
4073
4271
  ${JSON.stringify(slim)}
4074
4272
  \`\`\``);
4075
4273
  } catch (e) {
4076
- return fail17(errMsg16(e));
4274
+ return fail2(errMsg2(e));
4077
4275
  }
4078
4276
  }
4079
4277
  );
@@ -4082,8 +4280,8 @@ ${JSON.stringify(slim)}
4082
4280
  {
4083
4281
  description: 'Inspect one js-top task origin in depth: schedules vs runs, avg/max/total exclusive ms, a 5s activity sparkline, freeze attribution, and the captured scheduling site (caller function + stack frames). Pass a row `key` from get_js_thread_top\'s JSON (e.g. "interval|pollFeed" or "unattributed" for the system row).',
4084
4282
  inputSchema: {
4085
- deviceId: import_zod17.z.string().optional().describe("Target device id (auto-selected if one is connected)."),
4086
- key: import_zod17.z.string().describe("Origin key from get_js_thread_top's JSON rows.")
4283
+ deviceId: deviceIdInput,
4284
+ key: import_zod19.z.string().describe("Origin key from get_js_thread_top's JSON rows.")
4087
4285
  }
4088
4286
  },
4089
4287
  async ({ deviceId, key }) => {
@@ -4096,18 +4294,18 @@ ${JSON.stringify(slim)}
4096
4294
  target
4097
4295
  );
4098
4296
  if (!detail) {
4099
- return fail17(
4297
+ return fail2(
4100
4298
  `Origin "${key}" is not tracked (cleared, evicted, or never existed). Re-run get_js_thread_top for current keys.`
4101
4299
  );
4102
4300
  }
4103
4301
  const md = formatDetailMarkdown(detail);
4104
- return text15(`${md}
4302
+ return text(`${md}
4105
4303
 
4106
4304
  \`\`\`json
4107
4305
  ${JSON.stringify(detail)}
4108
4306
  \`\`\``);
4109
4307
  } catch (e) {
4110
- return fail17(errMsg16(e));
4308
+ return fail2(errMsg2(e));
4111
4309
  }
4112
4310
  }
4113
4311
  );
@@ -4116,27 +4314,24 @@ ${JSON.stringify(detail)}
4116
4314
  {
4117
4315
  description: "Reset the js-top task table, freeze history, and busy history on the device (fresh measurement window).",
4118
4316
  inputSchema: {
4119
- deviceId: import_zod17.z.string().optional().describe("Target device id (auto-selected if one is connected).")
4317
+ deviceId: deviceIdInput
4120
4318
  }
4121
4319
  },
4122
4320
  async ({ deviceId }) => {
4123
4321
  try {
4124
4322
  const target = sync.requireProDevice(deviceId);
4125
4323
  await sync.callAction(JS_TOP_TOOL_ID, "clear", {}, target);
4126
- return text15("Cleared js-top stats.");
4324
+ return text("Cleared js-top stats.");
4127
4325
  } catch (e) {
4128
- return fail17(errMsg16(e));
4326
+ return fail2(errMsg2(e));
4129
4327
  }
4130
4328
  }
4131
4329
  );
4132
4330
  }
4133
4331
 
4134
4332
  // src/tools/packs/images.ts
4135
- var import_zod18 = require("zod");
4333
+ var import_zod20 = require("zod");
4136
4334
  var IMAGES_TOOL_ID = "images";
4137
- var text16 = (s) => ({ content: [{ type: "text", text: s }] });
4138
- var fail18 = (message) => ({ isError: true, content: [{ type: "text", text: message }] });
4139
- var errMsg17 = (e) => e instanceof Error ? e.message : String(e);
4140
4335
  function uriTail(uri) {
4141
4336
  if (!uri) return "(no source)";
4142
4337
  const unstable = uri.match(/[?&]unstable_path=([^&?]+)/);
@@ -4156,8 +4351,8 @@ function cacheLabel(r) {
4156
4351
  if (r.progressSeen) return "network";
4157
4352
  return "?";
4158
4353
  }
4159
- function mb(bytes) {
4160
- return `${(bytes / (1024 * 1024)).toFixed(1)}MB`;
4354
+ function mb(bytes2) {
4355
+ return `${(bytes2 / (1024 * 1024)).toFixed(1)}MB`;
4161
4356
  }
4162
4357
  function formatListMarkdown(list, limit) {
4163
4358
  const lines = [];
@@ -4224,9 +4419,9 @@ function registerImagesTools(server, sync) {
4224
4419
  {
4225
4420
  description: "Live image registry from the app: every RN <Image> and expo-image load with cache verdict (memory/disk/network), load ms, decoded vs displayed size with an oversize/upscale verdict and wasted-KB estimate, error details, and any active simulation overrides. Image HTTP never hits the JS network stack, so this is the only visibility into image loading. Requires @buoy-gg/images on the device.",
4226
4421
  inputSchema: {
4227
- deviceId: import_zod18.z.string().optional().describe("Target device id (auto-selected if one is connected)."),
4228
- limit: import_zod18.z.number().optional().describe("Max records to return (newest first). Default 30, max 200."),
4229
- status: import_zod18.z.enum(["pending", "loading", "loaded", "error"]).optional().describe("Only records in this state (e.g. 'error' for the failure log).")
4422
+ deviceId: deviceIdInput,
4423
+ limit: import_zod20.z.number().optional().describe("Max records to return (newest first). Default 30, max 200."),
4424
+ status: import_zod20.z.enum(["pending", "loading", "loaded", "error"]).optional().describe("Only records in this state (e.g. 'error' for the failure log).")
4230
4425
  }
4231
4426
  },
4232
4427
  async ({ deviceId, limit, status }) => {
@@ -4239,7 +4434,7 @@ function registerImagesTools(server, sync) {
4239
4434
  { limit: capped, status },
4240
4435
  target
4241
4436
  );
4242
- if (!list) return fail18("Device returned nothing \u2014 is @buoy-gg/images installed in the app?");
4437
+ if (!list) return fail2("Device returned nothing \u2014 is @buoy-gg/images installed in the app?");
4243
4438
  const md = formatListMarkdown(list, capped);
4244
4439
  const slim = {
4245
4440
  stats: list.stats,
@@ -4266,13 +4461,13 @@ function registerImagesTools(server, sync) {
4266
4461
  layoutShifts: r.layoutShifts
4267
4462
  }))
4268
4463
  };
4269
- return text16(`${md}
4464
+ return text(`${md}
4270
4465
 
4271
4466
  \`\`\`json
4272
4467
  ${JSON.stringify(slim)}
4273
4468
  \`\`\``);
4274
4469
  } catch (e) {
4275
- return fail18(errMsg17(e));
4470
+ return fail2(errMsg2(e));
4276
4471
  }
4277
4472
  }
4278
4473
  );
@@ -4281,9 +4476,9 @@ ${JSON.stringify(slim)}
4281
4476
  {
4282
4477
  description: "Act on one image from get_images (by id): hard_reload (bypass caches + refetch), retry, evict_disk (expo disk entry), flash (red border to locate it on screen), prove_savings (re-encode at displayed size as WebP ON DEVICE and return real byte savings + a previewable file:// uri), or simulation overrides \u2014 force_error (instant offline-safe failure), force_loading (request hangs forever), force_blank, swap_url (requires url), restore (remove override). Returns the record's state after the action so the effect is visible.",
4283
4478
  inputSchema: {
4284
- deviceId: import_zod18.z.string().optional().describe("Target device id (auto-selected if one is connected)."),
4285
- id: import_zod18.z.number().describe("Record id from get_images."),
4286
- action: import_zod18.z.enum([
4479
+ deviceId: deviceIdInput,
4480
+ id: import_zod20.z.number().describe("Record id from get_images."),
4481
+ action: import_zod20.z.enum([
4287
4482
  "hard_reload",
4288
4483
  "retry",
4289
4484
  "evict_disk",
@@ -4295,7 +4490,7 @@ ${JSON.stringify(slim)}
4295
4490
  "swap_url",
4296
4491
  "restore"
4297
4492
  ]).describe("What to do."),
4298
- url: import_zod18.z.string().optional().describe("Replacement URL (swap_url only).")
4493
+ url: import_zod20.z.string().optional().describe("Replacement URL (swap_url only).")
4299
4494
  }
4300
4495
  },
4301
4496
  async ({ deviceId, id, action, url }) => {
@@ -4303,8 +4498,8 @@ ${JSON.stringify(slim)}
4303
4498
  const target = sync.requireProDevice(deviceId);
4304
4499
  if (action === "prove_savings") {
4305
4500
  const savings = await sync.callAction(IMAGES_TOOL_ID, "proveSavings", { id }, target);
4306
- if (!savings) return fail18("Device returned nothing for proveSavings.");
4307
- if (!savings.ok) return fail18(savings.message);
4501
+ if (!savings) return fail2("Device returned nothing for proveSavings.");
4502
+ if (!savings.ok) return fail2(savings.message);
4308
4503
  const parts = [`**prove_savings** on #${id}: ${savings.message}`];
4309
4504
  if (savings.originalBytes != null)
4310
4505
  parts.push(`Original: ${Math.round(savings.originalBytes / 1024)}KB (${savings.originalBytesSource})`);
@@ -4316,11 +4511,11 @@ ${JSON.stringify(slim)}
4316
4511
  parts.push(`**Saved: ${Math.round(savings.savedBytes / 1024)}KB (${savings.savedPct}%)**`);
4317
4512
  if (savings.optimizedUri)
4318
4513
  parts.push(`Preview in place: image_action {id:${id}, action:"swap_url", url:"${savings.optimizedUri}"}`);
4319
- return text16(parts.join("\n"));
4514
+ return text(parts.join("\n"));
4320
4515
  }
4321
4516
  const entry = ACTION_MAP[action];
4322
- if (!entry) return fail18(`Unknown action: ${action}`);
4323
- if (action === "swap_url" && !url) return fail18("swap_url requires the `url` param.");
4517
+ if (!entry) return fail2(`Unknown action: ${action}`);
4518
+ if (action === "swap_url" && !url) return fail2("swap_url requires the `url` param.");
4324
4519
  const result = await sync.callAction(
4325
4520
  IMAGES_TOOL_ID,
4326
4521
  entry.action,
@@ -4340,9 +4535,9 @@ ${JSON.stringify(slim)}
4340
4535
  );
4341
4536
  if (detail.error) lines.push(`Error: ${detail.error}`);
4342
4537
  }
4343
- return text16(lines.join("\n"));
4538
+ return text(lines.join("\n"));
4344
4539
  } catch (e) {
4345
- return fail18(errMsg17(e));
4540
+ return fail2(errMsg2(e));
4346
4541
  }
4347
4542
  }
4348
4543
  );
@@ -4351,12 +4546,12 @@ ${JSON.stringify(slim)}
4351
4546
  {
4352
4547
  description: "App-wide image simulation modes: network 'offline' (every NETWORK image load fails immediately, both libs \u2014 bundled assets still load; reload/remount on-screen images to see it), 'cold' (every image bypasses caches \u2014 first-launch behavior, nothing cleared), 'normal' to reset. blankImages renders every image without a source. massAction applies per-image simulations to everything mounted. Also clears the registry or expo caches when asked.",
4353
4548
  inputSchema: {
4354
- deviceId: import_zod18.z.string().optional().describe("Target device id (auto-selected if one is connected)."),
4355
- network: import_zod18.z.enum(["normal", "offline", "cold"]).optional().describe("Network simulation mode."),
4356
- blankImages: import_zod18.z.boolean().optional().describe("Render all images without a source."),
4357
- massAction: import_zod18.z.enum(["error", "loading", "blank", "reload", "flash", "restore"]).optional().describe("Apply to EVERY mounted image at once: force error/loading/blank overrides, hard-reload all, flash all, or restore all overrides."),
4358
- clearRegistry: import_zod18.z.boolean().optional().describe("Clear the captured image log."),
4359
- clearExpoCaches: import_zod18.z.boolean().optional().describe("Clear expo-image memory + disk caches.")
4549
+ deviceId: deviceIdInput,
4550
+ network: import_zod20.z.enum(["normal", "offline", "cold"]).optional().describe("Network simulation mode."),
4551
+ blankImages: import_zod20.z.boolean().optional().describe("Render all images without a source."),
4552
+ massAction: import_zod20.z.enum(["error", "loading", "blank", "reload", "flash", "restore"]).optional().describe("Apply to EVERY mounted image at once: force error/loading/blank overrides, hard-reload all, flash all, or restore all overrides."),
4553
+ clearRegistry: import_zod20.z.boolean().optional().describe("Clear the captured image log."),
4554
+ clearExpoCaches: import_zod20.z.boolean().optional().describe("Clear expo-image memory + disk caches.")
4360
4555
  }
4361
4556
  },
4362
4557
  async ({ deviceId, network, blankImages, massAction, clearRegistry, clearExpoCaches }) => {
@@ -4383,17 +4578,17 @@ ${JSON.stringify(slim)}
4383
4578
  const r = await sync.callAction(IMAGES_TOOL_ID, "clearExpoCaches", {}, target);
4384
4579
  done.push(r?.message ?? "expo caches cleared");
4385
4580
  }
4386
- if (done.length === 0) return fail18("Pass at least one of: network, blankImages, massAction, clearRegistry, clearExpoCaches.");
4387
- return text16(`Applied: ${done.join(" \xB7 ")}`);
4581
+ if (done.length === 0) return fail2("Pass at least one of: network, blankImages, massAction, clearRegistry, clearExpoCaches.");
4582
+ return text(`Applied: ${done.join(" \xB7 ")}`);
4388
4583
  } catch (e) {
4389
- return fail18(errMsg17(e));
4584
+ return fail2(errMsg2(e));
4390
4585
  }
4391
4586
  }
4392
4587
  );
4393
4588
  }
4394
4589
 
4395
4590
  // src/tools/packs/assets.ts
4396
- var import_zod19 = require("zod");
4591
+ var import_zod21 = require("zod");
4397
4592
 
4398
4593
  // src/tools/packs/assetsGraphNote.ts
4399
4594
  function graphUnavailableNote(ss) {
@@ -4408,14 +4603,11 @@ function graphUnavailableNote(ss) {
4408
4603
 
4409
4604
  // src/tools/packs/assets.ts
4410
4605
  var ASSETS_TOOL_ID = "assets";
4411
- var text17 = (s) => ({ content: [{ type: "text", text: s }] });
4412
- var fail19 = (message) => ({ isError: true, content: [{ type: "text", text: message }] });
4413
- var errMsg18 = (e) => e instanceof Error ? e.message : String(e);
4414
- function kb2(bytes) {
4415
- if (bytes == null || bytes <= 0) return "\u2013";
4416
- if (bytes < 1024) return `${bytes}B`;
4417
- if (bytes < 1024 * 1024) return `${(bytes / 1024).toFixed(1)}KB`;
4418
- return `${(bytes / (1024 * 1024)).toFixed(2)}MB`;
4606
+ function kb2(bytes2) {
4607
+ if (bytes2 == null || bytes2 <= 0) return "\u2013";
4608
+ if (bytes2 < 1024) return `${bytes2}B`;
4609
+ if (bytes2 < 1024 * 1024) return `${(bytes2 / 1024).toFixed(1)}KB`;
4610
+ return `${(bytes2 / (1024 * 1024)).toFixed(2)}MB`;
4419
4611
  }
4420
4612
  function sizeLabel(r) {
4421
4613
  if (r.sizeBytes == null) return "\u2013";
@@ -4498,10 +4690,10 @@ function registerAssetsTools(server, sync) {
4498
4690
  {
4499
4691
  description: "Bundled-asset inventory from the app: every asset (images, fonts, video, audio) with dimensions, @1x/@2x/@3x scale coverage, byte sizes (measured from the Metro server in dev), loaded vs never-loaded state, and audit findings \u2014 duplicates (by content hash), shipped-but-never-loaded, WebP candidates, images decoding larger than the screen, heavy GIFs/fonts, scale gaps \u2014 plus the change vs a saved baseline. This is what the app SHIPS; use get_images for what it RENDERS. Requires @buoy-gg/assets on the device.",
4500
4692
  inputSchema: {
4501
- deviceId: import_zod19.z.string().optional().describe("Target device id (auto-selected if one is connected)."),
4502
- limit: import_zod19.z.number().optional().describe("Max records to return (largest first). Default 40, max 500."),
4503
- kind: import_zod19.z.enum(["image", "font", "video", "audio", "data", "other"]).optional().describe("Only assets of this kind."),
4504
- unusedOnly: import_zod19.z.boolean().optional().describe("Only assets shipped in the bundle but never loaded at runtime.")
4693
+ deviceId: deviceIdInput,
4694
+ limit: import_zod21.z.number().optional().describe("Max records to return (largest first). Default 40, max 500."),
4695
+ kind: import_zod21.z.enum(["image", "font", "video", "audio", "data", "other"]).optional().describe("Only assets of this kind."),
4696
+ unusedOnly: import_zod21.z.boolean().optional().describe("Only assets shipped in the bundle but never loaded at runtime.")
4505
4697
  }
4506
4698
  },
4507
4699
  async ({ deviceId, limit, kind, unusedOnly }) => {
@@ -4514,7 +4706,7 @@ function registerAssetsTools(server, sync) {
4514
4706
  { limit: unusedOnly ? 500 : capped, kind, loadedOnly: false },
4515
4707
  target
4516
4708
  );
4517
- if (!list) return fail19("Device returned nothing \u2014 is @buoy-gg/assets installed in the app?");
4709
+ if (!list) return fail2("Device returned nothing \u2014 is @buoy-gg/assets installed in the app?");
4518
4710
  if (unusedOnly) {
4519
4711
  list.records = list.records.filter((r) => !r.loaded).slice(0, capped);
4520
4712
  }
@@ -4538,13 +4730,13 @@ function registerAssetsTools(server, sync) {
4538
4730
  sizeSource: r.sizeSource
4539
4731
  }))
4540
4732
  };
4541
- return text17(`${md}
4733
+ return text(`${md}
4542
4734
 
4543
4735
  \`\`\`json
4544
4736
  ${JSON.stringify(slim)}
4545
4737
  \`\`\``);
4546
4738
  } catch (e) {
4547
- return fail19(errMsg18(e));
4739
+ return fail2(errMsg2(e));
4548
4740
  }
4549
4741
  }
4550
4742
  );
@@ -4553,9 +4745,9 @@ ${JSON.stringify(slim)}
4553
4745
  {
4554
4746
  description: "Drive the assets tool: rescan (re-walk the registry + merge the Metro bundle graph + refresh fonts + measure sizes \u2014 run this first if the inventory looks stale or empty), measure_sizes (re-measure byte sizes only), detail (full metadata for one asset by id: source dir, per-scale sizes, resolved URI, per-file graph paths), save_baseline (persist the current inventory for before/after comparison \u2014 save, make your change, then get_assets shows added/removed/grown/net bytes), clear_baseline, clear_records (wipe + rescan rebuilds).",
4555
4747
  inputSchema: {
4556
- deviceId: import_zod19.z.string().optional().describe("Target device id (auto-selected if one is connected)."),
4557
- action: import_zod19.z.enum(["rescan", "measure_sizes", "detail", "save_baseline", "clear_baseline", "clear_records"]).describe("What to do."),
4558
- id: import_zod19.z.number().optional().describe("Asset id from get_assets (detail only).")
4748
+ deviceId: deviceIdInput,
4749
+ action: import_zod21.z.enum(["rescan", "measure_sizes", "detail", "save_baseline", "clear_baseline", "clear_records"]).describe("What to do."),
4750
+ id: import_zod21.z.number().optional().describe("Asset id from get_assets (detail only).")
4559
4751
  }
4560
4752
  },
4561
4753
  async ({ deviceId, action, id }) => {
@@ -4565,19 +4757,19 @@ ${JSON.stringify(slim)}
4565
4757
  case "rescan": {
4566
4758
  const result = await sync.callAction(ASSETS_TOOL_ID, "rescan", {}, target);
4567
4759
  const ss = result?.scanStatus;
4568
- if (!ss) return fail19("Device returned nothing for rescan.");
4569
- return text17(
4760
+ if (!ss) return fail2("Device returned nothing for rescan.");
4761
+ return text(
4570
4762
  `Rescan done: ${ss.registryCount} registered at runtime` + (ss.graphCount != null ? ` \xB7 ${ss.graphCount} in the bundle graph` : " \xB7 no bundle graph") + (ss.graphError ? ` (graph: ${ss.graphError})` : "") + ` \xB7 ${ss.fontFamilies.length} font families` + (ss.measuring ? " \xB7 size measurement running \u2014 get_assets in a moment" : "")
4571
4763
  );
4572
4764
  }
4573
4765
  case "measure_sizes": {
4574
4766
  const result = await sync.callAction(ASSETS_TOOL_ID, "measureSizes", {}, target);
4575
- return text17(`Measured ${result?.measured ?? 0} of ${result?.total ?? 0} assets.`);
4767
+ return text(`Measured ${result?.measured ?? 0} of ${result?.total ?? 0} assets.`);
4576
4768
  }
4577
4769
  case "detail": {
4578
- if (id == null) return fail19("detail requires the `id` param (from get_assets).");
4770
+ if (id == null) return fail2("detail requires the `id` param (from get_assets).");
4579
4771
  const detail = await sync.callAction(ASSETS_TOOL_ID, "getDetail", { id }, target);
4580
- if (!detail) return fail19(`Asset ${id} not found \u2014 re-run get_assets for current ids.`);
4772
+ if (!detail) return fail2(`Asset ${id} not found \u2014 re-run get_assets for current ids.`);
4581
4773
  const lines = [
4582
4774
  `## ${detail.name}.${detail.type} (#${detail.id})`,
4583
4775
  "",
@@ -4585,8 +4777,8 @@ ${JSON.stringify(slim)}
4585
4777
  ];
4586
4778
  if (detail.width != null) lines.push(`- Dimensions: ${detail.width}\xD7${detail.height} logical \xB7 scales ${scalesLabel(detail.scales)}`);
4587
4779
  if (detail.sizesByScale) {
4588
- for (const [scale, bytes] of Object.entries(detail.sizesByScale)) {
4589
- lines.push(`- Size @${scale}x: ${kb2(bytes)}`);
4780
+ for (const [scale, bytes2] of Object.entries(detail.sizesByScale)) {
4781
+ lines.push(`- Size @${scale}x: ${kb2(bytes2)}`);
4590
4782
  }
4591
4783
  }
4592
4784
  if (detail.sizeBytes != null) lines.push(`- Total: ${sizeLabel(detail)}`);
@@ -4595,32 +4787,32 @@ ${JSON.stringify(slim)}
4595
4787
  if (detail.fileSystemLocation) lines.push(`- Source dir: ${detail.fileSystemLocation}`);
4596
4788
  if (detail.files?.length) lines.push(`- Files: ${detail.files.join(", ")}`);
4597
4789
  if (detail.uri) lines.push(`- Resolved URI: ${detail.uri}`);
4598
- return text17(lines.join("\n"));
4790
+ return text(lines.join("\n"));
4599
4791
  }
4600
4792
  case "save_baseline": {
4601
4793
  const result = await sync.callAction(ASSETS_TOOL_ID, "saveBaseline", {}, target);
4602
- return text17(
4794
+ return text(
4603
4795
  `Baseline saved (${result?.assets ?? 0} assets). Make your change, then get_assets reports added/removed/grown/net bytes against it.`
4604
4796
  );
4605
4797
  }
4606
4798
  case "clear_baseline": {
4607
4799
  await sync.callAction(ASSETS_TOOL_ID, "clearBaseline", {}, target);
4608
- return text17("Baseline cleared.");
4800
+ return text("Baseline cleared.");
4609
4801
  }
4610
4802
  case "clear_records": {
4611
4803
  const result = await sync.callAction(ASSETS_TOOL_ID, "clearRecords", {}, target);
4612
- return text17(result?.message ?? "Inventory cleared.");
4804
+ return text(result?.message ?? "Inventory cleared.");
4613
4805
  }
4614
4806
  }
4615
4807
  } catch (e) {
4616
- return fail19(errMsg18(e));
4808
+ return fail2(errMsg2(e));
4617
4809
  }
4618
4810
  }
4619
4811
  );
4620
4812
  }
4621
4813
 
4622
4814
  // src/tools/packs/reload.ts
4623
- var import_zod20 = require("zod");
4815
+ var import_zod22 = require("zod");
4624
4816
 
4625
4817
  // src/capture/devServerReload.ts
4626
4818
  var import_ws = require("ws");
@@ -4683,9 +4875,6 @@ var DEFAULT_WAIT_MS = 3e4;
4683
4875
  var TEARDOWN_GRACE_MS = 1200;
4684
4876
  var PROBE_TIMEOUT_MS = 1500;
4685
4877
  var PROBE_INTERVAL_MS = 400;
4686
- var text18 = (s) => ({ content: [{ type: "text", text: s }] });
4687
- var fail20 = (message) => ({ isError: true, content: [{ type: "text", text: message }] });
4688
- var errMsg19 = (e) => e instanceof Error ? e.message : String(e);
4689
4878
  var delay3 = (ms) => new Promise((r) => setTimeout(r, ms));
4690
4879
  async function ping(sync, deviceId, timeoutMs) {
4691
4880
  try {
@@ -4724,12 +4913,12 @@ function registerReloadTools(server, sync) {
4724
4913
  {
4725
4914
  description: "Reload the running app's JS bundle (Expo Go, dev client, or RN CLI) \u2014 the equivalent of shaking the device and hitting Reload, but from here. Use it to pick up a change Fast Refresh didn't apply, to clear leaked in-memory state before a measurement, to re-run app startup, or to RECOVER A CRASHED APP: after a fatal error the other tools stop answering, and reloading is how you get them back (it falls through to the dev server when the app is too broken to reload itself). In dev builds it uses React Native's DevSettings.reload(); otherwise it falls back to expo-updates (if the app installs it). All in-memory state is lost \u2014 read anything you need from get_console/get_storage BEFORE reloading. By default this waits for the app to come back up and reports how long the reload took. Works on any app running @buoy-gg/core \u2014 no particular tool package needed. Requires Buoy Pro.",
4726
4915
  inputSchema: {
4727
- strategy: import_zod20.z.enum(["auto", "dev-settings", "expo-updates"]).optional().describe(
4916
+ strategy: import_zod22.z.enum(["auto", "dev-settings", "expo-updates"]).optional().describe(
4728
4917
  "Reload mechanism. 'auto' (default) uses DevSettings in dev builds and expo-updates otherwise. Force one only when debugging the reload itself."
4729
4918
  ),
4730
- wait: import_zod20.z.boolean().optional().describe("Wait for the app to come back before returning. Default true \u2014 turn off for fire-and-forget."),
4731
- timeoutMs: import_zod20.z.number().optional().describe("How long to wait for the app to reconnect, in ms. Default 30000."),
4732
- deviceId: import_zod20.z.string().optional().describe('Target device id (auto-selected if one is connected). Use "All" to reload every connected app.')
4919
+ wait: import_zod22.z.boolean().optional().describe("Wait for the app to come back before returning. Default true \u2014 turn off for fire-and-forget."),
4920
+ timeoutMs: import_zod22.z.number().optional().describe("How long to wait for the app to reconnect, in ms. Default 30000."),
4921
+ deviceId: import_zod22.z.string().optional().describe('Target device id (auto-selected if one is connected). Use "All" to reload every connected app.')
4733
4922
  }
4734
4923
  },
4735
4924
  async (args) => {
@@ -4771,39 +4960,39 @@ function registerReloadTools(server, sync) {
4771
4960
  }
4772
4961
  const via = recovered ? ` via the dev server (the app was unresponsive \u2014 its own reload could not be reached)` : ack?.strategy ? ` via ${ack.strategy}` : "";
4773
4962
  if (args.wait === false) {
4774
- return text18(`Reload requested${via}. The app is restarting \u2014 its in-memory state is gone; re-read anything you cached.`);
4963
+ return text(`Reload requested${via}. The app is restarting \u2014 its in-memory state is gone; re-read anything you cached.`);
4775
4964
  }
4776
4965
  const elapsed = await waitForAppBack(sync, target, args.timeoutMs ?? DEFAULT_WAIT_MS, previousRealm);
4777
4966
  if (elapsed == null) {
4778
- return fail20(
4967
+ return fail2(
4779
4968
  `Reload was accepted${via}, but the app hasn't reconnected yet. It may still be building the bundle (a cold Metro rebuild can take a while) \u2014 check the device, then run list_devices.`
4780
4969
  );
4781
4970
  }
4782
- return text18(
4971
+ return text(
4783
4972
  `Reloaded the app${via} \u2014 back and connected after ${(elapsed / 1e3).toFixed(1)}s. All in-memory state was reset, so re-read any state you cached before this.`
4784
4973
  );
4785
4974
  } catch (e) {
4786
- return fail20(errMsg19(e));
4975
+ return fail2(errMsg2(e));
4787
4976
  }
4788
4977
  }
4789
4978
  );
4790
4979
  }
4791
4980
 
4792
4981
  // src/tools/packs/triage.ts
4793
- var import_zod21 = require("zod");
4794
- var text19 = (s) => ({ content: [{ type: "text", text: s }] });
4795
- var fail21 = (message) => ({
4982
+ var import_zod23 = require("zod");
4983
+ var text6 = (s) => ({ content: [{ type: "text", text: s }] });
4984
+ var fail8 = (message) => ({
4796
4985
  isError: true,
4797
4986
  content: [{ type: "text", text: message }]
4798
4987
  });
4799
- var errMsg20 = (e) => e instanceof Error ? e.message : String(e);
4988
+ var errMsg7 = (e) => e instanceof Error ? e.message : String(e);
4800
4989
  var PROBE_TIMEOUT_MS2 = 5e3;
4801
4990
  var MAX_STORAGE_KEYS = 20;
4802
4991
  var MAX_STORAGE_VALUE = 400;
4803
4992
  var STORAGE_VALUE_BUDGET = 1600;
4804
4993
  var MAX_LINE = 160;
4805
4994
  var cap = (s, n = MAX_LINE) => s.length > n ? `${s.slice(0, n)}\u2026` : s;
4806
- function hhmmss2(ts) {
4995
+ function hhmmss3(ts) {
4807
4996
  if (!ts) return "--:--:--";
4808
4997
  const d = new Date(ts);
4809
4998
  const p = (n) => String(n).padStart(2, "0");
@@ -4833,7 +5022,7 @@ async function section(title, run2) {
4833
5022
  return body ? `${title}
4834
5023
  ${body}` : null;
4835
5024
  } catch (e) {
4836
- const msg = errMsg20(e);
5025
+ const msg = errMsg7(e);
4837
5026
  if (/not responding|timed out|got no answer/i.test(msg)) {
4838
5027
  return `${title}
4839
5028
  (no answer \u2014 see the note above)`;
@@ -4848,7 +5037,7 @@ function registerTriageTools(server, sync) {
4848
5037
  {
4849
5038
  description: "START HERE when debugging a running app. One call = the app's current runtime state: connected device, recent console errors (including [FATAL] crashes), recent network requests with statuses, persisted storage values, and what's on screen right now. Use it as the first step for ANY 'why is the app doing X' question \u2014 it is far cheaper than calling get_console + get_network_requests + get_storage + describe_screen separately, and it tells you which of those to drill into next. If the app has crashed it says so and shows the crash. Requires Buoy Pro.",
4850
5039
  inputSchema: {
4851
- deviceId: import_zod21.z.string().optional().describe("Target device id (auto-selected if one is connected).")
5040
+ deviceId: import_zod23.z.string().optional().describe("Target device id (auto-selected if one is connected).")
4852
5041
  }
4853
5042
  },
4854
5043
  async (args) => {
@@ -4872,7 +5061,7 @@ function registerTriageTools(server, sync) {
4872
5061
  return `(none \u2014 ${entries.length} entries captured)`;
4873
5062
  }
4874
5063
  return shown.map(
4875
- (e) => `${hhmmss2(e.timestamp)} [${e.level === "error" ? "ERR" : "warn"}] ${cap(
5064
+ (e) => `${hhmmss3(e.timestamp)} [${e.level === "error" ? "ERR" : "warn"}] ${cap(
4876
5065
  (e.message ?? "").split("\n").map((l) => l.trim()).filter(Boolean).slice(0, 3).join(" \u23CE "),
4877
5066
  220
4878
5067
  )}`
@@ -4955,7 +5144,7 @@ _Values: get_storage includeValues:true_`;
4955
5144
 
4956
5145
  \u{1F6D1} ${sync.unresponsiveNote(target)}` : "";
4957
5146
  const body = [consoleSec, netSec, storeSec, screenSec].filter(Boolean).join("\n\n");
4958
- return text19(
5147
+ return text6(
4959
5148
  `${head}${banner}
4960
5149
 
4961
5150
  ${body}
@@ -4963,22 +5152,22 @@ ${body}
4963
5152
  _Drill in with get_console, get_network_requests (includeBodies:true for response bodies), get_storage (includeValues:true), describe_screen._`
4964
5153
  );
4965
5154
  } catch (e) {
4966
- return fail21(errMsg20(e));
5155
+ return fail8(errMsg7(e));
4967
5156
  }
4968
5157
  }
4969
5158
  );
4970
5159
  }
4971
5160
 
4972
5161
  // src/tools/packs/flow.ts
4973
- var import_zod22 = require("zod");
5162
+ var import_zod24 = require("zod");
4974
5163
  var UI_TOOL_ID = "highlight-updates";
4975
5164
  var APP_TOOL_ID2 = "app";
4976
- var text20 = (s) => ({ content: [{ type: "text", text: s }] });
4977
- var fail22 = (message) => ({
5165
+ var text7 = (s) => ({ content: [{ type: "text", text: s }] });
5166
+ var fail9 = (message) => ({
4978
5167
  isError: true,
4979
5168
  content: [{ type: "text", text: message }]
4980
5169
  });
4981
- var errMsg21 = (e) => e instanceof Error ? e.message : String(e);
5170
+ var errMsg8 = (e) => e instanceof Error ? e.message : String(e);
4982
5171
  var delay4 = (ms) => new Promise((r) => setTimeout(r, ms));
4983
5172
  var POLL_MS = 250;
4984
5173
  var DEFAULT_WAIT_MS2 = 5e3;
@@ -4995,19 +5184,19 @@ function formatElement2(e) {
4995
5184
  const display = e.text ?? e.label;
4996
5185
  return `${e.interactive ? "\u2022 " : " "}${e.name}${display ? ` "${display}"` : ""}${e.testID ? ` [testID=${e.testID}]` : ""}`;
4997
5186
  }
4998
- var stepSchema = import_zod22.z.object({
4999
- tap: import_zod22.z.string().optional().describe("Tap this element: exact testID, or fuzzy visible text."),
5000
- longPress: import_zod22.z.boolean().optional().describe("Make `tap` a long-press instead."),
5001
- type: import_zod22.z.string().optional().describe("Text to type (needs `into`)."),
5002
- into: import_zod22.z.string().optional().describe("Target text input for `type`: testID or fuzzy text."),
5003
- set: import_zod22.z.union([import_zod22.z.number(), import_zod22.z.boolean()]).optional().describe("Set a toggle (true/false) or slider (number) \u2014 pair with `tap` as the target."),
5004
- reload: import_zod22.z.boolean().optional().describe("Reload the JS bundle before this step's action."),
5005
- waitMs: import_zod22.z.number().min(0).max(3e4).optional().describe("Fixed pause after the action, in ms (max 30000)."),
5006
- waitFor: import_zod22.z.string().optional().describe("Wait until an element matching this appears (default 5s)."),
5007
- waitGone: import_zod22.z.string().optional().describe("Wait until no element matches this (default 5s)."),
5008
- expect: import_zod22.z.string().optional().describe("Assert an element matching this IS on screen (waits up to 2s for it); fails the flow if not."),
5009
- expectGone: import_zod22.z.string().optional().describe("Assert nothing matches this (waits up to 2s for it to go); fails the flow if something does."),
5010
- timeoutMs: import_zod22.z.number().min(0).max(6e4).optional().describe("Budget for this step's waitFor/waitGone/expect, max 60000. Default 5000 (2000 for expect).")
5187
+ var stepSchema = import_zod24.z.object({
5188
+ tap: import_zod24.z.string().optional().describe("Tap this element: exact testID, or fuzzy visible text."),
5189
+ longPress: import_zod24.z.boolean().optional().describe("Make `tap` a long-press instead."),
5190
+ type: import_zod24.z.string().optional().describe("Text to type (needs `into`)."),
5191
+ into: import_zod24.z.string().optional().describe("Target text input for `type`: testID or fuzzy text."),
5192
+ set: import_zod24.z.union([import_zod24.z.number(), import_zod24.z.boolean()]).optional().describe("Set a toggle (true/false) or slider (number) \u2014 pair with `tap` as the target."),
5193
+ reload: import_zod24.z.boolean().optional().describe("Reload the JS bundle before this step's action."),
5194
+ waitMs: import_zod24.z.number().min(0).max(3e4).optional().describe("Fixed pause after the action, in ms (max 30000)."),
5195
+ waitFor: import_zod24.z.string().optional().describe("Wait until an element matching this appears (default 5s)."),
5196
+ waitGone: import_zod24.z.string().optional().describe("Wait until no element matches this (default 5s)."),
5197
+ expect: import_zod24.z.string().optional().describe("Assert an element matching this IS on screen (waits up to 2s for it); fails the flow if not."),
5198
+ expectGone: import_zod24.z.string().optional().describe("Assert nothing matches this (waits up to 2s for it to go); fails the flow if something does."),
5199
+ timeoutMs: import_zod24.z.number().min(0).max(6e4).optional().describe("Budget for this step's waitFor/waitGone/expect, max 60000. Default 5000 (2000 for expect).")
5011
5200
  }).describe("One flow step. Order within a step: reload \u2192 tap/type/set \u2192 waitMs \u2192 waitFor/waitGone \u2192 expect.");
5012
5201
  function registerFlowTools(server, sync) {
5013
5202
  server.registerTool(
@@ -5015,10 +5204,10 @@ function registerFlowTools(server, sync) {
5015
5204
  {
5016
5205
  description: "Run a scripted sequence of UI interactions in ONE call and report what happened: per-step results, the final screen, plus the network requests and console errors the flow produced. Use this instead of alternating describe_screen/tap_element \u2014 reproducing a bug ('open cart, checkout, submit') or verifying a fix after an edit is one call, not a dozen. Steps run in order and stop at the first failure, returning the screen at that point so you can see WHERE it broke. Each step can tap, type, set a toggle/slider, reload, wait for an element, and assert. Requires Buoy Pro.",
5017
5206
  inputSchema: {
5018
- steps: import_zod22.z.array(stepSchema).min(1).describe("Steps to run, in order."),
5019
- include: import_zod22.z.enum(["all", "screen", "none"]).optional().describe("What to report after the flow: 'all' (default) = final screen + network/console produced by the flow; 'screen' = final screen only; 'none' = step results only."),
5020
- continueOnFailure: import_zod22.z.boolean().optional().describe("Keep going after a failed step instead of stopping. Default false."),
5021
- deviceId: import_zod22.z.string().optional().describe("Target device id (auto-selected if one is connected).")
5207
+ steps: import_zod24.z.array(stepSchema).min(1).describe("Steps to run, in order."),
5208
+ include: import_zod24.z.enum(["all", "screen", "none"]).optional().describe("What to report after the flow: 'all' (default) = final screen + network/console produced by the flow; 'screen' = final screen only; 'none' = step results only."),
5209
+ continueOnFailure: import_zod24.z.boolean().optional().describe("Keep going after a failed step instead of stopping. Default false."),
5210
+ deviceId: deviceIdInput
5022
5211
  }
5023
5212
  },
5024
5213
  async (args) => {
@@ -5190,7 +5379,7 @@ function registerFlowTools(server, sync) {
5190
5379
  else stepErrors.push(`expected "${step.expectGone}" to be gone \u2014 still on screen`);
5191
5380
  }
5192
5381
  } catch (e) {
5193
- stepErrors.push(errMsg21(e));
5382
+ stepErrors.push(errMsg8(e));
5194
5383
  }
5195
5384
  if (stepErrors.length) {
5196
5385
  lines.push(`${label} \u2717 ${describeStep(step)} \u2014 ${stepErrors.join("; ")}`);
@@ -5213,7 +5402,7 @@ function registerFlowTools(server, sync) {
5213
5402
  ${shown.map(formatElement2).join("\n")}`
5214
5403
  );
5215
5404
  } catch (e) {
5216
- parts.push(`**Screen now:** unavailable \u2014 ${cap2(errMsg21(e))}`);
5405
+ parts.push(`**Screen now:** unavailable \u2014 ${cap2(errMsg8(e))}`);
5217
5406
  }
5218
5407
  }
5219
5408
  if (include === "all") {
@@ -5242,9 +5431,9 @@ ${shown.map(formatElement2).join("\n")}`
5242
5431
  }
5243
5432
  }
5244
5433
  }
5245
- return text20(parts.join("\n\n"));
5434
+ return text7(parts.join("\n\n"));
5246
5435
  } catch (e) {
5247
- return fail22(errMsg21(e));
5436
+ return fail9(errMsg8(e));
5248
5437
  }
5249
5438
  }
5250
5439
  );
@@ -5265,25 +5454,25 @@ var cap2 = (s, n = 160) => s.length > n ? `${s.slice(0, n)}\u2026` : s;
5265
5454
  var firstLines = (s, n = 3) => s.split("\n").map((l) => l.trim()).filter(Boolean).slice(0, n).join(" \u23CE ");
5266
5455
 
5267
5456
  // src/tools/packs/renders.ts
5268
- var import_zod23 = require("zod");
5457
+ var import_zod25 = require("zod");
5269
5458
  var UI_TOOL_ID2 = "highlight-updates";
5270
- var text21 = (s) => ({ content: [{ type: "text", text: s }] });
5271
- var fail23 = (message) => ({
5459
+ var text8 = (s) => ({ content: [{ type: "text", text: s }] });
5460
+ var fail10 = (message) => ({
5272
5461
  isError: true,
5273
5462
  content: [{ type: "text", text: message }]
5274
5463
  });
5275
- var errMsg22 = (e) => e instanceof Error ? e.message : String(e);
5464
+ var errMsg9 = (e) => e instanceof Error ? e.message : String(e);
5276
5465
  var delay5 = (ms) => new Promise((r) => setTimeout(r, ms));
5277
5466
  var DEFAULT_IDLE_MS = 3e3;
5278
5467
  var SETTLE_MS = 400;
5279
5468
  var DEFAULT_TOP = 12;
5280
5469
  var lastCapture = /* @__PURE__ */ new Map();
5281
- var stepSchema2 = import_zod23.z.object({
5282
- tap: import_zod23.z.string().optional().describe("Tap this element: exact testID, or fuzzy visible text."),
5283
- type: import_zod23.z.string().optional().describe("Text to type (needs `into`)."),
5284
- into: import_zod23.z.string().optional().describe("Target text input for `type`: testID or fuzzy text."),
5285
- waitMs: import_zod23.z.number().min(0).max(1e4).optional().describe("Pause after the action, in ms."),
5286
- repeat: import_zod23.z.number().min(1).max(20).optional().describe("Do this step N times (default 1). Useful for 'tap it five times'.")
5470
+ var stepSchema2 = import_zod25.z.object({
5471
+ tap: import_zod25.z.string().optional().describe("Tap this element: exact testID, or fuzzy visible text."),
5472
+ type: import_zod25.z.string().optional().describe("Text to type (needs `into`)."),
5473
+ into: import_zod25.z.string().optional().describe("Target text input for `type`: testID or fuzzy text."),
5474
+ waitMs: import_zod25.z.number().min(0).max(1e4).optional().describe("Pause after the action, in ms."),
5475
+ repeat: import_zod25.z.number().min(1).max(20).optional().describe("Do this step N times (default 1). Useful for 'tap it five times'.")
5287
5476
  }).describe("One interaction to measure. Keep it to the thing you're asking about.");
5288
5477
  function selector2(needle) {
5289
5478
  return /\s/.test(needle) ? { query: needle } : { testID: needle, query: needle };
@@ -5376,14 +5565,14 @@ function registerRenderTools(server, sync) {
5376
5565
  {
5377
5566
  description: "Find out WHAT re-rendered on the current screen and WHY, in one call \u2014 no test route, no scaffolding. THE tool for 'this page is slow/laggy/janky', 'typing lags', 'the whole page re-renders when I tap one button'. Give it `steps` to measure a specific interaction (tap a button, type in a field) \u2014 measuring an idle window instead tells you nothing about what taps cost, which is how re-render fixes usually go wrong. Returns per-component render COST in ms (sorted by it \u2014 a component rendering 40x for 0.4ms is noise, one rendering 4x for 38ms is the answer), the CAUSE of each (parent cascade / own state / changed props), which parent dragged it along, and which props changed. `wasted` counts renders that produced nothing new \u2014 that's the removable part. AFTER YOU EDIT, call it again with the same steps and compareToPrevious:true to prove the fix landed and didn't move the cost somewhere else. (For choosing between several implementations of a screen, use run_benchmark_batch instead \u2014 that ranks variants; this explains one screen.) Requires Buoy Pro and @buoy-gg/highlight-updates.",
5378
5567
  inputSchema: {
5379
- steps: import_zod23.z.array(stepSchema2).max(20).optional().describe(
5568
+ steps: import_zod25.z.array(stepSchema2).max(20).optional().describe(
5380
5569
  "The interaction to measure, e.g. [{tap:'favorite-deal-2', repeat:5}] or [{type:'bench', into:'deals-search'}]. Omit to sample the screen idle \u2014 only useful for churn that happens with nobody touching it."
5381
5570
  ),
5382
- idleMs: import_zod23.z.number().min(500).max(2e4).optional().describe(`Idle sample length when no steps are given. Default ${DEFAULT_IDLE_MS}.`),
5383
- label: import_zod23.z.string().optional().describe('Name this measurement, e.g. "before fix". Shown in the next comparison.'),
5384
- compareToPrevious: import_zod23.z.boolean().optional().describe("Diff against the previous measure_renders on this device. Use after an edit to prove the fix."),
5385
- top: import_zod23.z.number().min(1).max(40).optional().describe(`Components to list. Default ${DEFAULT_TOP}.`),
5386
- deviceId: import_zod23.z.string().optional().describe("Target device id (auto-selected if one is connected).")
5571
+ idleMs: import_zod25.z.number().min(500).max(2e4).optional().describe(`Idle sample length when no steps are given. Default ${DEFAULT_IDLE_MS}.`),
5572
+ label: import_zod25.z.string().optional().describe('Name this measurement, e.g. "before fix". Shown in the next comparison.'),
5573
+ compareToPrevious: import_zod25.z.boolean().optional().describe("Diff against the previous measure_renders on this device. Use after an edit to prove the fix."),
5574
+ top: import_zod25.z.number().min(1).max(40).optional().describe(`Components to list. Default ${DEFAULT_TOP}.`),
5575
+ deviceId: deviceIdInput
5387
5576
  }
5388
5577
  },
5389
5578
  async (args) => {
@@ -5392,18 +5581,18 @@ function registerRenderTools(server, sync) {
5392
5581
  const device = sync.connectedDevices().find((d) => d.deviceId === target);
5393
5582
  const uiTool = (device?.tools ?? []).find((t) => t.toolId === UI_TOOL_ID2);
5394
5583
  if (!uiTool) {
5395
- return fail23(
5584
+ return fail10(
5396
5585
  "This app doesn't expose the highlight-updates tool, so renders can't be measured. Install @buoy-gg/highlight-updates."
5397
5586
  );
5398
5587
  }
5399
5588
  if (!(uiTool.actions ?? []).includes("beginMeasurement")) {
5400
- return fail23(
5589
+ return fail10(
5401
5590
  "This app's @buoy-gg/highlight-updates is too old to measure renders (needs the version with beginMeasurement). Upgrade it, then try again. Meanwhile run_benchmark_batch with captureRenders can still give counts if you have a test route."
5402
5591
  );
5403
5592
  }
5404
5593
  const began = await sync.callAction(UI_TOOL_ID2, "beginMeasurement", {}, target);
5405
5594
  if (began && began.ok === false) {
5406
- return fail23(`Can't measure renders: ${began.reason ?? "capture unavailable"}`);
5595
+ return fail10(`Can't measure renders: ${began.reason ?? "capture unavailable"}`);
5407
5596
  }
5408
5597
  await delay5(SETTLE_MS);
5409
5598
  const startedAt = Date.now();
@@ -5448,7 +5637,7 @@ function registerRenderTools(server, sync) {
5448
5637
  const ended = await sync.callAction(UI_TOOL_ID2, "endMeasurement", {}, target);
5449
5638
  const summary = ended?.summary;
5450
5639
  if (!summary) {
5451
- return fail23(
5640
+ return fail10(
5452
5641
  "The capture window closed without returning anything. If the app reloaded mid-measurement, try again."
5453
5642
  );
5454
5643
  }
@@ -5473,9 +5662,9 @@ function registerRenderTools(server, sync) {
5473
5662
  } else if (!args.compareToPrevious) {
5474
5663
  parts.push("", "_Fixed something? Call this again with the same steps and `compareToPrevious:true` to prove it landed._");
5475
5664
  }
5476
- return text21(parts.join("\n"));
5665
+ return text8(parts.join("\n"));
5477
5666
  } catch (e) {
5478
- return fail23(errMsg22(e));
5667
+ return fail10(errMsg9(e));
5479
5668
  }
5480
5669
  }
5481
5670
  );
@@ -5759,6 +5948,7 @@ function createMcpServer(sync) {
5759
5948
  registerStorageTools(server, sync);
5760
5949
  registerRoutesTools(server, sync);
5761
5950
  registerConsoleTools(server, sync);
5951
+ registerSentryTools(server, sync);
5762
5952
  registerTimeMachineTools(server, sync);
5763
5953
  registerScenariosTools(server, sync);
5764
5954
  registerScreenshotTools(server, sync);
@@ -5771,7 +5961,7 @@ function createMcpServer(sync) {
5771
5961
  return server;
5772
5962
  }
5773
5963
  var deviceIdField = {
5774
- deviceId: import_zod24.z.string().optional().describe(
5964
+ deviceId: import_zod26.z.string().optional().describe(
5775
5965
  'Target device id (from list_devices). Omit to auto-select when exactly one device is connected. Use "All" to broadcast.'
5776
5966
  )
5777
5967
  };