npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

persona-review

v0.6.3

Published

AI persona feedback tool for web pages — visit a page as a simulated persona and get reactions, friction points, and suggestions.

Readme

persona-review

AI persona feedback for non-profit, advocacy, charity, and social-cause web pages. You give it a URL and a persona; it visits the page and tells you what that persona noticed, liked, found confusing, and where they'd give up. Reactions are written in the page's own language, as a native speaker.


What it does today

  1. Loads the URL you give it in a real headless Chromium (Playwright). Waits for network idle and a small cushion past DOMContentLoaded so the persona doesn't critique a half-booted UI.

  2. Captures the page's accessibility tree, the page language (<html lang>), and a JPEG screenshot of the top viewport.

  3. Runs an agent loop as one of the built-in persona archetypes: observe → choose an action (scroll / click / type) → observe again → repeat. Form submission is blocked. The persona narrates only at the end.

  4. Prints a final review — summary in the persona's voice (in the page's language), plus structured lists of what they liked, what confused them, where the friction is, and whether they'd abandon.

  5. Optional follow-up Q&A (--repl): after the review, you can ask the same persona questions about the same page in an interactive prompt. The persona may scroll/click/type to look again before answering. Or use --repl-only to skip the review and go straight to questions.

  6. Hard cost cap: $1 (configurable) per (URL, persona) session, shared across the review and every follow-up turn. The session aborts when exhausted. A soft action cap (15 actions per phase) is also enforced.

  7. Opt-in form submission (--allow-submit): with explicit consent, the persona fills the form using a shared test identity, clicks submit once per session, and reacts to the resulting thank-you message or error in their final feedback / answer. Copy ./submit-data.yaml to your own file and pass it with --submit-data <path>; editing the bundled submit-data.yaml directly may be overwritten when the software is updated.

It does not: authenticate. Custom personas come from YAML files in your personal .persona-review/personas/ directory.

Scope: every persona assumes some interest in the cause. This tool is not designed for commercial / SaaS pages — for that, the personas would need different goals and frustrations.


Architecture

┌─────────────────┐        ┌──────────────────┐        ┌──────────────┐
│  CLI            │  ───▶  │  Playwright      │  ───▶  │  Target URL  │
│  (persona-      │        │  headless        │        │  (public     │
│   review)       │  ◀──   │  Chromium        │  ◀──   │   page)      │
└────────┬────────┘  snap  └──────────────────┘        └──────────────┘
         │ persona + snapshot
         ▼
┌─────────────────┐        ┌──────────────────┐
│  LLM provider   │  ◀──▶  │  Anthropic API   │
│  adapter        │        │  or OpenAI API   │
└─────────────────┘        └──────────────────┘

Entry points planned:

| Entry point | Status | How to run | |---|---|---| | CLI (persona-review) | ✅ done | npx persona-review <url> or npm run review -- <url> | | MCP server (persona-review-mcp) | TBD | Mounts into Claude Code / Codex / Gemini CLI as a tool |


Prerequisites


Install

Choose ONE of the methods bellow. If you prefer you can use the free Google Cloud Shell instead of installing to your computer.

1. Install via npm (use with npx)

You just need to have installed Nodejs version 20 or more.

To install the browser dependency, run this once:

npx persona-review --install-browsers

This downloads the Chromium artifacts needed by the Playwright version bundled with persona-review, including the headless shell used for reviews. On Linux, it also installs the system packages Chromium needs. Do not use plain npx playwright install chromium for this npm/npx install method; it can install Chromium for a different Playwright package.

To use you need an API key from Anthropic, Open AI or Google:

# Use the export command to add your API key. See bellow for more info. 
export ANTHROPIC_API_KEY=sk-ant...

npx persona-review https://example.org/

npx downloads and runs the latest published version automatically. All options described in Usage work the same way — replace npm run review -- with npx persona-review throughout.


2. Install the development version

Use this if you want to modify the source or contribute:

git clone https://codeberg.org/osvik/persona-review.git
cd persona-review
npm install
npx playwright install chromium
npm run build

