lensmcp 1.16.27 → 1.16.28

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.
@@ -1295,9 +1295,9 @@ function makeUnixTmpDir() {
1295
1295
  function makeWin32TmpDir() {
1296
1296
  const winTmpPath = process.env.TEMP || process.env.TMP || (process.env.SystemRoot || process.env.windir) + "\\temp";
1297
1297
  const randomNumber = Math.floor(Math.random() * 9e7 + 1e7);
1298
- const tmpdir = join(winTmpPath, "lighthouse." + randomNumber);
1299
- mkdirSync(tmpdir, { recursive: true });
1300
- return tmpdir;
1298
+ const tmpdir2 = join(winTmpPath, "lighthouse." + randomNumber);
1299
+ mkdirSync(tmpdir2, { recursive: true });
1300
+ return tmpdir2;
1301
1301
  }
1302
1302
  var import_is_wsl, LauncherError, ChromePathNotSetError, InvalidUserDataDirectoryError, UnsupportedPlatformError, ChromeNotInstalledError;
1303
1303
  var init_utils = __esm({
@@ -1958,8 +1958,8 @@ var init_chrome_launcher = __esm({
1958
1958
  this.fs.closeSync(this.errFile);
1959
1959
  delete this.errFile;
1960
1960
  }
1961
- const rmSync = this.fs.rmSync || this.fs.rmdirSync;
1962
- rmSync(this.userDataDir, { recursive: true, force: true, maxRetries: 10 });
1961
+ const rmSync2 = this.fs.rmSync || this.fs.rmdirSync;
1962
+ rmSync2(this.userDataDir, { recursive: true, force: true, maxRetries: 10 });
1963
1963
  }
1964
1964
  };
1965
1965
  }
@@ -33206,15 +33206,15 @@ var require_api = __commonJS({
33206
33206
  }
33207
33207
  });
33208
33208
  }
