gazesight 0.5.1 → 0.5.3

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.
@@ -1,90 +1,90 @@
1
- {
2
- "manifest_version": 2,
3
- "name": "GazeSight",
4
- "version": "0.5.0",
5
- "description": "Give coding agents visual access to the Firefox tab you're already using.",
6
- "icons": {
7
- "16": "icons/icon-16.png",
8
- "32": "icons/icon-32.png",
9
- "48": "icons/icon-48.png",
10
- "64": "icons/icon-64.png",
11
- "96": "icons/icon-96.png",
12
- "128": "icons/icon-128.png"
13
- },
14
- "browser_specific_settings": {
15
- "gecko": {
16
- "id": "extension@gazesight.dev",
17
- "strict_min_version": "142.0",
18
- "data_collection_permissions": {
19
- "required": [
20
- "websiteContent",
21
- "browsingActivity"
22
- ]
23
- }
24
- }
25
- },
26
- "permissions": [
27
- "activeTab",
28
- "tabs",
29
- "storage",
30
- "scripting",
31
- "webRequest",
32
- "http://localhost/*",
33
- "https://localhost/*",
34
- "http://127.0.0.1/*",
35
- "https://127.0.0.1/*",
36
- "http://[::1]/*",
37
- "https://[::1]/*"
38
- ],
39
- "optional_permissions": [
40
- "<all_urls>",
41
- "downloads"
42
- ],
43
- "background": {
44
- "scripts": [
45
- "background.js"
46
- ],
47
- "persistent": true
48
- },
49
- "browser_action": {
50
- "default_title": "GazeSight — target and connection",
51
- "default_popup": "popup.html",
52
- "default_icon": {
53
- "16": "icons/icon-16.png",
54
- "32": "icons/icon-32.png",
55
- "48": "icons/icon-48.png",
56
- "64": "icons/icon-64.png",
57
- "96": "icons/icon-96.png",
58
- "128": "icons/icon-128.png"
59
- }
60
- },
61
- "commands": {
62
- "gazesight-select-target": {
63
- "suggested_key": {
64
- "default": "Ctrl+Shift+Y"
65
- },
66
- "description": "Select or reauthorize this tab with GazeSight"
67
- }
68
- },
69
- "options_ui": {
70
- "page": "options.html",
71
- "open_in_tab": true
72
- },
73
- "content_security_policy": "script-src 'self'; object-src 'none'; connect-src ws://127.0.0.1:* http://127.0.0.1:* data:",
74
- "content_scripts": [
75
- {
76
- "matches": [
77
- "http://localhost/*",
78
- "https://localhost/*",
79
- "http://127.0.0.1/*",
80
- "https://127.0.0.1/*",
81
- "http://[::1]/*",
82
- "https://[::1]/*"
83
- ],
84
- "js": [
85
- "content.js"
86
- ],
87
- "run_at": "document_start"
88
- }
89
- ]
90
- }
1
+ {
2
+ "manifest_version": 2,
3
+ "name": "GazeSight",
4
+ "version": "0.5.3",
5
+ "description": "Give coding agents visual access to the Firefox tab you're already using.",
6
+ "icons": {
7
+ "16": "icons/icon-16.png",
8
+ "32": "icons/icon-32.png",
9
+ "48": "icons/icon-48.png",
10
+ "64": "icons/icon-64.png",
11
+ "96": "icons/icon-96.png",
12
+ "128": "icons/icon-128.png"
13
+ },
14
+ "browser_specific_settings": {
15
+ "gecko": {
16
+ "id": "extension@gazesight.dev",
17
+ "strict_min_version": "142.0",
18
+ "data_collection_permissions": {
19
+ "required": [
20
+ "websiteContent",
21
+ "browsingActivity"
22
+ ]
23
+ }
24
+ }
25
+ },
26
+ "permissions": [
27
+ "activeTab",
28
+ "tabs",
29
+ "storage",
30
+ "scripting",
31
+ "webRequest",
32
+ "http://localhost/*",
33
+ "https://localhost/*",
34
+ "http://127.0.0.1/*",
35
+ "https://127.0.0.1/*",
36
+ "http://[::1]/*",
37
+ "https://[::1]/*"
38
+ ],
39
+ "optional_permissions": [
40
+ "<all_urls>",
41
+ "downloads"
42
+ ],
43
+ "background": {
44
+ "scripts": [
45
+ "background.js"
46
+ ],
47
+ "persistent": true
48
+ },
49
+ "browser_action": {
50
+ "default_title": "GazeSight — target and connection",
51
+ "default_popup": "popup.html",
52
+ "default_icon": {
53
+ "16": "icons/icon-16.png",
54
+ "32": "icons/icon-32.png",
55
+ "48": "icons/icon-48.png",
56
+ "64": "icons/icon-64.png",
57
+ "96": "icons/icon-96.png",
58
+ "128": "icons/icon-128.png"
59
+ }
60
+ },
61
+ "commands": {
62
+ "gazesight-select-target": {
63
+ "suggested_key": {
64
+ "default": "Ctrl+Shift+Y"
65
+ },
66
+ "description": "Select or reauthorize this tab with GazeSight"
67
+ }
68
+ },
69
+ "options_ui": {
70
+ "page": "options.html",
71
+ "open_in_tab": true
72
+ },
73
+ "content_security_policy": "script-src 'self'; object-src 'none'; connect-src ws://127.0.0.1:* http://127.0.0.1:* data:",
74
+ "content_scripts": [
75
+ {
76
+ "matches": [
77
+ "http://localhost/*",
78
+ "https://localhost/*",
79
+ "http://127.0.0.1/*",
80
+ "https://127.0.0.1/*",
81
+ "http://[::1]/*",
82
+ "https://[::1]/*"
83
+ ],
84
+ "js": [
85
+ "content.js"
86
+ ],
87
+ "run_at": "document_start"
88
+ }
89
+ ]
90
+ }
@@ -19,8 +19,8 @@
19
19
  <div class="paper">
