cssprobe-cli
by mack-peng
README.md
# cssprobe-cli
Runtime CSS probe — inspect layout, scroll, colors, backgrounds, fonts and more in a live browser. Built for both humans and AI agents.
[](https://opensource.org/licenses/MIT) [](https://nodejs.org/) [](https://www.npmjs.com/package/cssprobe-cli) [](https://www.npmjs.com/package/cssprobe-cli) [](https://github.com/mack-peng/cssprobe-cli/stargazers)
> Static + runtime CSS diagnosis for AI agents — pair with [cssgraph](https://github.com/mack-peng/cssgraph) (the CSS knowledge graph) for the full picture.
---
## Why cssprobe-cli?
When an AI agent needs to debug CSS — why is this element overflowing, what's the actual rendered layout, is this container scrollable — it can't see the browser. It guesses from static code, missing runtime-only issues like overflow, scroll chains, and containing block hijacks.
**cssprobe-cli gives the agent eyes.** It opens a real browser, collects computed styles and metrics via injected JavaScript, analyzes them with a confidence model, and returns structured findings — all in one command. The agent asks a question about any selector and gets back the DOM tree, ASCII layout diagram, overflow warnings, and scroll diagnosis without leaving the terminal.
---
## Installation
### For Humans
Copy and paste this prompt to your LLM agent (Claude Code, Cursor, Codex, etc.):
```
Install and configure cssprobe-cli by following the instructions here:
https://raw.githubusercontent.com/mack-peng/cssprobe-cli/main/docs/guide/installation.md
```
Or read the [Installation Guide](docs/guide/installation.md), but seriously, let an agent do it. Humans fat-finger configs.
### For LLM Agents
Fetch the installation guide and follow it:
```bash
curl -s https://raw.githubusercontent.com/mack-peng/cssprobe-cli/main/docs/guide/installation.md
```
Or install directly:
```bash
npm install -g cssprobe-cli
cssprobe-cli skill-install
```
---
## How It Works
```
┌───────────────────────────────────────────────────────────┐
│ AI Agent │
│ │
│ "Is .sidebar overflowing?" │
│ calls: cssprobe-cli inspect .sidebar │
│ │ │
└─────────────────────────────┬─────────────────────────────┘
│
▼
┌───────────────────────────────────────────────────────────┐
│ cssprobe-cli daemon │
│ │
│ collector (IIFE) → analyzer (pure) → renderer (md/json) │
│ │ │
│ ▼ │
│ Playwright browser (Chromium) │
│ getBoundingClientRect · getComputedStyle · DOM walk │
└───────────────────────────────────────────────────────────┘
```
1. **Session** — `open` spawns a daemon process managing a browser. Commands connect via Unix socket.
2. **Collector** — Injected as IIFE into the page. Walks the DOM, reads computed styles, declared values, and element metrics.
3. **Analyzer** — Pure Node.js functions. Classifies findings with confidence levels (DEFINITE / INDEFINITE / UNVERIFIABLE).
4. **Renderer** — Outputs Markdown (default) or JSON. Includes ASCII layout diagram, DOM tree, and findings.
---
## Quick Start
### 1. Inspect a Page
```bash
# Open browser (non-blocking, starts daemon session)
cssprobe-cli open https://getbootstrap.com/docs/5.3/examples/checkout
# Inspect with CSS selector
cssprobe-cli inspect .container
# Show ASCII layout diagram
cssprobe-cli layout .container
# Show only issues/warnings
cssprobe-cli findings .container
# Close browser when done
cssprobe-cli close
```
### 2. Login-Protected Pages
```bash
# Open browser in headed mode
cssprobe-cli open https://mysite.com --headed
# User manually logs in...
# Save session state for future use
cssprobe-cli state-save --name mysite
# Reopen with saved state (no login needed)
cssprobe-cli open https://mysite.com --state state.json --headed
```
### 3. JSON Output
```bash
cssprobe-cli inspect body --json
cssprobe-cli inspect body --json | jq '.findings[] | {id, confidence, message}'
```
### 4. Use as an MCP Server (AI clients)
cssprobe-cli ships an MCP server (stdio), so AI clients call it as a native tool — no shell needed:
```bash
cssprobe-cli mcp-install # auto-detect and configure installed clients
cssprobe-cli mcp-install --target=claude,cursor,codex
cssprobe-cli mcp-install --local # write project-scoped config
```
Manual configuration for any MCP client:
```json
{
"mcpServers": {
"cssprobe-cli": { "command": "cssprobe-cli", "args": ["mcp"] }
}
}
```
Exposes 10 tools: `cssprobe_open`, `cssprobe_inspect`, `cssprobe_tree`, `cssprobe_layout`,
`cssprobe_findings`, `cssprobe_eval`, `cssprobe_screenshot`, `cssprobe_inject_css`,
`cssprobe_close`, `cssprobe_status`. Remove with `cssprobe-cli mcp-uninstall`.
---
## Commands
### Session Management
| Command | Description |
|---------|-------------|
| `open [url]` | Open browser in session mode (non-blocking) |
| `open [url] --headed` | Show browser window |
| `open [url] --state <file>` | Open with saved cookies + localStorage |
| `open [url] --viewport 1280x720` | Custom viewport size |
| `open [url] --expect-url <regex>` | Fail unless the final URL matches the pattern |
| `open [url] --fail-on-login` | Fail fast when redirected to a login URL |
| `close` | Close browser session |
| `close --all` | Close all sessions across all workspaces |
| `status` | Show session status (URL, title, frame count) |
| `frames` | List frames: index, name, URL, main marker |
| `doctor` | Diagnose browser install vs Playwright expected revision |
### CSS Inspection
| Command | Description |
|---------|-------------|
| `inspect <selector>` | Full CSS diagnosis (computed values, declared values, findings) |
| `inspect <selector> --layout` | Include ASCII layout diagram |
| `inspect <selector> --brief` | Compact: tree sketch + warnings/errors only |
| `inspect <selector> --json` | Structured JSON output |
| `tree <selector>` | DOM tree structure |
| `layout <selector>` | ASCII layout diagram |
| `findings <selector>` | Only issues/warnings/errors |
Inspection commands accept `--frame <index|name|url-substring>` to target an iframe; the default is the main frame.
### CSS Injection
| Command | Description |
|---------|-------------|
| `inject-css <css>` | Inject CSS into current page |
### Browser
| Command | Description |
|---------|-------------|
| `resize <width> <height>` | Resize browser viewport |
| `eval <expression>` | Evaluate JavaScript (browser context); DOM nodes are auto-serialized; promises are awaited, `undefined` fields become `null` |
| `eval @file` / `eval -` | Read the expression from a file or stdin |
| `playwright <call>` | Execute Playwright API (Node.js context) |
| `screenshot` | Take screenshot (saves to ~/.cssprobe-cli/screenshots/, --out to override) |
### Interaction
| Command | Description |
|---------|-------------|
| `click <selector>` | Real mouse click (auto-scroll + actionability wait; works with custom React components) |
| `hover <selector>` | Real mouse hover |
| `type <selector> <text>` | Fill text (replaces value); `--press Enter` to submit; `--sequential` for real key events |
| `wait-for <selector>` | Wait for selector state (`--state visible\|hidden\|attached\|detached`, `--timeout ms`) |
| `wait-for --js "<predicate>"` | Wait until a JS predicate is truthy |
| `wait-for --network-idle` | Wait until the page is network-idle (page-level; no `--frame`) |
| `wait-for ... --frame <spec>` | Wait inside an iframe (selector/js modes) |
| `sample "<expr>"` | Sample an expression over time (`--interval`, `--duration`, `--key`, `--frame`); returns timeline + summary |
| `wait-stable "<expr>"` | Wait until an expression stops changing (`--tolerance`, `--window`, `--timeout`) |
| `run <file>` | Run a multi-step flow script |
`click` / `hover` support `--nth N` (1-based, default first match), `--force` (skip actionability checks) and `--timeout ms`. Without `--nth`, multiple matches fail (strict) to prevent wrong-target clicks.
### State
| Command | Description |
|---------|-------------|
| `state-import <file>` | Import cookies from Netscape format |
| `state-save` | Save current browser session state (cookies + localStorage) |
| `state-check` | Validate a state file: cookie count, expiry, target domain coverage (`--state`, `--url`) |
### Configuration
| Command | Description |
|---------|-------------|
| `config-show` | Show current config |
| `config-set <key> <value>` | Set config value |
| `config-list` | List all profiles |
| `config-use <name>` | Switch active profile |
| `config-new <name>` | Create new profile |
| `config-path` | Show config file path |
---
## Multi-step Flows
The interaction commands drive a page step by step; `run` turns a sequence into a versionable script:
```bash
cssprobe-cli open https://example.com/editor
cssprobe-cli wait-for .editor-canvas
cssprobe-cli click ".layout-switch" --nth 1
cssprobe-cli type ".block-title input" "Hello" --press Enter
cssprobe-cli inspect .block-grid --json
cssprobe-cli close
```
For repeatable flows, write a script and run it with one command. The script runs inside the daemon session (full Playwright `page` access):
```js
// flow.js
export default async ({ page, context, frames, newPage, click, waitFor, type, inspect, screenshot, expect, log }) => {
await page.goto('https://example.com/editor', { waitUntil: 'domcontentloaded' });
await waitFor('.editor-canvas', { state: 'visible', timeout: 10000 });
await click('.layout-switch', { nth: 1 });
await waitFor({ js: "() => document.body.dataset.layout === 'two-col'" });
await type('.block-title input', 'Hello', { press: 'Enter', sequential: true, clear: true });
await waitFor({ networkIdle: true });
const { findings } = await inspect('.block-grid', { format: 'json' });
log('findings:', findings.length);
await screenshot({ path: './after-layout-switch.png' });
return { findings: findings.length };
};
```
```bash
cssprobe-cli run ./flow.js # --timeout ms for the whole script (default 300000, 0 = unlimited)
```
Scripts must `default-export` an async function receiving the API object: `page`, `context`, `frames()`, `newPage()`, `click`, `hover`, `type`, `waitFor`, `inspect`, `screenshot`, `expect`, `log`. `waitFor` accepts `{ state, timeout, frame }`. Uncaught page errors are collected into the result envelope as `pageErrors`. `screenshot` relative paths resolve against the script's directory; logs (`log()` and `console.*`) are collected and returned with the result. On failure the command exits `1`, prints the collected logs, and keeps the session alive.
> Scripts are executed by the local daemon with full Node privileges — only run trusted scripts.
---
## Scenario Suites (Regression)
Run repeatable regression cases from a JSON suite: each case opens a fresh browser, executes steps, asserts on saved values, and writes artifacts + an optional JUnit report.
```bash
cssprobe-cli scenario run suites/ticket-26571394.json \
--state ~/.cssprobe-cli/states/preprod.json \
--artifacts out/ticket-26571394 \
--junit out/junit.xml
```
```json
{
"name": "ticket 26571394 preprod",
"defaults": { "viewport": { "width": 1280, "height": 800 } },
"cases": [
{
"name": "refresh keeps the hash section aligned",
"url": "https://example.com/#listen-to-tcmi",
"steps": [
{ "do": "sample", "expression": "({ hash: location.hash, top: Math.round(document.querySelector('#listen-to-tcmi')?.getBoundingClientRect().top ?? -1) })", "interval": 250, "duration": 12000, "key": "top", "tailFrom": 9500, "save": "timeline" },
{ "do": "assert", "label": "hash retained", "on": "timeline", "path": "summary.last.value.hash", "eq": "#listen-to-tcmi" },
{ "do": "assert", "label": "anchor stable", "on": "timeline", "path": "summary.tail.min", "min": 70 },
{ "do": "assert", "label": "anchor stable", "on": "timeline", "path": "summary.tail.max", "max": 90 }
]
},
{
"name": "deployment fingerprint",
"url": "https://example.com/",
"steps": [
{ "do": "waitFor", "js": "() => !!window.DEBUG?.NavigatorStore", "timeout": 20000 },
{ "do": "assert", "globals": { "DEBUG.NavigatorStore.setHashRestoreProtected": "function" }, "absent": ["DEBUG.NavigatorStore.navigateToSectionId"] }
]
}
]
}
```
Steps: `goto`, `reload`, `wait`, `eval`, `sample`, `waitStable`, `click`, `waitFor`, `screenshot`, `assert`. `wait` takes a fixed `ms`; `waitFor` supports `frame` for in-iframe selector/js waits. `sample` and `eval` save values with `save: "<name>"`; assertions read them with `on: "<name>"` + `path`, or check live `state` (`url`, `title`, `frames`), `url`, `globals`, `absent`. `sample` supports `key`, `tailFrom`, `ignoreErrors`; every run writes `summary.json`, per-case `case.json`, sample series and failure screenshots.
**Iframe and preview pages.** A page can contain hidden helper iframes (`about:blank`); use `frames` to pick the real one, and `state: "attached"` when waiting for a frame element that is not visible. Preview frames can take 6–8s to render and analytics requests keep `networkIdle` busy — prefer a fixed `wait` step followed by a `waitFor` inside the frame (`frame: "i/preview"`) instead of `networkIdle`.
## Confidence Model
Every finding carries a confidence level:
| Level | Meaning |
|-------|---------|
| **DEFINITE** | Based on computed values (facts from getComputedStyle) or accessible declared values |
| **INDEFINITE** | Declared value uses `%` — resolves at runtime |
| **UNVERIFIABLE** | Declared value missing or from blocked cross-origin stylesheet |
The report header shows: `confidence: DEFINITE 8 | INDEFINITE 0 | UNVERIFIABLE 1`
---
## Complementary Tool: cssgraph
cssprobe-cli is a **runtime** CSS probe — it opens a real browser and reads what is actually rendered. For **static** questions about the source code — where is `.btn-primary` defined, what cascades over it, which components reference it, what would change if I edited it — use [cssgraph](https://github.com/mack-peng/cssgraph), a local SQLite knowledge graph of every className, property, variable, and at-rule in your stylesheets. The two tools complement each other:
| | cssgraph (static) | cssprobe-cli (runtime) |
|---|---|---|
| Analyzes | source code (CSS/SCSS/Less + JSX/TSX + templates) | live browser (computed styles + DOM) |
| Answers | where a class is defined, cascade, impact, unused CSS | actual rendered layout, overflow, scroll, height chains |
| When | before/without a browser | after static analysis, to verify at runtime |
| Usage | `cssgraph init` + `explore`/`rule`/`impact` | `open` + `inspect`/`layout`/`findings` |
**Recommended workflow for a style problem** — go static first, then verify at runtime:
```bash
# 1. Static: find the definitions, cascade, and blast radius (cssgraph)
cssgraph explore .btn-primary
cssgraph cascade .btn-primary
# 2. Runtime: confirm what actually renders (cssprobe-cli)
cssprobe-cli open https://example.com
cssprobe-cli inspect .btn-primary
cssprobe-cli findings .btn-primary
```
Install: `npm install -g cssgraph` (requires Node.js >= 22.5.0). It exposes 12 MCP tools (`cssgraph_explore`, `cssgraph_rule`, `cssgraph_impact`, …) plus CLI commands. See [npm](https://www.npmjs.com/package/cssgraph) / [GitHub](https://github.com/mack-peng/cssgraph).
---
## Configuration
```bash
# Set values
cssprobe-cli config-set browser chromium
cssprobe-cli config-set depth 8
cssprobe-cli config-set viewport 375x812
# Profile management
cssprobe-cli config-new staging
cssprobe-cli -p staging config-set browser firefox
cssprobe-cli config-use staging
```
Priority: CLI flags > Environment variables > Config file
```
--browser CSSPROBE_BROWSER
--depth CSSPROBE_DEPTH
--headed CSSPROBE_HEADED
--viewport CSSPROBE_VIEWPORT
-p, --profile CSSPROBE_PROFILE
```
Config file: `~/.cssprobe-clirc`
---
## Supported Platforms
| Platform | Architectures |
|----------|---------------|
| macOS | x64, arm64 |
| Linux | x64, arm64 |
| Windows | x64, arm64 |
---
## Development
```bash
npm install
npm run build # tsc + esbuild collector + daemon entry + generate help.json
npm test # Run unit tests (58 tests)
npx tsc --noEmit # Type check only
```
## If cssprobe-cli is useful
A ⭐ star helps other developers discover it — and tells us to keep shipping.
---
## License
MIT
This server cannot be deployed
Maintenance
ActivityActive
ResponsivenessUnresponsive