versa/skills/sway-browser-ecovoyage/SKILL.md
Sway-browser-vnc instance wired to versa/ecovoyage tailnet URLs — chrome-devtools-mcp + CDP for debugging the generated MapLibre + folium maps.
npx skillsauth add overthinkos/overthink-plugins sway-browser-ecovoyageInstall this skill globally with one command. Works with Claude Code, Cursor, and Windsurf.
3 of 9 scanners reported clean
Some scanners were skipped, did not run, or reported a non-clean status. Review each row below.
Deploys sway-browser-vnc as a pinned Pattern A instance
(sway-browser-vnc/ecovoyage) preconfigured to debug the maps
rendered by the versa/ecovoyage notebook (osm-monaco-viz.py).
The pod's chrome-devtools-mcp + raw CDP + VNC ports are all exposed
on the host's existing tailnet identity (ac.armadillo-quail.ts.net)
so external Claude Code / agents — and you —
can drive Chrome programmatically over Streamable HTTP MCP.
The versa/ecovoyage notebook generates 5 client-rendered maps
(4 MapLibre via martin, 1 shortbread via versatiles, 1 folium).
Verifying these from a sandboxed agent context requires a browser
that can reach the dynamic tile URLs (e.g.
https://ac.armadillo-quail.ts.net:33000/monaco/{z}/{x}/{y}).
The existing eval-sway-browser-vnc-pod is general-purpose and
isn't preconfigured with the ecovoyage URLs — and its loopback
isn't the same loopback as the host, so 127.0.0.1 references to
versa/ecovoyage's host ports don't resolve. Tailnet URLs solve
both problems: same identity from any device on the tailnet, no
loopback collision, TLS-terminated by tailscaled.
charly config sway-browser-vnc/ecovoyage
charly start sway-browser-vnc/ecovoyage
The deploy entry is fully specified in ~/.config/charly/charly.yml —
it pins host ports (5901 VNC, 9230 CDP, 9232 MCP) and forwards
every *_PUBLIC_URL env var so chrome's address bar + scripts
| Endpoint | Tailnet URL | What it is |
|---|---|---|
| chrome-devtools-mcp | https://ac.armadillo-quail.ts.net:9232/mcp | Streamable HTTP MCP, 29 tools |
| Chrome DevTools raw | https://ac.armadillo-quail.ts.net:9230 | CDP /json/version etc. |
| VNC | tcp://ac.armadillo-quail.ts.net:5901 | wayvnc, no auth |
These are passed in as env vars (MARTIN_PUBLIC_URL,
AIRFLOW_PUBLIC_URL, etc.) so chrome scripts can use them via
process.env or via the supervisord-launched VERSA_ECOVOYAGE_MARIMO_URL:
| Var | Tailnet URL |
|---|---|
| VERSA_ECOVOYAGE_MARIMO_URL | https://ac.armadillo-quail.ts.net:32718/?file=notebooks/osm-monaco-viz.py |
| MARTIN_PUBLIC_URL | https://ac.armadillo-quail.ts.net:33000 |
| AIRFLOW_PUBLIC_URL | https://ac.armadillo-quail.ts.net:38080 |
| PMTILES_VIEWER_PUBLIC_URL | https://ac.armadillo-quail.ts.net:38001 |
| VERSATILES_PUBLIC_URL | https://ac.armadillo-quail.ts.net:38090 |
| VERSATILES_ASSETS_PUBLIC_URL | https://ac.armadillo-quail.ts.net:38002 |
| VERSATILES_STYLE_PUBLIC_URL | https://ac.armadillo-quail.ts.net:38002/style |
# 1. Open the marimo notebook in chrome
charly eval cdp open sway-browser-vnc/ecovoyage \
"https://ac.armadillo-quail.ts.net:32718/?file=notebooks/osm-monaco-viz.py"
# 2. Run all stale cells (after the DAGs finish executing)
charly eval cdp evaluate_script sway-browser-vnc/ecovoyage \
--expression 'document.querySelectorAll("button[data-testid=run-button]").forEach(b => b.click())'
# 3. Wait for the streets MapLibre canvas to appear
charly eval cdp wait sway-browser-vnc/ecovoyage \
--selector ".maplibregl-canvas" --timeout 90s
# 4. Screenshot — validate dimensions + non-uniform content
charly eval cdp screenshot sway-browser-vnc/ecovoyage \
--artifact /tmp/ecovoyage-streets.png \
--artifact-min-bytes 50000 \
--artifact-min-dimensions 800x600 \
--artifact-not-uniform
# 5. Inspect the iframe contents directly (catch JS errors before they
# silently produce blank canvases — see the hyphen-in-JS-identifier
# bug story below)
charly eval cdp evaluate_script sway-browser-vnc/ecovoyage \
--expression '[...document.querySelectorAll("iframe")].map(f => ({
src: f.src, canvas: !!f.contentDocument?.querySelector("canvas"),
consts: [...(f.contentDocument?.body?.innerHTML?.matchAll(/const map_[a-z_-]+/g) || [])].map(m=>m[0])
}))'
The chrome-devtools-mcp server's MCP endpoint is published on the
tailnet so any Claude Code instance — local or remote on the tailnet —
can plug it into its MCP config:
// .mcp.json
{
"mcpServers": {
"chrome-devtools-ecovoyage": {
"url": "https://ac.armadillo-quail.ts.net:9232/mcp",
"transport": "http"
}
}
}
In opencharly's provides: registry this server is published as
chrome-devtools-ecovoyage (instance-suffixed by charly config's
MCP name-disambiguation rule — see /charly-core:deploy) so it
doesn't collide with the eval-sway-browser-vnc-pod's base
chrome-devtools provide.
When verifying that maps render, count the canvas elements inside
each iframe, not just the iframes themselves. A failed const map_…
declaration leaves the iframe with its <script> tag present but no
canvas — and the iframe count is unchanged. The right check:
[...document.querySelectorAll("iframe")].map(f => ({
canvas: !!f.contentDocument?.querySelector("canvas")
}))
A canvas: false row for a map cell is the signal that MapLibre
threw during init. Pull the script body and grep for invalid
identifiers (hyphens in const names was the canonical bug:
const map_duckdb-mvt = … parses as map_duckdb − mvt).
127.0.0.1If you debug from a container-sandboxed chrome
(e.g. eval-sway-browser-vnc-pod), 127.0.0.1 is that pod's
loopback, not the host's. Maps with http://127.0.0.1:<host-port>
tile URLs will silently fail to fetch tiles even though MapLibre
instantiates the canvas. This is why every *_PUBLIC_URL env var
should be tailnet-form for browser-side consumption — tailnet
URLs are the same from anywhere on the tailnet, including
sandboxed pods.
/charly-versa:notebook-osm — the marimo notebook whose 5 maps this skill exists to debug/charly-versa:versa — the image hosting the maps (versa/ecovoyage = Pattern A instance)/charly-selkies:sway-browser-vnc — the base image this deploy instantiates/charly-selkies:chrome-devtools-mcp — the layer providing the MCP server/charly-eval:cdp — the verb catalog driving Chrome/charly-build:charly-mcp-cmd — verifying the MCP endpoint via charly eval mcp ping/list-tools/call/charly-core:deploy — Pattern A multi-instance + MCP name disambiguation/charly-automation:sidecar — alternative tailscale sidecar pattern (not used here; this skill uses host-serve)tools
OpenCharly CLI (charly) binary installed into container/VM images for in-container use. Use when working with charly binary deployment inside containers, native D-Bus support, or the full charly toolchain (charly binary + virtualization + gocryptfs + socat).
development
Operator CachyOS workstation profile — a kind:local template + target:local deploy that installs the full dev stack (30 candies) onto a CachyOS host via ShellExecutor. Lives in the overthinkos/cachyos submodule. MUST be invoked before editing or applying the charly-cachyos workstation profile.
tools
Fedora box with the full charly toolchain using shared candies. Rootless-first — runs as uid=1000 with passwordless sudo (no root, no cap_add: ALL). Same candy list as charly-arch. Includes NVIDIA GPU runtime. MUST be invoked before building, deploying, configuring, or troubleshooting the charly-fedora box.
tools
Arch Linux box with the full charly toolchain. Rootless-first — runs as uid=1000 with passwordless sudo (no root, no cap_add: ALL). Composes /charly-coder:charly-mcp so the box is reachable as an MCP gateway on port 18765. NVIDIA GPU runtime composed in. MUST be invoked before building, deploying, configuring, or troubleshooting the charly-arch box.