@flowslens/cli
v1.1.2
Published
Trace any user action from the UI to the database. Maps React/Next + NestJS/Express + Prisma/Mongoose features, shows what a change would break, and serves a local dashboard.
Maintainers
Readme
@flowslens/cli
Trace any user action from the UI to the database.
You join a project. You need to change one screen. So you start clicking through files: which handler runs, which endpoint it calls, which service answers, which collection it writes. Eight tools and an afternoon later you still do not know what else writes that collection.
Flowslens reads your source and answers in one command.
npx @flowslens/cli scan .It only reads files. It never connects to a database, never runs your code, and never writes anything into your project.
First: will this work on my project?
Flowslens reads a specific set of stacks. Check here before installing — if your stack is on the right, you will get a file count and little else.
| It reads today | Not yet |
| --------------------------------------------------------------------------- | ----------------------------------- |
| React and Next.js (pages/ and App Router) | Vue, Svelte, Angular, Astro |
| NestJS, Express, Fastify | Django, Rails, Go, Java, .NET, PHP |
| Next.js pages/api + App Router handlers, Nuxt server/api | GraphQL and tRPC resolvers |
| MongoDB via Mongoose, and the native driver | Prisma, TypeORM, Sequelize, raw SQL |
| TypeScript or plain JavaScript with JSX (.js, .jsx, .mjs, .cjs) | Queues, cron jobs, websockets |
So the sweet spot is React/Next + NestJS or Express + Mongoose. Any folder layout of those works — Flowslens decides what a file is by reading it, not by which directory it sits in, and a frontend and backend in separate repositories is a first-class case.
Point it at something unsupported and it says so plainly, rather than reporting zero and letting you assume it is broken.
Install
npm install -g @flowslens/cliOr run it without installing anything:
npx @flowslens/cli scan .Requires Node 18.18 or newer. Nothing else — no database, no config file, no plugin in your project.
The package is
@flowslens/cli. The command isflowlens. The extrasis in the npm scope only.
Your first two minutes
1. Scan
cd ~/code/my-app
flowlens scanThis is the only command you need to remember. It prints what it found, what looks wrong, every feature it can trace, and the command to run next:
Feature flows (6)
─────────────────
id action endpoint collections
──────────────────────────── ─────────────────── ───────────────────────────── ───────────────────────────────────
customerspage-delete Customers · Delete DELETE /customers/:param customers,auditlogs
orderform-submit-order Submit Order POST /orders customers,products,auditlogs,orders
customerform-create-customer Create Customer POST /customers auditlogs,customers
next: flowlens flow customerspage-delete
or: flowlens serve2. Pick a feature and follow it
Copy any id from that table:
flowlens flow orderform-submit-orderSubmit Order (orderform-submit-order)
web/src/components/OrderForm.tsx:42
risk high (50) evidence static
USER ACTION
└── [ui action] Submit Order
web/src/components/OrderForm.tsx:42
│
▼
FRONTEND
└── [handler] OrderForm.handleSubmit
web/src/components/OrderForm.tsx:15 sets: note, products
│
▼
NETWORK
├── [api call] POST /orders
│ web/src/components/OrderForm.tsx:16 body: customerId, products, note
└── [route] POST /orders
api/src/orders/orders.controller.ts:9 dto: CreateOrderDto
│
▼
BACKEND
├── [method] OrdersController.create
├── [method] OrdersService.create
└── [method] AuditService.record
│
▼
DATABASE
├── [db op] orders.create schema: Order create
├── [db op] auditlogs.create schema: AuditLog create
└── [db op] customers.findById schema: Customer read3. Or click around instead
flowlens serveA local dashboard on http://127.0.0.1:4177, which asks six questions about the
feature you have open. Each tab carries its own headline number, so the worrying
one is visible before you open it:
Flow · 24 APIs · 1 Timing · no runs Breaks · 2 Tests · none Changed · 5| Tab | The question | Where the answer comes from |
| ----------- | ----------------------------------------- | -------------------------------------------------------------- |
| Flow | What happens when a user does this? | click → handler → request → route → service → collection |
| APIs | What exactly does it request? | body, guards, DTO, the code it runs, every collection, callers |
| Timing | Where does the time go? | runtime spans only — no spans, no numbers, never an estimate |
| Breaks | What else would a change here break? | the graph walked backwards from every step |
| Tests | What would catch it if you broke it? | which test files import the files this flow runs through |
| Changed | What do my uncommitted edits put at risk? | git status crossed with the graph |
Breaks is the one that changes how you work. A flow read on its own is quietly misleading: most of the chain is shared, and editing one service because one screen needs a field is a five-minute change that breaks four other screens. Breaks splits the same steps into "shared with other features" and "only this one uses", and keeps infrastructure — a toast hook, a cache, an audit trail — out of the way so the real findings are not competing with wallpaper.
Changed needs no instrumentation and no tests, so it works on the first run:
high risk 1 changed file is used by 5 features; 5 of them have no test.An action that makes several requests is shown as a sequence, and the shapes are told apart rather than lumped together:
| In your code | What the APIs tab says |
| --------------------------------------------------------- | --------------------------------------------- |
| const a = await get(); post({ id: a.id }) | needs the response from GET … |
| post(…).then(() => put(…)) | only after POST … resolves |
| Promise.all([get(a), get(b)]) | sent at the same time as GET … |
| useEffect(() => get(…), [user]) + a call setting user | re-runs when the state set by GET … arrives |
| if (isEdit) put() else post() | one step, two alternatives, joined with or |
| a call inside a catch | only when the request fails |
Every file:line in every tab opens your editor (?editor=vscode, cursor,
idea, zed, …).
That is the whole workflow. Everything below is for when you want more.
Four more questions it answers
# "What is this file I'm reading?" — which features run through this line
flowlens where src/components/OrderForm.tsx:20
# "If I change this, what breaks?"
flowlens impact AuditService.record
# "What is already wrong here?" — broken calls, dead endpoints, shared writes
flowlens doctor
# "Write the docs for me" — a feature document in markdown
flowlens flow orderform-submit-order --markdown > docs/submit-order.mdimpact is the one to reach for before a refactor: it reports the blast radius
and every user-visible feature that would be affected.
Something went wrong
flowlens: command not found — either the global install is not on your
PATH, or you skipped it. Use npx @flowslens/cli <command> instead, which
always works.
"No flows found" or an almost-empty report. Flowslens prints the reason under
Notes. The three common ones:
| Note says | Fix |
| ------------------------------------- | ------------------------------------------------------------ |
| Frontend found, but no backend routes | Your backend is a separate repo: flowlens scan ./web ./api |
| No API calls detected | Your requests go through a house-built wrapper — see below |
| Contains 40 .vue files, not parsed | Unsupported stack; see the table at the top |
Your team wraps HTTP in its own helpers. Flowslens already reads the common shape — verb in the function name, path in an options object:
getRequest({ url: getUsersList }); // GET /users
patchRequestNoLoader({ url: getUser, params: `/${id}` }); // PATCH /users/:idIf yours is named differently, describe it once (capture group 1 is the verb):
flowlens scan --request-fn '^(get|post|put|patch|delete)Api'Routes and calls match nothing, but both were found. Usually a global
prefix. /api is stripped from both sides by default; change it with
--api-prefix /v2.
Your terminal draws boxes as garbage. FLOWLENS_ASCII=1 flowlens flows, or
NO_COLOR=1 to drop colour.
SyntaxError on an old Node. You need 18.18 or newer; node --version.
Commands
| Command | What it answers |
| ------------------------------- | --------------------------------------------------- |
| flowlens stack [project] | What is this built with? Frameworks, with versions. |
| flowlens scan [project] | Build the graph, and list what it found. |
| flowlens flows [project] | Which user actions reach the backend? |
| flowlens flow <id> | Everything one click does, end to end. |
| flowlens flow <id> --markdown | Generate a living feature document. |
| flowlens where <file>:<line> | What is this code for? Features running through it. |
| flowlens impact <symbol> | If I change this, what breaks? |
| flowlens doctor [project] | Broken API calls, dead endpoints, shared writes. |
| flowlens serve [project] | The dashboard. |
| flowlens init [project] | Detect the layout and write flowlens.config.json. |
| flowlens trace [project] | Merge recorded runtime spans into the graph. |
[project] defaults to the current directory, and scan works from any
subdirectory of it. Add --json to any command for machine-readable output.
Two repositories, one graph
A frontend and backend in sibling folders is the case Flowslens is built for — the seam between them is the whole point:
flowlens scan ./my-web ./my-api
flowlens scan ./api ./web ./mobile # several consumers of one APIScanning every consumer at once also sharpens the findings: an endpoint that looks dead against one frontend may simply be called by the mobile app.
Save your settings
Tired of retyping flags? flowlens init writes a flowlens.config.json you can
commit, so everyone on the team gets the same graph:
{
// Scanned together when no paths are given on the command line.
"roots": [".", "../shop-api"],
// Stripped from BOTH frontend URLs and backend routes.
"apiPrefixes": ["/api"],
// Your request layer: capture group 1 is the HTTP verb.
"requestFunctionPattern": "^(get|post|put|patch|delete)Request",
"ignore": ["legacy", "generated"],
}Paths in roots are resolved relative to the config file and should use /, so
one committed file works for everyone on any OS.
Where it puts things
Nothing goes into your project. The graph lives in your OS cache, keyed by
project path, so git status after a scan is empty:
| OS | Location |
| ------- | ---------------------------------------------------- |
| Linux | $XDG_CACHE_HOME/flowlens, else ~/.cache/flowlens |
| macOS | ~/Library/Caches/flowlens |
| Windows | %LOCALAPPDATA%\flowlens\Cache |
scan and serve both print the exact path. FLOWLENS_CACHE moves it.
flowlens init is the one command that writes to your project, because writing
a config file is what you asked it to do.
Optional: prove it actually ran
Everything above is read from source, so a step means "this path can run".
Add @flowslens/runtime to
your app and a step becomes confirmed — it did run, and here is how long it
took. Entirely optional; the CLI works without it.
Related packages
@flowslens/core— the graph engine, if you want the graph programmatically instead of a report.@flowslens/runtime— optional development-only tracer.
Documentation
Full README, architecture notes and roadmap: This page is the documentation. Questions and bug reports: [email protected].
Licence
MIT
