Skip to content
Back to skills

Bridge Eyes

ASecurity

Use when frontend work in this repo needs to be looked at rather than asserted — after changing anything under bridge/dashboard/web, bridge/miniapp/web or site/, or when the user says "screenshot it", "does it look right", "show me the dashboard", or asks you to verify a UI change. Covers headless capture in WSL and driving the live UIs over CDP.

  • 6 stars
  • 0 votes
  • 0 copies
  • 0 views
  • Added September 5, 2026
ai-agentspythonshellnodeapifrontend

Works with

  • cli
  • api
  • mcp

Security analysis

A100/100

Pro scans all 2 files and shows the line behind each finding

Scanned September 22, 2026

npx -y skills add ainurdev/mystical-assistant --skill bridge-eyes --agent claude-code

Installs into .claude/skills of the current project.

Are you the author of Bridge Eyes?

Add the live security badge to your README. It updates with every re-scan.

Security grade badge for Bridge Eyes
[![Security: A — Skills Directory](https://www.skillsdirectory.com/api/skills/ainurdev-bridge-eyes/badge)](https://www.skillsdirectory.com/skills/ainurdev-bridge-eyes)

More formats (shields.io, HTML) on the badges page. Keep it an A: scan every change in CI with Pro.

Download with Pro
SKILL.md
---
name: bridge-eyes
description: Use when frontend work in this repo needs to be looked at rather than asserted — after changing anything under bridge/dashboard/web, bridge/miniapp/web or site/, or when the user says "screenshot it", "does it look right", "show me the dashboard", or asks you to verify a UI change. Covers headless capture in WSL and driving the live UIs over CDP.
user-invocable: true
---

# Looking at the UIs headlessly

There is no display here. Every visual claim has to come from a captured pixel,
and the obvious way to capture one hangs forever on this project's own UIs.

## Which port is which

| Port | Surface | Notes |
|------|---------|-------|
| 8790 | Dashboard | Desktop. `?skipboot=1` bypasses the 4.25s boot intro. |
| 8787 | Mini App | 390px wide. `/api/*` needs Telegram init-data. |
| 8791 | Landing page | Static. |

`DASH_TOKEN` is empty in this machine's `.env`, so local GETs need no auth. If it
is set, append `?token=…`.

## Static pages: use the bridge's own helper

```python
from bridge import screenshot
open("/tmp/shot.png", "wb").write(screenshot.capture("http://127.0.0.1:8791/", 1280, 800))
```

`bridge/screenshot.py` already owns the WSL specifics — it finds the newest
chrome-headless-shell under `~/.cache/ms-playwright/` (the build number changes
on every Playwright update, so never hardcode the path) and puts the cached
`libasound.so.2` stub on `LD_LIBRARY_PATH`. Don't hand-roll chrome flags.

Then `Read` the PNG to actually look at it.

## Live app pages: `capture()` will hang — use CDP

Chrome's `--screenshot` mode waits for the page to finish loading. The dashboard
and Mini App hold SSE streams open forever, so it never fires: no image, no
error, no timeout worth waiting for. Measured on the live dashboard — still
nothing at 75s, while the landing page returns in 0.8s.

Use the bundled CDP script instead, which navigates, waits a fixed settle, and
grabs `Page.captureScreenshot`:

```sh
node .claude/skills/bridge-eyes/shot.mjs 'http://127.0.0.1:8790/?skipboot=1' /tmp/dash.png 1280 800
# → /tmp/dash.png 169KB, ~3.4s
```

Then `Read /tmp/dash.png`.

## Driving, not just looking

The script is deliberately small; when you need to click through to a state,
extend the same CDP session with `Runtime.evaluate` rather than reaching for
Playwright (the npm module is not installed, and the chrome-devtools MCP fails
with "Target closed" in this WSL box).

- Launch with your own `--user-data-dir` — without it chrome hands off to an
  already-running instance and your debug port never listens. (`shot.mjs` does.)
- Dashboard selectors: session rows are `.sessrow`; several `.mscroll` elements
  exist — the transcript scroller is the one containing `[data-key]` rows. Turn
  rows carry `data-index`/`data-key`; `[data-prompt-idx]` was removed in the
  virtualization rewrite and now silently matches nothing.
- The editor is inside `AnalyzeModal`, not a top-level tab: click `MAP` on a
  project row, then the `EDITOR` tab.
- Mini App with real data needs a forged `X-Telegram-Init-Data`:
  `secret = HMAC_SHA256("WebAppData", TELEGRAM_BOT_TOKEN)`, then
  `hash = HMAC_SHA256(secret, "\n".join(sorted("k=v")))` over `auth_date`,
  `query_id`, `user` — the user id must be in `ALLOWED_CHAT_IDS`. Block
  `*telegram-web-app.js*` via `Network.setBlockedURLs` or the real CDN script
  overwrites your stub `window.Telegram`.
- Opening a session from another repo calls `api.select` and **changes the
  bridge's active project**. Stay in the active project, or restore it with
  `POST /local/select {"dir": …}` on 8790.

## Recording, not just stills

A still cannot show a transition, a hover, or a race. `bridge/rec.mjs`
is `shot2.mjs` with `Page.startScreencast` in place of the single capture, and
takes the same arguments plus an output fps:

```sh
node bridge/rec.mjs 'http://127.0.0.1:8790/?skipboot=1' /tmp/clip.webm \
  1280 800 3000 '{}' "$ASYNC_JS" 10
# → /tmp/clip.webm 800KB  490 frames -> 102 @10fps  10.2s
```

The 7th argument is the same `Runtime.evaluate` string `shot2.mjs` takes, run
with `awaitPromise`, so an async IIFE with `await sleep()` between clicks is the
whole step language — no separate DSL.

- **Output is VP8/webm, never mp4.** Playwright's bundled ffmpeg (the only one
  on this machine) is built `--disable-everything`: mjpeg in, libvpx out, webm
  muxed. No h264, no concat demuxer, no gif. Telegram accepts it via
  `sendVideo` (measured 2026-08-31); `send.py` falls back to `sendDocument` if
  that ever stops being true.
- Chrome emits ~50 frames/s and only when pixels *change*, so the recorder
  drops the surplus and pads gaps with the last frame, against an absolute
  video clock. Check the printed real-time figure against the container's
  `Duration` — they should match within a frame or two. If they don't, the
  video is lying about how long the UI took.
- Watch it yourself before sending: `send.py` now takes `.webm` as well as PNGs.

Isolation needs nothing new. Build in a worktree, serve that `dist` with
`.mystical/probe/probe.py` (static + read-only GET proxy), and record against
*that* port — the recorder already gets a fresh `mkdtemp` chrome profile per
run, so nothing touches the live bridge's state.

## In-session alternative

A run started by the bridge gets a `Screenshot` MCP tool
(`bridge/verify_mcp.py`) that returns the image as a content block — no file, no
node. Use it when it's there; it caps at 1600×1600 to bound token cost.

Files in this skill

  • SKILL.md3.8 KB
  • shot.mjs2.3 KB

Attribution

Is this your skill, or is something wrong with this listing? Request removal or report an issue. Author removals are honored within 72 hours.

Comments

Loading comments…

Newsletter

Get the weekly list of safe new skills, and the ones we pulled.