agy-ui-mcp 0.1.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (37) hide show
  1. agy_ui_mcp-0.1.0/LICENSE +21 -0
  2. agy_ui_mcp-0.1.0/PKG-INFO +467 -0
  3. agy_ui_mcp-0.1.0/README.md +432 -0
  4. agy_ui_mcp-0.1.0/pyproject.toml +82 -0
  5. agy_ui_mcp-0.1.0/setup.cfg +4 -0
  6. agy_ui_mcp-0.1.0/src/agy_ui_mcp/__init__.py +25 -0
  7. agy_ui_mcp-0.1.0/src/agy_ui_mcp/__main__.py +24 -0
  8. agy_ui_mcp-0.1.0/src/agy_ui_mcp/agy_runner.py +368 -0
  9. agy_ui_mcp-0.1.0/src/agy_ui_mcp/policy_builder.py +271 -0
  10. agy_ui_mcp-0.1.0/src/agy_ui_mcp/scope.py +743 -0
  11. agy_ui_mcp-0.1.0/src/agy_ui_mcp/screenshot.py +1335 -0
  12. agy_ui_mcp-0.1.0/src/agy_ui_mcp/server.py +1547 -0
  13. agy_ui_mcp-0.1.0/src/agy_ui_mcp/vendor/axe.min.js +12 -0
  14. agy_ui_mcp-0.1.0/src/agy_ui_mcp/worktree.py +475 -0
  15. agy_ui_mcp-0.1.0/src/agy_ui_mcp.egg-info/PKG-INFO +467 -0
  16. agy_ui_mcp-0.1.0/src/agy_ui_mcp.egg-info/SOURCES.txt +35 -0
  17. agy_ui_mcp-0.1.0/src/agy_ui_mcp.egg-info/dependency_links.txt +1 -0
  18. agy_ui_mcp-0.1.0/src/agy_ui_mcp.egg-info/entry_points.txt +2 -0
  19. agy_ui_mcp-0.1.0/src/agy_ui_mcp.egg-info/requires.txt +8 -0
  20. agy_ui_mcp-0.1.0/src/agy_ui_mcp.egg-info/top_level.txt +1 -0
  21. agy_ui_mcp-0.1.0/tests/test_a11y.py +63 -0
  22. agy_ui_mcp-0.1.0/tests/test_android.py +387 -0
  23. agy_ui_mcp-0.1.0/tests/test_apply.py +143 -0
  24. agy_ui_mcp-0.1.0/tests/test_browser_launch.py +197 -0
  25. agy_ui_mcp-0.1.0/tests/test_diff_gate.py +138 -0
  26. agy_ui_mcp-0.1.0/tests/test_in_place_safety.py +181 -0
  27. agy_ui_mcp-0.1.0/tests/test_native_flow.py +584 -0
  28. agy_ui_mcp-0.1.0/tests/test_parse_match.py +137 -0
  29. agy_ui_mcp-0.1.0/tests/test_platform.py +163 -0
  30. agy_ui_mcp-0.1.0/tests/test_policy_builder.py +102 -0
  31. agy_ui_mcp-0.1.0/tests/test_prompt_targets.py +104 -0
  32. agy_ui_mcp-0.1.0/tests/test_scope.py +76 -0
  33. agy_ui_mcp-0.1.0/tests/test_simulator.py +375 -0
  34. agy_ui_mcp-0.1.0/tests/test_target_cases.py +171 -0
  35. agy_ui_mcp-0.1.0/tests/test_targets.py +234 -0
  36. agy_ui_mcp-0.1.0/tests/test_worktree_revert.py +122 -0
  37. agy_ui_mcp-0.1.0/tests/test_zero_config.py +457 -0
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 qdzsh
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1,467 @@
1
+ Metadata-Version: 2.4
2
+ Name: agy-ui-mcp
3
+ Version: 0.1.0
4
+ Summary: MCP server that delegates frontend/UI work to the agy CLI (Gemini), scoped via a diff-gate to never touch backend/API/business logic.
5
+ Author-email: qdzsh <github@qdzsh.dev>
6
+ License-Expression: MIT
7
+ Project-URL: Homepage, https://github.com/qdzsh/agy-ui-mcp
8
+ Project-URL: Repository, https://github.com/qdzsh/agy-ui-mcp
9
+ Project-URL: Issues, https://github.com/qdzsh/agy-ui-mcp/issues
10
+ Project-URL: Documentation, https://github.com/qdzsh/agy-ui-mcp#readme
11
+ Keywords: mcp,ui,frontend,gemini,agy,playwright
12
+ Classifier: Development Status :: 4 - Beta
13
+ Classifier: Intended Audience :: Developers
14
+ Classifier: Operating System :: POSIX
15
+ Classifier: Operating System :: MacOS
16
+ Classifier: Operating System :: POSIX :: Linux
17
+ Classifier: Programming Language :: Python :: 3
18
+ Classifier: Programming Language :: Python :: 3.10
19
+ Classifier: Programming Language :: Python :: 3.11
20
+ Classifier: Programming Language :: Python :: 3.12
21
+ Classifier: Programming Language :: Python :: 3.13
22
+ Classifier: Topic :: Software Development :: User Interfaces
23
+ Classifier: Topic :: Software Development :: Code Generators
24
+ Requires-Python: >=3.10
25
+ Description-Content-Type: text/markdown
26
+ License-File: LICENSE
27
+ Requires-Dist: mcp<2,>=1.0
28
+ Requires-Dist: pydantic<3,>=2
29
+ Requires-Dist: pyyaml<7,>=6
30
+ Requires-Dist: pathspec<1,>=0.12
31
+ Requires-Dist: playwright<2,>=1.40
32
+ Provides-Extra: dev
33
+ Requires-Dist: pytest>=8; extra == "dev"
34
+ Dynamic: license-file
35
+
36
+ # agy-ui-mcp
37
+
38
+ An MCP (Model Context Protocol) server that delegates **frontend / UI** work to
39
+ Google Antigravity's **`agy` CLI** (Gemini) - while guaranteeing the agent
40
+ **never touches backend, API, or business logic**. It is designed to be shared
41
+ by **Claude Code** and **Codex** as a dedicated "FE/UI worker".
42
+
43
+ The server exposes two tools:
44
+
45
+ - **`ui_implement`** - an iterative vision loop: screenshot the running app,
46
+ prompt `agy` to edit CSS/components toward the target design, diff-gate the
47
+ result to revert anything out of scope, re-screenshot, and repeat until it
48
+ converges (or hits `max_iters`). Edits are applied to your working tree.
49
+ - **`ui_review`** - serve the app, screenshot it across every target
50
+ (route × device × theme × state), optionally run accessibility checks, and
51
+ have `agy` critique it **read-only** (any edit `agy` makes is reverted).
52
+
53
+ ## What it can drive
54
+
55
+ | Surface | Platform values | How it's captured |
56
+ |---|---|---|
57
+ | Web apps | `web` (default) | Playwright (Chromium) over the dev-server URL |
58
+ | Mobile **web-targets** | `expo-web`, `ionic`, `flutter-web` | Playwright (same as web) |
59
+ | Native **iOS** | `ios-sim` | `flutter run` on the iOS Simulator + `xcrun simctl` screenshots |
60
+ | Native **Android** | `android-emu` | `flutter run` on an Android emulator + `adb` screenshots |
61
+
62
+ Across these it supports responsive viewports, device emulation, dark mode /
63
+ `prefers-color-scheme`, `forced-colors` (high contrast), print media, RTL,
64
+ component states (via `pre_steps`), seeded `localStorage`, per-target design
65
+ references, and match-score convergence when design references are provided.
66
+
67
+ **Accessibility:** for web targets, `ui_review` injects the vendored
68
+ [axe-core](https://github.com/dequelabs/axe-core) into the page and returns
69
+ structured WCAG violations (per target), which also ground `agy`'s critique.
70
+
71
+ ## Use case: realign a drifted frontend
72
+
73
+ The case this server is built for: you (or Claude Code / Codex) shipped a
74
+ **full-stack project** - backend and frontend both done - but the **FE drifted
75
+ from, or doesn't match, the original design** (screen mockups, or design tokens
76
+ with an HTML/CSS demo). You want to **redo the FE to match the design without
77
+ risking the working backend**. That is exactly what the diff-gate guarantees:
78
+ `agy` realigns the UI, and anything outside your FE `allow` scope (API, server,
79
+ business logic) is reverted automatically.
80
+
81
+ **What it's strong at vs. where it needs help** - this is an *iterative
82
+ refinement loop*, not a from-scratch FE generator:
83
+
84
+ | Your FE today | Fit |
85
+ |---|---|
86
+ | Structure is right, **styling/layout/colors/spacing/responsive** is off | **Great** - its core job; realistically ~80-90% then human polish |
87
+ | **Partly** wrong (a few components / screens drifted) | Good - run it **screen by screen** with the matching `design_ref` |
88
+ | **Structurally** wrong (wrong component tree, missing screens, wrong layout) | **Partial** - it nudges existing code toward the design within scope; it does **not** rebuild markup from scratch. Have Claude Code/Codex scaffold the correct structure first, then use this server to drive pixel fidelity |
89
+
90
+ Fidelity is highest when you provide an **HTML/CSS demo or design tokens** (exact
91
+ colors/spacing/fonts) rather than an image alone (values are inferred from
92
+ pixels). Note the convergence score is `agy`'s **own visual self-assessment** -
93
+ always eyeball the returned `shots_before`/`shots_after` and `diff` to sign off.
94
+
95
+ **Workflow**
96
+
97
+ 1. **Commit** your current state (a dirty tree is fine - it's snapshotted and
98
+ preserved; the only requirement is a git repo with ≥1 commit).
99
+ 2. Drop a `.agy-ui-scope` that **allows only FE files** and **denies the
100
+ backend**, declares how to serve the app, and lists **one target per screen**
101
+ with that screen's `design_ref` (see below).
102
+ 3. Run **`ui_implement`** per screen with its design ref; review
103
+ `shots_before`/`shots_after` + `diff`, then iterate (`max_iters`).
104
+ 4. Run **`ui_review`** (read-only + a11y) to have `agy` critique what's left and
105
+ surface WCAG issues.
106
+ 5. **Human-polish** the last ~10-20% and anything structural the loop can't
107
+ reach within scope.
108
+
109
+ **Sample config** - a Vite/React app, realigned screen-by-screen against
110
+ mockups in `./design/`:
111
+
112
+ ```yaml
113
+ model: "gemini-3.5-flash"
114
+ platform: web
115
+
116
+ # FE surface agy may edit/create.
117
+ allow:
118
+ - "src/**/*.css"
119
+ - "src/**/*.scss"
120
+ - "src/components/**"
121
+ - "src/**/*.tsx"
122
+ - "index.html"
123
+
124
+ # Backend / logic - always reverted, even if agy edits them.
125
+ deny:
126
+ - "**/api/**"
127
+ - "**/server/**"
128
+ - "**/*.server.*"
129
+ - "**/route.*"
130
+
131
+ # Sensitive entry points - reverted AND reported for a human to decide.
132
+ ambiguous:
133
+ - "src/main.tsx"
134
+ - "src/App.tsx"
135
+ - "vite.config.*"
136
+
137
+ serve:
138
+ cmd: "npm run dev"
139
+ url: "http://localhost:5173"
140
+ ready_timeout: 30
141
+
142
+ devices:
143
+ desktop: { width: 1440, height: 900 }
144
+ mobile: { name: "iPhone 13" } # full Playwright device emulation
145
+
146
+ # One capture per screen, each matched against its own design mockup.
147
+ # (targets supersedes the simple `viewports` list when present.)
148
+ targets:
149
+ - name: "home-desktop"
150
+ route: "/"
151
+ device: "desktop"
152
+ design_ref: "./design/home-desktop.png" # image OR an HTML/CSS demo render
153
+
154
+ - name: "dashboard-desktop"
155
+ route: "/dashboard"
156
+ device: "desktop"
157
+ design_ref: "./design/dashboard-desktop.png"
158
+
159
+ - name: "settings-mobile-dark"
160
+ route: "/settings"
161
+ device: "mobile"
162
+ color_scheme: "dark" # emulate prefers-color-scheme: dark
163
+ design_ref: "./design/settings-mobile-dark.png"
164
+ ```
165
+
166
+ Then drive each screen, e.g. `ui_implement(project_dir=".",
167
+ task="Match this screen to its design_ref", target_route="/dashboard")`. Targets
168
+ carry the per-screen mockup; `target_route` picks which one to work on.
169
+
170
+ ## Platform support
171
+
172
+ The server runs on **macOS and Linux**. It spawns `agy` (and native
173
+ `flutter run`) through a Unix pseudo-terminal (`pty`) and manages process groups
174
+ with POSIX-only calls, so **native Windows is not supported** - run it under
175
+ **WSL2** (Windows Subsystem for Linux) instead.
176
+
177
+ | OS | Web + mobile web-targets | Native Android | Native iOS |
178
+ |---|---|---|---|
179
+ | **macOS** | yes | yes | yes |
180
+ | **Linux** | yes | yes | no (iOS needs macOS + Xcode) |
181
+ | **Windows (native)** | no | no | no |
182
+ | **Windows via WSL2** | yes | with adb/emulator setup | no |
183
+
184
+ Notes:
185
+
186
+ - **iOS always requires macOS + Xcode**, regardless of host OS.
187
+ - **WSL2:** install the Linux build of `agy` (and log in) and run
188
+ `playwright install chromium` inside WSL. Web and mobile web-targets work out
189
+ of the box; native Android additionally needs `adb`/emulator wiring (e.g.
190
+ connecting to a Windows-side emulator over TCP, or running the emulator inside
191
+ WSL2).
192
+ - A native-Windows port would require replacing the `pty` layer with ConPTY
193
+ (e.g. `pywinpty`) and the POSIX process-group calls; it is not implemented.
194
+
195
+ ## How it works
196
+
197
+ - **PTY spawn.** `agy` is run as `agy -p "<prompt>"` through a Python
198
+ pseudo-terminal (`pty.openpty` + `subprocess.Popen`), because `agy` drops its
199
+ stdout when attached to a non-TTY pipe. Output is captured from the PTY
200
+ master; ANSI escapes and carriage returns are stripped.
201
+ - **Subscription auth.** `agy` authenticates via your existing Gemini
202
+ subscription/login - no `GEMINI_API_KEY` is passed by this server.
203
+ - **Diff-gate (the real guardrail).** Scope is **not** enforced inside `agy`.
204
+ Web runs happen in a throwaway **git worktree**; after each turn the server
205
+ classifies every changed path against your scope
206
+ (`deny > ambiguous > allow > default-deny`) and reverts anything not allowed
207
+ (ambiguous paths are reverted and reported as escalations). A staged edit is
208
+ restored from the baseline, not the index, so it cannot slip through.
209
+ - **Vision loop.** The orchestrator (this server) screenshots to files with
210
+ Playwright, embeds those paths in the prompt (`agy` opens them with its own
211
+ `read_file` tool - there is no image flag), lets `agy` edit, applies the
212
+ diff-gate, re-screenshots, and loops.
213
+ - **Native runs.** Native platforms run **in place** (no worktree, to reuse the
214
+ build cache). `flutter run` is launched under a PTY and **hot-reloaded** (`r`)
215
+ between iterations - with an automatic **hot-restart** (`R`) fallback when a
216
+ reload produces no visual change. A graceful quit (`q`) lets Flutter release
217
+ its lockfile cleanly.
218
+ - **In-place safety (snapshot-restore).** Before a native run, the server
219
+ snapshots your project's current state into a dangling git baseline commit
220
+ (without touching your index/HEAD/worktree) and records your pre-existing
221
+ untracked files. The diff-gate and reverts compare against that baseline, so
222
+ **only `agy`'s edits are gated/undone and your uncommitted work is preserved
223
+ exactly** - you do **not** need to commit or stash first. The only hard
224
+ requirement is that the project is a git repo with at least one commit; if it
225
+ isn't, the tool returns a structured `{"status": "blocked", ...}` result
226
+ explaining how to fix it (e.g. `git init`) instead of running unprotected.
227
+
228
+ ## Requirements
229
+
230
+ - **Python ≥ 3.10**
231
+ - **The `agy` CLI**, installed and logged in (subscription auth)
232
+ - **Playwright Chromium** for web/a11y captures - **auto-installed on first use**
233
+ (or set `AGY_UI_CHROME_CHANNEL=chrome` to reuse an already-installed Chrome);
234
+ not needed for native-only use
235
+ - **`.agy-ui-scope` is optional** - it is auto-detected from your stack when
236
+ absent. Run `ui_init` to generate one, and see `templates/agy-ui.rule.md` for
237
+ the recommended agent rule block
238
+ - **Native iOS** (`ios-sim`): macOS + Xcode + a Flutter project, and a booted
239
+ iOS Simulator
240
+ - **Native Android** (`android-emu`): the Android SDK platform-tools (`adb`) and
241
+ an AVD; the adapter can auto-launch the AVD by name (`emulator -avd <name>`)
242
+
243
+ ## Install
244
+
245
+ ### One-liner (easiest)
246
+
247
+ Runs a self-contained installer straight from the internet (no clone needed). It
248
+ installs the server, tries to install Chromium, and offers to register with
249
+ Claude Code:
250
+
251
+ ```bash
252
+ curl -fsSL https://raw.githubusercontent.com/qdzsh/agy-ui-mcp/main/scripts/bootstrap.sh | bash
253
+ ```
254
+
255
+ ### From GitHub (recommended)
256
+
257
+ The package is installed straight from the GitHub repo. (PyPI is intentionally
258
+ not used yet for security reasons; see "From PyPI" below.)
259
+
260
+ ```bash
261
+ # 1. Install the server (gives you an `agy-ui-mcp` command on PATH)
262
+ pipx install git+https://github.com/qdzsh/agy-ui-mcp
263
+ # or: uv tool install git+https://github.com/qdzsh/agy-ui-mcp
264
+
265
+ # 2. Register it with Claude Code
266
+ claude mcp add agy-ui --scope user -- agy-ui-mcp
267
+ ```
268
+
269
+ ### From PyPI (future, once published)
270
+
271
+ PyPI install is **not** available yet: the `agy-ui-mcp` name is not yet
272
+ published and reserved on PyPI, and installing an unclaimed name first would be
273
+ a name-squatting risk. Once the name is published and reserved, you will be able
274
+ to run:
275
+
276
+ ```bash
277
+ # Available only AFTER the package is published + reserved on PyPI.
278
+ pipx install agy-ui-mcp
279
+ # or: uv tool install agy-ui-mcp
280
+ ```
281
+
282
+ Until then, use the GitHub install above (or the one-liner, which is git-backed).
283
+
284
+ The Chromium browser auto-installs on first use, so there is no manual
285
+ `playwright install chromium` step. (To reuse an already-installed Chrome and
286
+ skip the download, set `AGY_UI_CHROME_CHANNEL=chrome`.)
287
+
288
+ ### From a clone (one command)
289
+
290
+ ```bash
291
+ git clone https://github.com/qdzsh/agy-ui-mcp.git && cd agy-ui-mcp
292
+ ./scripts/install.sh # installs the package + Chromium, and offers to
293
+ # register with Claude Code
294
+ ```
295
+
296
+ `scripts/install.sh` is interactive and idempotent; re-run it any time.
297
+
298
+ ## Zero-config quick start
299
+
300
+ You do **not** need a `.agy-ui-scope` file to get going - the server
301
+ auto-detects your stack (Vite/React, Next.js, Expo, Ionic, CRA, Flutter-web, or
302
+ generic web) and synthesizes a scope on the fly. The minimal flow:
303
+
304
+ 1. **Install** (any option above).
305
+ 2. **Log into `agy` once** (subscription auth - no API key).
306
+ 3. **Drop a mockup image** into your project, e.g. `./design/home.png`.
307
+ 4. **Ask the agent to match it** by calling:
308
+
309
+ ```
310
+ ui_implement(project_dir=".", task="Match the running home screen to this mockup",
311
+ design_refs=["./design/home.png"], target_route="/")
312
+ ```
313
+
314
+ That is enough for the loop to run with zero config files. When you want to
315
+ customize the scope (allow/deny globs, per-screen `targets`, serve command),
316
+ call `ui_init(project_dir=".")` once to detect your stack and write a real,
317
+ inspectable `.agy-ui-scope` you can edit (it never overwrites an existing one
318
+ unless `overwrite=True`). See `templates/agy-ui.rule.md` for a copy-paste rule
319
+ block that teaches your agent how to use this MCP correctly.
320
+
321
+ The Chromium browser **auto-installs on first use** (a one-time download; no
322
+ manual `playwright install chromium` step). Set `AGY_UI_CHROME_CHANNEL=chrome`
323
+ to reuse an already-installed Chrome and skip that download entirely.
324
+
325
+ ## Wire into Claude Code
326
+
327
+ ```bash
328
+ # If installed as a console script (pipx / uv tool / pip):
329
+ claude mcp add agy-ui --scope user -- agy-ui-mcp
330
+
331
+ # Or run the module directly (e.g. from an editable/venv install):
332
+ claude mcp add agy-ui --scope user -- python -m agy_ui_mcp
333
+ ```
334
+
335
+ ## Wire into Codex
336
+
337
+ Add to `~/.codex/config.toml`:
338
+
339
+ ```toml
340
+ [mcp_servers.agy-ui]
341
+ command = "agy-ui-mcp" # or: command = "python", args = ["-m", "agy_ui_mcp"]
342
+ ```
343
+
344
+ ## Configure a project
345
+
346
+ A `.agy-ui-scope` file is **optional** - without one the server auto-detects your
347
+ stack and synthesizes a scope (see "Zero-config quick start" above). Add a real
348
+ file only when you want to customize the allow/deny globs, serve command, or
349
+ per-screen `targets`. The easiest way is `ui_init(project_dir=".")`, which
350
+ detects your stack and writes a starter `.agy-ui-scope` you can then edit.
351
+
352
+ Alternatively, copy the fully annotated template and edit it for your stack:
353
+
354
+ ```bash
355
+ cp .agy-ui-scope.example /path/to/your/app/.agy-ui-scope
356
+ ```
357
+
358
+ A minimal web scope:
359
+
360
+ ```yaml
361
+ # platform: web # default; also expo-web / ionic / flutter-web / ios-sim / android-emu
362
+ allow:
363
+ - "src/**/*.css"
364
+ - "src/components/**"
365
+ deny:
366
+ - "src/api/**" # backend - agy edits here are always reverted
367
+ - "**/*.server.*"
368
+ ambiguous:
369
+ - "src/main.tsx" # reverted AND reported for a human to decide
370
+ serve:
371
+ cmd: "npm run dev"
372
+ url: "http://localhost:5173"
373
+ ready_timeout: 30
374
+ viewports: [1440, 768, 390]
375
+ model: "gemini-3.5-flash"
376
+ ```
377
+
378
+ A native (iOS) scope uses `targets` + a device registry instead of `viewports`:
379
+
380
+ ```yaml
381
+ platform: ios-sim
382
+ serve:
383
+ cmd: "flutter run -d <simulator-udid>" # argv-split (no shell) for native
384
+ url: ""
385
+ ready_timeout: 600 # first Xcode/gradle build is slow
386
+ allow: ["lib/main.dart"]
387
+ deny: ["lib/data.dart"]
388
+ devices:
389
+ sim: { name: "iPhone 17" } # or udid: "..."
390
+ targets:
391
+ - { name: order-mobile, device: sim }
392
+ model: "gemini-3.5-flash"
393
+ ```
394
+
395
+ See `.agy-ui-scope.example` for the full set of options (per-target
396
+ `design_ref`, `theme`, `rtl`, `color_scheme`, `forced_colors`, `media`,
397
+ `full_page`, `local_storage`, `pre_steps`, `serve.reload_cmd`, etc.).
398
+
399
+ ## Tool reference
400
+
401
+ > **Zero-config:** every tool works with no `.agy-ui-scope` present - the server
402
+ > auto-detects your stack (Vite/React, Next.js, Expo, Ionic, CRA, Flutter-web, or
403
+ > generic web) and synthesizes a scope for the run.
404
+
405
+ **`ui_init(project_dir=".", overwrite=False)`** -> detects your stack and writes a
406
+ starter `.agy-ui-scope` (never clobbers an existing one unless `overwrite=True`).
407
+ Returns `status` (`ok`/`exists`/`error`), `scope_path`, `written`,
408
+ `detected` (`{framework, platform, serve_cmd, serve_url, package_manager}`),
409
+ `allow`, `deny`, `design_dir_found`, `next_steps`, and `warnings`.
410
+
411
+ **`ui_implement(project_dir, task, design_refs=None, target_route=None,
412
+ max_iters=4, apply=True, match_threshold=90)`** → returns `files_changed`,
413
+ `diff`, `escalations`, `iterations`, `shots_before`/`shots_after`, `targets`,
414
+ `applied`/`applied_files`, `match_score`, `match_gaps`, `warnings`. When `apply`
415
+ is true the surviving in-scope edits are written to your working tree.
416
+
417
+ **`ui_review(project_dir, target_route=None, against_design=None, a11y=True)`**
418
+ → returns `critique`, `shots`, `targets`, `a11y` (`{target: [violations]}`),
419
+ `warnings`. Read-only.
420
+
421
+ Both may instead return `{"status": "blocked", "blocked_reason": "...", ...}`
422
+ when a native/in-place run can't be made safe (non-git or no commit yet) - the
423
+ `blocked_reason` tells you exactly what to do.
424
+
425
+ ## Notes & limitations
426
+
427
+ - **Native needs a git repo + ≥1 commit.** This is by design (the in-place
428
+ safety snapshot). A dirty working tree is fine and is preserved; only a
429
+ non-git or commit-less project is refused (with a clear message). Web runs use
430
+ an isolated worktree and also require git.
431
+ - **Playwright Chromium auto-installs.** Web and a11y features need a Chromium
432
+ browser; the server installs it automatically on first use (a one-time
433
+ download, with a short stderr notice). Set `AGY_UI_CHROME_CHANNEL=chrome` to
434
+ reuse an installed Chrome/Edge and skip the download, or
435
+ `AGY_UI_NO_BROWSER_AUTOINSTALL=1` to opt out. Native-only use needs no browser.
436
+ - **Native serve command is argv-split** (`shlex.split`, no shell) so the PTY
437
+ can deliver `r`/`R`/`q` keystrokes to `flutter` directly - shell features
438
+ (`&&`, env-var expansion, `cd`) in `serve.cmd` won't work for native.
439
+ - **First native build can take minutes** (Xcode / Gradle). Set
440
+ `serve.ready_timeout` generously (e.g. 300-600s).
441
+ - **Flutter scaffolding.** If `flutter run` reports a missing `ios/` or
442
+ `android/` project, regenerate it with `flutter create --platforms=ios .`
443
+ (or `android`).
444
+
445
+ ## Environment variables
446
+
447
+ | Variable | Effect |
448
+ |---|---|
449
+ | `AGY_UI_CHROME_CHANNEL` | Use an installed browser channel (e.g. `chrome` or `msedge`) instead of Playwright's bundled Chromium. Skips the one-time Chromium download. |
450
+ | `AGY_UI_CHROME_EXECUTABLE` | Explicit path to a Chromium-based browser executable to launch. |
451
+ | `AGY_UI_NO_BROWSER_AUTOINSTALL` | Set to a truthy value to disable the automatic `playwright install chromium` on first use. |
452
+ | `AGY_UI_DRY_RUN` | Skip every external side effect and return a stub payload (see below). |
453
+
454
+ ## Offline / dry runs
455
+
456
+ Set `AGY_UI_DRY_RUN=1` to make the tools skip every external side effect
457
+ (spawning `agy`, launching Playwright, running git, starting a dev server) and
458
+ return a stub payload. The package imports and the scope/diff-gate logic
459
+ unit-test without `agy`, Playwright browsers, or a running dev server.
460
+
461
+ ```bash
462
+ pip install -e ".[dev]" && python -m pytest -q
463
+ ```
464
+
465
+ ## License
466
+
467
+ MIT.