@buoy-gg/mcp 7.0.66 → 7.0.68
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/README.md +18 -1
- package/dist/camera-KL6KTSFE.mjs +1 -0
- package/dist/chunk-D4P6PPYP.mjs +422 -0
- package/dist/chunk-DVRR7W22.mjs +146 -0
- package/dist/chunk-FOMLVT7K.mjs +1 -0
- package/dist/{chunk-EPYLVJ7F.mjs → chunk-KXEKQVCL.mjs} +142 -132
- package/dist/chunk-OP23W722.mjs +3 -0
- package/dist/chunk-OUCB2Y5J.mjs +5 -0
- package/dist/{chunk-OCWGZASK.mjs → chunk-PDYZSHOS.mjs} +1 -1
- package/dist/chunk-SPQBWBV6.mjs +4 -0
- package/dist/cli.js +563 -130
- package/dist/cli.mjs +14 -11
- package/dist/flowRun-F3PQIRPB.mjs +2 -0
- package/dist/freeView-3LHV3RBG.mjs +1 -0
- package/dist/index.d.mts +95 -88
- package/dist/index.d.ts +95 -88
- package/dist/index.js +201 -155
- package/dist/index.mjs +1 -1
- package/dist/policy-D342AXEF.mjs +1 -0
- package/dist/simFarm-V7TBSSV2.mjs +1 -0
- package/dist/src-N7WTCDAL.mjs +1 -0
- package/dist/{webBench-QCHYXYCA.mjs → webBench-BEMJWKB3.mjs} +1 -1
- package/package.json +4 -4
- package/skill/buoy-optimize/MEASURING.md +22 -1
- package/skill/buoy-optimize/SKILL.md +11 -1
- package/dist/camera-F5Q7WMHF.mjs +0 -1
- package/dist/chunk-MHL43LJN.mjs +0 -2
- package/dist/chunk-OPHPYPJD.mjs +0 -146
- package/dist/chunk-VKWX4XAY.mjs +0 -5
- package/dist/chunk-Y7SNXKA2.mjs +0 -6
- package/dist/flowRun-OP4UJXKS.mjs +0 -2
- package/dist/simFarm-EY4R3A4O.mjs +0 -1
- package/dist/src-XCBUDSR7.mjs +0 -1
package/dist/index.mjs
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
import{createRequire as
|
|
1
|
+
import{createRequire as r}from"module";const L=r(import.meta.url);import{runInit as e}from"./chunk-PDYZSHOS.mjs";import{ALL_DEVICES as o,BROKER_PORT as t,BROKER_URL as i,DASHBOARD_NAME as s,DEFAULT_ACTION_TIMEOUT_MS as a,DEFAULT_SNAPSHOT_TIMEOUT_MS as m,PACKAGE_NAME as T,SyncClient as n,VERSION as p,buildAutomationConfig as g,compareReportStats as l,createMcpServer as R,estimateBatchMs as E,formatRankingMarkdown as _,getUpdateNotice as c,rankBatchFromIndex as u,registerConsoleTools as A,registerEventsTools as S,registerGenericTools as M,registerJotaiTools as O,registerPerfMonitorTools as C,registerReactQueryTools as I,registerReduxTools as d,registerRoutesTools as f,registerScreenshotTools as N,registerStorageTools as U,registerZustandTools as B}from"./chunk-KXEKQVCL.mjs";import"./chunk-SPQBWBV6.mjs";import"./chunk-D4P6PPYP.mjs";import"./chunk-OUCB2Y5J.mjs";import"./chunk-FOMLVT7K.mjs";import"./chunk-OP23W722.mjs";import"./chunk-DVRR7W22.mjs";import"./chunk-MTTWR4T7.mjs";export{o as ALL_DEVICES,t as BROKER_PORT,i as BROKER_URL,s as DASHBOARD_NAME,a as DEFAULT_ACTION_TIMEOUT_MS,m as DEFAULT_SNAPSHOT_TIMEOUT_MS,T as PACKAGE_NAME,n as SyncClient,p as VERSION,g as buildAutomationConfig,l as compareReportStats,R as createMcpServer,E as estimateBatchMs,_ as formatRankingMarkdown,c as getUpdateNotice,u as rankBatchFromIndex,A as registerConsoleTools,S as registerEventsTools,M as registerGenericTools,O as registerJotaiTools,C as registerPerfMonitorTools,I as registerReactQueryTools,d as registerReduxTools,f as registerRoutesTools,N as registerScreenshotTools,U as registerStorageTools,B as registerZustandTools,e as runInit};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import{createRequire as e}from"module";const d=e(import.meta.url);import{FREE_SUMMARY as s,accessFor as r,currentAccess as o,freeToolNames as t,hasProAccess as c,isFreeAppCall as i,isFreeTool as a,isWeekendPass as m,meetsAccess as u,outsideCalls as l,proRequiredMessage as p,runWithAccess as A,setClockForTests as n,weekendNow as F}from"./chunk-FOMLVT7K.mjs";import"./chunk-MTTWR4T7.mjs";export{s as FREE_SUMMARY,r as accessFor,o as currentAccess,t as freeToolNames,c as hasProAccess,i as isFreeAppCall,a as isFreeTool,m as isWeekendPass,u as meetsAccess,l as outsideCalls,p as proRequiredMessage,A as runWithAccess,n as setClockForTests,F as weekendNow};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import{createRequire as r}from"module";const t=r(import.meta.url);import{farmFairness as e,registerSimFarmTools as m,runFarmd as o}from"./chunk-SPQBWBV6.mjs";import"./chunk-D4P6PPYP.mjs";import"./chunk-OUCB2Y5J.mjs";import"./chunk-FOMLVT7K.mjs";import"./chunk-MTTWR4T7.mjs";export{e as farmFairness,m as registerSimFarmTools,o as runFarmd};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import{createRequire as e}from"module";const ae=e(import.meta.url);import{ALWAYS_ENABLED as a,BUILT_IN_PHONES as r,DEFAULT_PHONE as i,DEFAULT_SETTINGS as t,Engine as o,FarmClient as n,FarmError as l,LoadMeter as m,LocalDylibBuilder as s,PHONE_OFF as u,PROTOCOL_VERSION as d,SLIM_CATEGORIES as L,STATE_VERSION as p,SimctlHost as E,Store as f,agentLabel as h,buildDylib as c,connectFarm as F,crashLoop as S,defaultCapMB as O,defaultSeedDir as _,detectAgent as I,eventsFilePath as T,farmDefaults as D,farmHelp as P,farmHome as A,farmOwnedUdids as C,findDataContainer as N,findExampleBuild as R,findRepoRoot as b,joinInsertList as H,launchFilePath as B,loadPhones as v,measurePhone as y,nodeLauncher as U,parseCpuTime as g,parseEvents as q,paths as M,phoneDir as V,profileHash as w,profileLabels as x,profileVars as G,readBuoyInstallId as j,readLaunchFile as z,readSummary as W,removeLaunchFile as Y,runFarmCommand as k,savePhone as J,simLaunchEnv as K,slimLabels as Q,startFarmd as X,summarize as Z,writeLaunchFile as $}from"./chunk-D4P6PPYP.mjs";import"./chunk-MTTWR4T7.mjs";export{a as ALWAYS_ENABLED,r as BUILT_IN_PHONES,i as DEFAULT_PHONE,t as DEFAULT_SETTINGS,o as Engine,n as FarmClient,l as FarmError,m as LoadMeter,s as LocalDylibBuilder,u as PHONE_OFF,d as PROTOCOL_VERSION,L as SLIM_CATEGORIES,p as STATE_VERSION,E as SimctlHost,f as Store,h as agentLabel,c as buildDylib,F as connectFarm,S as crashLoop,O as defaultCapMB,_ as defaultSeedDir,I as detectAgent,T as eventsFilePath,D as farmDefaults,P as farmHelp,A as farmHome,C as farmOwnedUdids,N as findDataContainer,R as findExampleBuild,b as findRepoRoot,H as joinInsertList,B as launchFilePath,v as loadPhones,y as measurePhone,U as nodeLauncher,g as parseCpuTime,q as parseEvents,M as paths,V as phoneDir,w as profileHash,x as profileLabels,G as profileVars,j as readBuoyInstallId,z as readLaunchFile,W as readSummary,Y as removeLaunchFile,k as runFarmCommand,J as savePhone,K as simLaunchEnv,Q as slimLabels,X as startFarmd,Z as summarize,$ as writeLaunchFile};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
import{createRequire as e}from"module";const q=e(import.meta.url);import{GPU_RENDERER_JS as r,PLAYWRIGHT_MISSING as t,aggregateCase as a,caseUrl as o,cdpDeltas as i,checkBudgets as s,closeCall as c,closeWithin as m,expectFailureFrom as n,formatWebBenchMarkdown as l,isSoftwareGpu as u,keyMatches as p,loadChromium as h,locateStepTargets as d,mergeInteractions as R,missedInput as f,oneLine as g,percentile as b,runWebBench as k,shuffled as w,snapRefreshRate as B,summarizeFrameTrace as F,summarizeProbe as I,takeHostLock as S,tiedPairs as W,validateWebBenchOptions as _,waitForHostLock as C,webBenchFlags as G,withDeadline as L}from"./chunk-
|
|
1
|
+
import{createRequire as e}from"module";const q=e(import.meta.url);import{GPU_RENDERER_JS as r,PLAYWRIGHT_MISSING as t,aggregateCase as a,caseUrl as o,cdpDeltas as i,checkBudgets as s,closeCall as c,closeWithin as m,expectFailureFrom as n,formatWebBenchMarkdown as l,isSoftwareGpu as u,keyMatches as p,loadChromium as h,locateStepTargets as d,mergeInteractions as R,missedInput as f,oneLine as g,percentile as b,runWebBench as k,shuffled as w,snapRefreshRate as B,summarizeFrameTrace as F,summarizeProbe as I,takeHostLock as S,tiedPairs as W,validateWebBenchOptions as _,waitForHostLock as C,webBenchFlags as G,withDeadline as L}from"./chunk-DVRR7W22.mjs";import"./chunk-MTTWR4T7.mjs";export{r as GPU_RENDERER_JS,t as PLAYWRIGHT_MISSING,a as aggregateCase,o as caseUrl,i as cdpDeltas,s as checkBudgets,c as closeCall,m as closeWithin,n as expectFailureFrom,l as formatWebBenchMarkdown,u as isSoftwareGpu,p as keyMatches,h as loadChromium,d as locateStepTargets,R as mergeInteractions,f as missedInput,g as oneLine,b as percentile,k as runWebBench,w as shuffled,B as snapRefreshRate,F as summarizeFrameTrace,I as summarizeProbe,S as takeHostLock,W as tiedPairs,_ as validateWebBenchOptions,C as waitForHostLock,G as webBenchFlags,L as withDeadline};
|
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.68",
|
|
5
5
|
"buoy": {
|
|
6
6
|
"dashboardAuthVersion": 1
|
|
7
7
|
},
|
|
@@ -35,8 +35,8 @@
|
|
|
35
35
|
"socket.io-client": "^4.8.1",
|
|
36
36
|
"ws": "^8.18.0",
|
|
37
37
|
"zod": "^3.25.0",
|
|
38
|
-
"@buoy-gg/license": "^7.0.
|
|
39
|
-
"@buoy-gg/sync-broker": "^7.0.
|
|
38
|
+
"@buoy-gg/license": "^7.0.68",
|
|
39
|
+
"@buoy-gg/sync-broker": "^7.0.68"
|
|
40
40
|
},
|
|
41
41
|
"devDependencies": {
|
|
42
42
|
"@types/node": "^22.0.0",
|
|
@@ -47,7 +47,7 @@
|
|
|
47
47
|
"rimraf": "^5.0.0",
|
|
48
48
|
"tsup": "^8.5.1",
|
|
49
49
|
"typescript": "~5.8.3",
|
|
50
|
-
"@buoy-gg/sim-farm": "7.0.
|
|
50
|
+
"@buoy-gg/sim-farm": "7.0.68"
|
|
51
51
|
},
|
|
52
52
|
"engines": {
|
|
53
53
|
"node": ">=18"
|
|
@@ -74,7 +74,7 @@ Open the dev server's network URL on a phone on the same Wi-Fi (for Vite, `vite
|
|
|
74
74
|
|
|
75
75
|
## React Native
|
|
76
76
|
|
|
77
|
-
- `list_devices`: pick the device with `perf-monitor`. Benchmarking
|
|
77
|
+
- `list_devices`: pick the device with `perf-monitor`. Benchmarking works on a free account. Free results leave out render names and causes. Don't work around account checks.
|
|
78
78
|
- `get_benchmark_settings` with `pages` = the number of cases: read back the settings and time estimate. Don't call `set_benchmark_settings` unless the user asked to save settings.
|
|
79
79
|
- `run_benchmark_batch` with `targetRoute`, `deviceId` and `cases`. The first case is the baseline. The route reads each case's `params`. Pass `captureRenders: false` while ranking; render capture adds work that differs by variant.
|
|
80
80
|
- If a batch times out, check `get_automation_status` before starting another.
|
|
@@ -84,6 +84,23 @@ Open the dev server's network URL on a phone on the same Wi-Fi (for Vite, `vite
|
|
|
84
84
|
- A remount doesn't reset module state or storage. If you use `reloadBetweenCases`, make sure `AutomationResumer` is mounted.
|
|
85
85
|
- A simulator result holds for that simulator. Confirm on real hardware before claiming phone speed.
|
|
86
86
|
|
|
87
|
+
## Many ideas at once: the Sim Farm (iOS sims)
|
|
88
|
+
|
|
89
|
+
Use the farm when the `sim_farm_*` tools exist and you have many ideas. Check `sim_farm_status` first. Its `roomFor` says how many more sims fit. A 16 GB Mac may fit only 1 to 3. Then pass `allowPartial: true`, and open the ideas one after another on the sims you got.
|
|
90
|
+
|
|
91
|
+
Sims share the Mac's CPU and graphics with everything else on it. So many sims at once are good for looks and crashes. They are bad for speed. One run has two steps.
|
|
92
|
+
|
|
93
|
+
1. **Look.** Take one sim per idea with `sim_farm_acquire`. Pass `launch: true`, a `goal`, and one `labels` entry per idea. Use the idea IDs from your bank as labels. Each sim comes back with a `deviceId`. Pass it to the other Buoy tools. Open each idea with `sim_farm_action` `open-url`, using the test route's link and that idea's params. If iOS asks "Open in …?", tap Open with `native_tap`. Take stills with `screenshot`. Mark ideas that crash or look wrong `invalid`. Send `sim_farm_report` for each one. Don't rank speed in this step.
|
|
94
|
+
2. **Speed.** Release your sims with `sim_farm_release` and `keepRun: true`. Take one sim into the same run with `sim_farm_acquire` and its `runId`. Hold `perf-slot` with `sim_farm_lock`. Another agent may hold it, so pass `waitSeconds`. First run the baseline 5 times, so you know how much the numbers move on their own. Then run `run_benchmark_batch` on that sim with `captureRenders: false`. Keep the baseline in every batch.
|
|
95
|
+
|
|
96
|
+
While you hold `perf-slot`, the farm records how busy the Mac is every second. `run_benchmark_batch` on your farm sim then says which runs were fair. A run is unfair when the Mac got hot, ran low on memory, swapped, ran low on spare CPU, or had other work jump during it. Small laptops get hot and swap more often. If many runs come back unfair, wait longer between runs with `coolDownMs`, and close apps you don't need. Never rank on an unfair run. Run those cases again with the baseline once the Mac is calm. If the farm can't check, say your numbers are unchecked.
|
|
97
|
+
|
|
98
|
+
Finish with `sim_farm_finish` and a one line summary. Ask for more time with `sim_farm_extend` before `deadlineAt`. If a call says `RUN_STOPPED`, the person stopped you. Stop and tell them.
|
|
99
|
+
|
|
100
|
+
Farm apps run as a real phone: an iPhone SE (2nd gen) by default. The app sees that phone's RAM and cores and gets its memory warnings. If an idea's sim shows `overLimit: true` in `sim_farm_status`, or the run gets a note that the app went past the phone's memory limit, a real SE would close that app. Mark the idea `invalid` and say why. The farm closes an app that keeps growing past a safety cap.
|
|
101
|
+
|
|
102
|
+
Farm numbers come from a slim debug sim. Say so in your report, and confirm the finalists on a real device.
|
|
103
|
+
|
|
87
104
|
## Tool list
|
|
88
105
|
|
|
89
106
|
| Tool | Use |
|
|
@@ -97,4 +114,8 @@ Open the dev server's network URL on a phone on the same Wi-Fi (for Vite, `vite
|
|
|
97
114
|
| `run_benchmark_batch` / `get_automation_status` | Measure variants in the app with Bench. |
|
|
98
115
|
| `get_batch_report` / `compare_reports` | Read or compare saved runs. |
|
|
99
116
|
| `measure_renders` | Count renders in one interaction. |
|
|
117
|
+
| `sim_farm_acquire` / `sim_farm_release` / `sim_farm_finish` | Take farm sims for a run, give them back and end the run. |
|
|
118
|
+
| `sim_farm_action` | Open a link, take a still or relaunch the app on your farm sims. |
|
|
119
|
+
| `sim_farm_lock` | Hold `perf-slot` for fair speed tests on one farm sim. |
|
|
120
|
+
| `sim_farm_report` | Tell the person how each idea is going. |
|
|
100
121
|
| `navigate` | Open a route in the app. |
|
|
@@ -90,7 +90,7 @@ Then measure every valid variant the same way. Screen them at a few loads (one l
|
|
|
90
90
|
The ceiling is the score.
|
|
91
91
|
|
|
92
92
|
- On web, use `find_web_ceiling`. It raises the load about 30% a step until the frame rate misses the target, then narrows the gap to a few percent. It has no cap by default (up to 1,000,000,000).
|
|
93
|
-
- On React Native, ramp the count with `run_benchmark_batch` cases.
|
|
93
|
+
- On React Native, ramp the count with `run_benchmark_batch` cases. With many ideas on iOS sims, use the Sim Farm (see MEASURING.md): many sims check looks, and one quiet sim measures speed.
|
|
94
94
|
- Record the highest pass and the lowest miss. A single miss can be noise; run the boundary again before you call it.
|
|
95
95
|
- If nothing missed, the score is "at least" that load. Raise the max and run again.
|
|
96
96
|
- **Rank on the score.** Dropped frames, frame times, CPU, memory and profiles explain why a variant won. They only rank when the contract says so.
|
|
@@ -143,6 +143,16 @@ Then report:
|
|
|
143
143
|
- How you checked the looks.
|
|
144
144
|
- Why you stopped: show the wall check (the last 3 rounds, their families, the research, and why each idea left in the bank won't help). Say "best found", not "the maximum possible".
|
|
145
145
|
|
|
146
|
+
## Free and Pro
|
|
147
|
+
|
|
148
|
+
Every tool here works on a free Buoy account. That includes the farm, every speed test and every report.
|
|
149
|
+
|
|
150
|
+
- **Free shows what is faster.** You get fps, CPU, memory, jank, frame gaps, time totals, render totals and the winner.
|
|
151
|
+
- **Pro shows why.** It names the components that re-render and why. It names the scripts in slow frames. It shows which code used the JS thread (`get_js_thread_top`'s table, `get_js_thread_origin_detail`). `measure_renders` and `traceDir` are Pro too.
|
|
152
|
+
- **On free, find the why yourself.** Read the code, profile by hand and test your guesses as variants. The loop is the same. It takes more rounds.
|
|
153
|
+
- **A free result can end with a 🔒 note.** It says what Pro would show for this run. Tell the user once, in one line, then keep working. Never stop the loop for it, and never ask again. Later results show a one-line version; don't repeat it.
|
|
154
|
+
- Every Saturday and Sunday a free account gets everything Pro does.
|
|
155
|
+
|
|
146
156
|
## Rules
|
|
147
157
|
|
|
148
158
|
- Every variant does the same work: same items, same look, same behavior. Drawing fewer items, skipping frames, or anything that makes the meter read high without the user seeing it, is cheating and can't win.
|
package/dist/camera-F5Q7WMHF.mjs
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
import{createRequire as e}from"module";const c=e(import.meta.url);import{cliPath as r,describeDevices as t,describeDiagnosis as i,describeInputs as o,proRequired as s,readEntitlement as m,run as n,setEntitlementFileForTests as u}from"./chunk-VKWX4XAY.mjs";import"./chunk-MTTWR4T7.mjs";export{r as cliPath,t as describeDevices,i as describeDiagnosis,o as describeInputs,s as proRequired,m as readEntitlement,n as run,u as setEntitlementFileForTests};
|
package/dist/chunk-MHL43LJN.mjs
DELETED
|
@@ -1,2 +0,0 @@
|
|
|
1
|
-
import{createRequire as S}from"module";const U=S(import.meta.url);import{proRequired as I,readEntitlement as x}from"./chunk-VKWX4XAY.mjs";import{FarmError as b,agentLabel as T,connectFarm as v,detectAgent as D,nodeLauncher as P}from"./chunk-Y7SNXKA2.mjs";import{z as t}from"zod";var F=r=>({content:[{type:"text",text:r}]});var l=r=>({isError:true,content:[{type:"text",text:r}]});var i=r=>F(JSON.stringify(r,null,2));var c=r=>x().isPro?null:l(I(r));var d=null;var y=()=>void 0;function h(){if(!d){d=D({clientName:y()}).catch(()=>void 0).then(r=>v({label:process.env.BUOY_AGENT_LABEL||(r?T(r):"Buoy MCP"),agent:r,launcher:P(process.argv[1],["farmd"])})).catch(r=>{d=null;throw r})}return d}async function n(r,e,s,o){return(await h()).call(r,e,{timeoutMs:s,requestId:o})}function u(r){if(r instanceof b&&r.code==="RUN_STOPPED"){return l('RUN_STOPPED: The person stopped this run in Buoy Desktop (reason=user). Stop work on it. Tell them: "You stopped this run in Sims." Start a new run only if they ask.')}if(r instanceof b)return l(`${r.code}: ${r.message}${r.next?`
|
|
2
|
-
Next: ${r.next}`:""}`);return l(`Sim Farm error: ${r?.message??r}`)}var f={leaseId:t.string().describe("The lease id from sim_farm_acquire."),epoch:t.number().int().describe("The epoch from sim_farm_acquire. A stale epoch is refused.")};var w=t.string().optional().describe("Any unique string for this call. Repeating the same call with the same requestId returns the first result instead of acting twice.");var g=t.string().describe("The runId from sim_farm_acquire.");function R(r){y=()=>r.server.getClientVersion()?.name;r.registerTool("sim_farm_status",{description:"Show the Sim Farm: how many farm simulators exist, which are awake or in use and by whom, free memory and disk, how many more sims fit, templates, leases, and recent events. Farm sims are slim iOS simulators (about 1.5 GB each instead of 5) that the farm makes from a template. Start here before sim_farm_acquire. Set `outside` to also list this Mac's other simulators, which the farm never touches.",inputSchema:{outside:t.boolean().optional().describe("Also list simulators the farm does not own.")}},async({outside:e})=>{try{return i(await n("status",{outside:!!e}))}catch(s){return u(s)}});r.registerTool("sim_farm_setup",{description:"Create or resize the Sim Farm. `create` makes a template sim from an app build and, optionally, a seed folder (signed-in app data); clones are made from it in about 4 s each. Dev-client apps (Expo) need `launchUrl` so they open on Metro instead of the launcher. `grow` / `shrink` change how many sims exist. Use `dryRun` first: it returns the plan, what slim mode turns off (push, StoreKit, app links, widgets, Spotlight\u2026) and whether the Mac has room. You own the pool: Buoy Desktop has no size control. sim_farm_acquire makes sims as needed; `shrink` the ones you no longer need so they stop using disk space.",inputSchema:{mode:t.enum(["create","grow","shrink"]).describe("create = new template; grow/shrink = pool size."),templateId:t.string().optional().describe("For grow/shrink. Defaults to the newest usable template."),count:t.number().int().min(1).max(64).optional().describe("For grow/shrink: how many sims to add or remove."),appPath:t.string().optional().describe("For create: path to a simulator .app build."),seedDir:t.string().nullable().optional().describe("For create: seed folder (Documents/, AsyncStorage/, Preferences/, seed.json). null = no seed."),launchUrl:t.string().optional().describe("For create: URL opened after each launch, e.g. exp+my-app://expo-development-client/?url=http%3A%2F%2Flocalhost%3A8081."),deviceType:t.string().optional().describe("For create: a simctl device type id. Default iPhone 17 Pro."),runtime:t.string().optional().describe("For create: a simctl runtime id. Default iOS 26.3."),profile:t.enum(["slim","stock"]).optional().describe("For create: slim (default) or stock."),metroPort:t.number().int().min(1).max(65535).optional().describe("For create: the Metro port the app loads from. Default 8802."),dryRun:t.boolean().optional().describe("Return the plan and its cost without changing anything."),requestId:w}},async e=>{const s=c("sim_farm_setup");if(s)return s;try{if(e.mode==="create"){const a={appPath:e.appPath,seedDir:e.seedDir,deviceType:e.deviceType??"com.apple.CoreSimulator.SimDeviceType.iPhone-17-Pro",runtime:e.runtime??"com.apple.CoreSimulator.SimRuntime.iOS-26-3",profile:e.profile??"slim",metroPort:e.metroPort??8802,...e.launchUrl?{launchUrl:e.launchUrl}:{}};if(!a.appPath)return l("create needs appPath.");if(a.seedDir===void 0)return l("create needs seedDir, or seedDir: null for an app with no seed.");if(e.dryRun)return i({wouldCreateTemplate:a,budget:await n("budget",{ramGB:6,diskGB:4})});return i(await n("setup",a,15*6e4,e.requestId))}let o=e.templateId;if(!o){const a=await n("status");o=a.templates.filter(p=>p.usable).pop()?.id;if(!o)return l("No usable template yet. Run sim_farm_setup with mode create first.")}const m=e.count??1;if(e.dryRun){const a=await n("budget",{ramGB:0,diskGB:e.mode==="grow"?2*m:0});return i({mode:e.mode,templateId:o,count:m,roomForMoreSims:a.roomFor,ok:a.ok,reason:a.reason})}return i(await n(e.mode,{templateId:o,count:m},30*6e4,e.requestId))}catch(o){return u(o)}});r.registerTool("sim_farm_acquire",{description:"Take farm simulators for your work. The person watches your work live in Buoy Desktop, so give a clear `goal` and a `labels` entry per sim. Returns a run (runId, deadlineAt), a lease (leaseId + epoch) and exact sims (UDID, name, and buoyDeviceId once the app ran: the 8-character tail that its list_devices id ends with). Parked sims are booted and new ones cloned as needed, within the Mac's memory and disk limits. This MCP keeps the lease alive while it runs; release it with sim_farm_finish when done. Only ever target the returned UDIDs: never `booted`, which picks a random simulator when many run. While working, send sim_farm_report when a sim starts, passes or fails. The run stops at deadlineAt unless you ask for more time with sim_farm_extend. If any call returns RUN_STOPPED, the person stopped you: stop and tell them.",inputSchema:{count:t.number().int().min(1).max(64).describe("How many sims. There is no fixed cap: the farm gives as many as this Mac's memory and disk allow. Use allowPartial to take what fits."),goal:t.string().max(200).optional().describe("One short sentence the person sees on the run card, e.g. 'Make the menu list scroll faster'."),labels:t.array(t.string().max(80)).optional().describe("One label per sim, in order, e.g. ['Idea 1: memo rows', 'Idea 2: flat list']."),kind:t.enum(["try-ideas","run-tests","reproduce-bug","explore"]).optional().describe("What sort of work this is."),runId:t.string().optional().describe("Add these sims to a run you already have, instead of starting a new one."),purpose:t.string().optional().describe("Old name for goal. Use goal."),templateId:t.string().optional().describe("Defaults to the newest usable template."),launch:t.boolean().optional().describe("Launch the app on each sim and wait for its Buoy id."),allowPartial:t.boolean().optional().describe("Return fewer sims than asked instead of failing."),waitSeconds:t.number().int().min(0).max(1800).optional().describe("Wait this long for sims to free up."),ttlMinutes:t.number().int().min(1).max(1440).optional().describe("How long the run may go. Default 15. Agents are capped (1 hour by default); ask for more later with sim_farm_extend."),requestId:w}},async e=>{const s=c("sim_farm_acquire");if(s)return s;try{let o=e.templateId;if(!o){const k=await n("status");o=k.templates.filter(_=>_.usable).pop()?.id;if(!o)return l("No usable template yet. Run sim_farm_setup with mode create first.")}const m=(e.goal??e.purpose??"").trim();if(!m)return l("Give a goal: one short sentence about what you are doing.");const a=await h();const p=await a.acquire({templateId:o,count:e.count,purpose:m,goal:m,labels:e.labels,kind:e.kind,runId:e.runId,launch:e.launch,allowPartial:e.allowPartial,waitMs:(e.waitSeconds??0)*1e3,ttlMs:(e.ttlMinutes??15)*6e4,requestId:e.requestId});return i(p)}catch(o){return u(o)}});r.registerTool("sim_farm_release",{description:"Give one lease's sims back. `fast` (default) stops the app and restores its seeded data in about 2 s; `full` rebuilds each sim from the template. Use `full` after anything changed OS settings (permissions, keychain, location) or after a crash. Releasing your last lease ends the run, unless `keepRun` is true. To end a run with a result, use sim_farm_finish instead.",inputSchema:{...f,reset:t.enum(["fast","full"]).optional(),keepRun:t.boolean().optional().describe("Keep the run open to take more sims into it later.")}},async e=>{try{const s=await h();return i(await s.release(e.leaseId,e.epoch,e.reset??"fast",e.keepRun===true))}catch(s){return u(s)}});r.registerTool("sim_farm_action",{description:"Act on sims in your lease: `screenshot` (returns file paths), `open-url` (deep link, pass `url`), `launch` the app, `reset` (restore seeded app data), or `rebuild` (fresh clone; the UDID changes, the result maps old to new).",inputSchema:{...f,udids:t.array(t.string()).min(1).describe("UDIDs from your lease."),action:t.enum(["screenshot","open-url","launch","reset","rebuild"]),url:t.string().optional().describe("For open-url."),requestId:w}},async e=>{const s=c("sim_farm_action");if(s)return s;try{return i(await n("action",{leaseId:e.leaseId,epoch:e.epoch,udids:e.udids,action:e.action,arg:e.url},10*6e4,e.requestId))}catch(o){return u(o)}});r.registerTool("sim_farm_lock",{description:"Hold or free a Mac-wide lock tied to your lease. `perf-slot` is for fair speed tests: while you hold it the farm boots, clones and screenshots nothing else, so measure only then. Other names guard shared test fixtures (e.g. `ask-buoy-gateway`, `shop`). Locks end with the lease.",inputSchema:{...f,name:t.string().regex(/^[a-z0-9-]{1,40}$/).describe("Lock name, e.g. perf-slot."),hold:t.boolean().describe("true = take it, false = free it."),waitSeconds:t.number().int().min(0).max(1800).optional()}},async e=>{const s=c("sim_farm_lock");if(s)return s;try{const o={leaseId:e.leaseId,epoch:e.epoch,name:e.name,waitMs:(e.waitSeconds??0)*1e3};return i(await n(e.hold?"lock":"unlock",o,(e.waitSeconds??0)*1e3+3e4))}catch(o){return u(o)}});r.registerTool("sim_farm_report",{description:"Tell the person how your run is going. They watch it live in Buoy Desktop. Send one when a sim starts work, passes, fails or needs help, with a short note. `counts` only when you know the full task list. Reports never add time. Pass an increasing `sequence` so a retry is never counted twice.",inputSchema:{runId:g,udid:t.string().optional().describe("The sim this is about. Leave out for the whole run."),state:t.enum(["setting-up","working","waiting","passed","failed","needs-help"]).optional(),note:t.string().max(200).optional().describe("Short and plain, e.g. 'Idea 3: 58 fps, best so far'."),counts:t.object({done:t.number().int().min(0),total:t.number().int().min(0),passed:t.number().int().min(0).optional(),failed:t.number().int().min(0).optional()}).optional(),resultSource:t.enum(["agent","test"]).optional().describe("'test' when a test runner decided pass or fail, 'agent' when you judged it. Default agent."),sequence:t.number().int().min(0).optional()}},async e=>{try{return i(await n("report",e,3e4))}catch(s){return u(s)}});r.registerTool("sim_farm_extend",{description:"Ask for more time on your run before deadlineAt. The farm never goes past the run's cap (capAt). It says how much you got.",inputSchema:{runId:g,minutes:t.number().int().min(1).max(1440).describe("Minutes from now.")}},async e=>{try{return i(await n("extend",e))}catch(s){return u(s)}});r.registerTool("sim_farm_finish",{description:"End your run with a result. Gives back every sim it still holds. The person sees the summary in Buoy Desktop, so make it one or two plain sentences, e.g. 'Idea 3 won: 52 \u2192 58 fps.'",inputSchema:{runId:g,outcome:t.enum(["passed","failed","mixed","unknown"]).optional(),summary:t.string().max(600).optional()}},async e=>{try{return i(await n("finish",e,6e4,`fin-${e.runId}`))}catch(s){return u(s)}})}async function q(){const{startFarmd:r}=await import("./src-XCBUDSR7.mjs");const e=await r({hostLabel:"buoy-mcp",idleExitMs:10*6e4,onExit:()=>process.exit(0)});if(!e)process.exit(0);for(const s of["SIGTERM","SIGINT"])process.on(s,()=>void e.stop(s))}export{R as registerSimFarmTools,q as runFarmd};
|
package/dist/chunk-OPHPYPJD.mjs
DELETED
|
@@ -1,146 +0,0 @@
|
|
|
1
|
-
import{createRequire as Ze}from"module";const vt=Ze(import.meta.url);import{createRequire as et}from"module";import{execFileSync as Se}from"child_process";import{join as de}from"path";import{closeSync as ke,linkSync as ve,mkdirSync as tt,openSync as xe,readFileSync as nt,renameSync as st,statSync as Ee,unlinkSync as Y,writeFileSync as Te,writeSync as rt}from"fs";import{tmpdir as at}from"os";import{pathToFileURL as ot}from"url";var it=`(() => {
|
|
2
|
-
const supported = (t) => {
|
|
3
|
-
try { return (PerformanceObserver.supportedEntryTypes || []).includes(t); } catch { return false; }
|
|
4
|
-
};
|
|
5
|
-
const frames = [], longTasks = [], loaf = [], shifts = [], events = [], observers = [];
|
|
6
|
-
const watch = (type, cb, extra) => {
|
|
7
|
-
if (!supported(type)) return false;
|
|
8
|
-
const o = new PerformanceObserver((l) => l.getEntries().forEach(cb));
|
|
9
|
-
o.observe(Object.assign({ type, buffered: false }, extra || {}));
|
|
10
|
-
observers.push([o, cb]);
|
|
11
|
-
return true;
|
|
12
|
-
};
|
|
13
|
-
const hasLong = watch("longtask", (e) => longTasks.push({ start: e.startTime, end: e.startTime + e.duration }));
|
|
14
|
-
const hasLoaf = watch("long-animation-frame", (e) => loaf.push({
|
|
15
|
-
startTime: e.startTime,
|
|
16
|
-
duration: e.duration,
|
|
17
|
-
blockingDuration: e.blockingDuration || 0,
|
|
18
|
-
styleAndLayoutStart: e.styleAndLayoutStart || 0,
|
|
19
|
-
scripts: (e.scripts || []).map((s) => {
|
|
20
|
-
const file = s.sourceURL ? s.sourceURL.split("?")[0].split("/").pop() : "";
|
|
21
|
-
const where = s.sourceFunctionName ? s.sourceFunctionName + (file ? " @ " + file : "") : file;
|
|
22
|
-
// Invokers like "FrameRequestCallback" say how a script ran, not which
|
|
23
|
-
// one; add the function and file when the browser gives them.
|
|
24
|
-
const name = s.invoker ? (where ? s.invoker + " \u2192 " + where : s.invoker) : where || "(anonymous)";
|
|
25
|
-
return { duration: s.duration, name, forced: s.forcedStyleAndLayoutDuration || 0 };
|
|
26
|
-
}),
|
|
27
|
-
}));
|
|
28
|
-
// 16 ms is Event Timing's smallest threshold; the default (104) hides most taps.
|
|
29
|
-
const hasEvents = watch("event", (e) => {
|
|
30
|
-
if (e.interactionId) events.push({
|
|
31
|
-
name: e.name, startTime: e.startTime, duration: e.duration,
|
|
32
|
-
processingStart: e.processingStart, processingEnd: e.processingEnd, interactionId: e.interactionId,
|
|
33
|
-
});
|
|
34
|
-
}, { durationThreshold: 16 });
|
|
35
|
-
// Click proof: which step selectors each real click landed inside, read
|
|
36
|
-
// at the moment of the click (capture phase, before the page's handlers).
|
|
37
|
-
const clickTargets = Array.isArray(window.__buoyBenchClickTargets) ? window.__buoyBenchClickTargets : [];
|
|
38
|
-
const clicks = [], keys = [];
|
|
39
|
-
// Only real (trusted) input counts as proof; a click the page fakes in
|
|
40
|
-
// script doesn't. Input after the run's end doesn't count either.
|
|
41
|
-
const onClick = (e) => {
|
|
42
|
-
if (!e.isTrusted || ended) return;
|
|
43
|
-
const t = e.target;
|
|
44
|
-
const hit = clickTargets.filter((sel) => { try { return t && t.closest && t.closest(sel); } catch { return false; } });
|
|
45
|
-
clicks.push({ t: performance.now(), hit });
|
|
46
|
-
};
|
|
47
|
-
const onKey = (e) => {
|
|
48
|
-
if (!e.isTrusted || ended) return;
|
|
49
|
-
keys.push({ t: performance.now(), key: e.key, ctrl: e.ctrlKey, shift: e.shiftKey, alt: e.altKey, meta: e.metaKey });
|
|
50
|
-
};
|
|
51
|
-
// Expect steps, judged inside end() so no page work can slip in between
|
|
52
|
-
// the end of the run and the check.
|
|
53
|
-
const expects = Array.isArray(window.__buoyBenchExpects) ? window.__buoyBenchExpects : [];
|
|
54
|
-
let expectResults = null;
|
|
55
|
-
document.addEventListener("click", onClick, true);
|
|
56
|
-
document.addEventListener("keydown", onKey, true);
|
|
57
|
-
const hasShift = watch("layout-shift", (e) => shifts.push({ startTime: e.startTime, value: e.value, hadRecentInput: e.hadRecentInput }));
|
|
58
|
-
let hidden = document.visibilityState === "hidden";
|
|
59
|
-
const onVis = () => { if (document.visibilityState === "hidden") hidden = true; };
|
|
60
|
-
document.addEventListener("visibilitychange", onVis);
|
|
61
|
-
// Latched: a resize or zoom that's undone before stop still changed the work.
|
|
62
|
-
let resized = false;
|
|
63
|
-
const onResize = () => { resized = true; };
|
|
64
|
-
window.addEventListener("resize", onResize);
|
|
65
|
-
// A pixel-ratio change (moving to another screen) may not fire resize.
|
|
66
|
-
let dprQuery = null;
|
|
67
|
-
try {
|
|
68
|
-
dprQuery = matchMedia("(resolution: " + window.devicePixelRatio + "dppx)");
|
|
69
|
-
dprQuery.addEventListener("change", onResize);
|
|
70
|
-
} catch {}
|
|
71
|
-
let raf = 0;
|
|
72
|
-
const tick = (t) => { frames.push(t); raf = requestAnimationFrame(tick); };
|
|
73
|
-
raf = requestAnimationFrame(tick);
|
|
74
|
-
const start = performance.now();
|
|
75
|
-
// Trace markers: the driver counts Chrome's frames between these two.
|
|
76
|
-
try { performance.mark("__buoy_bench_start"); } catch {}
|
|
77
|
-
const domStart = document.getElementsByTagName("*").length;
|
|
78
|
-
let ended = 0;
|
|
79
|
-
window.__buoyBench = {
|
|
80
|
-
// Two steps so the driver can read Chrome's counters between them:
|
|
81
|
-
// end() freezes the end time at once; collect() then waits up to two
|
|
82
|
-
// frames (capped at 300 ms) so an input's entry, which only arrives after
|
|
83
|
-
// its paint, isn't lost. Entries after the end are filtered out later.
|
|
84
|
-
end: () => {
|
|
85
|
-
if (ended) return ended;
|
|
86
|
-
ended = performance.now();
|
|
87
|
-
try { performance.mark("__buoy_bench_end"); } catch {}
|
|
88
|
-
cancelAnimationFrame(raf);
|
|
89
|
-
expectResults = expects.map((x) => {
|
|
90
|
-
const n = document.querySelector(x.selector);
|
|
91
|
-
const full = n ? (n.textContent || "") : null;
|
|
92
|
-
// Match against the full text here; only a short snippet goes back.
|
|
93
|
-
return { selector: x.selector, text: x.text == null ? null : x.text, found: !!n, matched: full != null && (x.text == null || full.includes(x.text)), content: full == null ? null : full.slice(0, 200) };
|
|
94
|
-
});
|
|
95
|
-
return ended;
|
|
96
|
-
},
|
|
97
|
-
collect: () => new Promise((resolve) => {
|
|
98
|
-
const end = window.__buoyBench.end();
|
|
99
|
-
let done = false;
|
|
100
|
-
const finish = () => {
|
|
101
|
-
if (done) return;
|
|
102
|
-
done = true;
|
|
103
|
-
document.removeEventListener("visibilitychange", onVis);
|
|
104
|
-
window.removeEventListener("resize", onResize);
|
|
105
|
-
document.removeEventListener("click", onClick, true);
|
|
106
|
-
document.removeEventListener("keydown", onKey, true);
|
|
107
|
-
try { dprQuery && dprQuery.removeEventListener("change", onResize); } catch {}
|
|
108
|
-
for (const [o, cb] of observers) { o.takeRecords().forEach(cb); o.disconnect(); }
|
|
109
|
-
resolve({
|
|
110
|
-
start, end, frames,
|
|
111
|
-
longTasks: hasLong ? longTasks : null,
|
|
112
|
-
loaf: hasLoaf ? loaf : null,
|
|
113
|
-
shifts: hasShift ? shifts : null,
|
|
114
|
-
events: hasEvents ? events : null,
|
|
115
|
-
clicks,
|
|
116
|
-
keys,
|
|
117
|
-
expectResults,
|
|
118
|
-
domStart,
|
|
119
|
-
domEnd: document.getElementsByTagName("*").length,
|
|
120
|
-
hidden,
|
|
121
|
-
resized,
|
|
122
|
-
});
|
|
123
|
-
};
|
|
124
|
-
requestAnimationFrame(() => requestAnimationFrame(finish));
|
|
125
|
-
setTimeout(finish, 300);
|
|
126
|
-
}),
|
|
127
|
-
};
|
|
128
|
-
return true;
|
|
129
|
-
})()`;var lt=`(() => (window.__buoyBench ? window.__buoyBench.end() : null))()`;var ct=`(() => (window.__buoyBench ? window.__buoyBench.collect() : null))()`;var ut=`new Promise((resolve) => {
|
|
130
|
-
let n = 0, t0 = 0;
|
|
131
|
-
const tick = (t) => {
|
|
132
|
-
if (!t0) t0 = t;
|
|
133
|
-
n++;
|
|
134
|
-
if (t - t0 >= 1000) resolve(((n - 1) / (t - t0)) * 1000);
|
|
135
|
-
else requestAnimationFrame(tick);
|
|
136
|
-
};
|
|
137
|
-
requestAnimationFrame(tick);
|
|
138
|
-
})`;function Pe(e,r,t=[]){const a=e.median;const s=[];const n=[];const i=(u,l,d)=>{if(l==null)return;if(d==null)n.push(u);else if(d>l)s.push(`${u} ${d} > ${l}`)};i("droppedPct",r.droppedPct,e.droppedMin!=null?a?.droppedPct:null);const o=e.runs.filter(u=>!u.failed&&u.fps!=null);const c=t.some(u=>"click"in u||"press"in u||"type"in u);const p=o.map(u=>u.interactions==null||!u.inputCaptureComplete?null:u.worstInteractionMs??16);i("inputMs",r.inputMs,c&&p.length&&p.every(u=>u!=null)?Math.max(...p):null);const h=o.map(u=>Math.max(u.frameGapMaxMs??0,u.edgeSilenceMs??0));i("freezeMs",r.freezeMs,h.length?Math.max(...h):null);i("mainThreadPct",r.mainThreadPct,a?.mainThreadPct);i("jsHeapMb",r.jsHeapMb,a?.heapMb);return{meets:Boolean(a)&&s.length===0&&n.length===0,over:s,unknown:n}}var x=e=>Math.round(e*10)/10;var Re=`(() => {
|
|
139
|
-
try {
|
|
140
|
-
const gl = document.createElement("canvas").getContext("webgl2") || document.createElement("canvas").getContext("webgl");
|
|
141
|
-
if (!gl) return null;
|
|
142
|
-
const ext = gl.getExtension("WEBGL_debug_renderer_info");
|
|
143
|
-
return String(gl.getParameter(ext ? ext.UNMASKED_RENDERER_WEBGL : gl.RENDERER));
|
|
144
|
-
} catch { return null; }
|
|
145
|
-
})()`;function fe(e){return/swiftshader|llvmpipe|softpipe|software|microsoft basic render/i.test(e)}function _e(e,r){if(e.length===0)return null;const t=[...e].sort((s,n)=>s-n);const a=Math.ceil(r/100*t.length)-1;return t[Math.min(t.length-1,Math.max(0,a))]}var dt=[30,60,75,90,120,144,165,240];function Ce(e){for(const r of dt)if(Math.abs(e-r)/r<=.08)return r;return Math.max(1,Math.round(e))}function he(e,r){let t=r>>>0;const a=()=>{t=t+1831565813>>>0;let n=t;n=Math.imul(n^n>>>15,n|1);n^=n+Math.imul(n^n>>>7,n|61);return((n^n>>>14)>>>0)/4294967296};const s=[...e];for(let n=s.length-1;n>0;n--){const i=Math.floor(a()*(n+1));[s[n],s[i]]=[s[i],s[n]]}return s}function Ae(e,r,t){const a=new URL(e);const s=r.path??t;if(s)a.pathname=s.startsWith("/")?s:`${a.pathname.replace(/\/$/,"")}/${s}`;for(const[n,i]of Object.entries(r.params??{}))a.searchParams.set(n,i);return a.toString()}var ft=["STATE_PRESENTED_ALL","STATE_PRESENTED_PARTIAL","STATE_DROPPED"];function Le(e){const r=d=>e.find(M=>M.name===d&&M.ts!=null);const t=r("__buoy_bench_start");const a=r("__buoy_bench_end");if(!t||!a||a.ts<=t.ts)return null;if(t.pid!==a.pid)return null;const s=t.pid;const n={STATE_PRESENTED_ALL:0,STATE_PRESENTED_PARTIAL:1,STATE_DROPPED:2};const i=new Map;for(const d of e){if(d.name!=="PipelineReporter"||d.ph!=="b")continue;if(s!=null&&d.pid!==s)continue;if(d.ts==null||d.ts<t.ts||d.ts>a.ts)continue;const M=d.args?.frame_reporter;if(!M||M.frame_sequence==null)continue;const S=M.state;if(!ft.includes(S))continue;const T=`${M.layer_tree_host_id??""}:${M.frame_source??""}:${M.frame_sequence}`;const b=Boolean(M.affects_smoothness)&&S!=="STATE_PRESENTED_ALL";const A=i.get(T);if(!A)i.set(T,{state:S,fault:b});else{if(n[S]>n[A.state])A.state=S;A.fault=A.fault||b}}let o=0;let c=0;let p=0;let h=0;for(const d of i.values()){if(d.state==="STATE_PRESENTED_ALL")o++;else if(d.state==="STATE_PRESENTED_PARTIAL")c++;else p++;if(d.fault)h++}const u=o+c+p;if(u===0)return null;const l=(a.ts-t.ts)/1e6;return{full:o,partial:c,dropped:p,faults:h,shownFps:x((o+c)/l),droppedPct:x(h/u*100)}}function Fe(e,r){const t=e.split("+");const a=t[t.length-1];const s=new Set(t.slice(0,-1).map(o=>o.toLowerCase()));if(r.key!==a)return false;const n=a.toLowerCase();const i={ctrl:s.has("control")||n==="control",alt:s.has("alt")||n==="alt",meta:s.has("meta")||s.has("command")||n==="meta",shift:s.has("shift")||n==="shift"};if(Boolean(r.ctrl)!==i.ctrl||Boolean(r.alt)!==i.alt||Boolean(r.meta)!==i.meta)return false;if(!i.shift&&r.shift&&a.length!==1)return false;if(i.shift&&!r.shift)return false;return true}function Ne(e){const r=[];for(const t of e){const a=t.startTime+t.duration;const s=r.find(i=>i.interactionId===t.interactionId&&Math.abs(i.startTime+i.duration-a)<=8);if(!s){r.push({...t});continue}const n=s.startTime+s.duration;s.startTime=Math.min(s.startTime,t.startTime);s.processingStart=Math.min(s.processingStart,t.processingStart);s.processingEnd=Math.max(s.processingEnd,t.processingEnd);s.duration=Math.max(n,a)-s.startTime;if(t.processingEnd-t.processingStart>0)s.name=t.name}return r}function De(e,r){const{start:t,end:a}=e;const s=Math.max(1,a-t);const n=[];for(let m=1;m<e.frames.length;m++)n.push(e.frames[m]-e.frames[m-1]);const i=1e3/r;const o=_e(n,95);let c=null;if(e.longTasks){let m=0;for(const k of e.longTasks){const q=Math.min(k.end,a)-Math.max(k.start,t);if(q>0)m+=q}c=x(Math.min(100,m/s*100))}let p=null;let h=null;let u=null;let l=[];let d=0;if(e.loaf){let m=0;let k=0;let q=0;const te=new Map;for(const E of e.loaf){if(E.startTime>=t&&E.startTime<=a&&E.startTime+E.duration>a)d++;if(E.startTime<t||E.startTime+E.duration>a)continue;m+=E.blockingDuration;if(E.styleAndLayoutStart>E.startTime)k+=Math.max(0,E.startTime+E.duration-E.styleAndLayoutStart);for(const L of E.scripts){q+=L.forced;const f=te.get(L.name)??{ms:0,count:0};f.ms+=L.duration;f.count++;te.set(L.name,f)}}p=x(m);h=x(k);u=x(q);l=[...te.entries()].map(([E,L])=>({name:E,ms:x(L.ms),count:L.count})).sort((E,L)=>L.ms-E.ms).slice(0,5)}let M=null;if(e.shifts){let m=0;for(const k of e.shifts)if(!k.hadRecentInput&&k.startTime>=t&&k.startTime<=a)m+=k.value;M=Math.round(m*1e3)/1e3}let S=null;let T=null;if(e.events){const m=Ne(e.events.filter(k=>k.startTime>=t&&k.startTime<=a));S=new Set(m.map(k=>k.interactionId)).size;for(const k of m)if(!T||k.duration>T.duration)T=k}const b=T;const A=e.frames;const ee=[...(e.clicks??[]).map(m=>m.t),...(e.keys??[]).map(m=>m.t)];const I=ee.length?Math.max(...ee):null;const se=I==null||A.filter(m=>m>I).length>=2;const ae=A.length===0?s:Math.max(0,A[0]-t,a-A[A.length-1]);return{edgeSilenceMs:x(ae),frameGapCount:n.length,inputCaptureComplete:se,resized:Boolean(e.resized),interactions:S,worstInteractionMs:b?x(b.duration):null,worstInputDelayMs:b?x(Math.max(0,b.processingStart-b.startTime)):null,worstProcessingMs:b?x(Math.max(0,b.processingEnd-b.processingStart)):null,worstPresentationMs:b?x(Math.max(0,b.startTime+b.duration-b.processingEnd)):null,fps:x(e.frames.length/s*1e3),frameGapP95Ms:o==null?null:x(o),frameGapMaxMs:n.length?x(Math.max(...n)):null,longFrames:n.filter(m=>m>i*1.5).length,longTaskPct:c,loafBlockingMs:p,styleLayoutMs:h,forcedLayoutMs:u,topScripts:l,loafCutOff:d,layoutShift:M,domNodesStart:e.domStart,domNodesEnd:e.domEnd}}function Be(e,r,t){const a=o=>o in e&&o in r?r[o]-e[o]:null;const s=o=>{const c=a(o);return c==null?null:x(c*1e3)};const n=s("TaskDuration");const i=s("Timestamp")??t;return{mainThreadPct:n==null?null:x(n/Math.max(1,i)*100),scriptMs:s("ScriptDuration"),styleMs:s("RecalcStyleDuration"),layoutMs:s("LayoutDuration"),layoutCount:a("LayoutCount"),styleCount:a("RecalcStyleCount"),heapMb:"JSHeapUsedSize"in r?x(r.JSHeapUsedSize/(1024*1024)):null}}function Ie(e,r){const t=[];if(e.droppedPct!=null&&e.droppedPct>=5)t.push(`${e.droppedPct}% of frames dropped or partial`);if(e.fps!=null&&e.fps<r*.95)t.push("Low main-thread frame rate");if(e.frameGapCount&&e.longFrames/e.frameGapCount>=.02)t.push("Frequent long frames");const a=Math.max(e.frameGapMaxMs??0,e.edgeSilenceMs??0);if(a>=100)t.push(`Freeze of ${Math.round(a)} ms`);if(e.mainThreadPct!=null&&e.mainThreadPct>=80)t.push("Main thread near full");if(e.longTaskPct!=null&&e.longTaskPct>=20)t.push("Long tasks block the main thread");if(e.forcedLayoutMs!=null&&e.forcedLayoutMs>=50)t.push(`Forced layout in scripts (${Math.round(e.forcedLayoutMs)} ms)`);if(e.layoutShift!=null&&e.layoutShift>=.1)t.push(`Layout shifts (${e.layoutShift})`);if(e.worstInteractionMs!=null&&e.worstInteractionMs>=200){t.push(`Slow response to input (${Math.round(e.worstInteractionMs)} ms)`)}return t}function pe(e,r){const t=e.filter(c=>!c.failed&&c.fps!=null);const a=[...t].sort((c,p)=>(c.fps??0)-(p.fps??0));const s=t.filter(c=>c.droppedPct!=null).length;const n=t.length>0&&s===t.length;const i=n?[...t].sort((c,p)=>c.droppedPct-p.droppedPct):a;const o=i.length?i[Math.floor((i.length-1)/2)]:null;return{caseName:e[0]?.caseName??"",cpu:e[0]?.cpu??1,validRuns:t.length,plannedRuns:e.length,isBaseline:false,deltaDroppedPct:null,tracedRuns:s,...(()=>{const c=n?t.map(h=>h.droppedPct):[];const p=t.map(h=>h.worstInteractionMs).filter(h=>h!=null);return{droppedMin:c.length?Math.min(...c):null,droppedMax:c.length?Math.max(...c):null,inputMin:p.length?Math.min(...p):null,inputMax:p.length?Math.max(...p):null}})(),runs:e,median:o,fpsMin:a.length?a[0].fps:null,fpsMax:a.length?a[a.length-1].fps:null,flags:o?Ie(n?o:{...o,droppedPct:null},r):[]}}var ze="Playwright isn't installed in this project. Install it, then run again:\n npm i -D playwright && npx playwright install chromium";async function Oe(e){const r=et(de(e,"noop.js"));for(const t of["playwright","playwright-core"]){let a;try{a=r.resolve(t)}catch{continue}const s=await import(ot(a).href);const n=s.chromium??s.default?.chromium;if(n)return n}throw new Error(`${ze}
|
|
146
|
-
(Searched from ${e}. Pass projectRoot to look elsewhere.)`)}async function G(e,r,t){let a;const s=new Promise(n=>{a=setTimeout(()=>n("timeout"),r)});try{const n=await Promise.race([e,s]);if(n==="timeout"){e.catch(()=>{});return await t()}return n}finally{clearTimeout(a)}}function ht(e){try{return Se("git",["rev-parse","HEAD"],{cwd:e,stdio:["ignore","pipe","ignore"],timeout:3e3}).toString().trim()}catch{return null}}function pt(e){try{const r=Se("git",["status","--porcelain"],{cwd:e,stdio:["ignore","pipe","ignore"],timeout:5e3});return r.toString().trim().length>0}catch{return null}}async function U(e,r){if(!e)return true;return G(e.close().then(()=>true,()=>false),r,async()=>false)}function je(e){if(!e.cases?.length)throw new Error("Give at least one case.");const r=new Set;for(const i of e.cases){if(!i.name?.trim())throw new Error("Every case needs a name.");if(r.has(i.name))throw new Error(`Two cases are named "${i.name}". Give each case its own name.`);r.add(i.name)}const t=(i,o,c,p)=>{if(i==null)return;if(!Number.isInteger(i)||i<c||i>p)throw new Error(`${o} must be a whole number from ${c} to ${p}.`)};const a=(i,o,c)=>{if(i==null)return;if(!Number.isFinite(i)||i<c)throw new Error(`${o} must be a number of at least ${c}.`)};const s=(i,o,c,p)=>{if(i==null)return;if(!Number.isFinite(i)||i<c||i>p)throw new Error(`${o} must be from ${c} to ${p}.`)};t(e.runsPerCase,"runsPerCase",1,10);s(e.durationMs,"durationMs",500,12e4);s(e.settleMs,"settleMs",0,6e4);s(e.refreshHz,"refreshHz",1,500);const n=e.cpuSlowdown??[];for(const i of n)s(i,"each cpuSlowdown rate",1,20);if(new Set(n).size!==n.length)throw new Error("cpuSlowdown has the same rate twice.");if(e.viewport){t(e.viewport.width,"viewport.width",200,8e3);t(e.viewport.height,"viewport.height",200,8e3);s(e.viewport.deviceScaleFactor,"viewport.deviceScaleFactor",.5,4)}t(e.extraRoundsWhenClose,"extraRoundsWhenClose",0,10);s(e.maxBatchMs,"maxBatchMs",1e4,6*60*6e4);for(const[i,o]of Object.entries(e.budgets??{}))s(o,`budgets.${i}`,0,1e9);for(const i of e.steps??[]){if("wait"in i)s(i.wait,"a wait step",0,6e4);if("scroll"in i)s(i.scroll,"a scroll step",-1e5,1e5)}new URL(e.url)}function re(e){return e.replace(/\u001b\[[0-9;]*m/g,"").split("\n")[0].trim().slice(0,160)}var mt=["disabled-by-default-devtools.timeline.frame","blink.user_timing"];var wt=["devtools.timeline","disabled-by-default-devtools.timeline","disabled-by-default-devtools.timeline.frame","blink.user_timing","loading","latencyInfo","v8.execute"];var me=15e7;var we=e=>new Promise(r=>setTimeout(r,e));async function Ge(e,r){const t=new Map;for(const a of r){if("expect"in a)continue;const s="click"in a?a.click:"type"in a?a.type.selector:null;if(!s||t.has(s))continue;const n=await e.$(s);const i=n?await n.boundingBox():null;if(!i)throw new Error(`step target ${s} isn't on the page (or isn't visible)`);t.set(s,{x:i.x+i.width/2,y:i.y+i.height/2})}return t}async function gt(e,r,t){for(const a of r){if("click"in a){const s=t.get(a.click);await e.mouse.click(s.x,s.y)}else if("press"in a)await e.keyboard.press(a.press);else if("type"in a){const s=t.get(a.type.selector);await e.mouse.click(s.x,s.y);await e.keyboard.type(a.type.text)}else if("scroll"in a)await e.mouse.wheel(0,a.scroll);else if("wait"in a)await we(a.wait)}}function Ue(e,r,t=[]){const a=[...r].sort((h,u)=>h.t-u.t);const s=[...t].sort((h,u)=>h.t-u.t);let n=0;let i=0;let o=-Infinity;const c=h=>{while(n<a.length){const u=a[n++];if(u.t>=o&&u.hit.includes(h)){o=u.t;return true}}return false};const p=h=>{while(i<s.length){const u=s[i++];if(u.t>=o&&Fe(h,u)){o=u.t;return true}}return false};for(const[h,u]of e.entries()){if("click"in u&&!c(u.click)){return`step ${h+1} (click ${u.click}) has no real click on its target, in order (moved, covered, or ignored)`}if("type"in u){if(!c(u.type.selector))return`step ${h+1} (type into ${u.type.selector}) has no real click on its target`;for(const l of u.type.text){if(!p(l))return`step ${h+1} (type "${u.type.text}") didn't deliver the key "${l}"`}}if("press"in u&&!p(u.press))return`step ${h+1} (press ${u.press}) didn't deliver that key`}return null}async function yt(e,r){for(const t of r){if(!("expect"in t))continue;const{selector:a,text:s}=t.expect;const n=await e.evaluate(`(() => { const n = document.querySelector(${JSON.stringify(a)}); if (!n) return false; ${s==null?"return true;":`return (n.textContent || "").includes(${JSON.stringify(s)});`} })()`);if(n)return s==null?a:`${a} contains "${s}"`}return null}async function Mt(e,r){for(const[t,a]of r){const s=await e.$(t);const n=s?await s.boundingBox():null;if(!n)return`step target ${t} was gone after the run`;const i=n.x+n.width/2;const o=n.y+n.height/2;if(Math.abs(i-a.x)>4||Math.abs(o-a.y)>4){return`step target ${t} moved during the run, so a click may have missed it`}}return null}function He(e){for(const r of e){if(!r.found)return`expected ${r.selector} at the end of the run, but it isn't there`;const t=r.matched??(r.text==null||(r.content??"").includes(r.text));if(!t){return`expected ${r.selector} to contain "${r.text}" at the end of the run, got "${(r.content??"").slice(0,60)}"`}}return null}async function qe(e){const{metrics:r}=await e.send("Performance.getMetrics");return Object.fromEntries(r.map(t=>[t.name,t.value]))}async function bt(e,r=()=>{}){je(e);const t=await We(e.lockWaitMs??20*6e4,r);const a={browserLeftRunning:false};try{return await $t(e,r,a)}finally{t(a.browserLeftRunning)}}async function We(e,r=()=>{},t=2e3){const a=Date.now()+Math.max(0,e);let s=false;for(;;){try{return Je()}catch(n){const i=n?.message??String(n);const o=/Another web benchmark|already running/.test(i);if(!o||Date.now()+t>a)throw n;if(!s){r("another web benchmark is running; waiting for it to finish");s=true}await new Promise(c=>setTimeout(c,t))}}}var Z=de(at(),"buoy-web-bench.lock");var ge=false;function Je(){if(ge){throw new Error("A web benchmark is already running in this process (or left a stuck browser). Wait, or restart the MCP server.")}const e=`${process.pid}:${Date.now()}:${Math.random().toString(36).slice(2)}`;const r=JSON.stringify({pid:process.pid,token:e,startedAt:new Date().toISOString()});const t=()=>{const n=`${Z}.${e.replace(/[^\w]/g,"")}.tmp`;try{Te(n,r);ve(n,Z);return true}catch{return false}finally{try{Y(n)}catch{}}};const a=()=>{try{return JSON.parse(nt(Z,"utf8"))}catch{return null}};const s=n=>{if(!n)return false;try{process.kill(n,0);return true}catch{return false}};if(!t()){const n=a();if(!n)throw new Error("Another web benchmark is starting. Wait for it to finish.");if(s(n.pid)){throw new Error(`Another web benchmark is running (pid ${n.pid}, since ${n.startedAt}). Two at once would skew both; wait for it to finish.`)}const i=`${Z}.cleanup`;let o=null;try{if(Date.now()-Ee(i).mtimeMs>3e4){const c=`${i}.${e.replace(/[^\w]/g,"")}.old`;st(i,c);if(Date.now()-Ee(c).mtimeMs>3e4)Y(c);else{try{ve(c,i)}catch{}Y(c)}}}catch{}try{o=xe(i,"wx")}catch{throw new Error("Another web benchmark is clearing an old lock. Try again in a moment.")}try{const c=a();if(c?.token===n.token)Y(Z);if(!t())throw new Error("Another web benchmark just started. Wait for it to finish.")}finally{ke(o);try{Y(i)}catch{}}}ge=true;return(n=false)=>{if(n)return;ge=false;if(a()?.token===e){try{Y(Z)}catch{}}}}async function $t(e,r,t){const a=e.runsPerCase??3;const s=Math.max(500,e.durationMs??5e3);const n=Math.max(0,e.settleMs??1e3);const i=e.frameTrace!==false;const o=(e.cpuSlowdown?.length?e.cpuSlowdown:[1]).map(f=>Math.max(1,f));let c=e.refreshHz??60;const p={width:e.viewport?.width??1280,height:e.viewport?.height??800,deviceScaleFactor:e.viewport?.deviceScaleFactor??1};const h=[];const u=[];let l=null;if(a<2)h.push("runsPerCase=1 gives no spread; use 3 or more before choosing.");const d=await Oe(e.projectRoot??process.cwd());const M=await d.launch({headless:!e.headed,args:["--enable-gpu"]});const S=[];const T=[];const b=Date.now();const A=new Date(b).toISOString();const ee=e.maxBatchMs??30*6e4;let I=false;let se=false;const ae={commit:ht(e.projectRoot??process.cwd()),dirty:pt(e.projectRoot??process.cwd())};try{const f={viewport:{width:p.width,height:p.height},deviceScaleFactor:p.deviceScaleFactor};const $=await G(M.newContext(f),3e4,async()=>{t.browserLeftRunning=true;throw new Error("Chromium didn't open a page in 30 s. Restart the MCP server and try again.")});const R=3e4+15e3+n+s+6e4;const V=async(g,w,v,_)=>{const C={};return G(F(C,g,w,v,_),R,async()=>{C.cancelled=true;if(!C.ctx)I=true;else if(!await U(C.ctx,1e4))I=true;return{...W(g.name,w,v),failed:`run took over ${Math.round(R/1e3)} s (the page may be stuck in a loop)`}})};const W=(g,w,v)=>({caseName:g,cpu:w,round:v,fps:null,frameGapP95Ms:null,frameGapMaxMs:null,longFrames:0,longTaskPct:null,loafBlockingMs:null,styleLayoutMs:null,forcedLayoutMs:null,topScripts:[],layoutShift:null,domNodesStart:null,domNodesEnd:null,mainThreadPct:null,scriptMs:null,styleMs:null,layoutMs:null,layoutCount:null,styleCount:null,heapMb:null,pageErrors:[]});const F=async(g,w,v,_,C)=>{const J=await M.newContext(f);g.ctx=J;if(g.cancelled){await U(J,1e4);throw new Error("run was cancelled during setup")}const y=await J.newPage();const z=[];y.on("pageerror",P=>z.push(String(P?.message??P)));const N={...W(w.name,v,_),pageErrors:z};try{const P=await J.newCDPSession(y);await P.send("Performance.enable",{timeDomain:"timeTicks"});if(v>1)await P.send("Emulation.setCPUThrottlingRate",{rate:v});const oe=await y.goto(Ae(e.url,w,e.path),{waitUntil:"load",timeout:3e4});if(oe&&oe.status()>=400)return{...N,failed:`HTTP ${oe.status()}`};if(e.readySelector)await y.waitForSelector(e.readySelector,{state:"attached",timeout:15e3});await we(n);const Me=C||i;if(Me){await P.send("Tracing.start",{transferMode:"ReturnAsStream",categories:(C?wt:mt).join(",")})}const ie=e.steps?.length?await Ge(y,e.steps):new Map;if(e.steps?.length){const K=await yt(y,e.steps);if(K)return{...N,failed:`expect ${K} already held before the steps, so it can't prove they worked`};const Q=[...ie.keys()];const j=e.steps.flatMap(X=>"expect"in X?[X.expect]:[]);await y.evaluate(`window.__buoyBenchClickTargets = ${JSON.stringify(Q)}; window.__buoyBenchExpects = ${JSON.stringify(j)}; true`)}const le=await qe(P);await y.evaluate(it);const be=Date.now();if(e.steps?.length)await gt(y,e.steps,ie);const $e=s-(Date.now()-be);if($e>0)await we($e);await y.evaluate(lt);const ce=await qe(P);const Xe=Date.now()-be;const O=await y.evaluate(ct);let D=null;if(Me){const K=new Promise(B=>P.on("Tracing.tracingComplete",ne=>B(ne.stream)));await P.send("Tracing.end");const Q=await K;const j=C?xe(C,"w"):null;const X=[];let ue=0;try{for(;;){const B=await P.send("IO.read",{handle:Q});const ne=B.base64Encoded?Buffer.from(B.data,"base64").toString("utf8"):B.data;ue+=ne.length;if(j!=null)rt(j,ne);if(ue<=me)X.push(ne);else X.length=0;if(B.eof)break}}finally{await P.send("IO.close",{handle:Q}).catch(()=>{});if(j!=null)ke(j)}if(C)u.push(C);if(ue>me){h.push(`Trace for ${w.name} was over ${Math.round(me/1e6)} MB, so its frames weren't counted. A page that busy shows its problem in the table already.`)}else{try{const B=JSON.parse(X.join(""));D=Le(Array.isArray(B)?B:B.traceEvents??[])}catch{D=null}}}if(!O)return{...N,failed:"probe returned nothing (page navigated away?)"};if(O.hidden)return{...N,failed:"page was hidden during the run"};if(O.resized)return{...N,failed:"the window size or zoom changed during the run"};if(z.length&&!e.allowPageErrors){return{...N,failed:`page error: ${re(z[0])}`}}if(e.steps?.length){const K=Ue(e.steps,O.clicks??[],O.keys??[]);if(K)return{...N,failed:K};const Q=He(O.expectResults??[]);if(Q)return{...N,failed:Q};const j=await Mt(y,ie);if(j)return{...N,failed:j}}const Ye="Timestamp"in le&&"Timestamp"in ce?x((ce.Timestamp-le.Timestamp)*1e3):null;return{...N,...De(O,c),...Be(le,ce,Xe),probeSpanMs:x(O.end-O.start),cdpSpanMs:Ye,shownFps:D?.shownFps??null,droppedFrames:D?.faults??null,frameCounts:D?{full:D.full,partial:D.partial,dropped:D.dropped,faults:D.faults}:null,droppedPct:D?.droppedPct??null}}catch(P){return{...N,failed:re(P?.message??String(P))}}finally{await U(y,5e3);if(!await U(J,1e4))I=true}};{const g=await G($.newPage(),15e3,async()=>{throw new Error("Chromium didn't open the GPU check page in 15 s.")});try{l=await G(g.evaluate(Re),5e3,async()=>null)??null}catch{l=null}finally{await U(g,5e3)}if(l)r(`GPU ${l}`);if(l&&fe(l)){h.push(`Chrome is drawing with a software GPU (${l}). Canvas, WebGL and Skia pages run several times slower than on a real screen, so their numbers mean little. Try headed: true, or a machine with a GPU.`)}}if(e.refreshHz==null){const g=await G($.newPage(),15e3,async()=>{throw new Error("Chromium didn't open the calibration page in 15 s.")});try{await G(g.goto("about:blank"),15e3,async()=>null);const w=await G(g.evaluate(ut),5e3,async()=>{throw new Error("idle rate check timed out")});c=Ce(w);r(`idle frame rate ${w.toFixed(1)} \u2192 ${c} Hz`)}catch{h.push("Couldn't measure the idle frame rate; frame budgets assume 60 Hz.")}finally{await U(g,5e3)}}r("warmup load");await V(e.cases[0],1,-1);const H=async(g,w,v,_)=>{if(I){S.push({...W(g.name,w,v),failed:"an earlier run left the browser stuck; batch stopped"});return}if(Date.now()-b>ee){S.push({...W(g.name,w,v),failed:"batch time limit reached; run skipped"});return}r(_);T.push({cpu:w,round:v,caseName:g.name});S.push(await V(g,w,v))};for(const g of o){for(let w=0;w<a;w++){const v=he(e.cases,2654435769^w*7919+g);for(const _ of v)await H(_,g,w,`cpu ${g}x \xB7 round ${w+1}/${a} \xB7 ${_.name}`)}for(let w=0;w<(e.extraRoundsWhenClose??0);w++){const v=e.cases.map(y=>pe(S.filter(z=>z.cpu===g&&z.caseName===y.name),c));for(const y of v)y.control=e.cases.find(z=>z.name===y.caseName)?.control===true;const _=new Set([...ye(v).flat(),...v.filter(y=>!y.control&&y.validRuns<2&&y.plannedRuns>0).map(y=>y.caseName)]);if(_.size===0)break;const C=a+w;const J=he(e.cases.filter(y=>_.has(y.name)),20973^C*7919+g);for(const y of J)await H(y,g,C,`cpu ${g}x \xB7 extra round ${w+1} (close call) \xB7 ${y.name}`)}}if(e.traceDir){tt(e.traceDir,{recursive:true});for(const[g,w]of e.cases.entries()){const v=de(e.traceDir,`${String(g+1).padStart(2,"0")}-${w.name.replace(/[^\w.-]+/g,"_")}.json`);if(I||Date.now()-b>ee){h.push(`Trace run for ${w.name} skipped (batch time limit or a stuck browser).`);continue}r(`trace \xB7 ${w.name}`);const _=await V(w,o[0],-1,v);if(_.failed)h.push(`Trace run for ${w.name} failed: ${_.failed}`)}}await U($,1e4)}finally{const f=await U(M,15e3);t.browserLeftRunning=!f;if(!f){se=true;h.push("Chromium didn't close in 15 s and may still be running. The benchmark lock stays held; restart the MCP server before the next batch.")}}const m=[];for(const f of o){for(const $ of e.cases){m.push(pe(S.filter(R=>R.cpu===f&&R.caseName===$.name),c))}}for(const f of m)f.control=e.cases.find($=>$.name===f.caseName)?.control===true;for(const f of o){const $=m.filter(F=>F.cpu===f);const R=$.find(F=>!F.control);if(!R)continue;R.isBaseline=true;const V=F=>F.droppedMin!=null?F.median?.droppedPct??null:null;const W=V(R);for(const F of $.filter(H=>H!==R&&!H.control)){const H=V(F);F.deltaDroppedPct=W!=null&&H!=null?x(H-W):null}}if(e.allowPageErrors&&S.some(f=>f.pageErrors.length)){h.push("Some runs threw page errors (kept because allowPageErrors is on).")}if(I)h.push("A browser context wouldn't close, so the batch stopped early. Restart and run again.");if(e.budgets)for(const f of m)f.budget=Pe(f,e.budgets,e.steps??[]);const k=m.filter(f=>f.median).length;const q=m.every(f=>f.validRuns===f.plannedRuns);const te=k===0?"failed":k<m.length||!q?"partial":"ok";const E=m.filter(f=>f.median&&f.validRuns>0&&f.validRuns<f.plannedRuns);for(const f of E){const $=Array.from(new Set(f.runs.filter(R=>R.failed).map(R=>R.failed)));h.push(`${f.caseName} (CPU ${f.cpu}x): ${f.plannedRuns-f.validRuns} of ${f.plannedRuns} runs failed: ${$.join("; ")}`)}for(const f of m){const $=f.median;if($?.probeSpanMs&&$.cdpSpanMs&&Math.abs($.cdpSpanMs-$.probeSpanMs)/$.probeSpanMs>.15){h.push(`${f.caseName} (CPU ${f.cpu}x): Chrome's counters cover ${Math.round($.cdpSpanMs)} ms but frames cover ${Math.round($.probeSpanMs)} ms, so its cost columns include extra time.`)}if($?.probeSpanMs!=null&&$.probeSpanMs<s*.8){h.push(`${f.caseName} (CPU ${f.cpu}x) recorded ${Math.round($.probeSpanMs)} ms, not the ${s} ms asked for.`)}}const L={status:te,hasSteps:Boolean(e.steps?.length),browserLeftRunning:se,replay:{startedAt:A,...ae,runOrder:T},url:e.url,browser:`Chromium ${M.version()}${e.headed?"":" (headless)"}`,gpu:l,viewport:p,refreshHz:c,durationMs:s,runsPerCase:a,results:m,traces:u,warnings:h};if(e.outFile){try{Te(e.outFile,JSON.stringify({options:e,result:L},null,2))}catch(f){L.warnings.push(`Couldn't write outFile ${e.outFile}: ${re(f?.message??String(f))}`)}}return L}var Ke=1.5;var Qe=2;function ye(e){const r=e.filter(n=>!n.control&&n.median&&n.validRuns>1);const t=r.filter(n=>n.droppedMin!=null);const a=[];if(t.length>=2&&t.length===r.length){const n=[...t].sort((i,o)=>i.median.droppedPct-o.median.droppedPct);for(let i=1;i<n.length;i++){const[o,c]=[n[i-1],n[i]];const p=c.median.droppedPct-o.median.droppedPct<=Ke;if(c.droppedMin<=o.droppedMax||p)a.push([o.caseName,c.caseName])}return a}if(t.length>0)return a;const s=r.filter(n=>n.median.fps!=null).sort((n,i)=>i.median.fps-n.median.fps);for(let n=1;n<s.length;n++){const[i,o]=[s[n-1],s[n]];const c=i.median.fps-o.median.fps<=Qe;if((o.fpsMax??0)>=(i.fpsMin??0)||c)a.push([i.caseName,o.caseName])}return a}function Ve(e){const r=e.filter(o=>!o.control&&o.median);const t=[];const a=r.filter(o=>o.validRuns<2);if(a.length)t.push(`**Too few runs to rank:** ${a.map(o=>o.caseName).join(", ")} (need 2 or more valid runs).`);const s=r.filter(o=>o.validRuns>1);const n=s.filter(o=>o.droppedMin!=null);if(n.length>0&&n.length<s.length){t.push(`**Not enough traced runs to compare smoothness:** ${s.filter(o=>!n.includes(o)).map(o=>`${o.caseName} (${o.tracedRuns}/${o.validRuns} traced)`).join(", ")}.`);return t.join("\n")}const i=new Map(s.map(o=>[o.caseName,o]));for(const[o,c]of ye(e)){const p=i.get(o);const h=i.get(c);t.push(n.length?`**Too close to call:** ${o} (${p.droppedMin}\u2013${p.droppedMax}% dropped) and ${c} (${h.droppedMin}\u2013${h.droppedMax}%) overlap or sit within ${Ke} pp. No clear winner between them; more runs may separate them.`:`**Too close to call:** ${o} and ${c} overlap or sit within ${Qe} FPS. No clear winner between them; more runs may separate them.`)}return t.length?t.join("\n"):null}function St(e){const r=(o,c=1)=>o==null?"\u2014":o.toFixed(c);const t=[];t.push(`### Web benchmark \xB7 ${e.url}`);t.push(`${e.browser}${e.gpu?` \xB7 GPU: ${e.gpu}${fe(e.gpu)?" \u26A0\uFE0F software":""}`:""} \xB7 ${e.viewport.width}\xD7${e.viewport.height} @${e.viewport.deviceScaleFactor}x \xB7 ${e.runsPerCase} runs${e.results.some(o=>o.plannedRuns>e.runsPerCase)?" (more for close calls)":""} \xD7 ${e.durationMs/1e3}s per case, fresh browser context each run, case order shuffled each round.`);const a=e.results.map(o=>o.median?.probeSpanMs).filter(o=>o!=null);const s=e.results.map(o=>o.median?.cdpSpanMs).filter(o=>o!=null);const n=o=>`${(Math.min(...o)/1e3).toFixed(2)}\u2013${(Math.max(...o)/1e3).toFixed(2)} s`;if(a.length){t.push(`Recorded: frames over ${n(a)}${s.length?`; cost columns (Chrome's counters) over ${n(s)}`:""}, not the requested time.`)}if(e.replay?.dirty)t.push(`Note: the project had uncommitted changes, so commit ${e.replay.commit?.slice(0,10)??"?"} alone doesn't describe the code that ran.`);t.push("");t.push(`**How to read this.** Dropped % is the smoothness number: frames Chrome dropped or only partly produced that it says hurt smoothness, as a share of the frames it wanted, from its own verdicts in a trace. Rank on it first, and only across variants doing the same work. Shown FPS counts frames Chrome produced (headless Chrome displays nothing), so an animation that changes less often needs fewer; don't compare it across variants that update at different rates. Main-thread FPS and frame gaps come from requestAnimationFrame. Main thread % is Chrome's task time for the run. Desktop numbers, even with CPU slowdown, don't stand in for a real phone's GPU, memory or heat.`);const i=Array.from(new Set(e.results.map(o=>o.cpu)));for(const o of i){t.push("");t.push(o===1?`#### No CPU slowdown`:`#### CPU slowed ${o}x`);t.push("");t.push(`| Case | Runs | Shown FPS | Dropped % | Main-thread FPS (range) | p95 gap ms | Worst gap ms | Main thread % | Script ms | Style ms | Layout ms | Layouts | DOM nodes | JS heap MB | Flags |`);t.push(`|------|-----:|----------:|----------:|------------------------:|-----------:|-------------:|--------------:|----------:|---------:|----------:|--------:|----------:|--------:|-------|`);for(const l of e.results.filter(d=>d.cpu===o)){const d=l.median;if(!d){const T=l.runs.find(b=>b.failed)?.failed??"no valid runs";t.push(`| ${l.caseName} | 0/${l.plannedRuns} | \u2014 | \u2014 | \u2014 | \u2014 | \u2014 | \u2014 | \u2014 | \u2014 | \u2014 | \u2014 | \u2014 | \u2014 | \u274C FAILED (${T}) |`);continue}const M=l.fpsMin!=null&&l.fpsMax!=null&&l.validRuns>1?` (${r(l.fpsMin)}\u2013${r(l.fpsMax)})`:"";const S=l.flags.length?`\u26A0\uFE0F ${l.flags.join("; ")}`:"no flags";t.push(`| ${l.isBaseline?`${l.caseName} (baseline)`:l.control?`${l.caseName} (control)`:l.caseName} | ${l.validRuns}/${l.plannedRuns} | ${r(d.shownFps)} | ${l.droppedMin==null&&l.tracedRuns>0?`\u2014 (${l.tracedRuns}/${l.validRuns} traced)`:r(l.droppedMin==null?null:d.droppedPct)}${l.droppedMin!=null&&l.validRuns>1?` [${l.droppedMin}\u2013${l.droppedMax}]`:""}${l.deltaDroppedPct!=null?` (${l.deltaDroppedPct>0?"+":""}${l.deltaDroppedPct} pp)`:""} | ${r(d.fps)}${M} | ${r(d.frameGapP95Ms)} | ${r(d.frameGapMaxMs)} | ${r(d.mainThreadPct)} | ${r(d.scriptMs,0)} | ${r(d.styleMs,0)} | ${r(d.layoutMs,0)} | ${r(d.layoutCount,0)} | ${r(d.domNodesEnd,0)} | ${r(d.heapMb)} | ${S} |`)}const c=Ve(e.results.filter(l=>l.cpu===o));if(c){t.push("");t.push(c)}const p=e.results.filter(l=>l.cpu===o&&l.budget);if(p.length){t.push("");t.push(`Budget:`);for(const l of p){const d=l.budget;const M=[...d.over,...d.unknown.map(T=>`${T} not measured`)].join("; ");const S=l.control?`${l.caseName} (control)`:l.caseName;t.push(`- **${S}**: ${d.meets?"meets every limit":`misses (${M||"no result"})`}`)}}const h=e.results.filter(l=>l.cpu===o&&l.median?.interactions!=null&&(e.hasSteps||l.median.interactions>0)).map(l=>{const d=l.median;const M=l.inputMin!=null&&l.inputMax!=null?` \xB7 slowest across all runs ${l.inputMax} ms (range ${l.inputMin}\u2013${l.inputMax})`:"";const S=l.runs.filter(b=>!b.failed&&b.fps!=null&&b.inputCaptureComplete===false).length;const T=S?` \xB7 \u26A0\uFE0F timing incomplete in ${S} run(s): the last input's paint came after the run ended, so its time is unknown (leave more time after the last step)`:"";if(!d.interactions)return`- **${l.caseName}**: middle run had no input entry (inputs under 16 ms aren't reported)${M}${T}`;return`- **${l.caseName}**: middle run: ${d.interactions} reported (16 ms or more), slowest ${d.worstInteractionMs} ms (waiting ${d.worstInputDelayMs} \xB7 handlers ${d.worstProcessingMs} \xB7 to paint ${d.worstPresentationMs})${M}${T}`});if(h.length){t.push("");t.push(`Input response (scripted steps, Event Timing). Inputs under 16 ms aren't reported. Under 200 ms is good.`);t.push(...h)}const u=e.results.filter(l=>l.cpu===o&&l.median&&l.median.topScripts.length).map(l=>`- **${l.caseName}**: ${l.median.topScripts.slice(0,3).map(d=>`${d.name} ${d.ms} ms`).join(", ")}`);if(u.length){t.push("");t.push(`Scripts in slow frames (Long Animation Frames; frames that ran past the end of a run are left out):`);t.push(...u)}}if(e.traces.length){t.push("");t.push(`Chrome traces (open in DevTools \u2192 Performance \u2192 Load profile): ${e.traces.join(", ")}`)}if(e.warnings.length){t.push("");t.push(...e.warnings.map(o=>`\u26A0\uFE0F ${o}`))}return t.join("\n")}export{Pe as checkBudgets,Re as GPU_RENDERER_JS,fe as isSoftwareGpu,_e as percentile,Ce as snapRefreshRate,he as shuffled,Ae as caseUrl,Le as summarizeFrameTrace,Fe as keyMatches,Ne as mergeInteractions,De as summarizeProbe,Be as cdpDeltas,Ie as webBenchFlags,pe as aggregateCase,ze as PLAYWRIGHT_MISSING,Oe as loadChromium,G as withDeadline,U as closeWithin,je as validateWebBenchOptions,re as oneLine,Ge as locateStepTargets,Ue as missedInput,He as expectFailureFrom,bt as runWebBench,We as waitForHostLock,Je as takeHostLock,ye as tiedPairs,Ve as closeCall,St as formatWebBenchMarkdown};
|
package/dist/chunk-VKWX4XAY.mjs
DELETED
|
@@ -1,5 +0,0 @@
|
|
|
1
|
-
import{createRequire as p}from"module";const B=p(import.meta.url);import{execFile as d}from"child_process";import{promisify as h}from"util";import{existsSync as c,readFileSync as f}from"fs";import u from"os";import i from"path";var y=h(d);function g(){const n=process.env.BUOY_SIMCAM_DIR;const e=[...n?[i.join(n,"buoycam")]:[],"/Applications/Buoy.app/Contents/Resources/simcam/buoycam",i.join(u.homedir(),"Applications/Buoy.app/Contents/Resources/simcam/buoycam")];let t=__dirname;for(let o=0;o<8;o++){e.push(i.join(t,"apps/desktop/resources/simcam/buoycam"));const a=i.dirname(t);if(a===t)break;t=a}return e}var s=null;function m(){if(s)return s;s=g().find(n=>c(n))??null;return s}async function b(n){const e=m();if(!e){return{ok:false,error:"The simulator camera components were not found. They ship with the Buoy desktop app \u2014 install it, or set BUOY_SIMCAM_DIR to the directory holding `buoycam`."}}try{const{stdout:t}=await y(e,[...n,"--json"],{timeout:3e4});return{ok:true,data:JSON.parse(t)}}catch(t){const o=t.stdout??"";try{return{ok:false,data:JSON.parse(o)}}catch{return{ok:false,error:t.message||"the simulator camera CLI failed"}}}}function _(n){const e=n.filter(r=>r.level==="fail");const t=n.filter(r=>r.level==="warn");if(e.length===0&&t.length===0){return"The simulator camera is healthy \u2014 every check passed."}const o=[];for(const r of[...e,...t]){o.push(`- **${r.name}** (${r.level}): ${r.detail}`);if(r.fix)o.push(` ${r.fix}`)}const a=e.length>0?`${e.length} problem${e.length===1?"":"s"} found.`:`No hard failures, but ${t.length} thing${t.length===1?"":"s"} worth knowing.`;return[a,"",...o].join("\n")}function $(n,e){if(n.length===0){return"No booted simulators.\n\n_Boot one first \u2014 `xcrun simctl boot <udid>`, or open Simulator.app. The camera attaches to a simulator, so nothing else here works until one is running._"}const t=[n.length===1?"1 booted simulator:":`${n.length} booted simulators:`,...n.map(o=>`- **${o.name}** (${o.runtime}) \u2014 \`${o.udid}\``)];if(n.length>1&&e===null){t.push("","_More than one is booted, so pass `udid` to list a specific one's apps \u2014 and to every other camera tool that takes one._");return t.join("\n")}if(e===null){t.push("","_Could not read the installed apps._");return t.join("\n")}if(e.length===0){t.push("","No apps installed on it.");return t.join("\n")}t.push("",`${e.length} installed app${e.length===1?"":"s"}:`,...e.map(o=>`- ${o.name} \u2014 \`${o.bundleId}\``),"","_Pass one of these bundle ids to `camera_launch` to relaunch it with the camera attached._");return t.join("\n")}function k(n,e){const t=[];t.push(n.length?'**Mac cameras** (for `kind: "camera"`):':"**Mac cameras**: none found.");if(n.length){t.push(...n.map(o=>`- ${o.name} \u2014 \`${o.uniqueId}\``))}else{t.push("_If this Mac has a webcam, Buoy has not been granted Camera permission (System Settings \u2192 Privacy & Security \u2192 Camera)._")}t.push("");t.push(e.length?'**Capturable windows** (for `kind: "screen"`, `value: "window:<id>"`):':"**Capturable windows**: none found.");if(e.length){t.push(...e.map(o=>`- ${o.app}${o.title?` \u2014 ${o.title}`:""} \u2014 \`window:${o.id}\``))}else{t.push("_Screen Recording permission is almost certainly missing (System Settings \u2192 Privacy & Security \u2192 Screen & System Audio Recording)._")}return t.join("\n")}var l=null;function S(n){l=n}function v(){if(l)return[l];return[i.join(u.homedir(),"Library/Application Support/Buoy/entitlement.json"),i.join(u.homedir(),"Library/Application Support/buoy-desktop/entitlement.json")]}function w(){for(const n of v()){try{if(!c(n))continue;const e=JSON.parse(f(n,"utf8"));return{tier:e.tier==="pro"||e.tier==="free"?e.tier:"locked",isPro:e.isPro===true&&e.hasDesktopAccess===true&&typeof e.validUntil==="number"&&Number.isFinite(e.validUntil)&&Date.now()<e.validUntil&&typeof e.proValidUntil==="number"&&Number.isFinite(e.proValidUntil)&&Date.now()<e.proValidUntil}}catch{}}return{tier:"locked",isPro:false}}function j(n){return`\`${n}\` is a Buoy Pro feature.
|
|
2
|
-
|
|
3
|
-
The simulator camera itself is free \u2014 the Mac camera, image, video, test-pattern and QR sources all work in the Buoy desktop panel with a Free account. Pro adds the screen region, the non-QR symbologies (PDF417, Aztec, DataMatrix, Code 128), and driving any of it from an agent.
|
|
4
|
-
|
|
5
|
-
_Tell the user to enter a licence in Buoy Desktop, or see https://buoy.gg/pricing. Don't try to work around it \u2014 use the desktop panel by hand instead._`}export{m as cliPath,b as run,_ as describeDiagnosis,$ as describeDevices,k as describeInputs,S as setEntitlementFileForTests,w as readEntitlement,j as proRequired};
|