@mirofish-ai/pi-gui-use
GUI-use agent built on pi: a11y-first browser control with one driver seam for future desktop/mobile platforms.
Package details
Install @mirofish-ai/pi-gui-use from npm and Pi will load the resources declared by the package manifest.
$ pi install npm:@mirofish-ai/pi-gui-use- Package
@mirofish-ai/pi-gui-use- Version
0.0.11- Published
- Aug 8, 2026
- Downloads
- 1,268/mo · 1,268/wk
- Author
- tangao
- License
- MIT
- Types
- extension
- Size
- 285.6 KB
- Dependencies
- 6 dependencies · 0 peers
Pi manifest JSON
{
"extensions": [
"./dist/entries/extension.js"
]
}Security note
Pi packages can execute code and influence agent behavior. Review the source before installing third-party packages.
README
pi-gui-use
A GUI-use agent built on pi — a sibling of
pi-coding-agent that drives a GUI instead of a codebase. A11y-first: the agent reads
screens as numbered text element lists and asks for pixels only when text is not enough.
Two drivers today — browser (playwright-core over CDP, the default) and android
(adb against a running emulator); the GuiDriver seam keeps further desktop/mobile
drivers addable without touching tools or prompts.
Install
Install the command globally:
npm install -g @mirofish-ai/pi-gui-use
Or add the library to a project:
npm install @mirofish-ai/pi-gui-use
Browser startup is automatic. In normal use pi-gui tries, in order:
PI_GUI_CDP_URLwhen set (treated as a recoverable hint),- an existing Chrome at
http://127.0.0.1:9222, - the managed Chrome: adopt-or-launch on a persistent profile at
~/.pi-gui/chrome.
The managed browser is a device, not a subprocess. It is launched detached with
--remote-debugging-port=0 and found again through the DevToolsActivePort file Chrome
publishes into the profile, so a follow-up session adopts the running browser
(milliseconds) instead of paying a launch (~0.7–1.5s) — logins, cookies and tabs carry
over. Closing pi-gui disconnects; it never closes the browser. A leftover browser whose
renderer froze is detected by a short attach deadline, killed by identity (only pids
whose command line names our profile directory — the user's own Chrome is unreachable by
construction) and replaced in about a second.
While the managed browser runs, its endpoint is published for observers at
~/.pi-gui/run/<profile>/endpoint.json ({cdpUrl, port, pid, profileDir, updatedAt}),
modelled on Chrome's own DevToolsActivePort: a host that wants a live read-only view
(screencast, debugging) attaches there without asking pi-gui.
Action points are published the same way, as a sibling actions.jsonl: one JSON line
per successful action ({ts, kind, x?, y?, url}, plus direction for scrolls), where
x/y is the page-CSS-pixel point the action landed on — the given point for
coordinate actions, the element center for ref actions. A host compositing a live
preview reads it to animate a pointer where each action lands. Best-effort and
observer-only: appends are fire-and-forget, and the file is truncated once it exceeds
256 KB. Attached (non-managed) browsers publish nothing — they have no run dir.
Managed-browser knobs:
PI_GUI_PROFILE_DIR— profile directory (default~/.pi-gui/chrome).PI_GUI_CHROME— Chrome/Chromium binary, when discovery misses.PI_GUI_WINDOW=offscreen— park the window off-screen (for hosts that render their own preview). Off-screen, never minimised: a minimised window stops compositing, which kills screencast frames and hangs wheel input. Default is a visible window.
If no Chrome exists and none of the knobs point at one, the first browser action fails
with the fix in hand (install Chrome / set PI_GUI_CHROME / set PI_GUI_CDP_URL) — never
a silent degrade.
To drive an existing Chrome, start it with CDP enabled:
"/Applications/Google Chrome.app/Contents/MacOS/Google Chrome" --remote-debugging-port=9222
A stale PI_GUI_CDP_URL no longer blocks startup. For a strict endpoint that must not
fall back to another browser, use pi-gui run --cdp <url> ....
Android (emulator)
The second driver (--driver android or PI_GUI_DRIVER=android) drives an Android
device entirely through public adb — the mechanisms are ported from mirasim's
simulator. Requirements: adb (platform-tools) and a booted device. Device DISCOVERY
picks emulators only (never whatever phone is plugged in); an explicitly named serial —
PI_GUI_ANDROID_SERIAL, tcp serials like a redroid container included — is honored
verbatim as the caller's informed choice.
Lifecycle is a separate concern from driving, with its own verbs:
pi-gui android doctor # adb/emulator/SDK/AVD/device state, with the fix for each gap
pi-gui android setup --yes # install SDK pieces + create the profile's AVD
# (--profile plain|androidworld; androidworld = Pixel 6 /
# API 33 / -grpc 8554 / cold boot — the benchmark's exact
# environment; ≈1.5 GB from Google, explicit consent only)
pi-gui android start # adopt a running emulator or boot one (window shown;
# --headless boots -no-window -gpu host); prints the serial
pi-gui android stop # stop an emulator (emulator-<port> serials only)
An existing SDK (ANDROID_HOME / ANDROID_SDK_ROOT / the default install paths) is
adopted, never duplicated; a fresh install lands in ~/.pi-gui/android-sdk. Setup — and
only setup — needs a JRE 17+ (sdkmanager/avdmanager are JVM tools).
What works: observations come from uiautomator dump — every element named with exact
bounds, which on Android is both cheaper and more precise than pixels; screenshots via
screencap, downscaled to ≤1568px on the long side with the model's point coordinates
mapped back to device pixels (vision APIs silently resize past that, which would break
every tap); input via adb shell input (tap at ref centers or coordinates, swipe-based
scrolling, text with shell escaping, keyevents for Enter/Back/Home/Tab/arrows); extra
verbs swipe (explicit gesture), launch_app (resolved launcher activity via
am start — never monkey, which lies about success), open_url (VIEW intent) and
back. All three strategies (tree/hybrid/pixels) apply.
Knobs:
PI_GUI_ANDROID_SERIAL— target device serial (default: the firstemulator-*fromadb devices). Any serialadb devicesshows is accepted when named.PI_GUI_ADB— adb binary, when discovery (PATH, then the default SDK locations) misses.
One honest limitation inherited from Android itself: the OS has a single UiAutomation
slot. While an accessibility service or instrumentation harness holds it,
uiautomator dump cannot run — element observations fail with the cause and fix named,
and the device can only be driven from screenshots (--strategy pixels) until the slot
is released.
Use
pi-gui # interactive TUI — the full pi experience, GUI tools only
pi-gui -p "task..." # non-interactive print mode (from the pi runtime)
pi-gui run --task "..." --json # headless one-shot, JSONL events (integration contract)
pi-gui mcp # MCP stdio server for Claude Code / Codex
pi-gui eval setup android # one command to a runnable eval harness (browser|android)
Inside the TUI, /eval lists every installed benchmark (WebArena, AndroidWorld) and
/eval <benchmark> <task> runs a case in-session — environment staged automatically
(an emulator boots itself if needed), every tool call visible, verdict from the same
harness batch runs use. A benchmark needing the other driver answers with the exact
relaunch command instead of half-working. Embedding pi-gui in another agent:
docs/EMBEDDING.md.
- TUI / print / rpc run on the pi runtime (
pi-coding-agent's exportedmain()) with an inline extension that registers thegui_*tools, narrows the toolset to GUI-only, and swaps in the GUI system prompt. Models,/login, sessions, and~/.pi/agentauth are shared with pi. pi's native skills and CLAUDE.md/AGENTS.md discovery are disabled — prompt content comes only from pi-gui's own knobs. To use a skill library in GUI sessions, pointPI_GUI_SKILL_DIRat it (:-separated dirs, SKILL.md format —~/.pi/agent/skillsand~/.claude/skillsload unchanged); skills are then listed by name in the prompt and fetched via theload_skilltool.PI_GUI_IDENTITY_FILEreplaces the identity line of the system prompt (the operational rules always remain). runis our own thin runner on pi-agent-core: one task per process, events on stdout (assistant/action/observation/result), exit code 0 iff status isdone. Model via--model provider/idorPI_GUI_MODEL(defaultopenrouter/google/gemini-2.5-flash; API keys via provider env vars).mcpserves the tools to a host that brings its own agent loop:claude mcp add pi-gui -- pi-gui mcp
Model gateway (host embedding)
A host that routes model traffic through its own Anthropic-Messages-compatible endpoint
(a local proxy, a relay, any LiteLLM-style gateway) sets one env knob — nothing is written
to ~/.pi, and standalone use is unaffected when the variable is absent:
PI_GUI_GATEWAY_URL=http://127.0.0.1:PORT # endpoint origin (anthropic-messages wire)
PI_GUI_GATEWAY_KEY=... # credential, sent as x-api-key
PI_GUI_GATEWAY_MODELS=google/gemini-3.5-flash,claude-sonnet-5 # wire ids, verbatim
PI_GUI_GATEWAY_PROVIDER=mirasim # pi-local prefix, default "gateway"
This registers an in-memory provider on every entry (TUI/print/rpc and run), and the
default model becomes PI_GUI_MODEL or the first roster entry. See src/agent/gateway.ts.
Already a pi user? The package is also a regular pi extension:
pi install npm:@mirofish-ai/pi-gui-use # adds gui_* tools to your normal pi
Tools
Three tools, assembled per driver × strategy at session start:
| tool | role |
|---|---|
gui_targets |
list operable surfaces (tabs / windows / apps) with stable ids |
gui_observe |
read state — what: elements (refs + form states) / text / screenshot / full |
gui_act |
every mutation: click, hover, set_text, keys, scroll, focus, wait + driver verbs (open_url, select_option, back, reload, ...); results report whether the screen changed |
Strategies (--strategy or PI_GUI_STRATEGY): tree (text element lists, default),
pixels (screenshots + coordinates), hybrid (refs + screenshot fallback).
Layout
src/platform/ the platform layer: shared types, abstract GuiDriver, the 3 tool factories,
driver subclasses (browser + android today; macos/... later), registry
src/agent/ session bootstrap (profile, prompt assembly, skills) + our own loop: runner, pruning
src/entries/ the four ways in: cli, tui, mcp, extension (+ entries/commands/ bodies)
src/index.ts library export
Repository maintainers can see CLAUDE.md for the design rules. It remains in the
source repository and is intentionally excluded from the published npm package.
Controlling the pi version
pi packages are pinned exactly (pi releases in lockstep):
npm run pi:set 0.84.0 # rewrite pins + reinstall
npm test # typecheck + smoke + agent test
Development
npm run dev # run the CLI from source (tsx)
npm test # typecheck + smoke (real headless Chrome) + agent test (faux LLM, no key)
npm run demo # watch a headed Chrome driven live, agent view printed in lockstep
npm run build # compile to dist/ (what the published bin runs)
Publishing to npm (maintainers)
The release script requires an active npm login. It checks that the Git working tree is clean, verifies that the version is unused, runs the test and build suites, rejects packages containing source/private files, performs an npm dry run, and asks for confirmation immediately before the public publish.
From the source repository, run:
npm run release:npm
Confirm the active account before publishing:
npm whoami
If this command is not authenticated, run npm login first. The logged-in account
must have permission to create or update the package's scope.
The equivalent manual validation and publishing commands are:
npm whoami
npm ci
npm test
npm run build
npm pack --dry-run
npm publish --dry-run --access public
npm publish --access public
npm view @mirofish-ai/pi-gui-use version
Each npm version is immutable. Increment version in package.json before publishing
a subsequent release.