gazesight 0.4.0 → 0.5.0

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.
@@ -117,7 +117,9 @@
117
117
  ><span id="select-label">Use this tab</span>
118
118
  </button>
119
119
  <p class="hint">
120
- Select the page you want your agent to see.
120
+ Selecting a tab allows your configured coding agent to receive its
121
+ screenshots, page structure, URL, and diagnostics through your local MCP
122
+ runtime.
121
123
  <span id="shortcut">Ctrl+Shift+Y</span> also selects or reauthorizes
122
124
  a tab.
123
125
  </p>
@@ -1 +1 @@
1
- var V=Object.create;var N=Object.defineProperty;var Z=Object.getOwnPropertyDescriptor;var K=Object.getOwnPropertyNames;var J=Object.getPrototypeOf,Q=Object.prototype.hasOwnProperty;var X=(e,r)=>()=>(r||e((r={exports:{}}).exports,r),r.exports);var Y=(e,r,g,m)=>{if(r&&typeof r=="object"||typeof r=="function")for(let l of K(r))!Q.call(e,l)&&l!==g&&N(e,l,{get:()=>r[l],enumerable:!(m=Z(r,l))||m.enumerable});return e};var ee=(e,r,g)=>(g=e!=null?V(J(e)):{},Y(r||!e||!e.__esModule?N(g,"default",{value:e,enumerable:!0}):g,e));var _=X((B,M)=>{(function(e,r){if(typeof define=="function"&&define.amd)define("webextension-polyfill",["module"],r);else if(typeof B<"u")r(M);else{var g={exports:{}};r(g),e.browser=g.exports}})(typeof globalThis<"u"?globalThis:typeof self<"u"?self:B,function(e){"use strict";if(!(globalThis.chrome&&globalThis.chrome.runtime&&globalThis.chrome.runtime.id))throw new Error("This script should only be loaded in a browser extension.");if(globalThis.browser&&globalThis.browser.runtime&&globalThis.browser.runtime.id)e.exports=globalThis.browser;else{let r="The message port closed before a response was received.",g=m=>{let l={alarms:{clear:{minArgs:0,maxArgs:1},clearAll:{minArgs:0,maxArgs:0},get:{minArgs:0,maxArgs:1},getAll:{minArgs:0,maxArgs:0}},bookmarks:{create:{minArgs:1,maxArgs:1},get:{minArgs:1,maxArgs:1},getChildren:{minArgs:1,maxArgs:1},getRecent:{minArgs:1,maxArgs:1},getSubTree:{minArgs:1,maxArgs:1},getTree:{minArgs:0,maxArgs:0},move:{minArgs:2,maxArgs:2},remove:{minArgs:1,maxArgs:1},removeTree:{minArgs:1,maxArgs:1},search:{minArgs:1,maxArgs:1},update:{minArgs:2,maxArgs:2}},browserAction:{disable:{minArgs:0,maxArgs:1,fallbackToNoCallback:!0},enable:{minArgs:0,maxArgs:1,fallbackToNoCallback:!0},getBadgeBackgroundColor:{minArgs:1,maxArgs:1},getBadgeText:{minArgs:1,maxArgs:1},getPopup:{minArgs:1,maxArgs:1},getTitle:{minArgs:1,maxArgs:1},openPopup:{minArgs:0,maxArgs:0},setBadgeBackgroundColor:{minArgs:1,maxArgs:1,fallbackToNoCallback:!0},setBadgeText:{minArgs:1,maxArgs:1,fallbackToNoCallback:!0},setIcon:{minArgs:1,maxArgs:1},setPopup:{minArgs:1,maxArgs:1,fallbackToNoCallback:!0},setTitle:{minArgs:1,maxArgs:1,fallbackToNoCallback:!0}},browsingData:{remove:{minArgs:2,maxArgs:2},removeCache:{minArgs:1,maxArgs:1},removeCookies:{minArgs:1,maxArgs:1},removeDownloads:{minArgs:1,maxArgs:1},removeFormData:{minArgs:1,maxArgs:1},removeHistory:{minArgs:1,maxArgs:1},removeLocalStorage:{minArgs:1,maxArgs:1},removePasswords:{minArgs:1,maxArgs:1},removePluginData:{minArgs:1,maxArgs:1},settings:{minArgs:0,maxArgs:0}},commands:{getAll:{minArgs:0,maxArgs:0}},contextMenus:{remove:{minArgs:1,maxArgs:1},removeAll:{minArgs:0,maxArgs:0},update:{minArgs:2,maxArgs:2}},cookies:{get:{minArgs:1,maxArgs:1},getAll:{minArgs:1,maxArgs:1},getAllCookieStores:{minArgs:0,maxArgs:0},remove:{minArgs:1,maxArgs:1},set:{minArgs:1,maxArgs:1}},devtools:{inspectedWindow:{eval:{minArgs:1,maxArgs:2,singleCallbackArg:!1}},panels:{create:{minArgs:3,maxArgs:3,singleCallbackArg:!0},elements:{createSidebarPane:{minArgs:1,maxArgs:1}}}},downloads:{cancel:{minArgs:1,maxArgs:1},download:{minArgs:1,maxArgs:1},erase:{minArgs:1,maxArgs:1},getFileIcon:{minArgs:1,maxArgs:2},open:{minArgs:1,maxArgs:1,fallbackToNoCallback:!0},pause:{minArgs:1,maxArgs:1},removeFile:{minArgs:1,maxArgs:1},resume:{minArgs:1,maxArgs:1},search:{minArgs:1,maxArgs:1},show:{minArgs:1,maxArgs:1,fallbackToNoCallback:!0}},extension:{isAllowedFileSchemeAccess:{minArgs:0,maxArgs:0},isAllowedIncognitoAccess:{minArgs:0,maxArgs:0}},history:{addUrl:{minArgs:1,maxArgs:1},deleteAll:{minArgs:0,maxArgs:0},deleteRange:{minArgs:1,maxArgs:1},deleteUrl:{minArgs:1,maxArgs:1},getVisits:{minArgs:1,maxArgs:1},search:{minArgs:1,maxArgs:1}},i18n:{detectLanguage:{minArgs:1,maxArgs:1},getAcceptLanguages:{minArgs:0,maxArgs:0}},identity:{launchWebAuthFlow:{minArgs:1,maxArgs:1}},idle:{queryState:{minArgs:1,maxArgs:1}},management:{get:{minArgs:1,maxArgs:1},getAll:{minArgs:0,maxArgs:0},getSelf:{minArgs:0,maxArgs:0},setEnabled:{minArgs:2,maxArgs:2},uninstallSelf:{minArgs:0,maxArgs:1}},notifications:{clear:{minArgs:1,maxArgs:1},create:{minArgs:1,maxArgs:2},getAll:{minArgs:0,maxArgs:0},getPermissionLevel:{minArgs:0,maxArgs:0},update:{minArgs:2,maxArgs:2}},pageAction:{getPopup:{minArgs:1,maxArgs:1},getTitle:{minArgs:1,maxArgs:1},hide:{minArgs:1,maxArgs:1,fallbackToNoCallback:!0},setIcon:{minArgs:1,maxArgs:1},setPopup:{minArgs:1,maxArgs:1,fallbackToNoCallback:!0},setTitle:{minArgs:1,maxArgs:1,fallbackToNoCallback:!0},show:{minArgs:1,maxArgs:1,fallbackToNoCallback:!0}},permissions:{contains:{minArgs:1,maxArgs:1},getAll:{minArgs:0,maxArgs:0},remove:{minArgs:1,maxArgs:1},request:{minArgs:1,maxArgs:1}},runtime:{getBackgroundPage:{minArgs:0,maxArgs:0},getPlatformInfo:{minArgs:0,maxArgs:0},openOptionsPage:{minArgs:0,maxArgs:0},requestUpdateCheck:{minArgs:0,maxArgs:0},sendMessage:{minArgs:1,maxArgs:3},sendNativeMessage:{minArgs:2,maxArgs:2},setUninstallURL:{minArgs:1,maxArgs:1}},sessions:{getDevices:{minArgs:0,maxArgs:1},getRecentlyClosed:{minArgs:0,maxArgs:1},restore:{minArgs:0,maxArgs:1}},storage:{local:{clear:{minArgs:0,maxArgs:0},get:{minArgs:0,maxArgs:1},getBytesInUse:{minArgs:0,maxArgs:1},remove:{minArgs:1,maxArgs:1},set:{minArgs:1,maxArgs:1}},managed:{get:{minArgs:0,maxArgs:1},getBytesInUse:{minArgs:0,maxArgs:1}},sync:{clear:{minArgs:0,maxArgs:0},get:{minArgs:0,maxArgs:1},getBytesInUse:{minArgs:0,maxArgs:1},remove:{minArgs:1,maxArgs:1},set:{minArgs:1,maxArgs:1}}},tabs:{captureVisibleTab:{minArgs:0,maxArgs:2},create:{minArgs:1,maxArgs:1},detectLanguage:{minArgs:0,maxArgs:1},discard:{minArgs:0,maxArgs:1},duplicate:{minArgs:1,maxArgs:1},executeScript:{minArgs:1,maxArgs:2},get:{minArgs:1,maxArgs:1},getCurrent:{minArgs:0,maxArgs:0},getZoom:{minArgs:0,maxArgs:1},getZoomSettings:{minArgs:0,maxArgs:1},goBack:{minArgs:0,maxArgs:1},goForward:{minArgs:0,maxArgs:1},highlight:{minArgs:1,maxArgs:1},insertCSS:{minArgs:1,maxArgs:2},move:{minArgs:2,maxArgs:2},query:{minArgs:1,maxArgs:1},reload:{minArgs:0,maxArgs:2},remove:{minArgs:1,maxArgs:1},removeCSS:{minArgs:1,maxArgs:2},sendMessage:{minArgs:2,maxArgs:3},setZoom:{minArgs:1,maxArgs:2},setZoomSettings:{minArgs:1,maxArgs:2},update:{minArgs:1,maxArgs:2}},topSites:{get:{minArgs:0,maxArgs:0}},webNavigation:{getAllFrames:{minArgs:1,maxArgs:1},getFrame:{minArgs:1,maxArgs:1}},webRequest:{handlerBehaviorChanged:{minArgs:0,maxArgs:0}},windows:{create:{minArgs:0,maxArgs:1},get:{minArgs:1,maxArgs:2},getAll:{minArgs:0,maxArgs:1},getCurrent:{minArgs:0,maxArgs:1},getLastFocused:{minArgs:0,maxArgs:1},remove:{minArgs:1,maxArgs:1},update:{minArgs:2,maxArgs:2}}};if(Object.keys(l).length===0)throw new Error("api-metadata.json has not been included in browser-polyfill");class w extends WeakMap{constructor(n,a=void 0){super(a),this.createItem=n}get(n){return this.has(n)||this.set(n,this.createItem(n)),super.get(n)}}let p=t=>t&&typeof t=="object"&&typeof t.then=="function",E=(t,n)=>(...a)=>{m.runtime.lastError?t.reject(new Error(m.runtime.lastError.message)):n.singleCallbackArg||a.length<=1&&n.singleCallbackArg!==!1?t.resolve(a[0]):t.resolve(a)},S=t=>t==1?"argument":"arguments",D=(t,n)=>function(i,...d){if(d.length<n.minArgs)throw new Error(`Expected at least ${n.minArgs} ${S(n.minArgs)} for ${t}(), got ${d.length}`);if(d.length>n.maxArgs)throw new Error(`Expected at most ${n.maxArgs} ${S(n.maxArgs)} for ${t}(), got ${d.length}`);return new Promise((u,A)=>{if(n.fallbackToNoCallback)try{i[t](...d,E({resolve:u,reject:A},n))}catch(s){console.warn(`${t} API method doesn't seem to support the callback parameter, falling back to call it without a callback: `,s),i[t](...d),n.fallbackToNoCallback=!1,n.noCallback=!0,u()}else n.noCallback?(i[t](...d),u()):i[t](...d,E({resolve:u,reject:A},n))})},R=(t,n,a)=>new Proxy(n,{apply(i,d,u){return a.call(d,t,...u)}}),C=Function.call.bind(Object.prototype.hasOwnProperty),T=(t,n={},a={})=>{let i=Object.create(null),d={has(A,s){return s in t||s in i},get(A,s,b){if(s in i)return i[s];if(!(s in t))return;let c=t[s];if(typeof c=="function")if(typeof n[s]=="function")c=R(t,t[s],n[s]);else if(C(a,s)){let y=D(s,a[s]);c=R(t,t[s],y)}else c=c.bind(t);else if(typeof c=="object"&&c!==null&&(C(n,s)||C(a,s)))c=T(c,n[s],a[s]);else if(C(a,"*"))c=T(c,n[s],a["*"]);else return Object.defineProperty(i,s,{configurable:!0,enumerable:!0,get(){return t[s]},set(y){t[s]=y}}),c;return i[s]=c,c},set(A,s,b,c){return s in i?i[s]=b:t[s]=b,!0},defineProperty(A,s,b){return Reflect.defineProperty(i,s,b)},deleteProperty(A,s){return Reflect.deleteProperty(i,s)}},u=Object.create(t);return new Proxy(u,d)},P=t=>({addListener(n,a,...i){n.addListener(t.get(a),...i)},hasListener(n,a){return n.hasListener(t.get(a))},removeListener(n,a){n.removeListener(t.get(a))}}),G=new w(t=>typeof t!="function"?t:function(a){let i=T(a,{},{getContent:{minArgs:0,maxArgs:0}});t(i)}),L=new w(t=>typeof t!="function"?t:function(a,i,d){let u=!1,A,s=new Promise(v=>{A=function(x){u=!0,v(x)}}),b;try{b=t(a,i,A)}catch(v){b=Promise.reject(v)}let c=b!==!0&&p(b);if(b!==!0&&!c&&!u)return!1;let y=v=>{v.then(x=>{d(x)},x=>{let I;x&&(x instanceof Error||typeof x.message=="string")?I=x.message:I="An unexpected error occurred",d({__mozWebExtensionPolyfillReject__:!0,message:I})}).catch(x=>{console.error("Failed to send onMessage rejected reply",x)})};return y(c?b:s),!0}),W=({reject:t,resolve:n},a)=>{m.runtime.lastError?m.runtime.lastError.message===r?n():t(new Error(m.runtime.lastError.message)):a&&a.__mozWebExtensionPolyfillReject__?t(new Error(a.message)):n(a)},$=(t,n,a,...i)=>{if(i.length<n.minArgs)throw new Error(`Expected at least ${n.minArgs} ${S(n.minArgs)} for ${t}(), got ${i.length}`);if(i.length>n.maxArgs)throw new Error(`Expected at most ${n.maxArgs} ${S(n.maxArgs)} for ${t}(), got ${i.length}`);return new Promise((d,u)=>{let A=W.bind(null,{resolve:d,reject:u});i.push(A),a.sendMessage(...i)})},H={devtools:{network:{onRequestFinished:P(G)}},runtime:{onMessage:P(L),onMessageExternal:P(L),sendMessage:$.bind(null,"sendMessage",{minArgs:1,maxArgs:3})},tabs:{sendMessage:$.bind(null,"sendMessage",{minArgs:2,maxArgs:3})}},z={clear:{minArgs:1,maxArgs:1},get:{minArgs:1,maxArgs:1},set:{minArgs:1,maxArgs:1}};return l.privacy={network:{"*":z},services:{"*":z},websites:{"*":z}},T(m,H,l)};e.exports=g(chrome)}})});var q=ee(_(),1);globalThis.browser=q.default;function re(){return[...crypto.getRandomValues(new Uint8Array(32))].map(r=>r.toString(16).padStart(2,"0")).join("")}function te(){return location.protocol==="moz-extension:"?"firefox":"chrome"}async function O(e,r,g,m){return fetch(`http://127.0.0.1:${e}/gazesight/pairing/${r}`,{method:"POST",headers:{"Content-Type":"application/json",...m?{Authorization:`Bearer ${m}`}:{}},body:JSON.stringify(g),signal:AbortSignal.timeout(2500)})}async function j(e,r=32147){e("searching");let g=re(),m;try{m=await O(r,"request",{browser:te(),extensionId:browser.runtime.id,secret:g})}catch{return e("unavailable"),!1}if(!m.ok)return e("unavailable"),!1;let l=await m.json();for(e("pending",l.code);;){if(Date.now()>=l.expiresAt)return e("expired",l.code),!1;await new Promise(E=>setTimeout(E,800)),e("waiting_approval",l.code);let w;try{w=await O(r,"status",{requestId:l.requestId,secret:g})}catch{continue}let p=await w.json();if(p.state==="approved"&&p.credential&&p.pairingId)return await browser.storage.local.set({token:p.credential,pairingId:p.pairingId,port:r,pairingState:"connected"}),e("connected"),!0;if(p.state==="rejected")return e("reconnect_required",l.code),!1;if(p.state==="expired"||w.status===410)return e("expired",l.code),!1}}function o(e,r){document.getElementById(e).textContent=r}var k=!1,F;function U(e,r=!1){clearTimeout(F),o("feedback",e),r&&(F=setTimeout(()=>o("feedback",""),3500))}async function f(e){let r=await browser.runtime.sendMessage({type:"gazesight-ui",action:e});if(!r?.ok)throw new Error(r?.error??"GazeSight unavailable. Open settings to check pairing.");return r.value}async function h(){let e=await f("status");document.body.dataset.connected=String(e.connected),o("connection",e.connected?"Connected":"Disconnected");let r=await browser.storage.local.get(["token","port"]);document.getElementById("pairing-connect").hidden=typeof r.token=="string",document.body.dataset.background=e.backgroundCapture.enabled?"enabled":"disabled",o("background-state",e.backgroundCapture.enabled?"On":"Off"),o("background-toggle",e.backgroundCapture.enabled?"Disable":"Enable"),document.getElementById("background-toggle").setAttribute("aria-pressed",String(e.backgroundCapture.enabled)),o("background-description",e.backgroundCapture.enabled?"GazeSight can capture this target while you work elsewhere.":e.backgroundCapture.permissionGranted?"Background mode is off. Firefox still reports the optional permission; remove it in Add-ons Manager if needed.":e.backgroundMode==="permission_denied"?"Permission was not granted. Standard foreground capture remains available.":"Allow GazeSight to keep capturing the selected development tab while you work in another tab or application."),document.body.dataset.continuous=e.continuousAuthorization.enabled?"enabled":"disabled",o("continuous-state",e.continuousAuthorization.enabled?"On":"Off"),o("continuous-toggle",e.continuousAuthorization.enabled?"Disable":"Enable"),document.getElementById("continuous-toggle").setAttribute("aria-pressed",String(e.continuousAuthorization.enabled)),o("continuous-description",e.continuousAuthorization.enabled?e.continuousAuthorization.authorizedOrigin?`Staying connected to ${e.continuousAuthorization.authorizedOrigin} across reloads, navigation and previews.`:"Keeps GazeSight connected across reloads, navigation and previews once you select a tab.":"Keep GazeSight connected to this development tab across reloads, navigation and responsive previews, so you don't have to reauthorize it every time."),o("pairing",e.paired?"Paired":"Not paired"),o("target",e.targetState==="authorized"?"Authorized":e.targetState==="none"?"None":"Reauthorize"),o("url",e.url??"No target selected"),o("viewport",e.viewport?`${e.viewport.width} \xD7 ${e.viewport.height} CSS \xB7 DPR ${e.viewport.devicePixelRatio}`:"Viewport unavailable"),o("select-label",e.targetState==="reauthorization_required"?"Reauthorize this tab":"Use this tab"),o("last-capture",e.lastCapture?`${e.lastCapture.mode} \xB7 ${e.lastCapture.width} \xD7 ${e.lastCapture.height} \xB7 ${new Date(e.lastCapture.time).toLocaleTimeString()}`:"None yet"),o("last-error",e.lastError?`${e.lastError.code}: ${e.lastError.message}`:"None");for(let g of["fast","stable"])document.getElementById(g).disabled=k||!e.connected||e.targetState!=="authorized";document.getElementById("disconnect").disabled=k||e.targetState==="none"}var ne={not_connected:"Not connected.",searching:"Searching for local GazeSight\u2026",pending:"Pairing request pending.",waiting_approval:"Waiting for approval in your terminal\u2026",connected:"Connected.",unavailable:"Connection unavailable. Run gazesight pair and try again.",expired:"Pairing expired. Try again.",reconnect_required:"Reconnect required."};document.getElementById("connect").addEventListener("click",async()=>{let e=document.getElementById("connect"),r=document.getElementById("pairing-code");e.disabled=!0;let g=await browser.storage.local.get(["port"]);await j((m,l)=>{o("pairing-message",ne[m]),r.hidden=!l,r.textContent=l?`Verification code: ${l}`:""},Number(g.port??32147)),e.disabled=!1,await h()});for(let e of["select","disconnect","fast","stable","diagnostics"])document.getElementById(e).addEventListener("click",async()=>{if(!k){k=!0,document.querySelectorAll("button").forEach(r=>r.disabled=!0),o("feedback",e==="fast"||e==="stable"?"Capturing\u2026":"Working\u2026");try{if(e==="fast"&&!await browser.permissions.contains({permissions:["downloads"]})){await browser.windows.create({url:browser.runtime.getURL("permission.html?action=download"),type:"popup",focused:!0,width:600,height:700}),window.close();return}let r=await f(e);e==="diagnostics"&&(document.getElementById("diagnostics-panel").hidden=!1,o("diagnostics-output",JSON.stringify(r,null,2))),U(e==="disconnect"?"Target disconnected.":e==="select"?"Target selected.":e==="diagnostics"?"Diagnostics updated.":e==="fast"?`\u2713 Saved to Downloads/${r.filename}`:"Capture completed.",e==="fast")}catch(r){U(r instanceof Error?r.message:"Operation failed.")}finally{k=!1,document.querySelectorAll("button").forEach(r=>r.disabled=!1),await h().catch(r=>o("feedback",String(r)))}}});document.getElementById("copy-url").addEventListener("click",async()=>{let e=document.getElementById("url").textContent;if(!(!e||e==="No target selected"))try{await navigator.clipboard.writeText(e),o("feedback","URL copied.")}catch{o("feedback","Unable to copy URL. Select and copy it manually.")}});document.getElementById("background-toggle").addEventListener("click",async()=>{if((await f("status")).backgroundCapture.enabled){await f("background-disable"),await h();return}await browser.windows.create({url:browser.runtime.getURL("permission.html?action=background"),type:"popup",focused:!0,width:600,height:700}),window.close()});document.getElementById("continuous-toggle").addEventListener("click",async()=>{let e=await f("status");if(e.continuousAuthorization.enabled){await f("continuous-disable"),await h();return}if(e.continuousAuthorization.permissionGranted){await f("continuous-enable"),await h();return}await browser.windows.create({url:browser.runtime.getURL("permission.html?action=continuous"),type:"popup",focused:!0,width:600,height:700}),window.close()});browser.commands.getAll().then(e=>{let r=e.find(g=>g.name==="gazesight-select-target")?.shortcut;r&&o("shortcut",r)}).catch(()=>{});document.getElementById("settings").addEventListener("click",()=>void browser.runtime.openOptionsPage());h().catch(e=>o("feedback",String(e)));var se=setInterval(()=>{k||h().catch(e=>o("feedback",String(e)))},2e3);window.addEventListener("pagehide",()=>clearInterval(se),{once:!0});
1
+ var Q=Object.create;var $=Object.defineProperty;var Y=Object.getOwnPropertyDescriptor;var X=Object.getOwnPropertyNames;var ee=Object.getPrototypeOf,re=Object.prototype.hasOwnProperty;var te=(e,r)=>()=>(r||e((r={exports:{}}).exports,r),r.exports);var ne=(e,r,a,c)=>{if(r&&typeof r=="object"||typeof r=="function")for(let l of X(r))!re.call(e,l)&&l!==a&&$(e,l,{get:()=>r[l],enumerable:!(c=Y(r,l))||c.enumerable});return e};var se=(e,r,a)=>(a=e!=null?Q(ee(e)):{},ne(r||!e||!e.__esModule?$(a,"default",{value:e,enumerable:!0}):a,e));var q=te((z,O)=>{(function(e,r){if(typeof define=="function"&&define.amd)define("webextension-polyfill",["module"],r);else if(typeof z<"u")r(O);else{var a={exports:{}};r(a),e.browser=a.exports}})(typeof globalThis<"u"?globalThis:typeof self<"u"?self:z,function(e){"use strict";if(!(globalThis.chrome&&globalThis.chrome.runtime&&globalThis.chrome.runtime.id))throw new Error("This script should only be loaded in a browser extension.");if(globalThis.browser&&globalThis.browser.runtime&&globalThis.browser.runtime.id)e.exports=globalThis.browser;else{let r="The message port closed before a response was received.",a=c=>{let l={alarms:{clear:{minArgs:0,maxArgs:1},clearAll:{minArgs:0,maxArgs:0},get:{minArgs:0,maxArgs:1},getAll:{minArgs:0,maxArgs:0}},bookmarks:{create:{minArgs:1,maxArgs:1},get:{minArgs:1,maxArgs:1},getChildren:{minArgs:1,maxArgs:1},getRecent:{minArgs:1,maxArgs:1},getSubTree:{minArgs:1,maxArgs:1},getTree:{minArgs:0,maxArgs:0},move:{minArgs:2,maxArgs:2},remove:{minArgs:1,maxArgs:1},removeTree:{minArgs:1,maxArgs:1},search:{minArgs:1,maxArgs:1},update:{minArgs:2,maxArgs:2}},browserAction:{disable:{minArgs:0,maxArgs:1,fallbackToNoCallback:!0},enable:{minArgs:0,maxArgs:1,fallbackToNoCallback:!0},getBadgeBackgroundColor:{minArgs:1,maxArgs:1},getBadgeText:{minArgs:1,maxArgs:1},getPopup:{minArgs:1,maxArgs:1},getTitle:{minArgs:1,maxArgs:1},openPopup:{minArgs:0,maxArgs:0},setBadgeBackgroundColor:{minArgs:1,maxArgs:1,fallbackToNoCallback:!0},setBadgeText:{minArgs:1,maxArgs:1,fallbackToNoCallback:!0},setIcon:{minArgs:1,maxArgs:1},setPopup:{minArgs:1,maxArgs:1,fallbackToNoCallback:!0},setTitle:{minArgs:1,maxArgs:1,fallbackToNoCallback:!0}},browsingData:{remove:{minArgs:2,maxArgs:2},removeCache:{minArgs:1,maxArgs:1},removeCookies:{minArgs:1,maxArgs:1},removeDownloads:{minArgs:1,maxArgs:1},removeFormData:{minArgs:1,maxArgs:1},removeHistory:{minArgs:1,maxArgs:1},removeLocalStorage:{minArgs:1,maxArgs:1},removePasswords:{minArgs:1,maxArgs:1},removePluginData:{minArgs:1,maxArgs:1},settings:{minArgs:0,maxArgs:0}},commands:{getAll:{minArgs:0,maxArgs:0}},contextMenus:{remove:{minArgs:1,maxArgs:1},removeAll:{minArgs:0,maxArgs:0},update:{minArgs:2,maxArgs:2}},cookies:{get:{minArgs:1,maxArgs:1},getAll:{minArgs:1,maxArgs:1},getAllCookieStores:{minArgs:0,maxArgs:0},remove:{minArgs:1,maxArgs:1},set:{minArgs:1,maxArgs:1}},devtools:{inspectedWindow:{eval:{minArgs:1,maxArgs:2,singleCallbackArg:!1}},panels:{create:{minArgs:3,maxArgs:3,singleCallbackArg:!0},elements:{createSidebarPane:{minArgs:1,maxArgs:1}}}},downloads:{cancel:{minArgs:1,maxArgs:1},download:{minArgs:1,maxArgs:1},erase:{minArgs:1,maxArgs:1},getFileIcon:{minArgs:1,maxArgs:2},open:{minArgs:1,maxArgs:1,fallbackToNoCallback:!0},pause:{minArgs:1,maxArgs:1},removeFile:{minArgs:1,maxArgs:1},resume:{minArgs:1,maxArgs:1},search:{minArgs:1,maxArgs:1},show:{minArgs:1,maxArgs:1,fallbackToNoCallback:!0}},extension:{isAllowedFileSchemeAccess:{minArgs:0,maxArgs:0},isAllowedIncognitoAccess:{minArgs:0,maxArgs:0}},history:{addUrl:{minArgs:1,maxArgs:1},deleteAll:{minArgs:0,maxArgs:0},deleteRange:{minArgs:1,maxArgs:1},deleteUrl:{minArgs:1,maxArgs:1},getVisits:{minArgs:1,maxArgs:1},search:{minArgs:1,maxArgs:1}},i18n:{detectLanguage:{minArgs:1,maxArgs:1},getAcceptLanguages:{minArgs:0,maxArgs:0}},identity:{launchWebAuthFlow:{minArgs:1,maxArgs:1}},idle:{queryState:{minArgs:1,maxArgs:1}},management:{get:{minArgs:1,maxArgs:1},getAll:{minArgs:0,maxArgs:0},getSelf:{minArgs:0,maxArgs:0},setEnabled:{minArgs:2,maxArgs:2},uninstallSelf:{minArgs:0,maxArgs:1}},notifications:{clear:{minArgs:1,maxArgs:1},create:{minArgs:1,maxArgs:2},getAll:{minArgs:0,maxArgs:0},getPermissionLevel:{minArgs:0,maxArgs:0},update:{minArgs:2,maxArgs:2}},pageAction:{getPopup:{minArgs:1,maxArgs:1},getTitle:{minArgs:1,maxArgs:1},hide:{minArgs:1,maxArgs:1,fallbackToNoCallback:!0},setIcon:{minArgs:1,maxArgs:1},setPopup:{minArgs:1,maxArgs:1,fallbackToNoCallback:!0},setTitle:{minArgs:1,maxArgs:1,fallbackToNoCallback:!0},show:{minArgs:1,maxArgs:1,fallbackToNoCallback:!0}},permissions:{contains:{minArgs:1,maxArgs:1},getAll:{minArgs:0,maxArgs:0},remove:{minArgs:1,maxArgs:1},request:{minArgs:1,maxArgs:1}},runtime:{getBackgroundPage:{minArgs:0,maxArgs:0},getPlatformInfo:{minArgs:0,maxArgs:0},openOptionsPage:{minArgs:0,maxArgs:0},requestUpdateCheck:{minArgs:0,maxArgs:0},sendMessage:{minArgs:1,maxArgs:3},sendNativeMessage:{minArgs:2,maxArgs:2},setUninstallURL:{minArgs:1,maxArgs:1}},sessions:{getDevices:{minArgs:0,maxArgs:1},getRecentlyClosed:{minArgs:0,maxArgs:1},restore:{minArgs:0,maxArgs:1}},storage:{local:{clear:{minArgs:0,maxArgs:0},get:{minArgs:0,maxArgs:1},getBytesInUse:{minArgs:0,maxArgs:1},remove:{minArgs:1,maxArgs:1},set:{minArgs:1,maxArgs:1}},managed:{get:{minArgs:0,maxArgs:1},getBytesInUse:{minArgs:0,maxArgs:1}},sync:{clear:{minArgs:0,maxArgs:0},get:{minArgs:0,maxArgs:1},getBytesInUse:{minArgs:0,maxArgs:1},remove:{minArgs:1,maxArgs:1},set:{minArgs:1,maxArgs:1}}},tabs:{captureVisibleTab:{minArgs:0,maxArgs:2},create:{minArgs:1,maxArgs:1},detectLanguage:{minArgs:0,maxArgs:1},discard:{minArgs:0,maxArgs:1},duplicate:{minArgs:1,maxArgs:1},executeScript:{minArgs:1,maxArgs:2},get:{minArgs:1,maxArgs:1},getCurrent:{minArgs:0,maxArgs:0},getZoom:{minArgs:0,maxArgs:1},getZoomSettings:{minArgs:0,maxArgs:1},goBack:{minArgs:0,maxArgs:1},goForward:{minArgs:0,maxArgs:1},highlight:{minArgs:1,maxArgs:1},insertCSS:{minArgs:1,maxArgs:2},move:{minArgs:2,maxArgs:2},query:{minArgs:1,maxArgs:1},reload:{minArgs:0,maxArgs:2},remove:{minArgs:1,maxArgs:1},removeCSS:{minArgs:1,maxArgs:2},sendMessage:{minArgs:2,maxArgs:3},setZoom:{minArgs:1,maxArgs:2},setZoomSettings:{minArgs:1,maxArgs:2},update:{minArgs:1,maxArgs:2}},topSites:{get:{minArgs:0,maxArgs:0}},webNavigation:{getAllFrames:{minArgs:1,maxArgs:1},getFrame:{minArgs:1,maxArgs:1}},webRequest:{handlerBehaviorChanged:{minArgs:0,maxArgs:0}},windows:{create:{minArgs:0,maxArgs:1},get:{minArgs:1,maxArgs:2},getAll:{minArgs:0,maxArgs:1},getCurrent:{minArgs:0,maxArgs:1},getLastFocused:{minArgs:0,maxArgs:1},remove:{minArgs:1,maxArgs:1},update:{minArgs:2,maxArgs:2}}};if(Object.keys(l).length===0)throw new Error("api-metadata.json has not been included in browser-polyfill");class h extends WeakMap{constructor(n,i=void 0){super(i),this.createItem=n}get(n){return this.has(n)||this.set(n,this.createItem(n)),super.get(n)}}let C=t=>t&&typeof t=="object"&&typeof t.then=="function",k=(t,n)=>(...i)=>{c.runtime.lastError?t.reject(new Error(c.runtime.lastError.message)):n.singleCallbackArg||i.length<=1&&n.singleCallbackArg!==!1?t.resolve(i[0]):t.resolve(i)},A=t=>t==1?"argument":"arguments",E=(t,n)=>function(o,...d){if(d.length<n.minArgs)throw new Error(`Expected at least ${n.minArgs} ${A(n.minArgs)} for ${t}(), got ${d.length}`);if(d.length>n.maxArgs)throw new Error(`Expected at most ${n.maxArgs} ${A(n.maxArgs)} for ${t}(), got ${d.length}`);return new Promise((u,b)=>{if(n.fallbackToNoCallback)try{o[t](...d,k({resolve:u,reject:b},n))}catch(s){console.warn(`${t} API method doesn't seem to support the callback parameter, falling back to call it without a callback: `,s),o[t](...d),n.fallbackToNoCallback=!1,n.noCallback=!0,u()}else n.noCallback?(o[t](...d),u()):o[t](...d,k({resolve:u,reject:b},n))})},N=(t,n,i)=>new Proxy(n,{apply(o,d,u){return i.call(d,t,...u)}}),P=Function.call.bind(Object.prototype.hasOwnProperty),I=(t,n={},i={})=>{let o=Object.create(null),d={has(b,s){return s in t||s in o},get(b,s,p){if(s in o)return o[s];if(!(s in t))return;let m=t[s];if(typeof m=="function")if(typeof n[s]=="function")m=N(t,t[s],n[s]);else if(P(i,s)){let v=E(s,i[s]);m=N(t,t[s],v)}else m=m.bind(t);else if(typeof m=="object"&&m!==null&&(P(n,s)||P(i,s)))m=I(m,n[s],i[s]);else if(P(i,"*"))m=I(m,n[s],i["*"]);else return Object.defineProperty(o,s,{configurable:!0,enumerable:!0,get(){return t[s]},set(v){t[s]=v}}),m;return o[s]=m,m},set(b,s,p,m){return s in o?o[s]=p:t[s]=p,!0},defineProperty(b,s,p){return Reflect.defineProperty(o,s,p)},deleteProperty(b,s){return Reflect.deleteProperty(o,s)}},u=Object.create(t);return new Proxy(u,d)},R=t=>({addListener(n,i,...o){n.addListener(t.get(i),...o)},hasListener(n,i){return n.hasListener(t.get(i))},removeListener(n,i){n.removeListener(t.get(i))}}),K=new h(t=>typeof t!="function"?t:function(i){let o=I(i,{},{getContent:{minArgs:0,maxArgs:0}});t(o)}),B=new h(t=>typeof t!="function"?t:function(i,o,d){let u=!1,b,s=new Promise(T=>{b=function(f){u=!0,T(f)}}),p;try{p=t(i,o,b)}catch(T){p=Promise.reject(T)}let m=p!==!0&&C(p);if(p!==!0&&!m&&!u)return!1;let v=T=>{T.then(f=>{d(f)},f=>{let M;f&&(f instanceof Error||typeof f.message=="string")?M=f.message:M="An unexpected error occurred",d({__mozWebExtensionPolyfillReject__:!0,message:M})}).catch(f=>{console.error("Failed to send onMessage rejected reply",f)})};return v(m?p:s),!0}),Z=({reject:t,resolve:n},i)=>{c.runtime.lastError?c.runtime.lastError.message===r?n():t(new Error(c.runtime.lastError.message)):i&&i.__mozWebExtensionPolyfillReject__?t(new Error(i.message)):n(i)},L=(t,n,i,...o)=>{if(o.length<n.minArgs)throw new Error(`Expected at least ${n.minArgs} ${A(n.minArgs)} for ${t}(), got ${o.length}`);if(o.length>n.maxArgs)throw new Error(`Expected at most ${n.maxArgs} ${A(n.maxArgs)} for ${t}(), got ${o.length}`);return new Promise((d,u)=>{let b=Z.bind(null,{resolve:d,reject:u});o.push(b),i.sendMessage(...o)})},J={devtools:{network:{onRequestFinished:R(K)}},runtime:{onMessage:R(B),onMessageExternal:R(B),sendMessage:L.bind(null,"sendMessage",{minArgs:1,maxArgs:3})},tabs:{sendMessage:L.bind(null,"sendMessage",{minArgs:2,maxArgs:3})}},_={clear:{minArgs:1,maxArgs:1},get:{minArgs:1,maxArgs:1},set:{minArgs:1,maxArgs:1}};return l.privacy={network:{"*":_},services:{"*":_},websites:{"*":_}},I(c,J,l)};e.exports=a(chrome)}})});var U=se(q(),1);globalThis.browser=U.default;var x="pendingPairing";function ie(){return[...crypto.getRandomValues(new Uint8Array(32))].map(r=>r.toString(16).padStart(2,"0")).join("")}function ae(){return location.protocol==="moz-extension:"?"firefox":"chrome"}async function j(e,r,a,c,l=2500){return fetch(`http://127.0.0.1:${e}/gazesight/pairing/${r}`,{method:"POST",headers:{"Content-Type":"application/json",...c?{Authorization:`Bearer ${c}`}:{}},body:JSON.stringify(a),signal:AbortSignal.timeout(l)})}async function D(e,r){let{requestId:a,proof:c,code:l,expiresAt:h,port:C}=r;for(;;){if(Date.now()>=h)return await browser.storage.local.remove(x),e("expired",l),!1;e("waiting_approval",l);let k;try{k=await j(C,"status",{requestId:a,secret:c})}catch{await new Promise(E=>setTimeout(E,800));continue}let A=await k.json();if(A.state==="approved"&&A.credential&&A.pairingId)return await browser.storage.local.set({token:A.credential,pairingId:A.pairingId,port:C,pairingState:"connected"}),await browser.storage.local.remove(x),e("connected"),!0;if(A.state==="rejected")return await browser.storage.local.remove(x),e("reconnect_required",l),!1;if(A.state==="expired"||k.status===410)return await browser.storage.local.remove(x),e("expired",l),!1;await new Promise(E=>setTimeout(E,800))}}async function F(e,r=32147){e("searching");let a=ie(),c;try{c=await j(r,"request",{browser:ae(),extensionId:browser.runtime.id,secret:a},void 0,6e4)}catch{return e("unavailable"),!1}if(!c.ok)return e("unavailable"),!1;let l=await c.json(),h={requestId:l.requestId,proof:a,code:l.code,expiresAt:l.expiresAt,port:r};return await browser.storage.local.set({[x]:h}),e("pending",l.code),D(e,h)}async function G(e){let a=(await browser.storage.local.get(x))[x];if(a){if(Date.now()>=a.expiresAt){await browser.storage.local.remove(x);return}return D(e,a)}}function g(e,r){document.getElementById(e).textContent=r}var S=!1,W;function V(e,r=!1){clearTimeout(W),g("feedback",e),r&&(W=setTimeout(()=>g("feedback",""),3500))}async function y(e){let r=await browser.runtime.sendMessage({type:"gazesight-ui",action:e});if(!r?.ok)throw new Error(r?.error??"GazeSight unavailable. Open settings to check pairing.");return r.value}async function w(){let e=await y("status");document.body.dataset.connected=String(e.connected),g("connection",e.connected?"Connected":"Disconnected");let r=await browser.storage.local.get(["token","port"]);document.getElementById("pairing-connect").hidden=typeof r.token=="string",document.body.dataset.background=e.backgroundCapture.enabled?"enabled":"disabled",g("background-state",e.backgroundCapture.enabled?"On":"Off"),g("background-toggle",e.backgroundCapture.enabled?"Disable":"Enable"),document.getElementById("background-toggle").setAttribute("aria-pressed",String(e.backgroundCapture.enabled)),g("background-description",e.backgroundCapture.enabled?"GazeSight can capture this target while you work elsewhere.":e.backgroundCapture.permissionGranted?"Background mode is off. Firefox still reports the optional permission; remove it in Add-ons Manager if needed.":e.backgroundMode==="permission_denied"?"Permission was not granted. Standard foreground capture remains available.":"Allow GazeSight to keep capturing the selected development tab while you work in another tab or application."),document.body.dataset.continuous=e.continuousAuthorization.enabled?"enabled":"disabled",g("continuous-state",e.continuousAuthorization.enabled?"On":"Off"),g("continuous-toggle",e.continuousAuthorization.enabled?"Disable":"Enable"),document.getElementById("continuous-toggle").setAttribute("aria-pressed",String(e.continuousAuthorization.enabled)),g("continuous-description",e.continuousAuthorization.enabled?e.continuousAuthorization.authorizedOrigin?`Staying connected to ${e.continuousAuthorization.authorizedOrigin} across reloads, navigation and previews.`:"Keeps GazeSight connected across reloads, navigation and previews once you select a tab.":"Keep GazeSight connected to this development tab across reloads, navigation and responsive previews, so you don't have to reauthorize it every time."),g("pairing",e.paired?"Paired":"Not paired"),g("target",e.targetState==="authorized"?"Authorized":e.targetState==="none"?"None":"Reauthorize"),g("url",e.url??"No target selected"),g("viewport",e.viewport?`${e.viewport.width} \xD7 ${e.viewport.height} CSS \xB7 DPR ${e.viewport.devicePixelRatio}`:"Viewport unavailable"),g("select-label",e.targetState==="reauthorization_required"?"Reauthorize this tab":"Use this tab"),g("last-capture",e.lastCapture?`${e.lastCapture.mode} \xB7 ${e.lastCapture.width} \xD7 ${e.lastCapture.height} \xB7 ${new Date(e.lastCapture.time).toLocaleTimeString()}`:"None yet"),g("last-error",e.lastError?`${e.lastError.code}: ${e.lastError.message}`:"None");for(let a of["fast","stable"])document.getElementById(a).disabled=S||!e.connected||e.targetState!=="authorized";document.getElementById("disconnect").disabled=S||e.targetState==="none"}var oe={not_connected:"Not connected.",searching:"Searching for local GazeSight\u2026",pending:"Pairing request pending.",waiting_approval:"Waiting for approval in your terminal\u2026",connected:"Connected.",unavailable:"Connection unavailable. Run gazesight pair and try again.",expired:"Pairing expired. Try again.",reconnect_required:"Reconnect required."};function H(e,r){let a=document.getElementById("pairing-code");g("pairing-message",oe[e]),a.hidden=!r,a.textContent=r?`Verification code: ${r}`:""}document.getElementById("connect").addEventListener("click",async()=>{let e=document.getElementById("connect");e.disabled=!0;let r=await browser.storage.local.get(["port"]);await F(H,Number(r.port??32147)),e.disabled=!1,await w()});(async()=>typeof(await browser.storage.local.get(["token"])).token!="string"&&await G(H)&&await w())();for(let e of["select","disconnect","fast","stable","diagnostics"])document.getElementById(e).addEventListener("click",async()=>{if(!S){S=!0,document.querySelectorAll("button").forEach(r=>r.disabled=!0),g("feedback",e==="fast"||e==="stable"?"Capturing\u2026":"Working\u2026");try{if(e==="fast"&&!await browser.permissions.contains({permissions:["downloads"]})){await browser.windows.create({url:browser.runtime.getURL("permission.html?action=download"),type:"popup",focused:!0,width:600,height:700}),window.close();return}let r=await y(e);e==="diagnostics"&&(document.getElementById("diagnostics-panel").hidden=!1,g("diagnostics-output",JSON.stringify(r,null,2))),V(e==="disconnect"?"Target disconnected.":e==="select"?"Target selected.":e==="diagnostics"?"Diagnostics updated.":e==="fast"?`\u2713 Saved to Downloads/${r.filename}`:"Capture completed.",e==="fast")}catch(r){V(r instanceof Error?r.message:"Operation failed.")}finally{S=!1,document.querySelectorAll("button").forEach(r=>r.disabled=!1),await w().catch(r=>g("feedback",String(r)))}}});document.getElementById("copy-url").addEventListener("click",async()=>{let e=document.getElementById("url").textContent;if(!(!e||e==="No target selected"))try{await navigator.clipboard.writeText(e),g("feedback","URL copied.")}catch{g("feedback","Unable to copy URL. Select and copy it manually.")}});document.getElementById("background-toggle").addEventListener("click",async()=>{if((await y("status")).backgroundCapture.enabled){await y("background-disable"),await w();return}await browser.windows.create({url:browser.runtime.getURL("permission.html?action=background"),type:"popup",focused:!0,width:600,height:700}),window.close()});document.getElementById("continuous-toggle").addEventListener("click",async()=>{let e=await y("status");if(e.continuousAuthorization.enabled){await y("continuous-disable"),await w();return}if(e.continuousAuthorization.permissionGranted){await y("continuous-enable"),await w();return}await browser.windows.create({url:browser.runtime.getURL("permission.html?action=continuous"),type:"popup",focused:!0,width:600,height:700}),window.close()});browser.commands.getAll().then(e=>{let r=e.find(a=>a.name==="gazesight-select-target")?.shortcut;r&&g("shortcut",r)}).catch(()=>{});document.getElementById("settings").addEventListener("click",()=>void browser.runtime.openOptionsPage());w().catch(e=>g("feedback",String(e)));var ge=setInterval(()=>{S||w().catch(e=>g("feedback",String(e)))},2e3);window.addEventListener("pagehide",()=>clearInterval(ge),{once:!0});
package/dist/cli/index.js CHANGED
@@ -1,10 +1,9 @@
1
1
  #!/usr/bin/env node
