Skip to main content
Glama
README.md
<div align="center">

# flowlens

**See every user flow of your app, how well your E2E tests cover it, and the Playwright evidence behind it. All in one local viewer.**

[![CI](https://github.com/jlaguna553/flowlens/actions/workflows/ci.yml/badge.svg)](https://github.com/jlaguna553/flowlens/actions/workflows/ci.yml)
[![License: MIT](https://img.shields.io/badge/license-MIT-blue.svg)](LICENSE)
![Node >= 20](https://img.shields.io/badge/node-%3E%3D20-339933)
![MCP plugin](https://img.shields.io/badge/MCP-plugin-8A2BE2)

**English** · [Español](README.es.md)

<img src="docs/images/galaxy.png" alt="flowlens galaxy view: 667 nodes, 749 edges and 67 flows of a synthetic e-commerce platform" width="100%">

<sub>Galaxy view of a synthetic project: 67 flows, 667 nodes, 749 edges. Bigger stars are the nodes shared by more flows.</sub>

</div>

---

flowlens maps a repository's **user flows** (UI action → API call → database → decision), shows
which of them your end-to-end tests actually cover, and embeds the Playwright **video, trace
and steps** next to the graph. It also ships an **MCP plugin**, so a local AI agent (opencode,
Claude Code, ...) can map the flows, stand up a local test environment and write the tests
that are missing.

flowlens is **generic and non-invasive**: it stores nothing about the repository it analyzes.
Config, flows, labels, environment and build output live **outside the target repo**, in a data
dir you control (default `~/.flowlens/projects/<project>/`). Nothing lands in your commits.

## Highlights

- **Flow graph that scales**: hundreds of nodes stay readable. Friendly names, coverage colors,
  coverage filter, search, shared nodes rendered once.
- **Four layouts**: Graphify (force graph), **Galaxy** (constellation with starfield), **Org
  chart** (top-down tree) and **3D** (interactive Three.js).
- **Evidence per flow**: video, Playwright trace viewer, step list and a Mermaid sequence
  diagram, synced with the graph while the test plays.
- **Coverage from facts**: derived from your Playwright results, not from guesses.
- **Local test environment** declared once in `env.json` and driven with Docker.
- **MCP plugin** (`flowlens mcp`) that exposes the whole pipeline as tools for a local agent,
  plus an **Implement test** panel that launches your agent to write the missing spec.
- **English and Spanish UI**, light / dark / system themes, works fully offline.
- Plain Node.js, a single tiny runtime dependency ([cross-spawn](https://github.com/moxystudio/node-cross-spawn)).

## Gallery

<table>
  <tr>
    <td width="50%"><img src="docs/images/graph-overview.png" alt="Graphify force-graph layout"><br><sub><b>Graphify layout</b> · force-directed graph colored by flow</sub></td>
    <td width="50%"><img src="docs/images/org-chart.png" alt="Org chart layout"><br><sub><b>Org chart layout</b> · top-down hierarchy, wide levels wrap into rows</sub></td>
  </tr>
  <tr>
    <td width="50%"><img src="docs/images/graph-3d.png" alt="3D layout"><br><sub><b>3D layout</b> · drag to rotate, scroll to zoom</sub></td>
    <td width="50%"><img src="docs/images/flow-focus.png" alt="Focusing one flow among hundreds of nodes"><br><sub><b>Flow focus</b> · pick a flow to isolate its path and preview its sequence diagram</sub></td>
  </tr>
  <tr>
    <td width="50%"><img src="docs/images/evidence-trace.png" alt="Embedded Playwright trace viewer"><br><sub><b>Evidence</b> · the Playwright trace opens inside flowlens, in a floating, maximizable window</sub></td>
    <td width="50%"><img src="docs/images/galaxy-es.png" alt="Spanish UI"><br><sub><b>Spanish UI</b> · switch language from the side panel</sub></td>
  </tr>
  <tr>
    <td colspan="2"><img src="docs/images/graph-light.png" alt="Light theme"><br><sub><b>Light theme</b> · also dark and system</sub></td>
  </tr>
</table>

All big-graph screenshots come from the synthetic project in [examples/large](examples/large)
(no real project data); you can regenerate and explore it yourself, see below.

## Quick start

Requirements: **Node.js >= 20**. Docker is optional (only for `flowlens env`). A local agent
(opencode, Claude Code, ...) is needed only for the AI features.

### 1. Try it in 30 seconds

```sh
git clone https://github.com/jlaguna553/flowlens
cd flowlens
npm install
npm run demo          # builds and serves the 667-node synthetic project
```

Open <http://127.0.0.1:8017>, then switch between **Graphify / Galaxy / Org chart / 3D** in the
top-left corner. (The warnings about a missing Playwright report are expected: the demo has no test run.)

### 2. Point it at your own repository

```sh
npm install -g github:jlaguna553/flowlens      # provides the `flowlens` command
flowlens init --suite /path/to/target-repo --project my-app
flowlens serve --project my-app
```

`init` scaffolds the project data, scans the repo and **launches your local agent to map the
flows automatically**. The viewer shows the result. No installation inside the target repo.

> flowlens is not published on the npm registry yet, so install it from GitHub. You can also
> run it without installing: `npx -y github:jlaguna553/flowlens init --suite ... --project ...`.
> The binary is always called `flowlens`.

## How it works

```
 target repo ──scan──▶ agent maps flows ──▶ flows.json ──┐
                                                         ├──▶ build ──▶ graph.json + viewer
 Playwright run ──flowlens reporter──▶ run.json ─────────┘          (coverage, video, trace)
```

A flow is a small graph of nodes:

| Kind | Meaning |
| --- | --- |
| `interaction` | a user action in the UI |
| `backend` | an API call / endpoint |
| `db` | a database step |
| `decision` | a branch or outcome |

Each flow has a coverage state: `covered`, `partial` or `uncovered`. Coverage is derived from the
Playwright results at build time.

### The agent workflow (3 capabilities)

1. **Map the flows.** `init` (or `flowlens map` later) lets the agent read specs, frontend routes
   and backend controllers, write a draft, and flowlens validates and persists it.
2. **Stand up the local test env.** Declare it once in `env.json`; the agent previews with
   `flowlens_env_up { dryRun: true }`, then brings it up with Docker, waits for health checks and runs seeds.
3. **Implement the missing tests.** For every `uncovered` / `partial` flow the agent gets a task
   (suggested spec, reporter command, acceptance criteria), writes the spec, runs it and ingests the results.

The full reference (every MCP tool, payloads, env block, agent block, endpoints) is in
[docs/agent-plugin.md](docs/agent-plugin.md) ([Español](docs/agent-plugin.es.md)).

## Configure it in another repo

`init` writes everything **outside the target repo**:

```
~/.flowlens/projects/my-app/
  flowlens.config.json   suite path + flows pointer (+ optional agent/env blocks)
  flows.json             the flow graph the agent authors
  labels.json            optional friendly-name patterns for backend/db labels
  env.json               optional local E2E environment declaration
  out/                   built graph.json + viewer (index.html, vendor, trace assets)
  env/                   generated compose override, logs, notes, state
```

| `init` flag | Meaning |
| --- | --- |
| `--suite <path>` | **Required.** Target repo or its E2E suite root. |
| `--project <name>` | Project name. Defaults to the suite directory name. |
| `--data-dir <dir>` | Data dir. Defaults to `~/.flowlens/projects` or `FLOWLENS_DATA_DIR`. |
| `--agent opencode\|claude` | Agent used to map flows. Defaults to the agent that launched `init`, else the first installed one. |
| `--model <provider/model>` | Preferred AI model. flowlens probes it and falls back to another reachable model. |
| `--tests-dir <dir>` | Project-relative folder for generated specs (default `tests`). |
| `--isolated-tests` | Generate specs under `<dataDir>/<project>/tests` instead of the target repo. |
| `--no-map` | Skip the automatic flow mapping. |
| `--timeout <ms>` | Kill the mapping agent after this many milliseconds. |
| `--force` | Overwrite an existing config / flows / AGENTS skeleton. |

Every other command accepts `--project <name>` **or** `--config <path>`, plus `--data-dir <dir>`.
With neither, flowlens looks for `flowlens.config.json` in the current directory, then for
`<dataDir>/<cwd-name>/flowlens.config.json`.

### Commands

| Command | What it does |
| --- | --- |
| `flowlens init` | Scaffold a project and map its flows with your agent. |
| `flowlens map [--dry-run] [--agent ...] [--model ...]` | (Re)map the flows. `--dry-run` prints the prompt only. |
| `flowlens write-flows --file <draft.json>` | Validate a draft against `schemaVersion` 1 and persist it. |
| `flowlens build [--run <path>] [--report <dir>] [--out <dir>] [--lang en\|es]` | Build `graph.json` and the viewer. `--lang` picks the language of generated friendly names (default `en`; also `"language"` in the config). |
| `flowlens serve [--port 8017]` | Serve the viewer. |
| `flowlens env up\|status\|down [--dry-run]` | Manage the local E2E environment. |
| `flowlens mcp` | Start the MCP server over stdio. |
| `flowlens reporter-path` | Print the path of the bundled Playwright reporter. |

## Wire the MCP plugin

flowlens speaks MCP over stdio. Add it to your agent:

**opencode** (`opencode.json`)

```json
{
  "$schema": "https://opencode.ai/config.json",
  "mcp": {
    "flowlens": {
      "type": "local",
      "enabled": true,
      "command": ["npx", "-y", "github:jlaguna553/flowlens", "mcp", "--project", "my-app"]
    }
  }
}
```

**Claude Desktop** (`claude_desktop_config.json`) and **Claude Code** (`.mcp.json`)

```json
{
  "mcpServers": {
    "flowlens": {
      "command": "npx",
      "args": ["-y", "github:jlaguna553/flowlens", "mcp", "--project", "my-app"]
    }
  }
}
```

If you installed it globally you can use `"command": "flowlens"` with `"args": ["mcp", "--project", "my-app"]`.
Add `--config <path>` or `--data-dir <dir>` to keep project data elsewhere.

| Tool | Inputs |
| --- | --- |
| `flowlens_status` | none |
| `flowlens_build` | none |
| `flowlens_serve` | `{ port?, host? }` (default port `8020`) |
| `flowlens_scan` | `{ repoPath }` |
| `flowlens_scaffold_flows` | `{ repoPath, suite?, project? }` |
| `flowlens_write_flows` | `{ flows, configPath? }` |
| `flowlens_coverage` | none |
| `flowlens_uncovered_tasks` | none |
| `flowlens_implement_test` | `{ flowId, dryRun? }` |
| `flowlens_ingest_results` | `{ runPath?, reportDir?, configPath? }` |
| `flowlens_env_up` | `{ dryRun?, configPath?, envPath?, port? }` |
| `flowlens_env_down` | `{ configPath?, envPath? }` |
| `flowlens_env_status` | `{ configPath?, envPath? }` |

`flowlens_scan`, `flowlens_scaffold_flows` and the `repoPath` they receive are **read-only**. All
writes go under the project data dir. The one exception is `flowlens_implement_test`, which launches
your configured local agent in the suite directory, so that agent may write the suggested spec inside
the target repo (that is the point).

## Evidence: the reporter

Coverage and evidence come from Playwright runs. Point Playwright at the bundled reporter and a run
JSON inside the data dir:

```sh
cd /path/to/target-repo/e2e
REPORTER=$(flowlens reporter-path)
FLOWLENS_RUN_OUTPUT=~/.flowlens/projects/my-app/out/run.json \
  npx playwright test \
  --reporter="$REPORTER" \
  --output=~/.flowlens/projects/my-app/out/test-results

flowlens build --project my-app --run ~/.flowlens/projects/my-app/out/run.json
```

The reporter refuses to run unless `FLOWLENS_RUN_OUTPUT` is set. Enable `trace: 'on'` and
`video: 'on'` in your Playwright config to get the Trace and Video tabs. With no `--run` and no
`playwright-report/`, `build` emits the flow graph without test results.

## The viewer

```sh
flowlens serve --project my-app --port 8017
```

- **Graph** colored by flow and by node kind (`Front`, `Back`, `BD`, `Decision`) in four layouts:
  Graphify, Galaxy, Org chart and 3D. In Galaxy, labels appear as you zoom in so large graphs stay readable.
- **Collapsible, resizable sections**: Node Info, Evidence, Kinds, Flows. The state is remembered.
- **Node Info**: label, endpoint, kind, coverage, payload, response, neighbors and type-specific
  details (frontend file/line/component/selector, backend source and payload example, database table
  and fields). Shared nodes list every flow that uses them.
- **Flows legend** with a **coverage filter** (All / Covered / Partial / Uncovered). Hover a flow for
  its Mermaid sequence preview; click it to zoom and highlight the flow (`Clear` resets focus).
- **Hover a node** for a compact info card; **click a node** to focus its flow and jump the video to
  that step. **Hover an edge** for a large sequence preview.
- **Playback bar** with speed from `0.1x` to `2x`; a cursor animates along the flow while the test plays.
- **Evidence tabs**: **Video**, **Trace**, **Steps**, **Sequence** (Mermaid, zoom and full screen) and
  **Implement** (spec, command, acceptance criteria and the **Run agent** button with a live log).
- **Search** across nodes, light / dark / system themes, English / Spanish.

## `flows.json` (schemaVersion 1)

```json
{
  "schemaVersion": 1,
  "project": "my-app",
  "suite": { "root": "e2e", "projectName": "chromium" },
  "flows": [
    {
      "id": "F1",
      "title": "Sign in",
      "coverage": "covered",
      "actors": ["user"],
      "nodes": [
        { "id": "login", "label": "Login", "kind": "interaction", "sharedKey": "auth.login" },
        { "id": "auth", "label": "POST /auth/login", "kind": "backend", "endpoint": "POST /auth/login" }
      ],
      "edges": [["login", "auth"]],
      "tests": [{ "spec": "auth.spec.ts", "title": "signs in" }],
      "mermaid": "sequenceDiagram\n  actor User\n  participant UI\n  participant API\n  User->>UI: Login\n  UI->>API: POST /auth/login\n  API-->>UI: 200 + token"
    }
  ],
  "moduleEdges": []
}
```

- `coverage` is `covered|partial|uncovered`; node `kind` is `interaction|backend|db|decision`.
- `edges` are `[from,to]` pairs of existing node ids; `tests` are `{ spec, title }` join keys.
- Node ids only need to be unique inside a flow. Reuse the same `id` or `sharedKey` for a step shared by
  several flows; backend/db nodes with the same `endpoint` merge automatically.
- `mermaid` is optional but recommended (powers the **Sequence** tab). Nodes can carry source metadata
  (`file`, `line`, `component`, `selector`, `payloadExample`, `table`, `dbFields`); omit what you cannot verify.

## Agent and test settings

The agent is configured in `flowlens.config.json` (an `agent` block) or `agent.json`:

```json
{
  "suite": "/absolute/path/to/target-repo/e2e",
  "projectName": "my-app",
  "flows": "flows.json",
  "language": "en",
  "agent": { "command": "claude", "argsTemplate": ["-p", "{{prompt}}"] },
  "tests": { "mode": "project", "dir": "tests" }
}
```

- Default: `{ "command": "opencode", "argsTemplate": ["run", "{{prompt}}"] }`. For Claude Code use
  `{ "command": "claude", "argsTemplate": ["-p", "{{prompt}}"] }`.
- An **array** `argsTemplate` is spawned with `shell: false` through cross-spawn, so the prompt is a
  single argv element and nothing is shell-interpreted. A **string** is a shell command line (POSIX
  only). `agent.env` adds environment variables to the child; never store secrets there.
- `tests.mode` is `project` (specs inside the target repo) or `isolated` (specs under the flowlens data dir,
  with a generated `playwright.isolated.config.ts`).
- With opencode, `init` / `map` / `serve` probe the configured model (`--model`, `FLOWLENS_MODEL` or
  `agent.model`) and persist the first reachable one in `agent.argsTemplate`.
- `suite` is resolved relative to the **config file**; `init` writes it as an absolute path. Use an
  absolute path if you write the config by hand.
- **Windows:** use the array form. flowlens resolves `opencode.cmd` / `claude.cmd` through cross-spawn
  and builds test commands with `cd /d` and `set "VAR=..."`.

## Troubleshooting

- **Browser dependencies missing.** `init` warns when `libnspr4.so` / `libnss3.so` are absent. If you run
  Playwright on the host: `sudo apt-get install -y libnspr4 libnss3 libasound2`. Suites that run in Docker can ignore it.
- **Docker not available.** `flowlens env up` needs the `docker` CLI. Use `--dry-run` to inspect the commands.
- **Port in use.** Pass another `--port` to `serve` / `mcp`, or `{ port }` to `flowlens_serve`.
- **MCP not loading.** Run the exact command from your config by hand. If it exits immediately, check
  Node >= 20 and that `--project` / `--config` points to an existing `flowlens.config.json`.
- **Implement test fails.** The panel log prints `[flowlens] agent command` and `agent cwd`. Typical causes:
  the agent is not installed where `flowlens serve` runs, `suite` is not absolute, the flow id is wrong
  (ids are case-sensitive; rebuild first), or the viewer was opened as a file instead of through `flowlens serve`.
  If a pinned model fails, test it by hand with `opencode run "Reply with exactly: OK"`.
- **Automatic mapping failed.** `init` keeps the scaffolded files and warns. Retry with `flowlens map`
  (`--dry-run` shows the prompt, `--agent claude` switches agent, `--model provider/model` picks a model).
- **Model not reachable.** Run `opencode models`, then pass `--model`, `FLOWLENS_MODEL` or `agent.model`.
- **Generated labels in the wrong language.** Friendly names for API/DB nodes are generated at build time:
  `flowlens build --lang es` (or `"language": "es"` in the config).

## Privacy

The flowlens repository contains no project data. Config, flows, labels, env and build output live under
`<dataDir>/<project>/`. The only committed examples are synthetic: [examples/demo](examples/demo) and
[examples/large](examples/large). If something must live inside the target repo, flowlens records it in
`.git/info/exclude`, never in the tracked `.gitignore`. The viewer binds to `127.0.0.1` and loads nothing
from the network.

## Credits and licenses

flowlens stands on the shoulders of other open-source projects:

- **[Graphify](https://github.com/Graphify-Labs/graphify)** (Apache-2.0, by Safi Shamsi and contributors):
  the flowlens viewer template is ported and modified from Graphify's HTML exporter. Thank you!
- **[Playwright](https://playwright.dev)** (Apache-2.0, Microsoft): flowlens reads its results and traces,
  ships a reporter for it and embeds its trace viewer. Playwright is not bundled in this repository.
- **[vis-network](https://visjs.github.io/vis-network/)**, **[Mermaid](https://mermaid.js.org/)**,
  **[3d-force-graph](https://github.com/vasturiano/3d-force-graph)** / **[three.js](https://threejs.org/)**
  and **[cross-spawn](https://github.com/moxystudio/node-cross-spawn)**.

flowlens itself is released under the [MIT License](LICENSE). Third-party code keeps its own license;
see [NOTICE](NOTICE) and [THIRD_PARTY_NOTICES.md](THIRD_PARTY_NOTICES.md) for the full list, versions and
license texts. flowlens is an independent project, not affiliated with or endorsed by Microsoft or Graphify;
"Playwright" is a trademark of Microsoft Corporation.

## Development

```sh
git clone https://github.com/jlaguna553/flowlens && cd flowlens
npm install
npm test                                   # node --test, runs on Linux and Windows in CI
node src/cli.mjs init --suite /path/to/repo --project my-app
npm run build:viewer                       # regenerate src/viewer/render.mjs (needs Node >= 22.13)
npm run demo:generate                      # regenerate examples/large/flows.json (deterministic)
```

Edit `src/viewer/render.ts`, never `render.mjs` (generated). Contributions are welcome: open an issue or a pull request.

## License

[MIT](LICENSE) © 2026 Javier Laguna. Includes third-party components under their own licenses, see [NOTICE](NOTICE).