@buoy-gg/mcp 7.0.80 → 7.0.81
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/{chunk-BKCCZNDR.mjs → chunk-2GUL4POJ.mjs} +1 -1
- package/dist/{chunk-2324LPBJ.mjs → chunk-IDYAVQKW.mjs} +1 -1
- package/dist/{chunk-Y3TUCQB7.mjs → chunk-OHNYG34R.mjs} +1 -1
- package/dist/{chunk-23AXQYJ4.mjs → chunk-TMEMTQ45.mjs} +1 -1
- package/dist/cli.js +3 -3
- package/dist/cli.mjs +4 -4
- package/dist/{flowRun-DYM3QAEL.mjs → flowRun-V4HHLFX2.mjs} +1 -1
- package/dist/index.js +1 -1
- package/dist/index.mjs +1 -1
- package/dist/{init-HT6H7SCW.mjs → init-L5TIZUWZ.mjs} +1 -1
- package/dist/{main-QEH33AS3.mjs → main-YZO2NLH5.mjs} +2 -2
- package/package.json +4 -4
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import{createRequire as sc}from"module";const Qm=sc(import.meta.url);import{GPU_RENDERER_JS as ic,caseUrl as Fa,closeWithin as xt,formatWebBenchMarkdown as cc,isSoftwareGpu as Ba,loadChromium as ja,runWebBench as lc,shuffled as dc,snapRefreshRate as uc,waitForHostLock as qa,withDeadline as La}from"./chunk-DVRR7W22.mjs";import{PACKAGE_NAME as za,VERSION as Dn}from"./chunk-
|
|
1
|
+
import{createRequire as sc}from"module";const Qm=sc(import.meta.url);import{GPU_RENDERER_JS as ic,caseUrl as Fa,closeWithin as xt,formatWebBenchMarkdown as cc,isSoftwareGpu as Ba,loadChromium as ja,runWebBench as lc,shuffled as dc,snapRefreshRate as uc,waitForHostLock as qa,withDeadline as La}from"./chunk-DVRR7W22.mjs";import{PACKAGE_NAME as za,VERSION as Dn}from"./chunk-TMEMTQ45.mjs";import{ALL_DEVICES as pc,BLACK_PHONE_NOTE as hc,MIN_SECRET_LENGTH as mc,NOT_READY_MARK as Ua,ProfileService as fc,adb as An,adbAppFrame as To,adbFor as Mo,adbScreenSize as Cn,adbSerials as wc,adbShell as ct,adbSwipe as gc,adbTap as bc,adbText as vc,adbUiNodes as Do,assertUdid as yc,bootSimulator as $c,captureIosFramebuffer as kc,coreDeviceId as On,cropToBuffer as Sc,fillSecrets as Rn,findHost as xc,findUiNodes as _c,getProfileInput as Ic,hostBackground as Tc,hostLabel as _t,hostLaunch as Pn,hostOpenUrl as Mc,hostPng as Zt,hostQuit as Ao,hostReinstall as Dc,hostSummary as Ac,hostTarget as he,isAllBlack as Cc,isDeviceUdid as Oc,phoneRecordArgs as Rc,phoneScreenshot as Pc,profileActions as Nc,profileInput as Ec,q as Ja,readProjectKey as Fc,rememberedHost as Bc,require_png as Wa,resolveSims as jc,scrubSecrets as ye,secretValues as qc,setRecordingProbe as Lc,toSmallImage as It,typedSecretValues as Co,verifyHost as zc,withSteadyStatusBar as Uc}from"./chunk-TMN3LXKB.mjs";import{accountRequired as Nn,farmFairness as Jc,hasBuoyAccount as En,registerSimFarmTools as Wc}from"./chunk-5XATNNKY.mjs";import{coreInfo as Hc,ensureKeeper as Gc,farmHome as Kc,findShell as Vc,installShell as Xc,keeperAlive as Tt,keeperCall as lt,suggestCounts as Yc}from"./chunk-FYJWWTAC.mjs";import{describeDevices as Zc,describeDiagnosis as Qc,describeInputs as el,entitlementHasPro as Fn,proRequired as tl,readEntitlement as nl,run as Re}from"./chunk-5NOZVPWB.mjs";import{accessFor as ol,currentAccess as Ha,freeToolNames as al,hasProAccess as Pe,meetsAccess as rl,proRequiredMessage as sl,runWithAccess as Ga}from"./chunk-D5K6WO6B.mjs";import{freeComparison as il,freeJsTop as cl,freeRanking as Oo,freeWebBench as ll,nothingLocked as Bn,withLockedLines as Mt}from"./chunk-OP23W722.mjs";import{__toESM as Ka}from"./chunk-MTTWR4T7.mjs";import{promises as Va}from"fs";import dl from"os";import ul from"path";var pl=24*60*60*1e3;var hl=1500;var ml=`https://registry.npmjs.org/${za}/latest`;var Xa=ul.join(dl.tmpdir(),"buoy-mcp-update-check.json");function fl(e,n){const t=r=>r.split("-")[0].split(".").map(s=>Number(s)||0);const o=t(e);const a=t(n);for(let r=0;r<3;r++){const s=o[r]??0;const i=a[r]??0;if(s>i)return true;if(s<i)return false}return false}async function wl(){try{const e=await Va.readFile(Xa,"utf8");const n=JSON.parse(e);if(typeof n.checkedAt==="number"&&typeof n.latest==="string"){return n}}catch{}return null}async function gl(e){const n=new AbortController;const t=setTimeout(()=>n.abort(),hl);try{const o=await fetch(ml,{signal:n.signal});if(!o.ok)return null;const a=await o.json();const r=typeof a.version==="string"?a.version:null;if(r){await Va.writeFile(Xa,JSON.stringify({checkedAt:e,latest:r})).catch(()=>{})}return r}catch{return null}finally{clearTimeout(t)}}async function bl(e){const n=await wl();if(n&&e-n.checkedAt<pl)return n.latest;return gl(e)}async function Ya(e=Date.now()){if(Dn==="0.0.0-dev")return null;try{const n=await bl(e);if(n&&fl(n,Dn)){return`A newer buoy-mcp is available (${n}; you're on ${Dn}). Update by re-running: npx -y ${za}@latest init \u2014 then restart your editor.`}}catch{}return null}import{z as Q}from"zod";import{isBuoyAccountKeyName as vl,maskBuoyAccountKey as yl}from"@buoy-gg/license/headless";function jn(e,n=0){if(n>32||!e||typeof e!=="object")return e;if(Array.isArray(e)){let o;e.forEach((a,r)=>{const s=jn(a,n+1);if(s!==a)(o??=[...e])[r]=s});return o??e}let t;for(const[o,a]of Object.entries(e)){const r=typeof a==="string"&&a&&vl(o)?yl(a):jn(a,n+1);if(r!==a)(t??={...e})[o]=r}return t??e}function Dt(e){return{content:[{type:"text",text:JSON.stringify(e,null,2)}]}}function At(e){return{isError:true,content:[{type:"text",text:e}]}}function Za(e,n){e.registerTool("list_devices",{description:"List the apps connected to Buoy: id, name, platform, whether it is connected and has MCP access, the simulator or phone it runs on with the host controls that work there (screenshot, recording, app control, real touches) and anything missing, and the Buoy tools it exposes. Start here. Pass details:true for every tool's action names.",inputSchema:{details:Q.boolean().optional().describe("Also list every action of every tool. Default false (tool names only); list_tool_actions gives one tool's actions."),includeDisconnected:Q.boolean().optional().describe("Also list apps that were seen before but are not connected now. Default false.")}},async(t={})=>{const o=await Ya();let a=n.listDevices();for(let b=0;a.length===0&&b<10;b++){await new Promise(m=>setTimeout(m,200));a=n.listDevices()}if(a.length===0){return Dt({devices:[],hint:"No devices connected. Open the app with Buoy devtools running, then retry.",...o?{updateAvailable:o}:{}})}const r=t.includeDisconnected?0:a.filter(b=>!b.isConnected).length;if(!t.includeDisconnected)a=a.filter(b=>b.isConnected);if(a.length===0){return Dt({devices:[],hint:`No app is connected right now${r?` (${r} seen before; pass includeDisconnected:true to list them)`:""}. Open the app with Buoy devtools running, then retry.`,...o?{updateAvailable:o}:{}})}const s=await jc(a.filter(b=>b.platform==="ios").map(b=>b.deviceId)).catch(()=>new Map);const i=a.filter(b=>b.isConnected);const c=i.some(b=>Pe(b));const u=i.some(b=>b.tier!=="locked");const d=i.filter(b=>b.collision);const l=await Promise.all(a.map(b=>b.isConnected?Ac(b,s.get(b.deviceId)).catch(()=>void 0):void 0));const p=a.map((b,m)=>{const g=s.get(b.deviceId);const f=l[m];return{deviceId:b.deviceId,deviceName:b.deviceName,platform:b.platform,isConnected:b.isConnected,isPro:b.isPro,tier:b.tier,...!b.isPro&&Pe(b)?{weekendPass:true}:{},access:n.accessState(b.deviceId,"free"),...g?{simulator:g}:{},...f?{host:f}:{},...b.collision?{collision:true}:{},tools:t.details?b.tools:b.tools.map(w=>w.toolId)}});return Dt({devices:p,...t.details?{}:{note:`Tool names only. Pass details:true, or list_tool_actions({toolId}), for action names.${r?` ${r} apps seen before but not connected now are left out (includeDisconnected:true lists them).`:""}`},...d.length>0?{warning:`${d.map(b=>`"${b.deviceId}"`).join(", ")}: more than one app is registered under this id, so actions sent to it land on an arbitrary device. Give each device its own externalSync.deviceId (or leave it unset for a per-install default) before mutating anything.`}:{},...c?{}:{hint:u?`The connected app is on the free tier, so only the free MCP tools work: ${al().join(", ")} (get_network_requests without includeBodies; get_triage without storage values). Every other tool needs Buoy Pro (https://buoy.gg/pricing). After upgrading, the user runs \`npx --package=@buoy-gg/core buoy login\` again, or signs in again in the app. A headless app can take a Pro key through \`licenseKey\`.`:"The broker has not confirmed account access for the connected devices, so data/action tools are blocked. A locked status can mean missing configuration, pending/failed validation, or a missing account announcement after reconnect; it does not prove the user is signed out. Check app account status and broker compatibility."},...o?{updateAvailable:o}:{}})});e.registerTool("list_tool_actions",{description:"List the actions a tool exposes, and which connected devices offer it. Leave toolId out to list every tool's actions. Use the action names with call_action.",inputSchema:{toolId:Q.string().optional().describe('The tool id, e.g. "perf-monitor", "network", "react-query". Leave out for every tool.'),tool:Q.string().optional().describe("Same as toolId."),deviceId:Q.string().optional().describe("Only list what this device offers.")}},async({toolId:t,tool:o,deviceId:a})=>{const r=t??o;if(!r){const c=n.connectedDevices().filter(d=>!a||d.deviceId===a);if(c.length===0)return At(a?`No connected device "${a}". Call list_devices.`:"No devices are connected. Call list_devices.");const u=c.map(d=>[`**${d.deviceName}** (${d.deviceId})`,...d.tools.map(l=>`- ${l.toolId}: ${l.actions.join(", ")}`)].join("\n"));return{content:[{type:"text",text:`${u.join("\n\n")}
|
|
2
2
|
|
|
3
3
|
Call one with call_action({toolId, action, params}).`}]}}const s=new Set(n.connectedDevices().map(c=>c.deviceId));const i=n.toolActions(r).filter(c=>s.has(c.deviceId)&&(!a||c.deviceId===a));if(i.length===0){const c=[...new Set(n.connectedDevices().flatMap(u=>u.tools.map(d=>d.toolId)))].sort();return At(`No connected device exposes tool "${r}". Tool ids: ${c.join(", ")||"(none connected)"}. MCP tool names like "${r}" are not tool ids.`)}return Dt({toolId:r,offerings:i})});e.registerTool("get_snapshot",{description:"Read a tool's current state (its sync snapshot) from a device. Starts watching the tool and returns the next fresh snapshot.",inputSchema:{toolId:Q.string().optional().describe("The tool id to read state from."),tool:Q.string().optional().describe("Same as toolId."),deviceId:Q.string().optional().describe("The device to act on: its id from list_devices or from your instructions. Pass it whenever more than one app may be connected. Once a call names a device, later calls in this session use it when deviceId is left out."),timeoutMs:Q.number().optional().describe("How long to wait for a fresh snapshot before falling back to the cached one.")}},async({toolId:t,tool:o,deviceId:a,timeoutMs:r})=>{try{const s=t??o;if(!s)return At("get_snapshot needs toolId (see list_tool_actions).");const i=n.requireProDevice(a);const{payload:c,stale:u}=await n.getSnapshot(s,i,{timeoutMs:r});return Dt({toolId:s,deviceId:i,...u?{stale:u}:{},snapshot:jn(c)})}catch(s){return At(Qa(s))}});e.registerTool("call_action",{description:"Invoke any action a tool exposes (see list_tool_actions) and return its result. This is the generic escape hatch that works for every Buoy tool.",inputSchema:{toolId:Q.string().optional().describe('The tool id, e.g. "perf-monitor".'),tool:Q.string().optional().describe("Same as toolId."),action:Q.string().describe('The action name, e.g. "startRecording", "clearAll".'),params:Q.union([Q.record(Q.unknown()),Q.string()]).optional().describe("JSON params object for the action (shape depends on the action)."),args:Q.union([Q.record(Q.unknown()),Q.string()]).optional().describe("Same as params."),deviceId:Q.string().optional().describe('The device to act on: its id from list_devices or from your instructions. Pass it whenever more than one app may be connected. Once a call names a device, later calls in this session use it when deviceId is left out. Or "All" to broadcast.'),timeoutMs:Q.number().optional().describe("Max time to wait for the device's result.")}},async({toolId:t,tool:o,action:a,params:r,args:s,deviceId:i,timeoutMs:c})=>{try{const u=t??o;if(!u)return At("call_action needs toolId (see list_tool_actions).");const d=$l(r??s);const l=n.requireProDevice(i);const p=await n.callAction(u,a,d,l,{timeoutMs:c});return Dt({toolId:u,action:a,ok:true,result:jn(p??null)})}catch(u){return At(Qa(u))}})}function $l(e){if(e===void 0||typeof e==="object")return e;const n=e.trim();if(!n)return void 0;let t;try{t=JSON.parse(n)}catch{throw new Error('params is a string but not JSON. Pass an object, e.g. {"user": {...}}.')}if(!t||typeof t!=="object"||Array.isArray(t))throw new Error("params must be a JSON object.");return t}function Qa(e){return e instanceof Error?e.message:String(e)}var Ct=(e,n,t)=>Math.max(n,Math.min(t,e));var Ro="__batch_warmup__";var kl={nominal:"\u{1F7E2} nominal",fair:"\u{1F7E1} fair",serious:"\u{1F7E0} serious",critical:"\u{1F534} critical",unknown:"\u2014"};var Ot={perCaseDurationMs:5e3,settleMs:600,navTimeoutMs:5e3,runsPerCase:3,coolDownMs:2e3,discardWarmupRuns:0};function Rt(e,n,t){return e??n??t}function qn(e,n={}){const t=[];const o=(e.targetRoute||"").trim();if(!o)throw new Error("targetRoute is required (the screen to benchmark).");let a=(e.bounceRoute||"").trim();if(!a)a=o==="/"?"/_buoy_bounce":"/";if(a===o){throw new Error(`bounceRoute ("${a}") must differ from targetRoute \u2014 it's the screen we bounce to so the test screen fully remounts each run.`)}if(!Array.isArray(e.cases)||e.cases.length===0){throw new Error("At least one case is required.")}const r=e.cases.map((l,p)=>{const b=(l.name||"").trim()||`case ${p+1}`;return{id:`case-${p}`,name:b,params:l.params??{},route:l.route}});const s=Ct(Math.round(Rt(e.runsPerCase,n.runsPerCase,Ot.runsPerCase)),1,10);const i=Ct(Math.round(Rt(e.coolDownMs,n.coolDownMs,Ot.coolDownMs)),0,3e4);const c=Ct(Math.round(Rt(e.discardWarmupRuns,n.discardWarmupRuns,Ot.discardWarmupRuns)),0,s-1);const u=e.shuffleCases??n.shuffleCases??r.length>3;if(r.length>3&&u===false){t.push("shuffleCases is off with >3 cases \u2014 later cases may be thermally throttled, biasing results. Consider enabling it.")}if(s===1){t.push("runsPerCase=1 \u2014 no median/variance; a single noisy run can mislead. 3+ is recommended.")}const d={targetRoute:o,bounceRoute:a,perCaseDurationMs:Ct(Math.round(Rt(e.perCaseDurationMs,n.perCaseDurationMs,Ot.perCaseDurationMs)),1e3,6e4),settleMs:Ct(Math.round(Rt(e.settleMs,n.settleMs,Ot.settleMs)),0,1e4),navTimeoutMs:Ct(Math.round(Rt(e.navTimeoutMs,n.navTimeoutMs,Ot.navTimeoutMs)),1e3,3e4),cases:r,runsPerCase:s,coolDownMs:i,discardWarmupRuns:c,discardWarmupCase:e.discardWarmupCase??n.discardWarmupCase??true,shuffleCases:u,reloadBetweenCases:e.reloadBetweenCases??n.reloadBetweenCases??false,captureRenders:e.captureRenders??n.captureRenders??true};return{config:d,warnings:t}}function er(e){const n=c=>c>=1e3?`${(c/1e3).toFixed(c%1e3?1:0)}s`:`${c}ms`;const t=e.runsPerCase??1;const o=Po(e);const a=o/6e4;const r=a>=1?`~${a.toFixed(1)} min`:`~${Math.round(o/1e3)}s`;const s=[`**${n(e.perCaseDurationMs)} measured per page**`,t>1?`**${t} runs each** (median taken)`:`**1 run each** (no median \u2014 noisier)`,e.reloadBetweenCases?"**full reload between pages** (no state leaks across tests)":"no reload between pages",e.shuffleCases?"shuffled order (cancels thermal bias)":"fixed order",e.captureRenders!==false?"render capture on (per-component why-data)":"render capture off"];const i=e.settleMs+e.perCaseDurationMs+(e.coolDownMs??0)+1800;return`${s.join(", ")}. That's ~${n(i)} wall-clock per page; for ${e.cases.length} page(s) \xD7 ${t} run(s), **${r}** total.`}function Po(e){const n=1800;const t=e.settleMs+e.perCaseDurationMs+e.coolDownMs+n;let o=e.cases.length*(e.runsPerCase??1)*t;if(e.reloadBetweenCases)o+=e.cases.length*3e3;return o}function Sl(e){const n=[];const t=e.deviceMaxRefreshRate;if(e.uiFps!=null&&t&&e.uiFps<t*.95){n.push("UI thread drops frames")}else if(e.uiJank!=null&&e.uiJank>=2){n.push("UI jank spikes")}if(e.cpu!=null&&e.cpu>=90)n.push("CPU near cap");if(e.jsJank!=null&&e.jsJank>=5)n.push("JS thread stalls");return n}function xl(e){const n=[];const t=e.deviceMaxRefreshRate;if(e.jsFps!=null&&t&&e.jsFps<t*.95){n.push("Low main-thread frame rate")}else if(e.longFrames!=null&&e.frames>0&&e.longFrames/e.frames>=.02){n.push("Frequent long frames")}const o=Math.max(e.frameGapMaxMs??0,e.edgeSilenceMs??0);if(o>=100)n.push(`Freeze of ${Math.round(o)} ms`);if(e.longTaskPct!=null&&e.longTaskPct>=20)n.push("Long tasks block the main thread");if(e.forcedLayoutMs!=null&&e.forcedLayoutMs>=50){n.push(`Forced layout in scripts (${Math.round(e.forcedLayoutMs)} ms)`)}if(e.styleLayoutMs!=null&&e.styleLayoutMs>=100){n.push(`Style and layout in slow frames (${Math.round(e.styleLayoutMs)} ms)`)}if(e.worstInteractionMs!=null&&e.worstInteractionMs>=200){n.push(`Slow response to input (${Math.round(e.worstInteractionMs)} ms)`)}if(e.layoutShift!=null&&e.layoutShift>=.1)n.push(`Layout shifts (${e.layoutShift})`);if(e.domGrowth!=null&&e.domGrowth>0){n.push(`DOM grew every run (+${e.domGrowth} nodes): check for a leak`)}return n}function _l(e){const n=e.filter(o=>!o.batchFailureReason&&!o.discardedWarmup).sort((o,a)=>(o.runIndex??0)-(a.runIndex??0)).map(o=>o.webRun?.domNodesStart).filter(o=>typeof o==="number");if(n.length<3)return null;for(let o=1;o<n.length;o++)if(n[o]<=n[o-1])return null;const t=n[n.length-1]-n[0];return t>=Math.max(50,n[0]*.05)?t:null}function Il(e,n){const t=e.find(a=>a.isBaseline);if(t)return t;const o=n?.[0]?.name;if(o){const a=e.filter(r=>r.caseName===o);if(a.length===1)return a[0]}return e[0]}function Ln(e,n,t,o=[]){const a=e.filter(w=>w.batchId===n);const r=new Map(o.map(w=>[w.index,w.reason]));const s=Array.from(new Set(a.map(w=>w.batchIndex??0))).sort((w,h)=>w-h);const i=w=>{const h=a.filter($=>($.batchIndex??0)===w);if(h.length===0)return void 0;const y=h.filter($=>!$.batchFailureReason&&!$.discardedWarmup);const v=y.filter($=>$.webRun?.fps!=null);if(v.length>0&&v.length===y.length){const $=[...v].sort((I,_)=>I.webRun.fps-_.webRun.fps);return $[Math.floor(($.length-1)/2)]}if(y.length>0){return y.find($=>$.isMedianRun===true)??y.find($=>$.runIndex===0||$.runIndex===void 0)??y[0]}return h.find($=>$.batchFailureReason)??h[0]};const c=w=>typeof w==="number"?w:null;const u=s.map(w=>{const h=i(w);const y=a.filter(P=>(P.batchIndex??0)===w);const v=y.length;const $=y.filter(P=>!P.batchFailureReason&&!P.discardedWarmup);const I=P=>P.webRun?P.webRun.fps??P.jsFpsAvg:P.uiFpsAvg;const _=$.map(I).filter(P=>typeof P==="number");const T=h?.name??t?.[w]?.name??`case ${w+1}`;const A=a.some(P=>(P.batchIndex??0)===w&&P.isBaseline===true);const D=r.get(w)??h?.batchFailureReason;const N=y.every(P=>P.batchFailureReason||P.discardedWarmup);const x=Boolean(D)||!h||N;const S={batchIndex:w,caseName:T,params:h?.params??{},runs:v,failed:x,...h?.route?{route:h.route}:{},...A?{isBaseline:true}:{},failureReason:x?D??(N&&h?"only warmup runs were saved":"no run recorded"):void 0,uiFps:c(h?.uiFpsAvg),jsFps:c(h?.jsFpsAvg),cpu:c(h?.cpuAvg),memMb:c(h?.memMaxMb),jsJank:c(h?.jsJankFrames),uiJank:c(h?.uiJankFrames),deviceMaxRefreshRate:c(h?.deviceMaxRefreshRate),deltaUiFps:null,deltaJsFps:null,validRuns:$.length,fpsMin:_.length?Te(Math.min(..._),1):null,fpsMax:_.length?Te(Math.max(..._),1):null,...h?.thermalState?{thermalState:h.thermalState}:{},renderCommits:c(h?.renderCommits),renderWasted:h?.renderWasted,topRenderers:h?.topRenderers??[]};const C=h?.webRun;if(!C)return{...S,flags:x?[]:Sl(S)};const R=[...C.longTasksSupported?[]:["long tasks"],...C.heapSupported?[]:["JS heap"],...C.loaf===null?["long animation frames"]:[]];const F={...S,platform:"web",uiFps:null,uiJank:null,jsJank:null,cpu:null,memMb:C.heapSupported?S.memMb:null,deviceMaxRefreshRate:Math.max(S.deviceMaxRefreshRate??0,C.refreshHz??0)||null,longTaskPct:C.longTaskPct,frameGapP95Ms:C.frameGapP95Ms,frameGapMaxMs:C.frameGapMaxMs,edgeSilenceMs:C.edgeSilenceMs??null,jsFps:C.fps??S.jsFps,longFrames:C.longFrames,...R.length?{missingMetrics:R}:{},loafBlockingMs:C.loaf?.blockingMs??null,styleLayoutMs:C.loaf?.styleLayoutMs??null,forcedLayoutMs:C.loaf?.forcedLayoutMs??null,topScripts:C.loaf?.topScripts??[],worstInteractionMs:C.interactions?.worstMs??null,layoutShift:C.layoutShift??null,domNodes:C.domNodesEnd??null,domGrowth:_l(y),pageLoad:C.pageLoad??null,refreshCalibrated:C.refreshCalibrated??null};return{...F,flags:x?[]:xl({...F,frames:C.frames})}}).filter(w=>w.caseName!==Ro).map((w,h)=>({...w,batchIndex:h}));const d=Il(u,t);if(d){d.isBaseline=true;for(const w of u){if(w===d)continue;if(w.uiFps!=null&&d.uiFps!=null)w.deltaUiFps=Te(w.uiFps-d.uiFps,1);if(w.jsFps!=null&&d.jsFps!=null)w.deltaJsFps=Te(w.jsFps-d.jsFps,1)}}const l=u.filter(w=>!w.failed);const p=(w,h)=>{let y;let v=NaN;for(const $ of l){const I=w($);if(I==null)continue;if(y===void 0||h(I,v)){y=$;v=I}}return y?.caseName};const b=w=>w.platform==="web"?w.longFrames??null:w.jsJank==null&&w.uiJank==null?null:(w.jsJank??0)+(w.uiJank??0);const m={uiFps:p(w=>w.uiFps,(w,h)=>w>h),jsFps:p(w=>w.jsFps,(w,h)=>w>h),lowestCpu:p(w=>w.cpu,(w,h)=>w<h),lowestMem:p(w=>w.memMb,(w,h)=>w<h),fewestJank:p(b,(w,h)=>w<h),fewestLongTasks:p(w=>w.longTaskPct??null,(w,h)=>w<h)};const g=l.filter(w=>w.flags.length>0).map(w=>({caseName:w.caseName,flags:w.flags}));const f=l.filter(w=>w.flags.length===0).sort((w,h)=>(h.uiFps??h.jsFps??0)-(w.uiFps??w.jsFps??0)||(w.cpu??w.longTaskPct??Infinity)-(h.cpu??h.longTaskPct??Infinity)||(w.memMb??Infinity)-(h.memMb??Infinity)).map(w=>w.caseName);return{batchId:n,baselineName:d?.caseName??null,deviceMaxRefreshRate:l.find(w=>w.deviceMaxRefreshRate!=null)?.deviceMaxRefreshRate??null,rows:u,leaders:m,flagged:g,balanced:f}}function Tl(e){const n=e.filter(s=>!s.failed);if(n.length===0)return[];const t=n.map(s=>s.pageLoad?.navigationId);if(t.some(s=>s==null)||new Set(t).size!==t.length)return[];if(n.some(s=>(s.pageLoad?.pageAgeMs??Infinity)>3e4))return[];const o=s=>(s??"").replace(/\/+$/,"")||"/";const a=s=>{const i=s.pageLoad;if(!i?.loadPath||o(i.loadPath)!==o(s.route))return false;const c=new URLSearchParams(i.loadSearch??"");const u=[...c.keys()];if(new Set(u).size!==u.length)return false;const d=Object.entries(s.params);return u.length===d.length&&d.every(([l,p])=>c.get(l)===p)};if(n.some(s=>!a(s)))return[];const r=s=>s==null?"\u2014":String(Math.round(s));return[`**Page load** (each case started on a fresh page load):`,"",`| Case | TTFB ms | FCP ms | LCP ms | DOM ready ms | Script KB | Total KB |`,`|------|--------:|-------:|-------:|-------------:|----------:|---------:|`,...n.map(s=>{const i=s.pageLoad;return`| ${s.caseName} | ${r(i.ttfbMs)} | ${r(i.fcpMs)} | ${r(i.lcpMs)} | ${r(i.domContentLoadedMs)} | ${r(i.scriptKb)} | ${r(i.totalKb)} |`}),"",`_KB is bytes over the network. 0 can mean the file came from cache, so compare cold loads with cold loads._`]}function tr(e){const n=r=>r.platform==="web"?r.jsFps:r.uiFps;const t=e.filter(r=>!r.failed&&n(r)!=null&&r.fpsMin!=null&&r.fpsMax!=null).sort((r,s)=>(n(s)??0)-(n(r)??0));if(t.length<2)return null;const[o,a]=t;if((o.validRuns??0)<2||(a.validRuns??0)<2){return`**Too few runs to separate the top cases.** ${o.caseName} and ${a.caseName} each need 2+ valid runs before their FPS gap means anything.`}if((a.fpsMax??0)>=(o.fpsMin??0)||(n(o)??0)-(n(a)??0)<=2){return`**Too close to call:** ${o.caseName} (${o.fpsMin}\u2013${o.fpsMax} FPS) and ${a.caseName} (${a.fpsMin}\u2013${a.fpsMax} FPS) overlap or sit within 2 FPS. No clear winner between them; more runs may separate them.`}return null}function Qt(e){const n=(p,b=1)=>p==null?"\u2014":p.toFixed(b);const t=p=>p==null?"":p>0?` (+${p.toFixed(1)})`:` (${p.toFixed(1)})`;const o=[];o.push(`### Benchmark batch \`${e.batchId}\``);if(e.rows.some(p=>p.platform==="web")){Dl(e,o);return o.join("\n")}o.push(`**No single winner \u2014 weigh the trade-offs.** UI-thread health matters more than raw averages: a cheap run that drops UI frames is worse than a pricier one that holds them.`);o.push("");const a=e.rows.some(p=>p.thermalState);const r=a?" Thermal |":"";const s=a?"-------|":"";o.push(`| # | Case | UI FPS | JS FPS | CPU % | Mem MB | JS jank | UI jank |${r} Flags |`);o.push(`|---|------|-------:|-------:|------:|-------:|--------:|--------:|${s}-------|`);for(const p of e.rows){const b=a?` ${kl[p.thermalState??"unknown"]??"\u2014"} |`:"";if(p.failed){o.push(`| ${p.batchIndex} | ${p.caseName} | \u2014 | \u2014 | \u2014 | \u2014 | \u2014 | \u2014 |${b} \u274C FAILED (${p.failureReason}) |`);continue}const m=p.flags.length?`\u26A0\uFE0F ${p.flags.join("; ")}`:"no flags";o.push(`| ${p.batchIndex} | ${p.caseName} | ${n(p.uiFps)}${t(p.deltaUiFps)} | ${n(p.jsFps)}${t(p.deltaJsFps)} | ${n(p.cpu)} | ${n(p.memMb)} | ${n(p.jsJank,0)} | ${n(p.uiJank,0)} |${b} ${m} |`)}const i=tr(e.rows);if(i){o.push("");o.push(i)}const c=e.rows.filter(p=>p.thermalState==="serious"||p.thermalState==="critical");if(c.length){o.push("");o.push(`**\u{1F525} Thermally throttled: ${c.map(p=>p.caseName).join(", ")}.** A throttled device runs SLOWER at a LOWER cpu%, so these cases can look like the cheapest in the batch while actually delivering fewer frames \u2014 check their UI FPS and jank, and re-run them on a cool device before drawing any conclusion.`)}o.push("");const u=e.leaders;const d=[u.uiFps&&`UI FPS \u2192 **${u.uiFps}**`,u.jsFps&&`JS FPS \u2192 **${u.jsFps}**`,u.lowestCpu&&`lowest CPU \u2192 **${u.lowestCpu}**`,u.lowestMem&&`lowest mem \u2192 **${u.lowestMem}**`,u.fewestJank&&`fewest jank \u2192 **${u.fewestJank}**`].filter(Boolean).join(" \xB7 ");if(d)o.push(`**Per-metric best:** ${d}`);o.push(e.balanced.length?`**No configured risk flags:** ${e.balanced.join(", ")}. Check missing metrics and workload requirements before choosing.`:`**No configured risk flags:** no candidates listed. Review failures, available metrics and flags before choosing.`);if(e.flagged.length){o.push(`**\u26A0\uFE0F At-risk:** ${e.flagged.map(p=>`${p.caseName} (${p.flags.join("; ")})`).join(" \xB7 ")}`)}const l=e.rows.filter(p=>!p.failed&&(p.renderCommits!=null||p.topRenderers.length>0)).map(p=>{const b=p.topRenderers.map(f=>`${f.name} \xD7${f.renders} (${f.totalMs}ms${f.cause?`, ${f.cause}`:""})`).join(", ");const m=p.renderCommits!=null?`${p.renderCommits} commits`:"";const g=p.renderWasted!=null?`${p.renderWasted} wasted`:"";return`- **${p.caseName}** \u2014 ${[m,g,b].filter(Boolean).join(": ")}`});if(l.length){o.push("");o.push("**Renders (median run)** \u2014 `wasted` = parent cascades + identity-only prop churn (removable). Cause per component: `propsUnstable` means a parent handed it a fresh function/object with unchanged values \u2014 fix upstream.");o.push(...l)}o.push("");o.push(`_Deltas vs baseline (\`${e.baselineName??"case #0"}\`). Don't pick on one metric \u2014 explain the trade-off (e.g. "A is cheapest but drops UI frames \u2192 jank risk; B costs more CPU but caps nothing")._`);return o.join("\n")}function No(e,n){const t=(o,a)=>({a:{avg:Te(o.avg,1),p95:Te(o.p95,1)},b:{avg:Te(a.avg,1),p95:Te(a.p95,1)},deltaAvg:Te(a.avg-o.avg,1)});return{a:e.name,b:n.name,uiFps:t(e.stats.uiFps,n.stats.uiFps),jsFps:t(e.stats.jsFps,n.stats.jsFps),cpuUsage:t(e.stats.cpuUsage,n.stats.cpuUsage),memoryUsage:t(e.stats.memoryUsage,n.stats.memoryUsage),jsJankFrames:{a:e.stats.jsJankFrames,b:n.stats.jsJankFrames,delta:n.stats.jsJankFrames-e.stats.jsJankFrames},uiJankFrames:{a:e.stats.uiJankFrames,b:n.stats.uiJankFrames,delta:n.stats.uiJankFrames-e.stats.uiJankFrames},...nr(e.renders,n.renders)}}function Ml(e,n){const t=(s,i,c)=>({a:s??null,b:i??null,delta:s!=null&&i!=null?Te(i-s,1):null,better:c});const o=s=>s.webRun.heapSupported?Te(s.stats.memoryUsage.max,1):null;const a=e.webRun;const r=n.webRun;return{a:e.name,b:n.name,platform:"web",note:"Browser runs: FPS is the main thread's whole-run requestAnimationFrame rate; long tasks % is not CPU. null = this browser couldn't measure it.",fps:t(a.fps??e.stats.jsFps.avg,r.fps??n.stats.jsFps.avg,"higher"),frameGapP95Ms:t(a.frameGapP95Ms,r.frameGapP95Ms,"lower"),frameGapMaxMs:t(a.frameGapMaxMs,r.frameGapMaxMs,"lower"),longFrames:t(a.longFrames,r.longFrames,"lower"),longTaskPct:t(a.longTaskPct,r.longTaskPct,"lower"),slowFrameBlockingMs:t(a.loaf?.blockingMs,r.loaf?.blockingMs,"lower"),styleLayoutPaintMs:t(a.loaf?.styleLayoutMs,r.loaf?.styleLayoutMs,"lower"),forcedLayoutMs:t(a.loaf?.forcedLayoutMs,r.loaf?.forcedLayoutMs,"lower"),worstInteractionMs:t(a.interactions?.worstMs,r.interactions?.worstMs,"lower"),layoutShift:t(a.layoutShift,r.layoutShift,"lower"),domNodes:t(a.domNodesEnd,r.domNodesEnd,"lower"),jsHeapMb:t(o(e),o(n),"lower"),topScripts:{a:a.loaf?.topScripts??[],b:r.loaf?.topScripts??[]},...nr(e.renders,n.renders)}}function nr(e,n){if(!e&&!n)return null;const t=o=>o??0;return{renders:{total:{a:t(e?.totalRenders),b:t(n?.totalRenders),delta:t(n?.totalRenders)-t(e?.totalRenders)},commits:{a:t(e?.totalCommits),b:t(n?.totalCommits),delta:t(n?.totalCommits)-t(e?.totalCommits)},wasted:{a:t(e?.wastedRenders),b:t(n?.wastedRenders),delta:t(n?.wastedRenders)-t(e?.wastedRenders)},causes:{a:e?.causes??{},b:n?.causes??{}}}}}function Te(e,n){const t=Math.pow(10,n);return Math.round(e*t)/t}function Dl(e,n){const t=(d,l=1)=>d==null?"\u2014":d.toFixed(l);const o=d=>d==null?"":d>0?` (+${d.toFixed(1)})`:` (${d.toFixed(1)})`;n.push(`**How to read this.** This is a browser run. FPS is the main thread's frame rate (requestAnimationFrame); it can't see frames dropped later by the GPU or compositor. Long tasks % is time the main thread was blocked by tasks over 50 ms, not CPU use. For Chrome's own dropped-frame count, use run_web_benchmark.`);n.push("");n.push(`| # | Case | FPS | p95 frame gap ms | Worst gap ms | Long frames | Long tasks % | JS heap MB | Flags |`);n.push(`|---|------|----:|-----------------:|-------------:|------------:|-------------:|--------:|-------|`);for(const d of e.rows){const l=d.isBaseline?`${d.caseName} (baseline)`:d.caseName;if(d.failed){n.push(`| ${d.batchIndex} | ${l} | \u2014 | \u2014 | \u2014 | \u2014 | \u2014 | \u2014 | \u274C FAILED (${d.failureReason}) |`);continue}const p=d.flags.length?`\u26A0\uFE0F ${d.flags.join("; ")}`:"no flags";n.push(`| ${d.batchIndex} | ${l} | ${t(d.jsFps)}${o(d.deltaJsFps)} | ${t(d.frameGapP95Ms)} | ${t(d.frameGapMaxMs)} | ${t(d.longFrames,0)} | ${t(d.longTaskPct)} | ${t(d.memMb)} | ${p} |`)}const a=e.rows.filter(d=>!d.failed).map(d=>{const l=[d.loafBlockingMs!=null&&d.loafBlockingMs>0&&`slow frames blocked input ${d.loafBlockingMs} ms`,d.styleLayoutMs!=null&&d.styleLayoutMs>0&&`style + layout ${d.styleLayoutMs} ms`,d.forcedLayoutMs!=null&&d.forcedLayoutMs>0&&`forced layout ${d.forcedLayoutMs} ms`,d.worstInteractionMs!=null&&`slowest input ${d.worstInteractionMs} ms`,d.layoutShift!=null&&d.layoutShift>0&&`layout shift ${d.layoutShift}`,d.domNodes!=null&&`${d.domNodes} DOM nodes${d.domGrowth?` (+${d.domGrowth} over the runs)`:""}`,d.topScripts&&d.topScripts.length>0&&`top scripts: ${d.topScripts.slice(0,3).map(p=>`${p.name} ${p.ms} ms`).join(", ")}`].filter(Boolean);return l.length?`- **${d.caseName}**: ${l.join(" \xB7 ")}`:null}).filter(Boolean);if(a.length){n.push("");n.push(`**Why frames were slow** (Long Animation Frames, Event Timing, DOM size; slow frames that ran past a run's end are left out):`);n.push(...a)}const r=Tl(e.rows);if(r.length){n.push("");n.push(...r)}const s=tr(e.rows);if(s){n.push("");n.push(s)}const i=Array.from(new Set(e.rows.flatMap(d=>d.missingMetrics??[])));if(i.length){n.push("");n.push(`**Not measured in this browser:** ${i.join(", ")}. These show as \u2014 and can't win or lose.`)}if(e.rows.some(d=>d.refreshCalibrated===false)){n.push("");n.push(`**Refresh rate not checked on an idle screen:** the batch couldn't reach its bounce screen first, so the screen rate (and frame budgets) may read low.`)}if(e.deviceMaxRefreshRate){n.push("");n.push(`Screen rate: ${e.deviceMaxRefreshRate} Hz. A frame gap over ${(1500/e.deviceMaxRefreshRate).toFixed(1)} ms missed at least one frame.`)}n.push("");const c=e.leaders;const u=[c.jsFps&&`FPS \u2192 **${c.jsFps}**`,c.fewestJank&&`fewest long frames \u2192 **${c.fewestJank}**`,c.fewestLongTasks&&`fewest long tasks \u2192 **${c.fewestLongTasks}**`,c.lowestMem&&`lowest heap \u2192 **${c.lowestMem}**`].filter(Boolean).join(" \xB7 ");if(u)n.push(`**Per-metric leaders:** ${u}`);if(e.flagged.length){n.push(`**\u26A0\uFE0F At-risk:** ${e.flagged.map(d=>`${d.caseName} (${d.flags.join("; ")})`).join(" \xB7 ")}`)}n.push(e.balanced.length?`**No configured risk flags:** ${e.balanced.join(", ")}. Check missing metrics and workload requirements before choosing.`:`**No configured risk flags:** no candidates listed. Review failures, available metrics and flags before choosing.`);n.push("");n.push(`_Deltas vs baseline (\`${e.baselineName??"case #0"}\`). Confirm finalists on a real phone browser: desktop numbers don't predict mobile GPU, memory or heat._`)}import{z as H}from"zod";import{z as Al}from"zod";var M=e=>({content:[{type:"text",text:e}]});var k=e=>({isError:true,content:[{type:"text",text:e}]});var B=e=>e instanceof Error?e.message:String(e);var q=Al.string().optional().describe("The device to act on: its id from list_devices or from your instructions. Pass it whenever more than one app may be connected. Once a call names a device, later calls in this session use it when deviceId is left out.");var qe=()=>process.env.BUOY_LOAD_CORE_TOOLS==="0"?void 0:{"anthropic/alwaysLoad":true};function Cl(e,n){return typeof e==="number"&&e>0?e:n}function et(e){return e?`${e}
|
|
4
4
|
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import{createRequire as A}from"module";const G=A(import.meta.url);import{PACKAGE_NAME as l,VERSION as v}from"./chunk-
|
|
1
|
+
import{createRequire as A}from"module";const G=A(import.meta.url);import{PACKAGE_NAME as l,VERSION as v}from"./chunk-TMEMTQ45.mjs";import{spawnSync as m}from"child_process";import s from"fs";import E from"os";import c from"path";function g(){return{BUOY_VERIFY:"auto"}}var w="https://registry.npmjs.org";var f=3e3;function R(e={}){const t=e.cwd??process.cwd();const r=e.skillSrcDir??c.resolve(__dirname,"../skill");const n=e.log??(a=>process.stdout.write(a+"\n"));const o=k(e,n);const i=[];d(c.join(t,".mcp.json"),"mcpServers",o,t,i);s.mkdirSync(c.join(t,".cursor"),{recursive:true});d(c.join(t,".cursor","mcp.json"),"mcpServers",o,t,i);if(s.existsSync(c.join(t,".vscode"))){d(c.join(t,".vscode","mcp.json"),"servers",o,t,i)}D(r,t,i);x(c.join(t,"CLAUDE.md"),t,i);x(c.join(t,"AGENTS.md"),t,i);n("Buoy MCP configuration written:");for(const a of i)n(" \u2022 "+a);n("");n("Next steps:");n(" 1. Restart your editor (or reconnect the MCP server) to load `buoy`.");n(" 2. Sign in: run `npx --package=@buoy-gg/core buoy login` in your app's folder. Then open an app with the required Buoy tools and sync integration.");n(" 3. Confirm the device and tools are available. Free accounts get the basic MCP tools; Pro adds the rest.");n("");n(`Want your AI to use the web too? Run \`npx -y ${l}@latest init --web\` for Buoy Web (free, no account).`);if(o.command==="npx"){n("");n(`The editor launches the server with \`npx -y ${l}@latest\`, so it stays`);n(`Registry access may be needed. Re-run \`npx -y ${l}@latest init\` to refresh configuration.`)}else if(!e.cliPath){n("");n(`This uses a pinned local install (no network on startup). To update it later,`);n(`To update the local package, run: npx -y ${o.args[0]?.replace(/dist[/\\]cli\.js$/,"")??""}@latest init --local`)}}function k(e,t){if(e.cliPath){return{type:"stdio",command:"node",args:[e.cliPath],env:g()}}const r=e.mode??"auto";if(r==="npx"){return y()}if(r==="local"){return $(e,t)}const n=I();if(n.reachable){return y()}t(`\u26A0 npm registry ${n.registry?`(${n.registry}) `:""}looks unreachable \u2014 \`npx @latest\` would hang your editor at startup. Falling back to a pinned local install.`);return $(e,t)}function y(){return{type:"stdio",command:"npx",args:["-y",`${l}@latest`],env:g()}}function $(e,t){const r=e.installDir??process.env.BUOY_MCP_HOME??c.join(E.homedir(),".buoy-mcp");const n=e.registry??w;const o=C(r,n,t);if(o){return{type:"stdio",command:"node",args:[o],env:g()}}t("\u26A0 local install failed \u2014 writing the npx entry instead (see the error above).");return y()}function C(e,t,r){const n=`${l}@${v==="0.0.0-dev"?"latest":v}`;s.mkdirSync(e,{recursive:true});const o=process.platform==="win32"?"npm.cmd":"npm";r(`Installing ${n} into ${e} from ${t} \u2026`);const i=m(o,["install",n,"--prefix",e,"--registry",t,`--@buoy-gg:registry=${t}`,"--no-audit","--no-fund","--loglevel=error"],{stdio:["ignore","ignore","inherit"],encoding:"utf8"});if(i.status!==0){r(`\u26A0 npm install exited with code ${i.status??"?"}${i.error?` (${i.error.message})`:""}.`);return null}const a=c.join(e,"node_modules",l,"dist","cli.js");if(!s.existsSync(a)){r(`\u26A0 install finished but ${a} is missing.`);return null}return a}function I(){const e=P();if(!e)return{reachable:false,registry:null};if(/^https?:\/\/registry\.npmjs\.org\/?$/i.test(e)){return{reachable:true,registry:e}}return{reachable:T(e),registry:e}}function P(){const e=S("@buoy-gg:registry");if(e)return e;const t=S("registry");return t??w}function S(e){const t=process.platform==="win32"?"npm.cmd":"npm";const r=m(t,["config","get",e],{encoding:"utf8",timeout:f});if(r.status!==0||typeof r.stdout!=="string")return null;const n=r.stdout.trim();if(!n||n==="undefined"||n==="null")return null;return n}function T(e){const t=m(process.execPath,["-e",F,e,String(f)],{encoding:"utf8",timeout:f+2e3});return t.status===0}var F=`
|
|
2
2
|
const url = process.argv[1];
|
|
3
3
|
const ms = Number(process.argv[2]) || 3000;
|
|
4
4
|
const c = new AbortController();
|
|
@@ -1,4 +1,4 @@
|
|
|
1
1
|
import{createRequire as q}from"module";const fe=q(import.meta.url);import I from"fs";import R from"os";import y from"path";var G='// Buoy Web helper. Chrome starts it for the Buoy Web extension (native messaging) and\n// ends it when Chrome closes. It passes messages between the extension and Buoy MCP\n// sessions, which connect over a Unix socket only this user can open.\n//\n// extension <-- stdin/stdout (native messaging) --> helper <-- ~/.buoy/web/chrome-<pid>.sock --> MCP sessions\nimport fs from "node:fs";\nimport net from "node:net";\nimport os from "node:os";\nimport path from "node:path";\n\nconst dir = process.env.BUOY_WEB_DIR ?? path.join(os.homedir(), ".buoy", "web");\nfs.mkdirSync(dir, { recursive: true, mode: 0o700 });\nfs.chmodSync(dir, 0o700);\nconst sock = path.join(dir, `chrome-${process.pid}.sock`);\nconst info = path.join(dir, `chrome-${process.pid}.json`);\n\n// ---- native messaging: 4-byte little-endian length, then JSON -----------------\nlet inbuf = Buffer.alloc(0);\nprocess.stdin.on("data", (chunk) => {\n inbuf = Buffer.concat([inbuf, chunk]);\n while (inbuf.length >= 4) {\n const n = inbuf.readUInt32LE(0);\n if (inbuf.length < 4 + n) break;\n const msg = JSON.parse(inbuf.subarray(4, 4 + n).toString("utf8"));\n inbuf = inbuf.subarray(4 + n);\n fromExtension(msg);\n }\n});\nprocess.stdin.on("end", quit); // Chrome closed or the extension let go\nprocess.stdout.on("error", quit);\nfunction toExtension(msg) {\n const body = Buffer.from(JSON.stringify(msg), "utf8");\n if (body.length > 1024 * 1024) throw new Error("Message too big for Chrome (over 1 MB).");\n const head = Buffer.alloc(4);\n head.writeUInt32LE(body.length, 0);\n process.stdout.write(Buffer.concat([head, body]));\n}\n\n// ---- MCP sessions: one JSON message per line ------------------------------------\nconst clients = new Map(); // id -> socket\nlet nextClient = 1;\nfunction fromExtension(msg) {\n const s = clients.get(msg.c);\n if (!s) return;\n const { c, ...rest } = msg;\n s.write(JSON.stringify(rest) + "\\n");\n}\n\nconst server = net.createServer((s) => {\n const c = nextClient++;\n clients.set(c, s);\n let buf = "";\n s.setEncoding("utf8");\n s.on("data", (d) => {\n buf += d;\n let i;\n while ((i = buf.indexOf("\\n")) >= 0) {\n const line = buf.slice(0, i);\n buf = buf.slice(i + 1);\n if (!line) continue;\n let msg;\n try {\n msg = JSON.parse(line);\n toExtension({ ...msg, c });\n } catch (e) {\n if (msg?.id != null) s.write(JSON.stringify({ id: msg.id, error: String(e.message ?? e) }) + "\\n");\n }\n }\n });\n const gone = () => {\n if (!clients.delete(c)) return;\n try {\n toExtension({ c, method: "client.closed" });\n } catch {}\n };\n s.on("close", gone);\n s.on("error", gone);\n});\n\n// Clear out sockets left by helpers that died without cleaning up.\nfor (const f of fs.readdirSync(dir)) {\n const m = /^chrome-(\\d+)\\.(sock|json)$/.exec(f);\n if (!m || Number(m[1]) === process.pid) continue;\n try {\n process.kill(Number(m[1]), 0);\n } catch {\n fs.rmSync(path.join(dir, f), { force: true });\n }\n}\n\nconst old = process.umask(0o177); // the socket file is created 0600\nserver.listen(sock, () => {\n process.umask(old);\n fs.writeFileSync(info, JSON.stringify({ pid: process.pid, sock, started: Date.now() }), { mode: 0o600 });\n // Tells Buoy the extension is set up, so a closed Chrome isn\'t mistaken for "no extension".\n fs.writeFileSync(path.join(dir, "seen"), new Date().toISOString(), { mode: 0o600 });\n toExtension({ ready: true }); // the extension shows "connected" only after this\n});\n\nfunction quit() {\n for (const f of [sock, info]) fs.rmSync(f, { force: true });\n process.exit(0);\n}\nprocess.on("SIGTERM", quit);\nprocess.on("SIGINT", quit);\n';import V from"crypto";import N from"fs";import X from"http";import K from"net";import z from"os";import U from"path";import{WebSocketServer as Q}from"ws";var Z=process.env.BUOY_WEB_RELAYLOG==="1";var M=(...n)=>Z&&process.stderr.write(`[buoy-web relay] ${n.join(" ")}
|
|
2
2
|
`);var x=()=>process.env.BUOY_WEB_DIR??U.join(z.homedir(),".buoy","web");function ee(n=x()){let e=[];try{e=N.readdirSync(n)}catch{return[]}const i=[];for(const o of e){const a=/^chrome-(\d+)\.json$/.exec(o);if(!a)continue;try{process.kill(Number(a[1]),0);const b=JSON.parse(N.readFileSync(U.join(n,o),"utf8"));if(N.existsSync(b.sock))i.push(b)}catch{}}return i.sort((o,a)=>a.started-o.started)}function te(n){return new Promise((e,i)=>{const o=K.createConnection(n);const a=new Map;let b=1;let l="";let d=false;let v=false;const w={onEvent:()=>{},onClose:()=>{},call(m,S){if(v)return Promise.reject(new Error("Chrome's Buoy helper closed."));const c=b++;o.write(JSON.stringify({id:c,method:m,params:S})+"\n");return new Promise((O,B)=>a.set(c,{res:O,rej:B}))},close:()=>o.end()};o.setEncoding("utf8");o.on("connect",()=>{d=true;e(w)});o.on("error",m=>{if(!d)i(m)});o.on("data",m=>{l+=m;let S;while((S=l.indexOf("\n"))>=0){const c=JSON.parse(l.slice(0,S));l=l.slice(S+1);if(c.id!=null&&a.has(c.id)){const{res:O,rej:B}=a.get(c.id);a.delete(c.id);if(c.error)B(new Error(c.error));else O(c.result)}else if(c.event)w.onEvent(c.event,c.params)}});o.on("close",()=>{v=true;for(const{rej:m}of a.values())m(new Error("Chrome's Buoy helper closed."));a.clear();w.onClose()})})}async function ne({sock:n}){const e=await te(n);const i=await e.call("hello");const o=new Map;let a=1;let b=false;let l=null;const d=new Set;const v=t=>{if(l?.readyState===1)l.send(JSON.stringify(t))};const w=t=>[...o.values()].find(t);const m={url:"",hello:i,onDetach:()=>{},onClose:()=>{},idle:()=>Promise.all([...d]),close:()=>{}};async function S(t,{settle:r=false}={}){const s=o.get(t);if(s)return s;await e.call("debugger.attach",{tabId:t});if(r){const E=Date.now();while(Date.now()-E<5e3){const T=await e.call("debugger.send",{tabId:t,method:"Runtime.evaluate",params:{expression:"[location.href, document.readyState]",returnByValue:true}}).catch(()=>null);const[D,L]=T?.result?.value??[];if(D&&D!=="about:blank"&&L!=="loading")break;await new Promise(H=>setTimeout(H,50))}}const{targetInfo:u}=await e.call("debugger.send",{tabId:t,method:"Target.getTargetInfo"});const{openerId:g,openerFrameId:f,...p}=u;p.canAccessOpener=false;const h={tabId:t,sessionId:`buoy-tab-${a++}`,targetInfo:p,children:new Set,framesSent:false,held:[]};o.set(t,h);v({method:"Target.attachedToTarget",params:{sessionId:h.sessionId,targetInfo:{...p,attached:true},waitingForDebugger:false}});return h}function c(t){const r=o.get(t);if(!r)return;o.delete(t);v({method:"Target.detachedFromTarget",params:{sessionId:r.sessionId,targetId:r.targetInfo?.targetId}})}e.onEvent=(t,r)=>{if(t==="debugger.event"){const s=o.get(r.tabId);if(!s)return;const u=r.params?.sessionId;if(r.method==="Target.attachedToTarget"&&u)s.children.add(u);if(r.method==="Target.detachedFromTarget"&&u)s.children.delete(u);const g={sessionId:r.sessionId||s.sessionId,method:r.method,params:r.params};if(!s.framesSent)s.held.push(g);else v(g)}else if(t==="debugger.detached"){c(r.tabId);m.onDetach(r)}else if(t==="tabs.created"){if(b){const s=S(r.id,{settle:true}).catch(()=>{});d.add(s);void s.finally(()=>d.delete(s))}}else if(t==="tabs.removed")c(r.tabId)};e.onClose=()=>{for(const t of[...o.keys()])c(t);l?.close();m.onClose()};async function O(t,r,s){if(!s){switch(t){case"Browser.getVersion":return{protocolVersion:"1.3",product:"Chrome/BuoyWeb",userAgent:i.userAgent,jsVersion:""};case"Browser.setDownloadBehavior":case"Target.setDiscoverTargets":case"Browser.close":return{};case"Target.setAutoAttach":b=true;return{};case"Target.getTargetInfo":return{targetInfo:{targetId:"buoy-browser",type:"browser",title:"",url:"",attached:true,canAccessOpener:false}};case"Target.getBrowserContexts":return{browserContextIds:[]};case"Target.createTarget":{const p=await e.call("tabs.create",{url:r?.url});const h=await S(p.id);return{targetId:h.targetInfo.targetId}}case"Target.closeTarget":{const p=w(h=>h.targetInfo?.targetId===r?.targetId);if(!p)return{success:false};await e.call("tabs.remove",{tabId:p.tabId});return{success:true}}}const f=o.values().next().value;if(!f)throw new Error(`No Buoy tab to run ${t} on.`);return e.call("debugger.send",{tabId:f.tabId,method:t,params:r})}if(t==="Target.getTargetInfo"){const f=w(p=>p.sessionId===s);if(f)return{targetInfo:f.targetInfo}}let u=w(f=>f.sessionId===s);let g;if(!u){u=w(f=>f.children.has(s));g=s}if(!u)throw new Error(`No Buoy tab for session ${s}.`);return e.call("debugger.send",{tabId:u.tabId,sessionId:g,method:t,params:r})}const B=V.randomUUID();const _=X.createServer((t,r)=>r.writeHead(404).end());const C=new Q({server:_});C.on("connection",(t,r)=>{if(r.url!==`/${B}`||l)return t.close(4004,"No.");l=t;t.on("message",async s=>{const{id:u,sessionId:g,method:f,params:p}=JSON.parse(s.toString());try{const h=await O(f,p,g);M("ok ",g??"-",f);t.send(JSON.stringify({id:u,sessionId:g,result:h??{}}));if(f==="Page.getFrameTree"){const E=w(T=>T.sessionId===g);if(E&&!E.framesSent){E.framesSent=true;for(const T of E.held.splice(0))v(T)}}}catch(h){M("ERR",g??"-",f,h.message);t.send(JSON.stringify({id:u,sessionId:g,error:{message:String(h.message??h)}}))}});t.on("close",()=>{l=null;e.close()})});await new Promise(t=>_.listen(0,"127.0.0.1",t));m.url=`ws://127.0.0.1:${_.address().port}/${B}`;m.close=()=>{e.close();C.close();_.close()};return m}var J="gg.buoy.web";var $=["cfolfiajhlenfifknbomdogooagnlamf","kjbgbjimhjiocnfifnemmenkondjeifp",...(process.env.BUOY_WEB_EXTENSION_IDS??"").split(",").filter(Boolean)];var A=["Google/Chrome","Google/Chrome Beta","Google/Chrome Dev","Google/Chrome Canary","Chromium","BraveSoftware/Brave-Browser","Microsoft Edge","Vivaldi","Arc/User Data"];function P(){if(process.env.BUOY_WEB_NMH_DIRS)return process.env.BUOY_WEB_NMH_DIRS.split(y.delimiter).filter(Boolean);if(process.platform!=="darwin")return[];const n=y.join(R.homedir(),"Library","Application Support");return A.map(e=>y.join(n,e)).filter(e=>I.existsSync(e)).map(e=>y.join(e,"NativeMessagingHosts"))}var j=(n,e,i)=>{try{if(I.readFileSync(n,"utf8")===e)return}catch{}I.writeFileSync(n,e,{mode:i});I.chmodSync(n,i)};function oe(){try{const n=y.join(x(),"helper");I.mkdirSync(n,{recursive:true,mode:448});const e=y.join(n,"helper.mjs");const i=y.join(n,"helper.sh");j(e,G,384);const o=d=>`'${d.replace(/'/g,`'\\''`)}'`;const a=process.env.BUOY_WEB_DIR?`BUOY_WEB_DIR=${o(process.env.BUOY_WEB_DIR)} `:"";j(i,`#!/bin/sh
|
|
3
3
|
${a}exec ${o(process.execPath)} ${o(e)} "$@"
|
|
4
|
-
`,448);const b=JSON.stringify({name:J,description:"Buoy Web helper: lets the Buoy MCP use Buoy's own tabs through the Buoy Web extension.",path:i,type:"stdio",allowed_origins:$.map(d=>`chrome-extension://${d}/`)},null,2);const l=[];for(const d of P()){try{I.mkdirSync(d,{recursive:true});j(y.join(d,`${J}.json`),b+"\n",420);l.push(d)}catch{}}return l}catch{return[]}}function re(){if(process.env.BUOY_WEB_NMH_DIRS)return false;if(process.platform!=="darwin")return false;const n=y.join(R.homedir(),"Library","Application Support");for(const e of A){let i=[];try{i=I.readdirSync(y.join(n,e))}catch{continue}for(const o of i){if(o!=="Default"&&!/^Profile \d+$/.test(o))continue;if($.some(a=>I.existsSync(y.join(n,e,o,"Extensions",a))))return true}}return false}import se from"crypto";import k from"fs";import W from"path";var ie=process.env.BUOY_TELEMETRY_URL??"https://buoy.gg/api/t";var ae=24*60*60*1e3;function Y(){const n=(process.env.BUOY_TELEMETRY??"").toLowerCase();return n==="0"||n==="false"||n==="off"||process.env.DO_NOT_TRACK==="1"}var F=false;function ce(){if(F||Y())return;F=true;try{const n=W.join(x(),"install.json");let e={};try{e=JSON.parse(k.readFileSync(n,"utf8"))}catch{}if(typeof e.installId!=="string")e={installId:se.randomUUID(),lastPingAt:0};if(Date.now()-(e.lastPingAt??0)<ae)return;e.lastPingAt=Date.now();k.mkdirSync(W.dirname(n),{recursive:true,mode:448});k.writeFileSync(n,JSON.stringify(e),{mode:384});const i=JSON.stringify({installId:e.installId,version:"7.0.
|
|
4
|
+
`,448);const b=JSON.stringify({name:J,description:"Buoy Web helper: lets the Buoy MCP use Buoy's own tabs through the Buoy Web extension.",path:i,type:"stdio",allowed_origins:$.map(d=>`chrome-extension://${d}/`)},null,2);const l=[];for(const d of P()){try{I.mkdirSync(d,{recursive:true});j(y.join(d,`${J}.json`),b+"\n",420);l.push(d)}catch{}}return l}catch{return[]}}function re(){if(process.env.BUOY_WEB_NMH_DIRS)return false;if(process.platform!=="darwin")return false;const n=y.join(R.homedir(),"Library","Application Support");for(const e of A){let i=[];try{i=I.readdirSync(y.join(n,e))}catch{continue}for(const o of i){if(o!=="Default"&&!/^Profile \d+$/.test(o))continue;if($.some(a=>I.existsSync(y.join(n,e,o,"Extensions",a))))return true}}return false}import se from"crypto";import k from"fs";import W from"path";var ie=process.env.BUOY_TELEMETRY_URL??"https://buoy.gg/api/t";var ae=24*60*60*1e3;function Y(){const n=(process.env.BUOY_TELEMETRY??"").toLowerCase();return n==="0"||n==="false"||n==="off"||process.env.DO_NOT_TRACK==="1"}var F=false;function ce(){if(F||Y())return;F=true;try{const n=W.join(x(),"install.json");let e={};try{e=JSON.parse(k.readFileSync(n,"utf8"))}catch{}if(typeof e.installId!=="string")e={installId:se.randomUUID(),lastPingAt:0};if(Date.now()-(e.lastPingAt??0)<ae)return;e.lastPingAt=Date.now();k.mkdirSync(W.dirname(n),{recursive:true,mode:448});k.writeFileSync(n,JSON.stringify(e),{mode:384});const i=JSON.stringify({installId:e.installId,version:"7.0.81",platform:process.platform==="darwin"?"macos":process.platform==="win32"?"windows":"linux",tier:"locked",surface:"web-mcp"});void fetch(ie,{method:"POST",headers:{"content-type":"application/json"},body:i,signal:AbortSignal.timeout(5e3)}).catch(()=>{})}catch{}}export{x as webDir,ee as findHelpers,ne as startRelay,P as helperManifestDirs,oe as ensureHelper,re as extensionInstalled,Y as telemetryOff,ce as noteUse};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
import{createRequire as e}from"module";const a=e(import.meta.url);var r="7.0.
|
|
1
|
+
import{createRequire as e}from"module";const a=e(import.meta.url);var r="7.0.81"?"7.0.81":"0.0.0-dev";var o="@buoy-gg/mcp";export{r as VERSION,o as PACKAGE_NAME};
|