{
  "markdown": "# uisight\n\n[![CI](https://github.com/sololabstr/uisight/actions/workflows/ci.yml/badge.svg)](https://github.com/sololabstr/uisight/actions/workflows/ci.yml)\n[![npm](https://img.shields.io/npm/v/uisight)](https://www.npmjs.com/package/uisight)\n\n**Your AI can already see the screen. It just can't measure it.**\n\nScreenshots make an agent *guess*: \"that heading looks a bit faint.\" uisight makes it **know**:\n\n```diff\n- from a screenshot:  \"the heading looks a little washed out, maybe adjust the color?\"\n+ from uisight:       INVISIBLE TEXT 1.04:1 — span.bg-gradient-to-r \"your headline\"\n+                     (text rgba(255,255,255,.5) / bg rgb(247,247,248))\n```\n\nOne is an impression. The other is a measurement with a selector attached — the agent fixes *that* element instead of hunting for it.\n\nuisight is an [MCP](https://modelcontextprotocol.io) server for **web and responsive UIs** (Claude Code, Cursor, Antigravity, anything that speaks MCP). It runs live mobile + desktop sessions side by side, measures what it finds, and puts you and the agent in front of the exact same screen.\n\nBuilt by a solo founder who got tired of taking phone screenshots, pasting them into chat, and typing \"the button looks broken, can you see it?\"\n\n![uisight live panel — desktop and mobile side by side, with per-device inspection findings](docs/assets/live-panel.png)\n*The live panel: desktop + mobile sessions of the same site, URL-synced. Inspect runs on every screen; findings come back per device. Your AI sees this exact view through MCP.*\n\n## What makes it different\n\n| | Multi-viewport browsers<br>(Polypane etc.) | Browser tools / computer use<br>(Playwright MCP, agent harnesses) | Native app toolkits<br>(Argent etc.) | **uisight** |\n|---|---|---|---|---|\n| Measures the UI (`1.14:1`, not \"looks low\") | ✅ for humans | — | — | ✅ **as text, for the agent** |\n| Human + agent share one live session | — | — | — | ✅ |\n| Device × theme matrix in one run | ✅ | — | — | ✅ |\n| Human pins a bug → agent reads note + frame | — | — | — | ✅ |\n| Native iOS/Android apps | — | — | ✅ | — (web only) |\n\nThe measurement engine is the heart: instead of your AI burning tokens squinting at screenshots, `inspect` returns findings like\n\n```\n[mobile · Pixel 7 · light] https://yourapp.com/\n  INVISIBLE TEXT 1.04:1 — span.bg-gradient-to-r \"your headline\" (text rgba(255,255,255,.5) / bg rgb(247,247,248))\n  BUTTON a.text-white \"Get Started\" → text/background contrast 3.35:1\n  touch target below 44px 180x23 — \"read the guide\"\n```\n\nText findings are cheap, precise, and directly actionable — your AI fixes the exact selector instead of guessing.\n\n## \"My agent already does this\"\n\nFair — and partly true. Computer use, browser tools and most agent harnesses can already open a page and take a screenshot. That's the part uisight doesn't try to replace. Three things are still missing:\n\n**1. Looking isn't measuring.** A vision model reading a screenshot cannot tell you a contrast ratio. It can't tell 4.6:1 (fine) from 4.3:1 (fails WCAG AA) — they look identical. It won't notice that a tap target is 41px instead of 44px, or that an element renders identically in light and dark mode because its color is hard-coded. uisight computes these from the live DOM: alpha-composited backgrounds, gradient text, `oklch()` colors and all.\n\n**2. The same price buys far more.** This used to claim a screenshot costs several times what a measurement does. Measured, that is not true: a mobile frame is ~460 tokens and the matching `inspect` result is ~570. `inspect` is not the cheap option — it is the option that says `4.38:1 (threshold 4.5)` where a picture only lets the model guess.\n\nThe real saving is a different choice: `uisight <url>` writes a report the model reads once (~800 tokens), while driving the MCP tools screen by screen re-sends the whole conversation at every step. Someone put the problem perfectly under the launch thread: *\"it burns some tokens but it manages.\"* [What it costs](#what-it-costs) has the whole table, because a claim like this one is worth checking.\n\n**3. Nobody's watching with you.** In the usual setup the agent looks at the page alone and reports back. Here you both watch the same live session — you see what it does as it does it, and when *you* spot something, you pin it (📌) with a note and the agent reads your note plus that exact frame. No more describing a bug in words.\n\nScope note: uisight is for **web and responsive UIs**. For native iOS/Android app control, [Argent](https://github.com/software-mansion/argent) is excellent and does far more than we do there.\n\n## Quickstart\n\n```bash\n# one-shot audit: PNGs + gallery + report for iPhone/Pixel/desktop, light+dark\nnpx uisight https://yourapp.com --theme both\n\n# live panel: mobile + desktop side by side, you browse, AI watches (and vice versa)\nnpx -y -p uisight uisight-panel http://localhost:3000\n```\n\n**First run.** Playwright ships its driver over npm but downloads browsers\nseparately, so the first run has nothing to drive. In a terminal, uisight offers\nto fetch what it needs (~150 MB, once) and shows the download. Where there is\nnobody to answer — CI, or a panel an editor or agent host started — it never\nasks and never downloads; it names the exact command instead. `UISIGHT_NO_INSTALL=1`\nturns the offer off everywhere, and you can always do it yourself:\n\n```bash\nnpx playwright install chromium        # add webkit for the real iOS Safari engine\n```\n\nThe one-shot audit produces a device × theme gallery with findings per card:\n\n![uisight gallery — 4 devices × light/dark with findings per card](docs/assets/gallery.png)\n\n### Hook it into your AI (MCP)\n\n```bash\n# Claude Code\nclaude mcp add --scope user uisight -- npx -y -p uisight@latest uisight-mcp\n```\n\nFor Cursor / Antigravity / other MCP hosts, add to your MCP config:\n\n```json\n{ \"mcpServers\": { \"uisight\": { \"command\": \"npx\", \"args\": [\"-y\", \"-p\", \"uisight@latest\", \"uisight-mcp\"] } } }\n```\n\nThen just tell your agent: *\"look at my app with uisight\"*. The panel server starts automatically when needed.\n\n## MCP tools\n\n| Tool | What it does |\n|---|---|\n| `see_screen` | Returns the current screen as an image — the exact frame the human sees in the panel |\n| `inspect` | Runs contrast / touch-target / overflow / theme checks; returns **measured findings as text** |\n| `goto` | Navigates all sessions to a URL (localhost included) |\n| `tap` / `type_text` / `scroll` | Drives the page — the human watches it happen live |\n| `set_device` | Switches device profile (iphone-15, iphone-se, pixel, galaxy, ipad, desktop, laptop) or light/dark theme |\n| `status` | Open URL, sessions, recent console/network errors — first stop when hunting a bug |\n| `marks` | Reads the notes the human pinned in the panel (📌 note + screenshot at that moment) |\n\nTurkish tool names available with `UISIGHT_LANG=tr` (`ekrani_gor`, `denetle`, ...).\n\n## The panel (human side)\n\n`npx -y -p uisight uisight-panel <url>` opens a browser page at `localhost:5055`:\n\n- **Mobile + desktop side by side**, both live, URL-synced\n- Click = tap on that device · wheel = scroll · type after clicking\n- Per-pane device switcher, shared light/dark toggle\n- **Inspect** button runs the measurement engine on every screen\n- **📌 Pin**: type a note, pin it — your AI reads note + screenshot via `marks`. No more \"let me describe what I'm seeing.\"\n\nWorks inside VS Code / Antigravity via *Simple Browser: Show* → `http://localhost:5055`.\n\n## What it checks\n\n**Can you read it**\n\n- Invisible text (contrast < 1.6:1) and WCAG AA contrast failures — alpha-composited backgrounds, gradient text, `oklab()`/`oklch()` colors all handled\n- Text below 12px, images without alt\n- Text cut off by its own container (`line-clamp` and friends are not \"clipped\" — they are a decision)\n\n**Can you reach it**\n\n- Touch targets below 44px (mobile profiles only; inline text links exempt by width, per WCAG)\n- Controls painted over by something else — confirmed with `elementFromPoint`, not geometry, and sampled edge to edge so a floating button covering one end of a wide button is caught\n- Controls trapped under a fixed bar, or under the on-screen keyboard (`keyboard-audit` opens the keyboard the way a phone does and re-measures)\n- Content clipped by a container with no way to scroll to it — the same box with `overflow-x: auto` is fine, because the content is reachable\n- Text sliding behind a control when a row does not wrap\n- Horizontal overflow with the offending elements\n- Fixed bars sitting under the notch or home indicator — only when the page asked for the full screen (`viewport-fit=cover`) and then never used the inset it got back; without that flag iOS letterboxes the page and nothing can be hidden\n\n**Does it make sense**\n\n- A row of actions where every one looks identical, so nothing says which is primary — quiet on tabs, menus and filter chips, and only fires when mis-clicking costs something (save, delete, send, pay)\n- Light patches left behind in dark mode\n- Two languages in one screen, and US date formats in a non-US locale\n- \"0 results\" shown while a spinner is still turning — an intermediate state presented as the truth\n- An error message that names nothing (\"An error occurred.\") with no way out beside it\n- An irreversible action — delete, remove, delete account — on a page that owns no confirmation step at all; the button is never clicked, because clicking it really deletes\n- A permission asked for during load, before the person has done anything that would explain it\n- **Theme drift**: elements identical in light *and* dark = likely hard-coded colors\n- Console/JS errors and failed network requests per device\n\nEvery check has a false-alarm test next to its detection test. That is not politeness: a tool that cries wolf on every bottom navigation bar gets ignored, and then its real findings go unread too.\n\nAnd the honest limit: automated checks cannot see *design* mistakes — a collided header measures fine. That's why `see_screen` exists and why the report says \"eyeball the PNGs.\"\n\n## Behaviour you cannot see by looking (offline, back)\n\nTwo of these cannot be measured from a rendered page: the network has to\nactually drop, and the back button has to actually be pressed. Both run as panel\nactions, so the audit and the MCP tools share them.\n\n```bash\n# through the panel\ncurl -X POST localhost:5055/action -H 'x-uisight-token: ...' \\\n     -d '{\"type\":\"offline-audit\",\"session\":\"mobile\"}'\n```\n\n**Offline** drops the connection, reloads, and asks what the person is looking\nat: an explanation, a retry, a spinner that will never finish, or nothing. The\ndistinction that keeps it honest is the service worker — a page without one\n*cannot* answer offline, so that result is marked `expected` and the audit\nfilters it out. A page that registers a worker and still shows the browser's\nerror page is a real finding. The connection is restored in a `finally`, so a\nfailure never leaves the session stuck offline.\n\n**Back** follows an internal link and presses back, then checks that the address\nreturned to where it started and that the screen is not empty. Coming back to a\nblank page is how \"back\" turns into \"leave the app\".\n\n## Behind the login (`uisight-audit`)\n\nPublic pages are the half of an app nobody lives in. Of four real bugs a person\nfound by hand and sent in, three were behind a login and one showed up for a\nsingle role only.\n\n```bash\nuisight-audit                        # every configured role, 10 pages each\nuisight-audit --roles guide,agency   # only these\nuisight-audit --pages 20 --port 5062\n```\n\nAccounts live in `~/.uisight/accounts.json`. Sign-in tries three routes: a fixed\n`code` (the store-review-account pattern), a `devCode` read straight out of the\napp's own OTP response (dev/demo mode — no stored secret at all), or a password\nfield. Success means *leaving* the login page, not HTTP 200, so a wrong code is\nnever reported as a win. When the app refuses, its own words are passed through:\n\"HTTP 429 · too many codes requested\" instead of a guess about demo mode.\n\nRoles are switched through the app's own view-as endpoint where it has one, so\none admin account can audit every role. Pages not yet measured under any role go\nfirst, so a second role spends its budget on new ground instead of re-measuring\nthe same public pages.\n\n## Editor extension\n\n`extension/` is a VS Code / Antigravity extension: the live panel in the side\nbar, plus commands for device, theme, address, inspect and \"send the screen to\nyour AI\".\n\nSearch for `uisight` in the extensions panel of Antigravity, Cursor or\nVSCodium — [open-vsx.org/extension/sololabstr/uisight](https://open-vsx.org/extension/sololabstr/uisight).\nNode.js is the only requirement: the extension runs the published package\nthrough `npx uisight@latest`, so the engine updates itself and installing once\nkeeps getting new checks.\n\nTo build it from this repo instead:\n\n```bash\ncd extension && npx @vscode/vsce package\ncode --install-extension uisight-*.vsix\n```\n\nThe extension talks to the panel over HTTP and nothing type-checks that\nconversation, so `test/extension.test.mjs` compares the two sides: every action\nit sends must be one the server handles, every route must exist, every result\nfield it renders must be one the engine produces, and every command in the\nmanifest must be registered. That test exists because the pair drifted once and\nfailed *silently* — Inspect reported \"no findings\" on pages full of them.\n\n## What it costs\n\nSomeone burned through a plan running this and had no way to see where it went.\nSo here are measured numbers, not estimates — a Pixel 7 session on a real site,\nwith an image priced the way Claude prices one (width x height / 750):\n\n| | tokens |\n|---|---|\n| `uisight <url>` then read `REPORT.md` | **~800, once** |\n| `uisight-audit` then read `REPORT.md` | **~150-800, once** |\n| MCP `inspect` | ~570 per call |\n| MCP `see_screen` | ~260 per call (0.75 scale, the default) |\n| MCP `see_screen` with `full` | ~2,000 per call (capped; was ~5,800 uncapped) |\n| tool definitions | ~1,065 **per request** |\n\nThree things follow from that table.\n\n**The CLI is the cheap path and it is not close.** `uisight` and `uisight-audit`\nwrite a file; the model reads it once. Driving the MCP tools screen by screen\nre-sends the whole conversation on every step, so thirty round trips cost far\nmore than one report. Reach for the MCP tools when you need to *act* on a page —\ntap something, change device, look at a specific state — not to survey an app.\n\n**An image is not paid once.** It stays in the conversation and is re-sent on\nevery later turn. That is why `inspect` exists and why its output is text: the\nsame page costs ~570 tokens measured versus ~460 seen, and the measurement says\n`4.38:1 (threshold 4.5)` where the picture only lets the model guess. A full-page\ncapture is now capped (`UISIGHT_MAX_IMAGE_TOKENS`, default 2000) and the response\ntells you what it cost and what was left out, instead of quietly spending.\n\n**A screenshot does not need to be full size.** Measured on a real page: the\nsame mobile screen is 461 tokens at 1.0, 259 at 0.75 and 115 at 0.5 — and cost\nfalls with the *square* of the scale. At 0.75 it is indistinguishable, small\nprint included; at 0.5 the layout and every meaningful label still read and only\nthe smallest legal text goes soft. So 0.75 is the default and `scale` is a\nparameter on `see_screen`; pass `1` when small print is the thing you are\nlooking at, `0.5` for a cheap sweep.\n\n**Tool definitions are a fixed tax on every request.** Nine tools cost ~1,065\ntokens whether you call them or not:\n\n```jsonc\n// only what a measuring session needs: goto, inspect, see_screen, status\n{ \"env\": { \"UISIGHT_TOOLS\": \"core\" } }        // ~419 tokens\n{ \"env\": { \"UISIGHT_TOOLS\": \"goto,inspect\" } } // ~211 tokens\n```\n\n## Something didn't work?\n\nPlease open an issue — even a one-liner. This is a young project and the fastest way it improves is someone saying \"I ran it on X and got Y\". Screenshots of the panel or the contents of `REPORT.md` help a lot.\n\nKnown rough edges, so you can tell a bug from a limitation:\n\n- **Design mistakes are invisible to the engine.** A header that collides with the logo measures perfectly fine. Use `see_screen` and look.\n- **Photo backgrounds are skipped.** Contrast over a background image can't be computed from CSS, so those elements are left alone rather than guessed at.\n- **Theme drift samples structural elements** (body, header, nav, main, footer, button, a, input, cards/panels/modals/menus) — drift that lives only in body copy won't show up in the light↔dark comparison.\n- **iPhone profiles are WebKit, not an iOS Simulator** — very close to Safari, not identical to a device.\n- **Internals are still Turkish.** Public surfaces (tools, CLI flags, reports) are English; variable names inside `src/` aren't yet. PRs welcome either way.\n\n## Development\n\n```bash\nnpm install\nnpx playwright install chromium\nnpm test          # runs the inspection engine against fixture pages in a real browser\n```\n\nThe tests are regression locks: every case in `test/inspect.test.mjs` is something the engine got wrong at least once — a false \"clean\" verdict, a false alarm, or a measurement that silently skipped a color format.\n\nSee [CONTRIBUTING.md](CONTRIBUTING.md) before touching the measurement engine — it explains why the color math cannot be extracted into a module, and what a good bug report looks like.\n\n## Notes & limitations\n\n- iPhone profiles run on real WebKit (Safari's engine) — close to iOS, but not an iOS Simulator.\n- Browsers are downloaded once by Playwright on first run (`npx playwright install chromium webkit` if you want to pre-warm).\n- Everything runs **locally** — no cloud, no account, your screens never leave your machine.\n- As of v0.2 the codebase is English throughout — identifiers, comments, and the panel's HTTP field names. If you were calling the panel API directly, [CHANGELOG.md](CHANGELOG.md) has the rename table.\n\n## License\n\nMIT © [SoloLabs](https://sololabs.com.tr)\n",
  "bytes": 18016,
  "sha": "05d77e3d794bb70edd035f5076d9be9b506665dbe62fa7084e3e462135bdd582",
  "repo_slug": "sololabstr/uisight",
  "fonte": "repo",
  "truncated": false,
  "api": "https://agentalog.com/api/listings/mcp_io_github_yusufcemres_uisight_ee1f8471/readme"
}