@buoy-gg/mcp 7.0.27 → 7.0.29
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-OFUMTAIL.mjs → chunk-V7PYMDI2.mjs} +193 -52
- package/dist/cli.js +191 -52
- package/dist/cli.mjs +1 -1
- package/dist/index.d.mts +10 -8
- package/dist/index.d.ts +10 -8
- package/dist/index.js +191 -52
- package/dist/index.mjs +1 -1
- package/package.json +2 -2
package/dist/index.js
CHANGED
|
@@ -81,6 +81,33 @@ var DEVICE_RETENTION_MS = 15 * 6e4;
|
|
|
81
81
|
var DASHBOARD_NAME = "Dashboard";
|
|
82
82
|
var ALL_DEVICES = "All";
|
|
83
83
|
|
|
84
|
+
// src/sync/actionTimeout.ts
|
|
85
|
+
function isRecoveryAction(toolId, action) {
|
|
86
|
+
return toolId === "app" && (action === "ping" || action === "reloadApp");
|
|
87
|
+
}
|
|
88
|
+
function isUnresponsive(consecutiveTimeouts) {
|
|
89
|
+
return consecutiveTimeouts >= 2;
|
|
90
|
+
}
|
|
91
|
+
function actionTimeoutMs(input) {
|
|
92
|
+
if (isRecoveryAction(input.toolId, input.action)) {
|
|
93
|
+
return { timeoutMs: input.fullMs, usedFullBudget: false };
|
|
94
|
+
}
|
|
95
|
+
if (!isUnresponsive(input.consecutiveTimeouts)) {
|
|
96
|
+
return { timeoutMs: input.fullMs, usedFullBudget: false };
|
|
97
|
+
}
|
|
98
|
+
if (input.now - input.lastFullBudgetAt > input.recoveryProbeMs) {
|
|
99
|
+
return { timeoutMs: input.fullMs, usedFullBudget: true };
|
|
100
|
+
}
|
|
101
|
+
return { timeoutMs: input.shortMs, usedFullBudget: false };
|
|
102
|
+
}
|
|
103
|
+
function diagnoseCrashed(opts) {
|
|
104
|
+
if (opts.consecutiveTimeouts >= 2) return true;
|
|
105
|
+
if (opts.lastOkAt > 0 && opts.now - opts.lastOkAt > opts.unresponsiveAfterMs) {
|
|
106
|
+
return true;
|
|
107
|
+
}
|
|
108
|
+
return false;
|
|
109
|
+
}
|
|
110
|
+
|
|
84
111
|
// src/sync/SyncClient.ts
|
|
85
112
|
var SyncClient = class {
|
|
86
113
|
constructor(opts = {}) {
|
|
@@ -349,27 +376,34 @@ var SyncClient = class {
|
|
|
349
376
|
noteTimeout(deviceId) {
|
|
350
377
|
this.healthOf(deviceId).consecutiveTimeouts += 1;
|
|
351
378
|
}
|
|
352
|
-
/** True once a device has missed
|
|
379
|
+
/** True once a device has missed TWO calls and hasn't answered since. */
|
|
353
380
|
isUnresponsive(deviceId) {
|
|
354
|
-
return this.healthOf(deviceId).consecutiveTimeouts
|
|
381
|
+
return isUnresponsive(this.healthOf(deviceId).consecutiveTimeouts);
|
|
355
382
|
}
|
|
356
383
|
/**
|
|
357
384
|
* Full timeout normally; a short one for a device already not answering.
|
|
358
385
|
*
|
|
359
|
-
*
|
|
360
|
-
*
|
|
361
|
-
*
|
|
362
|
-
*
|
|
363
|
-
*
|
|
386
|
+
* A single missed call does not trigger the short budget — one slow
|
|
387
|
+
* `describeScreen` is not a crash. `app.ping` / `app.reloadApp` always get
|
|
388
|
+
* the full budget (they are the recovery path).
|
|
389
|
+
*
|
|
390
|
+
* Escape hatch: every RECOVERY_PROBE_MS a flagged device gets the full
|
|
391
|
+
* budget again so the short timeout cannot become self-sustaining.
|
|
364
392
|
*/
|
|
365
|
-
timeoutFor(deviceId) {
|
|
366
|
-
if (!this.isUnresponsive(deviceId)) return this.actionTimeoutMs;
|
|
393
|
+
timeoutFor(deviceId, toolId, action) {
|
|
367
394
|
const h = this.healthOf(deviceId);
|
|
368
|
-
|
|
369
|
-
h.
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
395
|
+
const { timeoutMs, usedFullBudget } = actionTimeoutMs({
|
|
396
|
+
consecutiveTimeouts: h.consecutiveTimeouts,
|
|
397
|
+
lastFullBudgetAt: h.lastFullBudgetAt,
|
|
398
|
+
now: Date.now(),
|
|
399
|
+
fullMs: this.actionTimeoutMs,
|
|
400
|
+
shortMs: UNRESPONSIVE_ACTION_TIMEOUT_MS,
|
|
401
|
+
recoveryProbeMs: RECOVERY_PROBE_MS,
|
|
402
|
+
toolId,
|
|
403
|
+
action
|
|
404
|
+
});
|
|
405
|
+
if (usedFullBudget) h.lastFullBudgetAt = Date.now();
|
|
406
|
+
return timeoutMs;
|
|
373
407
|
}
|
|
374
408
|
/**
|
|
375
409
|
* Plain-language diagnosis for a device that isn't answering, including the
|
|
@@ -382,7 +416,12 @@ var SyncClient = class {
|
|
|
382
416
|
unresponsiveNote(deviceId) {
|
|
383
417
|
const h = this.healthOf(deviceId);
|
|
384
418
|
const since = h.lastOkAt ? Math.round((Date.now() - h.lastOkAt) / 1e3) : null;
|
|
385
|
-
const crashed =
|
|
419
|
+
const crashed = diagnoseCrashed({
|
|
420
|
+
consecutiveTimeouts: h.consecutiveTimeouts,
|
|
421
|
+
lastOkAt: h.lastOkAt,
|
|
422
|
+
now: Date.now(),
|
|
423
|
+
unresponsiveAfterMs: UNRESPONSIVE_AFTER_MS
|
|
424
|
+
});
|
|
386
425
|
const lines = [
|
|
387
426
|
`The app on "${deviceId}" is not responding (${h.consecutiveTimeouts} call${h.consecutiveTimeouts === 1 ? "" : "s"} unanswered${since != null ? `, last answered ${since}s ago` : ""}).`,
|
|
388
427
|
crashed ? "It is most likely CRASHED \u2014 a fatal render error unmounts the React tree, and the dev tools live in that tree, so nothing on-device can answer until the app is reloaded." : "It may be busy, mid-reload, or crashing."
|
|
@@ -584,7 +623,7 @@ var SyncClient = class {
|
|
|
584
623
|
const target = deviceId === ALL_DEVICES ? ALL_DEVICES : this.resolveDevice(deviceId);
|
|
585
624
|
if (!this.socket) throw new Error("SyncClient is not connected");
|
|
586
625
|
const requestId = (0, import_node_crypto.randomUUID)();
|
|
587
|
-
const timeoutMs = opts.timeoutMs ?? this.timeoutFor(target);
|
|
626
|
+
const timeoutMs = opts.timeoutMs ?? this.timeoutFor(target, toolId, action);
|
|
588
627
|
const result = await new Promise((resolve, reject) => {
|
|
589
628
|
const timer = setTimeout(() => {
|
|
590
629
|
this.pendingActions.delete(requestId);
|
|
@@ -647,7 +686,7 @@ var import_node_os = __toESM(require("os"));
|
|
|
647
686
|
var import_node_path = __toESM(require("path"));
|
|
648
687
|
|
|
649
688
|
// src/version.ts
|
|
650
|
-
var VERSION = "7.0.
|
|
689
|
+
var VERSION = "7.0.29" ? "7.0.29" : "0.0.0-dev";
|
|
651
690
|
var PACKAGE_NAME = "@buoy-gg/mcp";
|
|
652
691
|
|
|
653
692
|
// src/update/checkForUpdate.ts
|
|
@@ -1673,7 +1712,14 @@ function statusIcon(e) {
|
|
|
1673
1712
|
return "\u2705";
|
|
1674
1713
|
}
|
|
1675
1714
|
function statusLabel(e) {
|
|
1676
|
-
if (e.
|
|
1715
|
+
if (e.status !== void 0 && e.status > 0) return String(e.status);
|
|
1716
|
+
if (e.error) {
|
|
1717
|
+
const cleaned = e.error.replace(/^HTTP\s+\d+:\s*/i, "").trim();
|
|
1718
|
+
if (!cleaned || cleaned === "undefined" || cleaned === "no error") {
|
|
1719
|
+
return "ERR";
|
|
1720
|
+
}
|
|
1721
|
+
return `ERR ${e.error}`;
|
|
1722
|
+
}
|
|
1677
1723
|
if (e.status === void 0) return "pending";
|
|
1678
1724
|
return String(e.status);
|
|
1679
1725
|
}
|
|
@@ -2133,10 +2179,20 @@ A rule that never fires is usually one of: the master switch is off, the interce
|
|
|
2133
2179
|
|
|
2134
2180
|
// src/tools/packs/reactQuery.ts
|
|
2135
2181
|
var import_zod5 = require("zod");
|
|
2136
|
-
var RQ_TOOL_ID = "
|
|
2182
|
+
var RQ_TOOL_ID = "query";
|
|
2183
|
+
var RQ_TOOL_ID_LEGACY = "react-query";
|
|
2137
2184
|
var text4 = (s) => ({ content: [{ type: "text", text: s }] });
|
|
2138
2185
|
var fail5 = (message) => ({ isError: true, content: [{ type: "text", text: message }] });
|
|
2139
2186
|
var errMsg5 = (e) => e instanceof Error ? e.message : String(e);
|
|
2187
|
+
async function callQueryAction(sync, action, params, target) {
|
|
2188
|
+
try {
|
|
2189
|
+
return await sync.callAction(RQ_TOOL_ID, action, params, target);
|
|
2190
|
+
} catch (e) {
|
|
2191
|
+
const msg = errMsg5(e);
|
|
2192
|
+
if (!/unknown tool/i.test(msg)) throw e;
|
|
2193
|
+
return await sync.callAction(RQ_TOOL_ID_LEGACY, action, params, target);
|
|
2194
|
+
}
|
|
2195
|
+
}
|
|
2140
2196
|
var STATUS_ICON = {
|
|
2141
2197
|
fresh: "\u{1F7E2}",
|
|
2142
2198
|
stale: "\u{1F7E1}",
|
|
@@ -2169,8 +2225,8 @@ function registerReactQueryTools(server, sync) {
|
|
|
2169
2225
|
async (args) => {
|
|
2170
2226
|
try {
|
|
2171
2227
|
const target = sync.requireProDevice(args.deviceId);
|
|
2172
|
-
const res = await
|
|
2173
|
-
|
|
2228
|
+
const res = await callQueryAction(
|
|
2229
|
+
sync,
|
|
2174
2230
|
"listQueries",
|
|
2175
2231
|
{ includeData: args.includeData ?? false, staleOnly: args.staleOnly ?? false, limit: args.limit ?? 25 },
|
|
2176
2232
|
target
|
|
@@ -2216,13 +2272,13 @@ ${lines.join("\n")}${note}`);
|
|
|
2216
2272
|
try {
|
|
2217
2273
|
const target = sync.requireProDevice(args.deviceId);
|
|
2218
2274
|
if (args.action === "clear") {
|
|
2219
|
-
await sync
|
|
2275
|
+
await callQueryAction(sync, "clearQueryCache", {}, target);
|
|
2220
2276
|
return text4("Cleared the entire React Query cache.");
|
|
2221
2277
|
}
|
|
2222
2278
|
if (!args.queryHash) {
|
|
2223
2279
|
return fail5(`'${args.action}' needs a queryHash. Call get_react_query first and use a row's hash (in backticks).`);
|
|
2224
2280
|
}
|
|
2225
|
-
await sync
|
|
2281
|
+
await callQueryAction(sync, args.action, { queryHash: args.queryHash }, target);
|
|
2226
2282
|
return text4(`Ran ${args.action} on query \`${args.queryHash}\`.`);
|
|
2227
2283
|
} catch (e) {
|
|
2228
2284
|
return fail5(errMsg5(e));
|
|
@@ -2649,17 +2705,23 @@ ${shown.map((r) => `- \`${r}\``).join("\n")}`
|
|
|
2649
2705
|
server.registerTool(
|
|
2650
2706
|
"navigate",
|
|
2651
2707
|
{
|
|
2652
|
-
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.",
|
|
2708
|
+
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 '/').",
|
|
2653
2709
|
inputSchema: {
|
|
2654
2710
|
path: import_zod10.z.string().describe("The concrete path to navigate to, e.g. '/pokemon/25'."),
|
|
2711
|
+
replace: import_zod10.z.boolean().optional().describe("Replace the current screen instead of pushing (use when resetting, e.g. to '/')."),
|
|
2655
2712
|
deviceId: import_zod10.z.string().optional().describe("Target device id (auto-selected if one is connected).")
|
|
2656
2713
|
}
|
|
2657
2714
|
},
|
|
2658
2715
|
async (args) => {
|
|
2659
2716
|
try {
|
|
2660
2717
|
const target = sync.requireProDevice(args.deviceId);
|
|
2661
|
-
await sync.callAction(
|
|
2662
|
-
|
|
2718
|
+
await sync.callAction(
|
|
2719
|
+
ROUTES_TOOL_ID,
|
|
2720
|
+
"navigate",
|
|
2721
|
+
{ path: args.path, replace: args.replace === true },
|
|
2722
|
+
target
|
|
2723
|
+
);
|
|
2724
|
+
return text9(`Navigated to \`${args.path}\`${args.replace ? " (replaced current screen)" : ""}.`);
|
|
2663
2725
|
} catch (e) {
|
|
2664
2726
|
return fail10(errMsg10(e));
|
|
2665
2727
|
}
|
|
@@ -2959,6 +3021,40 @@ async function cropToBuffer(srcPath, full, region) {
|
|
|
2959
3021
|
}
|
|
2960
3022
|
}
|
|
2961
3023
|
|
|
3024
|
+
// src/tools/packs/screenshotLocate.ts
|
|
3025
|
+
function pickDescribeScreenMatch(elements, query, nativeTag) {
|
|
3026
|
+
if (nativeTag != null) {
|
|
3027
|
+
const exact = elements.find((e) => e.nativeTag === nativeTag);
|
|
3028
|
+
if (exact) return exact;
|
|
3029
|
+
}
|
|
3030
|
+
const q = (query ?? "").trim().toLowerCase();
|
|
3031
|
+
if (!q) return void 0;
|
|
3032
|
+
let best;
|
|
3033
|
+
for (const el of elements) {
|
|
3034
|
+
const fields = [el.testID, el.label, el.text, el.name].filter(Boolean).map((s) => s.toLowerCase());
|
|
3035
|
+
let score = 0;
|
|
3036
|
+
for (const f of fields) {
|
|
3037
|
+
if (f === q) score = Math.max(score, 100);
|
|
3038
|
+
else if (f.startsWith(q)) score = Math.max(score, 50);
|
|
3039
|
+
else if (f.includes(q)) score = Math.max(score, 25);
|
|
3040
|
+
}
|
|
3041
|
+
const tap = el.tap;
|
|
3042
|
+
if (tap && tap.x >= 0 && tap.x <= 1 && tap.y >= 0 && tap.y <= 1) score += 20;
|
|
3043
|
+
else if (tap) score -= 40;
|
|
3044
|
+
if (score <= 0) continue;
|
|
3045
|
+
if (!best || score > best.score) best = { el, score };
|
|
3046
|
+
}
|
|
3047
|
+
return best?.el;
|
|
3048
|
+
}
|
|
3049
|
+
function frameToRect(frame, screen) {
|
|
3050
|
+
return {
|
|
3051
|
+
x: frame.x * screen.width,
|
|
3052
|
+
y: frame.y * screen.height,
|
|
3053
|
+
width: frame.width * screen.width,
|
|
3054
|
+
height: frame.height * screen.height
|
|
3055
|
+
};
|
|
3056
|
+
}
|
|
3057
|
+
|
|
2962
3058
|
// src/tools/packs/screenshot.ts
|
|
2963
3059
|
var TOOL_ID = "highlight-updates";
|
|
2964
3060
|
var MARGIN_PT = 12;
|
|
@@ -2968,7 +3064,7 @@ var errMsg13 = (e) => e instanceof Error ? e.message : String(e);
|
|
|
2968
3064
|
function reasonMessage(res, query) {
|
|
2969
3065
|
switch (res.reason) {
|
|
2970
3066
|
case "no-renders":
|
|
2971
|
-
return "No components
|
|
3067
|
+
return "No components matched via highlight tracking or the live fiber walk. Pass a testID / name from describe_screen, then retry.";
|
|
2972
3068
|
case "no-match":
|
|
2973
3069
|
return query ? `Nothing matched "${query}".` : "No query or nativeTag given \u2014 pass one, or a testID/name to search for.";
|
|
2974
3070
|
case "no-measurement":
|
|
@@ -2992,7 +3088,7 @@ function registerScreenshotTools(server, sync) {
|
|
|
2992
3088
|
server.registerTool(
|
|
2993
3089
|
"screenshot_component",
|
|
2994
3090
|
{
|
|
2995
|
-
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.
|
|
3091
|
+
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.",
|
|
2996
3092
|
inputSchema: {
|
|
2997
3093
|
query: import_zod13.z.string().optional().describe('Fuzzy match against testID, nativeID, accessibilityLabel, component name, or view type \u2014 e.g. "submit-button".'),
|
|
2998
3094
|
nativeTag: import_zod13.z.number().optional().describe("An exact nativeTag (from a previous call's candidates) to skip fuzzy matching."),
|
|
@@ -3016,15 +3112,37 @@ function registerScreenshotTools(server, sync) {
|
|
|
3016
3112
|
},
|
|
3017
3113
|
target
|
|
3018
3114
|
);
|
|
3019
|
-
|
|
3020
|
-
|
|
3115
|
+
let located = res;
|
|
3116
|
+
if (!located.matched && (located.reason === "no-renders" || located.reason === "no-match")) {
|
|
3117
|
+
const screen = await sync.callAction(TOOL_ID, "describeScreen", {}, target).catch(() => null);
|
|
3118
|
+
const match = screen?.elements ? pickDescribeScreenMatch(screen.elements, args.query, args.nativeTag) : void 0;
|
|
3119
|
+
if (match?.frame && screen?.screen) {
|
|
3120
|
+
located = {
|
|
3121
|
+
matched: true,
|
|
3122
|
+
live: true,
|
|
3123
|
+
rect: frameToRect(match.frame, screen.screen),
|
|
3124
|
+
screen: screen.screen,
|
|
3125
|
+
nativeTag: match.nativeTag ?? void 0,
|
|
3126
|
+
componentName: match.name,
|
|
3127
|
+
testID: match.testID
|
|
3128
|
+
};
|
|
3129
|
+
} else if (located.reason === "no-renders") {
|
|
3130
|
+
located = {
|
|
3131
|
+
matched: false,
|
|
3132
|
+
reason: "no-match",
|
|
3133
|
+
candidates: located.candidates
|
|
3134
|
+
};
|
|
3135
|
+
}
|
|
3136
|
+
}
|
|
3137
|
+
if (!located.matched || !located.rect || !located.screen) {
|
|
3138
|
+
return fail13(reasonMessage(located, args.query) + candidatesText(located.candidates));
|
|
3021
3139
|
}
|
|
3022
3140
|
const shot = await captureIosFramebuffer();
|
|
3023
3141
|
if (!shot.ok || !shot.srcPath || !shot.width || !shot.height) {
|
|
3024
3142
|
return fail13(shot.error ?? "Screenshot capture failed.");
|
|
3025
3143
|
}
|
|
3026
|
-
const pxScale = shot.width /
|
|
3027
|
-
const r =
|
|
3144
|
+
const pxScale = shot.width / located.screen.width;
|
|
3145
|
+
const r = located.rect;
|
|
3028
3146
|
const pad = CAPTURE_PADDING_PT;
|
|
3029
3147
|
const crop = await cropToBuffer(
|
|
3030
3148
|
shot.srcPath,
|
|
@@ -3039,9 +3157,9 @@ function registerScreenshotTools(server, sync) {
|
|
|
3039
3157
|
if (!crop.ok || !crop.buffer) {
|
|
3040
3158
|
return fail13(crop.error ?? "Crop failed.");
|
|
3041
3159
|
}
|
|
3042
|
-
const label =
|
|
3043
|
-
const idPart =
|
|
3044
|
-
const warn =
|
|
3160
|
+
const label = located.componentName || "component";
|
|
3161
|
+
const idPart = located.testID ? ` (testID: "${located.testID}")` : "";
|
|
3162
|
+
const warn = located.inView === false ? "\n\nNote: the component is partly off-screen even after scrolling, so the crop is clamped to the screen edge." : "";
|
|
3045
3163
|
const summary = `Captured "${label}"${idPart} \u2014 ${crop.width}\xD7${crop.height}px.${warn}`;
|
|
3046
3164
|
return {
|
|
3047
3165
|
content: [
|
|
@@ -3605,6 +3723,19 @@ ${JSON.stringify(slim)}
|
|
|
3605
3723
|
|
|
3606
3724
|
// src/tools/packs/assets.ts
|
|
3607
3725
|
var import_zod17 = require("zod");
|
|
3726
|
+
|
|
3727
|
+
// src/tools/packs/assetsGraphNote.ts
|
|
3728
|
+
function graphUnavailableNote(ss) {
|
|
3729
|
+
if (ss.graphSupported && ss.graphCount != null) return null;
|
|
3730
|
+
const err = (ss.graphError ?? "").trim();
|
|
3731
|
+
const looksLikeDev = ss.dev === true || !!err;
|
|
3732
|
+
if (looksLikeDev) {
|
|
3733
|
+
return err ? `\u26A0 Metro bundle graph unavailable (${err}) \u2014 showing runtime-registered assets only; never-loaded detection unavailable.` : "\u26A0 Metro bundle graph unavailable \u2014 showing runtime-registered assets only; never-loaded detection unavailable.";
|
|
3734
|
+
}
|
|
3735
|
+
return "\u26A0 No Metro bundle graph (release build, or graph fetch failed) \u2014 showing runtime-registered assets only; never-loaded detection unavailable.";
|
|
3736
|
+
}
|
|
3737
|
+
|
|
3738
|
+
// src/tools/packs/assets.ts
|
|
3608
3739
|
var ASSETS_TOOL_ID = "assets";
|
|
3609
3740
|
var text15 = (s) => ({ content: [{ type: "text", text: s }] });
|
|
3610
3741
|
var fail17 = (message) => ({ isError: true, content: [{ type: "text", text: message }] });
|
|
@@ -3647,11 +3778,8 @@ function formatListMarkdown2(list, limit) {
|
|
|
3647
3778
|
if (ss.fontFamilies.length > 0) {
|
|
3648
3779
|
lines.push(`Loaded font families: ${ss.fontFamilies.join(", ")}`);
|
|
3649
3780
|
}
|
|
3650
|
-
|
|
3651
|
-
|
|
3652
|
-
"\u26A0 No Metro bundle graph (release build or graph fetch failed) \u2014 showing runtime-registered assets only; never-loaded detection unavailable." + (ss.graphError ? ` (${ss.graphError})` : "")
|
|
3653
|
-
);
|
|
3654
|
-
}
|
|
3781
|
+
const graphNote = graphUnavailableNote(ss);
|
|
3782
|
+
if (graphNote) lines.push(graphNote);
|
|
3655
3783
|
if (ss.measuring) lines.push("\u26A0 Size measurement in progress \u2014 byte totals are partial; re-run get_assets shortly.");
|
|
3656
3784
|
const ins = list.insights;
|
|
3657
3785
|
const chips = [];
|
|
@@ -3940,16 +4068,14 @@ function registerReloadTools(server, sync) {
|
|
|
3940
4068
|
const previousRealm = args.wait === false ? null : await ping(sync, target, PROBE_TIMEOUT_MS);
|
|
3941
4069
|
let ack = null;
|
|
3942
4070
|
let recovered = false;
|
|
3943
|
-
|
|
3944
|
-
ack = await sync.callAction(
|
|
3945
|
-
APP_TOOL_ID,
|
|
3946
|
-
RELOAD_ACTION,
|
|
3947
|
-
{ strategy: args.strategy ?? "auto" },
|
|
3948
|
-
target
|
|
3949
|
-
);
|
|
3950
|
-
} catch (inAppError) {
|
|
4071
|
+
const tryDevServer = async (inAppError) => {
|
|
3951
4072
|
const origin = sync.devServerOrigin(target);
|
|
3952
|
-
if (!origin)
|
|
4073
|
+
if (!origin) {
|
|
4074
|
+
if (inAppError) throw inAppError;
|
|
4075
|
+
throw new Error(
|
|
4076
|
+
"The app is not answering and no dev-server origin is known (it was never learned from a successful ping). Reload the app by hand."
|
|
4077
|
+
);
|
|
4078
|
+
}
|
|
3953
4079
|
const res = await reloadViaDevServer(origin);
|
|
3954
4080
|
if (!res.ok) {
|
|
3955
4081
|
throw new Error(
|
|
@@ -3957,6 +4083,20 @@ function registerReloadTools(server, sync) {
|
|
|
3957
4083
|
);
|
|
3958
4084
|
}
|
|
3959
4085
|
recovered = true;
|
|
4086
|
+
};
|
|
4087
|
+
if (sync.isUnresponsive(target) && sync.devServerOrigin(target)) {
|
|
4088
|
+
await tryDevServer();
|
|
4089
|
+
} else {
|
|
4090
|
+
try {
|
|
4091
|
+
ack = await sync.callAction(
|
|
4092
|
+
APP_TOOL_ID,
|
|
4093
|
+
RELOAD_ACTION,
|
|
4094
|
+
{ strategy: args.strategy ?? "auto" },
|
|
4095
|
+
target
|
|
4096
|
+
);
|
|
4097
|
+
} catch (inAppError) {
|
|
4098
|
+
await tryDevServer(inAppError);
|
|
4099
|
+
}
|
|
3960
4100
|
}
|
|
3961
4101
|
const via = recovered ? ` via the dev server (the app was unresponsive \u2014 its own reload could not be reached)` : ack?.strategy ? ` via ${ack.strategy}` : "";
|
|
3962
4102
|
if (args.wait === false) {
|
|
@@ -4966,9 +5106,8 @@ function runInit(opts = {}) {
|
|
|
4966
5106
|
const entry = resolveEntry(opts, log);
|
|
4967
5107
|
const done = [];
|
|
4968
5108
|
upsertMcpConfig(import_node_path4.default.join(cwd, ".mcp.json"), "mcpServers", entry, cwd, done);
|
|
4969
|
-
|
|
4970
|
-
|
|
4971
|
-
}
|
|
5109
|
+
import_node_fs4.default.mkdirSync(import_node_path4.default.join(cwd, ".cursor"), { recursive: true });
|
|
5110
|
+
upsertMcpConfig(import_node_path4.default.join(cwd, ".cursor", "mcp.json"), "mcpServers", entry, cwd, done);
|
|
4972
5111
|
if (import_node_fs4.default.existsSync(import_node_path4.default.join(cwd, ".vscode"))) {
|
|
4973
5112
|
upsertMcpConfig(import_node_path4.default.join(cwd, ".vscode", "mcp.json"), "servers", entry, cwd, done);
|
|
4974
5113
|
}
|
package/dist/index.mjs
CHANGED
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@buoy-gg/mcp",
|
|
3
3
|
"mcpName": "io.github.buoy-gg/mcp",
|
|
4
|
-
"version": "7.0.
|
|
4
|
+
"version": "7.0.29",
|
|
5
5
|
"description": "MCP server for Buoy devtools — lets any LLM copilot drive Buoy tools (perf-monitor benchmarks, etc.) over the devtool sync protocol",
|
|
6
6
|
"main": "dist/index.js",
|
|
7
7
|
"module": "dist/index.mjs",
|
|
@@ -32,7 +32,7 @@
|
|
|
32
32
|
"socket.io-client": "^4.8.1",
|
|
33
33
|
"zod": "^3.25.0",
|
|
34
34
|
"ws": "^8.18.0",
|
|
35
|
-
"@buoy-gg/sync-broker": "^7.0.
|
|
35
|
+
"@buoy-gg/sync-broker": "^7.0.29"
|
|
36
36
|
},
|
|
37
37
|
"devDependencies": {
|
|
38
38
|
"@types/node": "^22.0.0",
|