gazesight 0.5.0 → 0.5.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +128 -57
- package/dist/chrome/background.js +1 -1
- package/dist/chrome/manifest.json +2 -2
- package/dist/chrome/popup.html +2 -2
- package/dist/chrome/popup.js +1 -1
- package/dist/cli/index.js +393 -177
- package/dist/firefox/background.js +21 -21
- package/dist/firefox/manifest.json +90 -90
- package/dist/firefox/popup.html +2 -2
- package/dist/firefox/popup.js +1 -1
- package/dist/gazesight-firefox.xpi +0 -0
- package/dist/mcp/index.js +2418 -1397
- package/package.json +15 -4
package/dist/cli/index.js
CHANGED
|
@@ -3,13 +3,13 @@
|
|
|
3
3
|
// apps/cli/src/index.ts
|
|
4
4
|
import { existsSync as existsSync2 } from "node:fs";
|
|
5
5
|
import { createInterface as createInterface2 } from "node:readline/promises";
|
|
6
|
-
import { resolve as resolve2, dirname as dirname2, join as join2
|
|
6
|
+
import { resolve as resolve2, dirname as dirname2, join as join2 } from "node:path";
|
|
7
7
|
import { fileURLToPath, pathToFileURL } from "node:url";
|
|
8
8
|
|
|
9
9
|
// apps/cli/src/config.ts
|
|
10
10
|
import { randomBytes } from "node:crypto";
|
|
11
11
|
import { readFileSync, writeFileSync, mkdirSync, renameSync } from "node:fs";
|
|
12
|
-
import { dirname,
|
|
12
|
+
import { dirname, posix, win32 } from "node:path";
|
|
13
13
|
import { homedir } from "node:os";
|
|
14
14
|
|
|
15
15
|
// apps/cli/src/config-browser.ts
|
|
@@ -41,22 +41,23 @@ var localConfigSchema = z.strictObject({
|
|
|
41
41
|
|
|
42
42
|
// apps/cli/src/config.ts
|
|
43
43
|
function configPathFor(platform, home, env) {
|
|
44
|
+
const pathJoin = platform === "win32" ? win32.join : posix.join;
|
|
44
45
|
if (platform === "win32")
|
|
45
|
-
return
|
|
46
|
-
env.APPDATA ??
|
|
46
|
+
return pathJoin(
|
|
47
|
+
env.APPDATA ?? pathJoin(home, "AppData", "Roaming"),
|
|
47
48
|
"GazeSight",
|
|
48
49
|
"config.json"
|
|
49
50
|
);
|
|
50
51
|
if (platform === "darwin")
|
|
51
|
-
return
|
|
52
|
+
return pathJoin(
|
|
52
53
|
home,
|
|
53
54
|
"Library",
|
|
54
55
|
"Application Support",
|
|
55
56
|
"GazeSight",
|
|
56
57
|
"config.json"
|
|
57
58
|
);
|
|
58
|
-
return
|
|
59
|
-
env.XDG_CONFIG_HOME ??
|
|
59
|
+
return pathJoin(
|
|
60
|
+
env.XDG_CONFIG_HOME ?? pathJoin(home, ".config"),
|
|
60
61
|
"gazesight",
|
|
61
62
|
"config.json"
|
|
62
63
|
);
|
|
@@ -101,7 +102,7 @@ function writeLocalConfig(path, value) {
|
|
|
101
102
|
}
|
|
102
103
|
|
|
103
104
|
// packages/protocol/src/release.ts
|
|
104
|
-
var VERSION = "0.5.
|
|
105
|
+
var VERSION = "0.5.2";
|
|
105
106
|
|
|
106
107
|
// apps/cli/src/doctor.ts
|
|
107
108
|
import { z as z2 } from "zod";
|
|
@@ -119,18 +120,20 @@ function diagnosticRows(status, reachable = false, authenticated = false) {
|
|
|
119
120
|
},
|
|
120
121
|
{
|
|
121
122
|
name: "extension connected",
|
|
122
|
-
pass: !!status?.connected,
|
|
123
|
+
pass: !!status?.connected || Object.values(status?.connectedBrowsers ?? {}).some(Boolean),
|
|
123
124
|
action: status?.authorizedPairings ? "An extension is paired but disconnected. Start it and verify the configured bridge port." : "Open Firefox or Chrome, then click Connect to GazeSight in the extension."
|
|
124
125
|
},
|
|
125
126
|
{
|
|
126
127
|
name: "pairing",
|
|
127
|
-
|
|
128
|
+
// Pairing is durable authorization, while `paired` means a live
|
|
129
|
+
// WebSocket connection. An approved extension can be offline.
|
|
130
|
+
pass: authenticated && (status?.paired === true || (status?.authorizedPairings ?? 0) > 0),
|
|
128
131
|
action: "Run gazesight pair, click Connect to GazeSight, and approve the matching code."
|
|
129
132
|
},
|
|
130
133
|
{
|
|
131
134
|
name: "target selected",
|
|
132
135
|
pass: !!status && status.targetState !== "none",
|
|
133
|
-
action:
|
|
136
|
+
action: `Open your local preview in ${status?.activeTarget?.browser === "chrome" ? "Chrome" : "Firefox or Chrome"} and click the GazeSight icon.`
|
|
134
137
|
},
|
|
135
138
|
{
|
|
136
139
|
name: "capture permission",
|
|
@@ -164,7 +167,9 @@ var statusSchema = z2.object({
|
|
|
164
167
|
paired: z2.boolean(),
|
|
165
168
|
targetState: z2.enum(["none", "authorized", "reauthorization_required"]),
|
|
166
169
|
backgroundCapture: z2.object({ enabled: z2.boolean(), permissionGranted: z2.boolean() }).optional(),
|
|
167
|
-
authorizedPairings: z2.number().int().nonnegative().optional()
|
|
170
|
+
authorizedPairings: z2.number().int().nonnegative().optional(),
|
|
171
|
+
connectedBrowsers: z2.object({ firefox: z2.boolean().optional(), chrome: z2.boolean().optional() }).optional(),
|
|
172
|
+
activeTarget: z2.object({ browser: z2.enum(["firefox", "chrome"]).optional() }).nullable().optional()
|
|
168
173
|
});
|
|
169
174
|
|
|
170
175
|
// apps/cli/src/pair.ts
|
|
@@ -951,7 +956,7 @@ var PairingService = class {
|
|
|
951
956
|
}
|
|
952
957
|
listPending() {
|
|
953
958
|
this.expire();
|
|
954
|
-
return [...this.pending.values()].filter((item) => item.state === "pending").map(({ id, browser, extensionId, code, createdAt, expiresAt }) => ({ id, browser, extensionId, code, createdAt, expiresAt }));
|
|
959
|
+
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);
|
|
955
960
|
}
|
|
956
961
|
listPairings() {
|
|
957
962
|
return this.pairings.map(({ id, browser, extensionId, origin, createdAt, revokedAt }) => ({ id, browser, extensionId, origin, createdAt, ...revokedAt ? { revokedAt } : {} }));
|
|
@@ -1247,6 +1252,9 @@ var Bridge = class {
|
|
|
1247
1252
|
server;
|
|
1248
1253
|
http;
|
|
1249
1254
|
listenError;
|
|
1255
|
+
closePromise;
|
|
1256
|
+
mcpClientCount = 0;
|
|
1257
|
+
desktopIpcReady = false;
|
|
1250
1258
|
pairing;
|
|
1251
1259
|
// Resolves the connection currently backing the global active target, or
|
|
1252
1260
|
// undefined if no browser is active (nothing selected yet, or the
|
|
@@ -1309,6 +1317,8 @@ var Bridge = class {
|
|
|
1309
1317
|
})
|
|
1310
1318
|
);
|
|
1311
1319
|
return {
|
|
1320
|
+
mcpClients: this.mcpClientCount,
|
|
1321
|
+
desktopIpcReady: this.desktopIpcReady,
|
|
1312
1322
|
connected: this.connected,
|
|
1313
1323
|
paired: this.connected,
|
|
1314
1324
|
authorizedPairings: this.pairing.listPairings().filter((item) => !item.revokedAt).length,
|
|
@@ -1321,22 +1331,47 @@ var Bridge = class {
|
|
|
1321
1331
|
activeTarget: active ? { browser: active.browser, url: active.state.url } : null
|
|
1322
1332
|
};
|
|
1323
1333
|
}
|
|
1334
|
+
setMcpClientCount(count) {
|
|
1335
|
+
this.mcpClientCount = Math.max(0, Math.min(32, Math.floor(count)));
|
|
1336
|
+
}
|
|
1337
|
+
setDesktopIpcReady(ready) {
|
|
1338
|
+
this.desktopIpcReady = ready;
|
|
1339
|
+
}
|
|
1324
1340
|
async ready() {
|
|
1325
1341
|
if (this.listenError) throw this.listenError;
|
|
1326
1342
|
if (this.server.address()) return;
|
|
1327
1343
|
await new Promise((resolve3, reject) => {
|
|
1328
|
-
|
|
1329
|
-
|
|
1344
|
+
const cleanup = () => {
|
|
1345
|
+
this.server.off("listening", onListening);
|
|
1346
|
+
this.server.off("error", onError);
|
|
1347
|
+
this.http.off("error", onError);
|
|
1348
|
+
};
|
|
1349
|
+
const onListening = () => {
|
|
1350
|
+
cleanup();
|
|
1351
|
+
resolve3();
|
|
1352
|
+
};
|
|
1353
|
+
const onError = () => {
|
|
1354
|
+
cleanup();
|
|
1355
|
+
reject(this.listenError);
|
|
1356
|
+
};
|
|
1357
|
+
this.server.once("listening", onListening);
|
|
1330
1358
|
this.server.once("error", onError);
|
|
1331
1359
|
this.http.once("error", onError);
|
|
1332
1360
|
});
|
|
1333
1361
|
}
|
|
1334
1362
|
async close() {
|
|
1335
|
-
|
|
1336
|
-
|
|
1337
|
-
|
|
1338
|
-
|
|
1339
|
-
|
|
1363
|
+
if (this.closePromise) return this.closePromise;
|
|
1364
|
+
this.closePromise = (async () => {
|
|
1365
|
+
clearInterval(this.heartbeat);
|
|
1366
|
+
for (const wiring of this.wiring.values()) wiring.router.disconnect();
|
|
1367
|
+
this.wiring.clear();
|
|
1368
|
+
for (const client of this.server.clients) client.terminate();
|
|
1369
|
+
if (this.http.listening)
|
|
1370
|
+
await new Promise((resolve3) => this.http.close(() => resolve3()));
|
|
1371
|
+
else if (this.server.address())
|
|
1372
|
+
await new Promise((resolve3) => this.server.close(() => resolve3()));
|
|
1373
|
+
})();
|
|
1374
|
+
return this.closePromise;
|
|
1340
1375
|
}
|
|
1341
1376
|
};
|
|
1342
1377
|
|
|
@@ -1389,7 +1424,10 @@ async function pairingFlow(config, configPath, revoke) {
|
|
|
1389
1424
|
const answer = (await rl.question("Approve this matching request? [y/N] ")).trim().toLowerCase();
|
|
1390
1425
|
const approved = answer === "y" || answer === "yes";
|
|
1391
1426
|
const result = await admin(config.port, config.token, approved ? "approve" : "reject", "POST", { id: request.id, code: request.code });
|
|
1392
|
-
if (!result.ok)
|
|
1427
|
+
if (!result.ok) {
|
|
1428
|
+
console.log("That request expired or was replaced. Checking for the latest request\u2026");
|
|
1429
|
+
continue;
|
|
1430
|
+
}
|
|
1393
1431
|
if (approved) {
|
|
1394
1432
|
const deadline = Date.now() + 5e3;
|
|
1395
1433
|
let delivered = false;
|
|
@@ -1402,8 +1440,32 @@ async function pairingFlow(config, configPath, revoke) {
|
|
|
1402
1440
|
await new Promise((resolve3) => setTimeout(resolve3, 100));
|
|
1403
1441
|
}
|
|
1404
1442
|
if (!delivered) throw new Error("The approved credential was not delivered; keep the extension open and retry pairing.");
|
|
1405
|
-
console.log("Pairing approved and delivered.
|
|
1406
|
-
|
|
1443
|
+
console.log("Pairing approved and delivered.");
|
|
1444
|
+
console.log("Waiting for the extension to connect to this GazeSight bridge (up to 15 seconds)\u2026");
|
|
1445
|
+
const connectionDeadline = Date.now() + 15e3;
|
|
1446
|
+
let connected = false;
|
|
1447
|
+
while (Date.now() < connectionDeadline) {
|
|
1448
|
+
const status = await fetch(`http://127.0.0.1:${config.port}/gazesight/status`, {
|
|
1449
|
+
headers: headers(config.token),
|
|
1450
|
+
signal: AbortSignal.timeout(1500)
|
|
1451
|
+
}).catch(() => void 0);
|
|
1452
|
+
if (status?.ok) {
|
|
1453
|
+
const value = await status.json();
|
|
1454
|
+
if (value.connectedBrowsers?.[request.browser] === true) {
|
|
1455
|
+
connected = true;
|
|
1456
|
+
break;
|
|
1457
|
+
}
|
|
1458
|
+
}
|
|
1459
|
+
await new Promise((resolve3) => setTimeout(resolve3, 250));
|
|
1460
|
+
}
|
|
1461
|
+
if (connected)
|
|
1462
|
+
console.log("Extension connected. Pairing is ready.");
|
|
1463
|
+
else
|
|
1464
|
+
console.log(`Credential delivered, but ${request.browser} did not connect. Keep the extension installed and enabled, open its popup, verify its configured bridge port is ${config.port}, then click Connect to GazeSight again.`);
|
|
1465
|
+
} else {
|
|
1466
|
+
console.log("Pairing rejected. Waiting for another request\u2026");
|
|
1467
|
+
continue;
|
|
1468
|
+
}
|
|
1407
1469
|
return;
|
|
1408
1470
|
}
|
|
1409
1471
|
} finally {
|
|
@@ -1414,159 +1476,309 @@ async function pairingFlow(config, configPath, revoke) {
|
|
|
1414
1476
|
}
|
|
1415
1477
|
}
|
|
1416
1478
|
|
|
1417
|
-
// apps/
|
|
1418
|
-
import {
|
|
1419
|
-
import {
|
|
1420
|
-
import {
|
|
1421
|
-
|
|
1422
|
-
|
|
1423
|
-
|
|
1424
|
-
|
|
1425
|
-
|
|
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;
|
|
1479
|
+
// apps/agent-integration/src/index.ts
|
|
1480
|
+
import { spawn as nodeSpawn } from "node:child_process";
|
|
1481
|
+
import { constants as fsConstants, existsSync, readFileSync as readFileSync2 } from "node:fs";
|
|
1482
|
+
import { access, realpath, stat } from "node:fs/promises";
|
|
1483
|
+
import { delimiter, join, posix as posix2, resolve, win32 as win322 } from "node:path";
|
|
1484
|
+
var PROCESS_TIMEOUT_MS = 12e3;
|
|
1485
|
+
var PROCESS_OUTPUT_BYTES = 16 * 1024;
|
|
1486
|
+
function createBoundedRunner(spawnProcess = nodeSpawn) {
|
|
1487
|
+
return (command, args2, options = {}) => runBounded(command, args2, options, spawnProcess);
|
|
1436
1488
|
}
|
|
1437
|
-
|
|
1438
|
-
|
|
1439
|
-
|
|
1489
|
+
var DEFAULT_SPAWN = createBoundedRunner();
|
|
1490
|
+
var runAgentCommand = DEFAULT_SPAWN;
|
|
1491
|
+
function runBounded(command, args2, options = {}, spawnProcess = nodeSpawn) {
|
|
1492
|
+
return new Promise((resolveResult) => {
|
|
1493
|
+
let child;
|
|
1494
|
+
try {
|
|
1495
|
+
child = spawnProcess(command, [...args2], {
|
|
1496
|
+
shell: false,
|
|
1497
|
+
windowsHide: true,
|
|
1498
|
+
cwd: options.cwd,
|
|
1499
|
+
env: options.env ?? process.env,
|
|
1500
|
+
stdio: ["ignore", "pipe", "pipe"]
|
|
1501
|
+
});
|
|
1502
|
+
} catch {
|
|
1503
|
+
resolveResult({ code: null, stdout: "", stderr: "", timedOut: false });
|
|
1504
|
+
return;
|
|
1505
|
+
}
|
|
1506
|
+
let stdout = "";
|
|
1507
|
+
let stderr = "";
|
|
1508
|
+
let timedOut = false;
|
|
1509
|
+
const append = (current, chunk) => {
|
|
1510
|
+
const value = current + String(chunk);
|
|
1511
|
+
return Buffer.byteLength(value) <= PROCESS_OUTPUT_BYTES ? value : Buffer.from(value).subarray(0, PROCESS_OUTPUT_BYTES).toString();
|
|
1512
|
+
};
|
|
1513
|
+
child.stdout?.on("data", (chunk) => {
|
|
1514
|
+
stdout = append(stdout, chunk);
|
|
1515
|
+
});
|
|
1516
|
+
child.stderr?.on("data", (chunk) => {
|
|
1517
|
+
stderr = append(stderr, chunk);
|
|
1518
|
+
});
|
|
1519
|
+
const timer = setTimeout(() => {
|
|
1520
|
+
timedOut = true;
|
|
1521
|
+
child.kill();
|
|
1522
|
+
}, options.timeoutMs ?? PROCESS_TIMEOUT_MS);
|
|
1523
|
+
child.once("error", () => {
|
|
1524
|
+
clearTimeout(timer);
|
|
1525
|
+
resolveResult({ code: null, stdout, stderr, timedOut });
|
|
1526
|
+
});
|
|
1527
|
+
child.once("close", (code) => {
|
|
1528
|
+
clearTimeout(timer);
|
|
1529
|
+
resolveResult({ code, stdout, stderr, timedOut });
|
|
1530
|
+
});
|
|
1531
|
+
});
|
|
1440
1532
|
}
|
|
1441
|
-
function
|
|
1442
|
-
const
|
|
1443
|
-
|
|
1444
|
-
|
|
1445
|
-
|
|
1533
|
+
function pathCandidates(agent, platform, env, home) {
|
|
1534
|
+
const name = agent === "claude" ? "claude" : "codex";
|
|
1535
|
+
const pathJoin = (platform === "win32" ? win322 : posix2).join;
|
|
1536
|
+
const native = [];
|
|
1537
|
+
const packageScripts = [];
|
|
1538
|
+
if (platform === "win32") {
|
|
1539
|
+
const packageManagerBins = [env.PNPM_HOME, env.NPM_CONFIG_PREFIX].filter((value) => Boolean(value)).flatMap((root2) => [pathJoin(root2, `${name}.exe`), pathJoin(root2, `${name}.cmd`)]);
|
|
1540
|
+
if (agent === "claude") {
|
|
1541
|
+
native.push(
|
|
1542
|
+
pathJoin(home, ".local", "bin", "claude.exe"),
|
|
1543
|
+
pathJoin(env.LOCALAPPDATA ?? pathJoin(home, "AppData", "Local"), "Programs", "Claude", "claude.exe"),
|
|
1544
|
+
pathJoin(env.ProgramFiles ?? "C:\\Program Files", "Claude", "claude.exe")
|
|
1545
|
+
);
|
|
1546
|
+
} else {
|
|
1547
|
+
native.push(
|
|
1548
|
+
pathJoin(env.LOCALAPPDATA ?? pathJoin(home, "AppData", "Local"), "Programs", "OpenAI", "Codex", "bin", "codex.exe")
|
|
1549
|
+
);
|
|
1550
|
+
}
|
|
1551
|
+
const npm = pathJoin(env.APPDATA ?? pathJoin(home, "AppData", "Roaming"), "npm", `${name}.cmd`);
|
|
1552
|
+
const modules = [
|
|
1553
|
+
pathJoin(env.APPDATA ?? pathJoin(home, "AppData", "Roaming"), "npm", "node_modules"),
|
|
1554
|
+
...env.NPM_CONFIG_PREFIX ? [pathJoin(env.NPM_CONFIG_PREFIX, "node_modules")] : [],
|
|
1555
|
+
...env.PNPM_HOME ? [pathJoin(env.PNPM_HOME, "global", "5", "node_modules"), pathJoin(env.PNPM_HOME, "node_modules")] : [],
|
|
1556
|
+
pathJoin(home, "AppData", "Local", "pnpm", "global", "5", "node_modules")
|
|
1557
|
+
];
|
|
1558
|
+
const packageName = agent === "claude" ? "@anthropic-ai/claude-code" : "@openai/codex";
|
|
1559
|
+
const nodeCandidates = [
|
|
1560
|
+
env.ProgramFiles ? pathJoin(env.ProgramFiles, "nodejs", "node.exe") : "C:\\Program Files\\nodejs\\node.exe",
|
|
1561
|
+
...env.LOCALAPPDATA ? [pathJoin(env.LOCALAPPDATA, "Programs", "nodejs", "node.exe")] : [],
|
|
1562
|
+
...(env.PATH ?? env.Path ?? "").split(delimiter).filter(Boolean).map((directory) => pathJoin(directory, "node.exe"))
|
|
1563
|
+
];
|
|
1564
|
+
for (const modulesRoot of modules) {
|
|
1565
|
+
const root2 = pathJoin(modulesRoot, packageName);
|
|
1566
|
+
try {
|
|
1567
|
+
const metadata = JSON.parse(readFileSync2(pathJoin(root2, "package.json"), "utf8"));
|
|
1568
|
+
const bin = typeof metadata.bin === "string" ? metadata.bin : metadata.bin && typeof metadata.bin === "object" ? metadata.bin[name] : void 0;
|
|
1569
|
+
if (typeof bin !== "string" || bin.startsWith("/") || bin.split(/[\\/]/).includes("..")) continue;
|
|
1570
|
+
const script = pathJoin(root2, bin);
|
|
1571
|
+
for (const node of nodeCandidates)
|
|
1572
|
+
packageScripts.push({ command: node, prefixArgs: [script], source: "verified npm package" });
|
|
1573
|
+
} catch {
|
|
1574
|
+
}
|
|
1575
|
+
}
|
|
1576
|
+
native.push(npm, ...packageManagerBins);
|
|
1577
|
+
} else {
|
|
1578
|
+
native.push(
|
|
1579
|
+
pathJoin(home, ".local", "bin", name),
|
|
1580
|
+
pathJoin(home, ".npm-global", "bin", name),
|
|
1581
|
+
pathJoin(home, ".local", "share", "pnpm", name),
|
|
1582
|
+
...env.PNPM_HOME ? [pathJoin(env.PNPM_HOME, name)] : [],
|
|
1583
|
+
...env.NPM_CONFIG_PREFIX ? [pathJoin(env.NPM_CONFIG_PREFIX, "bin", name)] : []
|
|
1584
|
+
);
|
|
1585
|
+
if (platform === "darwin")
|
|
1586
|
+
native.push(
|
|
1587
|
+
pathJoin(home, "Library", "pnpm", name),
|
|
1588
|
+
"/opt/homebrew/bin/" + name,
|
|
1589
|
+
"/usr/local/bin/" + name
|
|
1590
|
+
);
|
|
1591
|
+
else if (platform === "linux")
|
|
1592
|
+
native.push("/usr/bin/" + name, "/usr/local/bin/" + name);
|
|
1593
|
+
}
|
|
1594
|
+
const fromPath = (env.PATH ?? env.Path ?? "").split(delimiter).filter(Boolean).flatMap(
|
|
1595
|
+
(directory) => platform === "win32" ? [pathJoin(directory, `${name}.exe`), pathJoin(directory, `${name}.cmd`)] : [pathJoin(directory, name)]
|
|
1596
|
+
);
|
|
1597
|
+
const seen = /* @__PURE__ */ new Set();
|
|
1598
|
+
const direct = [...native, ...fromPath].filter((path) => {
|
|
1599
|
+
const key = platform === "win32" ? path.toLowerCase() : path;
|
|
1600
|
+
if (seen.has(key)) return false;
|
|
1601
|
+
seen.add(key);
|
|
1602
|
+
return true;
|
|
1603
|
+
}).map((command) => ({ command, prefixArgs: [], source: "candidate" }));
|
|
1604
|
+
return [...packageScripts, ...direct];
|
|
1446
1605
|
}
|
|
1447
|
-
function
|
|
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;
|
|
1606
|
+
async function verifyCandidate(candidate, platform) {
|
|
1455
1607
|
try {
|
|
1456
|
-
const
|
|
1457
|
-
|
|
1608
|
+
const canonical = await realpath(candidate.command);
|
|
1609
|
+
if (canonical.includes("..")) return void 0;
|
|
1610
|
+
const info = await stat(canonical);
|
|
1611
|
+
if (!info.isFile()) return void 0;
|
|
1612
|
+
for (const argument of candidate.prefixArgs) {
|
|
1613
|
+
if (!argument.toLowerCase().endsWith(".js")) continue;
|
|
1614
|
+
const script = await realpath(argument);
|
|
1615
|
+
if (!(await stat(script)).isFile()) return void 0;
|
|
1616
|
+
if (platform === "win32" && !script.toLowerCase().endsWith(".js")) return void 0;
|
|
1617
|
+
}
|
|
1618
|
+
if (platform !== "win32") {
|
|
1619
|
+
await access(canonical, fsConstants.X_OK);
|
|
1620
|
+
if ((info.mode & 73) === 0) return void 0;
|
|
1621
|
+
} else if (!/\.(exe|cmd)$/i.test(canonical)) return void 0;
|
|
1622
|
+
return { ...candidate, command: canonical };
|
|
1458
1623
|
} catch {
|
|
1459
1624
|
return void 0;
|
|
1460
1625
|
}
|
|
1461
1626
|
}
|
|
1462
|
-
function
|
|
1463
|
-
const
|
|
1464
|
-
|
|
1465
|
-
if (
|
|
1466
|
-
|
|
1467
|
-
|
|
1468
|
-
|
|
1469
|
-
const
|
|
1627
|
+
function parseVersion(agent, output) {
|
|
1628
|
+
const value = output.match(/\b(\d+\.\d+\.\d+(?:[-+][0-9A-Za-z.-]+)?)\b/)?.[1];
|
|
1629
|
+
if (!value) return void 0;
|
|
1630
|
+
if (agent === "claude" && Number(value.split(".")[0]) < 2) return void 0;
|
|
1631
|
+
return value;
|
|
1632
|
+
}
|
|
1633
|
+
async function findAgentBinary(id, options = {}) {
|
|
1634
|
+
const platform = options.platform ?? process.platform;
|
|
1635
|
+
const env = options.env ?? process.env;
|
|
1636
|
+
const home = options.home ?? env.USERPROFILE ?? env.HOME ?? "";
|
|
1637
|
+
const run = options.spawn ?? DEFAULT_SPAWN;
|
|
1638
|
+
let sawCandidate = false;
|
|
1639
|
+
let sawDiscoveryError = false;
|
|
1640
|
+
for (const pathCandidate of pathCandidates(id, platform, env, home)) {
|
|
1641
|
+
const candidate = await verifyCandidate(pathCandidate, platform);
|
|
1642
|
+
if (!candidate) continue;
|
|
1643
|
+
sawCandidate = true;
|
|
1644
|
+
const versionResult = await run(candidate.command, [...candidate.prefixArgs, "--version"], { timeoutMs: PROCESS_TIMEOUT_MS, cwd: home });
|
|
1645
|
+
const version = versionResult.code === 0 ? parseVersion(id, `${versionResult.stdout}
|
|
1646
|
+
${versionResult.stderr}`) : void 0;
|
|
1647
|
+
const help = await run(candidate.command, [...candidate.prefixArgs, "mcp", "--help"], { timeoutMs: PROCESS_TIMEOUT_MS, cwd: home });
|
|
1648
|
+
if (help.code === 0 && /\badd\b/i.test(help.stdout + help.stderr) && /\bremove\b/i.test(help.stdout + help.stderr))
|
|
1649
|
+
return { candidate, installation: version ? "INSTALLED" : "INSTALLED_VERSION_UNKNOWN", ...version ? { version } : {} };
|
|
1650
|
+
if (!version || help.timedOut || versionResult.timedOut) sawDiscoveryError = true;
|
|
1651
|
+
}
|
|
1470
1652
|
return {
|
|
1471
|
-
|
|
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
|
|
1653
|
+
installation: sawDiscoveryError ? "DISCOVERY_ERROR" : sawCandidate ? "UNSUPPORTED_VERSION" : "NOT_INSTALLED"
|
|
1478
1654
|
};
|
|
1479
1655
|
}
|
|
1480
|
-
function
|
|
1481
|
-
|
|
1656
|
+
function parseMcpEntry(agent, output) {
|
|
1657
|
+
const command = output.match(/^\s*(?:Command|command):\s*(.+)$/im)?.[1]?.trim();
|
|
1658
|
+
const args2 = output.match(/^\s*(?:Args|args):\s*(.*)$/im)?.[1]?.trim();
|
|
1659
|
+
if (!command || args2 === void 0) return void 0;
|
|
1660
|
+
const scopeText = output.match(/^\s*Scope:\s*(.+)$/im)?.[1]?.trim().toLowerCase() ?? "user";
|
|
1661
|
+
const scope = scopeText.match(/^(user|project|local)\b/)?.[1] ?? "user";
|
|
1662
|
+
const legacyNpm = /(?:^|\s)(?:npx(?:\.cmd)?\s+(?:(?:--yes|-y)\s+)?(?:gazesight|@gazesight\/mcp-server)|gazesight(?:@\d+\.\d+\.\d+)?\s+mcp)(?:\s|$)/i.test(`${command} ${args2}`) || /[\\/]_npx[\\/]/i.test(`${command} ${args2}`);
|
|
1663
|
+
const knownOwner = legacyNpm || /(?:^|[\\/])dist[\\/]cli[\\/]index\.js(?:\s|$)/i.test(args2) && /\bmcp\b/i.test(args2);
|
|
1664
|
+
const oldCli = args2.match(/^(.*?[\\/]dist[\\/]cli[\\/]index\.js)\s+mcp(?:\s+--config\s+(.+?))?(?:\s+(--desktop-adapter))?$/i);
|
|
1665
|
+
const oldNpm = args2.match(/^(--yes\s+)?(gazesight(?:@\d+\.\d+\.\d+)?|@gazesight\/mcp-server)(?:\s+--yes)?\s+mcp(?:\s+--config\s+(.+))?$/i);
|
|
1666
|
+
const argsVector = oldCli ? [oldCli[1], "mcp", ...oldCli[2] ? ["--config", oldCli[2]] : [], ...oldCli[3] ? [oldCli[3]] : []] : oldNpm ? [...oldNpm[1] ? ["--yes"] : [], oldNpm[2], "mcp", ...oldNpm[3] ? ["--config", oldNpm[3]] : []] : /(?:^|[\\/])gazesight(?:-mcp)?(?:\.cmd|\.exe)?$/i.test(command) && /^mcp\b/i.test(args2) ? ["mcp", ...args2.match(/^mcp\s+--config\s+(.+)$/i)?.[1] ? ["--config", args2.match(/^mcp\s+--config\s+(.+)$/i)[1]] : []] : args2.split(/\s+/).filter(Boolean);
|
|
1667
|
+
return { command, args: args2, argsVector, scope: agent === "claude" ? scope : "user", legacyNpm, knownOwner };
|
|
1482
1668
|
}
|
|
1483
|
-
function
|
|
1484
|
-
|
|
1485
|
-
|
|
1486
|
-
|
|
1669
|
+
async function getEntry(agent, binary, run) {
|
|
1670
|
+
const result = await run(binary.command, [...binary.prefixArgs, "mcp", "get", "gazesight"], { timeoutMs: PROCESS_TIMEOUT_MS });
|
|
1671
|
+
const output = `${result.stdout}
|
|
1672
|
+
${result.stderr}`;
|
|
1673
|
+
const headers2 = output.match(/(?:^|\n)\s*gazesight\s*:?\s*(?=\r?\n|$)/gi) ?? [];
|
|
1674
|
+
const entry = result.code === 0 && headers2.length <= 1 ? parseMcpEntry(agent, output) : void 0;
|
|
1675
|
+
const named = headers2.length > 0;
|
|
1676
|
+
return { entry, named, code: result.code, output };
|
|
1487
1677
|
}
|
|
1488
|
-
function
|
|
1489
|
-
|
|
1678
|
+
function cliArgs(agent, operation, launch, scope) {
|
|
1679
|
+
if (operation === "remove") return agent === "claude" ? ["mcp", "remove", "gazesight", "-s", scope ?? "user"] : ["mcp", "remove", "gazesight"];
|
|
1680
|
+
const spec = launch;
|
|
1681
|
+
return agent === "claude" ? ["mcp", "add", "--transport", "stdio", "-s", scope ?? "user", "gazesight", "--", spec.command, ...spec.args] : ["mcp", "add", "gazesight", "--", spec.command, ...spec.args];
|
|
1490
1682
|
}
|
|
1491
|
-
function
|
|
1492
|
-
const
|
|
1493
|
-
|
|
1494
|
-
|
|
1495
|
-
|
|
1496
|
-
|
|
1497
|
-
|
|
1498
|
-
|
|
1499
|
-
|
|
1500
|
-
|
|
1501
|
-
|
|
1502
|
-
|
|
1503
|
-
|
|
1504
|
-
|
|
1505
|
-
|
|
1683
|
+
function sameLaunch(entry, launch) {
|
|
1684
|
+
const norm = (value) => {
|
|
1685
|
+
return value.trim().replace(/^['"]|['"]$/g, "").replaceAll("\\", "/").toLowerCase();
|
|
1686
|
+
};
|
|
1687
|
+
return norm(entry.command) === norm(launch.command) && norm(entry.args) === norm(launch.args.join(" "));
|
|
1688
|
+
}
|
|
1689
|
+
async function verifyCurrent(agent, binary, launch, run) {
|
|
1690
|
+
const result = await getEntry(agent, binary, run);
|
|
1691
|
+
return result.entry && result.entry.knownOwner && sameLaunch(result.entry, launch);
|
|
1692
|
+
}
|
|
1693
|
+
function safeCliFailure(result) {
|
|
1694
|
+
if (result.timedOut) return "TIMEOUT";
|
|
1695
|
+
const value = `${result.stderr}
|
|
1696
|
+
${result.stdout}`;
|
|
1697
|
+
if (/eacces|eperm|access denied|permission denied/i.test(value)) return "PERMISSION";
|
|
1698
|
+
if (/scope/i.test(value)) return "SCOPE";
|
|
1699
|
+
if (/managed|read.only|policy/i.test(value)) return "MANAGED";
|
|
1700
|
+
if (/not found|not configured|no (?:mcp )?server|does not exist/i.test(value)) return "ENTRY";
|
|
1701
|
+
if (result.code === null) return "SPAWN";
|
|
1702
|
+
return "OTHER";
|
|
1703
|
+
}
|
|
1704
|
+
async function reconcileMcpEntry(id, binary, launch, action, run = DEFAULT_SPAWN, cwd) {
|
|
1705
|
+
const invoke = (args2) => run(binary.command, [...binary.prefixArgs, ...args2], { timeoutMs: PROCESS_TIMEOUT_MS, cwd });
|
|
1706
|
+
const before = await getEntry(id, binary, (command, args2, options) => run(command, args2, { ...options, cwd }));
|
|
1707
|
+
if (before.named && !before.entry) return { ok: false, state: "conflict", message: "AGENT_ENTRY_AMBIGUOUS" };
|
|
1708
|
+
if (before.entry && !before.entry.knownOwner) return { ok: false, state: "conflict", message: "AGENT_ENTRY_AMBIGUOUS" };
|
|
1709
|
+
if (action === "configure" && before.entry) {
|
|
1710
|
+
return sameLaunch(before.entry, launch) ? { ok: true, state: "identical", message: "Already configured and verified." } : { ok: false, state: "outdated", message: "AGENT_ALREADY_CONFIGURED" };
|
|
1711
|
+
}
|
|
1712
|
+
if (action === "repair" && !before.entry)
|
|
1713
|
+
return { ok: false, state: "failed", message: "AGENT_NOT_CONFIGURED" };
|
|
1714
|
+
if (action === "repair" && before.entry && sameLaunch(before.entry, launch))
|
|
1715
|
+
return { ok: true, state: "identical", message: "Already configured and verified." };
|
|
1716
|
+
if (action === "remove" && !before.entry)
|
|
1717
|
+
return { ok: false, state: "failed", message: "AGENT_NOT_CONFIGURED" };
|
|
1718
|
+
const old = before.entry;
|
|
1719
|
+
const safeRun = (command, args2, options) => run(command, args2, { ...options, cwd });
|
|
1720
|
+
if (old) {
|
|
1721
|
+
const removed = await invoke(cliArgs(id, "remove", void 0, old.scope));
|
|
1722
|
+
if (removed.code !== 0)
|
|
1723
|
+
return {
|
|
1724
|
+
ok: false,
|
|
1725
|
+
state: "failed",
|
|
1726
|
+
message: `AGENT_REMOVE_FAILED_${safeCliFailure(removed)}`
|
|
1727
|
+
};
|
|
1728
|
+
const after = await getEntry(id, binary, safeRun);
|
|
1729
|
+
if (after.entry || after.named) {
|
|
1730
|
+
await invoke(cliArgs(id, "add", { command: old.command, args: old.argsVector, version: "legacy" }, old.scope));
|
|
1731
|
+
return { ok: false, state: "failed", message: "AGENT_REMOVE_VERIFY_FAILED" };
|
|
1732
|
+
}
|
|
1733
|
+
if (action === "remove") return { ok: true, state: "removed", message: "Removed and verified." };
|
|
1734
|
+
}
|
|
1735
|
+
const added = await invoke(cliArgs(id, "add", launch));
|
|
1736
|
+
if (added.code === 0 && await verifyCurrent(id, binary, launch, safeRun))
|
|
1737
|
+
return { ok: true, state: old ? "updated" : "configured", message: old ? "Repaired and verified." : "Configured and verified." };
|
|
1738
|
+
if (old) {
|
|
1739
|
+
const maybe = await getEntry(id, binary, safeRun);
|
|
1740
|
+
if (maybe.entry?.knownOwner) await invoke(cliArgs(id, "remove", void 0, maybe.entry.scope));
|
|
1741
|
+
await invoke(cliArgs(id, "add", { command: old.command, args: old.argsVector, version: "legacy" }, old.scope));
|
|
1506
1742
|
}
|
|
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
1743
|
return {
|
|
1565
1744
|
ok: false,
|
|
1566
1745
|
state: "failed",
|
|
1567
|
-
message:
|
|
1568
|
-
|
|
1569
|
-
|
|
1746
|
+
message: added.timedOut ? "AGENT_OPERATION_TIMEOUT" : "AGENT_CONFIGURE_VERIFY_FAILED"
|
|
1747
|
+
};
|
|
1748
|
+
}
|
|
1749
|
+
|
|
1750
|
+
// apps/cli/src/agent-config.ts
|
|
1751
|
+
import { homedir as homedir2 } from "node:os";
|
|
1752
|
+
function safeFailure(result) {
|
|
1753
|
+
const value = result.stderr || result.stdout || "unknown error";
|
|
1754
|
+
return value.trim().split(/\r?\n/)[0].slice(0, 180);
|
|
1755
|
+
}
|
|
1756
|
+
async function configureAgent(agent, binaryPath, proposed, allowUpdate, spawn) {
|
|
1757
|
+
const run = async (command, args2) => {
|
|
1758
|
+
if (!spawn) return runAgentCommand(command, args2);
|
|
1759
|
+
const result2 = spawn(command, args2);
|
|
1760
|
+
return {
|
|
1761
|
+
code: result2.status,
|
|
1762
|
+
stdout: result2.stdout,
|
|
1763
|
+
stderr: result2.stderr,
|
|
1764
|
+
timedOut: false
|
|
1765
|
+
};
|
|
1766
|
+
};
|
|
1767
|
+
const binary = typeof binaryPath === "string" ? { command: binaryPath, prefixArgs: [], source: "cli setup" } : binaryPath;
|
|
1768
|
+
const result = await reconcileMcpEntry(
|
|
1769
|
+
agent,
|
|
1770
|
+
binary,
|
|
1771
|
+
proposed,
|
|
1772
|
+
allowUpdate ? "repair" : "configure",
|
|
1773
|
+
run,
|
|
1774
|
+
homedir2()
|
|
1775
|
+
);
|
|
1776
|
+
const states = ["configured", "identical", "outdated", "updated", "conflict", "failed"];
|
|
1777
|
+
const state = states.includes(result.state) ? result.state : "failed";
|
|
1778
|
+
return {
|
|
1779
|
+
ok: result.ok,
|
|
1780
|
+
state,
|
|
1781
|
+
message: result.ok ? result.message : `${result.message}${result.message.startsWith("AGENT_") ? "" : ` \u2014 ${safeFailure({ stdout: "", stderr: result.message })}`}`
|
|
1570
1782
|
};
|
|
1571
1783
|
}
|
|
1572
1784
|
|
|
@@ -1580,14 +1792,6 @@ function option(name) {
|
|
|
1580
1792
|
throw new Error("Missing option value.");
|
|
1581
1793
|
return args[i + 1];
|
|
1582
1794
|
}
|
|
1583
|
-
function detectOnPath(binary) {
|
|
1584
|
-
for (const directory of (process.env.PATH ?? "").split(delimiter))
|
|
1585
|
-
for (const name of [binary, `${binary}.exe`, `${binary}.cmd`]) {
|
|
1586
|
-
const candidate = join2(directory, name);
|
|
1587
|
-
if (existsSync2(candidate)) return candidate;
|
|
1588
|
-
}
|
|
1589
|
-
return void 0;
|
|
1590
|
-
}
|
|
1591
1795
|
async function promptClientChoice() {
|
|
1592
1796
|
if (!process.stdin.isTTY) return ["claude", "codex"];
|
|
1593
1797
|
const rl = createInterface2({ input: process.stdin, output: process.stdout });
|
|
@@ -1671,17 +1875,19 @@ Agent guidance: ${config.agentGuidance.enabled ? "Enabled" : "Disabled"}${config
|
|
|
1671
1875
|
args: serverArgs.slice(1),
|
|
1672
1876
|
version: VERSION
|
|
1673
1877
|
};
|
|
1878
|
+
const available = /* @__PURE__ */ new Set();
|
|
1674
1879
|
for (const agent of chosen) {
|
|
1675
1880
|
const label = agent === "claude" ? "Claude Code" : "Codex";
|
|
1676
|
-
const
|
|
1677
|
-
if (!
|
|
1881
|
+
const binary = await findAgentBinary(agent, { home: process.env.USERPROFILE ?? process.env.HOME, env: process.env });
|
|
1882
|
+
if (!binary.candidate) {
|
|
1678
1883
|
console.log(`${label}: not detected on PATH \u2014 configure manually below.`);
|
|
1679
1884
|
continue;
|
|
1680
1885
|
}
|
|
1681
|
-
|
|
1886
|
+
available.add(agent);
|
|
1887
|
+
const result2 = await configureAgent(agent, binary.candidate, proposed, args.includes("--update"));
|
|
1682
1888
|
console.log(`${label}: ${result2.ok ? "PASS" : "FAIL"} \u2014 ${result2.message}`);
|
|
1683
1889
|
}
|
|
1684
|
-
const undetected = explicit ? chosen.filter((agent) => !
|
|
1890
|
+
const undetected = explicit ? chosen.filter((agent) => !available.has(agent)) : ["claude", "codex"];
|
|
1685
1891
|
if (undetected.length) {
|
|
1686
1892
|
const server = { command: process.execPath, args: serverArgs.slice(1) };
|
|
1687
1893
|
if (undetected.includes("claude")) {
|
|
@@ -1701,8 +1907,8 @@ args = ${JSON.stringify(server.args)}`
|
|
|
1701
1907
|
}
|
|
1702
1908
|
console.log(
|
|
1703
1909
|
`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
|
|
1705
|
-
Chrome
|
|
1910
|
+
Firefox: install or update the signed extension from the official listing \u2014 https://addons.mozilla.org/addon/6a2319d4fa544731884d/.
|
|
1911
|
+
Chrome: install or update the public extension from the Chrome Web Store \u2014 https://chromewebstore.google.com/detail/gazesight/hikhblabkhbaoilcffenldphdkjdmphc.
|
|
1706
1912
|
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")}.
|
|
1707
1913
|
Manual JSON import remains available under Advanced settings.`
|
|
1708
1914
|
);
|
|
@@ -1724,9 +1930,19 @@ Manual JSON import remains available under Advanced settings.`
|
|
|
1724
1930
|
if (args[0] === "doctor" && rows.some((row) => !row.pass))
|
|
1725
1931
|
process.exitCode = 1;
|
|
1726
1932
|
}
|
|
1727
|
-
} catch {
|
|
1728
|
-
|
|
1729
|
-
|
|
1730
|
-
|
|
1731
|
-
|
|
1933
|
+
} catch (error) {
|
|
1934
|
+
const message = error instanceof Error ? error.message : "";
|
|
1935
|
+
if (args[0] === "pair" && [
|
|
1936
|
+
"Pairing administration unavailable.",
|
|
1937
|
+
"The pairing request expired or was already used.",
|
|
1938
|
+
"The approved credential was not delivered; keep the extension open and retry pairing."
|
|
1939
|
+
].includes(message)) {
|
|
1940
|
+
console.error(`Pairing failed \u2014 ${message}`);
|
|
1941
|
+
process.exitCode = 1;
|
|
1942
|
+
} else {
|
|
1943
|
+
console.error(
|
|
1944
|
+
"FAIL \u2014 Check the command/options, Node >=20, installed package, private configuration and port 1024\u201365535. Run gazesight --help. Secrets are redacted."
|
|
1945
|
+
);
|
|
1946
|
+
process.exitCode = 1;
|
|
1947
|
+
}
|
|
1732
1948
|
}
|