20
20
  <main>
21
21
  <section id="pairing-connect" class="pairing-connect" hidden>
22
- <strong>Connect your browser</strong>
23
- <p id="pairing-message">Run <code>gazesight pair</code>, then approve the matching code.</p>
22
+ <strong id="pairing-title">Connect your browser</strong>
23
+ <p id="pairing-message">Click Connect to GazeSight and approve the matching code in your terminal.</p>
24
24
  <p id="pairing-code" class="pairing-code" hidden></p>
25
25
  <button id="connect" class="primary connect">Connect to GazeSight</button>
26
26
  </section>
@@ -1 +1 @@
1
- var r="pendingPairing";function k(){return[...crypto.getRandomValues(new Uint8Array(32))].map(t=>t.toString(16).padStart(2,"0")).join("")}function E(){return location.protocol==="moz-extension:"?"firefox":"chrome"}async function m(e,t,o,i,a=2500){return fetch(`http://127.0.0.1:${e}/gazesight/pairing/${t}`,{method:"POST",headers:{"Content-Type":"application/json",...i?{Authorization:`Bearer ${i}`}:{}},body:JSON.stringify(o),signal:AbortSignal.timeout(a)})}async function w(e,t){let{requestId:o,proof:i,code:a,expiresAt:l,port:b}=t;for(;;){if(Date.now()>=l)return await browser.storage.local.remove(r),e("expired",a),!1;e("waiting_approval",a);let g;try{g=await m(b,"status",{requestId:o,secret:i})}catch{await new Promise(p=>setTimeout(p,800));continue}let d=await g.json();if(d.state==="approved"&&d.credential&&d.pairingId)return await browser.storage.local.set({token:d.credential,pairingId:d.pairingId,port:b,pairingState:"connected"}),await browser.storage.local.remove(r),e("connected"),!0;if(d.state==="rejected")return await browser.storage.local.remove(r),e("reconnect_required",a),!1;if(d.state==="expired"||g.status===410)return await browser.storage.local.remove(r),e("expired",a),!1;await new Promise(p=>setTimeout(p,800))}}async function f(e,t=32147){e("searching");let o=k(),i;try{i=await m(t,"request",{browser:E(),extensionId:browser.runtime.id,secret:o},void 0,6e4)}catch{return e("unavailable"),!1}if(!i.ok)return e("unavailable"),!1;let a=await i.json(),l={requestId:a.requestId,proof:o,code:a.code,expiresAt:a.expiresAt,port:t};return await browser.storage.local.set({[r]:l}),e("pending",a.code),w(e,l)}async function h(e){let o=(await browser.storage.local.get(r))[r];if(o){if(Date.now()>=o.expiresAt){await browser.storage.local.remove(r);return}return w(e,o)}}function n(e,t){document.getElementById(e).textContent=t}var u=!1,y;function v(e,t=!1){clearTimeout(y),n("feedback",e),t&&(y=setTimeout(()=>n("feedback",""),3500))}async function c(e){let t=await browser.runtime.sendMessage({type:"gazesight-ui",action:e});if(!t?.ok)throw new Error(t?.error??"GazeSight unavailable. Open settings to check pairing.");return t.value}async function s(){let e=await c("status");document.body.dataset.connected=String(e.connected),n("connection",e.connected?"Connected":"Disconnected");let t=await browser.storage.local.get(["token","port"]);document.getElementById("pairing-connect").hidden=typeof t.token=="string",document.body.dataset.background=e.backgroundCapture.enabled?"enabled":"disabled",n("background-state",e.backgroundCapture.enabled?"On":"Off"),n("background-toggle",e.backgroundCapture.enabled?"Disable":"Enable"),document.getElementById("background-toggle").setAttribute("aria-pressed",String(e.backgroundCapture.enabled)),n("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",n("continuous-state",e.continuousAuthorization.enabled?"On":"Off"),n("continuous-toggle",e.continuousAuthorization.enabled?"Disable":"Enable"),document.getElementById("continuous-toggle").setAttribute("aria-pressed",String(e.continuousAuthorization.enabled)),n("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."),n("pairing",e.paired?"Paired":"Not paired"),n("target",e.targetState==="authorized"?"Authorized":e.targetState==="none"?"None":"Reauthorize"),n("url",e.url??"No target selected"),n("viewport",e.viewport?`${e.viewport.width} \xD7 ${e.viewport.height} CSS \xB7 DPR ${e.viewport.devicePixelRatio}`:"Viewport unavailable"),n("select-label",e.targetState==="reauthorization_required"?"Reauthorize this tab":"Use this tab"),n("last-capture",e.lastCapture?`${e.lastCapture.mode} \xB7 ${e.lastCapture.width} \xD7 ${e.lastCapture.height} \xB7 ${new Date(e.lastCapture.time).toLocaleTimeString()}`:"None yet"),n("last-error",e.lastError?`${e.lastError.code}: ${e.lastError.message}`:"None");for(let o of["fast","stable"])document.getElementById(o).disabled=u||!e.connected||e.targetState!=="authorized";document.getElementById("disconnect").disabled=u||e.targetState==="none"}var I={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 S(e,t){let o=document.getElementById("pairing-code");n("pairing-message",I[e]),o.hidden=!t,o.textContent=t?`Verification code: ${t}`:""}document.getElementById("connect").addEventListener("click",async()=>{let e=document.getElementById("connect");e.disabled=!0;let t=await browser.storage.local.get(["port"]);await f(S,Number(t.port??32147)),e.disabled=!1,await s()});(async()=>typeof(await browser.storage.local.get(["token"])).token!="string"&&await h(S)&&await s())();for(let e of["select","disconnect","fast","stable","diagnostics"])document.getElementById(e).addEventListener("click",async()=>{if(!u){u=!0,document.querySelectorAll("button").forEach(t=>t.disabled=!0),n("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 t=await c(e);e==="diagnostics"&&(document.getElementById("diagnostics-panel").hidden=!1,n("diagnostics-output",JSON.stringify(t,null,2))),v(e==="disconnect"?"Target disconnected.":e==="select"?"Target selected.":e==="diagnostics"?"Diagnostics updated.":e==="fast"?`\u2713 Saved to Downloads/${t.filename}`:"Capture completed.",e==="fast")}catch(t){v(t instanceof Error?t.message:"Operation failed.")}finally{u=!1,document.querySelectorAll("button").forEach(t=>t.disabled=!1),await s().catch(t=>n("feedback",String(t)))}}});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),n("feedback","URL copied.")}catch{n("feedback","Unable to copy URL. Select and copy it manually.")}});document.getElementById("background-toggle").addEventListener("click",async()=>{if((await c("status")).backgroundCapture.enabled){await c("background-disable"),await s();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 c("status");if(e.continuousAuthorization.enabled){await c("continuous-disable"),await s();return}if(e.continuousAuthorization.permissionGranted){await c("continuous-enable"),await s();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 t=e.find(o=>o.name==="gazesight-select-target")?.shortcut;t&&n("shortcut",t)}).catch(()=>{});document.getElementById("settings").addEventListener("click",()=>void browser.runtime.openOptionsPage());s().catch(e=>n("feedback",String(e)));var P=setInterval(()=>{u||s().catch(e=>n("feedback",String(e)))},2e3);window.addEventListener("pagehide",()=>clearInterval(P),{once:!0});
1
+ var r="pendingPairing";function k(){return[...crypto.getRandomValues(new Uint8Array(32))].map(t=>t.toString(16).padStart(2,"0")).join("")}function E(){return location.protocol==="moz-extension:"?"firefox":"chrome"}async function m(e,t,o,i,a=2500){return fetch(`http://127.0.0.1:${e}/gazesight/pairing/${t}`,{method:"POST",headers:{"Content-Type":"application/json",...i?{Authorization:`Bearer ${i}`}:{}},body:JSON.stringify(o),signal:AbortSignal.timeout(a)})}async function w(e,t){let{requestId:o,proof:i,code:a,expiresAt:l,port:b}=t;for(;;){if(Date.now()>=l)return await browser.storage.local.remove(r),e("expired",a),!1;e("waiting_approval",a);let g;try{g=await m(b,"status",{requestId:o,secret:i})}catch{await new Promise(p=>setTimeout(p,800));continue}let c=await g.json();if(c.state==="approved"&&c.credential&&c.pairingId)return await browser.storage.local.set({token:c.credential,pairingId:c.pairingId,port:b,pairingState:"connected"}),await browser.storage.local.remove(r),e("connected"),!0;if(c.state==="rejected")return await browser.storage.local.remove(r),e("reconnect_required",a),!1;if(c.state==="expired"||g.status===410)return await browser.storage.local.remove(r),e("expired",a),!1;await new Promise(p=>setTimeout(p,800))}}async function h(e,t=32147){e("searching");let o=k(),i;try{i=await m(t,"request",{browser:E(),extensionId:browser.runtime.id,secret:o},void 0,6e4)}catch{return e("unavailable"),!1}if(!i.ok)return e("unavailable"),!1;let a=await i.json(),l={requestId:a.requestId,proof:o,code:a.code,expiresAt:a.expiresAt,port:t};return await browser.storage.local.set({[r]:l}),e("pending",a.code),w(e,l)}async function f(e){let o=(await browser.storage.local.get(r))[r];if(o){if(Date.now()>=o.expiresAt){await browser.storage.local.remove(r);return}return w(e,o)}}function n(e,t){document.getElementById(e).textContent=t}var u=!1,y;function v(e,t=!1){clearTimeout(y),n("feedback",e),t&&(y=setTimeout(()=>n("feedback",""),3500))}async function d(e){let t=await browser.runtime.sendMessage({type:"gazesight-ui",action:e});if(!t?.ok)throw new Error(t?.error??"GazeSight unavailable. Open settings to check pairing.");return t.value}async function s(){let e=await d("status");document.body.dataset.connected=String(e.connected),n("connection",e.connected?"Connected":"Disconnected");let t=await browser.storage.local.get(["token","port"]);document.getElementById("pairing-connect").hidden=e.connected,n("pairing-title",typeof t.token=="string"?"Reconnect or re-pair":"Connect your browser"),document.body.dataset.background=e.backgroundCapture.enabled?"enabled":"disabled",n("background-state",e.backgroundCapture.enabled?"On":"Off"),n("background-toggle",e.backgroundCapture.enabled?"Disable":"Enable"),document.getElementById("background-toggle").setAttribute("aria-pressed",String(e.backgroundCapture.enabled)),n("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",n("continuous-state",e.continuousAuthorization.enabled?"On":"Off"),n("continuous-toggle",e.continuousAuthorization.enabled?"Disable":"Enable"),document.getElementById("continuous-toggle").setAttribute("aria-pressed",String(e.continuousAuthorization.enabled)),n("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."),n("pairing",e.paired?"Paired":"Not paired"),n("target",e.targetState==="authorized"?"Authorized":e.targetState==="none"?"None":"Reauthorize"),n("url",e.url??"No target selected"),n("viewport",e.viewport?`${e.viewport.width} \xD7 ${e.viewport.height} CSS \xB7 DPR ${e.viewport.devicePixelRatio}`:"Viewport unavailable"),n("select-label",e.targetState==="reauthorization_required"?"Reauthorize this tab":"Use this tab"),n("last-capture",e.lastCapture?`${e.lastCapture.mode} \xB7 ${e.lastCapture.width} \xD7 ${e.lastCapture.height} \xB7 ${new Date(e.lastCapture.time).toLocaleTimeString()}`:"None yet"),n("last-error",e.lastError?`${e.lastError.code}: ${e.lastError.message}`:"None");for(let o of["fast","stable"])document.getElementById(o).disabled=u||!e.connected||e.targetState!=="authorized";document.getElementById("disconnect").disabled=u||e.targetState==="none"}var I={not_connected:"Not connected. Choose Connect and approve the matching code in your terminal.",searching:"Searching for local GazeSight\u2026",pending:"Pairing request pending.",waiting_approval:"Check that this code matches the one in your terminal.",connected:"Connected.",unavailable:"Connection unavailable. Run gazesight pair and try again.",expired:"Pairing expired. Try again.",reconnect_required:"Reconnect required."};function S(e,t){let o=document.getElementById("pairing-code");n("pairing-message",I[e]),o.hidden=!t,o.textContent=t?`Verification code: ${t}`:""}document.getElementById("connect").addEventListener("click",async()=>{let e=document.getElementById("connect");e.disabled=!0;let t=await browser.storage.local.get(["port"]);await h(S,Number(t.port??32147)),e.disabled=!1,await s()});(async()=>typeof(await browser.storage.local.get(["token"])).token!="string"&&await f(S)&&await s())();for(let e of["select","disconnect","fast","stable","diagnostics"])document.getElementById(e).addEventListener("click",async()=>{if(!u){u=!0,document.querySelectorAll("button").forEach(t=>t.disabled=!0),n("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 t=await d(e);e==="diagnostics"&&(document.getElementById("diagnostics-panel").hidden=!1,n("diagnostics-output",JSON.stringify(t,null,2))),v(e==="disconnect"?"Target disconnected.":e==="select"?"Target selected.":e==="diagnostics"?"Diagnostics updated.":e==="fast"?`\u2713 Saved to Downloads/${t.filename}`:"Capture completed.",e==="fast")}catch(t){v(t instanceof Error?t.message:"Operation failed.")}finally{u=!1,document.querySelectorAll("button").forEach(t=>t.disabled=!1),await s().catch(t=>n("feedback",String(t)))}}});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),n("feedback","URL copied.")}catch{n("feedback","Unable to copy URL. Select and copy it manually.")}});document.getElementById("background-toggle").addEventListener("click",async()=>{if((await d("status")).backgroundCapture.enabled){await d("background-disable"),await s();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 d("status");if(e.continuousAuthorization.enabled){await d("continuous-disable"),await s();return}if(e.continuousAuthorization.permissionGranted){await d("continuous-enable"),await s();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 t=e.find(o=>o.name==="gazesight-select-target")?.shortcut;t&&n("shortcut",t)}).catch(()=>{});document.getElementById("settings").addEventListener("click",()=>void browser.runtime.openOptionsPage());s().catch(e=>n("feedback",String(e)));var P=setInterval(()=>{u||s().catch(e=>n("feedback",String(e)))},2e3);window.addEventListener("pagehide",()=>clearInterval(P),{once:!0});
Binary file
package/dist/mcp/index.js CHANGED
@@ -1,6 +1,8 @@
1
1
  #!/usr/bin/env node
2
2
 
3
3
  // apps/mcp-server/src/index.ts
4
+ import { spawn } from "node:child_process";
5
+ import { fileURLToPath } from "node:url";
4
6
  import { StdioServerTransport as StdioServerTransport2 } from "@modelcontextprotocol/server/stdio";
5
7
 
6
8
  // apps/mcp-server/src/config.ts
@@ -119,7 +121,7 @@ function loadConfig(env = process.env) {
119
121
  }
120
122
 
121
123
  // packages/protocol/src/release.ts
122
- var VERSION = "0.5.1";
124
+ var VERSION = "0.5.3";
123
125
 
124
126
  // apps/mcp-server/src/server.ts
125
127
  import { McpServer } from "@modelcontextprotocol/server";
@@ -738,7 +740,7 @@ async function runDiff(baseline, current, threshold) {
738
740
  );
739
741
  let timer;
740
742
  try {
741
- return await new Promise((resolve, reject) => {
743
+ return await new Promise((resolve2, reject) => {
742
744
  timer = setTimeout(
743
745
  () => reject(
744
746
  new GSError(
@@ -750,7 +752,7 @@ async function runDiff(baseline, current, threshold) {
750
752
  );
751
753
  worker.once(
752
754
  "message",
753
- (result) => result.ok ? resolve(result.value) : reject(new GSError(result.code, "Local PNG diff failed."))
755
+ (result) => result.ok ? resolve2(result.value) : reject(new GSError(result.code, "Local PNG diff failed."))
754
756
  );
755
757
  worker.once(
756
758
  "error",
@@ -1486,7 +1488,7 @@ var RequestRouter = class {
1486
1488
  type,
1487
1489
  payload
1488
1490
  });
1489
- return new Promise((resolve, reject) => {
1491
+ return new Promise((resolve2, reject) => {
1490
1492
  const timer = setTimeout(() => {
1491
1493
  this.pending.delete(request.id);
1492
1494
  reject(
@@ -1496,7 +1498,7 @@ var RequestRouter = class {
1496
1498
  )
1497
1499
  );
1498
1500
  }, this.timeoutMs);
1499
- this.pending.set(request.id, { resolve, reject, timer });
1501
+ this.pending.set(request.id, { resolve: resolve2, reject, timer });
1500
1502
  try {
1501
1503
  this.send(request);
1502
1504
  } catch (error) {
@@ -1718,7 +1720,7 @@ var PairingService = class {
1718
1720
  }
1719
1721
  listPending() {
1720
1722
  this.expire();
1721
- return [...this.pending.values()].filter((item) => item.state === "pending").map(({ id, browser, extensionId, code, createdAt, expiresAt }) => ({ id, browser, extensionId, code, createdAt, expiresAt }));
1723
+ return [...this.pending.values()].filter((item) => item.state === "pending").map(({ id, browser, extensionId, code, createdAt, expiresAt }) => ({ id, browser, extensionId, code, createdAt, expiresAt })).sort((a, b) => b.createdAt - a.createdAt);
1722
1724
  }
1723
1725
  listPairings() {
1724
1726
  return this.pairings.map(({ id, browser, extensionId, origin, createdAt, revokedAt }) => ({ id, browser, extensionId, origin, createdAt, ...revokedAt ? { revokedAt } : {} }));
@@ -2102,7 +2104,7 @@ var Bridge = class {
2102
2104
  async ready() {
2103
2105
  if (this.listenError) throw this.listenError;
2104
2106
  if (this.server.address()) return;
2105
- await new Promise((resolve, reject) => {
2107
+ await new Promise((resolve2, reject) => {
2106
2108
  const cleanup = () => {
2107
2109
  this.server.off("listening", onListening);
2108
2110
  this.server.off("error", onError);
@@ -2110,7 +2112,7 @@ var Bridge = class {
2110
2112
  };
2111
2113
  const onListening = () => {
2112
2114
  cleanup();
2113
- resolve();
2115
+ resolve2();
2114
2116
  };
2115
2117
  const onError = () => {
2116
2118
  cleanup();
@@ -2129,9 +2131,9 @@ var Bridge = class {
2129
2131
  this.wiring.clear();
2130
2132
  for (const client of this.server.clients) client.terminate();
2131
2133
  if (this.http.listening)
2132
- await new Promise((resolve) => this.http.close(() => resolve()));
2134
+ await new Promise((resolve2) => this.http.close(() => resolve2()));
2133
2135
  else if (this.server.address())
2134
- await new Promise((resolve) => this.server.close(() => resolve()));
2136
+ await new Promise((resolve2) => this.server.close(() => resolve2()));
2135
2137
  })();
2136
2138
  return this.closePromise;
2137
2139
  }
@@ -2260,14 +2262,17 @@ import { EventEmitter } from "node:events";
2260
2262
  // apps/mcp-server/src/runtime-ipc/protocol.ts
2261
2263
  import { createHash as createHash2, randomBytes as randomBytes2 } from "node:crypto";
2262
2264
  import { tmpdir } from "node:os";
2263
- import { basename, dirname as dirname2, join as join3 } from "node:path";
2265
+ import { basename, dirname as dirname2, join as join3, resolve } from "node:path";
2264
2266
  var RUNTIME_PROTOCOL_VERSION = 1;
2265
2267
  var MAX_FRAME_BYTES = 32 * 1024 * 1024;
2266
2268
  var MAX_PENDING_REQUESTS = 16;
2267
2269
  var REQUEST_TIMEOUT_MS = 3e5;
2268
2270
  var HANDSHAKE_TIMEOUT_MS = 5e3;
2269
2271
  function runtimeManifestPath(configPath) {
2270
- return join3(dirname2(configPath), "runtime-ipc.json");
2272
+ if (basename(configPath).toLowerCase() === "config.json")
2273
+ return join3(dirname2(configPath), "runtime-ipc.json");
2274
+ const identity = createHash2("sha256").update(resolve(configPath).toLowerCase()).digest("hex").slice(0, 16);
2275
+ return join3(dirname2(configPath), `runtime-ipc-${identity}.json`);
2271
2276
  }
2272
2277
  function runtimeEndpoint(platform, userId, home, env = process.env, temporaryDirectory = tmpdir()) {
2273
2278
  const runtimeIdentity = randomBytes2(24).toString("hex");
@@ -2406,9 +2411,9 @@ var JsonLineChannel = class extends EventEmitter {
2406
2411
  async nextFrame(timeoutMs) {
2407
2412
  if (this.frames.length) return this.frames.shift();
2408
2413
  if (this.closed) throw new Error("GS_RUNTIME_UNAVAILABLE");
2409
- return new Promise((resolve, reject) => {
2414
+ return new Promise((resolve2, reject) => {
2410
2415
  const waiter = {
2411
- resolve,
2416
+ resolve: resolve2,
2412
2417
  reject,
2413
2418
  timer: setTimeout(() => {
2414
2419
  const index = this.waiters.indexOf(waiter);
@@ -2471,8 +2476,8 @@ var JsonLineChannel = class extends EventEmitter {
2471
2476
  throw new Error("GS_RUNTIME_INVALID_MESSAGE");
2472
2477
  }
2473
2478
  if (!this.socket.write(data))
2474
- await new Promise((resolve, reject) => {
2475
- this.socket.once("drain", resolve);
2479
+ await new Promise((resolve2, reject) => {
2480
+ this.socket.once("drain", resolve2);
2476
2481
  this.socket.once("error", reject);
2477
2482
  });
2478
2483
  }
@@ -2547,20 +2552,20 @@ function safeToRemove(path) {
2547
2552
  ).catch(() => false);
2548
2553
  }
2549
2554
  function endpointIsLive(endpoint) {
2550
- return new Promise((resolve) => {
2555
+ return new Promise((resolve2) => {
2551
2556
  const socket = connectSocket(endpoint);
2552
2557
  const timer = setTimeout(() => {
2553
2558
  socket.destroy();
2554
- resolve(false);
2559
+ resolve2(false);
2555
2560
  }, 250);
2556
2561
  socket.once("connect", () => {
2557
2562
  clearTimeout(timer);
2558
2563
  socket.destroy();
2559
- resolve(true);
2564
+ resolve2(true);
2560
2565
  });
2561
2566
  socket.once("error", () => {
2562
2567
  clearTimeout(timer);
2563
- resolve(false);
2568
+ resolve2(false);
2564
2569
  });
2565
2570
  });
2566
2571
  }
@@ -2650,12 +2655,12 @@ var RuntimeIpcServer = class {
2650
2655
  }
2651
2656
  this.server = createServer3((socket) => this.accept(socket));
2652
2657
  this.server.maxConnections = 32;
2653
- await new Promise((resolve, reject) => {
2658
+ await new Promise((resolve2, reject) => {
2654
2659
  const onError = (error) => reject(error);
2655
2660
  this.server.once("error", onError);
2656
2661
  this.server.listen(this.endpointInfo.endpoint, () => {
2657
2662
  this.server.off("error", onError);
2658
- resolve();
2663
+ resolve2();
2659
2664
  });
2660
2665
  });
2661
2666
  if (this.platform !== "win32")
@@ -2762,7 +2767,7 @@ var RuntimeIpcServer = class {
2762
2767
  this.hosts.clear();
2763
2768
  this.runtime.setMcpClientCount(0);
2764
2769
  if (this.server)
2765
- await new Promise((resolve) => this.server.close(() => resolve()));
2770
+ await new Promise((resolve2) => this.server.close(() => resolve2()));
2766
2771
  if (await safeToRemove(this.manifestPath)) {
2767
2772
  try {
2768
2773
  const value = JSON.parse(await readFile2(this.manifestPath, "utf8"));
@@ -2833,7 +2838,7 @@ async function readManifest(path) {
2833
2838
  }
2834
2839
  }
2835
2840
  function connect(endpoint) {
2836
- return new Promise((resolve, reject) => {
2841
+ return new Promise((resolve2, reject) => {
2837
2842
  const socket = connectSocket2(endpoint);
2838
2843
  const timer = setTimeout(() => {
2839
2844
  socket.destroy();
@@ -2841,7 +2846,7 @@ function connect(endpoint) {
2841
2846
  }, HANDSHAKE_TIMEOUT_MS);
2842
2847
  socket.once("connect", () => {
2843
2848
  clearTimeout(timer);
2844
- resolve(socket);
2849
+ resolve2(socket);
2845
2850
  });
2846
2851
  socket.once("error", () => {
2847
2852
  clearTimeout(timer);
@@ -2988,7 +2993,8 @@ try {
2988
2993
  await runDesktopAdapter(
2989
2994
  process.env.GAZESIGHT_CONFIG_FILE ?? defaultConfigPath()
2990
2995
  );
2991
- } else if (args.includes("--desktop-runtime")) {
2996
+ } else if (args.includes("--desktop-runtime") || args.includes("--auto-runtime")) {
2997
+ const auto = args.includes("--auto-runtime");
2992
2998
  const config = loadConfig();
2993
2999
  const runtime = createRuntime(config);
2994
3000
  const ipc = new RuntimeIpcServer(
@@ -3006,40 +3012,85 @@ try {
3006
3012
  }
3007
3013
  console.error(`INFO bridge listening 127.0.0.1:${config.port}`);
3008
3014
  let closing = false;
3015
+ let idleTimer;
3009
3016
  const close = async () => {
3010
3017
  if (closing) return;
3011
3018
  closing = true;
3019
+ if (idleTimer) clearInterval(idleTimer);
3012
3020
  await ipc.stop();
3013
3021
  await runtime.stop();
3014
3022
  };
3015
3023
  process.once("SIGINT", () => void close());
3016
3024
  process.once("SIGTERM", () => void close());
3017
- process.stdin.once("end", () => void close());
3025
+ if (auto) {
3026
+ let idleSince = Date.now();
3027
+ idleTimer = setInterval(() => {
3028
+ if (ipc.connectedClients > 0) idleSince = Date.now();
3029
+ else if (Date.now() - idleSince > 3e4) void close();
3030
+ }, 1e3);
3031
+ } else process.stdin.once("end", () => void close());
3018
3032
  } else {
3019
3033
  const config = loadConfig();
3020
- const runtime = createRuntime(config);
3021
- let closeHost = async () => {
3022
- };
3023
- try {
3024
- const session = await runtime.connectMcpHost();
3025
- closeHost = session.close;
3026
- await session.host.connect(new StdioServerTransport2());
3027
- } catch (error) {
3028
- await runtime.stop();
3029
- await closeHost();
3030
- throw error;
3034
+ if (!config.configPath) {
3035
+ const runtime = createRuntime(config);
3036
+ let closeHost = async () => {
3037
+ };
3038
+ try {
3039
+ const session = await runtime.connectMcpHost();
3040
+ closeHost = session.close;
3041
+ await session.host.connect(new StdioServerTransport2());
3042
+ } catch (error) {
3043
+ await runtime.stop();
3044
+ await closeHost();
3045
+ throw error;
3046
+ }
3047
+ console.error(`INFO bridge listening 127.0.0.1:${config.port}`);
3048
+ let closing = false;
3049
+ const close = async () => {
3050
+ if (closing) return;
3051
+ closing = true;
3052
+ await runtime.stop();
3053
+ await closeHost();
3054
+ };
3055
+ process.once("SIGINT", () => void close());
3056
+ process.once("SIGTERM", () => void close());
3057
+ process.stdin.once("end", () => void close());
3058
+ } else {
3059
+ const configPath = config.configPath;
3060
+ const attach = async () => {
3061
+ try {
3062
+ await runDesktopAdapter(configPath);
3063
+ return true;
3064
+ } catch (error) {
3065
+ if (error instanceof Error && error.message === "GS_DESKTOP_RUNTIME_NOT_FOUND")
3066
+ return false;
3067
+ throw error;
3068
+ }
3069
+ };
3070
+ if (!await attach()) {
3071
+ const child = spawn(
3072
+ process.execPath,
3073
+ [fileURLToPath(import.meta.url), "--auto-runtime"],
3074
+ {
3075
+ detached: true,
3076
+ windowsHide: true,
3077
+ stdio: "ignore",
3078
+ env: { ...process.env, GAZESIGHT_CONFIG_FILE: configPath }
3079
+ }
3080
+ );
3081
+ await new Promise((resolve2, reject) => {
3082
+ child.once("spawn", resolve2);
3083
+ child.once("error", reject);
3084
+ });
3085
+ child.unref();
3086
+ let attached = false;
3087
+ for (let attempt = 0; attempt < 50 && !attached; attempt++) {
3088
+ await new Promise((resolve2) => setTimeout(resolve2, 100));
3089
+ attached = await attach();
3090
+ }
3091
+ if (!attached) throw new Error("GS_RUNTIME_UNAVAILABLE");
3092
+ }
3031
3093
  }
3032
- console.error(`INFO bridge listening 127.0.0.1:${config.port}`);
3033
- let closing = false;
3034
- const close = async () => {
3035
- if (closing) return;
3036
- closing = true;
3037
- await runtime.stop();
3038
- await closeHost();
3039
- };
3040
- process.once("SIGINT", () => void close());
3041
- process.once("SIGTERM", () => void close());
3042
- process.stdin.once("end", () => void close());
3043
3094
  }
3044
3095
  } catch (error) {
3045
3096
  const code = error instanceof Error ? error.message : "";