npm install pulls the dependencies; npx playwright install chromium downloads the browser for the local Playwright dependency; npm run build compiles TypeScript to dist/ and marks the CLI executable.

To use you need an API key from Anthropic, Open AI or Google:

# Use the export command to add your API key. See bellow for more info. 
export ANTHROPIC_API_KEY=sk-ant...

npm run review -- https://example.org/

3. Install using Docker

If you can't use another method, you can use this software inside a Docker container. You need to install Docker first!

This method takes much longer to install, as you have to download the Docker image, but it's reliable.

Create the container from the terminal (just once):

docker run -it --init --ipc=host --name persona-review node:20-bookworm /bin/bash

Install inside your container (just once):

npx persona-review --install-browsers

The official node:20-bookworm image runs as root by default, so this command can install both the browser artifacts and the Linux packages Chromium needs.

Test it with your API key:

export ANTHROPIC_API_KEY=sk-ant...
npx persona-review https://example.org

Use:

Now you can use it by opening Docker desktop's terminal in the conainer persona-review:

bash
export ANTHROPIC_API_KEY=sk-ant...
npx persona-review http://example.org

Configure the LLM provider

By default the CLI calls Anthropic's API directly. Create your API key and add it to the console with:

export ANTHROPIC_API_KEY=sk-ant-...

To use OpenAI instead, create your API key set OPENAI_API_KEY and pass --provider openai:

export OPENAI_API_KEY=sk-...
npm run review -- https://example.org --provider openai

To use Google Gemini instead, create your API key, set GEMINI_API_KEY, and pass --provider google:

export GEMINI_API_KEY=...
npm run review -- https://example.org --provider google

You can also store the API key in an .env file. First let's create the file with an editor:

nano .env

Put the API key with the export variable sintax in the .env file and exit + save:

export ANTHROPIC_API_KEY=sk-ant-...

and then run:

source .env

Now this should work with both npx persona-review or npm run review --.

The CLI reads only the key for the selected provider; nothing else leaves your machine except the HTTP request to the selected LLM provider and the page-load request to the target URL.

Check which providers are ready, which model ids are built in, and where defaults come from:

npm run review -- --status

This only checks whether each provider's environment variable exists; it does not print API key values.

Model: Anthropic defaults to claude-sonnet-4-6; OpenAI defaults to gpt-5.4; Google defaults to gemini-3.1-pro-preview-customtools. Override any provider with --model, e.g. --model claude-opus-4-7, --model gpt-5.5, or --model gemini-3.1-pro-preview.


Usage

Important: All examples below use the development npm run review -- form. If you installed via npm, replace that prefix with npx persona-review:

npm run review -- https://example.org/   # development install
npx persona-review https://example.org/  # npm install

Help

npm run review -- --help
# or
npx persona-review --help

Version

npm run review -- --version
# or
npx persona-review --version

Status

npm run review -- --status
# or
npx persona-review --status

Prints whether ANTHROPIC_API_KEY, OPENAI_API_KEY, and GEMINI_API_KEY are set, without showing their values. It also lists the model ids accepted by --model with built-in pricing, then shows every effective default and whether it came from the software or the user defaults file.

User defaults

On first run, persona-review creates an empty defaults file and an empty personal personas directory:

  • macOS / Linux: $HOME/.persona-review/defaults.yaml
  • macOS / Linux: $HOME/.persona-review/personas/
  • Windows: %USERPROFILE%\.persona-review\defaults.yaml
  • Windows: %USERPROFILE%\.persona-review\personas\

Leave it empty to use the built-in defaults. Add YAML keys there to change your normal defaults without editing source code or rebuilding, which is especially useful with npx persona-review.

Precedence is:

  1. Built-in software defaults.
  2. User defaults from defaults.yaml.
  3. Options passed on the command line.

Example:

persona: evidence-and-accountability-checker
provider: openai
model: gpt-5.4
device: desktop
cost_cap_usd: 2
max_actions: 20
max_tokens: 4096
full_page_snapshot: true
allow_downloads: true
allow_cross_page_navigation: false  # set true to let personas follow links to other pages
submit_data: /Users/me/.persona-review/submit-data.local.yaml

