A native desktop client for the Pi coding agent — sessions, transcripts,
worktrees and diffs in one window, entirely on your machine. No Electron: the UI is
Flutter rendering with the GPU, the window chrome is raw Win32, and the agent talks
to pi --mode rpc over strict JSONL.
- Multi-session, multi-project — every session owns its own
piprocess, so turns keep running while you switch around. - Native window — frameless title bar with Win11 rounded corners, snap, and edge-resizing (Win32 FFI), plus native folder/file pickers (no plugins).
- Git worktrees — one click creates
~/.pi_studio/worktrees/<repo>/<branch>and runs the session there, so parallel agents never collide. - Review rail — line-numbered diff viewer with per-file counts, a file tree, and a
command runner; live
+added / -removedstats in the header. - Agent activity feed —
Processing 2m 20s · 76 token/sheader, one-line steps, collapse to the current step, and aThought N, Viewed N, Ran Nsummary per turn. - Sessions sidebar — grouped by age, searchable, with per-session ⋮ actions (rename / archive / delete) and a timeline scrubber to jump between turns.
- Composer — model + reasoning picker with search, context-usage ring with token
and cost breakdowns,
@filementions, and access mode pill. - Settings page — a full page for pi's global
~/.pi/agent/settings.json: model and thinking defaults, tools, compaction, retries, transport, interface, and package/skill/theme lists. Only the keys the page models are written; the rest of the file is preserved verbatim, with a.bakon every save. - Providers & models — add and edit custom providers and their models in
~/.pi/agent/models.json: base URL, API type, key, headers, and per-model reasoning/vision/context/output/cost. A Discover button asks the provider what it serves and fills the list from its own/modelsendpoint. Reuse a built-in id to reroute it through a proxy instead of adding a second provider. pi re-reads this file whenever its model picker opens, so edits need no restart.
lib/
main.dart UI shell: title bar, sidebar, transcript, composer, rail
ui/app_theme.dart Design tokens: surfaces, text ramp, accents, motion
ui/primitives.dart Shared leaves: hover tint, code box, splitter, rail tabs
ui/transcript_view.dart Transcript: chat items, activity, scrubber, skeleton
ui/review_pane.dart Review rail: diff view, file tree, terminal panel
ui/session_chrome.dart Session chrome: rows, pills, ring, title bar, menus
pi/pi_client.dart Backend client: bundled pi_runtime/ or `pi` on PATH (JSONL RPC)
pi/pi_runtime.dart Bundled-vs-PATH backend resolution (`pi_runtime/`, `PI_STUDIO_PI`)
pi/session_store.dart Session discovery in ~/.pi/agent/sessions
state/session_controller.dart One session = one pi process + transcript state
state/projects_store.dart Persisted project + archive lists
git/git_ops.dart Worktrees, branches, diffs (shells out to git)
platform/folder_picker.dart Win32 IFileOpenDialog / zenity
platform/window_controls.dart Win32 drag, resize, minimize/maximize/close
settings/json_file_store.dart Shared read-modify-write JSON store
settings/pi_settings.dart Read-modify-write of pi's settings.json
settings/settings_page.dart The settings page UI
settings/pi_models.dart Providers and models in pi's models.json
settings/model_discovery.dart Config-value resolution and /models discovery
windows/runner/ Custom frameless window (WM_NCCALCSIZE, hit-testing, DWM)
tool/ Small diagnostic scripts
Windows (requires Flutter 3.47+ and Visual Studio with the C++ workload):
flutter pub get
flutter build windows --release # -> build\windows\x64\runner\Release\Release builds bundle the pi backend so users need no install: the release
workflow resolves npm's latest tag once and installs that same
@earendil-works/pi-coding-agent version on each platform with Node 22, then runs
tool/stage_pi_runtime.ps1 (Windows) or tool/stage_pi_runtime.sh (Linux)
to stage pi_runtime/ (node binary + pi bundle) next to the app executable.
The app prefers the bundled runtime and falls back to pi on PATH, so local
dev builds keep working without staging anything. Override with the
PI_STUDIO_PI env var (path to a pi executable) to test a different pi.
In release builds, Settings → About can check for and install Pi runtime
updates per user; new sessions use the update while existing sessions finish
on their current runtime.
Linux (requires clang, cmake, ninja, pkg-config, libgtk-3-dev):
flutter build linux --release # -> build/linux/x64/release/bundle/On Linux the app keeps the native GTK header bar (the custom title bar and
resize bands are Windows-only), opens files with xdg-open, and uses
zenity/kdialog for the folder picker when installed (otherwise a text
prompt).
An Inno Setup script for the Windows installer lives at installer.iss.
# Reliable: one file per invocation.
Get-ChildItem test -Filter *_test.dart |
ForEach-Object { flutter test $_.FullName }Run the whole directory in one flutter test and it goes flaky. With several
files in flight the runner intermittently kills a test isolate — surfacing as
Connection closed before test suite loaded, or a run of did not complete
with no exception at all, sometimes for every test in a file that passes on its
own. --concurrency=1 helps but is not reliable; one invocation per file is.
A single-file run has never been observed to fail this way.
Two further constraints, both learned the hard way:
- One
pumpWidgetper test file. Repeatedly replacing the widget tree destabilises the isolate for the rest of the file. Add new cases inside the existing single test, or give them their own file. - Real file I/O needs
tester.runAsync. AtestWidgetsbody runs in a fake-async zone wheredart:iofutures never complete, so a disk-backed store leaves the page stuck loading.PiSettings.inMemoryexists for this.
- Node 22+ and the
piCLI are only needed for dev builds (the app falls back topion PATH when no bundled runtime is staged). Release builds ship their own runtime: no install needed, just your provider API keys. - Windows 10/11, or a Linux desktop with GTK 3.
gitfor worktrees and diffs (optional).
MIT — see LICENSE.