device-devtools-mcp 0.1.0
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.
- package/AGENTS.md +327 -0
- package/LICENSE +21 -0
- package/README.md +491 -0
- package/VERSION +1 -0
- package/bin/device-devtools-mcp.js +106 -0
- package/bin/devicetools +315 -0
- package/config.example.json +63 -0
- package/integrations/agent-pointer.sh +48 -0
- package/integrations/claude/SKILL.md +8 -0
- package/integrations/cursor/devicetools.mdc +8 -0
- package/integrations/gemini/GEMINI.md +5 -0
- package/integrations/mcp/README.md +184 -0
- package/integrations/mcp/mcp.json +10 -0
- package/integrations/mcp/reference.sh +119 -0
- package/integrations/mcp/selftest.sh +158 -0
- package/integrations/mcp/server.sh +343 -0
- package/package.json +50 -0
- package/scripts/android/app.sh +241 -0
- package/scripts/android/back.sh +73 -0
- package/scripts/android/controls.sh +56 -0
- package/scripts/android/devices.sh +77 -0
- package/scripts/android/doctor.sh +253 -0
- package/scripts/android/lib.sh +699 -0
- package/scripts/android/logs.sh +289 -0
- package/scripts/android/permission.sh +119 -0
- package/scripts/android/settings.sh +63 -0
- package/scripts/android/setup.sh +97 -0
- package/scripts/android/tree.awk +166 -0
- package/scripts/android/tree.sh +127 -0
- package/scripts/android/type.sh +191 -0
- package/scripts/common/find.sh +95 -0
- package/scripts/common/key.sh +65 -0
- package/scripts/common/lib.sh +14 -0
- package/scripts/common/measure.sh +170 -0
- package/scripts/common/open.sh +131 -0
- package/scripts/common/screenshot.sh +102 -0
- package/scripts/common/scroll.sh +177 -0
- package/scripts/common/snapshot.sh +69 -0
- package/scripts/common/swipe.sh +171 -0
- package/scripts/common/tap.sh +226 -0
- package/scripts/common/wait.sh +212 -0
- package/scripts/common/waypoint.sh +141 -0
- package/scripts/dispatch.sh +21 -0
- package/scripts/flow.sh +266 -0
- package/scripts/init.sh +101 -0
- package/scripts/ios/app.sh +404 -0
- package/scripts/ios/back.sh +95 -0
- package/scripts/ios/controls.sh +68 -0
- package/scripts/ios/devices.sh +80 -0
- package/scripts/ios/doctor.sh +386 -0
- package/scripts/ios/lib.sh +864 -0
- package/scripts/ios/logs.sh +272 -0
- package/scripts/ios/permission.sh +108 -0
- package/scripts/ios/settings.sh +76 -0
- package/scripts/ios/setup.sh +175 -0
- package/scripts/ios/tree.sh +128 -0
- package/scripts/ios/type.sh +178 -0
- package/scripts/lib.sh +1032 -0
- package/scripts/links.tsv +44 -0
- package/scripts/relink.sh +121 -0
- package/scripts/run.sh +415 -0
- package/scripts/selftest.sh +1709 -0
- package/scripts/snapshot.awk +362 -0
- package/scripts/verify-npm-package.js +133 -0
- package/tests/fixtures/ios-contacts-list.expected +52 -0
- package/tests/fixtures/ios-contacts-list.rows +140 -0
package/AGENTS.md
ADDED
|
@@ -0,0 +1,327 @@
|
|
|
1
|
+
# DeviceTools
|
|
2
|
+
|
|
3
|
+
You are operating a real application on a real phone, plugged into this Mac.
|
|
4
|
+
There is no person watching the screen for you.
|
|
5
|
+
|
|
6
|
+
This file is the contract. It is the only document you need; the README is for
|
|
7
|
+
whoever set the machine up.
|
|
8
|
+
|
|
9
|
+
## The one rule
|
|
10
|
+
|
|
11
|
+
**Read before you act, and act on what you read.**
|
|
12
|
+
|
|
13
|
+
`snapshot` is cheap and exact. Guessing where a control is — from a screenshot,
|
|
14
|
+
or from memory of the last screen — is the one failure that matters here. A
|
|
15
|
+
wrong tap is not a failed assertion. It is a thing that happened, on somebody's
|
|
16
|
+
phone, and every step you take afterwards reasons about a screen you are no
|
|
17
|
+
longer on.
|
|
18
|
+
|
|
19
|
+
When a verb refuses, it is refusing because it could not act unambiguously. The
|
|
20
|
+
answer is another `snapshot`, not another attempt.
|
|
21
|
+
|
|
22
|
+
## Start here, every session
|
|
23
|
+
|
|
24
|
+
```sh
|
|
25
|
+
./scripts/doctor.sh
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
A fixed list of checks, always the same ones in the same order,
|
|
29
|
+
`OK|WARN|FAIL <name> — <detail>`. Exit 0 means you can work. Run it again the
|
|
30
|
+
moment any verb exits **3**: that code means the connection to the device died,
|
|
31
|
+
and nothing you do afterwards is real until it is back.
|
|
32
|
+
|
|
33
|
+
If it fails with the driver missing, run `./scripts/setup.sh` once and doctor
|
|
34
|
+
again. Do not try to install the driver by hand.
|
|
35
|
+
|
|
36
|
+
Some failures are not yours to fix. Signing in to Xcode, registering the device
|
|
37
|
+
with Apple, trusting a certificate on the phone, and turning on Settings →
|
|
38
|
+
Developer → Enable UI Automation are all things a person has to do while holding
|
|
39
|
+
the device. When doctor says one of those, stop and ask — retrying will not
|
|
40
|
+
change the answer, and neither will installing anything.
|
|
41
|
+
|
|
42
|
+
## The loop
|
|
43
|
+
|
|
44
|
+
```
|
|
45
|
+
you edit code
|
|
46
|
+
you run xcodebuild / gradlew ← your own shell. DeviceTools knows nothing
|
|
47
|
+
about Xcode, Gradle or signing
|
|
48
|
+
app relaunch ← the app is back on its first screen
|
|
49
|
+
waypoint action=goto name=… ← walk back to where you were
|
|
50
|
+
snapshot ← what is on it, with the geometry
|
|
51
|
+
tap 7 snapshot=true ← act, and get the new screen in the same call
|
|
52
|
+
screenshot ← only when the tree cannot answer
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
**Two things collapse that loop, and you should reach for both.**
|
|
56
|
+
|
|
57
|
+
`snapshot=true` on any action prints the screen it produced and renumbers the
|
|
58
|
+
uids, so the read you were about to do is already done. It costs nothing extra:
|
|
59
|
+
the action reads the tree anyway to tell you whether the screen moved.
|
|
60
|
+
|
|
61
|
+
`run` takes several steps in one call. What a person does as one continuous
|
|
62
|
+
act — type the password, hide the keyboard, press submit — is one call, not
|
|
63
|
+
four. The number of calls is what this loop costs, not the size of any one
|
|
64
|
+
answer:
|
|
65
|
+
|
|
66
|
+
```
|
|
67
|
+
run steps='[{"tap":"label:Đăng nhập"},{"type":"{{ password }}"},
|
|
68
|
+
{"key":"hide"},{"tap":"label:Xác nhận"},{"wait":"screen:Home"}]'
|
|
69
|
+
vars='{"password":"…"}'
|
|
70
|
+
```
|
|
71
|
+
|
|
72
|
+
It stops at the first failing step and shows you the screen it stopped on. uids
|
|
73
|
+
are refused inside a batch: a uid names a position on a screen that is already
|
|
74
|
+
two steps old by step three, so use selectors. `snapshot: true` inside a step is
|
|
75
|
+
a flag on that step, not a step of its own. Start a batch with
|
|
76
|
+
`{"expect": "screen:X"}` when you know where it should be beginning — it is a
|
|
77
|
+
wait with a three-second deadline, and it fails at the door instead of tapping
|
|
78
|
+
into the wrong screen.
|
|
79
|
+
|
|
80
|
+
`flow save <name>` keeps a batch under a name, in the project. If you have just
|
|
81
|
+
worked out how to log in, save it — the next session says `flow run login`.
|
|
82
|
+
|
|
83
|
+
**Name the project.** You have no working directory: whatever the host set when
|
|
84
|
+
it spawned this server is fixed for the whole session, and it is the DeviceTools
|
|
85
|
+
checkout, not the repository the person is working in. So `init` takes the
|
|
86
|
+
repository path once, and every verb after that takes `project=<that path>`.
|
|
87
|
+
Without it a saved flow lands in the checkout, where every app on the machine
|
|
88
|
+
shares it and nobody reviews it.
|
|
89
|
+
|
|
90
|
+
DeviceTools owns everything from **"the app is installed"** onward, and nothing
|
|
91
|
+
before it. Building and installing is your shell's job, and you are better at it
|
|
92
|
+
than a wrapper here would be.
|
|
93
|
+
|
|
94
|
+
## Verbs
|
|
95
|
+
|
|
96
|
+
`./scripts/<verb>.sh`, or `devicetools <verb>` where the command is on PATH — the
|
|
97
|
+
same program, same arguments, same output, same exit codes. Use whichever
|
|
98
|
+
spelling the pointer in this repository gave you and do not mix them.
|
|
99
|
+
|
|
100
|
+
| Verb | Use |
|
|
101
|
+
| --- | --- |
|
|
102
|
+
| `doctor` | can this machine drive the device right now |
|
|
103
|
+
| `devices` | what is attached, and which one the config selects |
|
|
104
|
+
| `snapshot [--all] [--grep S]` | **read the screen.** The default way to see |
|
|
105
|
+
| `screenshot [name] [--scale N] [--full]` | an image, when the tree cannot answer |
|
|
106
|
+
| `measure <uid>` | one element in full: parent, neighbours, gaps, overlaps |
|
|
107
|
+
| `tap <uid\|selector> [--index N] [--count N]` | tap exactly one thing, or refuse. `--count` repeats it |
|
|
108
|
+
| `type <text>` | type into whatever has focus, and verify it landed |
|
|
109
|
+
| `swipe <direction>` | a gesture between two points |
|
|
110
|
+
| `scroll <direction> [--until S]` | scroll, optionally until something appears |
|
|
111
|
+
| `key hide` | put the keyboard away |
|
|
112
|
+
| `back` | one screen back; fails if the screen does not move |
|
|
113
|
+
| `wait <selector> [--gone]` | until something appears or disappears |
|
|
114
|
+
| `open <url> [--expect-app <id>]` | a deep link, and which app answered |
|
|
115
|
+
| `app <launch\|relaunch\|kill\|reset\|state>` | the app's lifecycle, by bundle id — no icon to find |
|
|
116
|
+
| `app install <path> [--launch]` | put a build on the device the config selects |
|
|
117
|
+
| `app uninstall [id]` | remove it |
|
|
118
|
+
| `logs [start\|stop\|status] [--network] [--grep S]` | what the app printed |
|
|
119
|
+
| `waypoint <mark\|goto\|list\|forget> <name>` | get back after a reinstall |
|
|
120
|
+
| `settings <key> <value>` | dark mode, text size, orientation |
|
|
121
|
+
| `permission <allow\|deny\|reset> <name>` | a system permission |
|
|
122
|
+
| `find <selector>` | resolve a selector without acting on it |
|
|
123
|
+
| `run --steps '[…]'` | **several steps in one call.** The biggest thing you can do about cost |
|
|
124
|
+
| `flow <save\|run\|list\|show\|delete> <name>` | a batch, kept under a name, in the project |
|
|
125
|
+
|
|
126
|
+
Every acting verb takes `--snapshot` (`snapshot=true` over MCP): it prints the
|
|
127
|
+
screen the action produced and renumbers the uids, in the same call.
|
|
128
|
+
|
|
129
|
+
`tree` and `controls` still exist and are what `snapshot` was built from. Prefer
|
|
130
|
+
`snapshot`: it carries the geometry and assigns the numbers everything else
|
|
131
|
+
takes.
|
|
132
|
+
|
|
133
|
+
## Reading a screen
|
|
134
|
+
|
|
135
|
+
```
|
|
136
|
+
$ snapshot
|
|
137
|
+
SCREEN "Liên hệ" #9b41 428x926 portrait
|
|
138
|
+
|
|
139
|
+
[1] Application "DemoApp" (0,0) 428x926
|
|
140
|
+
[2] NavigationBar #Liên hệ (0,47) 428x101
|
|
141
|
+
[3] Button (20,51) 36x36 enabled
|
|
142
|
+
[4] StaticText "Liên hệ" (19,108) 116x31
|
|
143
|
+
…
|
|
144
|
+
[10] TextField "Tìm kiếm" (72,368) 316x25 enabled
|
|
145
|
+
|
|
146
|
+
⚠ hit area below the 44pt minimum — [3] [10] [19] [23] [27] [31] [35] [39]
|
|
147
|
+
⚠ no identifier: a selector has to use the label, which changes with the copy — [3] [10] [19] [23] [27] [31] [35] [39]
|
|
148
|
+
⚠ partly off screen by 8pt — [19] [23] [27] [31] [35] [39]
|
|
149
|
+
```
|
|
150
|
+
|
|
151
|
+
**The first line identifies the screen.** `#9b41` is a short hash over the set
|
|
152
|
+
of (type, label) pairs on it. It is not a URL and not a class name — XCUITest
|
|
153
|
+
and UiAutomator publish neither — but it is stable while the screen is at rest,
|
|
154
|
+
which is what change detection needs.
|
|
155
|
+
|
|
156
|
+
**The number in brackets is a uid, and it dies at the next `snapshot`.** Acting
|
|
157
|
+
on one re-reads the screen and requires that element to still be exactly what
|
|
158
|
+
was numbered: same type, same identifier, same label, same value, same rectangle
|
|
159
|
+
give or take two points. If it is not, the action is refused and says what
|
|
160
|
+
changed. Nothing is scored, and there is no runner-up.
|
|
161
|
+
|
|
162
|
+
**Warnings are grouped by fault, not printed under each element.** A list of six
|
|
163
|
+
identical rows would otherwise report the same thing six times, and an agent
|
|
164
|
+
that reads fifty warnings per screen stops reading warnings. To find out which
|
|
165
|
+
sibling `[10]` overlaps, ask `measure 10` — that is what it is for.
|
|
166
|
+
|
|
167
|
+
The whole list of warnings, and nothing beyond it:
|
|
168
|
+
|
|
169
|
+
| Warning | Computed from |
|
|
170
|
+
| --- | --- |
|
|
171
|
+
| `overlaps a sibling by Xpt` | two sibling rectangles intersect |
|
|
172
|
+
| `outside its parent by Xpt` | a child's rect is not contained by its parent's |
|
|
173
|
+
| `off screen` / `partly off screen by Xpt` | the rect against the window size |
|
|
174
|
+
| `zero size, but it carries text` | width or height is 0 on an element with text |
|
|
175
|
+
| `hit area below the Npt minimum` | 44pt on iOS, 48dp on Android |
|
|
176
|
+
| `the OS truncated this text with an ellipsis` | the string ends in `…` or `...` |
|
|
177
|
+
|
|
178
|
+
Nothing at or below **two points** is reported at all. Both drivers hand back
|
|
179
|
+
floating-point rectangles and this pipeline floors them, so any pair of edges
|
|
180
|
+
carries up to two points of error; without that floor, every pair of stacked
|
|
181
|
+
text lines "overlaps by 1pt". Measured on a real screen: sixty-three warnings
|
|
182
|
+
became eight, and the eight were true.
|
|
183
|
+
|
|
184
|
+
**There is no "this text is clipped, it needs 244pt".** That needs font metrics,
|
|
185
|
+
which neither driver exposes. Guessing a string's width from its length would be
|
|
186
|
+
wrong often enough that you would learn to skip the warnings, and a signal you
|
|
187
|
+
skip is worse than one that was never printed. When the OS truncates a string it
|
|
188
|
+
leaves an ellipsis, and that is what gets reported.
|
|
189
|
+
|
|
190
|
+
## Looking at a screen
|
|
191
|
+
|
|
192
|
+
```sh
|
|
193
|
+
screenshot # longest edge 768px, about 360 tokens
|
|
194
|
+
screenshot --full # device resolution, about 1500
|
|
195
|
+
```
|
|
196
|
+
|
|
197
|
+
Use it for what the tree cannot say: a wrong icon, a blurry asset, a colour, a
|
|
198
|
+
contrast, a gradient. Neither driver reports any of those, so looking is the
|
|
199
|
+
only way to know.
|
|
200
|
+
|
|
201
|
+
For anything about position, size or text, `snapshot` is cheaper and exact.
|
|
202
|
+
**The normal loop contains no images at all.**
|
|
203
|
+
|
|
204
|
+
Two things to know before you read one. Apps that mark secure inputs render them
|
|
205
|
+
as blank regions in any captured image — the pixels are genuinely absent, and a
|
|
206
|
+
blank field is not evidence it failed to draw; the verb warns when a text input
|
|
207
|
+
is on screen. And the image leaves this machine: it goes to your model provider,
|
|
208
|
+
with no masking. That is a deliberate trade for an internal tool on a
|
|
209
|
+
developer's own device, and it is written here rather than left to be
|
|
210
|
+
discovered.
|
|
211
|
+
|
|
212
|
+
## Selectors
|
|
213
|
+
|
|
214
|
+
| Spelling | Means |
|
|
215
|
+
| --- | --- |
|
|
216
|
+
| `7` | uid — the seventh thing the last snapshot showed |
|
|
217
|
+
| `id:<s>` | iOS accessibilityIdentifier, Android resource-id |
|
|
218
|
+
| `label:<s>` | iOS accessibility label, Android content-desc |
|
|
219
|
+
| `text:<s>` | iOS value, Android text |
|
|
220
|
+
| `xy:<x>,<y>` | a raw point, checked against nothing |
|
|
221
|
+
| anything else | a plain string, matched against id, label and value in turn |
|
|
222
|
+
|
|
223
|
+
The first kind that matches wins — not the union of all three. An identifier
|
|
224
|
+
that matches exactly is a better answer than two labels that also do, and
|
|
225
|
+
merging them would manufacture an ambiguity the screen does not have.
|
|
226
|
+
|
|
227
|
+
`id:`, `label:` and `text:` must resolve to **exactly one** visible element
|
|
228
|
+
unless `--index` says which. Zero matches, or several without an index, and the
|
|
229
|
+
verb exits non-zero naming what it saw, without touching anything.
|
|
230
|
+
|
|
231
|
+
`kind:<Type>` matches the element's class — the word `snapshot` prints first on
|
|
232
|
+
the line. It is what to use for a control that has no identifier and no label,
|
|
233
|
+
usually with `--index`, instead of falling back to `xy:`. Coordinates break: on
|
|
234
|
+
this app, hiding the keyboard moved a sheet from y=507 to y=774 and every
|
|
235
|
+
recorded tap in it missed.
|
|
236
|
+
|
|
237
|
+
**Every selector matches whole.** `text:OTP` does not find a label reading
|
|
238
|
+
"Vui lòng nhập mã OTP…"; `snapshot --grep OTP` does. Reading may guess, acting
|
|
239
|
+
may not.
|
|
240
|
+
|
|
241
|
+
## What the app said
|
|
242
|
+
|
|
243
|
+
```sh
|
|
244
|
+
logs start # once per session
|
|
245
|
+
… act …
|
|
246
|
+
logs --grep error
|
|
247
|
+
logs --network
|
|
248
|
+
```
|
|
249
|
+
|
|
250
|
+
Until you start a collector, actions carry no `LOG` section and `logs` has
|
|
251
|
+
nothing to read. That is deliberate: an action must not spawn a background
|
|
252
|
+
process as a side effect of touching a button, and the device under test
|
|
253
|
+
produced twenty-one thousand log lines in thirty seconds.
|
|
254
|
+
|
|
255
|
+
`--network` is the Network panel, **filtered from the app's own log output and
|
|
256
|
+
not from a proxy**. A proxy needs a CA certificate on the device, and an app
|
|
257
|
+
that pins its certificates defeats one. So this shows what the app chose to
|
|
258
|
+
print: no bodies, no headers it did not log, and nothing at all from an app that
|
|
259
|
+
prints none of it. On the app this was built against it returns nothing — an
|
|
260
|
+
empty result here means the app logs no traffic, not that the network was
|
|
261
|
+
quiet.
|
|
262
|
+
|
|
263
|
+
## After a rebuild
|
|
264
|
+
|
|
265
|
+
Installing a new build puts the app back on its first screen. Every action that
|
|
266
|
+
lands is appended to `.state/session.jsonl`, so:
|
|
267
|
+
|
|
268
|
+
```sh
|
|
269
|
+
waypoint mark otp # name where you are
|
|
270
|
+
… rebuild, reinstall …
|
|
271
|
+
waypoint goto otp # relaunch and walk back
|
|
272
|
+
```
|
|
273
|
+
|
|
274
|
+
`goto` replays the recorded actions, preferring the recorded identifier over the
|
|
275
|
+
recorded label — a label is application copy and gets rewritten, an identifier
|
|
276
|
+
is chosen by an engineer and rarely does. When a step no longer resolves it
|
|
277
|
+
**stops** and says which step and what it was looking for. It does not search,
|
|
278
|
+
score or improvise. Read the screen and walk the rest yourself.
|
|
279
|
+
|
|
280
|
+
## What every action tells you
|
|
281
|
+
|
|
282
|
+
```
|
|
283
|
+
TAP 7 — resolved by uid 7, verified still at 214,69 → tapped 214,69
|
|
284
|
+
SCREEN changed — "Trang chủ" #60a8 → "Xác thực" #abf8
|
|
285
|
+
LOG 2 lines
|
|
286
|
+
[error] AuthService: token refresh failed
|
|
287
|
+
```
|
|
288
|
+
|
|
289
|
+
Three facts: what was done, whether the screen moved, and what the app said
|
|
290
|
+
while it moved. Not the new tree by default — attaching one to every action
|
|
291
|
+
would triple the cost of a loop that mostly does not need it, and "changed" or
|
|
292
|
+
"unchanged" already answers the usual question.
|
|
293
|
+
|
|
294
|
+
Ask for it with `--snapshot` when the next thing you would do is read the
|
|
295
|
+
screen, which is most of the time while you are finding your way. It is free:
|
|
296
|
+
the action already fetched that tree to decide whether the screen moved.
|
|
297
|
+
|
|
298
|
+
`SCREEN same — #x, contents changed` is a fourth: the same controls holding
|
|
299
|
+
different text. A code being entered, a counter moving, a field filling up. Not
|
|
300
|
+
a new screen, and not nothing.
|
|
301
|
+
|
|
302
|
+
`SCREEN unknown` means the tree could not be read on one side or the other. It
|
|
303
|
+
is not the same claim as "unchanged".
|
|
304
|
+
|
|
305
|
+
## Exit codes
|
|
306
|
+
|
|
307
|
+
| Code | Meaning |
|
|
308
|
+
| --- | --- |
|
|
309
|
+
| 0 | done |
|
|
310
|
+
| 1 | usage error — you called it wrong |
|
|
311
|
+
| 2 | environment or configuration problem |
|
|
312
|
+
| 3 | the device is unreachable. **Run `doctor`.** Nothing after this is real |
|
|
313
|
+
| 4 | the command ran, and the screen did not satisfy it |
|
|
314
|
+
|
|
315
|
+
There is no 5. It used to mean "tests failed" and retired with the test runner.
|
|
316
|
+
|
|
317
|
+
Exit 4 is an ordinary outcome, not a fault: zero matches, several matches, a
|
|
318
|
+
condition that never held, a uid that has gone stale. Read the message. It says
|
|
319
|
+
what was on the screen.
|
|
320
|
+
|
|
321
|
+
## Where things are
|
|
322
|
+
|
|
323
|
+
`DT_HOME` is the checkout: scripts, adapters, this file. `DT_ROOT` is the
|
|
324
|
+
project you are working in, found by walking up for a `.devicetools` marker;
|
|
325
|
+
`config.json`, `.state/` and `.runs/` live there. Every relative path in a
|
|
326
|
+
config resolves against `DT_ROOT`, and nothing you author ever resolves into
|
|
327
|
+
`DT_HOME`.
|
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Hưng Phan
|
|
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.
|