Then a shorter command uses those defaults:

npx persona-review https://example.org/

Command-line options still win for that run:

npx persona-review https://example.org/ --persona time-pressed-task-completer --provider anthropic

For boolean defaults set to true, use the matching --no-... flag to turn them off for one run, for example --no-json, --no-repl, --no-allow-submit, --no-allow-downloads, --no-full-page-snapshot, or --no-allow-cross-page-navigation.

Supported defaults keys are persona, provider, model, device, json, repl, repl_only, allow_submit, allow_downloads, allow_cross_page_navigation, submit_data, yes, max_tokens, max_actions, cost_cap_usd, and full_page_snapshot. The equivalent long flag names with hyphens, such as max-tokens and allow-downloads, are also accepted.

Basic review with Anthropic model

npm run review -- https://example.org/

List the personas

npm run review -- --list-personas

Uses the default persona (newcomer-orientation-seeker).

Pick a persona

npm run review -- https://example.org --persona evidence-and-accountability-checker
npm run review -- https://example.org --persona time-pressed-task-completer

Different language

The page's language is detected from <html lang> and the LLM responds as a native speaker of that language. No flag needed.

JSON output

npm run review -- https://example.org --json > review.json

--json cannot be combined with --repl or --repl-only.

Follow-up questions (interactive REPL)

After the review, ask the same persona questions about the same page:

npm run review -- https://example.org --persona evidence-and-accountability-checker --repl
── Mei's feedback ── (review prose…)

[follow-up REPL — ask Mei a question. Type "exit" or press Ctrl-D to leave.
 Cost cap shared with the review: $0.78 of $1.00 remaining.]

> Ask Mei: would a clearer financial breakdown change your mind?

Mei: Probably yes — what I want is a single page that shows where the money
goes. The current "About" section gestures at it but the numbers aren't there.

[1 action(s) — $0.27 of $1.00 cap — $0.73 remaining]

Skip the review entirely and go straight to questions:

npm run review -- https://example.org --persona evidence-and-accountability-checker --repl-only

The browser session, page state, and conversation history are reused across every question, so the persona can scroll/click/type to look again. The cost cap is per (URL, persona) session and accumulates across the review plus every follow-up turn.

Allow form submission (--allow-submit)

By default, form-submit buttons are blocked at the browser layer. Pass --allow-submit to let the persona fill the form, click submit once, and react to the resulting thank-you / error / validation page. Submission is hard-capped at one per (URL, persona) session.

Before submitting real forms, copy the bundled identity template and pass your copy with --submit-data. Do not edit submit-data.yaml in place: updates may overwrite that file.

cp submit-data.yaml submit-data.local.yaml
npm run review -- https://example.org --persona regular-supporter-donor --allow-submit --submit-data ./submit-data.local.yaml

The CLI prints a consent prompt summarizing the test identity and target URL, then waits for y / yes before opening the browser:

=== --allow-submit: form submission ENABLED for this run ===

Target URL: https://example.org/petition
Persona:    Daniel (regular-supporter-donor)

Source: ./submit-data.local.yaml (pass --submit-data <path> to use your copy)
Test identity that will be typed into form fields:

  Name:    Daniel PersonaReview
  Email:   [email protected]
  Phone:   +49 30 12345678
  Address: Teststrasse 1, PersonaReview test record, 10115 Berlin, Germany
  Card:    4242 4242 4242 4242 (exp 12/2030)
  IBAN:    DE89 3704 0044 0532 0130 00
  Donation: 5 EUR (one-off)
  ...

This may create a real record in the target site's CRM, marketing
automation, or analytics. Records will be findable by the name and email
above; delete them after the run.
Hard limit: at most one successful submission per session.

Continue and submit the form? [y/N]

Customize the identity by duplicating submit-data.yaml, editing your copy, and passing it with --submit-data /path/to/your.yaml or --submit-data /path/to/your.yml. Do not edit the bundled submit-data.yaml directly because software updates may overwrite it. All personas share the same identity so you don't end up with one CRM record per persona — search the CRM for PersonaReview and the test email after the run, then delete.