2
2
 
3
3
  // apps/cli/src/index.ts
4
- import { existsSync } from "node:fs";
5
- import { spawnSync } from "node:child_process";
4
+ import { existsSync as existsSync2 } from "node:fs";
6
5
  import { createInterface as createInterface2 } from "node:readline/promises";
7
- import { resolve, dirname as dirname2, join as join2, delimiter } from "node:path";
6
+ import { resolve as resolve2, dirname as dirname2, join as join2, delimiter } from "node:path";
8
7
  import { fileURLToPath, pathToFileURL } from "node:url";
9
8
 
10
9
  // apps/cli/src/config.ts
@@ -102,7 +101,7 @@ function writeLocalConfig(path, value) {
102
101
  }
103
102
 
104
103
  // packages/protocol/src/release.ts
105
- var VERSION = "0.4.0";
104
+ var VERSION = "0.5.0";
106
105
 
107
106
  // apps/cli/src/doctor.ts
108
107
  import { z as z2 } from "zod";
@@ -326,9 +325,20 @@ var fullPageCaptureSchema = captureSchema.extend({
326
325
  });
327
326
  var navigateSchema = targetSchema.extend({ url: z3.url() });
328
327
  var diffSchema = z3.strictObject({
329
- baseline: z3.uuid(),
328
+ baseline: z3.uuid().optional().describe(
329
+ "A prior PNG imageReference from this session. Omit and use referenceImagePath instead to diff against an external design reference."
330
+ ),
331
+ referenceImagePath: z3.string().min(1).max(4096).optional().describe(
332
+ "Path (relative to the MCP server's working directory, or absolute) to a local PNG design reference to diff the live capture against. Exactly one of baseline/referenceImagePath is required."
333
+ ),
330
334
  current: z3.uuid().optional(),
331
- threshold: z3.number().finite().min(0).max(1).default(0.1)
335
+ tabId: z3.number().int().nonnegative().optional().describe(
336
+ "Only used with referenceImagePath when current is omitted, to select which tab to capture. Ignored otherwise (baseline's own tab is used)."
337
+ ),
338
+ threshold: z3.number().finite().min(0).max(1).default(0.1),
339
+ checkpoint: z3.string().min(1).max(200).regex(/^[A-Za-z0-9_-]+$/).optional().describe(
340
+ "Visual Convergence best-so-far tracking id (e.g. 'hero-desktop'). Only meaningful together with referenceImagePath: persists the best-known result locally and objectively reports isBest/regressed on every call, independent of the agent's own assessment."
341
+ )
332
342
  });
