looking-glass-mcp 3.1.0 → 3.1.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 CHANGED
@@ -44,7 +44,7 @@ Looking Glass v3.0 transforms from a browser automation tool into an **AI-native
44
44
  - **Intent-based navigation** -- `browser_go "settings page"` figures out how to get there
45
45
  - **Semantic waiting** -- `browser_wait_for "search results loaded"` instead of guessing CSS selectors
46
46
  - **Workflow tracking** -- every response includes page type, form progress, breadcrumbs, modal state, and step indicators
47
- - **Credential vault** -- AES-256-GCM encrypted credentials with PBKDF2 key derivation and blind injection (the agent never sees passwords)
47
+ - **Credential vault** -- AES-256-GCM encrypted credentials with PBKDF2 key derivation and blind injection (the agent never sees passwords). Smart field matching works on modern React forms with no `name` attributes
48
48
  - **HTTP transport** -- deploy as a cloud service with `StreamableHTTPServerTransport`
49
49
  - **Docker + Terraform** -- one-command Azure deployment with Key Vault, managed identity, and hardened security
50
50
  - **Enterprise security** -- timing-safe auth, rate limiting, auth lockout, deep audit logging, non-root containers
package/build/index.js CHANGED
@@ -29010,6 +29010,117 @@ async function injectCredentials(profile) {
29010
29010
  await label.fill(value, { timeout: 3e3 });
29011
29011
  injected++;
29012
29012
  matched.push(name);
29013
+ filled = true;
29014
+ }
29015
+ } catch {
29016
+ }
29017
+ }
29018
+ if (!filled) {
29019
+ const keywords = nameLower.split(/[\s_-]+/).filter((w) => w.length > 2);
29020
+ if (keywords.length === 0) keywords.push(nameLower);
29021
+ for (const kw of keywords) {
29022
+ if (filled) break;
29023
+ try {
29024
+ const ariaLocator = page.locator(`input[aria-label*="${kw}" i], textarea[aria-label*="${kw}" i]`).first();
29025
+ if (await ariaLocator.isVisible({ timeout: 1e3 })) {
29026
+ await ariaLocator.fill(value, { timeout: 3e3 });
29027
+ injected++;
29028
+ matched.push(name);
29029
+ filled = true;
29030
+ }
29031
+ } catch {
29032
+ }
29033
+ }
29034
+ if (!filled) {
29035
+ for (const kw of keywords) {
29036
+ if (filled) break;
29037
+ try {
29038
+ const phLocator = page.getByPlaceholder(kw, { exact: false }).first();
29039
+ if (await phLocator.isVisible({ timeout: 1e3 })) {
29040
+ await phLocator.fill(value, { timeout: 3e3 });
29041
+ injected++;
29042
+ matched.push(name);
29043
+ filled = true;
29044
+ }
29045
+ } catch {
29046
+ }
29047
+ }
29048
+ }
29049
+ }
29050
+ if (!filled) {
29051
+ try {
29052
+ const keywords = nameLower.split(/[\s_-]+/).filter((w) => w.length > 2);
29053
+ if (keywords.length === 0) keywords.push(nameLower);
29054
+ const selector = await page.evaluate((kws) => {
29055
+ const labels = document.querySelectorAll("label");
29056
+ for (const label of labels) {
29057
+ const labelText = label.textContent?.trim().toLowerCase() || "";
29058
+ if (!kws.some((kw) => labelText.includes(kw))) continue;
29059
+ if (label.htmlFor) {
29060
+ const target = document.getElementById(label.htmlFor);
29061
+ if (target && ["INPUT", "TEXTAREA", "SELECT"].includes(target.tagName)) {
29062
+ return `#${CSS.escape(label.htmlFor)}`;
29063
+ }
29064
+ }
29065
+ const wrapped = label.querySelector("input, textarea, select");
29066
+ if (wrapped) {
29067
+ const all = [...document.querySelectorAll(wrapped.tagName.toLowerCase())];
29068
+ const idx = all.indexOf(wrapped);
29069
+ if (idx >= 0) return `${wrapped.tagName.toLowerCase()}:nth-of-type(${idx + 1})`;
29070
+ }
29071
+ }
29072
+ for (const input of document.querySelectorAll("input, textarea, select")) {
29073
+ const labelledBy = input.getAttribute("aria-labelledby");
29074
+ if (!labelledBy) continue;
29075
+ const labelEl = document.getElementById(labelledBy);
29076
+ const labelText = labelEl?.textContent?.trim().toLowerCase() || "";
29077
+ if (kws.some((kw) => labelText.includes(kw))) {
29078
+ return `[aria-labelledby="${CSS.escape(labelledBy)}"]`;
29079
+ }
29080
+ }
29081
+ return null;
29082
+ }, keywords);
29083
+ if (selector) {
29084
+ const locator = page.locator(selector).first();
29085
+ if (await locator.isVisible({ timeout: 1e3 })) {
29086
+ await locator.fill(value, { timeout: 3e3 });
29087
+ injected++;
29088
+ matched.push(name);
29089
+ filled = true;
29090
+ }
29091
+ }
29092
+ } catch {
29093
+ }
29094
+ }
29095
+ if (!filled && /email|username|user|login|account/i.test(nameLower)) {
29096
+ try {
29097
+ const hasPasswordField = await page.locator('input[type="password"]').count() > 0;
29098
+ if (hasPasswordField) {
29099
+ const candidates = page.locator('input[type="text"]:visible, input[type="email"]:visible');
29100
+ const count = await candidates.count();
29101
+ for (let i = 0; i < count && !filled; i++) {
29102
+ const candidate = candidates.nth(i);
29103
+ try {
29104
+ const isBeforePassword = await page.evaluate((idx) => {
29105
+ const inputs = [...document.querySelectorAll("input")];
29106
+ const visible = inputs.filter((inp) => {
29107
+ const t = inp.type?.toLowerCase() || "text";
29108
+ return (t === "text" || t === "email") && inp.offsetParent !== null;
29109
+ });
29110
+ const pw = inputs.find((inp) => inp.type === "password");
29111
+ if (!visible[idx] || !pw) return false;
29112
+ return inputs.indexOf(visible[idx]) < inputs.indexOf(pw);
29113
+ }, i);
29114
+ if (isBeforePassword) {
29115
+ await candidate.fill(value, { timeout: 3e3 });
29116
+ injected++;
29117
+ matched.push(name);
29118
+ filled = true;
29119
+ }
29120
+ } catch {
29121
+ continue;
29122
+ }
29123
+ }
29013
29124
  }
29014
29125
  } catch {
29015
29126
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "looking-glass-mcp",
3
- "version": "3.1.0",
3
+ "version": "3.1.1",
4
4
  "mcpName": "io.github.Sahib-Sawhney-WH/looking-glass-mcp",
5
5
  "description": "AI-native browser for agents — semantic change detection, self-healing interactions, structured extraction, credential vault, and enterprise Azure deployment",
6
6
  "main": "build/index.js",