Country-specific fields. The four typed sub-sections (identity, address, payment, donation) accept extra keys beyond the documented ones, so you can add a Spanish DNI, a German VAT number, a US SSN, etc. without changing the code. They're surfaced to the LLM with their YAML key as the label:

identity:
  first_name: null            # falls back to the persona's name
  last_name: "PersonaReview"
  email: "[email protected]"
  phone: "666666666"
  dni_o_nie: "08966195J"      # custom Spain field — picked up automatically

address:
  line1: "Calle de las pruebas"
  line2: "66 ESC INT 1A"
  city: "Madrid"
  postcode: "28007"
  country_code: "ES"
  country_name: "España"

The persona will use these values verbatim when filling form fields it recognizes by label.

For automated runs (CI etc.) pass --yes to skip the prompt:

npm run review -- https://example.org --allow-submit --submit-data ./submit-data.local.yaml --yes

All flags

npm run review -- <url> [options]
npm run review -- --status
npm run review -- --list-personas
npm run review -- --version

  --persona <id>           Persona archetype id (default: newcomer-orientation-seeker).
  --provider <name>        LLM provider: 'anthropic', 'openai', or 'google'
                           (default: anthropic).
  --device <m|d>           Override the persona's device: 'mobile' or 'desktop'.
  --status                 Show provider readiness, available --model ids,
                           and default sources.
  --list-personas          Print available personas and exit.
  --json                   Emit JSON instead of prose. Mutually exclusive
                           with --repl / --repl-only.
  --repl                   After the review, enter an interactive prompt for
                           follow-up questions. Cost cap is shared.
  --repl-only              Skip the review; load the page and go straight
                           to the interactive prompt.
  --allow-submit           Permit ONE form submission this session. Persona
                           fills the form with the shared test identity and
                           reacts to the resulting page. Requires consent.
                           Copy submit-data.yaml and pass the copy with
                           --submit-data; edits to the bundled file may be
                           overwritten by updates.
  --allow-downloads        Permit browser downloads. Default: downloads are
                           blocked by Playwright.
  --allow-cross-page-navigation
                           Permit persona clicks to navigate away from the
                           reviewed URL. Default: blocked; same-page anchors
                           and non-link UI controls still work.
  --submit-data <path>     Use your copied .yaml/.yml test identity file
                           (default template: ./submit-data.yaml).
  -y, --yes                Skip the --allow-submit consent prompt.
  --model <id>             Provider-specific model id (defaults:
                           Anthropic claude-sonnet-4-6, OpenAI gpt-5.4,
                           Google gemini-3.1-pro-preview-customtools).
  --cost-cap-usd <n>       Hard cost cap in USD per (URL, persona) session
                           (default: 1.0). Includes review + all REPL turns.
  --max-actions <n>        Soft cap on browser actions per phase (default: 15).
  --max-tokens <n>         Max output tokens per LLM call (default: 4096).
  --full-page-snapshot     Send a full-page screenshot each turn (default:
                           viewport only — the persona must scroll to see more).
  --no-<boolean-flag>      Disable a boolean option set in user defaults
                           for this run, e.g. --no-json or --no-repl.
  -v, --version            Show the package version.
  -h, --help               Show help.

Device profile per persona

Each persona has a device field which determines the viewport and user agent:

| device value | Viewport | User agent | Touch | |---|---|---|---| | mobile | 390×844 (iPhone-class) | iOS Safari 18 | yes | | desktop | 1280×800 | Chrome 126 on macOS | no | | either | resolves to desktop | Chrome 126 on macOS | no |

Pass --device mobile or --device desktop to override the persona's default.

Example output (prose)

── Marta's feedback ──

I came here because a friend sent the link and said I should sign. The big
photo of the forest is beautiful, and the headline is clear. I like that I
can tell within two seconds what this is about and what they want from me.
But the form asks for my phone number and I don't love giving that out —
especially without a clear word on what they'll do with it.

