App-agnostic engine that models a web UI as a graph and compiles flows into Playwright-MCP-executable scripts for agents.
Project description
uipilot
App-agnostic engine that models a web UI as a graph and compiles flows into Playwright-MCP-executable scripts for agents.
An agent driving a web UI live pays, per element: snapshot → reason about intent
→ find the ref → act. uipilot turns that into a lookup. You describe your app's
elements, actions, and flows once as structured YAML (a pack); the engine
queries that graph and emits a step-by-step Playwright-MCP script with selectors
pre-resolved and page snapshots inserted only where the DOM actually changes.
The engine ships no domain vocabulary — no "login", "cart", "tenant". It knows only generic concepts (app, element, action, flow, step, param, capture, capability). Everything project-specific lives in a pack, so porting to a new web app means authoring a new pack, not touching the engine.
The engine executes nothing. Every command emits structured data; the agent owns the browser and runs it. That keeps
uipilotsafe to point at money-moving flows.
Install & set up in your project
uv tool install uipilot # install the CLI globally
cd your-project
uipilot init # scaffold a pack + agent instructions here
uipilot init writes a minimal, valid pack skeleton under .uipilot/
(.uipilot/flowmap.config.yaml, .uipilot/data/, .uipilot/capabilities.py)
plus a Claude Code skill (.claude/skills/uipilot/SKILL.md) by default. To also
write an AGENTS.md section, opt in with --agent agents (the --agent option
is repeatable, e.g. uipilot init --agent claude --agent agents). Then just
ask your agent to run a flow: it
explores the app, fills the pack, and drives it for you. Re-running init
refreshes the skill but never clobbers a pack you've started (use --force to
overwrite).
Upgrading
After upgrading the CLI, refresh the scaffolded agent instructions in each project — packs are never touched:
uv tool upgrade uipilot
cd your-project
uipilot update # refreshes SKILL.md / AGENTS.md block in place
Scaffolded files carry a version stamp, so update reports exactly what moved
(e.g. SKILL.md (v0.4.0 → v0.5.0)) and reminds you to re-run validate if the
pack was scaffolded by an older version.
Because init scaffolds a .uipilot/ pack in the cwd, every later uipilot …
call finds it with no --pack flag (resolution order: $UIPILOT_PACK → a
.uipilot/ pack in the cwd → the bundled examples/demo pack).
From source
uv sync # creates .venv, installs from uv.lock
uv run uipilot --pack examples/demo apps
Built with the uv_build backend; dependencies are locked in uv.lock.
The 60-second tour
# what apps/flows exist?
uipilot --pack examples/demo --format table apps
uipilot --pack examples/demo flows
# one-shot flow brief: app + guard + param manifest + compiled script in one call
uipilot --pack examples/demo plan --flow create_project_with_credential
# compile a flow into an executable Playwright-MCP script
uipilot --pack examples/demo script --flow create_project_with_credential
# find a route through the UI graph
uipilot --pack examples/demo path --from act_cs_view_dashboard --to act_cs_create_credential
# what breaks if I edit this button? (change blast radius)
uipilot --pack examples/demo uses cs_btn_create_project_submit
# is the map self-consistent? (CI gate)
uipilot --pack examples/demo validate
# does the map still match the running app? (read-only live probe)
uipilot --pack examples/demo verify --flow create_project_with_credential
Pack layout
.uipilot/ # the pack dir (scaffolded by `uipilot init`)
├── flowmap.config.yaml # binds apps, tokens, risk taxonomy, capabilities
├── .env # (optional, git-ignored) env-bound token/base_url defaults
├── capabilities.py # (optional) named auth adapters the engine calls by key
└── data/
├── <app>.app.yaml # one per app: app header + elements + actions
└── flows.yaml # named multi-app flows (+ shared/API actions)
Environment via .uipilot/.env. Tokens declared { from: env, name: X }
and each app's base_url { env: X } resolve X from the process environment,
falling back to a KEY=value line in an optional .uipilot/.env — so a
project can keep base URLs and login credentials (e.g. APP_EMAIL /
APP_PASSWORD wired to tokens a sign-in flow's steps consume) on disk without
exporting shell vars. A real shell variable of the same name wins over the file;
.env is git-ignored, so real secrets never land in version control.
- Element — a node with a structured selector (default
css/testid; XPath via acssxpath=…string;role/label/textavailable but text-based anchors are opt-in) and a purpose. Never a raw locator string, so it re-emits to a Playwright-MCP element description or a@playwright/testlocator. - Action — the graph proper. A UI action carries a
route, an orderedstepsrecipe,prev/nextedges,params, andcaptures. Theroutemay template{{param}}/{{token}}refs (e.g./clients/{{client_id}}) so URL-addressable detail pages are reached by navigation, not row-clicking. An API action (transport: api) binds to an existing test-framework factory viacall:for fast provisioning or backend cross-checks — it has noprev/nextand is excluded from pathfinding. - Flow — an ordered
pathof action ids. Entries can be a bare id, a subflow (use:), or an aliased repeat ({action, as, params}). Reuse is layered L1–L4 (shared actions → subflows → aliased invocations → guards). - Capture — a value one action produces (
from: url|element|response|…) that later steps consume as{{captured.x}}/{{alias.x}}, bridging UI and API transports unchanged.
See examples/demo/ for a complete two-app pack exercising
every feature.
Docs: src/uipilot/templates/skill.md — the
agent guide, shipped and installed by uipilot init; the single source of truth
for the core loop, execution contract, safety, and the full pack authoring
schema (config, app/element/action/flow field reference, selector conversion,
step ops) · docs/USE_CASES.md — business use cases at a
glance.
Commands
| Command | Purpose |
|---|---|
apps |
List apps, base URLs, auth entry flows |
actions |
List/filter actions (--app --risk --grep --section --transport) |
elements |
List/filter elements + resolved selectors |
show action <id> / show element <id> |
Full detail, selectors resolved inline |
uses <id> |
Reverse index — change blast radius before you edit |
flows / flow <name> [--params] |
List / show named flows (--params: aggregated param manifest only) |
path --from <a> --to <b> |
BFS a route through the flow graph |
plan --flow <name> … |
One-shot flow brief: app + guard + param manifest + compiled script in one JSON (see below) |
script … |
Emit an executable Playwright-MCP script (see below) |
validate |
Lint the model statically (CI gate) |
verify … |
Emit a read-only probe to detect live UI drift |
emit --format pw-pom|pw-test |
Generate Python POM classes / a pw-test spec |
capabilities [--check] |
List (and import-check) the pack's auth adapters |
init [dir] [--agent claude|agents] [--force] |
Scaffold a pack + Claude skill (add --agent agents for AGENTS.md) |
update [dir] [--agent claude|agents] |
Refresh scaffolded agent instructions to the installed version (pack untouched) |
import-md <file> --out <dir> |
One-time: seed a pack from a retired Markdown map |
plan
uipilot plan --flow <name>
[--params file.json] [--set key=value ...]
[--skip-auth] [--batch] [--refuse-destructive]
Returns the app (id + base URL + auth entry flow), the flow's guard, the full
param manifest, and the compiled script in a single JSON — collapsing the
apps → flow → flow --params → script discovery round-trips an agent
otherwise makes before it can drive a flow. Takes the same compile options as
script.
script
uipilot script (--flow <name> | --from <a> --to <b> | --actions a,b,c)
[--params file.json] [--set key=value ...]
[--skip-auth] [--batch] [--refuse-destructive]
[--format playwright-mcp|steps|json|pw-test|human]
-
--format humanemits a plain-English preview (numbered steps, params, risk, teardown) for a person to review before an agent runs a risky flow. -
Param resolution:
--set>--params> model defaults. Defaults expand pack tokens ({{prefix}},{{seq}}). Unresolved required params are emitted as{{placeholder}}and listed underparams_required. -
Secrets (
type: secret) never appear in the header echo — only in the one step that consumes them. When a required param's default draws from env-backed pack token(s),params_satisfiablereports{param: {source: "env:VAR", present: bool}}so the agent supplies the value with--set k=$VARinstead of asking a human; the value itself is never included. -
Auth skip check: the auth entry flow's
guardrides into the compiledauthprecondition as a machine-checkableskip_ifmarker (e.g.{"text": "Service Wallets"}), so the agent's first snapshot doubles as the "am I already signed in?" check. -
executor.tool_prefix(playwright-mcp output only) states how the baremcp.toolnames map onto a namespaced MCP registry (mcp__playwright__by convention), so the agent prepends the prefix instead of failing a first tool lookup. -
--refuse-destructiverefuses to emit if any action on the path carries ariskin the pack'srisk.gatedset. The header always surfacesrisk_max. -
--batchcollapses consecutive form fills into onebrowser_fill_form.
validate vs verify
validate(offline, CI-gating) — internal consistency only: dangling refs, broken edges, undeclared params, capture collisions, unreachable nodes, unbound API calls, coverage bypass. Answers "is the map self-consistent?"verify(live) — emits a read-only probe (navigate + snapshot + assert-each-element-resolves; no mutating clicks) the agent runs against the app. Answers "does the map still match the running UI?"--drivewalks a full flow but refusesrisk.gatedsteps unless--allow-gated.
Lint codes: E_DANGLING_ELEMENT, E_BROKEN_EDGE, E_SELECTOR_AMBIGUOUS,
E_PARAM_UNDECLARED, E_SUBFLOW_CYCLE, E_CAPTURE_COLLISION,
E_API_CALL_UNBOUND, W_NO_STEPS, W_UNREACHABLE, W_NO_CAPTURE,
W_DUPLICATE_RECIPE, W_UI_COVERAGE_BYPASS, W_UNMET_REQUIRES.
Development
uv sync # installs the project + the dev dependency group
uv run pytest -q
Architecture
The package is split into four layers under src/uipilot/, with dependencies
pointing strictly inward (presentation → application → {domain, infrastructure} → domain):
domain/ pure model + business rules (no I/O, no frameworks)
model entities: App, Element, Action, Flow, Step, Param, Capture, Selector, Pack
templating RuntimeContext + {{token}} resolution
flows subflow expansion (single source of truth)
graph BFS pathfinding + reachability
validation the static linter (E_/W_ codes)
compiler flow → CompiledScript
verification read-only drift-probe builder
usage reverse index
infrastructure/ I/O and external concerns
pack_loader YAML → domain model
capabilities dynamic import of pack auth adapters
markdown_importer one-shot MD → seed YAML
application/ use-case orchestration
service open_pack + compile/validate/verify/route/uses/… wiring
presentation/ the CLI and output renderers
cli Typer entrypoint (talks only to application)
renderers json / playwright-mcp / steps / pw-test / pw-pom
The domain layer imports nothing outside the standard library, so the business rules are testable with no YAML, filesystem, or CLI in the loop.
License
MIT.
Project details
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file uipilot-0.7.0.tar.gz.
File metadata
- Download URL: uipilot-0.7.0.tar.gz
- Upload date:
- Size: 60.0 kB
- Tags: Source
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
865d5c4a37f6cdc149eff2435b9c9053dbcfa2ada65cb7badf13a919018160cc
|
|
| MD5 |
0ca4c59825700ac3a61e2374bbb137cf
|
|
| BLAKE2b-256 |
247473be2f76150ecbe1c593ca3be8133558b893239a24c5f447bcd78f7b6994
|
Provenance
The following attestation bundles were made for uipilot-0.7.0.tar.gz:
Publisher:
publish-to-pypi.yml on l0kifs/uipilot
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
uipilot-0.7.0.tar.gz -
Subject digest:
865d5c4a37f6cdc149eff2435b9c9053dbcfa2ada65cb7badf13a919018160cc - Sigstore transparency entry: 2126794639
- Sigstore integration time:
-
Permalink:
l0kifs/uipilot@9b1027ca0209130fac56888fbdb3329d48bf164a -
Branch / Tag:
refs/tags/v0.7.0 - Owner: https://github.com/l0kifs
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish-to-pypi.yml@9b1027ca0209130fac56888fbdb3329d48bf164a -
Trigger Event:
release
-
Statement type:
File details
Details for the file uipilot-0.7.0-py3-none-any.whl.
File metadata
- Download URL: uipilot-0.7.0-py3-none-any.whl
- Upload date:
- Size: 72.0 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
149358da41a8589f1e6a9b45ad0a277c0555ba34c61b1f7102b0bf1a46fc3cad
|
|
| MD5 |
20d937340ee00a338a9b8f0041783054
|
|
| BLAKE2b-256 |
c9aa78709a7bab2c8cf257d67c8e3c99f98a44e509c932588198c1552ccdf219
|
Provenance
The following attestation bundles were made for uipilot-0.7.0-py3-none-any.whl:
Publisher:
publish-to-pypi.yml on l0kifs/uipilot
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
uipilot-0.7.0-py3-none-any.whl -
Subject digest:
149358da41a8589f1e6a9b45ad0a277c0555ba34c61b1f7102b0bf1a46fc3cad - Sigstore transparency entry: 2126794680
- Sigstore integration time:
-
Permalink:
l0kifs/uipilot@9b1027ca0209130fac56888fbdb3329d48bf164a -
Branch / Tag:
refs/tags/v0.7.0 - Owner: https://github.com/l0kifs
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish-to-pypi.yml@9b1027ca0209130fac56888fbdb3329d48bf164a -
Trigger Event:
release
-
Statement type: