@buoy-gg/mcp 7.0.26 → 7.0.28
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-NPMUQLCZ.mjs → chunk-SFG6ZYH6.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
|
@@ -19,6 +19,35 @@ import { io as ioClient } from "socket.io-client";
|
|
|
19
19
|
import {
|
|
20
20
|
createBroker
|
|
21
21
|
} from "@buoy-gg/sync-broker";
|
|
22
|
+
|
|
23
|
+
// src/sync/actionTimeout.ts
|
|
24
|
+
function isRecoveryAction(toolId, action) {
|
|
25
|
+
return toolId === "app" && (action === "ping" || action === "reloadApp");
|
|
26
|
+
}
|
|
27
|
+
function isUnresponsive(consecutiveTimeouts) {
|
|
28
|
+
return consecutiveTimeouts >= 2;
|
|
29
|
+
}
|
|
30
|
+
function actionTimeoutMs(input) {
|
|
31
|
+
if (isRecoveryAction(input.toolId, input.action)) {
|
|
32
|
+
return { timeoutMs: input.fullMs, usedFullBudget: false };
|
|
33
|
+
}
|
|
34
|
+
if (!isUnresponsive(input.consecutiveTimeouts)) {
|
|
35
|
+
return { timeoutMs: input.fullMs, usedFullBudget: false };
|
|
36
|
+
}
|
|
37
|
+
if (input.now - input.lastFullBudgetAt > input.recoveryProbeMs) {
|
|
38
|
+
return { timeoutMs: input.fullMs, usedFullBudget: true };
|
|
39
|
+
}
|
|
40
|
+
return { timeoutMs: input.shortMs, usedFullBudget: false };
|
|
41
|
+
}
|
|
42
|
+
function diagnoseCrashed(opts) {
|
|
43
|
+
if (opts.consecutiveTimeouts >= 2) return true;
|
|
44
|
+
if (opts.lastOkAt > 0 && opts.now - opts.lastOkAt > opts.unresponsiveAfterMs) {
|
|
45
|
+
return true;
|
|
46
|
+
}
|
|
47
|
+
return false;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
// src/sync/SyncClient.ts
|
|
22
51
|
var SyncClient = class {
|
|
23
52
|
constructor(opts = {}) {
|
|
24
53
|
this.socket = null;
|
|
@@ -286,27 +315,34 @@ var SyncClient = class {
|
|
|
286
315
|
noteTimeout(deviceId) {
|
|
287
316
|
this.healthOf(deviceId).consecutiveTimeouts += 1;
|
|
288
317
|
}
|
|
289
|
-
/** True once a device has missed
|
|
318
|
+
/** True once a device has missed TWO calls and hasn't answered since. */
|
|
290
319
|
isUnresponsive(deviceId) {
|
|
291
|
-
return this.healthOf(deviceId).consecutiveTimeouts
|
|
320
|
+
return isUnresponsive(this.healthOf(deviceId).consecutiveTimeouts);
|
|
292
321
|
}
|
|
293
322
|
/**
|
|
294
323
|
* Full timeout normally; a short one for a device already not answering.
|
|
295
324
|
*
|
|
296
|
-
*
|
|
297
|
-
*
|
|
298
|
-
*
|
|
299
|
-
*
|
|
300
|
-
*
|
|
325
|
+
* A single missed call does not trigger the short budget — one slow
|
|
326
|
+
* `describeScreen` is not a crash. `app.ping` / `app.reloadApp` always get
|
|
327
|
+
* the full budget (they are the recovery path).
|
|
328
|
+
*
|
|
329
|
+
* Escape hatch: every RECOVERY_PROBE_MS a flagged device gets the full
|
|
330
|
+
* budget again so the short timeout cannot become self-sustaining.
|
|
301
331
|
*/
|
|
302
|
-
timeoutFor(deviceId) {
|
|
303
|
-
if (!this.isUnresponsive(deviceId)) return this.actionTimeoutMs;
|
|
332
|
+
timeoutFor(deviceId, toolId, action) {
|
|
304
333
|
const h = this.healthOf(deviceId);
|
|
305
|
-
|
|
306
|
-
h.
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
334
|
+
const { timeoutMs, usedFullBudget } = actionTimeoutMs({
|
|
335
|
+
consecutiveTimeouts: h.consecutiveTimeouts,
|
|
336
|
+
lastFullBudgetAt: h.lastFullBudgetAt,
|
|
337
|
+
now: Date.now(),
|
|
338
|
+
fullMs: this.actionTimeoutMs,
|
|
339
|
+
shortMs: UNRESPONSIVE_ACTION_TIMEOUT_MS,
|
|
340
|
+
recoveryProbeMs: RECOVERY_PROBE_MS,
|
|
341
|
+
toolId,
|
|
342
|
+
action
|
|
343
|
+
});
|
|
344
|
+
if (usedFullBudget) h.lastFullBudgetAt = Date.now();
|
|
345
|
+
return timeoutMs;
|
|
310
346
|
}
|
|
311
347
|
/**
|
|
312
348
|
* Plain-language diagnosis for a device that isn't answering, including the
|
|
@@ -319,7 +355,12 @@ var SyncClient = class {
|
|
|
319
355
|
unresponsiveNote(deviceId) {
|
|
320
356
|
const h = this.healthOf(deviceId);
|
|
321
357
|
const since = h.lastOkAt ? Math.round((Date.now() - h.lastOkAt) / 1e3) : null;
|
|
322
|
-
const crashed =
|
|
358
|
+
const crashed = diagnoseCrashed({
|
|
359
|
+
consecutiveTimeouts: h.consecutiveTimeouts,
|
|
360
|
+
lastOkAt: h.lastOkAt,
|
|
361
|
+
now: Date.now(),
|
|
362
|
+
unresponsiveAfterMs: UNRESPONSIVE_AFTER_MS
|
|
363
|
+
});
|
|
323
364
|
const lines = [
|
|
324
365
|
`The app on "${deviceId}" is not responding (${h.consecutiveTimeouts} call${h.consecutiveTimeouts === 1 ? "" : "s"} unanswered${since != null ? `, last answered ${since}s ago` : ""}).`,
|
|
325
366
|
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."
|
|
@@ -521,7 +562,7 @@ var SyncClient = class {
|
|
|
521
562
|
const target = deviceId === ALL_DEVICES ? ALL_DEVICES : this.resolveDevice(deviceId);
|
|
522
563
|
if (!this.socket) throw new Error("SyncClient is not connected");
|
|
523
564
|
const requestId = randomUUID();
|
|
524
|
-
const timeoutMs = opts.timeoutMs ?? this.timeoutFor(target);
|
|
565
|
+
const timeoutMs = opts.timeoutMs ?? this.timeoutFor(target, toolId, action);
|
|
525
566
|
const result = await new Promise((resolve, reject) => {
|
|
526
567
|
const timer = setTimeout(() => {
|
|
527
568
|
this.pendingActions.delete(requestId);
|
|
@@ -572,7 +613,7 @@ async function waitForBroker(url, attempts = 20) {
|
|
|
572
613
|
var delay = (ms) => new Promise((r) => setTimeout(r, ms));
|
|
573
614
|
|
|
574
615
|
// src/version.ts
|
|
575
|
-
var VERSION = "7.0.
|
|
616
|
+
var VERSION = "7.0.28" ? "7.0.28" : "0.0.0-dev";
|
|
576
617
|
var PACKAGE_NAME = "@buoy-gg/mcp";
|
|
577
618
|
|
|
578
619
|
// src/update/checkForUpdate.ts
|
|
@@ -1575,10 +1616,20 @@ _Showing ${res.returned} of ${res.totalAvailable} captured event(s)${more}._`;
|
|
|
1575
1616
|
|
|
1576
1617
|
// src/tools/packs/reactQuery.ts
|
|
1577
1618
|
import { z as z4 } from "zod";
|
|
1578
|
-
var RQ_TOOL_ID = "
|
|
1619
|
+
var RQ_TOOL_ID = "query";
|
|
1620
|
+
var RQ_TOOL_ID_LEGACY = "react-query";
|
|
1579
1621
|
var text3 = (s) => ({ content: [{ type: "text", text: s }] });
|
|
1580
1622
|
var fail4 = (message) => ({ isError: true, content: [{ type: "text", text: message }] });
|
|
1581
1623
|
var errMsg4 = (e) => e instanceof Error ? e.message : String(e);
|
|
1624
|
+
async function callQueryAction(sync, action, params, target) {
|
|
1625
|
+
try {
|
|
1626
|
+
return await sync.callAction(RQ_TOOL_ID, action, params, target);
|
|
1627
|
+
} catch (e) {
|
|
1628
|
+
const msg = errMsg4(e);
|
|
1629
|
+
if (!/unknown tool/i.test(msg)) throw e;
|
|
1630
|
+
return await sync.callAction(RQ_TOOL_ID_LEGACY, action, params, target);
|
|
1631
|
+
}
|
|
1632
|
+
}
|
|
1582
1633
|
var STATUS_ICON = {
|
|
1583
1634
|
fresh: "\u{1F7E2}",
|
|
1584
1635
|
stale: "\u{1F7E1}",
|
|
@@ -1611,8 +1662,8 @@ function registerReactQueryTools(server, sync) {
|
|
|
1611
1662
|
async (args) => {
|
|
1612
1663
|
try {
|
|
1613
1664
|
const target = sync.requireProDevice(args.deviceId);
|
|
1614
|
-
const res = await
|
|
1615
|
-
|
|
1665
|
+
const res = await callQueryAction(
|
|
1666
|
+
sync,
|
|
1616
1667
|
"listQueries",
|
|
1617
1668
|
{ includeData: args.includeData ?? false, staleOnly: args.staleOnly ?? false, limit: args.limit ?? 25 },
|
|
1618
1669
|
target
|
|
@@ -1658,13 +1709,13 @@ ${lines.join("\n")}${note}`);
|
|
|
1658
1709
|
try {
|
|
1659
1710
|
const target = sync.requireProDevice(args.deviceId);
|
|
1660
1711
|
if (args.action === "clear") {
|
|
1661
|
-
await sync
|
|
1712
|
+
await callQueryAction(sync, "clearQueryCache", {}, target);
|
|
1662
1713
|
return text3("Cleared the entire React Query cache.");
|
|
1663
1714
|
}
|
|
1664
1715
|
if (!args.queryHash) {
|
|
1665
1716
|
return fail4(`'${args.action}' needs a queryHash. Call get_react_query first and use a row's hash (in backticks).`);
|
|
1666
1717
|
}
|
|
1667
|
-
await sync
|
|
1718
|
+
await callQueryAction(sync, args.action, { queryHash: args.queryHash }, target);
|
|
1668
1719
|
return text3(`Ran ${args.action} on query \`${args.queryHash}\`.`);
|
|
1669
1720
|
} catch (e) {
|
|
1670
1721
|
return fail4(errMsg4(e));
|
|
@@ -2091,17 +2142,23 @@ ${shown.map((r) => `- \`${r}\``).join("\n")}`
|
|
|
2091
2142
|
server.registerTool(
|
|
2092
2143
|
"navigate",
|
|
2093
2144
|
{
|
|
2094
|
-
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.",
|
|
2145
|
+
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 '/').",
|
|
2095
2146
|
inputSchema: {
|
|
2096
2147
|
path: z9.string().describe("The concrete path to navigate to, e.g. '/pokemon/25'."),
|
|
2148
|
+
replace: z9.boolean().optional().describe("Replace the current screen instead of pushing (use when resetting, e.g. to '/')."),
|
|
2097
2149
|
deviceId: z9.string().optional().describe("Target device id (auto-selected if one is connected).")
|
|
2098
2150
|
}
|
|
2099
2151
|
},
|
|
2100
2152
|
async (args) => {
|
|
2101
2153
|
try {
|
|
2102
2154
|
const target = sync.requireProDevice(args.deviceId);
|
|
2103
|
-
await sync.callAction(
|
|
2104
|
-
|
|
2155
|
+
await sync.callAction(
|
|
2156
|
+
ROUTES_TOOL_ID,
|
|
2157
|
+
"navigate",
|
|
2158
|
+
{ path: args.path, replace: args.replace === true },
|
|
2159
|
+
target
|
|
2160
|
+
);
|
|
2161
|
+
return text8(`Navigated to \`${args.path}\`${args.replace ? " (replaced current screen)" : ""}.`);
|
|
2105
2162
|
} catch (e) {
|
|
2106
2163
|
return fail9(errMsg9(e));
|
|
2107
2164
|
}
|
|
@@ -2248,6 +2305,40 @@ async function cropToBuffer(srcPath, full, region) {
|
|
|
2248
2305
|
}
|
|
2249
2306
|
}
|
|
2250
2307
|
|
|
2308
|
+
// src/tools/packs/screenshotLocate.ts
|
|
2309
|
+
function pickDescribeScreenMatch(elements, query, nativeTag) {
|
|
2310
|
+
if (nativeTag != null) {
|
|
2311
|
+
const exact = elements.find((e) => e.nativeTag === nativeTag);
|
|
2312
|
+
if (exact) return exact;
|
|
2313
|
+
}
|
|
2314
|
+
const q = (query ?? "").trim().toLowerCase();
|
|
2315
|
+
if (!q) return void 0;
|
|
2316
|
+
let best;
|
|
2317
|
+
for (const el of elements) {
|
|
2318
|
+
const fields = [el.testID, el.label, el.text, el.name].filter(Boolean).map((s) => s.toLowerCase());
|
|
2319
|
+
let score = 0;
|
|
2320
|
+
for (const f of fields) {
|
|
2321
|
+
if (f === q) score = Math.max(score, 100);
|
|
2322
|
+
else if (f.startsWith(q)) score = Math.max(score, 50);
|
|
2323
|
+
else if (f.includes(q)) score = Math.max(score, 25);
|
|
2324
|
+
}
|
|
2325
|
+
const tap = el.tap;
|
|
2326
|
+
if (tap && tap.x >= 0 && tap.x <= 1 && tap.y >= 0 && tap.y <= 1) score += 20;
|
|
2327
|
+
else if (tap) score -= 40;
|
|
2328
|
+
if (score <= 0) continue;
|
|
2329
|
+
if (!best || score > best.score) best = { el, score };
|
|
2330
|
+
}
|
|
2331
|
+
return best?.el;
|
|
2332
|
+
}
|
|
2333
|
+
function frameToRect(frame, screen) {
|
|
2334
|
+
return {
|
|
2335
|
+
x: frame.x * screen.width,
|
|
2336
|
+
y: frame.y * screen.height,
|
|
2337
|
+
width: frame.width * screen.width,
|
|
2338
|
+
height: frame.height * screen.height
|
|
2339
|
+
};
|
|
2340
|
+
}
|
|
2341
|
+
|
|
2251
2342
|
// src/tools/packs/screenshot.ts
|
|
2252
2343
|
var TOOL_ID = "highlight-updates";
|
|
2253
2344
|
var MARGIN_PT = 12;
|
|
@@ -2257,7 +2348,7 @@ var errMsg11 = (e) => e instanceof Error ? e.message : String(e);
|
|
|
2257
2348
|
function reasonMessage(res, query) {
|
|
2258
2349
|
switch (res.reason) {
|
|
2259
2350
|
case "no-renders":
|
|
2260
|
-
return "No components
|
|
2351
|
+
return "No components matched via highlight tracking or the live fiber walk. Pass a testID / name from describe_screen, then retry.";
|
|
2261
2352
|
case "no-match":
|
|
2262
2353
|
return query ? `Nothing matched "${query}".` : "No query or nativeTag given \u2014 pass one, or a testID/name to search for.";
|
|
2263
2354
|
case "no-measurement":
|
|
@@ -2281,7 +2372,7 @@ function registerScreenshotTools(server, sync) {
|
|
|
2281
2372
|
server.registerTool(
|
|
2282
2373
|
"screenshot_component",
|
|
2283
2374
|
{
|
|
2284
|
-
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.
|
|
2375
|
+
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.",
|
|
2285
2376
|
inputSchema: {
|
|
2286
2377
|
query: z11.string().optional().describe('Fuzzy match against testID, nativeID, accessibilityLabel, component name, or view type \u2014 e.g. "submit-button".'),
|
|
2287
2378
|
nativeTag: z11.number().optional().describe("An exact nativeTag (from a previous call's candidates) to skip fuzzy matching."),
|
|
@@ -2305,15 +2396,37 @@ function registerScreenshotTools(server, sync) {
|
|
|
2305
2396
|
},
|
|
2306
2397
|
target
|
|
2307
2398
|
);
|
|
2308
|
-
|
|
2309
|
-
|
|
2399
|
+
let located = res;
|
|
2400
|
+
if (!located.matched && (located.reason === "no-renders" || located.reason === "no-match")) {
|
|
2401
|
+
const screen = await sync.callAction(TOOL_ID, "describeScreen", {}, target).catch(() => null);
|
|
2402
|
+
const match = screen?.elements ? pickDescribeScreenMatch(screen.elements, args.query, args.nativeTag) : void 0;
|
|
2403
|
+
if (match?.frame && screen?.screen) {
|
|
2404
|
+
located = {
|
|
2405
|
+
matched: true,
|
|
2406
|
+
live: true,
|
|
2407
|
+
rect: frameToRect(match.frame, screen.screen),
|
|
2408
|
+
screen: screen.screen,
|
|
2409
|
+
nativeTag: match.nativeTag ?? void 0,
|
|
2410
|
+
componentName: match.name,
|
|
2411
|
+
testID: match.testID
|
|
2412
|
+
};
|
|
2413
|
+
} else if (located.reason === "no-renders") {
|
|
2414
|
+
located = {
|
|
2415
|
+
matched: false,
|
|
2416
|
+
reason: "no-match",
|
|
2417
|
+
candidates: located.candidates
|
|
2418
|
+
};
|
|
2419
|
+
}
|
|
2420
|
+
}
|
|
2421
|
+
if (!located.matched || !located.rect || !located.screen) {
|
|
2422
|
+
return fail11(reasonMessage(located, args.query) + candidatesText(located.candidates));
|
|
2310
2423
|
}
|
|
2311
2424
|
const shot = await captureIosFramebuffer();
|
|
2312
2425
|
if (!shot.ok || !shot.srcPath || !shot.width || !shot.height) {
|
|
2313
2426
|
return fail11(shot.error ?? "Screenshot capture failed.");
|
|
2314
2427
|
}
|
|
2315
|
-
const pxScale = shot.width /
|
|
2316
|
-
const r =
|
|
2428
|
+
const pxScale = shot.width / located.screen.width;
|
|
2429
|
+
const r = located.rect;
|
|
2317
2430
|
const pad = CAPTURE_PADDING_PT;
|
|
2318
2431
|
const crop = await cropToBuffer(
|
|
2319
2432
|
shot.srcPath,
|
|
@@ -2328,9 +2441,9 @@ function registerScreenshotTools(server, sync) {
|
|
|
2328
2441
|
if (!crop.ok || !crop.buffer) {
|
|
2329
2442
|
return fail11(crop.error ?? "Crop failed.");
|
|
2330
2443
|
}
|
|
2331
|
-
const label =
|
|
2332
|
-
const idPart =
|
|
2333
|
-
const warn =
|
|
2444
|
+
const label = located.componentName || "component";
|
|
2445
|
+
const idPart = located.testID ? ` (testID: "${located.testID}")` : "";
|
|
2446
|
+
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." : "";
|
|
2334
2447
|
const summary = `Captured "${label}"${idPart} \u2014 ${crop.width}\xD7${crop.height}px.${warn}`;
|
|
2335
2448
|
return {
|
|
2336
2449
|
content: [
|
|
@@ -2378,7 +2491,14 @@ function statusIcon(e) {
|
|
|
2378
2491
|
return "\u2705";
|
|
2379
2492
|
}
|
|
2380
2493
|
function statusLabel(e) {
|
|
2381
|
-
if (e.
|
|
2494
|
+
if (e.status !== void 0 && e.status > 0) return String(e.status);
|
|
2495
|
+
if (e.error) {
|
|
2496
|
+
const cleaned = e.error.replace(/^HTTP\s+\d+:\s*/i, "").trim();
|
|
2497
|
+
if (!cleaned || cleaned === "undefined" || cleaned === "no error") {
|
|
2498
|
+
return "ERR";
|
|
2499
|
+
}
|
|
2500
|
+
return `ERR ${e.error}`;
|
|
2501
|
+
}
|
|
2382
2502
|
if (e.status === void 0) return "pending";
|
|
2383
2503
|
return String(e.status);
|
|
2384
2504
|
}
|
|
@@ -3538,6 +3658,19 @@ ${JSON.stringify(slim)}
|
|
|
3538
3658
|
|
|
3539
3659
|
// src/tools/packs/assets.ts
|
|
3540
3660
|
import { z as z17 } from "zod";
|
|
3661
|
+
|
|
3662
|
+
// src/tools/packs/assetsGraphNote.ts
|
|
3663
|
+
function graphUnavailableNote(ss) {
|
|
3664
|
+
if (ss.graphSupported && ss.graphCount != null) return null;
|
|
3665
|
+
const err = (ss.graphError ?? "").trim();
|
|
3666
|
+
const looksLikeDev = ss.dev === true || !!err;
|
|
3667
|
+
if (looksLikeDev) {
|
|
3668
|
+
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.";
|
|
3669
|
+
}
|
|
3670
|
+
return "\u26A0 No Metro bundle graph (release build, or graph fetch failed) \u2014 showing runtime-registered assets only; never-loaded detection unavailable.";
|
|
3671
|
+
}
|
|
3672
|
+
|
|
3673
|
+
// src/tools/packs/assets.ts
|
|
3541
3674
|
var ASSETS_TOOL_ID = "assets";
|
|
3542
3675
|
var text15 = (s) => ({ content: [{ type: "text", text: s }] });
|
|
3543
3676
|
var fail17 = (message) => ({ isError: true, content: [{ type: "text", text: message }] });
|
|
@@ -3580,11 +3713,8 @@ function formatListMarkdown2(list, limit) {
|
|
|
3580
3713
|
if (ss.fontFamilies.length > 0) {
|
|
3581
3714
|
lines.push(`Loaded font families: ${ss.fontFamilies.join(", ")}`);
|
|
3582
3715
|
}
|
|
3583
|
-
|
|
3584
|
-
|
|
3585
|
-
"\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})` : "")
|
|
3586
|
-
);
|
|
3587
|
-
}
|
|
3716
|
+
const graphNote = graphUnavailableNote(ss);
|
|
3717
|
+
if (graphNote) lines.push(graphNote);
|
|
3588
3718
|
if (ss.measuring) lines.push("\u26A0 Size measurement in progress \u2014 byte totals are partial; re-run get_assets shortly.");
|
|
3589
3719
|
const ins = list.insights;
|
|
3590
3720
|
const chips = [];
|
|
@@ -3873,16 +4003,14 @@ function registerReloadTools(server, sync) {
|
|
|
3873
4003
|
const previousRealm = args.wait === false ? null : await ping(sync, target, PROBE_TIMEOUT_MS);
|
|
3874
4004
|
let ack = null;
|
|
3875
4005
|
let recovered = false;
|
|
3876
|
-
|
|
3877
|
-
ack = await sync.callAction(
|
|
3878
|
-
APP_TOOL_ID,
|
|
3879
|
-
RELOAD_ACTION,
|
|
3880
|
-
{ strategy: args.strategy ?? "auto" },
|
|
3881
|
-
target
|
|
3882
|
-
);
|
|
3883
|
-
} catch (inAppError) {
|
|
4006
|
+
const tryDevServer = async (inAppError) => {
|
|
3884
4007
|
const origin = sync.devServerOrigin(target);
|
|
3885
|
-
if (!origin)
|
|
4008
|
+
if (!origin) {
|
|
4009
|
+
if (inAppError) throw inAppError;
|
|
4010
|
+
throw new Error(
|
|
4011
|
+
"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."
|
|
4012
|
+
);
|
|
4013
|
+
}
|
|
3886
4014
|
const res = await reloadViaDevServer(origin);
|
|
3887
4015
|
if (!res.ok) {
|
|
3888
4016
|
throw new Error(
|
|
@@ -3890,6 +4018,20 @@ function registerReloadTools(server, sync) {
|
|
|
3890
4018
|
);
|
|
3891
4019
|
}
|
|
3892
4020
|
recovered = true;
|
|
4021
|
+
};
|
|
4022
|
+
if (sync.isUnresponsive(target) && sync.devServerOrigin(target)) {
|
|
4023
|
+
await tryDevServer();
|
|
4024
|
+
} else {
|
|
4025
|
+
try {
|
|
4026
|
+
ack = await sync.callAction(
|
|
4027
|
+
APP_TOOL_ID,
|
|
4028
|
+
RELOAD_ACTION,
|
|
4029
|
+
{ strategy: args.strategy ?? "auto" },
|
|
4030
|
+
target
|
|
4031
|
+
);
|
|
4032
|
+
} catch (inAppError) {
|
|
4033
|
+
await tryDevServer(inAppError);
|
|
4034
|
+
}
|
|
3893
4035
|
}
|
|
3894
4036
|
const via = recovered ? ` via the dev server (the app was unresponsive \u2014 its own reload could not be reached)` : ack?.strategy ? ` via ${ack.strategy}` : "";
|
|
3895
4037
|
if (args.wait === false) {
|
|
@@ -4899,9 +5041,8 @@ function runInit(opts = {}) {
|
|
|
4899
5041
|
const entry = resolveEntry(opts, log);
|
|
4900
5042
|
const done = [];
|
|
4901
5043
|
upsertMcpConfig(path4.join(cwd, ".mcp.json"), "mcpServers", entry, cwd, done);
|
|
4902
|
-
|
|
4903
|
-
|
|
4904
|
-
}
|
|
5044
|
+
fs4.mkdirSync(path4.join(cwd, ".cursor"), { recursive: true });
|
|
5045
|
+
upsertMcpConfig(path4.join(cwd, ".cursor", "mcp.json"), "mcpServers", entry, cwd, done);
|
|
4905
5046
|
if (fs4.existsSync(path4.join(cwd, ".vscode"))) {
|
|
4906
5047
|
upsertMcpConfig(path4.join(cwd, ".vscode", "mcp.json"), "servers", entry, cwd, done);
|
|
4907
5048
|
}
|