Liked: clear purpose, strong imagery, short form

Confused by: what happens after I sign, why they need a phone number.

Abandoned:   no — I'd probably sign, but reluctantly.

Friction:
  - [medium] phone number field: "I don't want calls from an organisation"
  - [low] privacy link in the footer: "it's far away from the form itself"

(Illustrative — actual output varies, because persona reactions are not seeded. See "Reproducibility" below.)

Example output (JSON)

{
  "persona_id": "marta-smb-owner",
  "url": "https://example.com",
  "summary": "...",
  "liked": ["..."],
  "confused_by": ["..."],
  "friction": [{"where": "...", "severity": "high", "quote": "..."}],
  "abandoned_at": null,
  "accessibility_issues": ["..."],
  "trust_signals": {"positive": ["..."], "negative": ["..."]},
  "trace": [{"step": "...", "reaction": "..."}]
}

Persona file format

Built-in personas live as YAML files in this package's personas/ directory. Custom user personas live in $HOME/.persona-review/personas/ on macOS/Linux or %USERPROFILE%\.persona-review\personas\ on Windows. Each file is validated against the Zod schema in src/persona.ts. Example:

id: newcomer-orientation-seeker # unique slug
name: Aisha                    # illustrative; LLM adapts in any language
age: 27                        # optional
role: New to the cause and deciding whether to engage
cause_engagement: casual       # neutral | casual | regular | committed
scrutiny: low-medium           # low-medium | medium | high
goals:
  - understand what this organization does in 30 seconds
  - decide whether it feels legitimate before doing anything
motivations:
  - wants to feel their small action could make a concrete difference
  - responds to human stories when they are backed by clear proof
frustrations:
  - jargon and acronyms
  - vague claims with no evidence
behaviours:
  - scans the headline, first call to action, and first proof point before reading
  - hesitates if commitment is requested before impact or legitimacy is clear
tech_confidence: medium        # low-medium | medium | medium-high (no extremes)
device: either                 # mobile | desktop | either
accessibility: []              # e.g. [larger-text, screen-reader]
reading_level: general         # general | detailed
voice: curious, open-minded, easily discouraged by unclear entry points

Notes:

  • No locale field. Language is auto-detected from the page's <html lang> at runtime, so each persona works in any language.
  • motivations and behaviours are optional for custom personas. If omitted, they default to empty arrays, so older custom persona files still load.
  • tech_confidence is restricted to a middle band — no zero-tech and no power-user, by design.
  • We describe UX-relevant traits (goals, motivations, frustrations, behaviours, tech confidence, device, accessibility needs) and avoid demographic caricature.

You can drop your own custom YAML files into your personal personas directory and they'll be picked up by --list-personas and --persona <id>. If a custom persona uses the same id as a built-in persona, the custom persona wins. If you edit the package's built-in files, they may be overwritten when you update the software.


The persona library

Built-in archetypes, most assuming at least some interest in the cause. The deadline journalist is neutral toward the organization and cause, but has a practical reason to use press material accurately. The fundraising and visual design specialists are expert lenses for teams that want a more professional critique.

| id | who | device | tech | engagement | scrutiny | |---|---|---|---|---|---| | newcomer-orientation-seeker | Aisha — new to the cause and deciding whether to engage | either | medium | casual | low-medium | | time-pressed-task-completer | Sofia — arrives with intent and wants to finish quickly | either | medium | regular | low-medium | | regular-supporter-donor | Daniel — gives occasionally and may consider recurring support | either | medium | regular | medium | | evidence-and-accountability-checker | Mei — checks evidence, transparency, and accountability | either | medium-high | regular | high | | deadline-journalist | Nadia — needs accurate, usable material on deadline | either | medium-high | neutral | high | | advocate-and-sharer | Femi — wants to act, share, and bring others in | either | medium-high | committed | medium | | plain-language-reader | Anna — prefers everyday words | either | medium | regular | medium | | accessibility-focused-reader | Yusuf — uses assistive and adaptive settings | either | medium | regular | medium | | help-seeker | Rosa — needs support for themselves or someone close | either | medium | regular | medium | | legacy-and-planned-giving-prospect | Margaret — considers major or planned giving | either | low-medium | committed | high | | marketing-fundraising-specialist | Ines — reviews fundraising and engagement pages | either | medium-high | committed | high | | visual-design-specialist | Kenji — reviews visual composition | either | medium-high | committed | high |