333
343
  var viewportProfileSchema = z3.enum([
334
344
  "mobile-small",
@@ -341,7 +351,10 @@ var setViewportSchema = targetSchema.extend({
341
351
  profile: viewportProfileSchema.optional(),
342
352
  width: z3.number().int().min(200).max(8192).optional(),
343
353
  height: z3.number().int().min(200).max(8192).optional(),
344
- dedicatedPreview: z3.boolean().default(true)
354
+ dedicatedPreview: z3.boolean().default(true),
355
+ close: z3.boolean().optional().describe(
356
+ "Close the current dedicated preview window instead of creating or resizing one. Width/height/profile are ignored when true. Idempotent: has no effect if no preview is open."
357
+ )
345
358
  });
346
359
  var viewportResultSchema = z3.strictObject({
347
360
  tabId: z3.number().int().nonnegative(),
@@ -363,7 +376,10 @@ var viewportResultSchema = z3.strictObject({
363
376
  exact: z3.boolean().optional(),
364
377
  attempts: z3.number().int().min(0).max(3).optional(),
365
378
  requiresTargetSelection: z3.boolean().optional(),
366
- sourceWindowUnchanged: z3.boolean().optional()
379
+ sourceWindowUnchanged: z3.boolean().optional(),
380
+ previewClosed: z3.boolean().optional().describe(
381
+ "Present when close:true was requested. True if a dedicated preview window was actually open and closed; false if there was none to close."
382
+ )
367
383
  });
368
384
  var domSchema = targetSchema.extend({
369
385
  selector: z3.string().min(1).max(2048).optional(),
@@ -703,7 +719,7 @@ var RequestRouter = class {
703
719
  type,
704
720
  payload
705
721
  });
706
- return new Promise((resolve2, reject) => {
722
+ return new Promise((resolve3, reject) => {
707
723
  const timer = setTimeout(() => {
708
724
  this.pending.delete(request.id);
709
725
  reject(
@@ -713,7 +729,7 @@ var RequestRouter = class {
713
729
  )
714
730
  );
715
731
  }, this.timeoutMs);
716
- this.pending.set(request.id, { resolve: resolve2, reject, timer });
732
+ this.pending.set(request.id, { resolve: resolve3, reject, timer });
717
733
  try {
718
734
  this.send(request);
719
735
  } catch (error) {
@@ -1092,6 +1108,7 @@ var PairingService = class {
1092
1108
 
1093
1109
  // apps/mcp-server/src/bridge/websocket-server.ts
1094
1110
  var EMPTY_STATE = { selectedTab: null, url: null };
1111
+ var DEFAULT_HEARTBEAT_INTERVAL_MS = 2e4;
1095
1112
  function browserFromOrigin(origin) {
1096
1113
  if (origin === void 0) return void 0;
1097
1114
  if (/^moz-extension:\/\/[a-f0-9-]+\/?$/i.test(origin)) return "firefox";
@@ -1121,6 +1138,12 @@ var Bridge = class {
1121
1138
  response.setHeader("Content-Type", "application/json");
1122
1139
  response.end(JSON.stringify(this.status()));
1123
1140
  });
1141
+ this.http.once("error", (error) => {
1142
+ this.listenError = error.code === "EADDRINUSE" ? new GSError(
1143
+ "GS_PORT_IN_USE",
1144
+ `Port ${config.port} is already in use by another GazeSight MCP server. Only one can run at a time -- close the other session first.`
1145
+ ) : error;
1146
+ });
1124
1147
  this.server = new WebSocketServer({
1125
1148
  server: this.http,
1126
1149
  maxPayload: 17 * 1024 * 1024,
@@ -1143,6 +1166,11 @@ var Bridge = class {
1143
1166
  this.wiring.delete(connection.connectionId);
1144
1167
  this.registry.remove(connection.connectionId);
1145
1168
  };
1169
+ this.alive.set(socket, { pongReceived: true });
1170
+ socket.on("pong", () => {
1171
+ const state = this.alive.get(socket);
1172
+ if (state) state.pongReceived = true;
1173
+ });
1146
1174
  socket.on("error", () => {
1147
1175
  });
1148
1176
  socket.on("message", (bytes, binary) => {
@@ -1197,12 +1225,28 @@ var Bridge = class {
1197
1225
  });
1198
1226
  socket.on("close", teardown);
1199
1227
  });
1228
+ this.heartbeat = setInterval(() => {
1229
+ for (const socket of this.server.clients) {
1230
+ const state = this.alive.get(socket);
1231
+ if (!state) continue;
1232
+ if (!state.pongReceived) {
1233
+ socket.terminate();
1234
+ continue;
1235
+ }
1236
+ state.pongReceived = false;
1237
+ socket.ping();
1238
+ }
1239
+ }, config.heartbeatIntervalMs ?? DEFAULT_HEARTBEAT_INTERVAL_MS);
1240
+ this.heartbeat.unref?.();
1200
1241
  }
1201
1242
  registry = new BrowserConnectionRegistry();
1202
1243
  activeTargetRouter = new ActiveTargetRouter();
1203
1244
  wiring = /* @__PURE__ */ new Map();
1245
+ alive = /* @__PURE__ */ new WeakMap();
1246
+ heartbeat;
1204
1247
  server;
1205
1248
  http;
1249
+ listenError;
1206
1250
  pairing;
1207
1251
  // Resolves the connection currently backing the global active target, or
1208
1252
  // undefined if no browser is active (nothing selected yet, or the
@@ -1278,17 +1322,21 @@ var Bridge = class {
1278
1322
  };
1279
1323
  }
1280
1324
  async ready() {
1325
+ if (this.listenError) throw this.listenError;
1281
1326
  if (this.server.address()) return;
1282
- await new Promise((resolve2, reject) => {
1283
- this.server.once("listening", resolve2);
1284
- this.server.once("error", reject);
1327
+ await new Promise((resolve3, reject) => {
1328
+ this.server.once("listening", resolve3);
1329
+ const onError = () => reject(this.listenError);
1330
+ this.server.once("error", onError);
1331
+ this.http.once("error", onError);
1285
1332
  });
1286
1333
  }
1287
1334
  async close() {
1335
+ clearInterval(this.heartbeat);
1288
1336
  for (const wiring of this.wiring.values()) wiring.router.disconnect();
1289
1337
  for (const client of this.server.clients) client.terminate();
1290
- await new Promise((resolve2) => this.server.close(() => resolve2()));
1291
- await new Promise((resolve2) => this.http.close(() => resolve2()));
1338
+ await new Promise((resolve3) => this.server.close(() => resolve3()));
1339
+ await new Promise((resolve3) => this.http.close(() => resolve3()));
1292
1340
  }
1293
1341
  };
1294
1342
 
@@ -1334,7 +1382,7 @@ async function pairingFlow(config, configPath, revoke) {
1334
1382
  const requests = (await response.json()).requests;
1335
1383
  const request = requests[0];
1336
1384
  if (!request) {
1337
- await new Promise((resolve2) => setTimeout(resolve2, 500));
1385
+ await new Promise((resolve3) => setTimeout(resolve3, 500));
1338
1386
  continue;
1339
1387
  }
1340
1388
  console.log(`Pairing request: ${request.browser} \xB7 verification code ${request.code}`);
@@ -1351,7 +1399,7 @@ async function pairingFlow(config, configPath, revoke) {
1351
1399
  delivered = true;
1352
1400
  break;
1353
1401
  }
1354
- await new Promise((resolve2) => setTimeout(resolve2, 100));
1402
+ await new Promise((resolve3) => setTimeout(resolve3, 100));
1355
1403
  }
1356
1404
  if (!delivered) throw new Error("The approved credential was not delivered; keep the extension open and retry pairing.");
1357
1405
  console.log("Pairing approved and delivered. The extension can now reconnect securely.");
@@ -1366,8 +1414,164 @@ async function pairingFlow(config, configPath, revoke) {
1366
1414
  }
1367
1415
  }
1368
1416
 
1417
+ // apps/cli/src/agent-config.ts
1418
+ import { existsSync, readFileSync as readFileSync2 } from "node:fs";
1419
+ import { basename, resolve } from "node:path";
1420
+ import { spawnSync } from "node:child_process";
1421
+ function parseKnownGazeSightArgs(command, argsText) {
1422
+ const cli = argsText.match(/^(.*?[\\/]dist[\\/]cli[\\/]index\.js)\s+mcp(?:\s+--config\s+(.+))?$/i);
1423
+ if (cli) return [cli[1], "mcp", ...cli[2] ? ["--config", cli[2]] : []];
1424
+ if (/^npx(?:\.cmd|\.exe)?$/i.test(basename(command))) {
1425
+ const npx = argsText.match(/^(.*?)\b(gazesight(?:@\d+\.\d+\.\d+)?)\s+mcp(?:\s+--config\s+(.+))?$/i);
1426
+ if (npx) {
1427
+ const prefix = npx[1].trim().split(/\s+/).filter(Boolean);
1428
+ return [...prefix, npx[2], "mcp", ...npx[3] ? ["--config", npx[3]] : []];
1429
+ }
1430
+ }
1431
+ if (/^gazesight(?:-mcp)?(?:\.cmd|\.exe)?$/i.test(basename(command))) {
1432
+ const direct = argsText.match(/^mcp(?:\s+--config\s+(.+))?$/i);
1433
+ if (direct) return ["mcp", ...direct[1] ? ["--config", direct[1]] : []];
1434
+ }
1435
+ return void 0;
1436
+ }
1437
+ function normalize(value) {
1438
+ const normalized = value.trim().replace(/^['"]|['"]$/g, "").replaceAll("\\", "/");
1439
+ return process.platform === "win32" ? normalized.toLowerCase() : normalized;
1440
+ }
1441
+ function parseScope(output) {
1442
+ const value = output.match(/^\s*Scope:\s*(.+)$/im)?.[1]?.toLowerCase() ?? "user";
1443
+ if (value.includes("project")) return "project";
1444
+ if (value.includes("local")) return "local";
1445
+ return "user";
1446
+ }
1447
+ function versionFromPackageRoot(argsText) {
1448
+ const normalized = argsText.replaceAll("\\", "/");
1449
+ const marker = "/dist/cli/index.js";
1450
+ const markerIndex = normalized.toLowerCase().indexOf(marker);
1451
+ if (markerIndex < 0) return void 0;
1452
+ const root2 = argsText.slice(0, markerIndex);
1453
+ const packagePath = resolve(root2, "package.json");
1454
+ if (!existsSync(packagePath)) return void 0;
1455
+ try {
1456
+ const parsed = JSON.parse(readFileSync2(packagePath, "utf8"));
1457
+ return typeof parsed.version === "string" ? parsed.version : void 0;
1458
+ } catch {
1459
+ return void 0;
1460
+ }
1461
+ }
1462
+ function parseRegisteredConfig(agent, output) {
1463
+ const command = output.match(/^\s*(?:Command|command):\s*(.+)$/im)?.[1]?.trim();
1464
+ const argsText = output.match(/^\s*(?:Args|args):\s*(.*)$/im)?.[1]?.trim();
1465
+ if (!command || argsText === void 0) return void 0;
1466
+ const combined = `${command} ${argsText}`;
1467
+ const explicitVersion = combined.match(/(?:^|\s)gazesight@(\d+\.\d+\.\d+)(?:\s|$)/i)?.[1];
1468
+ const commandName = basename(normalize(command));
1469
+ const relatedToGazeSight = /(?:^|[\\/])gazesight(?:-mcp)?(?:\.cmd|\.exe)?$/i.test(command) || /(?:^|[\\/])dist[\\/]cli[\\/]index\.js(?:\s|$)/i.test(argsText) || /(?:^|\s)gazesight(?:@\d+\.\d+\.\d+)?(?:\s|$)/i.test(argsText) || ["npx", "npx.cmd", "npx.exe"].includes(commandName) && /gazesight/i.test(argsText);
1470
+ return {
1471
+ command,
1472
+ argsText,
1473
+ args: parseKnownGazeSightArgs(command, argsText),
1474
+ scope: agent === "claude" ? parseScope(output) : "user",
1475
+ version: explicitVersion ?? versionFromPackageRoot(argsText),
1476
+ obsoleteNpxCache: /(?:^|[\\/])_npx(?:[\\/]|$)/i.test(combined),
1477
+ relatedToGazeSight
1478
+ };
1479
+ }
1480
+ function configurationsMatch(existing, proposed) {
1481
+ return !existing.obsoleteNpxCache && normalize(existing.command) === normalize(proposed.command) && normalize(existing.argsText) === normalize(proposed.args.join(" ")) && (!existing.version || existing.version === proposed.version);
1482
+ }
1483
+ function display(config) {
1484
+ if ("argsText" in config)
1485
+ return `command=${JSON.stringify(config.command)} args=${JSON.stringify(config.argsText)}${config.version ? ` version=${config.version}` : " version=unknown"} scope=${config.scope}`;
1486
+ return `command=${JSON.stringify(config.command)} args=${JSON.stringify(config.args.join(" "))} version=${config.version}`;
1487
+ }
1488
+ function failure(result) {
1489
+ return (result.stderr || result.stdout || "unknown error").trim().split(/\r?\n/)[0];
1490
+ }
1491
+ function configureAgent(agent, binaryPath, proposed, allowUpdate, spawn = (command, commandArgs) => spawnSync(command, commandArgs, { encoding: "utf8", windowsHide: true })) {
1492
+ const get = spawn(binaryPath, ["mcp", "get", "gazesight"]);
1493
+ const existing = get.status === 0 ? parseRegisteredConfig(agent, get.stdout) : void 0;
1494
+ if (!existing) {
1495
+ const addArgs2 = [
1496
+ "mcp",
1497
+ "add",
1498
+ "gazesight",
1499
+ ...agent === "claude" ? ["--scope", "user"] : [],
1500
+ "--",
1501
+ proposed.command,
1502
+ ...proposed.args
1503
+ ];
1504
+ const add2 = spawn(binaryPath, addArgs2);
1505
+ return add2.status === 0 ? { ok: true, state: "configured", message: "Configured." } : { ok: false, state: "failed", message: `Failed: ${failure(add2)}` };
1506
+ }
1507
+ if (!existing.relatedToGazeSight)
1508
+ return {
1509
+ ok: false,
1510
+ state: "conflict",
1511
+ message: `Refusing to overwrite an unrelated MCP entry named gazesight. Existing: ${display(existing)}`
1512
+ };
1513
+ if (!existing.args && allowUpdate)
1514
+ return {
1515
+ ok: false,
1516
+ state: "conflict",
1517
+ message: `Refusing an update because the existing GazeSight arguments cannot be reconstructed safely for rollback. Existing: ${display(existing)}`
1518
+ };
1519
+ if (configurationsMatch(existing, proposed))
1520
+ return { ok: true, state: "identical", message: "Already configured (identical; unchanged)." };
1521
+ const comparison = `Configuration differs.
1522
+ Existing: ${display(existing)}
1523
+ Proposed: ${display(proposed)}`;
1524
+ if (!allowUpdate)
1525
+ return {
1526
+ ok: false,
1527
+ state: "outdated",
1528
+ message: `${comparison}
1529
+ Re-run setup with --update to replace only this GazeSight entry while preserving its ${existing.scope} scope.`
1530
+ };
1531
+ const removeArgs = [
1532
+ "mcp",
1533
+ "remove",
1534
+ "gazesight",
1535
+ ...agent === "claude" ? ["--scope", existing.scope] : []
1536
+ ];
1537
+ const remove = spawn(binaryPath, removeArgs);
1538
+ if (remove.status !== 0)
1539
+ return { ok: false, state: "failed", message: `${comparison}
1540
+ Update failed before replacement: ${failure(remove)}` };
1541
+ const addArgs = [
1542
+ "mcp",
1543
+ "add",
1544
+ "gazesight",
1545
+ ...agent === "claude" ? ["--scope", existing.scope] : [],
1546
+ "--",
1547
+ proposed.command,
1548
+ ...proposed.args
1549
+ ];
1550
+ const add = spawn(binaryPath, addArgs);
1551
+ if (add.status === 0)
1552
+ return { ok: true, state: "updated", message: `${comparison}
1553
+ Updated safely.` };
1554
+ const restoreArgs = [
1555
+ "mcp",
1556
+ "add",
1557
+ "gazesight",
1558
+ ...agent === "claude" ? ["--scope", existing.scope] : [],
1559
+ "--",
1560
+ existing.command,
1561
+ ...existing.args
1562
+ ];
1563
+ const restore = spawn(binaryPath, restoreArgs);
1564
+ return {
1565
+ ok: false,
1566
+ state: "failed",
1567
+ message: restore.status === 0 ? `${comparison}
1568
+ Replacement failed: ${failure(add)}. The previous entry was restored.` : `${comparison}
1569
+ Replacement failed: ${failure(add)}. Automatic restoration also failed: ${failure(restore)}. Follow the displayed existing configuration to restore it manually.`
1570
+ };
1571
+ }
1572
+
1369
1573
  // apps/cli/src/index.ts
1370
- var root = resolve(dirname2(fileURLToPath(import.meta.url)), "..");
1574
+ var root = resolve2(dirname2(fileURLToPath(import.meta.url)), "..");
1371
1575
  var args = process.argv.slice(2);
1372
1576
  function option(name) {
1373
1577
  const i = args.indexOf(name);
@@ -1380,7 +1584,7 @@ function detectOnPath(binary) {
1380
1584
  for (const directory of (process.env.PATH ?? "").split(delimiter))
1381
1585
  for (const name of [binary, `${binary}.exe`, `${binary}.cmd`]) {
1382
1586
  const candidate = join2(directory, name);
1383
- if (existsSync(candidate)) return candidate;
1587
+ if (existsSync2(candidate)) return candidate;
1384
1588
  }
1385
1589
  return void 0;
1386
1590
  }
@@ -1398,38 +1602,8 @@ async function promptClientChoice() {
1398
1602
  rl.close();
1399
1603
  }
1400
1604
  }
1401
- function configureAgent(agent, binaryPath, serverArgs) {
1402
- if (agent === "claude") {
1403
- const existing = spawnSync(binaryPath, ["mcp", "get", "gazesight"], {
1404
- encoding: "utf8"
1405
- });
1406
- if (existing.status === 0)
1407
- return { ok: true, message: "Already configured (unchanged)." };
1408
- const add2 = spawnSync(
1409
- binaryPath,
1410
- ["mcp", "add", "gazesight", "--scope", "user", "--", ...serverArgs],
1411
- { encoding: "utf8" }
1412
- );
1413
- if (add2.status === 0) return { ok: true, message: "Configured." };
1414
- return {
1415
- ok: false,
1416
- message: `Failed: ${(add2.stderr || add2.stdout || "unknown error").trim().split("\n")[0]}`
1417
- };
1418
- }
1419
- const list = spawnSync(binaryPath, ["mcp", "list"], { encoding: "utf8" });
1420
- if (list.status === 0 && list.stdout.includes("gazesight"))
1421
- return { ok: true, message: "Already configured (unchanged)." };
1422
- const add = spawnSync(binaryPath, ["mcp", "add", "gazesight", "--", ...serverArgs], {
1423
- encoding: "utf8"
1424
- });
1425
- if (add.status === 0) return { ok: true, message: "Configured." };
1426
- return {
1427
- ok: false,
1428
- message: `Failed: ${(add.stderr || add.stdout || "unknown error").trim().split("\n")[0]}`
1429
- };
1430
- }
1431
1605
  try {
1432
- const configPath = resolve(
1606
+ const configPath = resolve2(
1433
1607
  option("--config") ?? process.env.GAZESIGHT_CONFIG_FILE ?? defaultConfigPath()
1434
1608
  );
1435
1609
  if (args[0] === "mcp") {
@@ -1439,24 +1613,31 @@ try {
1439
1613
  else if (!args[0] || args.includes("--help"))
1440
1614
  console.log(
1441
1615
  `GazeSight ${VERSION}
1442
- gazesight setup [--config PATH] [--port PORT] [--client claude|codex|both]
1616
+ gazesight setup [--config PATH] [--port PORT] [--client claude|codex|both] [--update]
1443
1617
  gazesight pair [--config PATH] [--revoke ID|all]
1444
1618
  gazesight doctor [--config PATH]
1445
1619
  gazesight mcp [--config PATH]
1620
+ --update replaces only an outdated GazeSight MCP entry after showing the existing and proposed configurations.
1621
+ Claude Code and Codex may both be configured, but only one MCP process can own a configured bridge port at a time. A second process exits with GS_PORT_IN_USE; close the first client session or configure a separate port.
1446
1622
  No browser is launched. Secrets are never printed.`
1447
1623
  );
1448
1624
  else {
1449
1625
  if (!["setup", "doctor", "pair"].includes(args[0]))
1450
1626
  throw new Error("Unknown command.");
1451
- if (args.slice(1).some(
1452
- (value, i) => i % 2 === 0 && !["--config", "--port", "--client", "--revoke"].includes(value)
1453
- ))
1454
- throw new Error("Unknown option.");
1627
+ const valueOptions = /* @__PURE__ */ new Set(["--config", "--port", "--client", "--revoke"]);
1628
+ const flagOptions = /* @__PURE__ */ new Set(["--update"]);
1629
+ for (let i = 1; i < args.length; i++) {
1630
+ const value = args[i];
1631
+ if (flagOptions.has(value)) continue;
1632
+ if (!valueOptions.has(value) || !args[i + 1] || args[i + 1].startsWith("--"))
1633
+ throw new Error("Unknown option or missing option value.");
1634
+ i++;
1635
+ }
1455
1636
  const clientOption = option("--client");
1456
1637
  if (clientOption && !["claude", "codex", "both"].includes(clientOption))
1457
1638
  throw new Error("--client must be claude, codex, or both.");
1458
1639
  const node = Number(process.versions.node.split(".")[0]) >= 20;
1459
- const build = existsSync(join2(root, "mcp/index.js")) && existsSync(join2(root, "firefox/manifest.json"));
1640
+ const build = existsSync2(join2(root, "mcp/index.js")) && existsSync2(join2(root, "firefox/manifest.json"));
1460
1641
  console.log(
1461
1642
  `Node: ${node ? "PASS" : "FAIL"}
1462
1643
  GazeSight build: ${build ? "PASS" : "FAIL"}`
@@ -1481,6 +1662,15 @@ Agent guidance: ${config.agentGuidance.enabled ? "Enabled" : "Disabled"}${config
1481
1662
  "--config",
1482
1663
  configPath
1483
1664
  ];
1665
+ if (chosen.length > 1)
1666
+ console.log(
1667
+ "Shared-use notice: Claude Code and Codex will both be configured, but they cannot use the same bridge port simultaneously. The first MCP process owns the port; a second exits explicitly with GS_PORT_IN_USE. Close the first client session before starting the other, or configure a separate port/config."
1668
+ );
1669
+ const proposed = {
1670
+ command: serverArgs[0],
1671
+ args: serverArgs.slice(1),
1672
+ version: VERSION
1673
+ };
1484
1674
  for (const agent of chosen) {
1485
1675
  const label = agent === "claude" ? "Claude Code" : "Codex";
1486
1676
  const binaryPath = detectOnPath(agent);
@@ -1488,7 +1678,7 @@ Agent guidance: ${config.agentGuidance.enabled ? "Enabled" : "Disabled"}${config
1488
1678
  console.log(`${label}: not detected on PATH \u2014 configure manually below.`);
1489
1679
  continue;
1490
1680
  }
1491
- const result2 = configureAgent(agent, binaryPath, serverArgs);
1681
+ const result2 = configureAgent(agent, binaryPath, proposed, args.includes("--update"));
1492
1682
  console.log(`${label}: ${result2.ok ? "PASS" : "FAIL"} \u2014 ${result2.message}`);
1493
1683
  }
1494
1684
  const undetected = explicit ? chosen.filter((agent) => !detectOnPath(agent)) : ["claude", "codex"];
@@ -1511,10 +1701,9 @@ args = ${JSON.stringify(server.args)}`
1511
1701
  }
1512
1702
  console.log(
1513
1703
  `Open your GazeSight browser extension and click Connect to GazeSight.
1704
+ Firefox: install or update the signed extension from the official listing \u2014 https://addons.mozilla.org/addon/6a2319d4fa544731884d/ (matches gazesight@${VERSION}).
1514
1705
  Chrome (manual install, not yet on the Chrome Web Store): open chrome://extensions, enable Developer mode, "Load unpacked", select ${join2(root, "chrome")}.
1515
- Development only (Firefox): about:debugging > Load Temporary Add-on > ${join2(root, "firefox/manifest.json")}
1516
- Unsigned XPI: ${join2(root, "gazesight-firefox.xpi")}
1517
- External Firefox Release/Beta installation requires the separately supplied Mozilla-signed XPI.
1706
+ Development only (building from this checkout, Firefox): about:debugging > Load Temporary Add-on > ${join2(root, "firefox/manifest.json")}, or install the unsigned XPI at ${join2(root, "gazesight-firefox.xpi")}.
1518
1707
  Manual JSON import remains available under Advanced settings.`
1519
1708
  );
1520
1709
  if (process.stdin.isTTY) await pairingFlow(config, configPath);