nolook 0.3.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.
- nolook-0.3.0/.claude-plugin/marketplace.json +7 -0
- nolook-0.3.0/.github/workflows/release.yml +19 -0
- nolook-0.3.0/.github/workflows/test.yml +19 -0
- nolook-0.3.0/.gitignore +6 -0
- nolook-0.3.0/LICENSE +21 -0
- nolook-0.3.0/PKG-INFO +251 -0
- nolook-0.3.0/README.md +225 -0
- nolook-0.3.0/README.zh-CN.md +172 -0
- nolook-0.3.0/plugin/.claude-plugin/plugin.json +19 -0
- nolook-0.3.0/plugin/agents/nolook-driver.md +36 -0
- nolook-0.3.0/plugin/hooks/hooks.json +1 -0
- nolook-0.3.0/plugin/hooks/register.tsx +201 -0
- nolook-0.3.0/plugin/skills/nolook/SKILL.md +214 -0
- nolook-0.3.0/plugin/types/index.d.ts +20 -0
- nolook-0.3.0/pyproject.toml +37 -0
- nolook-0.3.0/src/nolook/__init__.py +0 -0
- nolook-0.3.0/src/nolook/cli.py +369 -0
- nolook-0.3.0/src/nolook/config.py +89 -0
- nolook-0.3.0/src/nolook/device.py +360 -0
- nolook-0.3.0/src/nolook/doctor.py +94 -0
- nolook-0.3.0/src/nolook/flow.py +202 -0
- nolook-0.3.0/src/nolook/motion.py +449 -0
- nolook-0.3.0/src/nolook/perf.py +278 -0
- nolook-0.3.0/src/nolook/tracing.py +174 -0
- nolook-0.3.0/src/nolook/tree.py +341 -0
- nolook-0.3.0/tests/test_motion.py +66 -0
- nolook-0.3.0/tests/test_tree.py +120 -0
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
name: release
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
push:
|
|
5
|
+
tags: ["v*"]
|
|
6
|
+
|
|
7
|
+
jobs:
|
|
8
|
+
pypi:
|
|
9
|
+
runs-on: ubuntu-latest
|
|
10
|
+
environment: pypi
|
|
11
|
+
permissions:
|
|
12
|
+
id-token: write # PyPI Trusted Publishing: no token stored anywhere
|
|
13
|
+
steps:
|
|
14
|
+
- uses: actions/checkout@v4
|
|
15
|
+
- uses: astral-sh/setup-uv@v6
|
|
16
|
+
- name: Check the tag matches the package version
|
|
17
|
+
run: test "v$(grep -m1 '^version' pyproject.toml | cut -d'"' -f2)" = "$GITHUB_REF_NAME"
|
|
18
|
+
- run: uv build
|
|
19
|
+
- uses: pypa/gh-action-pypi-publish@release/v1
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
name: test
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
push:
|
|
5
|
+
pull_request:
|
|
6
|
+
|
|
7
|
+
jobs:
|
|
8
|
+
test:
|
|
9
|
+
runs-on: ubuntu-latest
|
|
10
|
+
strategy:
|
|
11
|
+
matrix:
|
|
12
|
+
python: ["3.11", "3.12", "3.13"]
|
|
13
|
+
steps:
|
|
14
|
+
- uses: actions/checkout@v4
|
|
15
|
+
- uses: astral-sh/setup-uv@v6
|
|
16
|
+
with:
|
|
17
|
+
python-version: ${{ matrix.python }}
|
|
18
|
+
- run: uv run --extra dev pytest -q
|
|
19
|
+
- run: uv run nolook --help >/dev/null
|
nolook-0.3.0/.gitignore
ADDED
nolook-0.3.0/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 HuanCheng65
|
|
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.
|
nolook-0.3.0/PKG-INFO
ADDED
|
@@ -0,0 +1,251 @@
|
|
|
1
|
+
Metadata-Version: 2.5
|
|
2
|
+
Name: nolook
|
|
3
|
+
Version: 0.3.0
|
|
4
|
+
Summary: Drive an Android device by what is on screen, not by screenshots. Built for coding agents.
|
|
5
|
+
Project-URL: Homepage, https://github.com/HuanCheng65/nolook
|
|
6
|
+
Project-URL: Issues, https://github.com/HuanCheng65/nolook/issues
|
|
7
|
+
Author: HuanCheng65
|
|
8
|
+
License-Expression: MIT
|
|
9
|
+
License-File: LICENSE
|
|
10
|
+
Keywords: adb,ai-agents,android,claude-code,testing,uiautomator
|
|
11
|
+
Classifier: Development Status :: 4 - Beta
|
|
12
|
+
Classifier: Environment :: Console
|
|
13
|
+
Classifier: Intended Audience :: Developers
|
|
14
|
+
Classifier: Operating System :: MacOS
|
|
15
|
+
Classifier: Operating System :: POSIX :: Linux
|
|
16
|
+
Classifier: Programming Language :: Python :: 3
|
|
17
|
+
Classifier: Topic :: Software Development :: Testing
|
|
18
|
+
Requires-Python: >=3.11
|
|
19
|
+
Requires-Dist: numpy>=1.26
|
|
20
|
+
Requires-Dist: perfetto>=0.50
|
|
21
|
+
Requires-Dist: pillow>=10
|
|
22
|
+
Requires-Dist: uiautomator2>=3.7
|
|
23
|
+
Provides-Extra: dev
|
|
24
|
+
Requires-Dist: pytest>=8; extra == 'dev'
|
|
25
|
+
Description-Content-Type: text/markdown
|
|
26
|
+
|
|
27
|
+
# nolook
|
|
28
|
+
|
|
29
|
+
Drive Android apps from a coding agent by reading the UI tree, not screenshots.
|
|
30
|
+
|
|
31
|
+
[中文说明](README.zh-CN.md)
|
|
32
|
+
|
|
33
|
+
An agent that debugs an app on a device usually works in a loop: take a
|
|
34
|
+
screenshot, read it, guess the coordinates of a button, tap, take another
|
|
35
|
+
screenshot. Every one of those images stays in the conversation and is sent
|
|
36
|
+
again with every later request, so the session gets slower and more
|
|
37
|
+
expensive with each step, and a guessed coordinate can tap the wrong thing.
|
|
38
|
+
|
|
39
|
+
nolook gives the agent the screen as a short text list instead. It taps by
|
|
40
|
+
what an element says or its id, reports what changed after each action, runs
|
|
41
|
+
several steps in one call, and takes small cropped screenshots only when the
|
|
42
|
+
question is how something looks.
|
|
43
|
+
|
|
44
|
+
```
|
|
45
|
+
$ nolook snap
|
|
46
|
+
com.example.notes · 1080x2400 · 9 elements
|
|
47
|
+
@e1 btn "Back" (63,140)
|
|
48
|
+
@e2 text "Notes" (300,140)
|
|
49
|
+
@e3 scroll #note_list (540,1250)
|
|
50
|
+
@e4 btn "Groceries · Milk, eggs, bread" (540,420)
|
|
51
|
+
@e5 btn "Trip · Tickets booked" (540,610)
|
|
52
|
+
@e6 btn "New note" #fab_new [covered] (960,2180)
|
|
53
|
+
@e7 btn "Search" (1000,140)
|
|
54
|
+
|
|
55
|
+
$ nolook tap Groceries
|
|
56
|
+
tap @e4 btn "Groceries · Milk, eggs, bread" (540,420) · settled 0.42s
|
|
57
|
+
screen changed (6 elements):
|
|
58
|
+
@e8 btn "Back" (63,140)
|
|
59
|
+
@e9 input "Groceries" #title (540,300)
|
|
60
|
+
@e10 input "Milk, eggs, bread" #body (540,700)
|
|
61
|
+
…
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
## What it does
|
|
65
|
+
|
|
66
|
+
- **Reads the screen as text.** `snap` lists interactive elements and text,
|
|
67
|
+
one line each, with a short ref (`@e4`). List rows read as one line.
|
|
68
|
+
Elements under a floating bar are marked `[covered]`.
|
|
69
|
+
- **Acts by query, safely.** `tap Groceries`, `tap #fab_new`, `tap @e4`.
|
|
70
|
+
A query must match exactly one element; a ref that left the screen is an
|
|
71
|
+
error, never a tap on whatever is there now. Taps on covered elements and
|
|
72
|
+
on labels that look like write actions (like, follow, send, delete …) are
|
|
73
|
+
refused unless asked for explicitly.
|
|
74
|
+
- **Reports what changed.** Every action waits for the screen to settle and
|
|
75
|
+
prints the elements that appeared and disappeared, so the agent rarely
|
|
76
|
+
needs to look again.
|
|
77
|
+
- **Runs flows.** Several steps with branches and loops, as a short Python
|
|
78
|
+
file, in one call. Flows worth keeping live in the project and run by name.
|
|
79
|
+
- **Takes small screenshots on request.** A crop to one element, as a small
|
|
80
|
+
JPEG, saved to a file; nolook prints the path and an estimate of its token
|
|
81
|
+
cost, and the agent reads it only if it has to.
|
|
82
|
+
- **Measures animations.** It records the screen around a tap and reports
|
|
83
|
+
duration, the 50% and 90% points, the shape of the progress curve,
|
|
84
|
+
overshoot and stalls, with one sheet of key frames.
|
|
85
|
+
- **Measures performance.** Frame timing over repeated flings (gfxinfo),
|
|
86
|
+
compile modes, Perfetto traces read into a one-page summary, ANR stacks.
|
|
87
|
+
- **Shows you the device** (Claude Code): a pane beside the chat with the
|
|
88
|
+
device's screen after each command. You see it; the model does not.
|
|
89
|
+
|
|
90
|
+
## Install
|
|
91
|
+
|
|
92
|
+
Requirements: Python 3.11+, `adb` on `PATH`, a device or emulator with USB
|
|
93
|
+
debugging. `ffmpeg` for `motion`. On first connect nolook pushes
|
|
94
|
+
uiautomator2's test server to the device; nothing is installed in your app.
|
|
95
|
+
|
|
96
|
+
```sh
|
|
97
|
+
uv tool install nolook # or: pipx install nolook
|
|
98
|
+
nolook doctor # checks adb, the device, ffmpeg, Perfetto
|
|
99
|
+
```
|
|
100
|
+
|
|
101
|
+
### Claude Code
|
|
102
|
+
|
|
103
|
+
```
|
|
104
|
+
/plugin marketplace add HuanCheng65/nolook
|
|
105
|
+
/plugin install nolook@nolook
|
|
106
|
+
```
|
|
107
|
+
|
|
108
|
+
The plugin adds the `nolook` skill (how to use the CLI well), a
|
|
109
|
+
`nolook-driver` agent for multi-step device work that reports back in text,
|
|
110
|
+
and the `/device` pane.
|
|
111
|
+
|
|
112
|
+
### Other agents
|
|
113
|
+
|
|
114
|
+
Any agent that can run shell commands can use the CLI. Give it
|
|
115
|
+
[`plugin/skills/nolook/SKILL.md`](plugin/skills/nolook/SKILL.md) as
|
|
116
|
+
instructions.
|
|
117
|
+
|
|
118
|
+
## Usage
|
|
119
|
+
|
|
120
|
+
```sh
|
|
121
|
+
nolook snap # what is on screen
|
|
122
|
+
nolook tap "Network & internet" # tap by text; prints what changed
|
|
123
|
+
nolook type "hello" --into "#search"
|
|
124
|
+
nolook scroll down --until "About phone"
|
|
125
|
+
nolook wait "Saved" --timeout 5
|
|
126
|
+
nolook back
|
|
127
|
+
nolook shot "#toolbar" # small crop; prints its path
|
|
128
|
+
nolook run flows/open_note.py # several steps in one call
|
|
129
|
+
```
|
|
130
|
+
|
|
131
|
+
### Queries
|
|
132
|
+
|
|
133
|
+
| Query | Matches |
|
|
134
|
+
|---|---|
|
|
135
|
+
| `@e12` | a ref from the last output |
|
|
136
|
+
| `#title` | resource id or Compose test tag |
|
|
137
|
+
| `Save` | text, content description, or one part of a row's label, exactly |
|
|
138
|
+
| `~Sav` | label contains |
|
|
139
|
+
| `/^Item \d+$/` | label matches a regex |
|
|
140
|
+
| `btn:Save` | any of the above, limited to a role |
|
|
141
|
+
|
|
142
|
+
A query that matches nothing lists the closest elements; one that matches
|
|
143
|
+
several lists them and asks for a ref, a role or `--nth`.
|
|
144
|
+
|
|
145
|
+
### Flows
|
|
146
|
+
|
|
147
|
+
```python
|
|
148
|
+
"""Open a note and check the editor."""
|
|
149
|
+
d.launch(fresh=True)
|
|
150
|
+
d.tap("Groceries")
|
|
151
|
+
if d.exists("Discard draft?"):
|
|
152
|
+
d.tap("Keep")
|
|
153
|
+
d.check(d.exists("#body"), "editor open")
|
|
154
|
+
d.shot("#toolbar")
|
|
155
|
+
```
|
|
156
|
+
|
|
157
|
+
`nolook run open_note` prints one line per step. On failure it prints the
|
|
158
|
+
step and line, the screen at that moment, and the path of a screenshot it
|
|
159
|
+
saved without reading.
|
|
160
|
+
|
|
161
|
+
### Animations
|
|
162
|
+
|
|
163
|
+
```sh
|
|
164
|
+
nolook motion --tap "#fab_new" --label open
|
|
165
|
+
nolook motion --key back --label close --slow 4
|
|
166
|
+
nolook motion-compare
|
|
167
|
+
```
|
|
168
|
+
|
|
169
|
+
```
|
|
170
|
+
motion [open] · #fab_new · 61 frames over 1.2s
|
|
171
|
+
runs 335ms to within 1% of its end · 50% at 54ms · 90% at 137ms · in [486, 331, 1080, 1168]
|
|
172
|
+
progress ▁▃▅▆▆▇██████████ decelerating (ease-out) · by position (p at 25/50/75% of time: 0.73/0.99/0.99)
|
|
173
|
+
monotonic: yes
|
|
174
|
+
14 frames in motion, median 16.4ms apart · no stalls
|
|
175
|
+
sheet (not read): ~/.cache/nolook/…/shots/012554-motion.jpg
|
|
176
|
+
```
|
|
177
|
+
|
|
178
|
+
It measures pixels inside the area that changed, comparing each frame with
|
|
179
|
+
the first and the last, so a spring's slow tail counts and a small menu is
|
|
180
|
+
not lost in a still screen. `--slow N` slows animations while recording,
|
|
181
|
+
for devices that record few frames.
|
|
182
|
+
|
|
183
|
+
### Performance
|
|
184
|
+
|
|
185
|
+
```sh
|
|
186
|
+
nolook perf frames --label before # gfxinfo reset → flings → summary
|
|
187
|
+
nolook perf frames --label after --trace traces/
|
|
188
|
+
nolook perf compare
|
|
189
|
+
nolook trace summary traces/nolook-….pftrace
|
|
190
|
+
nolook perf compile speed # ART compile mode, then relaunch
|
|
191
|
+
nolook perf anr
|
|
192
|
+
```
|
|
193
|
+
|
|
194
|
+
`trace summary` reads a trace with Perfetto's trace processor, which it
|
|
195
|
+
downloads on first use, and prints jank types with what each points at,
|
|
196
|
+
frame-time percentiles, what the main thread ran in the worst frames, and
|
|
197
|
+
where the main and render threads spent their time.
|
|
198
|
+
|
|
199
|
+
## Configuration
|
|
200
|
+
|
|
201
|
+
A `.nolook.toml` in the project root (or any parent directory):
|
|
202
|
+
|
|
203
|
+
```toml
|
|
204
|
+
serial = "emulator-5554"
|
|
205
|
+
adb_port = 5037 # a non-default adb server
|
|
206
|
+
package = "com.example.notes"
|
|
207
|
+
flows = "tools/flows"
|
|
208
|
+
guard = ["/api/like", "/api/post"] # logcat regexes flagged after each action
|
|
209
|
+
danger = ["Like", "Send", "Delete"] # tap refuses these labels without --confirm
|
|
210
|
+
```
|
|
211
|
+
|
|
212
|
+
`NOLOOK_SERIAL` and `NOLOOK_ADB_PORT` override it.
|
|
213
|
+
|
|
214
|
+
For Jetpack Compose, set `Modifier.semantics { testTagsAsResourceId = true }`
|
|
215
|
+
near the root (debug builds are enough) so `testTag`s appear as `#ids`, and
|
|
216
|
+
give icon buttons a `contentDescription`.
|
|
217
|
+
|
|
218
|
+
## Limits
|
|
219
|
+
|
|
220
|
+
- What the accessibility tree does not show, nolook does not see: WebView
|
|
221
|
+
content (partly), canvases, games, video. Use `tap --xy` and screenshots
|
|
222
|
+
there.
|
|
223
|
+
- Flutter apps expose little to the accessibility tree.
|
|
224
|
+
- `motion` judges pixels, not intent: it says how long and in what shape
|
|
225
|
+
something moved, not whether it feels right. Compare against a labelled
|
|
226
|
+
run of the version you trust.
|
|
227
|
+
- Frame timings on emulators say little; measure on a real device.
|
|
228
|
+
- The pane needs a terminal with the kitty graphics protocol (Ghostty,
|
|
229
|
+
kitty, iTerm2 3.5+). Inside a multiplexer that answers the graphics query
|
|
230
|
+
but reports another name (herdr reports `libghostty`), set
|
|
231
|
+
`CLAUDE_CODE_FORCE_TERMINAL_IMAGES=1`. It is not supported on Windows.
|
|
232
|
+
|
|
233
|
+
## Compared with
|
|
234
|
+
|
|
235
|
+
- **adb `screencap` + `input tap`**: what agents fall back to; the
|
|
236
|
+
screenshot loop this replaces.
|
|
237
|
+
- **Maestro**: YAML flows for UI tests, mature and fast. nolook's flows are
|
|
238
|
+
Python (branches, loops), and its output is written for an agent's
|
|
239
|
+
context: refs, deltas, refusals with next steps.
|
|
240
|
+
- **Appium / uiautomator2 MCP servers**: one tool call per action, raw
|
|
241
|
+
results. nolook is a CLI with short, deterministic output, which keeps
|
|
242
|
+
long sessions small.
|
|
243
|
+
- **For the web**: [playwright-cli](https://github.com/microsoft/playwright-cli)
|
|
244
|
+
and [agent-browser](https://agent-browser.dev/) take the same approach for
|
|
245
|
+
browsers.
|
|
246
|
+
|
|
247
|
+
nolook is built on [uiautomator2](https://github.com/openatx/uiautomator2).
|
|
248
|
+
|
|
249
|
+
## License
|
|
250
|
+
|
|
251
|
+
MIT
|
nolook-0.3.0/README.md
ADDED
|
@@ -0,0 +1,225 @@
|
|
|
1
|
+
# nolook
|
|
2
|
+
|
|
3
|
+
Drive Android apps from a coding agent by reading the UI tree, not screenshots.
|
|
4
|
+
|
|
5
|
+
[中文说明](README.zh-CN.md)
|
|
6
|
+
|
|
7
|
+
An agent that debugs an app on a device usually works in a loop: take a
|
|
8
|
+
screenshot, read it, guess the coordinates of a button, tap, take another
|
|
9
|
+
screenshot. Every one of those images stays in the conversation and is sent
|
|
10
|
+
again with every later request, so the session gets slower and more
|
|
11
|
+
expensive with each step, and a guessed coordinate can tap the wrong thing.
|
|
12
|
+
|
|
13
|
+
nolook gives the agent the screen as a short text list instead. It taps by
|
|
14
|
+
what an element says or its id, reports what changed after each action, runs
|
|
15
|
+
several steps in one call, and takes small cropped screenshots only when the
|
|
16
|
+
question is how something looks.
|
|
17
|
+
|
|
18
|
+
```
|
|
19
|
+
$ nolook snap
|
|
20
|
+
com.example.notes · 1080x2400 · 9 elements
|
|
21
|
+
@e1 btn "Back" (63,140)
|
|
22
|
+
@e2 text "Notes" (300,140)
|
|
23
|
+
@e3 scroll #note_list (540,1250)
|
|
24
|
+
@e4 btn "Groceries · Milk, eggs, bread" (540,420)
|
|
25
|
+
@e5 btn "Trip · Tickets booked" (540,610)
|
|
26
|
+
@e6 btn "New note" #fab_new [covered] (960,2180)
|
|
27
|
+
@e7 btn "Search" (1000,140)
|
|
28
|
+
|
|
29
|
+
$ nolook tap Groceries
|
|
30
|
+
tap @e4 btn "Groceries · Milk, eggs, bread" (540,420) · settled 0.42s
|
|
31
|
+
screen changed (6 elements):
|
|
32
|
+
@e8 btn "Back" (63,140)
|
|
33
|
+
@e9 input "Groceries" #title (540,300)
|
|
34
|
+
@e10 input "Milk, eggs, bread" #body (540,700)
|
|
35
|
+
…
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
## What it does
|
|
39
|
+
|
|
40
|
+
- **Reads the screen as text.** `snap` lists interactive elements and text,
|
|
41
|
+
one line each, with a short ref (`@e4`). List rows read as one line.
|
|
42
|
+
Elements under a floating bar are marked `[covered]`.
|
|
43
|
+
- **Acts by query, safely.** `tap Groceries`, `tap #fab_new`, `tap @e4`.
|
|
44
|
+
A query must match exactly one element; a ref that left the screen is an
|
|
45
|
+
error, never a tap on whatever is there now. Taps on covered elements and
|
|
46
|
+
on labels that look like write actions (like, follow, send, delete …) are
|
|
47
|
+
refused unless asked for explicitly.
|
|
48
|
+
- **Reports what changed.** Every action waits for the screen to settle and
|
|
49
|
+
prints the elements that appeared and disappeared, so the agent rarely
|
|
50
|
+
needs to look again.
|
|
51
|
+
- **Runs flows.** Several steps with branches and loops, as a short Python
|
|
52
|
+
file, in one call. Flows worth keeping live in the project and run by name.
|
|
53
|
+
- **Takes small screenshots on request.** A crop to one element, as a small
|
|
54
|
+
JPEG, saved to a file; nolook prints the path and an estimate of its token
|
|
55
|
+
cost, and the agent reads it only if it has to.
|
|
56
|
+
- **Measures animations.** It records the screen around a tap and reports
|
|
57
|
+
duration, the 50% and 90% points, the shape of the progress curve,
|
|
58
|
+
overshoot and stalls, with one sheet of key frames.
|
|
59
|
+
- **Measures performance.** Frame timing over repeated flings (gfxinfo),
|
|
60
|
+
compile modes, Perfetto traces read into a one-page summary, ANR stacks.
|
|
61
|
+
- **Shows you the device** (Claude Code): a pane beside the chat with the
|
|
62
|
+
device's screen after each command. You see it; the model does not.
|
|
63
|
+
|
|
64
|
+
## Install
|
|
65
|
+
|
|
66
|
+
Requirements: Python 3.11+, `adb` on `PATH`, a device or emulator with USB
|
|
67
|
+
debugging. `ffmpeg` for `motion`. On first connect nolook pushes
|
|
68
|
+
uiautomator2's test server to the device; nothing is installed in your app.
|
|
69
|
+
|
|
70
|
+
```sh
|
|
71
|
+
uv tool install nolook # or: pipx install nolook
|
|
72
|
+
nolook doctor # checks adb, the device, ffmpeg, Perfetto
|
|
73
|
+
```
|
|
74
|
+
|
|
75
|
+
### Claude Code
|
|
76
|
+
|
|
77
|
+
```
|
|
78
|
+
/plugin marketplace add HuanCheng65/nolook
|
|
79
|
+
/plugin install nolook@nolook
|
|
80
|
+
```
|
|
81
|
+
|
|
82
|
+
The plugin adds the `nolook` skill (how to use the CLI well), a
|
|
83
|
+
`nolook-driver` agent for multi-step device work that reports back in text,
|
|
84
|
+
and the `/device` pane.
|
|
85
|
+
|
|
86
|
+
### Other agents
|
|
87
|
+
|
|
88
|
+
Any agent that can run shell commands can use the CLI. Give it
|
|
89
|
+
[`plugin/skills/nolook/SKILL.md`](plugin/skills/nolook/SKILL.md) as
|
|
90
|
+
instructions.
|
|
91
|
+
|
|
92
|
+
## Usage
|
|
93
|
+
|
|
94
|
+
```sh
|
|
95
|
+
nolook snap # what is on screen
|
|
96
|
+
nolook tap "Network & internet" # tap by text; prints what changed
|
|
97
|
+
nolook type "hello" --into "#search"
|
|
98
|
+
nolook scroll down --until "About phone"
|
|
99
|
+
nolook wait "Saved" --timeout 5
|
|
100
|
+
nolook back
|
|
101
|
+
nolook shot "#toolbar" # small crop; prints its path
|
|
102
|
+
nolook run flows/open_note.py # several steps in one call
|
|
103
|
+
```
|
|
104
|
+
|
|
105
|
+
### Queries
|
|
106
|
+
|
|
107
|
+
| Query | Matches |
|
|
108
|
+
|---|---|
|
|
109
|
+
| `@e12` | a ref from the last output |
|
|
110
|
+
| `#title` | resource id or Compose test tag |
|
|
111
|
+
| `Save` | text, content description, or one part of a row's label, exactly |
|
|
112
|
+
| `~Sav` | label contains |
|
|
113
|
+
| `/^Item \d+$/` | label matches a regex |
|
|
114
|
+
| `btn:Save` | any of the above, limited to a role |
|
|
115
|
+
|
|
116
|
+
A query that matches nothing lists the closest elements; one that matches
|
|
117
|
+
several lists them and asks for a ref, a role or `--nth`.
|
|
118
|
+
|
|
119
|
+
### Flows
|
|
120
|
+
|
|
121
|
+
```python
|
|
122
|
+
"""Open a note and check the editor."""
|
|
123
|
+
d.launch(fresh=True)
|
|
124
|
+
d.tap("Groceries")
|
|
125
|
+
if d.exists("Discard draft?"):
|
|
126
|
+
d.tap("Keep")
|
|
127
|
+
d.check(d.exists("#body"), "editor open")
|
|
128
|
+
d.shot("#toolbar")
|
|
129
|
+
```
|
|
130
|
+
|
|
131
|
+
`nolook run open_note` prints one line per step. On failure it prints the
|
|
132
|
+
step and line, the screen at that moment, and the path of a screenshot it
|
|
133
|
+
saved without reading.
|
|
134
|
+
|
|
135
|
+
### Animations
|
|
136
|
+
|
|
137
|
+
```sh
|
|
138
|
+
nolook motion --tap "#fab_new" --label open
|
|
139
|
+
nolook motion --key back --label close --slow 4
|
|
140
|
+
nolook motion-compare
|
|
141
|
+
```
|
|
142
|
+
|
|
143
|
+
```
|
|
144
|
+
motion [open] · #fab_new · 61 frames over 1.2s
|
|
145
|
+
runs 335ms to within 1% of its end · 50% at 54ms · 90% at 137ms · in [486, 331, 1080, 1168]
|
|
146
|
+
progress ▁▃▅▆▆▇██████████ decelerating (ease-out) · by position (p at 25/50/75% of time: 0.73/0.99/0.99)
|
|
147
|
+
monotonic: yes
|
|
148
|
+
14 frames in motion, median 16.4ms apart · no stalls
|
|
149
|
+
sheet (not read): ~/.cache/nolook/…/shots/012554-motion.jpg
|
|
150
|
+
```
|
|
151
|
+
|
|
152
|
+
It measures pixels inside the area that changed, comparing each frame with
|
|
153
|
+
the first and the last, so a spring's slow tail counts and a small menu is
|
|
154
|
+
not lost in a still screen. `--slow N` slows animations while recording,
|
|
155
|
+
for devices that record few frames.
|
|
156
|
+
|
|
157
|
+
### Performance
|
|
158
|
+
|
|
159
|
+
```sh
|
|
160
|
+
nolook perf frames --label before # gfxinfo reset → flings → summary
|
|
161
|
+
nolook perf frames --label after --trace traces/
|
|
162
|
+
nolook perf compare
|
|
163
|
+
nolook trace summary traces/nolook-….pftrace
|
|
164
|
+
nolook perf compile speed # ART compile mode, then relaunch
|
|
165
|
+
nolook perf anr
|
|
166
|
+
```
|
|
167
|
+
|
|
168
|
+
`trace summary` reads a trace with Perfetto's trace processor, which it
|
|
169
|
+
downloads on first use, and prints jank types with what each points at,
|
|
170
|
+
frame-time percentiles, what the main thread ran in the worst frames, and
|
|
171
|
+
where the main and render threads spent their time.
|
|
172
|
+
|
|
173
|
+
## Configuration
|
|
174
|
+
|
|
175
|
+
A `.nolook.toml` in the project root (or any parent directory):
|
|
176
|
+
|
|
177
|
+
```toml
|
|
178
|
+
serial = "emulator-5554"
|
|
179
|
+
adb_port = 5037 # a non-default adb server
|
|
180
|
+
package = "com.example.notes"
|
|
181
|
+
flows = "tools/flows"
|
|
182
|
+
guard = ["/api/like", "/api/post"] # logcat regexes flagged after each action
|
|
183
|
+
danger = ["Like", "Send", "Delete"] # tap refuses these labels without --confirm
|
|
184
|
+
```
|
|
185
|
+
|
|
186
|
+
`NOLOOK_SERIAL` and `NOLOOK_ADB_PORT` override it.
|
|
187
|
+
|
|
188
|
+
For Jetpack Compose, set `Modifier.semantics { testTagsAsResourceId = true }`
|
|
189
|
+
near the root (debug builds are enough) so `testTag`s appear as `#ids`, and
|
|
190
|
+
give icon buttons a `contentDescription`.
|
|
191
|
+
|
|
192
|
+
## Limits
|
|
193
|
+
|
|
194
|
+
- What the accessibility tree does not show, nolook does not see: WebView
|
|
195
|
+
content (partly), canvases, games, video. Use `tap --xy` and screenshots
|
|
196
|
+
there.
|
|
197
|
+
- Flutter apps expose little to the accessibility tree.
|
|
198
|
+
- `motion` judges pixels, not intent: it says how long and in what shape
|
|
199
|
+
something moved, not whether it feels right. Compare against a labelled
|
|
200
|
+
run of the version you trust.
|
|
201
|
+
- Frame timings on emulators say little; measure on a real device.
|
|
202
|
+
- The pane needs a terminal with the kitty graphics protocol (Ghostty,
|
|
203
|
+
kitty, iTerm2 3.5+). Inside a multiplexer that answers the graphics query
|
|
204
|
+
but reports another name (herdr reports `libghostty`), set
|
|
205
|
+
`CLAUDE_CODE_FORCE_TERMINAL_IMAGES=1`. It is not supported on Windows.
|
|
206
|
+
|
|
207
|
+
## Compared with
|
|
208
|
+
|
|
209
|
+
- **adb `screencap` + `input tap`**: what agents fall back to; the
|
|
210
|
+
screenshot loop this replaces.
|
|
211
|
+
- **Maestro**: YAML flows for UI tests, mature and fast. nolook's flows are
|
|
212
|
+
Python (branches, loops), and its output is written for an agent's
|
|
213
|
+
context: refs, deltas, refusals with next steps.
|
|
214
|
+
- **Appium / uiautomator2 MCP servers**: one tool call per action, raw
|
|
215
|
+
results. nolook is a CLI with short, deterministic output, which keeps
|
|
216
|
+
long sessions small.
|
|
217
|
+
- **For the web**: [playwright-cli](https://github.com/microsoft/playwright-cli)
|
|
218
|
+
and [agent-browser](https://agent-browser.dev/) take the same approach for
|
|
219
|
+
browsers.
|
|
220
|
+
|
|
221
|
+
nolook is built on [uiautomator2](https://github.com/openatx/uiautomator2).
|
|
222
|
+
|
|
223
|
+
## License
|
|
224
|
+
|
|
225
|
+
MIT
|