persona-review
v1.2.0
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.
>>> Go to the website <<<
- persona-review
What it does today
Loads the URL you give it in a real headless Chromium (Playwright). Waits for network idle and a small cushion past
DOMContentLoadedso the persona doesn't critique a half-booted UI.Captures the page's accessibility tree, the page language (
<html lang>), and a JPEG screenshot of the top viewport.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.
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.
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-onlyto skip the review and go straight to questions.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.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.yamlto your own file and pass it with--submit-data <path>; editing the bundledsubmit-data.yamldirectly 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
┌─────────────────┐ ┌─────────────────┐ ┌─────────────────┐
│ Web UI │ │ TUI │ │ CLI │
│ (browser) │ │ (--ui / --tui) │ │ (persona- │
└────────┬────────┘ └────────┬────────┘ │ review) │
│ │ └────────┬────────┘
▼ │ │
┌─────────────────┐ │ │
│ HTTP server │ │ │
│ (server.ts) │ │ │
└────────┬────────┘ │ │
│ │ │
└──────────┬──────────┴─────────────────────┘
│
▼
┌──────────────────┐ ┌──────────────┐
│ Playwright │ ───▶ │ Target URL │
│ headless │ │ (public │
│ Chromium │ ◀── │ page) │
└──────────────────┘ └──────────────┘
│ persona + snapshot
▼
┌─────────────────┐ ┌──────────────────────────┐
│ LLM provider │ ◀──▶ │ Anthropic / OpenAI / │
│ adapter │ │ Google Gemini API │
└─────────────────┘ └──────────────────────────┘Entry points:
| Entry point | Status | How to run |
| ------------------------------- | ------- | ------------------------------------------------------- |
| CLI (persona-review) | ✅ done | npx persona-review <url> or npm run review -- <url> |
| TUI (persona-review --ui) | ✅ done | npx persona-review --ui or npm run review -- --ui |
| Web UI (persona-review --web) | ✅ done | npx persona-review --web or npm run review -- --web |
Prerequisites
- Node.js 20 or newer
- An API key from one of this providers:
- Anthropic (default) — console.anthropic.com
- OpenAI with
--provider openaiplatform.openai.com - Google Gemini with
--provider googleaistudio.google.com
- About 200 MB of disk for Chromium (installed via Playwright)
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-browsersThis downloads the Chromium browser artifact needed by the Playwright version bundled
with persona-review. 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.
In Google Cloud Shell, Playwright's bundled zip extractor hangs inside the
gVisor sandbox (the download reaches 100%, then extraction stalls on the first
large file). To work around this, persona-review downloads the browser archive
itself and extracts it with the system unzip into
$HOME/.cache/persona-review-ms-playwright, the only filesystem that persists
across Cloud Shell restarts. This needs the unzip package (preinstalled in
Cloud Shell; if missing, run sudo apt-get install -y unzip).
On Windows, if you previously saw an error about
chromium_headless_shell-... after installing browsers, update
persona-review and run npx persona-review --install-browsers again. Current
versions use the regular chromium-... browser artifact on Windows, and the
headless-shell artifact on macOS and Linux.
To use you need an API key from Anthropic, Open AI or Google:
# macOS / Linux
export ANTHROPIC_API_KEY=sk-ant...
# Windows (PowerShell)
$env:ANTHROPIC_API_KEY="sk-ant..."
# Windows (Command Prompt)
set 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
npm run build
npm run review -- --install-browsersnpm install pulls the dependencies; npm run build compiles TypeScript to
dist/ and marks the CLI executable; npm run review -- --install-browsers
downloads the browser for the local Playwright dependency.
To use you need an API key from Anthropic, Open AI or Google:
# macOS / Linux
export ANTHROPIC_API_KEY=sk-ant...
# Windows (PowerShell)
$env:ANTHROPIC_API_KEY="sk-ant..."
# Windows (Command Prompt)
set 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/bashInstall inside your container (just once):
npx persona-review --install-browsersThe 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.orgOf course, you can alternatively use an Open AI or Google Keys.
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.orgConfigure the LLM provider
By default the CLI calls Anthropic's API directly. Create your API key and add it to the console with:
# macOS / Linux
export ANTHROPIC_API_KEY=sk-ant-...
# Windows (PowerShell)
$env:ANTHROPIC_API_KEY="sk-ant-..."
# Windows (Command Prompt)
set ANTHROPIC_API_KEY=sk-ant-...To use OpenAI instead, create your API key set OPENAI_API_KEY and pass --provider openai:
# macOS / Linux
export OPENAI_API_KEY=sk-...
# Windows (PowerShell)
$env:OPENAI_API_KEY="sk-..."
# Windows (Command Prompt)
set OPENAI_API_KEY=sk-...
npm run review -- https://example.org --provider openaiTo use Google Gemini instead, create your API key, set GEMINI_API_KEY, and pass --provider google:
# macOS / Linux
export GEMINI_API_KEY=...
# Windows (PowerShell)
$env:GEMINI_API_KEY="..."
# Windows (Command Prompt)
set GEMINI_API_KEY=...
npm run review -- https://example.org --provider googleEnvironment variables take precedence, but you can also store API keys in
~/.persona-review/keys.yaml. The file is created empty the first time
persona-review looks for an API key and it does not already exist.
ANTHROPIC_API_KEY: sk-ant-...
OPENAI_API_KEY: sk-...
GEMINI_API_KEY: ...Use the same key names as the environment variables. You only need to fill in the providers you use.
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 -- --statusThis checks whether each provider has a key in the environment or
~/.persona-review/keys.yaml; 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 installHelp
npm run review -- --help
# or
npx persona-review --helpVersion
npm run review -- --version
# or
npx persona-review --versionStatus
npm run review -- --status
# or
npx persona-review --statusPrints whether ANTHROPIC_API_KEY, OPENAI_API_KEY, and GEMINI_API_KEY
are available from the environment or ~/.persona-review/keys.yaml, 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:
- Built-in software defaults.
- User defaults from
defaults.yaml. - 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.yamlThen 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 anthropicFor 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-personasUses 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-completerDifferent 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-onlyThe 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.yamlThe 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 --yesAll flags
npm run review -- --ui
npm run review -- <url> [options]
npm run review -- --list-personas
npm run review -- --status
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.
--ui, --tui Launch the interactive terminal UI instead of
running a single review. Requires a TTY (over
SSH use `ssh -t`). Cannot be combined with
--json. See "Terminal UI (TUI)" below.
-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": "..." }]
}Terminal UI (TUI)
If you'd rather not compose long flag strings, launch the interactive terminal UI:
npx persona-review --ui
# or, in a development checkout:
npm run review -- --uiThe TUI lets you pick the URL, persona, and device interactively, browse
the persona library with role summaries, run a review and watch its
progress, then chat with the persona in the same session. It reads the
same defaults (~/.persona-review/defaults.yaml), personas (built-in +
~/.persona-review/personas/), and API keys (env vars or
~/.persona-review/keys.yaml) as the CLI.
Requirements:
- An interactive terminal (TTY). Over SSH, connect with
ssh -t. - All the other CLI requirements (Node 20+, Chromium via
--install-browsers, an API key for the selected provider).
Inside the TUI:
↑/↓andEnternavigate menus.pfrom the main menu opens the full persona list with descriptions.sfrom the main menu opens the Settings screen.- During the review, status lines stream live; the final feedback and cost line are rendered when it completes.
- After the review, press
rto chat (REPL) with the same persona,nto start a new review, orqto quit. In chat, typeexit(or pressq) to leave. Ctrl-Cquits cleanly and closes the browser session.
The Settings screen lets you pick the LLM provider and model, manage
your API keys, toggle form submission (with a consent screen) /
browser downloads / cross-page navigation / full-page snapshot, pick
a custom submit-data file, and edit the cost cap, max actions per
phase, and max output tokens. Changes are session-only by default
— restarting the TUI returns everything to whatever
~/.persona-review/defaults.yaml says. The Settings screen has a
final "Save current settings as default" row: pressing Enter on it
writes the Settings-screen fields (provider, model, all toggles,
submit-data path, cost cap, max actions, max tokens) to
~/.persona-review/defaults.yaml in the same snake_case format the
CLI documents, so subsequent CLI runs pick up the same defaults.
Persona and device live in the form, not in Settings, and aren't
saved by this action — edit defaults.yaml directly to set them as
defaults. The URL is per-run and never saved. API keys are handled
separately by the API keys screen — see below. When any of submit /
downloads / cross-page navigation is on, the main form shows a
warning line so you can't miss it.
The API keys screen shows the status of ANTHROPIC_API_KEY,
OPENAI_API_KEY, and GEMINI_API_KEY (each either set (source,
last 4: …xxxx) or missing). Editing a key uses a masked input —
press Tab to peek at what you typed. Submitting an empty value
clears the entry. The key value itself never displays in full; only
the last four characters surface for verification. If a key is set
via environment variable, the TUI warns you that env vars override
the file so a TUI write won't take effect until the env var is
unset.
When --allow-submit is enabled, pressing Run opens a dedicated
consent screen showing the target URL, persona, the test identity that
will be typed into form fields, and a final yes/no choice — the same
safety prompt the CLI shows. The default is No, cancel.
If the API key for the selected provider is missing, a red banner
appears above the menu and Run is blocked until you set it (export the
env var, add it to ~/.persona-review/keys.yaml, or use the in-TUI
API keys editor).
From the persona list, press Enter on any persona to open the
inspector and view its raw YAML — including built-in personas and
any custom ones you've dropped into ~/.persona-review/personas/.
Use ↑↓ / PgUp / PgDn / g / G to scroll through long files.
The TUI is theme-safe: it uses colors that read well on both dark and light terminal backgrounds and never paints a background of its own.
--ui cannot be combined with --json. The TUI now covers the full
review workflow — including provider/model switching, in-TUI API-key
editing, and the persona inspector. The only remaining roadmap item
is a log viewer for past runs, which depends on first adding run-log
writing to the CLI — see AGENTS.md for the open question.
Web UI
A browser-based review interface is included in this project. It serves the
static public/ app and uses src/server.ts to run the review backend.
Warning: The web server provides no authentication. Anyone who can access it can use it with your API keys. Do not expose the web server to the public internet or use it in shared environments. It is intended for local, single-user development only.
Run the web server locally:
npx persona-review --webThen open:
http://localhost:3000The web UI lets you choose the target URL, persona, provider, model, and optional review settings in your browser. It shows live review progress, logs, browser screenshots, and the final persona feedback. After the review completes, you can ask follow-up questions in the same session.
The web server uses the same API-key sources as the CLI/TUI: environment
variables or ~/.persona-review/keys.yaml. The server loads .env if
present and listens on the port configured by PORT (default 3000).
If running from source, make sure dist/server.js exists by running
npm run build first.
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 pointsNotes:
- No
localefield. Language is auto-detected from the page's<html lang>at runtime, so each persona works in any language. motivationsandbehavioursare optional for custom personas. If omitted, they default to empty arrays, so older custom persona files still load.tech_confidenceis 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. Copysubmit-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-downloadsto 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-navigationwhen 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 CLISource 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.
keys.ts # API key lookup: environment variables and ~/.persona-review/keys.yaml.
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.
server.ts # Express web server: REST API routes (/api/config, /api/review/*),
# static file serving for public/, and session management.
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.
tui/
index.tsx # TUI entry: mounts the Ink app, wires CLI options.
app.tsx # Top-level Ink component: routing between screens.
state.ts # TUI state store: form fields, review status, settings.
theme.ts # Color definitions shared across TUI components.
validate.ts # TUI-specific input validation helpers.
components/
CostLine.tsx # Cost display component during/following review.
Feedback.tsx # Rendered persona feedback output.
KeyHint.tsx # Keyboard shortcut hint bar.
StatusLog.tsx # Live status lines during review execution.
screens/
apiKeys.tsx # API key management screen.
form.tsx # Main URL/persona/device input form.
personaInspector.tsx # Raw YAML persona file viewer.
personaList.tsx # Persona library browser with descriptions.
repl.tsx # Follow-up chat screen.
review.tsx # Running review progress and results display.
settings.tsx # Provider/model/toggles/settings screen.
submitConsent.tsx # --allow-submit consent prompt screen.
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
public/
index.html # Web UI entry point.
index.js # Frontend JavaScript: UI logic, API calls, polling, PDF export.
index.css # Stylesheet (Stripe-inspired design theme).
.env # Local environment variables (PORT, API keys) — not committed.
.env.example # Template documenting available env vars.
scripts/
build.mjs # Build script: runs tsc to compile src/ to dist/.
package.json # Package metadata, CLI bin, npm scripts, dependencies.
tsconfig.json # TypeScript compiler settings.
LICENSE # GNU Affero General Public License v3.0.About the TUI
The TUI is built with Ink 5.2.1 (React for
terminals) and React 18.3.1. Each screen in src/tui/screens/ is an Ink
component rendered to a <Box>-based layout; state flows through a plain
zustand-style store in state.ts. The ink-select-input
and ink-text-input packages
handle menus and masked text fields. Components use Ink's useInput hook for
custom keybindings and useStdin / useStdout for raw terminal access. The
TUI is launched by cli.ts when --ui or --tui is passed — it delegates to
tui/index.tsx which mounts the Ink app, then falls back to the CLI review
path on exit.
