@buoy-gg/mcp 4.0.1
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/LICENSE +58 -0
- package/dist/chunk-55UIOHVS.mjs +1145 -0
- package/dist/cli.js +1267 -0
- package/dist/cli.mjs +118 -0
- package/dist/index.d.mts +379 -0
- package/dist/index.d.ts +379 -0
- package/dist/index.js +1197 -0
- package/dist/index.mjs +34 -0
- package/package.json +60 -0
- package/skill/buoy-optimize/SKILL.md +95 -0
package/dist/cli.mjs
ADDED
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import {
|
|
3
|
+
SyncClient,
|
|
4
|
+
createMcpServer
|
|
5
|
+
} from "./chunk-55UIOHVS.mjs";
|
|
6
|
+
|
|
7
|
+
// src/cli.ts
|
|
8
|
+
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
|
|
9
|
+
|
|
10
|
+
// src/init.ts
|
|
11
|
+
import fs from "fs";
|
|
12
|
+
import path from "path";
|
|
13
|
+
function runInit(opts = {}) {
|
|
14
|
+
const cwd = opts.cwd ?? process.cwd();
|
|
15
|
+
const cliPath = opts.cliPath ?? path.resolve(__dirname, "cli.js");
|
|
16
|
+
const skillSrcDir = opts.skillSrcDir ?? path.resolve(__dirname, "../skill");
|
|
17
|
+
const log = opts.log ?? ((m) => process.stdout.write(m + "\n"));
|
|
18
|
+
const entry = { type: "stdio", command: "node", args: [cliPath] };
|
|
19
|
+
const done = [];
|
|
20
|
+
upsertMcpConfig(path.join(cwd, ".mcp.json"), "mcpServers", entry, cwd, done);
|
|
21
|
+
if (fs.existsSync(path.join(cwd, ".cursor"))) {
|
|
22
|
+
upsertMcpConfig(path.join(cwd, ".cursor", "mcp.json"), "mcpServers", entry, cwd, done);
|
|
23
|
+
}
|
|
24
|
+
if (fs.existsSync(path.join(cwd, ".vscode"))) {
|
|
25
|
+
upsertMcpConfig(path.join(cwd, ".vscode", "mcp.json"), "servers", entry, cwd, done);
|
|
26
|
+
}
|
|
27
|
+
copySkill(skillSrcDir, cwd, done);
|
|
28
|
+
log("Buoy MCP installed:");
|
|
29
|
+
for (const line of done) log(" \u2022 " + line);
|
|
30
|
+
log("");
|
|
31
|
+
log("Next steps:");
|
|
32
|
+
log(" 1. Restart your editor (or reconnect the MCP server) to load `buoy`.");
|
|
33
|
+
log(" 2. Open your app with Buoy devtools running on a device or simulator.");
|
|
34
|
+
log(' 3. Say "buoy optimize" to start a guided performance pass.');
|
|
35
|
+
}
|
|
36
|
+
function upsertMcpConfig(file, key, entry, cwd, done) {
|
|
37
|
+
let json = {};
|
|
38
|
+
if (fs.existsSync(file)) {
|
|
39
|
+
try {
|
|
40
|
+
json = JSON.parse(fs.readFileSync(file, "utf8"));
|
|
41
|
+
} catch {
|
|
42
|
+
fs.copyFileSync(file, file + ".bak");
|
|
43
|
+
done.push(`existing ${rel(file, cwd)} was invalid JSON \u2014 backed up to ${rel(file, cwd)}.bak`);
|
|
44
|
+
json = {};
|
|
45
|
+
}
|
|
46
|
+
} else {
|
|
47
|
+
fs.mkdirSync(path.dirname(file), { recursive: true });
|
|
48
|
+
}
|
|
49
|
+
const servers = json[key] && typeof json[key] === "object" ? json[key] : {};
|
|
50
|
+
const existed = "buoy" in servers;
|
|
51
|
+
servers.buoy = entry;
|
|
52
|
+
json[key] = servers;
|
|
53
|
+
fs.writeFileSync(file, JSON.stringify(json, null, 2) + "\n");
|
|
54
|
+
done.push(`${existed ? "updated" : "added"} \`buoy\` in ${rel(file, cwd)}`);
|
|
55
|
+
}
|
|
56
|
+
function copySkill(skillSrcDir, cwd, done) {
|
|
57
|
+
const src = path.join(skillSrcDir, "buoy-optimize", "SKILL.md");
|
|
58
|
+
if (!fs.existsSync(src)) {
|
|
59
|
+
done.push("\u26A0 skill source not found \u2014 skipped (skill not installed)");
|
|
60
|
+
return;
|
|
61
|
+
}
|
|
62
|
+
const destDir = path.join(cwd, ".claude", "skills", "buoy-optimize");
|
|
63
|
+
fs.mkdirSync(destDir, { recursive: true });
|
|
64
|
+
const dest = path.join(destDir, "SKILL.md");
|
|
65
|
+
fs.copyFileSync(src, dest);
|
|
66
|
+
done.push(`installed the buoy-optimize skill \u2192 ${rel(dest, cwd)}`);
|
|
67
|
+
}
|
|
68
|
+
function rel(file, cwd) {
|
|
69
|
+
const r = path.relative(cwd, file);
|
|
70
|
+
return r && !r.startsWith("..") ? r : file;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
// src/cli.ts
|
|
74
|
+
async function main() {
|
|
75
|
+
const portEnv = process.env.BUOY_BROKER_PORT;
|
|
76
|
+
const sync = new SyncClient({
|
|
77
|
+
url: process.env.BUOY_BROKER_URL,
|
|
78
|
+
port: portEnv ? Number(portEnv) : void 0,
|
|
79
|
+
autoSpawnBroker: process.env.BUOY_NO_SPAWN !== "1"
|
|
80
|
+
});
|
|
81
|
+
const server = createMcpServer(sync);
|
|
82
|
+
const transport = new StdioServerTransport();
|
|
83
|
+
await server.connect(transport);
|
|
84
|
+
process.stderr.write(`[buoy-mcp] serving MCP over stdio; connecting to broker\u2026
|
|
85
|
+
`);
|
|
86
|
+
sync.start().then((mode) => process.stderr.write(`[buoy-mcp] broker ${mode}
|
|
87
|
+
`)).catch(
|
|
88
|
+
(err) => process.stderr.write(
|
|
89
|
+
`[buoy-mcp] broker connection failed: ${err?.message ?? err} \u2014 tools will report no devices until it recovers
|
|
90
|
+
`
|
|
91
|
+
)
|
|
92
|
+
);
|
|
93
|
+
const shutdown = async () => {
|
|
94
|
+
await sync.stop().catch(() => {
|
|
95
|
+
});
|
|
96
|
+
await server.close().catch(() => {
|
|
97
|
+
});
|
|
98
|
+
process.exit(0);
|
|
99
|
+
};
|
|
100
|
+
process.on("SIGINT", shutdown);
|
|
101
|
+
process.on("SIGTERM", shutdown);
|
|
102
|
+
}
|
|
103
|
+
if (process.argv[2] === "init") {
|
|
104
|
+
try {
|
|
105
|
+
runInit();
|
|
106
|
+
process.exit(0);
|
|
107
|
+
} catch (err) {
|
|
108
|
+
process.stderr.write(`[buoy-mcp] init failed: ${err?.message ?? err}
|
|
109
|
+
`);
|
|
110
|
+
process.exit(1);
|
|
111
|
+
}
|
|
112
|
+
} else {
|
|
113
|
+
main().catch((err) => {
|
|
114
|
+
process.stderr.write(`[buoy-mcp] fatal: ${err?.message ?? err}
|
|
115
|
+
`);
|
|
116
|
+
process.exit(1);
|
|
117
|
+
});
|
|
118
|
+
}
|
package/dist/index.d.mts
ADDED
|
@@ -0,0 +1,379 @@
|
|
|
1
|
+
import { ToolCapability } from '@buoy-gg/sync-broker';
|
|
2
|
+
import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
|
|
3
|
+
|
|
4
|
+
interface SyncClientOptions {
|
|
5
|
+
/** Broker URL to connect to. @default http://localhost:42831 */
|
|
6
|
+
url?: string;
|
|
7
|
+
/** Broker port to probe/spawn on. @default 42831 */
|
|
8
|
+
port?: number;
|
|
9
|
+
/**
|
|
10
|
+
* If no broker is already running on the port, spawn one in-process so the
|
|
11
|
+
* MCP server works standalone (no desktop app needed). @default true
|
|
12
|
+
*/
|
|
13
|
+
autoSpawnBroker?: boolean;
|
|
14
|
+
/** Default per-action timeout. @default 15000 */
|
|
15
|
+
actionTimeoutMs?: number;
|
|
16
|
+
/** Optional logger — writes to stderr by default (stdout is the MCP channel). */
|
|
17
|
+
log?: (msg: string) => void;
|
|
18
|
+
}
|
|
19
|
+
/** A device the broker knows about, merged with its advertised tools. */
|
|
20
|
+
interface DeviceInfo {
|
|
21
|
+
deviceId: string;
|
|
22
|
+
deviceName: string;
|
|
23
|
+
platform?: string;
|
|
24
|
+
isConnected: boolean;
|
|
25
|
+
/** Whether the connected app reported an active Buoy Pro license. */
|
|
26
|
+
isPro: boolean;
|
|
27
|
+
tools: ToolCapability[];
|
|
28
|
+
}
|
|
29
|
+
/** How the client obtained its broker connection. */
|
|
30
|
+
type BrokerMode = "connected" | "spawned";
|
|
31
|
+
/**
|
|
32
|
+
* A headless "Dashboard" client of the Buoy sync broker — the same role the
|
|
33
|
+
* desktop app plays, but driven programmatically by the MCP server. Implements
|
|
34
|
+
* connect-or-spawn: connects to a running broker (e.g. the desktop app) if one
|
|
35
|
+
* is up, otherwise hosts its own in-process so it works standalone.
|
|
36
|
+
*/
|
|
37
|
+
declare class SyncClient {
|
|
38
|
+
private socket;
|
|
39
|
+
private broker;
|
|
40
|
+
private readonly url;
|
|
41
|
+
private readonly port;
|
|
42
|
+
private readonly actionTimeoutMs;
|
|
43
|
+
private readonly log;
|
|
44
|
+
/** Latest capabilities announce per deviceId. */
|
|
45
|
+
private capabilities;
|
|
46
|
+
/** Latest device list from the broker's `all-devices-update`. */
|
|
47
|
+
private devices;
|
|
48
|
+
/** Latest snapshot payload per `${deviceId}::${toolId}`. */
|
|
49
|
+
private snapshots;
|
|
50
|
+
/** Tools we've asked the broker to keep streaming, as `${deviceId}::${toolId}`. */
|
|
51
|
+
private watching;
|
|
52
|
+
/** In-flight actions awaiting a result, keyed by requestId. */
|
|
53
|
+
private pendingActions;
|
|
54
|
+
/** Callbacks waiting for the next snapshot of `${deviceId}::${toolId}`. */
|
|
55
|
+
private snapshotWaiters;
|
|
56
|
+
constructor(opts?: SyncClientOptions);
|
|
57
|
+
private readonly autoSpawn;
|
|
58
|
+
/** Connect to a running broker, or spawn one in-process, then handshake. */
|
|
59
|
+
start(): Promise<BrokerMode>;
|
|
60
|
+
private connectSocket;
|
|
61
|
+
/** Disconnect and tear down a spawned broker, if any. */
|
|
62
|
+
stop(): Promise<void>;
|
|
63
|
+
/** All devices the broker knows about, merged with their advertised tools. */
|
|
64
|
+
listDevices(): DeviceInfo[];
|
|
65
|
+
/** Connected devices only. */
|
|
66
|
+
connectedDevices(): DeviceInfo[];
|
|
67
|
+
/** Actions advertised for a tool, with the devices offering it. */
|
|
68
|
+
toolActions(toolId: string): {
|
|
69
|
+
deviceId: string;
|
|
70
|
+
deviceName: string;
|
|
71
|
+
actions: string[];
|
|
72
|
+
}[];
|
|
73
|
+
/**
|
|
74
|
+
* Resolve a concrete target deviceId. If `deviceId` is given it's used as-is.
|
|
75
|
+
* Otherwise, if exactly one device is connected, that one is chosen; if none
|
|
76
|
+
* or many, an informative error is thrown (callers can pass "All" explicitly).
|
|
77
|
+
*/
|
|
78
|
+
resolveDevice(deviceId?: string): string;
|
|
79
|
+
/**
|
|
80
|
+
* Resolve a target device AND require it to be on Buoy Pro. The Buoy MCP is a
|
|
81
|
+
* Pro feature: data/action tools call this so a free app can't be driven
|
|
82
|
+
* through the MCP. The app's Keygen-validated license is the source of truth
|
|
83
|
+
* (the device reports it over the broker); this just reads that flag.
|
|
84
|
+
*
|
|
85
|
+
* `list_devices` deliberately does NOT call this — a free user can still see
|
|
86
|
+
* their device and that it's unlicensed, which explains why everything else
|
|
87
|
+
* is blocked.
|
|
88
|
+
*/
|
|
89
|
+
requireProDevice(deviceId?: string): string;
|
|
90
|
+
private ensureWatching;
|
|
91
|
+
/** Stop streaming a tool (releases the device-side backpressure). */
|
|
92
|
+
unwatch(toolId: string, targetDeviceId: string): void;
|
|
93
|
+
/** Last snapshot received for a tool/device, if any. */
|
|
94
|
+
cachedSnapshot(toolId: string, deviceId: string): unknown;
|
|
95
|
+
/**
|
|
96
|
+
* Get a tool's current state. Starts watching (so the device begins emitting),
|
|
97
|
+
* explicitly requests state, and resolves with the next snapshot. Falls back
|
|
98
|
+
* to the last cached snapshot if the request times out.
|
|
99
|
+
*/
|
|
100
|
+
getSnapshot(toolId: string, deviceId?: string, opts?: {
|
|
101
|
+
timeoutMs?: number;
|
|
102
|
+
}): Promise<{
|
|
103
|
+
deviceId: string;
|
|
104
|
+
payload: unknown;
|
|
105
|
+
}>;
|
|
106
|
+
private waitForSnapshot;
|
|
107
|
+
/**
|
|
108
|
+
* Invoke a remote action on a device and await its result. Pass deviceId to
|
|
109
|
+
* target one device, or "All" to broadcast (resolves with the first responder).
|
|
110
|
+
*/
|
|
111
|
+
callAction(toolId: string, action: string, params?: unknown, deviceId?: string, opts?: {
|
|
112
|
+
timeoutMs?: number;
|
|
113
|
+
}): Promise<unknown>;
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
/**
|
|
117
|
+
* Build the Buoy MCP server and register all tools against a connected
|
|
118
|
+
* {@link SyncClient}. Tool packs (perf-monitor, etc.) are layered on top of the
|
|
119
|
+
* generic core here.
|
|
120
|
+
*/
|
|
121
|
+
declare function createMcpServer(sync: SyncClient): McpServer;
|
|
122
|
+
|
|
123
|
+
/**
|
|
124
|
+
* The generic core: tools that work for ANY Buoy tool exposing a sync adapter,
|
|
125
|
+
* with zero tool-specific knowledge. Tool packs add ergonomic wrappers on top.
|
|
126
|
+
*/
|
|
127
|
+
declare function registerGenericTools(server: McpServer, sync: SyncClient): void;
|
|
128
|
+
|
|
129
|
+
/**
|
|
130
|
+
* The perf-monitor tool pack: ergonomic, workflow-level tools that wrap the
|
|
131
|
+
* generic call_action/get_snapshot for benchmarking. Headlined by
|
|
132
|
+
* run_benchmark_batch — feed a case matrix, get back a ranked winner.
|
|
133
|
+
*/
|
|
134
|
+
declare function registerPerfMonitorTools(server: McpServer, sync: SyncClient): void;
|
|
135
|
+
|
|
136
|
+
/** One row in an automation batch (MCP-friendly input; `id` added internally). */
|
|
137
|
+
interface PerfCaseInput {
|
|
138
|
+
/** Display name; becomes the saved report's metadata.name. */
|
|
139
|
+
name: string;
|
|
140
|
+
/** Query params applied to the route for this case (e.g. { renderer: "v2" }). */
|
|
141
|
+
params?: Record<string, string>;
|
|
142
|
+
/** Optional per-case route override; falls back to the batch targetRoute. */
|
|
143
|
+
route?: string;
|
|
144
|
+
}
|
|
145
|
+
/** Full config sent to the device's `startAutomation` action. */
|
|
146
|
+
interface AutomationConfig {
|
|
147
|
+
targetRoute: string;
|
|
148
|
+
bounceRoute: string;
|
|
149
|
+
perCaseDurationMs: number;
|
|
150
|
+
settleMs: number;
|
|
151
|
+
navTimeoutMs: number;
|
|
152
|
+
cases: {
|
|
153
|
+
id: string;
|
|
154
|
+
name: string;
|
|
155
|
+
params: Record<string, string>;
|
|
156
|
+
route?: string;
|
|
157
|
+
}[];
|
|
158
|
+
reloadBetweenCases?: boolean;
|
|
159
|
+
reloadStrategy?: "auto" | "dev-settings" | "expo-updates";
|
|
160
|
+
postReloadSettleMs?: number;
|
|
161
|
+
runsPerCase?: number;
|
|
162
|
+
coolDownMs?: number;
|
|
163
|
+
discardWarmupRuns?: number;
|
|
164
|
+
shuffleCases?: boolean;
|
|
165
|
+
}
|
|
166
|
+
/** A lightweight saved-recording summary (the device's index entry). */
|
|
167
|
+
interface IndexEntry {
|
|
168
|
+
id: string;
|
|
169
|
+
createdAt: number;
|
|
170
|
+
name: string;
|
|
171
|
+
route?: string;
|
|
172
|
+
durationMs?: number;
|
|
173
|
+
sampleCount?: number;
|
|
174
|
+
jsFpsAvg?: number;
|
|
175
|
+
uiFpsAvg?: number;
|
|
176
|
+
cpuAvg?: number;
|
|
177
|
+
memMaxMb?: number;
|
|
178
|
+
jsJankFrames?: number;
|
|
179
|
+
uiJankFrames?: number;
|
|
180
|
+
deviceMaxRefreshRate?: number;
|
|
181
|
+
source?: "manual" | "batch";
|
|
182
|
+
batchId?: string;
|
|
183
|
+
batchIndex?: number;
|
|
184
|
+
batchFailureReason?: string;
|
|
185
|
+
params?: Record<string, string>;
|
|
186
|
+
caseId?: string;
|
|
187
|
+
runIndex?: number;
|
|
188
|
+
isMedianRun?: boolean;
|
|
189
|
+
}
|
|
190
|
+
/** Per-metric min/max/avg/p95 channel. */
|
|
191
|
+
interface AggregateChannel {
|
|
192
|
+
min: number;
|
|
193
|
+
max: number;
|
|
194
|
+
avg: number;
|
|
195
|
+
p95: number;
|
|
196
|
+
}
|
|
197
|
+
/** Full report stats (from loadReport) — used by compare_reports. */
|
|
198
|
+
interface BenchmarkReportStats {
|
|
199
|
+
sampleCount: number;
|
|
200
|
+
durationMs: number;
|
|
201
|
+
jsFps: AggregateChannel;
|
|
202
|
+
uiFps: AggregateChannel;
|
|
203
|
+
cpuUsage: AggregateChannel;
|
|
204
|
+
memoryUsage: AggregateChannel;
|
|
205
|
+
jsJankFrames: number;
|
|
206
|
+
uiJankFrames: number;
|
|
207
|
+
deviceMaxRefreshRate: number;
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
/**
|
|
211
|
+
* Pure helpers for the perf-monitor pack: build an AutomationConfig from
|
|
212
|
+
* MCP-friendly input, estimate batch duration, and rank a finished batch's
|
|
213
|
+
* results into a winner + comparison table. No network/sync — unit-testable.
|
|
214
|
+
*/
|
|
215
|
+
|
|
216
|
+
interface BuildConfigInput {
|
|
217
|
+
targetRoute: string;
|
|
218
|
+
bounceRoute?: string;
|
|
219
|
+
cases: PerfCaseInput[];
|
|
220
|
+
runsPerCase?: number;
|
|
221
|
+
perCaseDurationMs?: number;
|
|
222
|
+
settleMs?: number;
|
|
223
|
+
coolDownMs?: number;
|
|
224
|
+
navTimeoutMs?: number;
|
|
225
|
+
discardWarmupRuns?: number;
|
|
226
|
+
shuffleCases?: boolean;
|
|
227
|
+
reloadBetweenCases?: boolean;
|
|
228
|
+
}
|
|
229
|
+
/** The settings subset a driver inherits from the device's saved config. */
|
|
230
|
+
type BenchmarkSettings = Partial<Pick<AutomationConfig, "perCaseDurationMs" | "settleMs" | "navTimeoutMs" | "runsPerCase" | "coolDownMs" | "discardWarmupRuns" | "shuffleCases" | "reloadBetweenCases">>;
|
|
231
|
+
/**
|
|
232
|
+
* Build + validate the AutomationConfig the device expects. Throws on bad input.
|
|
233
|
+
*
|
|
234
|
+
* `base` is the device's saved benchmark settings — caller args override it,
|
|
235
|
+
* and it overrides the hardcoded fallback. So a run respects the profile the
|
|
236
|
+
* user tuned in the app unless the caller explicitly overrides a field.
|
|
237
|
+
*/
|
|
238
|
+
declare function buildAutomationConfig(input: BuildConfigInput, base?: BenchmarkSettings): {
|
|
239
|
+
config: AutomationConfig;
|
|
240
|
+
warnings: string[];
|
|
241
|
+
};
|
|
242
|
+
/** Rough wall-clock estimate for a batch, in ms. */
|
|
243
|
+
declare function estimateBatchMs(config: AutomationConfig): number;
|
|
244
|
+
interface RankRow {
|
|
245
|
+
batchIndex: number;
|
|
246
|
+
caseName: string;
|
|
247
|
+
params: Record<string, string>;
|
|
248
|
+
runs: number;
|
|
249
|
+
failed: boolean;
|
|
250
|
+
failureReason?: string;
|
|
251
|
+
uiFps: number | null;
|
|
252
|
+
jsFps: number | null;
|
|
253
|
+
cpu: number | null;
|
|
254
|
+
memMb: number | null;
|
|
255
|
+
jsJank: number | null;
|
|
256
|
+
uiJank: number | null;
|
|
257
|
+
deviceMaxRefreshRate: number | null;
|
|
258
|
+
/** Delta vs the baseline (first case) — positive = better/more fps. */
|
|
259
|
+
deltaUiFps: number | null;
|
|
260
|
+
deltaJsFps: number | null;
|
|
261
|
+
/** At-risk axes for this case (e.g. "UI thread drops frames", "CPU near cap"). */
|
|
262
|
+
flags: string[];
|
|
263
|
+
}
|
|
264
|
+
interface BatchRanking {
|
|
265
|
+
batchId: string;
|
|
266
|
+
baselineName: string | null;
|
|
267
|
+
deviceMaxRefreshRate: number | null;
|
|
268
|
+
rows: RankRow[];
|
|
269
|
+
/** The single best case per metric — so no one number masks the others. */
|
|
270
|
+
leaders: {
|
|
271
|
+
uiFps?: string;
|
|
272
|
+
jsFps?: string;
|
|
273
|
+
lowestCpu?: string;
|
|
274
|
+
lowestMem?: string;
|
|
275
|
+
fewestJank?: string;
|
|
276
|
+
};
|
|
277
|
+
/** Cases with an at-risk axis (esp. UI thread) — never a silent winner. */
|
|
278
|
+
flagged: {
|
|
279
|
+
caseName: string;
|
|
280
|
+
flags: string[];
|
|
281
|
+
}[];
|
|
282
|
+
/** Cases that cap nothing, best-first — the safe candidates to favor. */
|
|
283
|
+
balanced: string[];
|
|
284
|
+
}
|
|
285
|
+
/**
|
|
286
|
+
* Rank a finished batch from its index entries. Picks the median run per case,
|
|
287
|
+
* computes per-metric leaders + at-risk flags, and surfaces the "balanced"
|
|
288
|
+
* cases (those that cap no axis). Deliberately does NOT crown a single winner:
|
|
289
|
+
* a cheap run that drops UI frames is worse than a pricier one that holds them,
|
|
290
|
+
* so the model/user must weigh the trade-offs from the full picture.
|
|
291
|
+
*/
|
|
292
|
+
declare function rankBatchFromIndex(index: IndexEntry[], batchId: string, casesOrder?: {
|
|
293
|
+
name: string;
|
|
294
|
+
}[], failures?: {
|
|
295
|
+
index: number;
|
|
296
|
+
reason: string;
|
|
297
|
+
}[]): BatchRanking;
|
|
298
|
+
/** Render a batch ranking as a trade-off-aware report (no single false winner). */
|
|
299
|
+
declare function formatRankingMarkdown(ranking: BatchRanking): string;
|
|
300
|
+
/** Side-by-side comparison of two full report stats. */
|
|
301
|
+
declare function compareReportStats(a: {
|
|
302
|
+
name: string;
|
|
303
|
+
stats: BenchmarkReportStats;
|
|
304
|
+
}, b: {
|
|
305
|
+
name: string;
|
|
306
|
+
stats: BenchmarkReportStats;
|
|
307
|
+
}): {
|
|
308
|
+
a: string;
|
|
309
|
+
b: string;
|
|
310
|
+
uiFps: {
|
|
311
|
+
a: {
|
|
312
|
+
avg: number;
|
|
313
|
+
p95: number;
|
|
314
|
+
};
|
|
315
|
+
b: {
|
|
316
|
+
avg: number;
|
|
317
|
+
p95: number;
|
|
318
|
+
};
|
|
319
|
+
deltaAvg: number;
|
|
320
|
+
};
|
|
321
|
+
jsFps: {
|
|
322
|
+
a: {
|
|
323
|
+
avg: number;
|
|
324
|
+
p95: number;
|
|
325
|
+
};
|
|
326
|
+
b: {
|
|
327
|
+
avg: number;
|
|
328
|
+
p95: number;
|
|
329
|
+
};
|
|
330
|
+
deltaAvg: number;
|
|
331
|
+
};
|
|
332
|
+
cpuUsage: {
|
|
333
|
+
a: {
|
|
334
|
+
avg: number;
|
|
335
|
+
p95: number;
|
|
336
|
+
};
|
|
337
|
+
b: {
|
|
338
|
+
avg: number;
|
|
339
|
+
p95: number;
|
|
340
|
+
};
|
|
341
|
+
deltaAvg: number;
|
|
342
|
+
};
|
|
343
|
+
memoryUsage: {
|
|
344
|
+
a: {
|
|
345
|
+
avg: number;
|
|
346
|
+
p95: number;
|
|
347
|
+
};
|
|
348
|
+
b: {
|
|
349
|
+
avg: number;
|
|
350
|
+
p95: number;
|
|
351
|
+
};
|
|
352
|
+
deltaAvg: number;
|
|
353
|
+
};
|
|
354
|
+
jsJankFrames: {
|
|
355
|
+
a: number;
|
|
356
|
+
b: number;
|
|
357
|
+
delta: number;
|
|
358
|
+
};
|
|
359
|
+
uiJankFrames: {
|
|
360
|
+
a: number;
|
|
361
|
+
b: number;
|
|
362
|
+
delta: number;
|
|
363
|
+
};
|
|
364
|
+
};
|
|
365
|
+
|
|
366
|
+
/** Default broker port (shared with the desktop app + sync-broker). */
|
|
367
|
+
declare const BROKER_PORT = 42831;
|
|
368
|
+
/** Default broker URL the MCP server connects to as a "Dashboard" client. */
|
|
369
|
+
declare const BROKER_URL = "http://localhost:42831";
|
|
370
|
+
/** How long to wait for a device's action result before giving up. */
|
|
371
|
+
declare const DEFAULT_ACTION_TIMEOUT_MS = 15000;
|
|
372
|
+
/** How long to wait for a fresh snapshot after requesting tool state. */
|
|
373
|
+
declare const DEFAULT_SNAPSHOT_TIMEOUT_MS = 5000;
|
|
374
|
+
/** The exact handshake name the broker uses to recognize a dashboard client. */
|
|
375
|
+
declare const DASHBOARD_NAME = "Dashboard";
|
|
376
|
+
/** Target token meaning "every connected device". */
|
|
377
|
+
declare const ALL_DEVICES = "All";
|
|
378
|
+
|
|
379
|
+
export { ALL_DEVICES, BROKER_PORT, BROKER_URL, type BrokerMode, DASHBOARD_NAME, DEFAULT_ACTION_TIMEOUT_MS, DEFAULT_SNAPSHOT_TIMEOUT_MS, type DeviceInfo, SyncClient, type SyncClientOptions, buildAutomationConfig, compareReportStats, createMcpServer, estimateBatchMs, formatRankingMarkdown, rankBatchFromIndex, registerGenericTools, registerPerfMonitorTools };
|