Run npm run review -- --list-personas for the same list with full role descriptions.


Reproducibility

Two runs against the same URL with the same persona will produce different reactions. That is intentional — it simulates natural human variability. Don't treat a single review as canonical; run it twice, look at what both reviews agree on, and treat the overlap as the signal.


Scope and safety

  • Public pages only. Authenticated pages are out of scope.
  • Form submission is opt-in. Default is no submission — submit buttons are blocked at the browser layer. With --allow-submit (and confirmed consent), the persona fills the form using the shared test identity and submits at most once per session. Copy submit-data.yaml, edit your copy, and pass it with --submit-data; direct edits to the bundled file may be overwritten by software updates. The same test identity is used across all personas so records stay easy to find and delete in the target site's CRM.
  • Downloads are opt-in. Default is no browser downloads. Pass --allow-downloads to let Playwright accept downloads for the session. Accepted downloads stay in Playwright's temporary browser storage; this tool does not save them into the project, and they are deleted when the browser context closes.
  • Cross-page navigation is opt-in. Default reviews stay on the URL being reviewed. Links that would leave that URL are blocked at the browser layer, while same-page anchors, cookie banners, tabs, accordions, and other non-link controls still work. Pass --allow-cross-page-navigation when a review should intentionally follow links to other pages.
  • Local-only. No servers we operate. Traffic goes only to the target URL and to the selected LLM provider API.
  • Cost cap. Every session enforces a hard USD cap (default $1, override with --cost-cap-usd). The cap is per (URL, persona) session — shared across the review and every follow-up REPL turn — and aborts the run when exhausted, so a runaway agent cannot burn unbounded spend.

Development

Important: before you do any development, note that this software is licensed under the GNU Affero General Public License v3.0. Any modification of the software, or cloud use such as providing access through a graphical user interface or MCP server, must comply with the requirements of this license.

Build it

npm run typecheck    # type-check without emitting
npm run build        # compile to dist/
npm run review -- <url>   # run the compiled CLI

Source layout:

src/
  agent.ts        # PersonaConversation orchestration, review loop, REPL turns,
                  #   provider/model selection, action caps, and submit cap.
  browser.ts      # Playwright browser session: open, observe, scroll, click,
                  #   type, close; blocks submits, downloads, and cross-page
                  #   navigation unless enabled.
  cli.ts          # CLI entry: flags, persona loading, provider key checks,
                  #   --allow-submit consent, JSON/prose output, REPL.
  cost.ts         # Per-provider/model pricing and CostTracker cap enforcement.
  defaults.ts     # User defaults file creation/loading and option validation.
  persona.ts      # Persona Zod schema + YAML loader/listing helpers.
  review.ts       # Feedback/follow-up schemas, tool schemas, and system prompt
                  #   builder including conditional submission policy.
  submit-data.ts  # Test-identity Zod schema + loader; resolves first_name to
                  #   the persona's name when YAML leaves it null.
  user-config.ts  # Cross-platform paths for ~/.persona-review resources.
  llm/
    types.ts      # Shared provider-neutral message, tool, usage, and client
                  #   interfaces.
    anthropic.ts  # Anthropic Messages adapter.
    google.ts     # Google Gemini generateContent adapter.
    openai.ts     # OpenAI Responses API adapter.
personas/
  *.yaml          # 12 archetype files; drop your own in here too
submit-data.yaml  # Default shared test identity template for --allow-submit
                  #   copy it and pass the copy with --submit-data
package.json      # Package metadata, CLI bin, npm scripts, dependencies.
tsconfig.json     # TypeScript compiler settings.
LICENSE           # GNU Affero General Public License v3.0.