@buoy-gg/mcp 5.0.0 → 6.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/{chunk-SQKZOREF.mjs → chunk-3PEIMUN7.mjs} +75 -28
- package/dist/cli.js +75 -28
- package/dist/cli.mjs +1 -1
- package/dist/index.d.mts +24 -4
- package/dist/index.d.ts +24 -4
- package/dist/index.js +75 -28
- package/dist/index.mjs +1 -1
- package/package.json +3 -2
|
@@ -296,7 +296,7 @@ async function waitForBroker(url, attempts = 20) {
|
|
|
296
296
|
var delay = (ms) => new Promise((r) => setTimeout(r, ms));
|
|
297
297
|
|
|
298
298
|
// src/version.ts
|
|
299
|
-
var VERSION = "
|
|
299
|
+
var VERSION = "6.0.0" ? "6.0.0" : "0.0.0-dev";
|
|
300
300
|
var PACKAGE_NAME = "@buoy-gg/mcp";
|
|
301
301
|
|
|
302
302
|
// src/update/checkForUpdate.ts
|
|
@@ -528,7 +528,8 @@ function buildAutomationConfig(input, base = {}) {
|
|
|
528
528
|
coolDownMs,
|
|
529
529
|
discardWarmupRuns,
|
|
530
530
|
shuffleCases,
|
|
531
|
-
reloadBetweenCases: input.reloadBetweenCases ?? base.reloadBetweenCases ?? false
|
|
531
|
+
reloadBetweenCases: input.reloadBetweenCases ?? base.reloadBetweenCases ?? false,
|
|
532
|
+
captureRenders: input.captureRenders ?? base.captureRenders ?? true
|
|
532
533
|
};
|
|
533
534
|
return { config, warnings };
|
|
534
535
|
}
|
|
@@ -542,7 +543,8 @@ function describeBenchmarkSettings(config) {
|
|
|
542
543
|
`**${secs(config.perCaseDurationMs)} measured per page**`,
|
|
543
544
|
runs > 1 ? `**${runs} runs each** (median taken)` : `**1 run each** (no median \u2014 noisier)`,
|
|
544
545
|
config.reloadBetweenCases ? "**full reload between pages** (no state leaks across tests)" : "no reload between pages",
|
|
545
|
-
config.shuffleCases ? "shuffled order (cancels thermal bias)" : "fixed order"
|
|
546
|
+
config.shuffleCases ? "shuffled order (cancels thermal bias)" : "fixed order",
|
|
547
|
+
config.captureRenders !== false ? "render capture on (per-component why-data)" : "render capture off"
|
|
546
548
|
];
|
|
547
549
|
const perPageMs = config.settleMs + config.perCaseDurationMs + (config.coolDownMs ?? 0) + 1800;
|
|
548
550
|
return `${parts.join(", ")}. That's ~${secs(perPageMs)} wall-clock per page; for ${config.cases.length} page(s) \xD7 ${runs} run(s), **${est}** total.`;
|
|
@@ -597,7 +599,9 @@ function rankBatchFromIndex(index, batchId, casesOrder, failures = []) {
|
|
|
597
599
|
uiJank: num(median?.uiJankFrames),
|
|
598
600
|
deviceMaxRefreshRate: num(median?.deviceMaxRefreshRate),
|
|
599
601
|
deltaUiFps: null,
|
|
600
|
-
deltaJsFps: null
|
|
602
|
+
deltaJsFps: null,
|
|
603
|
+
renderCommits: num(median?.renderCommits),
|
|
604
|
+
topRenderers: median?.topRenderers ?? []
|
|
601
605
|
};
|
|
602
606
|
return { ...base, flags: failed ? [] : computeFlags(base) };
|
|
603
607
|
});
|
|
@@ -686,6 +690,16 @@ function formatRankingMarkdown(ranking) {
|
|
|
686
690
|
`**\u26A0\uFE0F At-risk:** ${ranking.flagged.map((x) => `${x.caseName} (${x.flags.join("; ")})`).join(" \xB7 ")}`
|
|
687
691
|
);
|
|
688
692
|
}
|
|
693
|
+
const renderLines = ranking.rows.filter((r) => !r.failed && (r.renderCommits != null || r.topRenderers.length > 0)).map((r) => {
|
|
694
|
+
const top = r.topRenderers.map((t) => `${t.name} \xD7${t.renders} (${t.totalMs}ms)`).join(", ");
|
|
695
|
+
const commits = r.renderCommits != null ? `${r.renderCommits} commits` : "";
|
|
696
|
+
return `- **${r.caseName}** \u2014 ${[commits, top].filter(Boolean).join(": ")}`;
|
|
697
|
+
});
|
|
698
|
+
if (renderLines.length) {
|
|
699
|
+
lines.push("");
|
|
700
|
+
lines.push("**Renders (median run)** \u2014 heaviest components by render self-time:");
|
|
701
|
+
lines.push(...renderLines);
|
|
702
|
+
}
|
|
689
703
|
lines.push("");
|
|
690
704
|
lines.push(
|
|
691
705
|
`_Deltas vs baseline (case #0). Don't pick on one metric \u2014 explain the trade-off (e.g. "A is cheapest but drops UI frames \u2192 jank risk; B costs more CPU but caps nothing")._`
|
|
@@ -744,7 +758,8 @@ async function readDeviceSettings(sync, deviceId) {
|
|
|
744
758
|
coolDownMs,
|
|
745
759
|
discardWarmupRuns,
|
|
746
760
|
shuffleCases,
|
|
747
|
-
reloadBetweenCases
|
|
761
|
+
reloadBetweenCases,
|
|
762
|
+
captureRenders
|
|
748
763
|
} = cfg;
|
|
749
764
|
return {
|
|
750
765
|
perCaseDurationMs,
|
|
@@ -754,7 +769,8 @@ async function readDeviceSettings(sync, deviceId) {
|
|
|
754
769
|
coolDownMs,
|
|
755
770
|
discardWarmupRuns,
|
|
756
771
|
shuffleCases,
|
|
757
|
-
reloadBetweenCases
|
|
772
|
+
reloadBetweenCases,
|
|
773
|
+
captureRenders
|
|
758
774
|
};
|
|
759
775
|
} catch {
|
|
760
776
|
return {};
|
|
@@ -858,6 +874,9 @@ function registerPerfMonitorTools(server, sync) {
|
|
|
858
874
|
discardWarmupRuns: z2.number().optional().describe("Discard first N runs of each case before taking the median. Default 0."),
|
|
859
875
|
shuffleCases: z2.boolean().optional().describe("Interleave + shuffle case order to cancel thermal bias. Default on for >3 cases."),
|
|
860
876
|
reloadBetweenCases: z2.boolean().optional().describe("Fully reload JS between cases (needs <AutomationResumer/> mounted). Default false."),
|
|
877
|
+
captureRenders: z2.boolean().optional().describe(
|
|
878
|
+
"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."
|
|
879
|
+
),
|
|
861
880
|
deviceId: z2.string().optional().describe("Target device id. Omit to auto-select when one device is connected."),
|
|
862
881
|
maxWaitMs: z2.number().optional().describe("Safety cap for how long to wait for completion. Defaults to ~3\xD7 the estimate + 60s.")
|
|
863
882
|
}
|
|
@@ -1121,6 +1140,7 @@ ${JSON.stringify(settings, null, 2)}
|
|
|
1121
1140
|
discardWarmupRuns: z2.number().optional().describe("Discard first N runs of each page before the median."),
|
|
1122
1141
|
shuffleCases: z2.boolean().optional().describe("Interleave + shuffle case order to cancel thermal bias."),
|
|
1123
1142
|
reloadBetweenCases: z2.boolean().optional().describe("Fully reload JS between pages (needs <AutomationResumer/> mounted)."),
|
|
1143
|
+
captureRenders: z2.boolean().optional().describe("Capture per-component render counts/durations during each run."),
|
|
1124
1144
|
deviceId: z2.string().optional().describe("Target device id (auto-selected if one is connected).")
|
|
1125
1145
|
}
|
|
1126
1146
|
},
|
|
@@ -1136,7 +1156,8 @@ ${JSON.stringify(settings, null, 2)}
|
|
|
1136
1156
|
"runsPerCase",
|
|
1137
1157
|
"discardWarmupRuns",
|
|
1138
1158
|
"shuffleCases",
|
|
1139
|
-
"reloadBetweenCases"
|
|
1159
|
+
"reloadBetweenCases",
|
|
1160
|
+
"captureRenders"
|
|
1140
1161
|
];
|
|
1141
1162
|
const overrides = {};
|
|
1142
1163
|
for (const k of keys) {
|
|
@@ -1566,42 +1587,68 @@ function registerStorageTools(server, sync) {
|
|
|
1566
1587
|
server.registerTool(
|
|
1567
1588
|
"get_storage",
|
|
1568
1589
|
{
|
|
1569
|
-
description: "Read the app's AsyncStorage
|
|
1590
|
+
description: "Read the app's storage keys across ALL backends: AsyncStorage, MMKV (per instance), and Expo SecureStore. 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. Use for 'what's in storage / read this key'.",
|
|
1570
1591
|
inputSchema: {
|
|
1571
1592
|
pattern: z8.string().optional().describe("Only keys containing this substring (case-insensitive)."),
|
|
1572
1593
|
includeValues: z8.boolean().optional().describe("Include each key's value (truncated). Default false (keys only)."),
|
|
1573
|
-
limit: z8.number().optional().describe("Max keys
|
|
1594
|
+
limit: z8.number().optional().describe("Max keys per backend section. Default 100."),
|
|
1574
1595
|
deviceId: z8.string().optional().describe("Target device id (auto-selected if one is connected).")
|
|
1575
1596
|
}
|
|
1576
1597
|
},
|
|
1577
1598
|
async (args) => {
|
|
1578
1599
|
try {
|
|
1579
1600
|
const target = sync.requireProDevice(args.deviceId);
|
|
1601
|
+
const pattern = args.pattern?.toLowerCase();
|
|
1602
|
+
const limit = typeof args.limit === "number" && args.limit > 0 ? args.limit : 100;
|
|
1603
|
+
const matches = (k) => !pattern || k.toLowerCase().includes(pattern);
|
|
1604
|
+
const sections = [];
|
|
1580
1605
|
const allKeys = await sync.callAction(STORAGE_TOOL_ID, "async.getAllKeys", {}, target);
|
|
1581
1606
|
if (!Array.isArray(allKeys)) {
|
|
1582
1607
|
return fail8("No storage result from the device. The storage tool may be inactive.");
|
|
1583
1608
|
}
|
|
1584
|
-
const
|
|
1585
|
-
|
|
1586
|
-
|
|
1587
|
-
|
|
1588
|
-
|
|
1589
|
-
|
|
1609
|
+
const asyncKeys = allKeys.filter(matches).slice(0, limit);
|
|
1610
|
+
if (asyncKeys.length > 0) {
|
|
1611
|
+
let lines;
|
|
1612
|
+
if (args.includeValues) {
|
|
1613
|
+
const pairs = await sync.callAction(STORAGE_TOOL_ID, "async.multiGet", { keys: asyncKeys }, target);
|
|
1614
|
+
const map = new Map((pairs ?? []).map(([k, v]) => [k, v]));
|
|
1615
|
+
lines = asyncKeys.map((k) => `- \`${k}\` = ${truncate(map.get(k) ?? null)}`);
|
|
1616
|
+
} else {
|
|
1617
|
+
lines = asyncKeys.map((k) => `- \`${k}\``);
|
|
1618
|
+
}
|
|
1619
|
+
sections.push(`**AsyncStorage (${asyncKeys.length}/${allKeys.length} keys):**
|
|
1620
|
+
${lines.join("\n")}`);
|
|
1590
1621
|
}
|
|
1591
|
-
|
|
1592
|
-
|
|
1593
|
-
const
|
|
1594
|
-
|
|
1595
|
-
lines =
|
|
1596
|
-
|
|
1597
|
-
|
|
1622
|
+
const mmkv = await sync.callAction(STORAGE_TOOL_ID, "mmkv.snapshot", {}, target).catch(() => null);
|
|
1623
|
+
for (const inst of mmkv ?? []) {
|
|
1624
|
+
const entries = inst.entries.filter((e) => matches(e.key)).slice(0, limit);
|
|
1625
|
+
if (entries.length === 0) continue;
|
|
1626
|
+
const lines = entries.map(
|
|
1627
|
+
(e) => args.includeValues ? `- \`${e.key}\` (${e.valueType}) = ${truncate(e.value)}` : `- \`${e.key}\``
|
|
1628
|
+
);
|
|
1629
|
+
const flags = [inst.encrypted && "encrypted", inst.readOnly && "read-only"].filter(Boolean).join(", ");
|
|
1630
|
+
sections.push(`**MMKV \`${inst.id}\`${flags ? ` (${flags})` : ""} (${entries.length}/${inst.entries.length} keys):**
|
|
1631
|
+
${lines.join("\n")}`);
|
|
1598
1632
|
}
|
|
1599
|
-
const
|
|
1600
|
-
const
|
|
1601
|
-
|
|
1602
|
-
|
|
1603
|
-
|
|
1604
|
-
|
|
1633
|
+
const secure = await sync.callAction(STORAGE_TOOL_ID, "secure.keys", {}, target).catch(() => null);
|
|
1634
|
+
const secureKeys = (secure ?? []).filter((d) => matches(d.key)).slice(0, limit);
|
|
1635
|
+
if (secureKeys.length > 0) {
|
|
1636
|
+
const lines = await Promise.all(
|
|
1637
|
+
secureKeys.map(async (d) => {
|
|
1638
|
+
if (d.requireAuthentication) return `- \`${d.key}\` \u{1F512} (biometric-protected \u2014 not read)`;
|
|
1639
|
+
if (!args.includeValues) return `- \`${d.key}\``;
|
|
1640
|
+
const v = await sync.callAction(STORAGE_TOOL_ID, "secure.get", { key: d.key }, target).catch(() => null);
|
|
1641
|
+
return `- \`${d.key}\` = ${truncate(v)}`;
|
|
1642
|
+
})
|
|
1643
|
+
);
|
|
1644
|
+
sections.push(`**SecureStore (${secureKeys.length} registered keys):**
|
|
1645
|
+
${lines.join("\n")}`);
|
|
1646
|
+
}
|
|
1647
|
+
if (sections.length === 0) {
|
|
1648
|
+
return text7(`No storage keys${pattern ? ` matching "${args.pattern}"` : ""} in any backend.`);
|
|
1649
|
+
}
|
|
1650
|
+
const note = args.includeValues ? "" : "\n\n_Keys only. Pass includeValues:true for values, or a pattern to filter. Modify AsyncStorage with storage_action._";
|
|
1651
|
+
return text7(`${sections.join("\n\n")}${note}`);
|
|
1605
1652
|
} catch (e) {
|
|
1606
1653
|
return fail8(errMsg8(e));
|
|
1607
1654
|
}
|
package/dist/cli.js
CHANGED
|
@@ -336,7 +336,7 @@ var import_node_os = __toESM(require("os"));
|
|
|
336
336
|
var import_node_path = __toESM(require("path"));
|
|
337
337
|
|
|
338
338
|
// src/version.ts
|
|
339
|
-
var VERSION = "
|
|
339
|
+
var VERSION = "6.0.0" ? "6.0.0" : "0.0.0-dev";
|
|
340
340
|
var PACKAGE_NAME = "@buoy-gg/mcp";
|
|
341
341
|
|
|
342
342
|
// src/update/checkForUpdate.ts
|
|
@@ -570,7 +570,8 @@ function buildAutomationConfig(input, base = {}) {
|
|
|
570
570
|
coolDownMs,
|
|
571
571
|
discardWarmupRuns,
|
|
572
572
|
shuffleCases,
|
|
573
|
-
reloadBetweenCases: input.reloadBetweenCases ?? base.reloadBetweenCases ?? false
|
|
573
|
+
reloadBetweenCases: input.reloadBetweenCases ?? base.reloadBetweenCases ?? false,
|
|
574
|
+
captureRenders: input.captureRenders ?? base.captureRenders ?? true
|
|
574
575
|
};
|
|
575
576
|
return { config, warnings };
|
|
576
577
|
}
|
|
@@ -584,7 +585,8 @@ function describeBenchmarkSettings(config) {
|
|
|
584
585
|
`**${secs(config.perCaseDurationMs)} measured per page**`,
|
|
585
586
|
runs > 1 ? `**${runs} runs each** (median taken)` : `**1 run each** (no median \u2014 noisier)`,
|
|
586
587
|
config.reloadBetweenCases ? "**full reload between pages** (no state leaks across tests)" : "no reload between pages",
|
|
587
|
-
config.shuffleCases ? "shuffled order (cancels thermal bias)" : "fixed order"
|
|
588
|
+
config.shuffleCases ? "shuffled order (cancels thermal bias)" : "fixed order",
|
|
589
|
+
config.captureRenders !== false ? "render capture on (per-component why-data)" : "render capture off"
|
|
588
590
|
];
|
|
589
591
|
const perPageMs = config.settleMs + config.perCaseDurationMs + (config.coolDownMs ?? 0) + 1800;
|
|
590
592
|
return `${parts.join(", ")}. That's ~${secs(perPageMs)} wall-clock per page; for ${config.cases.length} page(s) \xD7 ${runs} run(s), **${est}** total.`;
|
|
@@ -639,7 +641,9 @@ function rankBatchFromIndex(index, batchId, casesOrder, failures = []) {
|
|
|
639
641
|
uiJank: num(median?.uiJankFrames),
|
|
640
642
|
deviceMaxRefreshRate: num(median?.deviceMaxRefreshRate),
|
|
641
643
|
deltaUiFps: null,
|
|
642
|
-
deltaJsFps: null
|
|
644
|
+
deltaJsFps: null,
|
|
645
|
+
renderCommits: num(median?.renderCommits),
|
|
646
|
+
topRenderers: median?.topRenderers ?? []
|
|
643
647
|
};
|
|
644
648
|
return { ...base, flags: failed ? [] : computeFlags(base) };
|
|
645
649
|
});
|
|
@@ -728,6 +732,16 @@ function formatRankingMarkdown(ranking) {
|
|
|
728
732
|
`**\u26A0\uFE0F At-risk:** ${ranking.flagged.map((x) => `${x.caseName} (${x.flags.join("; ")})`).join(" \xB7 ")}`
|
|
729
733
|
);
|
|
730
734
|
}
|
|
735
|
+
const renderLines = ranking.rows.filter((r) => !r.failed && (r.renderCommits != null || r.topRenderers.length > 0)).map((r) => {
|
|
736
|
+
const top = r.topRenderers.map((t) => `${t.name} \xD7${t.renders} (${t.totalMs}ms)`).join(", ");
|
|
737
|
+
const commits = r.renderCommits != null ? `${r.renderCommits} commits` : "";
|
|
738
|
+
return `- **${r.caseName}** \u2014 ${[commits, top].filter(Boolean).join(": ")}`;
|
|
739
|
+
});
|
|
740
|
+
if (renderLines.length) {
|
|
741
|
+
lines.push("");
|
|
742
|
+
lines.push("**Renders (median run)** \u2014 heaviest components by render self-time:");
|
|
743
|
+
lines.push(...renderLines);
|
|
744
|
+
}
|
|
731
745
|
lines.push("");
|
|
732
746
|
lines.push(
|
|
733
747
|
`_Deltas vs baseline (case #0). Don't pick on one metric \u2014 explain the trade-off (e.g. "A is cheapest but drops UI frames \u2192 jank risk; B costs more CPU but caps nothing")._`
|
|
@@ -780,7 +794,8 @@ async function readDeviceSettings(sync, deviceId) {
|
|
|
780
794
|
coolDownMs,
|
|
781
795
|
discardWarmupRuns,
|
|
782
796
|
shuffleCases,
|
|
783
|
-
reloadBetweenCases
|
|
797
|
+
reloadBetweenCases,
|
|
798
|
+
captureRenders
|
|
784
799
|
} = cfg;
|
|
785
800
|
return {
|
|
786
801
|
perCaseDurationMs,
|
|
@@ -790,7 +805,8 @@ async function readDeviceSettings(sync, deviceId) {
|
|
|
790
805
|
coolDownMs,
|
|
791
806
|
discardWarmupRuns,
|
|
792
807
|
shuffleCases,
|
|
793
|
-
reloadBetweenCases
|
|
808
|
+
reloadBetweenCases,
|
|
809
|
+
captureRenders
|
|
794
810
|
};
|
|
795
811
|
} catch {
|
|
796
812
|
return {};
|
|
@@ -894,6 +910,9 @@ function registerPerfMonitorTools(server, sync) {
|
|
|
894
910
|
discardWarmupRuns: import_zod2.z.number().optional().describe("Discard first N runs of each case before taking the median. Default 0."),
|
|
895
911
|
shuffleCases: import_zod2.z.boolean().optional().describe("Interleave + shuffle case order to cancel thermal bias. Default on for >3 cases."),
|
|
896
912
|
reloadBetweenCases: import_zod2.z.boolean().optional().describe("Fully reload JS between cases (needs <AutomationResumer/> mounted). Default false."),
|
|
913
|
+
captureRenders: import_zod2.z.boolean().optional().describe(
|
|
914
|
+
"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."
|
|
915
|
+
),
|
|
897
916
|
deviceId: import_zod2.z.string().optional().describe("Target device id. Omit to auto-select when one device is connected."),
|
|
898
917
|
maxWaitMs: import_zod2.z.number().optional().describe("Safety cap for how long to wait for completion. Defaults to ~3\xD7 the estimate + 60s.")
|
|
899
918
|
}
|
|
@@ -1157,6 +1176,7 @@ ${JSON.stringify(settings, null, 2)}
|
|
|
1157
1176
|
discardWarmupRuns: import_zod2.z.number().optional().describe("Discard first N runs of each page before the median."),
|
|
1158
1177
|
shuffleCases: import_zod2.z.boolean().optional().describe("Interleave + shuffle case order to cancel thermal bias."),
|
|
1159
1178
|
reloadBetweenCases: import_zod2.z.boolean().optional().describe("Fully reload JS between pages (needs <AutomationResumer/> mounted)."),
|
|
1179
|
+
captureRenders: import_zod2.z.boolean().optional().describe("Capture per-component render counts/durations during each run."),
|
|
1160
1180
|
deviceId: import_zod2.z.string().optional().describe("Target device id (auto-selected if one is connected).")
|
|
1161
1181
|
}
|
|
1162
1182
|
},
|
|
@@ -1172,7 +1192,8 @@ ${JSON.stringify(settings, null, 2)}
|
|
|
1172
1192
|
"runsPerCase",
|
|
1173
1193
|
"discardWarmupRuns",
|
|
1174
1194
|
"shuffleCases",
|
|
1175
|
-
"reloadBetweenCases"
|
|
1195
|
+
"reloadBetweenCases",
|
|
1196
|
+
"captureRenders"
|
|
1176
1197
|
];
|
|
1177
1198
|
const overrides = {};
|
|
1178
1199
|
for (const k of keys) {
|
|
@@ -1602,42 +1623,68 @@ function registerStorageTools(server, sync) {
|
|
|
1602
1623
|
server.registerTool(
|
|
1603
1624
|
"get_storage",
|
|
1604
1625
|
{
|
|
1605
|
-
description: "Read the app's AsyncStorage
|
|
1626
|
+
description: "Read the app's storage keys across ALL backends: AsyncStorage, MMKV (per instance), and Expo SecureStore. 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. Use for 'what's in storage / read this key'.",
|
|
1606
1627
|
inputSchema: {
|
|
1607
1628
|
pattern: import_zod8.z.string().optional().describe("Only keys containing this substring (case-insensitive)."),
|
|
1608
1629
|
includeValues: import_zod8.z.boolean().optional().describe("Include each key's value (truncated). Default false (keys only)."),
|
|
1609
|
-
limit: import_zod8.z.number().optional().describe("Max keys
|
|
1630
|
+
limit: import_zod8.z.number().optional().describe("Max keys per backend section. Default 100."),
|
|
1610
1631
|
deviceId: import_zod8.z.string().optional().describe("Target device id (auto-selected if one is connected).")
|
|
1611
1632
|
}
|
|
1612
1633
|
},
|
|
1613
1634
|
async (args) => {
|
|
1614
1635
|
try {
|
|
1615
1636
|
const target = sync.requireProDevice(args.deviceId);
|
|
1637
|
+
const pattern = args.pattern?.toLowerCase();
|
|
1638
|
+
const limit = typeof args.limit === "number" && args.limit > 0 ? args.limit : 100;
|
|
1639
|
+
const matches = (k) => !pattern || k.toLowerCase().includes(pattern);
|
|
1640
|
+
const sections = [];
|
|
1616
1641
|
const allKeys = await sync.callAction(STORAGE_TOOL_ID, "async.getAllKeys", {}, target);
|
|
1617
1642
|
if (!Array.isArray(allKeys)) {
|
|
1618
1643
|
return fail8("No storage result from the device. The storage tool may be inactive.");
|
|
1619
1644
|
}
|
|
1620
|
-
const
|
|
1621
|
-
|
|
1622
|
-
|
|
1623
|
-
|
|
1624
|
-
|
|
1625
|
-
|
|
1645
|
+
const asyncKeys = allKeys.filter(matches).slice(0, limit);
|
|
1646
|
+
if (asyncKeys.length > 0) {
|
|
1647
|
+
let lines;
|
|
1648
|
+
if (args.includeValues) {
|
|
1649
|
+
const pairs = await sync.callAction(STORAGE_TOOL_ID, "async.multiGet", { keys: asyncKeys }, target);
|
|
1650
|
+
const map = new Map((pairs ?? []).map(([k, v]) => [k, v]));
|
|
1651
|
+
lines = asyncKeys.map((k) => `- \`${k}\` = ${truncate(map.get(k) ?? null)}`);
|
|
1652
|
+
} else {
|
|
1653
|
+
lines = asyncKeys.map((k) => `- \`${k}\``);
|
|
1654
|
+
}
|
|
1655
|
+
sections.push(`**AsyncStorage (${asyncKeys.length}/${allKeys.length} keys):**
|
|
1656
|
+
${lines.join("\n")}`);
|
|
1626
1657
|
}
|
|
1627
|
-
|
|
1628
|
-
|
|
1629
|
-
const
|
|
1630
|
-
|
|
1631
|
-
lines =
|
|
1632
|
-
|
|
1633
|
-
|
|
1658
|
+
const mmkv = await sync.callAction(STORAGE_TOOL_ID, "mmkv.snapshot", {}, target).catch(() => null);
|
|
1659
|
+
for (const inst of mmkv ?? []) {
|
|
1660
|
+
const entries = inst.entries.filter((e) => matches(e.key)).slice(0, limit);
|
|
1661
|
+
if (entries.length === 0) continue;
|
|
1662
|
+
const lines = entries.map(
|
|
1663
|
+
(e) => args.includeValues ? `- \`${e.key}\` (${e.valueType}) = ${truncate(e.value)}` : `- \`${e.key}\``
|
|
1664
|
+
);
|
|
1665
|
+
const flags = [inst.encrypted && "encrypted", inst.readOnly && "read-only"].filter(Boolean).join(", ");
|
|
1666
|
+
sections.push(`**MMKV \`${inst.id}\`${flags ? ` (${flags})` : ""} (${entries.length}/${inst.entries.length} keys):**
|
|
1667
|
+
${lines.join("\n")}`);
|
|
1634
1668
|
}
|
|
1635
|
-
const
|
|
1636
|
-
const
|
|
1637
|
-
|
|
1638
|
-
|
|
1639
|
-
|
|
1640
|
-
|
|
1669
|
+
const secure = await sync.callAction(STORAGE_TOOL_ID, "secure.keys", {}, target).catch(() => null);
|
|
1670
|
+
const secureKeys = (secure ?? []).filter((d) => matches(d.key)).slice(0, limit);
|
|
1671
|
+
if (secureKeys.length > 0) {
|
|
1672
|
+
const lines = await Promise.all(
|
|
1673
|
+
secureKeys.map(async (d) => {
|
|
1674
|
+
if (d.requireAuthentication) return `- \`${d.key}\` \u{1F512} (biometric-protected \u2014 not read)`;
|
|
1675
|
+
if (!args.includeValues) return `- \`${d.key}\``;
|
|
1676
|
+
const v = await sync.callAction(STORAGE_TOOL_ID, "secure.get", { key: d.key }, target).catch(() => null);
|
|
1677
|
+
return `- \`${d.key}\` = ${truncate(v)}`;
|
|
1678
|
+
})
|
|
1679
|
+
);
|
|
1680
|
+
sections.push(`**SecureStore (${secureKeys.length} registered keys):**
|
|
1681
|
+
${lines.join("\n")}`);
|
|
1682
|
+
}
|
|
1683
|
+
if (sections.length === 0) {
|
|
1684
|
+
return text7(`No storage keys${pattern ? ` matching "${args.pattern}"` : ""} in any backend.`);
|
|
1685
|
+
}
|
|
1686
|
+
const note = args.includeValues ? "" : "\n\n_Keys only. Pass includeValues:true for values, or a pattern to filter. Modify AsyncStorage with storage_action._";
|
|
1687
|
+
return text7(`${sections.join("\n\n")}${note}`);
|
|
1641
1688
|
} catch (e) {
|
|
1642
1689
|
return fail8(errMsg8(e));
|
|
1643
1690
|
}
|
package/dist/cli.mjs
CHANGED
package/dist/index.d.mts
CHANGED
|
@@ -204,9 +204,10 @@ declare function registerReduxTools(server: McpServer, sync: SyncClient): void;
|
|
|
204
204
|
|
|
205
205
|
/**
|
|
206
206
|
* The storage pack: a friendly, compact reader + writer over the app's
|
|
207
|
-
*
|
|
208
|
-
*
|
|
209
|
-
* keys
|
|
207
|
+
* storage backends — AsyncStorage, MMKV (all registered instances), and Expo
|
|
208
|
+
* SecureStore (registered keys). Reuses the storage adapter's async.*, mmkv.*
|
|
209
|
+
* and secure.* actions; the device already strips Buoy's own devtool keys at
|
|
210
|
+
* the source, so only real app keys are returned.
|
|
210
211
|
*/
|
|
211
212
|
declare function registerStorageTools(server: McpServer, sync: SyncClient): void;
|
|
212
213
|
|
|
@@ -274,6 +275,8 @@ interface AutomationConfig {
|
|
|
274
275
|
coolDownMs?: number;
|
|
275
276
|
discardWarmupRuns?: number;
|
|
276
277
|
shuffleCases?: boolean;
|
|
278
|
+
/** Capture per-component render counts/durations each run. Default true. */
|
|
279
|
+
captureRenders?: boolean;
|
|
277
280
|
}
|
|
278
281
|
/** A lightweight saved-recording summary (the device's index entry). */
|
|
279
282
|
interface IndexEntry {
|
|
@@ -298,6 +301,14 @@ interface IndexEntry {
|
|
|
298
301
|
caseId?: string;
|
|
299
302
|
runIndex?: number;
|
|
300
303
|
isMedianRun?: boolean;
|
|
304
|
+
/** Render-capture mirror (absent when capture was unavailable/disabled). */
|
|
305
|
+
renderCommits?: number;
|
|
306
|
+
renderTotalMs?: number;
|
|
307
|
+
topRenderers?: {
|
|
308
|
+
name: string;
|
|
309
|
+
renders: number;
|
|
310
|
+
totalMs: number;
|
|
311
|
+
}[];
|
|
301
312
|
}
|
|
302
313
|
/** Per-metric min/max/avg/p95 channel. */
|
|
303
314
|
interface AggregateChannel {
|
|
@@ -337,9 +348,10 @@ interface BuildConfigInput {
|
|
|
337
348
|
discardWarmupRuns?: number;
|
|
338
349
|
shuffleCases?: boolean;
|
|
339
350
|
reloadBetweenCases?: boolean;
|
|
351
|
+
captureRenders?: boolean;
|
|
340
352
|
}
|
|
341
353
|
/** The settings subset a driver inherits from the device's saved config. */
|
|
342
|
-
type BenchmarkSettings = Partial<Pick<AutomationConfig, "perCaseDurationMs" | "settleMs" | "navTimeoutMs" | "runsPerCase" | "coolDownMs" | "discardWarmupRuns" | "shuffleCases" | "reloadBetweenCases">>;
|
|
354
|
+
type BenchmarkSettings = Partial<Pick<AutomationConfig, "perCaseDurationMs" | "settleMs" | "navTimeoutMs" | "runsPerCase" | "coolDownMs" | "discardWarmupRuns" | "shuffleCases" | "reloadBetweenCases" | "captureRenders">>;
|
|
343
355
|
/**
|
|
344
356
|
* Build + validate the AutomationConfig the device expects. Throws on bad input.
|
|
345
357
|
*
|
|
@@ -372,6 +384,14 @@ interface RankRow {
|
|
|
372
384
|
deltaJsFps: number | null;
|
|
373
385
|
/** At-risk axes for this case (e.g. "UI thread drops frames", "CPU near cap"). */
|
|
374
386
|
flags: string[];
|
|
387
|
+
/** React commits during the median run (null when capture was off/unavailable). */
|
|
388
|
+
renderCommits: number | null;
|
|
389
|
+
/** Top components by total render self-time during the median run. */
|
|
390
|
+
topRenderers: {
|
|
391
|
+
name: string;
|
|
392
|
+
renders: number;
|
|
393
|
+
totalMs: number;
|
|
394
|
+
}[];
|
|
375
395
|
}
|
|
376
396
|
interface BatchRanking {
|
|
377
397
|
batchId: string;
|
package/dist/index.d.ts
CHANGED
|
@@ -204,9 +204,10 @@ declare function registerReduxTools(server: McpServer, sync: SyncClient): void;
|
|
|
204
204
|
|
|
205
205
|
/**
|
|
206
206
|
* The storage pack: a friendly, compact reader + writer over the app's
|
|
207
|
-
*
|
|
208
|
-
*
|
|
209
|
-
* keys
|
|
207
|
+
* storage backends — AsyncStorage, MMKV (all registered instances), and Expo
|
|
208
|
+
* SecureStore (registered keys). Reuses the storage adapter's async.*, mmkv.*
|
|
209
|
+
* and secure.* actions; the device already strips Buoy's own devtool keys at
|
|
210
|
+
* the source, so only real app keys are returned.
|
|
210
211
|
*/
|
|
211
212
|
declare function registerStorageTools(server: McpServer, sync: SyncClient): void;
|
|
212
213
|
|
|
@@ -274,6 +275,8 @@ interface AutomationConfig {
|
|
|
274
275
|
coolDownMs?: number;
|
|
275
276
|
discardWarmupRuns?: number;
|
|
276
277
|
shuffleCases?: boolean;
|
|
278
|
+
/** Capture per-component render counts/durations each run. Default true. */
|
|
279
|
+
captureRenders?: boolean;
|
|
277
280
|
}
|
|
278
281
|
/** A lightweight saved-recording summary (the device's index entry). */
|
|
279
282
|
interface IndexEntry {
|
|
@@ -298,6 +301,14 @@ interface IndexEntry {
|
|
|
298
301
|
caseId?: string;
|
|
299
302
|
runIndex?: number;
|
|
300
303
|
isMedianRun?: boolean;
|
|
304
|
+
/** Render-capture mirror (absent when capture was unavailable/disabled). */
|
|
305
|
+
renderCommits?: number;
|
|
306
|
+
renderTotalMs?: number;
|
|
307
|
+
topRenderers?: {
|
|
308
|
+
name: string;
|
|
309
|
+
renders: number;
|
|
310
|
+
totalMs: number;
|
|
311
|
+
}[];
|
|
301
312
|
}
|
|
302
313
|
/** Per-metric min/max/avg/p95 channel. */
|
|
303
314
|
interface AggregateChannel {
|
|
@@ -337,9 +348,10 @@ interface BuildConfigInput {
|
|
|
337
348
|
discardWarmupRuns?: number;
|
|
338
349
|
shuffleCases?: boolean;
|
|
339
350
|
reloadBetweenCases?: boolean;
|
|
351
|
+
captureRenders?: boolean;
|
|
340
352
|
}
|
|
341
353
|
/** The settings subset a driver inherits from the device's saved config. */
|
|
342
|
-
type BenchmarkSettings = Partial<Pick<AutomationConfig, "perCaseDurationMs" | "settleMs" | "navTimeoutMs" | "runsPerCase" | "coolDownMs" | "discardWarmupRuns" | "shuffleCases" | "reloadBetweenCases">>;
|
|
354
|
+
type BenchmarkSettings = Partial<Pick<AutomationConfig, "perCaseDurationMs" | "settleMs" | "navTimeoutMs" | "runsPerCase" | "coolDownMs" | "discardWarmupRuns" | "shuffleCases" | "reloadBetweenCases" | "captureRenders">>;
|
|
343
355
|
/**
|
|
344
356
|
* Build + validate the AutomationConfig the device expects. Throws on bad input.
|
|
345
357
|
*
|
|
@@ -372,6 +384,14 @@ interface RankRow {
|
|
|
372
384
|
deltaJsFps: number | null;
|
|
373
385
|
/** At-risk axes for this case (e.g. "UI thread drops frames", "CPU near cap"). */
|
|
374
386
|
flags: string[];
|
|
387
|
+
/** React commits during the median run (null when capture was off/unavailable). */
|
|
388
|
+
renderCommits: number | null;
|
|
389
|
+
/** Top components by total render self-time during the median run. */
|
|
390
|
+
topRenderers: {
|
|
391
|
+
name: string;
|
|
392
|
+
renders: number;
|
|
393
|
+
totalMs: number;
|
|
394
|
+
}[];
|
|
375
395
|
}
|
|
376
396
|
interface BatchRanking {
|
|
377
397
|
batchId: string;
|
package/dist/index.js
CHANGED
|
@@ -371,7 +371,7 @@ var import_node_os = __toESM(require("os"));
|
|
|
371
371
|
var import_node_path = __toESM(require("path"));
|
|
372
372
|
|
|
373
373
|
// src/version.ts
|
|
374
|
-
var VERSION = "
|
|
374
|
+
var VERSION = "6.0.0" ? "6.0.0" : "0.0.0-dev";
|
|
375
375
|
var PACKAGE_NAME = "@buoy-gg/mcp";
|
|
376
376
|
|
|
377
377
|
// src/update/checkForUpdate.ts
|
|
@@ -605,7 +605,8 @@ function buildAutomationConfig(input, base = {}) {
|
|
|
605
605
|
coolDownMs,
|
|
606
606
|
discardWarmupRuns,
|
|
607
607
|
shuffleCases,
|
|
608
|
-
reloadBetweenCases: input.reloadBetweenCases ?? base.reloadBetweenCases ?? false
|
|
608
|
+
reloadBetweenCases: input.reloadBetweenCases ?? base.reloadBetweenCases ?? false,
|
|
609
|
+
captureRenders: input.captureRenders ?? base.captureRenders ?? true
|
|
609
610
|
};
|
|
610
611
|
return { config, warnings };
|
|
611
612
|
}
|
|
@@ -619,7 +620,8 @@ function describeBenchmarkSettings(config) {
|
|
|
619
620
|
`**${secs(config.perCaseDurationMs)} measured per page**`,
|
|
620
621
|
runs > 1 ? `**${runs} runs each** (median taken)` : `**1 run each** (no median \u2014 noisier)`,
|
|
621
622
|
config.reloadBetweenCases ? "**full reload between pages** (no state leaks across tests)" : "no reload between pages",
|
|
622
|
-
config.shuffleCases ? "shuffled order (cancels thermal bias)" : "fixed order"
|
|
623
|
+
config.shuffleCases ? "shuffled order (cancels thermal bias)" : "fixed order",
|
|
624
|
+
config.captureRenders !== false ? "render capture on (per-component why-data)" : "render capture off"
|
|
623
625
|
];
|
|
624
626
|
const perPageMs = config.settleMs + config.perCaseDurationMs + (config.coolDownMs ?? 0) + 1800;
|
|
625
627
|
return `${parts.join(", ")}. That's ~${secs(perPageMs)} wall-clock per page; for ${config.cases.length} page(s) \xD7 ${runs} run(s), **${est}** total.`;
|
|
@@ -674,7 +676,9 @@ function rankBatchFromIndex(index, batchId, casesOrder, failures = []) {
|
|
|
674
676
|
uiJank: num(median?.uiJankFrames),
|
|
675
677
|
deviceMaxRefreshRate: num(median?.deviceMaxRefreshRate),
|
|
676
678
|
deltaUiFps: null,
|
|
677
|
-
deltaJsFps: null
|
|
679
|
+
deltaJsFps: null,
|
|
680
|
+
renderCommits: num(median?.renderCommits),
|
|
681
|
+
topRenderers: median?.topRenderers ?? []
|
|
678
682
|
};
|
|
679
683
|
return { ...base, flags: failed ? [] : computeFlags(base) };
|
|
680
684
|
});
|
|
@@ -763,6 +767,16 @@ function formatRankingMarkdown(ranking) {
|
|
|
763
767
|
`**\u26A0\uFE0F At-risk:** ${ranking.flagged.map((x) => `${x.caseName} (${x.flags.join("; ")})`).join(" \xB7 ")}`
|
|
764
768
|
);
|
|
765
769
|
}
|
|
770
|
+
const renderLines = ranking.rows.filter((r) => !r.failed && (r.renderCommits != null || r.topRenderers.length > 0)).map((r) => {
|
|
771
|
+
const top = r.topRenderers.map((t) => `${t.name} \xD7${t.renders} (${t.totalMs}ms)`).join(", ");
|
|
772
|
+
const commits = r.renderCommits != null ? `${r.renderCommits} commits` : "";
|
|
773
|
+
return `- **${r.caseName}** \u2014 ${[commits, top].filter(Boolean).join(": ")}`;
|
|
774
|
+
});
|
|
775
|
+
if (renderLines.length) {
|
|
776
|
+
lines.push("");
|
|
777
|
+
lines.push("**Renders (median run)** \u2014 heaviest components by render self-time:");
|
|
778
|
+
lines.push(...renderLines);
|
|
779
|
+
}
|
|
766
780
|
lines.push("");
|
|
767
781
|
lines.push(
|
|
768
782
|
`_Deltas vs baseline (case #0). Don't pick on one metric \u2014 explain the trade-off (e.g. "A is cheapest but drops UI frames \u2192 jank risk; B costs more CPU but caps nothing")._`
|
|
@@ -815,7 +829,8 @@ async function readDeviceSettings(sync, deviceId) {
|
|
|
815
829
|
coolDownMs,
|
|
816
830
|
discardWarmupRuns,
|
|
817
831
|
shuffleCases,
|
|
818
|
-
reloadBetweenCases
|
|
832
|
+
reloadBetweenCases,
|
|
833
|
+
captureRenders
|
|
819
834
|
} = cfg;
|
|
820
835
|
return {
|
|
821
836
|
perCaseDurationMs,
|
|
@@ -825,7 +840,8 @@ async function readDeviceSettings(sync, deviceId) {
|
|
|
825
840
|
coolDownMs,
|
|
826
841
|
discardWarmupRuns,
|
|
827
842
|
shuffleCases,
|
|
828
|
-
reloadBetweenCases
|
|
843
|
+
reloadBetweenCases,
|
|
844
|
+
captureRenders
|
|
829
845
|
};
|
|
830
846
|
} catch {
|
|
831
847
|
return {};
|
|
@@ -929,6 +945,9 @@ function registerPerfMonitorTools(server, sync) {
|
|
|
929
945
|
discardWarmupRuns: import_zod2.z.number().optional().describe("Discard first N runs of each case before taking the median. Default 0."),
|
|
930
946
|
shuffleCases: import_zod2.z.boolean().optional().describe("Interleave + shuffle case order to cancel thermal bias. Default on for >3 cases."),
|
|
931
947
|
reloadBetweenCases: import_zod2.z.boolean().optional().describe("Fully reload JS between cases (needs <AutomationResumer/> mounted). Default false."),
|
|
948
|
+
captureRenders: import_zod2.z.boolean().optional().describe(
|
|
949
|
+
"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."
|
|
950
|
+
),
|
|
932
951
|
deviceId: import_zod2.z.string().optional().describe("Target device id. Omit to auto-select when one device is connected."),
|
|
933
952
|
maxWaitMs: import_zod2.z.number().optional().describe("Safety cap for how long to wait for completion. Defaults to ~3\xD7 the estimate + 60s.")
|
|
934
953
|
}
|
|
@@ -1192,6 +1211,7 @@ ${JSON.stringify(settings, null, 2)}
|
|
|
1192
1211
|
discardWarmupRuns: import_zod2.z.number().optional().describe("Discard first N runs of each page before the median."),
|
|
1193
1212
|
shuffleCases: import_zod2.z.boolean().optional().describe("Interleave + shuffle case order to cancel thermal bias."),
|
|
1194
1213
|
reloadBetweenCases: import_zod2.z.boolean().optional().describe("Fully reload JS between pages (needs <AutomationResumer/> mounted)."),
|
|
1214
|
+
captureRenders: import_zod2.z.boolean().optional().describe("Capture per-component render counts/durations during each run."),
|
|
1195
1215
|
deviceId: import_zod2.z.string().optional().describe("Target device id (auto-selected if one is connected).")
|
|
1196
1216
|
}
|
|
1197
1217
|
},
|
|
@@ -1207,7 +1227,8 @@ ${JSON.stringify(settings, null, 2)}
|
|
|
1207
1227
|
"runsPerCase",
|
|
1208
1228
|
"discardWarmupRuns",
|
|
1209
1229
|
"shuffleCases",
|
|
1210
|
-
"reloadBetweenCases"
|
|
1230
|
+
"reloadBetweenCases",
|
|
1231
|
+
"captureRenders"
|
|
1211
1232
|
];
|
|
1212
1233
|
const overrides = {};
|
|
1213
1234
|
for (const k of keys) {
|
|
@@ -1637,42 +1658,68 @@ function registerStorageTools(server, sync) {
|
|
|
1637
1658
|
server.registerTool(
|
|
1638
1659
|
"get_storage",
|
|
1639
1660
|
{
|
|
1640
|
-
description: "Read the app's AsyncStorage
|
|
1661
|
+
description: "Read the app's storage keys across ALL backends: AsyncStorage, MMKV (per instance), and Expo SecureStore. 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. Use for 'what's in storage / read this key'.",
|
|
1641
1662
|
inputSchema: {
|
|
1642
1663
|
pattern: import_zod8.z.string().optional().describe("Only keys containing this substring (case-insensitive)."),
|
|
1643
1664
|
includeValues: import_zod8.z.boolean().optional().describe("Include each key's value (truncated). Default false (keys only)."),
|
|
1644
|
-
limit: import_zod8.z.number().optional().describe("Max keys
|
|
1665
|
+
limit: import_zod8.z.number().optional().describe("Max keys per backend section. Default 100."),
|
|
1645
1666
|
deviceId: import_zod8.z.string().optional().describe("Target device id (auto-selected if one is connected).")
|
|
1646
1667
|
}
|
|
1647
1668
|
},
|
|
1648
1669
|
async (args) => {
|
|
1649
1670
|
try {
|
|
1650
1671
|
const target = sync.requireProDevice(args.deviceId);
|
|
1672
|
+
const pattern = args.pattern?.toLowerCase();
|
|
1673
|
+
const limit = typeof args.limit === "number" && args.limit > 0 ? args.limit : 100;
|
|
1674
|
+
const matches = (k) => !pattern || k.toLowerCase().includes(pattern);
|
|
1675
|
+
const sections = [];
|
|
1651
1676
|
const allKeys = await sync.callAction(STORAGE_TOOL_ID, "async.getAllKeys", {}, target);
|
|
1652
1677
|
if (!Array.isArray(allKeys)) {
|
|
1653
1678
|
return fail8("No storage result from the device. The storage tool may be inactive.");
|
|
1654
1679
|
}
|
|
1655
|
-
const
|
|
1656
|
-
|
|
1657
|
-
|
|
1658
|
-
|
|
1659
|
-
|
|
1660
|
-
|
|
1680
|
+
const asyncKeys = allKeys.filter(matches).slice(0, limit);
|
|
1681
|
+
if (asyncKeys.length > 0) {
|
|
1682
|
+
let lines;
|
|
1683
|
+
if (args.includeValues) {
|
|
1684
|
+
const pairs = await sync.callAction(STORAGE_TOOL_ID, "async.multiGet", { keys: asyncKeys }, target);
|
|
1685
|
+
const map = new Map((pairs ?? []).map(([k, v]) => [k, v]));
|
|
1686
|
+
lines = asyncKeys.map((k) => `- \`${k}\` = ${truncate(map.get(k) ?? null)}`);
|
|
1687
|
+
} else {
|
|
1688
|
+
lines = asyncKeys.map((k) => `- \`${k}\``);
|
|
1689
|
+
}
|
|
1690
|
+
sections.push(`**AsyncStorage (${asyncKeys.length}/${allKeys.length} keys):**
|
|
1691
|
+
${lines.join("\n")}`);
|
|
1661
1692
|
}
|
|
1662
|
-
|
|
1663
|
-
|
|
1664
|
-
const
|
|
1665
|
-
|
|
1666
|
-
lines =
|
|
1667
|
-
|
|
1668
|
-
|
|
1693
|
+
const mmkv = await sync.callAction(STORAGE_TOOL_ID, "mmkv.snapshot", {}, target).catch(() => null);
|
|
1694
|
+
for (const inst of mmkv ?? []) {
|
|
1695
|
+
const entries = inst.entries.filter((e) => matches(e.key)).slice(0, limit);
|
|
1696
|
+
if (entries.length === 0) continue;
|
|
1697
|
+
const lines = entries.map(
|
|
1698
|
+
(e) => args.includeValues ? `- \`${e.key}\` (${e.valueType}) = ${truncate(e.value)}` : `- \`${e.key}\``
|
|
1699
|
+
);
|
|
1700
|
+
const flags = [inst.encrypted && "encrypted", inst.readOnly && "read-only"].filter(Boolean).join(", ");
|
|
1701
|
+
sections.push(`**MMKV \`${inst.id}\`${flags ? ` (${flags})` : ""} (${entries.length}/${inst.entries.length} keys):**
|
|
1702
|
+
${lines.join("\n")}`);
|
|
1669
1703
|
}
|
|
1670
|
-
const
|
|
1671
|
-
const
|
|
1672
|
-
|
|
1673
|
-
|
|
1674
|
-
|
|
1675
|
-
|
|
1704
|
+
const secure = await sync.callAction(STORAGE_TOOL_ID, "secure.keys", {}, target).catch(() => null);
|
|
1705
|
+
const secureKeys = (secure ?? []).filter((d) => matches(d.key)).slice(0, limit);
|
|
1706
|
+
if (secureKeys.length > 0) {
|
|
1707
|
+
const lines = await Promise.all(
|
|
1708
|
+
secureKeys.map(async (d) => {
|
|
1709
|
+
if (d.requireAuthentication) return `- \`${d.key}\` \u{1F512} (biometric-protected \u2014 not read)`;
|
|
1710
|
+
if (!args.includeValues) return `- \`${d.key}\``;
|
|
1711
|
+
const v = await sync.callAction(STORAGE_TOOL_ID, "secure.get", { key: d.key }, target).catch(() => null);
|
|
1712
|
+
return `- \`${d.key}\` = ${truncate(v)}`;
|
|
1713
|
+
})
|
|
1714
|
+
);
|
|
1715
|
+
sections.push(`**SecureStore (${secureKeys.length} registered keys):**
|
|
1716
|
+
${lines.join("\n")}`);
|
|
1717
|
+
}
|
|
1718
|
+
if (sections.length === 0) {
|
|
1719
|
+
return text7(`No storage keys${pattern ? ` matching "${args.pattern}"` : ""} in any backend.`);
|
|
1720
|
+
}
|
|
1721
|
+
const note = args.includeValues ? "" : "\n\n_Keys only. Pass includeValues:true for values, or a pattern to filter. Modify AsyncStorage with storage_action._";
|
|
1722
|
+
return text7(`${sections.join("\n\n")}${note}`);
|
|
1676
1723
|
} catch (e) {
|
|
1677
1724
|
return fail8(errMsg8(e));
|
|
1678
1725
|
}
|
package/dist/index.mjs
CHANGED
package/package.json
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@buoy-gg/mcp",
|
|
3
|
-
"
|
|
3
|
+
"mcpName": "io.github.buoy-gg/mcp",
|
|
4
|
+
"version": "6.0.0",
|
|
4
5
|
"description": "MCP server for Buoy devtools — lets any LLM copilot drive Buoy tools (perf-monitor benchmarks, etc.) over the devtool sync protocol",
|
|
5
6
|
"main": "dist/index.js",
|
|
6
7
|
"module": "dist/index.mjs",
|
|
@@ -30,7 +31,7 @@
|
|
|
30
31
|
"@modelcontextprotocol/sdk": "^1.29.0",
|
|
31
32
|
"socket.io-client": "^4.8.1",
|
|
32
33
|
"zod": "^3.25.0",
|
|
33
|
-
"@buoy-gg/sync-broker": "
|
|
34
|
+
"@buoy-gg/sync-broker": "6.0.0"
|
|
34
35
|
},
|
|
35
36
|
"devDependencies": {
|
|
36
37
|
"@types/node": "^22.0.0",
|