gazesight 0.5.0 → 0.5.1
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/cli/index.js +339 -164
- package/dist/firefox/background.js +1 -1
- package/dist/gazesight-firefox.xpi +0 -0
- package/dist/mcp/index.js +2360 -1390
- package/package.json +140 -129
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.1";
|
|
105
106
|
|
|
106
107
|
// apps/cli/src/doctor.ts
|
|
107
108
|
import { z as z2 } from "zod";
|
|
@@ -1247,6 +1248,9 @@ var Bridge = class {
|
|
|
1247
1248
|
server;
|
|
1248
1249
|
http;
|
|
1249
1250
|
listenError;
|
|
1251
|
+
closePromise;
|
|
1252
|
+
mcpClientCount = 0;
|
|
1253
|
+
desktopIpcReady = false;
|
|
1250
1254
|
pairing;
|
|
1251
1255
|
// Resolves the connection currently backing the global active target, or
|
|
1252
1256
|
// undefined if no browser is active (nothing selected yet, or the
|
|
@@ -1309,6 +1313,8 @@ var Bridge = class {
|
|
|
1309
1313
|
})
|
|
1310
1314
|
);
|
|
1311
1315
|
return {
|
|
1316
|
+
mcpClients: this.mcpClientCount,
|
|
1317
|
+
desktopIpcReady: this.desktopIpcReady,
|
|
1312
1318
|
connected: this.connected,
|
|
1313
1319
|
paired: this.connected,
|
|
1314
1320
|
authorizedPairings: this.pairing.listPairings().filter((item) => !item.revokedAt).length,
|
|
@@ -1321,22 +1327,47 @@ var Bridge = class {
|
|
|
1321
1327
|
activeTarget: active ? { browser: active.browser, url: active.state.url } : null
|
|
1322
1328
|
};
|
|
1323
1329
|
}
|
|
1330
|
+
setMcpClientCount(count) {
|
|
1331
|
+
this.mcpClientCount = Math.max(0, Math.min(32, Math.floor(count)));
|
|
1332
|
+
}
|
|
1333
|
+
setDesktopIpcReady(ready) {
|
|
1334
|
+
this.desktopIpcReady = ready;
|
|
1335
|
+
}
|
|
1324
1336
|
async ready() {
|
|
1325
1337
|
if (this.listenError) throw this.listenError;
|
|
1326
1338
|
if (this.server.address()) return;
|
|
1327
1339
|
await new Promise((resolve3, reject) => {
|
|
1328
|
-
|
|
1329
|
-
|
|
1340
|
+
const cleanup = () => {
|
|
1341
|
+
this.server.off("listening", onListening);
|
|
1342
|
+
this.server.off("error", onError);
|
|
1343
|
+
this.http.off("error", onError);
|
|
1344
|
+
};
|
|
1345
|
+
const onListening = () => {
|
|
1346
|
+
cleanup();
|
|
1347
|
+
resolve3();
|
|
1348
|
+
};
|
|
1349
|
+
const onError = () => {
|
|
1350
|
+
cleanup();
|
|
1351
|
+
reject(this.listenError);
|
|
1352
|
+
};
|
|
1353
|
+
this.server.once("listening", onListening);
|
|
1330
1354
|
this.server.once("error", onError);
|
|
1331
1355
|
this.http.once("error", onError);
|
|
1332
1356
|
});
|
|
1333
1357
|
}
|
|
1334
1358
|
async close() {
|
|
1335
|
-
|
|
1336
|
-
|
|
1337
|
-
|
|
1338
|
-
|
|
1339
|
-
|
|
1359
|
+
if (this.closePromise) return this.closePromise;
|
|
1360
|
+
this.closePromise = (async () => {
|
|
1361
|
+
clearInterval(this.heartbeat);
|
|
1362
|
+
for (const wiring of this.wiring.values()) wiring.router.disconnect();
|
|
1363
|
+
this.wiring.clear();
|
|
1364
|
+
for (const client of this.server.clients) client.terminate();
|
|
1365
|
+
if (this.http.listening)
|
|
1366
|
+
await new Promise((resolve3) => this.http.close(() => resolve3()));
|
|
1367
|
+
else if (this.server.address())
|
|
1368
|
+
await new Promise((resolve3) => this.server.close(() => resolve3()));
|
|
1369
|
+
})();
|
|
1370
|
+
return this.closePromise;
|
|
1340
1371
|
}
|
|
1341
1372
|
};
|
|
1342
1373
|
|
|
@@ -1414,159 +1445,309 @@ async function pairingFlow(config, configPath, revoke) {
|
|
|
1414
1445
|
}
|
|
1415
1446
|
}
|
|
1416
1447
|
|
|
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;
|
|
1448
|
+
// apps/agent-integration/src/index.ts
|
|
1449
|
+
import { spawn as nodeSpawn } from "node:child_process";
|
|
1450
|
+
import { constants as fsConstants, existsSync, readFileSync as readFileSync2 } from "node:fs";
|
|
1451
|
+
import { access, realpath, stat } from "node:fs/promises";
|
|
1452
|
+
import { delimiter, join, posix as posix2, resolve, win32 as win322 } from "node:path";
|
|
1453
|
+
var PROCESS_TIMEOUT_MS = 12e3;
|
|
1454
|
+
var PROCESS_OUTPUT_BYTES = 16 * 1024;
|
|
1455
|
+
function createBoundedRunner(spawnProcess = nodeSpawn) {
|
|
1456
|
+
return (command, args2, options = {}) => runBounded(command, args2, options, spawnProcess);
|
|
1436
1457
|
}
|
|
1437
|
-
|
|
1438
|
-
|
|
1439
|
-
|
|
1458
|
+
var DEFAULT_SPAWN = createBoundedRunner();
|
|
1459
|
+
var runAgentCommand = DEFAULT_SPAWN;
|
|
1460
|
+
function runBounded(command, args2, options = {}, spawnProcess = nodeSpawn) {
|
|
1461
|
+
return new Promise((resolveResult) => {
|
|
1462
|
+
let child;
|
|
1463
|
+
try {
|
|
1464
|
+
child = spawnProcess(command, [...args2], {
|
|
1465
|
+
shell: false,
|
|
1466
|
+
windowsHide: true,
|
|
1467
|
+
cwd: options.cwd,
|
|
1468
|
+
env: options.env ?? process.env,
|
|
1469
|
+
stdio: ["ignore", "pipe", "pipe"]
|
|
1470
|
+
});
|
|
1471
|
+
} catch {
|
|
1472
|
+
resolveResult({ code: null, stdout: "", stderr: "", timedOut: false });
|
|
1473
|
+
return;
|
|
1474
|
+
}
|
|
1475
|
+
let stdout = "";
|
|
1476
|
+
let stderr = "";
|
|
1477
|
+
let timedOut = false;
|
|
1478
|
+
const append = (current, chunk) => {
|
|
1479
|
+
const value = current + String(chunk);
|
|
1480
|
+
return Buffer.byteLength(value) <= PROCESS_OUTPUT_BYTES ? value : Buffer.from(value).subarray(0, PROCESS_OUTPUT_BYTES).toString();
|
|
1481
|
+
};
|
|
1482
|
+
child.stdout?.on("data", (chunk) => {
|
|
1483
|
+
stdout = append(stdout, chunk);
|
|
1484
|
+
});
|
|
1485
|
+
child.stderr?.on("data", (chunk) => {
|
|
1486
|
+
stderr = append(stderr, chunk);
|
|
1487
|
+
});
|
|
1488
|
+
const timer = setTimeout(() => {
|
|
1489
|
+
timedOut = true;
|
|
1490
|
+
child.kill();
|
|
1491
|
+
}, options.timeoutMs ?? PROCESS_TIMEOUT_MS);
|
|
1492
|
+
child.once("error", () => {
|
|
1493
|
+
clearTimeout(timer);
|
|
1494
|
+
resolveResult({ code: null, stdout, stderr, timedOut });
|
|
1495
|
+
});
|
|
1496
|
+
child.once("close", (code) => {
|
|
1497
|
+
clearTimeout(timer);
|
|
1498
|
+
resolveResult({ code, stdout, stderr, timedOut });
|
|
1499
|
+
});
|
|
1500
|
+
});
|
|
1440
1501
|
}
|
|
1441
|
-
function
|
|
1442
|
-
const
|
|
1443
|
-
|
|
1444
|
-
|
|
1445
|
-
|
|
1502
|
+
function pathCandidates(agent, platform, env, home) {
|
|
1503
|
+
const name = agent === "claude" ? "claude" : "codex";
|
|
1504
|
+
const pathJoin = (platform === "win32" ? win322 : posix2).join;
|
|
1505
|
+
const native = [];
|
|
1506
|
+
const packageScripts = [];
|
|
1507
|
+
if (platform === "win32") {
|
|
1508
|
+
const packageManagerBins = [env.PNPM_HOME, env.NPM_CONFIG_PREFIX].filter((value) => Boolean(value)).flatMap((root2) => [pathJoin(root2, `${name}.exe`), pathJoin(root2, `${name}.cmd`)]);
|
|
1509
|
+
if (agent === "claude") {
|
|
1510
|
+
native.push(
|
|
1511
|
+
pathJoin(home, ".local", "bin", "claude.exe"),
|
|
1512
|
+
pathJoin(env.LOCALAPPDATA ?? pathJoin(home, "AppData", "Local"), "Programs", "Claude", "claude.exe"),
|
|
1513
|
+
pathJoin(env.ProgramFiles ?? "C:\\Program Files", "Claude", "claude.exe")
|
|
1514
|
+
);
|
|
1515
|
+
} else {
|
|
1516
|
+
native.push(
|
|
1517
|
+
pathJoin(env.LOCALAPPDATA ?? pathJoin(home, "AppData", "Local"), "Programs", "OpenAI", "Codex", "bin", "codex.exe")
|
|
1518
|
+
);
|
|
1519
|
+
}
|
|
1520
|
+
const npm = pathJoin(env.APPDATA ?? pathJoin(home, "AppData", "Roaming"), "npm", `${name}.cmd`);
|
|
1521
|
+
const modules = [
|
|
1522
|
+
pathJoin(env.APPDATA ?? pathJoin(home, "AppData", "Roaming"), "npm", "node_modules"),
|
|
1523
|
+
...env.NPM_CONFIG_PREFIX ? [pathJoin(env.NPM_CONFIG_PREFIX, "node_modules")] : [],
|
|
1524
|
+
...env.PNPM_HOME ? [pathJoin(env.PNPM_HOME, "global", "5", "node_modules"), pathJoin(env.PNPM_HOME, "node_modules")] : [],
|
|
1525
|
+
pathJoin(home, "AppData", "Local", "pnpm", "global", "5", "node_modules")
|
|
1526
|
+
];
|
|
1527
|
+
const packageName = agent === "claude" ? "@anthropic-ai/claude-code" : "@openai/codex";
|
|
1528
|
+
const nodeCandidates = [
|
|
1529
|
+
env.ProgramFiles ? pathJoin(env.ProgramFiles, "nodejs", "node.exe") : "C:\\Program Files\\nodejs\\node.exe",
|
|
1530
|
+
...env.LOCALAPPDATA ? [pathJoin(env.LOCALAPPDATA, "Programs", "nodejs", "node.exe")] : [],
|
|
1531
|
+
...(env.PATH ?? env.Path ?? "").split(delimiter).filter(Boolean).map((directory) => pathJoin(directory, "node.exe"))
|
|
1532
|
+
];
|
|
1533
|
+
for (const modulesRoot of modules) {
|
|
1534
|
+
const root2 = pathJoin(modulesRoot, packageName);
|
|
1535
|
+
try {
|
|
1536
|
+
const metadata = JSON.parse(readFileSync2(pathJoin(root2, "package.json"), "utf8"));
|
|
1537
|
+
const bin = typeof metadata.bin === "string" ? metadata.bin : metadata.bin && typeof metadata.bin === "object" ? metadata.bin[name] : void 0;
|
|
1538
|
+
if (typeof bin !== "string" || bin.startsWith("/") || bin.split(/[\\/]/).includes("..")) continue;
|
|
1539
|
+
const script = pathJoin(root2, bin);
|
|
1540
|
+
for (const node of nodeCandidates)
|
|
1541
|
+
packageScripts.push({ command: node, prefixArgs: [script], source: "verified npm package" });
|
|
1542
|
+
} catch {
|
|
1543
|
+
}
|
|
1544
|
+
}
|
|
1545
|
+
native.push(npm, ...packageManagerBins);
|
|
1546
|
+
} else {
|
|
1547
|
+
native.push(
|
|
1548
|
+
pathJoin(home, ".local", "bin", name),
|
|
1549
|
+
pathJoin(home, ".npm-global", "bin", name),
|
|
1550
|
+
pathJoin(home, ".local", "share", "pnpm", name),
|
|
1551
|
+
...env.PNPM_HOME ? [pathJoin(env.PNPM_HOME, name)] : [],
|
|
1552
|
+
...env.NPM_CONFIG_PREFIX ? [pathJoin(env.NPM_CONFIG_PREFIX, "bin", name)] : []
|
|
1553
|
+
);
|
|
1554
|
+
if (platform === "darwin")
|
|
1555
|
+
native.push(
|
|
1556
|
+
pathJoin(home, "Library", "pnpm", name),
|
|
1557
|
+
"/opt/homebrew/bin/" + name,
|
|
1558
|
+
"/usr/local/bin/" + name
|
|
1559
|
+
);
|
|
1560
|
+
else if (platform === "linux")
|
|
1561
|
+
native.push("/usr/bin/" + name, "/usr/local/bin/" + name);
|
|
1562
|
+
}
|
|
1563
|
+
const fromPath = (env.PATH ?? env.Path ?? "").split(delimiter).filter(Boolean).flatMap(
|
|
1564
|
+
(directory) => platform === "win32" ? [pathJoin(directory, `${name}.exe`), pathJoin(directory, `${name}.cmd`)] : [pathJoin(directory, name)]
|
|
1565
|
+
);
|
|
1566
|
+
const seen = /* @__PURE__ */ new Set();
|
|
1567
|
+
const direct = [...native, ...fromPath].filter((path) => {
|
|
1568
|
+
const key = platform === "win32" ? path.toLowerCase() : path;
|
|
1569
|
+
if (seen.has(key)) return false;
|
|
1570
|
+
seen.add(key);
|
|
1571
|
+
return true;
|
|
1572
|
+
}).map((command) => ({ command, prefixArgs: [], source: "candidate" }));
|
|
1573
|
+
return [...packageScripts, ...direct];
|
|
1446
1574
|
}
|
|
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;
|
|
1575
|
+
async function verifyCandidate(candidate, platform) {
|
|
1455
1576
|
try {
|
|
1456
|
-
const
|
|
1457
|
-
|
|
1577
|
+
const canonical = await realpath(candidate.command);
|
|
1578
|
+
if (canonical.includes("..")) return void 0;
|
|
1579
|
+
const info = await stat(canonical);
|
|
1580
|
+
if (!info.isFile()) return void 0;
|
|
1581
|
+
for (const argument of candidate.prefixArgs) {
|
|
1582
|
+
if (!argument.toLowerCase().endsWith(".js")) continue;
|
|
1583
|
+
const script = await realpath(argument);
|
|
1584
|
+
if (!(await stat(script)).isFile()) return void 0;
|
|
1585
|
+
if (platform === "win32" && !script.toLowerCase().endsWith(".js")) return void 0;
|
|
1586
|
+
}
|
|
1587
|
+
if (platform !== "win32") {
|
|
1588
|
+
await access(canonical, fsConstants.X_OK);
|
|
1589
|
+
if ((info.mode & 73) === 0) return void 0;
|
|
1590
|
+
} else if (!/\.(exe|cmd)$/i.test(canonical)) return void 0;
|
|
1591
|
+
return { ...candidate, command: canonical };
|
|
1458
1592
|
} catch {
|
|
1459
1593
|
return void 0;
|
|
1460
1594
|
}
|
|
1461
1595
|
}
|
|
1462
|
-
function
|
|
1463
|
-
const
|
|
1464
|
-
|
|
1465
|
-
if (
|
|
1466
|
-
|
|
1467
|
-
|
|
1468
|
-
|
|
1469
|
-
const
|
|
1596
|
+
function parseVersion(agent, output) {
|
|
1597
|
+
const value = output.match(/\b(\d+\.\d+\.\d+(?:[-+][0-9A-Za-z.-]+)?)\b/)?.[1];
|
|
1598
|
+
if (!value) return void 0;
|
|
1599
|
+
if (agent === "claude" && Number(value.split(".")[0]) < 2) return void 0;
|
|
1600
|
+
return value;
|
|
1601
|
+
}
|
|
1602
|
+
async function findAgentBinary(id, options = {}) {
|
|
1603
|
+
const platform = options.platform ?? process.platform;
|
|
1604
|
+
const env = options.env ?? process.env;
|
|
1605
|
+
const home = options.home ?? env.USERPROFILE ?? env.HOME ?? "";
|
|
1606
|
+
const run = options.spawn ?? DEFAULT_SPAWN;
|
|
1607
|
+
let sawCandidate = false;
|
|
1608
|
+
let sawDiscoveryError = false;
|
|
1609
|
+
for (const pathCandidate of pathCandidates(id, platform, env, home)) {
|
|
1610
|
+
const candidate = await verifyCandidate(pathCandidate, platform);
|
|
1611
|
+
if (!candidate) continue;
|
|
1612
|
+
sawCandidate = true;
|
|
1613
|
+
const versionResult = await run(candidate.command, [...candidate.prefixArgs, "--version"], { timeoutMs: PROCESS_TIMEOUT_MS, cwd: home });
|
|
1614
|
+
const version = versionResult.code === 0 ? parseVersion(id, `${versionResult.stdout}
|
|
1615
|
+
${versionResult.stderr}`) : void 0;
|
|
1616
|
+
const help = await run(candidate.command, [...candidate.prefixArgs, "mcp", "--help"], { timeoutMs: PROCESS_TIMEOUT_MS, cwd: home });
|
|
1617
|
+
if (help.code === 0 && /\badd\b/i.test(help.stdout + help.stderr) && /\bremove\b/i.test(help.stdout + help.stderr))
|
|
1618
|
+
return { candidate, installation: version ? "INSTALLED" : "INSTALLED_VERSION_UNKNOWN", ...version ? { version } : {} };
|
|
1619
|
+
if (!version || help.timedOut || versionResult.timedOut) sawDiscoveryError = true;
|
|
1620
|
+
}
|
|
1470
1621
|
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
|
|
1622
|
+
installation: sawDiscoveryError ? "DISCOVERY_ERROR" : sawCandidate ? "UNSUPPORTED_VERSION" : "NOT_INSTALLED"
|
|
1478
1623
|
};
|
|
1479
1624
|
}
|
|
1480
|
-
function
|
|
1481
|
-
|
|
1625
|
+
function parseMcpEntry(agent, output) {
|
|
1626
|
+
const command = output.match(/^\s*(?:Command|command):\s*(.+)$/im)?.[1]?.trim();
|
|
1627
|
+
const args2 = output.match(/^\s*(?:Args|args):\s*(.*)$/im)?.[1]?.trim();
|
|
1628
|
+
if (!command || args2 === void 0) return void 0;
|
|
1629
|
+
const scopeText = output.match(/^\s*Scope:\s*(.+)$/im)?.[1]?.trim().toLowerCase() ?? "user";
|
|
1630
|
+
const scope = scopeText.match(/^(user|project|local)\b/)?.[1] ?? "user";
|
|
1631
|
+
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}`);
|
|
1632
|
+
const knownOwner = legacyNpm || /(?:^|[\\/])dist[\\/]cli[\\/]index\.js(?:\s|$)/i.test(args2) && /\bmcp\b/i.test(args2);
|
|
1633
|
+
const oldCli = args2.match(/^(.*?[\\/]dist[\\/]cli[\\/]index\.js)\s+mcp(?:\s+--config\s+(.+?))?(?:\s+(--desktop-adapter))?$/i);
|
|
1634
|
+
const oldNpm = args2.match(/^(--yes\s+)?(gazesight(?:@\d+\.\d+\.\d+)?|@gazesight\/mcp-server)(?:\s+--yes)?\s+mcp(?:\s+--config\s+(.+))?$/i);
|
|
1635
|
+
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);
|
|
1636
|
+
return { command, args: args2, argsVector, scope: agent === "claude" ? scope : "user", legacyNpm, knownOwner };
|
|
1482
1637
|
}
|
|
1483
|
-
function
|
|
1484
|
-
|
|
1485
|
-
|
|
1486
|
-
|
|
1638
|
+
async function getEntry(agent, binary, run) {
|
|
1639
|
+
const result = await run(binary.command, [...binary.prefixArgs, "mcp", "get", "gazesight"], { timeoutMs: PROCESS_TIMEOUT_MS });
|
|
1640
|
+
const output = `${result.stdout}
|
|
1641
|
+
${result.stderr}`;
|
|
1642
|
+
const headers2 = output.match(/(?:^|\n)\s*gazesight\s*:?\s*(?=\r?\n|$)/gi) ?? [];
|
|
1643
|
+
const entry = result.code === 0 && headers2.length <= 1 ? parseMcpEntry(agent, output) : void 0;
|
|
1644
|
+
const named = headers2.length > 0;
|
|
1645
|
+
return { entry, named, code: result.code, output };
|
|
1487
1646
|
}
|
|
1488
|
-
function
|
|
1489
|
-
|
|
1647
|
+
function cliArgs(agent, operation, launch, scope) {
|
|
1648
|
+
if (operation === "remove") return agent === "claude" ? ["mcp", "remove", "gazesight", "-s", scope ?? "user"] : ["mcp", "remove", "gazesight"];
|
|
1649
|
+
const spec = launch;
|
|
1650
|
+
return agent === "claude" ? ["mcp", "add", "--transport", "stdio", "-s", scope ?? "user", "gazesight", "--", spec.command, ...spec.args] : ["mcp", "add", "gazesight", "--", spec.command, ...spec.args];
|
|
1490
1651
|
}
|
|
1491
|
-
function
|
|
1492
|
-
const
|
|
1493
|
-
|
|
1494
|
-
|
|
1495
|
-
|
|
1496
|
-
|
|
1497
|
-
|
|
1498
|
-
|
|
1499
|
-
|
|
1500
|
-
|
|
1501
|
-
|
|
1502
|
-
|
|
1503
|
-
|
|
1504
|
-
|
|
1505
|
-
|
|
1652
|
+
function sameLaunch(entry, launch) {
|
|
1653
|
+
const norm = (value) => {
|
|
1654
|
+
return value.trim().replace(/^['"]|['"]$/g, "").replaceAll("\\", "/").toLowerCase();
|
|
1655
|
+
};
|
|
1656
|
+
return norm(entry.command) === norm(launch.command) && norm(entry.args) === norm(launch.args.join(" "));
|
|
1657
|
+
}
|
|
1658
|
+
async function verifyCurrent(agent, binary, launch, run) {
|
|
1659
|
+
const result = await getEntry(agent, binary, run);
|
|
1660
|
+
return result.entry && result.entry.knownOwner && sameLaunch(result.entry, launch);
|
|
1661
|
+
}
|
|
1662
|
+
function safeCliFailure(result) {
|
|
1663
|
+
if (result.timedOut) return "TIMEOUT";
|
|
1664
|
+
const value = `${result.stderr}
|
|
1665
|
+
${result.stdout}`;
|
|
1666
|
+
if (/eacces|eperm|access denied|permission denied/i.test(value)) return "PERMISSION";
|
|
1667
|
+
if (/scope/i.test(value)) return "SCOPE";
|
|
1668
|
+
if (/managed|read.only|policy/i.test(value)) return "MANAGED";
|
|
1669
|
+
if (/not found|not configured|no (?:mcp )?server|does not exist/i.test(value)) return "ENTRY";
|
|
1670
|
+
if (result.code === null) return "SPAWN";
|
|
1671
|
+
return "OTHER";
|
|
1672
|
+
}
|
|
1673
|
+
async function reconcileMcpEntry(id, binary, launch, action, run = DEFAULT_SPAWN, cwd) {
|
|
1674
|
+
const invoke = (args2) => run(binary.command, [...binary.prefixArgs, ...args2], { timeoutMs: PROCESS_TIMEOUT_MS, cwd });
|
|
1675
|
+
const before = await getEntry(id, binary, (command, args2, options) => run(command, args2, { ...options, cwd }));
|
|
1676
|
+
if (before.named && !before.entry) return { ok: false, state: "conflict", message: "AGENT_ENTRY_AMBIGUOUS" };
|
|
1677
|
+
if (before.entry && !before.entry.knownOwner) return { ok: false, state: "conflict", message: "AGENT_ENTRY_AMBIGUOUS" };
|
|
1678
|
+
if (action === "configure" && before.entry) {
|
|
1679
|
+
return sameLaunch(before.entry, launch) ? { ok: true, state: "identical", message: "Already configured and verified." } : { ok: false, state: "outdated", message: "AGENT_ALREADY_CONFIGURED" };
|
|
1680
|
+
}
|
|
1681
|
+
if (action === "repair" && !before.entry)
|
|
1682
|
+
return { ok: false, state: "failed", message: "AGENT_NOT_CONFIGURED" };
|
|
1683
|
+
if (action === "repair" && before.entry && sameLaunch(before.entry, launch))
|
|
1684
|
+
return { ok: true, state: "identical", message: "Already configured and verified." };
|
|
1685
|
+
if (action === "remove" && !before.entry)
|
|
1686
|
+
return { ok: false, state: "failed", message: "AGENT_NOT_CONFIGURED" };
|
|
1687
|
+
const old = before.entry;
|
|
1688
|
+
const safeRun = (command, args2, options) => run(command, args2, { ...options, cwd });
|
|
1689
|
+
if (old) {
|
|
1690
|
+
const removed = await invoke(cliArgs(id, "remove", void 0, old.scope));
|
|
1691
|
+
if (removed.code !== 0)
|
|
1692
|
+
return {
|
|
1693
|
+
ok: false,
|
|
1694
|
+
state: "failed",
|
|
1695
|
+
message: `AGENT_REMOVE_FAILED_${safeCliFailure(removed)}`
|
|
1696
|
+
};
|
|
1697
|
+
const after = await getEntry(id, binary, safeRun);
|
|
1698
|
+
if (after.entry || after.named) {
|
|
1699
|
+
await invoke(cliArgs(id, "add", { command: old.command, args: old.argsVector, version: "legacy" }, old.scope));
|
|
1700
|
+
return { ok: false, state: "failed", message: "AGENT_REMOVE_VERIFY_FAILED" };
|
|
1701
|
+
}
|
|
1702
|
+
if (action === "remove") return { ok: true, state: "removed", message: "Removed and verified." };
|
|
1703
|
+
}
|
|
1704
|
+
const added = await invoke(cliArgs(id, "add", launch));
|
|
1705
|
+
if (added.code === 0 && await verifyCurrent(id, binary, launch, safeRun))
|
|
1706
|
+
return { ok: true, state: old ? "updated" : "configured", message: old ? "Repaired and verified." : "Configured and verified." };
|
|
1707
|
+
if (old) {
|
|
1708
|
+
const maybe = await getEntry(id, binary, safeRun);
|
|
1709
|
+
if (maybe.entry?.knownOwner) await invoke(cliArgs(id, "remove", void 0, maybe.entry.scope));
|
|
1710
|
+
await invoke(cliArgs(id, "add", { command: old.command, args: old.argsVector, version: "legacy" }, old.scope));
|
|
1506
1711
|
}
|
|
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
1712
|
return {
|
|
1565
1713
|
ok: false,
|
|
1566
1714
|
state: "failed",
|
|
1567
|
-
message:
|
|
1568
|
-
|
|
1569
|
-
|
|
1715
|
+
message: added.timedOut ? "AGENT_OPERATION_TIMEOUT" : "AGENT_CONFIGURE_VERIFY_FAILED"
|
|
1716
|
+
};
|
|
1717
|
+
}
|
|
1718
|
+
|
|
1719
|
+
// apps/cli/src/agent-config.ts
|
|
1720
|
+
import { homedir as homedir2 } from "node:os";
|
|
1721
|
+
function safeFailure(result) {
|
|
1722
|
+
const value = result.stderr || result.stdout || "unknown error";
|
|
1723
|
+
return value.trim().split(/\r?\n/)[0].slice(0, 180);
|
|
1724
|
+
}
|
|
1725
|
+
async function configureAgent(agent, binaryPath, proposed, allowUpdate, spawn) {
|
|
1726
|
+
const run = async (command, args2) => {
|
|
1727
|
+
if (!spawn) return runAgentCommand(command, args2);
|
|
1728
|
+
const result2 = spawn(command, args2);
|
|
1729
|
+
return {
|
|
1730
|
+
code: result2.status,
|
|
1731
|
+
stdout: result2.stdout,
|
|
1732
|
+
stderr: result2.stderr,
|
|
1733
|
+
timedOut: false
|
|
1734
|
+
};
|
|
1735
|
+
};
|
|
1736
|
+
const binary = typeof binaryPath === "string" ? { command: binaryPath, prefixArgs: [], source: "cli setup" } : binaryPath;
|
|
1737
|
+
const result = await reconcileMcpEntry(
|
|
1738
|
+
agent,
|
|
1739
|
+
binary,
|
|
1740
|
+
proposed,
|
|
1741
|
+
allowUpdate ? "repair" : "configure",
|
|
1742
|
+
run,
|
|
1743
|
+
homedir2()
|
|
1744
|
+
);
|
|
1745
|
+
const states = ["configured", "identical", "outdated", "updated", "conflict", "failed"];
|
|
1746
|
+
const state = states.includes(result.state) ? result.state : "failed";
|
|
1747
|
+
return {
|
|
1748
|
+
ok: result.ok,
|
|
1749
|
+
state,
|
|
1750
|
+
message: result.ok ? result.message : `${result.message}${result.message.startsWith("AGENT_") ? "" : ` \u2014 ${safeFailure({ stdout: "", stderr: result.message })}`}`
|
|
1570
1751
|
};
|
|
1571
1752
|
}
|
|
1572
1753
|
|
|
@@ -1580,14 +1761,6 @@ function option(name) {
|
|
|
1580
1761
|
throw new Error("Missing option value.");
|
|
1581
1762
|
return args[i + 1];
|
|
1582
1763
|
}
|
|
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
1764
|
async function promptClientChoice() {
|
|
1592
1765
|
if (!process.stdin.isTTY) return ["claude", "codex"];
|
|
1593
1766
|
const rl = createInterface2({ input: process.stdin, output: process.stdout });
|
|
@@ -1671,17 +1844,19 @@ Agent guidance: ${config.agentGuidance.enabled ? "Enabled" : "Disabled"}${config
|
|
|
1671
1844
|
args: serverArgs.slice(1),
|
|
1672
1845
|
version: VERSION
|
|
1673
1846
|
};
|
|
1847
|
+
const available = /* @__PURE__ */ new Set();
|
|
1674
1848
|
for (const agent of chosen) {
|
|
1675
1849
|
const label = agent === "claude" ? "Claude Code" : "Codex";
|
|
1676
|
-
const
|
|
1677
|
-
if (!
|
|
1850
|
+
const binary = await findAgentBinary(agent, { home: process.env.USERPROFILE ?? process.env.HOME, env: process.env });
|
|
1851
|
+
if (!binary.candidate) {
|
|
1678
1852
|
console.log(`${label}: not detected on PATH \u2014 configure manually below.`);
|
|
1679
1853
|
continue;
|
|
1680
1854
|
}
|
|
1681
|
-
|
|
1855
|
+
available.add(agent);
|
|
1856
|
+
const result2 = await configureAgent(agent, binary.candidate, proposed, args.includes("--update"));
|
|
1682
1857
|
console.log(`${label}: ${result2.ok ? "PASS" : "FAIL"} \u2014 ${result2.message}`);
|
|
1683
1858
|
}
|
|
1684
|
-
const undetected = explicit ? chosen.filter((agent) => !
|
|
1859
|
+
const undetected = explicit ? chosen.filter((agent) => !available.has(agent)) : ["claude", "codex"];
|
|
1685
1860
|
if (undetected.length) {
|
|
1686
1861
|
const server = { command: process.execPath, args: serverArgs.slice(1) };
|
|
1687
1862
|
if (undetected.includes("claude")) {
|
|
@@ -1701,8 +1876,8 @@ args = ${JSON.stringify(server.args)}`
|
|
|
1701
1876
|
}
|
|
1702
1877
|
console.log(
|
|
1703
1878
|
`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
|
|
1879
|
+
Firefox: install or update the signed extension from the official listing \u2014 https://addons.mozilla.org/addon/6a2319d4fa544731884d/.
|
|
1880
|
+
Chrome: install or update the public extension from the Chrome Web Store \u2014 https://chromewebstore.google.com/detail/gazesight/hikhblabkhbaoilcffenldphdkjdmphc.
|
|
1706
1881
|
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
1882
|
Manual JSON import remains available under Advanced settings.`
|
|
1708
1883
|
);
|