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.
@@ -0,0 +1,7 @@
1
+ {
2
+ "name": "nolook",
3
+ "owner": { "name": "HuanCheng65" },
4
+ "plugins": [
5
+ { "name": "nolook", "source": "./plugin", "description": "Drive Android apps through their UI tree instead of screenshots." }
6
+ ]
7
+ }
@@ -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
@@ -0,0 +1,6 @@
1
+ dist/
2
+ *.egg-info/
3
+ evals/
4
+ evals/results/
5
+ __pycache__/
6
+ .venv/
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