33209
- function addCommand(chrome, domainName, command) {
33209
+ function addCommand(chrome2, domainName, command) {
33210
33210
  const commandName = `${domainName}.${command.name}`;
33211
33211
  const handler = (params, sessionId, callback) => {
33212
- return chrome.send(commandName, params, sessionId, callback);
33212
+ return chrome2.send(commandName, params, sessionId, callback);
33213
33213
  };
33214
33214
  decorate(handler, "command", command);
33215
- chrome[commandName] = chrome[domainName][command.name] = handler;
33215
+ chrome2[commandName] = chrome2[domainName][command.name] = handler;
33216
33216
  }
33217
- function addEvent(chrome, domainName, event) {
33217
+ function addEvent(chrome2, domainName, event) {
33218
33218
  const eventName = `${domainName}.${event.name}`;
33219
33219
  const handler = (sessionId, handler2) => {
33220
33220
  if (typeof sessionId === "function") {
@@ -33223,22 +33223,22 @@ var require_api = __commonJS({
33223
33223
  }
33224
33224
  const rawEventName = sessionId ? `${eventName}.${sessionId}` : eventName;
33225
33225
  if (typeof handler2 === "function") {
33226
- chrome.on(rawEventName, handler2);
33227
- return () => chrome.removeListener(rawEventName, handler2);
33226
+ chrome2.on(rawEventName, handler2);
33227
+ return () => chrome2.removeListener(rawEventName, handler2);
33228
33228
  } else {
33229
33229
  return new Promise((fulfill, reject) => {
33230
- chrome.once(rawEventName, fulfill);
33230
+ chrome2.once(rawEventName, fulfill);
33231
33231
  });
33232
33232
  }
33233
33233
  };
33234
33234
  decorate(handler, "event", event);
33235
- chrome[eventName] = chrome[domainName][event.name] = handler;
33235
+ chrome2[eventName] = chrome2[domainName][event.name] = handler;
33236
33236
  }
33237
- function addType(chrome, domainName, type) {
33237
+ function addType(chrome2, domainName, type) {
33238
33238
  const typeName = `${domainName}.${type.id}`;
33239
33239
  const help = {};
33240
33240
  decorate(help, "type", type);
33241
- chrome[typeName] = chrome[domainName][type.id] = help;
33241
+ chrome2[typeName] = chrome2[domainName][type.id] = help;
33242
33242
  }
33243
33243
  function prepare(object, protocol) {
33244
33244
  object.protocol = protocol;
@@ -33560,6 +33560,490 @@ var require_chrome_remote_interface = __commonJS({
33560
33560
  // libs/browser-capture/src/capture-runner.ts
33561
33561
  import { appendFileSync, readFileSync, renameSync, statSync } from "node:fs";
33562
33562
 
33563
+ // libs/core/dist/lib/ulid.js
33564
+ var ALPHABET = "0123456789ABCDEFGHJKMNPQRSTVWXYZ";
33565
+ var RANDOM_LEN = 16;
33566
+ function encodeTime(now, len) {
33567
+ let out = "";
33568
+ let n = now;
33569
+ for (let i = len - 1; i >= 0; i--) {
33570
+ const mod = n % 32;
33571
+ out = ALPHABET[mod] + out;
33572
+ n = (n - mod) / 32;
33573
+ }
33574
+ return out;
33575
+ }
33576
+ function encodeRandom(len) {
33577
+ const bytes = new Uint8Array(len);
33578
+ globalThis.crypto.getRandomValues(bytes);
33579
+ let out = "";
33580
+ for (let i = 0; i < len; i++) {
33581
+ out += ALPHABET[(bytes[i] ?? 0) & 31];
33582
+ }
33583
+ return out;
33584
+ }
33585
+ function ulid(now = Date.now()) {
33586
+ return encodeTime(now, 10) + encodeRandom(RANDOM_LEN);
33587
+ }
33588
+
33589
+ // libs/core/dist/lib/fingerprint.js
33590
+ var FNV_OFFSET_BASIS = 2166136261;
33591
+ var FNV_PRIME = 16777619;
33592
+ function fnv1a32(input) {
33593
+ let h = FNV_OFFSET_BASIS;
33594
+ for (let i = 0; i < input.length; i++) {
33595
+ h ^= input.charCodeAt(i);
33596
+ h = Math.imul(h, FNV_PRIME);
33597
+ }
33598
+ return (h >>> 0).toString(16).padStart(8, "0");
33599
+ }
33600
+ function fingerprint(input) {
33601
+ const parts = [input.kind, input.identity, input.file ?? "", input.detail ?? ""];
33602
+ return `${input.kind}:${fnv1a32(parts.join("|"))}`;
33603
+ }
33604
+
33605
+ // libs/browser-sidecar/dist/lib/page-controller.js
33606
+ var PageController = class {
33607
+ constructor(args) {
33608
+ this.url = "";
33609
+ this.pending = /* @__PURE__ */ new Map();
33610
+ this.pageId = args.pageId;
33611
+ this.cdp = args.cdp;
33612
+ this.emit = args.emit;
33613
+ this.sessionId = args.sessionId;
33614
+ }
33615
+ async attach() {
33616
+ await Promise.all([
33617
+ this.cdp.Runtime.enable(),
33618
+ this.cdp.Network.enable(),
33619
+ this.cdp.Page.enable()
33620
+ ]);
33621
+ this.cdp.Runtime.consoleAPICalled((params) => {
33622
+ const text = params.args.map((a) => a.value !== void 0 ? String(a.value) : a.description ?? "?").join(" ");
33623
+ const lvl = params.type === "error" || params.type === "warning" ? params.type : "log";
33624
+ const severity = lvl === "error" ? "error" : lvl === "warning" ? "warning" : "info";
33625
+ this.emit({
33626
+ id: ulid(),
33627
+ sessionId: this.sessionId,
33628
+ timestamp: Math.round(params.timestamp),
33629
+ source: "chrome",
33630
+ category: "runtime",
33631
+ severity,
33632
+ context: { sessionId: this.sessionId, tabId: this.pageId, url: this.url },
33633
+ fingerprint: fingerprint({
33634
+ kind: "runtime",
33635
+ identity: `console:${lvl}:${text.slice(0, 80)}`
33636
+ }),
33637
+ title: `console.${lvl}`,
33638
+ message: text,
33639
+ raw: params
33640
+ });
33641
+ });
33642
+ this.cdp.Runtime.exceptionThrown((params) => {
33643
+ const det = params.exceptionDetails;
33644
+ const desc = det.exception?.description ?? det.text;
33645
+ this.emit({
33646
+ id: ulid(),
33647
+ sessionId: this.sessionId,
33648
+ timestamp: Math.round(params.timestamp),
33649
+ source: "chrome",
33650
+ category: "runtime",
33651
+ severity: "error",
33652
+ context: { sessionId: this.sessionId, tabId: this.pageId, url: this.url },
33653
+ fingerprint: fingerprint({
33654
+ kind: "runtime",
33655
+ identity: `exception:${normaliseTopFrame(desc)}`,
33656
+ file: det.url
33657
+ }),
33658
+ title: det.text,
33659
+ message: desc,
33660
+ location: det.url ? { file: det.url, line: det.lineNumber, column: det.columnNumber } : void 0,
33661
+ raw: params
33662
+ });
33663
+ });
33664
+ this.cdp.Network.requestWillBeSent((p) => {
33665
+ this.pending.set(p.requestId, {
33666
+ url: p.request.url,
33667
+ method: p.request.method,
33668
+ startMs: Math.round(p.timestamp * 1e3)
33669
+ });
33670
+ });
33671
+ this.cdp.Network.responseReceived((p) => {
33672
+ const pend = this.pending.get(p.requestId);
33673
+ const durationMs = pend ? Math.round(p.timestamp * 1e3) - pend.startMs : void 0;
33674
+ const severity = p.response.status >= 400 ? "error" : "info";
33675
+ this.emit({
33676
+ id: ulid(),
33677
+ sessionId: this.sessionId,
33678
+ timestamp: Math.round(p.timestamp * 1e3),
33679
+ source: "chrome",
33680
+ category: "network",
33681
+ severity,
33682
+ context: { sessionId: this.sessionId, tabId: this.pageId, url: this.url },
33683
+ fingerprint: fingerprint({
33684
+ kind: "network",
33685
+ identity: `${pend?.method ?? "?"}:${p.response.url}:${p.response.status}`
33686
+ }),
33687
+ title: `${pend?.method ?? "?"} ${p.response.url} \u2192 ${p.response.status}`,
33688
+ message: durationMs !== void 0 ? `${durationMs}ms` : void 0,
33689
+ relatedUrls: [p.response.url],
33690
+ raw: p
33691
+ });
33692
+ });
33693
+ this.cdp.Network.loadingFailed((p) => {
33694
+ const pend = this.pending.get(p.requestId);
33695
+ this.pending.delete(p.requestId);
33696
+ this.emit({
33697
+ id: ulid(),
33698
+ sessionId: this.sessionId,
33699
+ timestamp: Math.round(p.timestamp * 1e3),
33700
+ source: "chrome",
33701
+ category: "network",
33702
+ severity: "error",
33703
+ context: { sessionId: this.sessionId, tabId: this.pageId, url: this.url },
33704
+ fingerprint: fingerprint({
33705
+ kind: "network",
33706
+ identity: `failed:${pend?.method ?? "?"}:${pend?.url ?? p.requestId}`
33707
+ }),
33708
+ title: `Request failed: ${pend?.url ?? p.requestId}`,
33709
+ message: p.errorText,
33710
+ relatedUrls: pend ? [pend.url] : void 0,
33711
+ raw: p
33712
+ });
33713
+ });
33714
+ this.cdp.Page.frameNavigated((p) => {
33715
+ if (!p.frame.name) {
33716
+ this.url = p.frame.url;
33717
+ }
33718
+ });
33719
+ }
33720
+ async dispose() {
33721
+ if (this.cdp.close) {
33722
+ try {
33723
+ await this.cdp.close();
33724
+ } catch {
33725
+ }
33726
+ }
33727
+ }
33728
+ };
33729
+ function normaliseTopFrame(desc) {
33730
+ if (!desc)
33731
+ return "unknown";
33732
+ const m = desc.match(/at\s+([\w$.<>]+)/);
33733
+ return m?.[1] ?? "unknown";
33734
+ }
33735
+
33736
+ // libs/browser-sidecar/dist/lib/chrome-launch.js
33737
+ import { execFileSync as execFileSync2 } from "node:child_process";
33738
+ import { existsSync, mkdtempSync, readdirSync, rmSync } from "node:fs";
33739
+ import { homedir as homedir2, tmpdir } from "node:os";
33740
+ import { join as join2 } from "node:path";
33741
+ var CAPTURE_DIR_MARKER = "lensmcp-capture-";
33742
+ function createManagedUserDataDir() {
33743
+ return mkdtempSync(join2(tmpdir(), `${CAPTURE_DIR_MARKER}${process.pid}-`));
33744
+ }
33745
+ function removeUserDataDir(dir) {
33746
+ try {
33747
+ rmSync(dir, { recursive: true, force: true });
33748
+ } catch {
33749
+ }
33750
+ }
33751
+ async function resolveChromePath2(log) {
33752
+ const override = process.env["LENSMCP_CHROME_PATH"] ?? process.env["CHROME_PATH"];
33753
+ if (override) {
33754
+ if (existsSync(override))
33755
+ return override;
33756
+ log?.(`[browser-capture] LENSMCP_CHROME_PATH/CHROME_PATH points at ${override}, which does not exist \u2014 auto-detecting instead`);
33757
+ }
33758
+ const isolated = chromeForTestingFromPuppeteerCache() ?? firstExisting(NON_COLLIDING_INSTALLS);
33759
+ if (isolated)
33760
+ return isolated;
33761
+ try {
33762
+ const { Launcher: Launcher2 } = await Promise.resolve().then(() => (init_dist(), dist_exports));
33763
+ const system = Launcher2.getFirstInstallation?.();
33764
+ if (system) {
33765
+ if (process.platform === "darwin") {
33766
+ log?.("[browser-capture] capturing with the system Chrome \u2014 while capture runs, opening Chrome from the Dock may misbehave. Install an isolated binary (`npx @puppeteer/browsers install chrome@stable`) or set LENSMCP_CHROME_PATH to avoid this.");
33767
+ }
33768
+ return system;
33769
+ }
33770
+ } catch {
33771
+ }
33772
+ return void 0;
33773
+ }
33774
+ var NON_COLLIDING_INSTALLS = process.platform === "darwin" ? [
33775
+ "/Applications/Google Chrome for Testing.app/Contents/MacOS/Google Chrome for Testing",
33776
+ "/Applications/Chromium.app/Contents/MacOS/Chromium"
33777
+ ] : [];
33778
+ function chromeForTestingFromPuppeteerCache() {
33779
+ const cacheRoot = process.env["PUPPETEER_CACHE_DIR"] ?? join2(homedir2(), ".cache", "puppeteer");
33780
+ const chromeRoot = join2(cacheRoot, "chrome");
33781
+ let versions;
33782
+ try {
33783
+ versions = readdirSync(chromeRoot);
33784
+ } catch {
33785
+ return void 0;
33786
+ }
33787
+ versions.sort((a, b) => b.localeCompare(a, void 0, { numeric: true }));
33788
+ const layouts = [
33789
+ ["chrome-mac-arm64", "Google Chrome for Testing.app", "Contents", "MacOS", "Google Chrome for Testing"],
33790
+ ["chrome-mac-x64", "Google Chrome for Testing.app", "Contents", "MacOS", "Google Chrome for Testing"],
33791
+ ["chrome-linux64", "chrome"],
33792
+ ["chrome-win64", "chrome.exe"]
33793
+ ];
33794
+ for (const version of versions) {
33795
+ for (const parts of layouts) {
33796
+ const candidate = join2(chromeRoot, version, ...parts);
33797
+ if (existsSync(candidate))
33798
+ return candidate;
33799
+ }
33800
+ }
33801
+ return void 0;
33802
+ }
33803
+ function firstExisting(candidates) {
33804
+ return candidates.find((c) => existsSync(c));
33805
+ }
33806
+ function planOrphanReap(psLines, isOwnerAlive, selfPid) {
33807
+ const dirRe = new RegExp(`--user-data-dir=(\\S*${CAPTURE_DIR_MARKER}(\\d+)-\\S*)`);
33808
+ const orphans = [];
33809
+ for (const line of psLines) {
33810
+ const m = /^\s*(\d+)\s+(.*)$/.exec(line);
33811
+ if (!m)
33812
+ continue;
33813
+ const cmd = m[2];
33814
+ if (cmd.includes("--type="))
33815
+ continue;
33816
+ const dir = dirRe.exec(cmd);
33817
+ if (!dir)
33818
+ continue;
33819
+ const ownerPid = Number(dir[2]);
33820
+ if (ownerPid === selfPid || isOwnerAlive(ownerPid))
33821
+ continue;
33822
+ orphans.push({ pid: Number(m[1]), ownerPid, userDataDir: dir[1] });
33823
+ }
33824
+ return orphans;
33825
+ }
33826
+ function reapOrphanCaptureChrome(log) {
33827
+ if (process.platform === "win32")
33828
+ return 0;
33829
+ let out;
33830
+ try {
33831
+ out = execFileSync2("ps", ["ax", "-ww", "-o", "pid=,command="], {
33832
+ encoding: "utf8",
33833
+ timeout: 5e3,
33834
+ maxBuffer: 64 * 1024 * 1024
33835
+ });
33836
+ } catch {
33837
+ return 0;
33838
+ }
33839
+ let reaped = 0;
33840
+ for (const orphan of planOrphanReap(out.split("\n"), isAlive, process.pid)) {
33841
+ try {
33842
+ process.kill(orphan.pid, "SIGKILL");
33843
+ reaped += 1;
33844
+ log?.(`[browser-capture] reaped orphaned capture Chrome pid=${orphan.pid} (owner ${orphan.ownerPid} is gone)`);
33845
+ } catch {
33846
+ }
33847
+ removeUserDataDir(orphan.userDataDir);
33848
+ }
33849
+ return reaped;
33850
+ }
33851
+ function isAlive(pid) {
33852
+ try {
33853
+ process.kill(pid, 0);
33854
+ return true;
33855
+ } catch (err) {
33856
+ return err.code === "EPERM";
33857
+ }
33858
+ }
33859
+
33860
+ // libs/browser-sidecar/dist/lib/dom-snapshot.js
33861
+ var COMPUTED_STYLE_WHITELIST = [
33862
+ // box model
33863
+ "display",
33864
+ "position",
33865
+ "box-sizing",
33866
+ "margin-top",
33867
+ "margin-right",
33868
+ "margin-bottom",
33869
+ "margin-left",
33870
+ "padding-top",
33871
+ "padding-right",
33872
+ "padding-bottom",
33873
+ "padding-left",
33874
+ // typography
33875
+ "font-family",
33876
+ "font-size",
33877
+ "font-weight",
33878
+ "line-height",
33879
+ "letter-spacing",
33880
+ "text-align",
33881
+ // color + bg
33882
+ "color",
33883
+ "background-color",
33884
+ "opacity",
33885
+ "visibility",
33886
+ // borders
33887
+ "border-top-width",
33888
+ "border-right-width",
33889
+ "border-bottom-width",
33890
+ "border-left-width",
33891
+ "border-radius",
33892
+ // layout
33893
+ "flex-direction",
33894
+ "justify-content",
33895
+ "align-items",
33896
+ "gap",
33897
+ "row-gap",
33898
+ "column-gap",
33899
+ "grid-template-columns",
33900
+ "grid-template-rows",
33901
+ "overflow",
33902
+ "overflow-x",
33903
+ "overflow-y",
33904
+ // stacking
33905
+ "z-index",
33906
+ // motion
33907
+ "transform",
33908
+ "transition",
33909
+ "animation"
33910
+ ];
33911
+ async function captureDomSnapshot(cdp, opts = {}) {
33912
+ const computedStyles = (opts.computedStyles ?? COMPUTED_STYLE_WHITELIST).slice();
33913
+ const raw = await cdp.DOMSnapshot.captureSnapshot({
33914
+ computedStyles,
33915
+ includePaintOrder: opts.includePaintOrder ?? true,
33916
+ includeDOMRects: true,
33917
+ includeBlendedBackgroundColors: opts.includeBlendedBackgroundColors ?? false,
33918
+ includeTextColorOpacities: opts.includeTextColorOpacities ?? false
33919
+ });
33920
+ const ax = opts.mergeAccessibility && cdp.Accessibility ? await cdp.Accessibility.getFullAXTree().catch(() => ({ nodes: [] })) : { nodes: [] };
33921
+ const viewport = await readViewport(cdp);
33922
+ const snapshotId = opts.snapshotId ?? `dom-${Date.now()}`;
33923
+ return normalise(raw, ax, computedStyles, viewport, snapshotId);
33924
+ }
33925
+ async function readViewport(cdp) {
33926
+ const lm = cdp.Page?.getLayoutMetrics;
33927
+ if (!lm)
33928
+ return { x: 0, y: 0, width: 0, height: 0 };
33929
+ try {
33930
+ const r = await lm();
33931
+ const v = r.cssVisualViewport ?? r.layoutViewport;
33932
+ if (!v)
33933
+ return { x: 0, y: 0, width: 0, height: 0 };
33934
+ return { x: 0, y: 0, width: v.clientWidth, height: v.clientHeight };
33935
+ } catch {
33936
+ return { x: 0, y: 0, width: 0, height: 0 };
33937
+ }
33938
+ }
33939
+ function normalise(raw, ax, computedStyles, viewport, snapshotId) {
33940
+ const nodes = {};
33941
+ const nodeOrder = [];
33942
+ const document2 = raw.documents[0];
33943
+ if (!document2) {
33944
+ return {
33945
+ capturedAt: (/* @__PURE__ */ new Date()).toISOString(),
33946
+ viewport,
33947
+ snapshotId,
33948
+ nodes,
33949
+ nodeOrder
33950
+ };
33951
+ }
33952
+ const strings = raw.strings;
33953
+ const decode = (i) => i >= 0 ? strings[i] ?? "" : "";
33954
+ const nodeCount = document2.nodes.backendNodeId.length;
33955
+ const byIndex = new Array(nodeCount);
33956
+ for (let i = 0; i < nodeCount; i++) {
33957
+ const backendNodeId = document2.nodes.backendNodeId[i];
33958
+ if (backendNodeId === void 0)
33959
+ continue;
33960
+ const tag = decode(document2.nodes.nodeName[i] ?? -1).toLowerCase();
33961
+ const attributePairs = document2.nodes.attributes[i] ?? [];
33962
+ const attributes = {};
33963
+ for (let j = 0; j + 1 < attributePairs.length; j += 2) {
33964
+ const name = decode(attributePairs[j]);
33965
+ const value = decode(attributePairs[j + 1]);
33966
+ if (name)
33967
+ attributes[name] = value;
33968
+ }
33969
+ const parentIndex = document2.nodes.parentIndex?.[i] ?? -1;
33970
+ const parentBackend = parentIndex >= 0 ? document2.nodes.backendNodeId[parentIndex] : void 0;
33971
+ const text = decode(document2.nodes.nodeValue[i] ?? -1).trim() || void 0;
33972
+ byIndex[i] = {
33973
+ backendNodeId,
33974
+ parentBackendNodeId: parentBackend,
33975
+ tag,
33976
+ attributes,
33977
+ text,
33978
+ rect: { x: 0, y: 0, width: 0, height: 0 },
33979
+ computedStyle: {},
33980
+ agentComponent: attributes["data-agent-component"]
33981
+ };
33982
+ }
33983
+ const layout = document2.layout;
33984
+ for (let li = 0; li < layout.nodeIndex.length; li++) {
33985
+ const idx = layout.nodeIndex[li];
33986
+ if (idx === void 0)
33987
+ continue;
33988
+ const node = byIndex[idx];
33989
+ if (!node)
33990
+ continue;
33991
+ const bounds = layout.bounds[li];
33992
+ if (bounds && bounds.length === 4) {
33993
+ const [x, y, w, h] = bounds;
33994
+ node.rect = {
33995
+ x: x ?? 0,
33996
+ y: y ?? 0,
33997
+ width: Math.max(0, w ?? 0),
33998
+ height: Math.max(0, h ?? 0)
33999
+ };
34000
+ }
34001
+ const styleRow = layout.styles[li];
34002
+ if (styleRow) {
34003
+ for (let s = 0; s < styleRow.length && s < computedStyles.length; s++) {
34004
+ const styleName = computedStyles[s];
34005
+ if (!styleName)
34006
+ continue;
34007
+ const styleValue = decode(styleRow[s]);
34008
+ if (styleValue)
34009
+ node.computedStyle[styleName] = styleValue;
34010
+ }
34011
+ }
34012
+ if (layout.paintOrders) {
34013
+ const po = layout.paintOrders[li];
34014
+ if (po !== void 0)
34015
+ node.paintOrder = po;
34016
+ }
34017
+ }
34018
+ const axByBackend = /* @__PURE__ */ new Map();
34019
+ for (const axNode of ax.nodes) {
34020
+ if (axNode.backendDOMNodeId !== void 0) {
34021
+ axByBackend.set(axNode.backendDOMNodeId, axNode);
34022
+ }
34023
+ }
34024
+ for (let i = 0; i < byIndex.length; i++) {
34025
+ const node = byIndex[i];
34026
+ if (!node)
34027
+ continue;
34028
+ const axNode = axByBackend.get(node.backendNodeId);
34029
+ if (axNode) {
34030
+ if (axNode.role?.value)
34031
+ node.role = axNode.role.value;
34032
+ if (axNode.name?.value)
34033
+ node.ariaName = axNode.name.value;
34034
+ }
34035
+ nodes[node.backendNodeId] = node;
34036
+ nodeOrder.push(node.backendNodeId);
34037
+ }
34038
+ return {
34039
+ capturedAt: (/* @__PURE__ */ new Date()).toISOString(),
34040
+ viewport,
34041
+ snapshotId,
34042
+ nodes,
34043
+ nodeOrder
34044
+ };
34045
+ }
34046
+
33563
34047
  // libs/visual-engine/dist/lib/diff.js
33564
34048
  var LAYOUT_TOLERANCE_PX = 0.5;
33565
34049
  function diffSnapshots(prev, curr, options) {
@@ -34186,372 +34670,12 @@ function resolveTargets(snapshot, rule) {
34186
34670
  return [];
34187
34671
  }
34188
34672
 
34189
- // libs/core/dist/lib/ulid.js
34190
- var ALPHABET = "0123456789ABCDEFGHJKMNPQRSTVWXYZ";
34191
- var RANDOM_LEN = 16;
34192
- function encodeTime(now, len) {
34193
- let out = "";
34194
- let n = now;
34195
- for (let i = len - 1; i >= 0; i--) {
34196
- const mod = n % 32;
34197
- out = ALPHABET[mod] + out;
34198
- n = (n - mod) / 32;
34199
- }
34200
- return out;
34201
- }
34202
- function encodeRandom(len) {
34203
- const bytes = new Uint8Array(len);
34204
- globalThis.crypto.getRandomValues(bytes);
34205
- let out = "";
34206
- for (let i = 0; i < len; i++) {
34207
- out += ALPHABET[(bytes[i] ?? 0) & 31];
34208
- }
34209
- return out;
34210
- }
34211
- function ulid(now = Date.now()) {
34212
- return encodeTime(now, 10) + encodeRandom(RANDOM_LEN);
34213
- }
34214
-
34215
- // libs/core/dist/lib/fingerprint.js
34216
- var FNV_OFFSET_BASIS = 2166136261;
34217
- var FNV_PRIME = 16777619;
34218
- function fnv1a32(input) {
34219
- let h = FNV_OFFSET_BASIS;
34220
- for (let i = 0; i < input.length; i++) {
34221
- h ^= input.charCodeAt(i);
34222
- h = Math.imul(h, FNV_PRIME);
34223
- }
34224
- return (h >>> 0).toString(16).padStart(8, "0");
34225
- }
34226
- function fingerprint(input) {
34227
- const parts = [input.kind, input.identity, input.file ?? "", input.detail ?? ""];
34228
- return `${input.kind}:${fnv1a32(parts.join("|"))}`;
34229
- }
34230
-
34231
- // libs/browser-sidecar/dist/lib/page-controller.js
34232
- var PageController = class {
34233
- constructor(args) {
34234
- this.url = "";
34235
- this.pending = /* @__PURE__ */ new Map();
34236
- this.pageId = args.pageId;
34237
- this.cdp = args.cdp;
34238
- this.emit = args.emit;
34239
- this.sessionId = args.sessionId;
34240
- }
34241
- async attach() {
34242
- await Promise.all([
34243
- this.cdp.Runtime.enable(),
34244
- this.cdp.Network.enable(),
34245
- this.cdp.Page.enable()
34246
- ]);
34247
- this.cdp.Runtime.consoleAPICalled((params) => {
34248
- const text = params.args.map((a) => a.value !== void 0 ? String(a.value) : a.description ?? "?").join(" ");
34249
- const lvl = params.type === "error" || params.type === "warning" ? params.type : "log";
34250
- const severity = lvl === "error" ? "error" : lvl === "warning" ? "warning" : "info";
34251
- this.emit({
34252
- id: ulid(),
34253
- sessionId: this.sessionId,
34254
- timestamp: Math.round(params.timestamp),
34255
- source: "chrome",
34256
- category: "runtime",
34257
- severity,
34258
- context: { sessionId: this.sessionId, tabId: this.pageId, url: this.url },
34259
- fingerprint: fingerprint({
34260
- kind: "runtime",
34261
- identity: `console:${lvl}:${text.slice(0, 80)}`
34262
- }),
34263
- title: `console.${lvl}`,
34264
- message: text,
34265
- raw: params
34266
- });
34267
- });
34268
- this.cdp.Runtime.exceptionThrown((params) => {
34269
- const det = params.exceptionDetails;
34270
- const desc = det.exception?.description ?? det.text;
34271
- this.emit({
34272
- id: ulid(),
34273
- sessionId: this.sessionId,
34274
- timestamp: Math.round(params.timestamp),
34275
- source: "chrome",
34276
- category: "runtime",
34277
- severity: "error",
34278
- context: { sessionId: this.sessionId, tabId: this.pageId, url: this.url },
34279
- fingerprint: fingerprint({
34280
- kind: "runtime",
34281
- identity: `exception:${normaliseTopFrame(desc)}`,
34282
- file: det.url
34283
- }),
34284
- title: det.text,
34285
- message: desc,
34286
- location: det.url ? { file: det.url, line: det.lineNumber, column: det.columnNumber } : void 0,
34287
- raw: params
34288
- });
34289
- });
34290
- this.cdp.Network.requestWillBeSent((p) => {
34291
- this.pending.set(p.requestId, {
34292
- url: p.request.url,
34293
- method: p.request.method,
34294
- startMs: Math.round(p.timestamp * 1e3)
34295
- });
34296
- });
34297
- this.cdp.Network.responseReceived((p) => {
34298
- const pend = this.pending.get(p.requestId);
34299
- const durationMs = pend ? Math.round(p.timestamp * 1e3) - pend.startMs : void 0;
34300
- const severity = p.response.status >= 400 ? "error" : "info";
34301
- this.emit({
34302
- id: ulid(),
34303
- sessionId: this.sessionId,
34304
- timestamp: Math.round(p.timestamp * 1e3),
34305
- source: "chrome",
34306
- category: "network",
34307
- severity,
34308
- context: { sessionId: this.sessionId, tabId: this.pageId, url: this.url },
34309
- fingerprint: fingerprint({
34310
- kind: "network",
34311
- identity: `${pend?.method ?? "?"}:${p.response.url}:${p.response.status}`
34312
- }),
34313
- title: `${pend?.method ?? "?"} ${p.response.url} \u2192 ${p.response.status}`,
34314
- message: durationMs !== void 0 ? `${durationMs}ms` : void 0,
34315
- relatedUrls: [p.response.url],
34316
- raw: p
34317
- });
34318
- });
34319
- this.cdp.Network.loadingFailed((p) => {
34320
- const pend = this.pending.get(p.requestId);
34321
- this.pending.delete(p.requestId);
34322
- this.emit({
34323
- id: ulid(),
34324
- sessionId: this.sessionId,
34325
- timestamp: Math.round(p.timestamp * 1e3),
34326
- source: "chrome",
34327
- category: "network",
34328
- severity: "error",
34329
- context: { sessionId: this.sessionId, tabId: this.pageId, url: this.url },
34330
- fingerprint: fingerprint({
34331
- kind: "network",
34332
- identity: `failed:${pend?.method ?? "?"}:${pend?.url ?? p.requestId}`
34333
- }),
34334
- title: `Request failed: ${pend?.url ?? p.requestId}`,
34335
- message: p.errorText,
34336
- relatedUrls: pend ? [pend.url] : void 0,
34337
- raw: p
34338
- });
34339
- });
34340
- this.cdp.Page.frameNavigated((p) => {
34341
- if (!p.frame.name) {
34342
- this.url = p.frame.url;
34343
- }
34344
- });
34345
- }
34346
- async dispose() {
34347
- if (this.cdp.close) {
34348
- try {
34349
- await this.cdp.close();
34350
- } catch {
34351
- }
34352
- }
34353
- }
34354
- };
34355
- function normaliseTopFrame(desc) {
34356
- if (!desc)
34357
- return "unknown";
34358
- const m = desc.match(/at\s+([\w$.<>]+)/);
34359
- return m?.[1] ?? "unknown";
34360
- }
34361
-
34362
- // libs/browser-sidecar/dist/lib/dom-snapshot.js
34363
- var COMPUTED_STYLE_WHITELIST = [
34364
- // box model
34365
- "display",
34366
- "position",
34367
- "box-sizing",
34368
- "margin-top",
34369
- "margin-right",
34370
- "margin-bottom",
34371
- "margin-left",
34372
- "padding-top",
34373
- "padding-right",
34374
- "padding-bottom",
34375
- "padding-left",
34376
- // typography
34377
- "font-family",
34378
- "font-size",
34379
- "font-weight",
34380
- "line-height",
34381
- "letter-spacing",
34382
- "text-align",
34383
- // color + bg
34384
- "color",
34385
- "background-color",
34386
- "opacity",
34387
- "visibility",
34388
- // borders
34389
- "border-top-width",
34390
- "border-right-width",
34391
- "border-bottom-width",
34392
- "border-left-width",
34393
- "border-radius",
34394
- // layout
34395
- "flex-direction",
34396
- "justify-content",
34397
- "align-items",
34398
- "gap",
34399
- "row-gap",
34400
- "column-gap",
34401
- "grid-template-columns",
34402
- "grid-template-rows",
34403
- "overflow",
34404
- "overflow-x",
34405
- "overflow-y",
34406
- // stacking
34407
- "z-index",
34408
- // motion
34409
- "transform",
34410
- "transition",
34411
- "animation"
34412
- ];
34413
- async function captureDomSnapshot(cdp, opts = {}) {
34414
- const computedStyles = (opts.computedStyles ?? COMPUTED_STYLE_WHITELIST).slice();
34415
- const raw = await cdp.DOMSnapshot.captureSnapshot({
34416
- computedStyles,
34417
- includePaintOrder: opts.includePaintOrder ?? true,
34418
- includeDOMRects: true,
34419
- includeBlendedBackgroundColors: opts.includeBlendedBackgroundColors ?? false,
34420
- includeTextColorOpacities: opts.includeTextColorOpacities ?? false
34421
- });
34422
- const ax = opts.mergeAccessibility && cdp.Accessibility ? await cdp.Accessibility.getFullAXTree().catch(() => ({ nodes: [] })) : { nodes: [] };
34423
- const viewport = await readViewport(cdp);
34424
- const snapshotId = opts.snapshotId ?? `dom-${Date.now()}`;
34425
- return normalise(raw, ax, computedStyles, viewport, snapshotId);
34426
- }
34427
- async function readViewport(cdp) {
34428
- const lm = cdp.Page?.getLayoutMetrics;
34429
- if (!lm)
34430
- return { x: 0, y: 0, width: 0, height: 0 };
34431
- try {
34432
- const r = await lm();
34433
- const v = r.cssVisualViewport ?? r.layoutViewport;
34434
- if (!v)
34435
- return { x: 0, y: 0, width: 0, height: 0 };
34436
- return { x: 0, y: 0, width: v.clientWidth, height: v.clientHeight };
34437
- } catch {
34438
- return { x: 0, y: 0, width: 0, height: 0 };
34439
- }
34440
- }
34441
- function normalise(raw, ax, computedStyles, viewport, snapshotId) {
34442
- const nodes = {};
34443
- const nodeOrder = [];
34444
- const document2 = raw.documents[0];
34445
- if (!document2) {
34446
- return {
34447
- capturedAt: (/* @__PURE__ */ new Date()).toISOString(),
34448
- viewport,
34449
- snapshotId,
34450
- nodes,
34451
- nodeOrder
34452
- };
34453
- }
34454
- const strings = raw.strings;
34455
- const decode = (i) => i >= 0 ? strings[i] ?? "" : "";
34456
- const nodeCount = document2.nodes.backendNodeId.length;
34457
- const byIndex = new Array(nodeCount);
34458
- for (let i = 0; i < nodeCount; i++) {
34459
- const backendNodeId = document2.nodes.backendNodeId[i];
34460
- if (backendNodeId === void 0)
34461
- continue;
34462
- const tag = decode(document2.nodes.nodeName[i] ?? -1).toLowerCase();
34463
- const attributePairs = document2.nodes.attributes[i] ?? [];
34464
- const attributes = {};
34465
- for (let j = 0; j + 1 < attributePairs.length; j += 2) {
34466
- const name = decode(attributePairs[j]);
34467
- const value = decode(attributePairs[j + 1]);
34468
- if (name)
34469
- attributes[name] = value;
34470
- }
34471
- const parentIndex = document2.nodes.parentIndex?.[i] ?? -1;
34472
- const parentBackend = parentIndex >= 0 ? document2.nodes.backendNodeId[parentIndex] : void 0;
34473
- const text = decode(document2.nodes.nodeValue[i] ?? -1).trim() || void 0;
34474
- byIndex[i] = {
34475
- backendNodeId,
34476
- parentBackendNodeId: parentBackend,
34477
- tag,
34478
- attributes,
34479
- text,
34480
- rect: { x: 0, y: 0, width: 0, height: 0 },
34481
- computedStyle: {},
34482
- agentComponent: attributes["data-agent-component"]
34483
- };
34484
- }
34485
- const layout = document2.layout;
34486
- for (let li = 0; li < layout.nodeIndex.length; li++) {
34487
- const idx = layout.nodeIndex[li];
34488
- if (idx === void 0)
34489
- continue;
34490
- const node = byIndex[idx];
34491
- if (!node)
34492
- continue;
34493
- const bounds = layout.bounds[li];
34494
- if (bounds && bounds.length === 4) {
34495
- const [x, y, w, h] = bounds;
34496
- node.rect = {
34497
- x: x ?? 0,
34498
- y: y ?? 0,
34499
- width: Math.max(0, w ?? 0),
34500
- height: Math.max(0, h ?? 0)
34501
- };
34502
- }
34503
- const styleRow = layout.styles[li];
34504
- if (styleRow) {
34505
- for (let s = 0; s < styleRow.length && s < computedStyles.length; s++) {
34506
- const styleName = computedStyles[s];
34507
- if (!styleName)
34508
- continue;
34509
- const styleValue = decode(styleRow[s]);
34510
- if (styleValue)
34511
- node.computedStyle[styleName] = styleValue;
34512
- }
34513
- }
34514
- if (layout.paintOrders) {
34515
- const po = layout.paintOrders[li];
34516
- if (po !== void 0)
34517
- node.paintOrder = po;
34518
- }
34519
- }
34520
- const axByBackend = /* @__PURE__ */ new Map();
34521
- for (const axNode of ax.nodes) {
34522
- if (axNode.backendDOMNodeId !== void 0) {
34523
- axByBackend.set(axNode.backendDOMNodeId, axNode);
34524
- }
34525
- }
34526
- for (let i = 0; i < byIndex.length; i++) {
34527
- const node = byIndex[i];
34528
- if (!node)
34529
- continue;
34530
- const axNode = axByBackend.get(node.backendNodeId);
34531
- if (axNode) {
34532
- if (axNode.role?.value)
34533
- node.role = axNode.role.value;
34534
- if (axNode.name?.value)
34535
- node.ariaName = axNode.name.value;
34536
- }
34537
- nodes[node.backendNodeId] = node;
34538
- nodeOrder.push(node.backendNodeId);
34539
- }
34540
- return {
34541
- capturedAt: (/* @__PURE__ */ new Date()).toISOString(),
34542
- viewport,
34543
- snapshotId,
34544
- nodes,
34545
- nodeOrder
34546
- };
34547
- }
34548
-
34549
34673
  // libs/browser-capture/src/lib/capture.ts
34550
34674
  async function startBrowserCapture(opts) {
34551
34675
  const log = opts.log ?? (() => void 0);
34552
34676
  const sessionId = opts.sessionId ?? ulid();
34553
34677
  const chromeLauncher = await Promise.resolve().then(() => (init_dist(), dist_exports));
34554
- const chromePath = safe(() => chromeLauncher.Launcher.getFirstInstallation?.());
34678
+ const chromePath = await resolveChromePath2(log);
34555
34679
  if (!chromePath) {
34556
34680
  log("[browser-capture] no Chrome installed \u2014 skipping live browser capture");
34557
34681
  return void 0;
@@ -34561,8 +34685,11 @@ async function startBrowserCapture(opts) {
34561
34685
  log(`[browser-capture] dev URL ${opts.url} not reachable \u2014 skipping`);
34562
34686
  return void 0;
34563
34687
  }
34564
- const chrome = await chromeLauncher.launch({
34688
+ const userDataDir = createManagedUserDataDir();
34689
+ const chrome2 = await chromeLauncher.launch({
34565
34690
  startingUrl: "about:blank",
34691
+ chromePath,
34692
+ userDataDir,
34566
34693
  chromeFlags: [
34567
34694
  ...opts.headless === false ? [] : ["--headless=new"],
34568
34695
  "--disable-gpu",
@@ -34570,12 +34697,20 @@ async function startBrowserCapture(opts) {
34570
34697
  "--disable-dev-shm-usage"
34571
34698
  ]
34572
34699
  });
34700
+ const killSync = () => {
34701
+ try {
34702
+ process.kill(chrome2.pid, "SIGKILL");
34703
+ } catch {
34704
+ }
34705
+ removeUserDataDir(userDataDir);
34706
+ };
34707
+ opts.onChrome?.({ pid: chrome2.pid, killSync });
34573
34708
  const CDP = (await Promise.resolve().then(() => __toESM(require_chrome_remote_interface(), 1))).default;
34574
34709
  const attachClient = async (navigate) => {
34575
- const targets = await CDP.List({ port: chrome.port });
34710
+ const targets = await CDP.List({ port: chrome2.port });
34576
34711
  const pageTarget = targets.find((t) => t.type === "page");
34577
34712
  if (!pageTarget) throw new Error("no page target");
34578
- const client = await CDP({ port: chrome.port, target: pageTarget });
34713
+ const client = await CDP({ port: chrome2.port, target: pageTarget });
34579
34714
  const controller = new PageController({
34580
34715
  pageId: pageTarget.id,
34581
34716
  cdp: client,
@@ -34593,7 +34728,8 @@ async function startBrowserCapture(opts) {
34593
34728
  try {
34594
34729
  cdp = await attachClient(true);
34595
34730
  } catch {
34596
- await chrome.kill();
34731
+ await chrome2.kill();
34732
+ removeUserDataDir(userDataDir);
34597
34733
  log("[browser-capture] no page target after launch \u2014 skipping");
34598
34734
  return void 0;
34599
34735
  }
@@ -34660,6 +34796,7 @@ async function startBrowserCapture(opts) {
34660
34796
  timer.unref?.();
34661
34797
  return {
34662
34798
  frameCount: () => frames,
34799
+ killSync,
34663
34800
  async stop() {
34664
34801
  stopped = true;
34665
34802
  clearInterval(timer);
@@ -34668,9 +34805,10 @@ async function startBrowserCapture(opts) {
34668
34805
  } catch {
34669
34806
  }
34670
34807
  try {
34671
- await chrome.kill();
34808
+ await chrome2.kill();
34672
34809
  } catch {
34673
34810
  }
34811
+ removeUserDataDir(userDataDir);
34674
34812
  }
34675
34813
  };
34676
34814
  }
@@ -34700,15 +34838,49 @@ async function waitForUrl(url, timeoutSec) {
34700
34838
  }
34701
34839
  return false;
34702
34840
  }
34703
- function safe(fn) {
34704
- try {
34705
- return fn();
34706
- } catch {
34707
- return void 0;
34708
- }
34709
- }
34710
34841
 
34711
34842
  // libs/browser-capture/src/capture-runner.ts
34843
+ var chrome;
34844
+ var capture;
34845
+ var shuttingDown = false;
34846
+ function shutdown() {
34847
+ if (shuttingDown) return;
34848
+ shuttingDown = true;
34849
+ const finish = () => process.exit(0);
34850
+ if (!capture) {
34851
+ chrome?.killSync();
34852
+ finish();
34853
+ return;
34854
+ }
34855
+ setTimeout(() => {
34856
+ chrome?.killSync();
34857
+ finish();
34858
+ }, 5e3).unref();
34859
+ void capture.stop().then(finish, () => {
34860
+ chrome?.killSync();
34861
+ finish();
34862
+ });
34863
+ }
34864
+ process.on("SIGINT", shutdown);
34865
+ process.on("SIGTERM", shutdown);
34866
+ process.on("SIGHUP", shutdown);
34867
+ process.on("exit", () => chrome?.killSync());
34868
+ process.on("uncaughtException", (err) => {
34869
+ console.error(`[browser-capture] fatal: ${err?.stack ?? String(err)}`);
34870
+ shutdown();
34871
+ });
34872
+ process.on("unhandledRejection", (reason) => {
34873
+ console.error(`[browser-capture] fatal: ${String(reason)}`);
34874
+ shutdown();
34875
+ });
34876
+ setInterval(() => {
34877
+ if (process.ppid === 1) {
34878
+ console.error("[browser-capture] parent process gone \u2014 shutting down");
34879
+ shutdown();
34880
+ }
34881
+ }, 5e3).unref();
34882
+ reapOrphanCaptureChrome((m) => console.error(m));
34883
+ setInterval(() => reapOrphanCaptureChrome((m) => console.error(m)), 6e4).unref();
34712
34884
  var MAX_EVENT_BYTES = (() => {
34713
34885
  const n = Number(process.env["LENSMCP_EVENT_MAX_BYTES"]);
34714
34886
  return Number.isFinite(n) && n > 0 ? n : 256 * 1024 * 1024;
@@ -34747,21 +34919,20 @@ async function main() {
34747
34919
  process.env["LENSMCP_TOKENS_FILE"] ?? process.cwd()
34748
34920
  );
34749
34921
  const rules = loadRules(process.env["LENSMCP_RULES_FILE"]);
34750
- const capture = await startBrowserCapture({
34922
+ capture = await startBrowserCapture({
34751
34923
  url,
34752
34924
  sink,
34753
34925
  tokens,
34754
34926
  rules,
34755
34927
  sessionId: process.env["LENSMCP_SESSION_ID"],
34756
34928
  headless: process.env["LENSMCP_HEADLESS"] !== "false",
34929
+ onChrome: (c) => {
34930
+ chrome = c;
34931
+ if (shuttingDown) c.killSync();
34932
+ },
34757
34933
  log: (m) => console.error(m)
34758
34934
  });
34759
- if (!capture) return;
34760
- const shutdown = () => {
34761
- void capture.stop().finally(() => process.exit(0));
34762
- };
34763
- process.on("SIGINT", shutdown);
34764
- process.on("SIGTERM", shutdown);
34935
+ if (!capture) process.exit(0);
34765
34936
  }
34766
34937
  function loadRules(rulesFile) {
34767
34938
  if (!rulesFile) return [];
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "lensmcp",
3
- "version": "1.16.27",
3
+ "version": "1.16.28",
4
4
  "type": "module",
5
5
  "main": "./index.js",
6
6
  "module": "./index.js",
@@ -2,7 +2,7 @@
2
2
  "name": "lensmcp",
3
3
  "displayName": "LensMCP",
4
4
  "description": "The observability lens for coding agents. One command brings up the dev cluster gateway (every project.json `cluster` decl → its host on :443), the per-project lens dashboard at https://lensmcp.local/<project>/, and the MCP server your agent connects to — scoped automatically to whatever project you opened Claude Code in.",
5
- "version": "1.16.27",
5
+ "version": "1.16.28",
6
6
  "author": {
7
7
  "name": "David Antoon",
8
8
  "email": "davidmantoon@gmail.com"