use-computer 0.0.1__py3-none-any.whl

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.
mmini/screenshot.py ADDED
@@ -0,0 +1,63 @@
1
+ from __future__ import annotations
2
+
3
+ import httpx
4
+
5
+
6
+ class Screenshot:
7
+ def __init__(self, http: httpx.Client, prefix: str):
8
+ self._http = http
9
+ self._prefix = prefix
10
+
11
+ def take_full_screen(self, show_cursor: bool = False) -> bytes:
12
+ resp = self._http.get(f"{self._prefix}/screenshot", params={"show_cursor": show_cursor})
13
+ resp.raise_for_status()
14
+ return resp.content
15
+
16
+ def take_region(self, x: int, y: int, width: int, height: int) -> bytes:
17
+ resp = self._http.get(
18
+ f"{self._prefix}/screenshot/region",
19
+ params={"x": x, "y": y, "width": width, "height": height},
20
+ )
21
+ resp.raise_for_status()
22
+ return resp.content
23
+
24
+ def take_compressed(
25
+ self, format: str = "jpeg", quality: int = 80, scale: float | None = None
26
+ ) -> bytes:
27
+ params: dict = {"format": format, "quality": quality}
28
+ if scale is not None:
29
+ params["scale"] = scale
30
+ resp = self._http.get(f"{self._prefix}/screenshot/compressed", params=params)
31
+ resp.raise_for_status()
32
+ return resp.content
33
+
34
+
35
+ class AsyncScreenshot:
36
+ def __init__(self, http: httpx.AsyncClient, prefix: str):
37
+ self._http = http
38
+ self._prefix = prefix
39
+
40
+ async def take_full_screen(self, show_cursor: bool = False) -> bytes:
41
+ resp = await self._http.get(
42
+ f"{self._prefix}/screenshot", params={"show_cursor": show_cursor}
43
+ )
44
+ resp.raise_for_status()
45
+ return resp.content
46
+
47
+ async def take_region(self, x: int, y: int, width: int, height: int) -> bytes:
48
+ resp = await self._http.get(
49
+ f"{self._prefix}/screenshot/region",
50
+ params={"x": x, "y": y, "width": width, "height": height},
51
+ )
52
+ resp.raise_for_status()
53
+ return resp.content
54
+
55
+ async def take_compressed(
56
+ self, format: str = "jpeg", quality: int = 80, scale: float | None = None
57
+ ) -> bytes:
58
+ params: dict = {"format": format, "quality": quality}
59
+ if scale is not None:
60
+ params["scale"] = scale
61
+ resp = await self._http.get(f"{self._prefix}/screenshot/compressed", params=params)
62
+ resp.raise_for_status()
63
+ return resp.content
@@ -0,0 +1,307 @@
1
+ """Retrieve and export collected tasks from the gateway."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import builtins
6
+ import json
7
+ import logging
8
+ import shutil
9
+ from dataclasses import dataclass, field
10
+ from pathlib import Path
11
+
12
+ import httpx
13
+
14
+ from mmini.ax_transpile import patch_curl_timeouts, transpile
15
+
16
+ logger = logging.getLogger(__name__)
17
+
18
+ _TEMPLATES = Path(__file__).parent / "templates"
19
+
20
+
21
+ def _tpl(name: str) -> str:
22
+ return (_TEMPLATES / name).read_text(encoding="utf-8")
23
+
24
+
25
+ def _render(text: str, **kwargs: str) -> str:
26
+ out = text
27
+ for k, v in kwargs.items():
28
+ out = out.replace("{{" + k + "}}", v)
29
+ return out
30
+
31
+
32
+ @dataclass
33
+ class TaskSummary:
34
+ """Lightweight task info from the list endpoint."""
35
+
36
+ id: str
37
+ task_name: str
38
+ instruction: str
39
+ category: str
40
+ platform: str
41
+ step_count: int
42
+ has_grader: bool
43
+ created_at: str
44
+ completed_at: str | None = None
45
+
46
+ @property
47
+ def runnable(self) -> bool:
48
+ """A task is runnable only if it has a grader."""
49
+ return self.has_grader
50
+
51
+
52
+ @dataclass
53
+ class Task:
54
+ """Full task detail from the gateway."""
55
+
56
+ id: str
57
+ sandbox_id: str
58
+ platform: str
59
+ phase: str
60
+ task_name: str
61
+ instruction: str
62
+ category: str
63
+ setup_commands: list
64
+ steps: list
65
+ app_state: dict
66
+ accessibility_tree: dict | None = None
67
+ grader: str = ""
68
+ setup_actions: list = field(default_factory=list)
69
+
70
+ @property
71
+ def runnable(self) -> bool:
72
+ return self.grader != ""
73
+
74
+
75
+ class TasksClient:
76
+ """Client for the gateway's task collection endpoints."""
77
+
78
+ def __init__(self, http: httpx.Client):
79
+ self._http = http
80
+
81
+ def list(self, limit: int = 50, offset: int = 0) -> builtins.list[TaskSummary]:
82
+ resp = self._http.get("/admin/tasks", params={"limit": limit, "offset": offset})
83
+ resp.raise_for_status()
84
+ return [
85
+ TaskSummary(
86
+ id=t["id"],
87
+ task_name=t.get("task_name", ""),
88
+ instruction=t.get("instruction", ""),
89
+ category=t.get("category", ""),
90
+ platform=t.get("platform", "macos"),
91
+ step_count=t.get("step_count", 0),
92
+ has_grader=t.get("has_grader", False),
93
+ created_at=t.get("created_at", ""),
94
+ completed_at=t.get("completed_at"),
95
+ )
96
+ for t in resp.json()
97
+ ]
98
+
99
+ def get(self, task_id: str) -> Task:
100
+ resp = self._http.get(f"/admin/tasks/{task_id}")
101
+ resp.raise_for_status()
102
+ d = resp.json()
103
+ meta = d.get("task_meta", {})
104
+ return Task(
105
+ id=d["id"],
106
+ sandbox_id=d.get("sandbox_id", ""),
107
+ platform=d.get("platform", meta.get("platform", "macos")),
108
+ phase=d.get("phase", ""),
109
+ task_name=meta.get("name", ""),
110
+ instruction=meta.get("instruction", ""),
111
+ category=meta.get("category", ""),
112
+ setup_commands=d.get("setup_commands", []),
113
+ steps=d.get("steps", []),
114
+ app_state=d.get("app_state", {}),
115
+ accessibility_tree=d.get("accessibility_tree"),
116
+ grader=d.get("grader", ""),
117
+ setup_actions=d.get("setup_actions") or [],
118
+ )
119
+
120
+ def export_harbor(
121
+ self,
122
+ task_id: str,
123
+ output_dir: str | Path,
124
+ *,
125
+ overwrite: bool = False,
126
+ ) -> Path:
127
+ task = self.get(task_id)
128
+
129
+ # Fetch each persisted upload via /admin/tasks/{id}/files/{name}.
130
+ # Returns None on 404 — task_to_harbor logs + skips.
131
+ def fetch_file(local_name: str) -> bytes | None:
132
+ r = self._http.get(f"/admin/tasks/{task_id}/files/{local_name}")
133
+ if r.status_code != 200:
134
+ return None
135
+ return r.content
136
+
137
+ return task_to_harbor(task, Path(output_dir), overwrite=overwrite, fetch_file=fetch_file)
138
+
139
+
140
+ def _build_test_sh(task: Task) -> str:
141
+ """iOS = JSON checker DSL POSTed to /grade. macOS = bash with perl-alarm wrap."""
142
+ if task.platform == "ios":
143
+ return _build_test_sh_ios(task)
144
+ return _build_test_sh_macos(task)
145
+
146
+
147
+ def _build_test_sh_ios(task: Task) -> str:
148
+ grader = (task.grader or "").strip()
149
+ if not grader.startswith("["):
150
+ return _tpl("test_ios_nograder.sh")
151
+ specs = grader.replace("'", "'\\''")
152
+ return _render(_tpl("test_ios.sh"), SPECS=specs)
153
+
154
+
155
+ def _build_test_sh_macos(task: Task) -> str:
156
+ grader = (task.grader or "").strip()
157
+ if not grader:
158
+ return _tpl("test_macos_nograder.sh")
159
+ check_tpl = _tpl("test_macos_check.sh")
160
+ checks = _render(check_tpl, N="1", CMD=grader.replace("'", "'\\''"))
161
+ out = _render(_tpl("test_macos.sh"), CHECKS=checks)
162
+ out, _ = transpile(out)
163
+ out, _ = patch_curl_timeouts(out)
164
+ return out
165
+
166
+
167
+ def _build_pre_command_sh(task: Task) -> str | None:
168
+ # `defaults delete` fails on fresh VMs (key never set). Tasks recording such
169
+ # commands likely rely on the cleanup; skip the whole task instead of
170
+ # silently shipping a broken pre_command.
171
+ for c in task.setup_commands:
172
+ if "defaults delete" in c:
173
+ raise ValueError(
174
+ f"task setup_commands contain `defaults delete` — skipping export: {c!r}"
175
+ )
176
+ if not task.setup_commands:
177
+ return None
178
+ return _render(_tpl("pre_command.sh"), COMMANDS="\n".join(task.setup_commands))
179
+
180
+
181
+ def task_to_harbor(
182
+ task: Task,
183
+ output_root: Path,
184
+ *,
185
+ overwrite: bool = False,
186
+ fetch_file=None,
187
+ ) -> Path:
188
+ """Convert a collected Task into a Harbor task directory.
189
+
190
+ Layout:
191
+ {category}__{task_id}/
192
+ instruction.md
193
+ task.toml
194
+ environment/
195
+ tests/
196
+ test.sh
197
+ setup/
198
+ pre_command.sh
199
+ files/
200
+ manifest.json
201
+ <local_name>...
202
+
203
+ `fetch_file(local_name) -> bytes | None` fetches collect-time file uploads
204
+ from the gateway so the runner's setup.py can re-upload them. Pass None to
205
+ skip — the resulting harbor dir will have no file uploads.
206
+ """
207
+ output_root = Path(output_root)
208
+ category = task.category or "uncategorized"
209
+ short_id = task.id.replace("col-", "")[:36]
210
+ task_dir = output_root / f"{category}__{short_id}"
211
+
212
+ if task_dir.exists() and not overwrite:
213
+ raise FileExistsError(f"Already exists: {task_dir}")
214
+
215
+ # Build pre_command early so we fail before creating any dirs if all setup
216
+ # commands got stripped (raises ValueError).
217
+ pre_cmd = _build_pre_command_sh(task)
218
+
219
+ if task_dir.exists():
220
+ shutil.rmtree(task_dir)
221
+
222
+ task_dir.mkdir(parents=True)
223
+ (task_dir / "environment").mkdir()
224
+ tests_dir = task_dir / "tests"
225
+ tests_dir.mkdir()
226
+
227
+ # instruction.md
228
+ (task_dir / "instruction.md").write_text(task.instruction + "\n", encoding="utf-8")
229
+
230
+ # task.toml
231
+ tags = ["collected", "gui", task.platform]
232
+ if category:
233
+ tags.append(category)
234
+ toml = _render(
235
+ _tpl("task.toml"),
236
+ TAGS=json.dumps(tags),
237
+ PLATFORM=task.platform,
238
+ RUNNABLE="true" if task.runnable else "false",
239
+ )
240
+ (task_dir / "task.toml").write_text(toml, encoding="utf-8")
241
+
242
+ # actions.json — flat {steps: [{function, args}, ...]} for the debug agent's
243
+ # replay path. Pulls the first tool_call from each ATIF step; non-tool steps
244
+ # (text-only assistant messages) drop out.
245
+ actions = []
246
+ for step in task.steps or []:
247
+ tcs = step.get("tool_calls") if isinstance(step, dict) else None
248
+ if not tcs:
249
+ continue
250
+ tc = tcs[0]
251
+ actions.append({"function": tc.get("function"), "args": tc.get("args") or {}})
252
+ (task_dir / "actions.json").write_text(
253
+ json.dumps({"steps": actions}, indent=2) + "\n", encoding="utf-8"
254
+ )
255
+
256
+ # tests/setup/pre_command.sh — skip directory entirely if no setup commands
257
+ setup_dir = tests_dir / "setup"
258
+ if pre_cmd:
259
+ setup_dir.mkdir(exist_ok=True)
260
+ pre_cmd_path = setup_dir / "pre_command.sh"
261
+ pre_cmd_path.write_text(pre_cmd, encoding="utf-8")
262
+ pre_cmd_path.chmod(0o755)
263
+
264
+ # tests/setup/files/<local_name> + manifest.json — files captured at
265
+ # collect time. The runner's setup.py reads manifest.json and uploads
266
+ # each entry to its remote_path before pre_command runs.
267
+ upload_actions = [
268
+ sa
269
+ for sa in (task.setup_actions or [])
270
+ if (sa.get("action_type") if isinstance(sa, dict) else None) == "upload_file"
271
+ ]
272
+ if upload_actions:
273
+ if fetch_file is None:
274
+ logger.warning(
275
+ "task has %d upload_file actions but no fetch_file callback — "
276
+ "skipping (runner won't have collected files)",
277
+ len(upload_actions),
278
+ )
279
+ else:
280
+ files_dir = setup_dir / "files"
281
+ files_dir.mkdir(parents=True, exist_ok=True)
282
+ manifest = []
283
+ for sa in upload_actions:
284
+ params = sa.get("params") or {}
285
+ remote_path = params.get("remote_path")
286
+ local_name = params.get("local_name")
287
+ if not remote_path or not local_name:
288
+ continue
289
+ data = fetch_file(local_name)
290
+ if data is None:
291
+ logger.warning("collected file missing on gateway: %s", local_name)
292
+ continue
293
+ (files_dir / local_name).write_bytes(data)
294
+ manifest.append({"remote_path": remote_path, "local_name": local_name})
295
+ logger.info("exported file %s → %s (%dB)", local_name, remote_path, len(data))
296
+ if manifest:
297
+ (files_dir / "manifest.json").write_text(
298
+ json.dumps(manifest, indent=2) + "\n",
299
+ encoding="utf-8",
300
+ )
301
+
302
+ # tests/test.sh
303
+ test_path = tests_dir / "test.sh"
304
+ test_path.write_text(_build_test_sh(task), encoding="utf-8")
305
+ test_path.chmod(0o755)
306
+
307
+ return task_dir
@@ -0,0 +1,2 @@
1
+ #!/bin/bash
2
+ {{COMMANDS}}
@@ -0,0 +1,18 @@
1
+ [metadata]
2
+ author_name = "mmini-collect"
3
+ difficulty = "unknown"
4
+ category = "desktop-automation"
5
+ tags = {{TAGS}}
6
+ platform = "{{PLATFORM}}"
7
+ runnable = {{RUNNABLE}}
8
+
9
+ [verifier]
10
+ timeout_sec = 1800
11
+
12
+ [agent]
13
+ timeout_sec = 1800
14
+
15
+ [environment]
16
+ cpus = 4
17
+ memory_mb = 8192
18
+ allow_internet = true
@@ -0,0 +1,20 @@
1
+ #!/bin/bash
2
+ # iOS test.sh — runs on the harbor host, evaluator runs server-side at /grade.
3
+ REWARD="${HARBOR_REWARD_FILE:-./reward.txt}"
4
+ GRADER_LOG="${HARBOR_GRADER_LOG:-./grader.log}"
5
+ : "${GATEWAY_URL:?GATEWAY_URL is required}"
6
+ : "${SANDBOX_ID:?SANDBOX_ID is required}"
7
+ : "${MMINI_API_KEY:?MMINI_API_KEY is required}"
8
+
9
+ SPECS='{{SPECS}}'
10
+ PAYLOAD=$(printf '{"specs": %s}' "$SPECS")
11
+ RESP=$(curl -sS -H "Authorization: Bearer $MMINI_API_KEY" -H "Content-Type: application/json" -X POST "$GATEWAY_URL/v1/sandboxes/$SANDBOX_ID/grade" --data-binary "$PAYLOAD")
12
+ echo "$RESP" >> "$GRADER_LOG"
13
+ if echo "$RESP" | python3 -c "import sys,json; sys.exit(0 if json.load(sys.stdin).get(\"passed\") else 1)"; then
14
+ echo "1" > "$REWARD"
15
+ echo "Score: 1"
16
+ exit 0
17
+ fi
18
+
19
+ echo "0" > "$REWARD"
20
+ echo "Score: 0 — grader response: $(echo "$RESP" | head -c 500)"
@@ -0,0 +1,4 @@
1
+ #!/bin/bash
2
+ REWARD="${HARBOR_REWARD_FILE:-./reward.txt}"
3
+ echo "0" > "$REWARD"
4
+ echo "Score: 0 (no DSL grader)"
@@ -0,0 +1,10 @@
1
+ #!/bin/bash
2
+ # macOS test.sh — runs inside the VM via Harbor.
3
+ PREFIX=""
4
+ [ -d "/tmp/harbor/logs" ] && PREFIX="/tmp/harbor"
5
+ REWARD="${PREFIX}/logs/verifier/reward.txt"
6
+ GRADER_LOG="${PREFIX}/logs/verifier/grader.log"
7
+
8
+ {{CHECKS}}
9
+ echo "0" > "$REWARD"
10
+ echo "Score: 0"
@@ -0,0 +1,10 @@
1
+ # Check {{N}}
2
+ _r=$(mktemp)
3
+ perl -e 'alarm 15; exec @ARGV' -- bash -c '{{CMD}}' > "$_r" 2>>"$GRADER_LOG"
4
+ if grep -qi "true" "$_r" 2>/dev/null; then
5
+ rm -f "$_r"
6
+ echo "1" > "$REWARD"
7
+ echo "Score: 1"
8
+ exit 0
9
+ fi
10
+ rm -f "$_r"
@@ -0,0 +1,7 @@
1
+ #!/bin/bash
2
+ # macOS test.sh — no grader saved for this task. Always 0.
3
+ PREFIX=""
4
+ [ -d "/tmp/harbor/logs" ] && PREFIX="/tmp/harbor"
5
+ REWARD="${PREFIX}/logs/verifier/reward.txt"
6
+ echo "0" > "$REWARD"
7
+ echo "Score: 0 (no grader)"
@@ -0,0 +1,15 @@
1
+ from __future__ import annotations
2
+
3
+ import mmini as _mmini
4
+ from mmini import * # noqa: F403
5
+
6
+
7
+ class Computer(_mmini.Mmini):
8
+ """use.computer client."""
9
+
10
+
11
+ class AsyncComputer(_mmini.AsyncMmini):
12
+ """Async use.computer client."""
13
+
14
+
15
+ __all__ = [*_mmini.__all__, "AsyncComputer", "Computer"]
use_computer/py.typed ADDED
@@ -0,0 +1 @@
1
+
@@ -0,0 +1,133 @@
1
+ Metadata-Version: 2.4
2
+ Name: use-computer
3
+ Version: 0.0.1
4
+ Summary: Python SDK for the use.computer macOS and iOS Computer Use API
5
+ Project-URL: Homepage, https://use.computer
6
+ Project-URL: Documentation, https://github.com/josancamon19/mmini-sdk#readme
7
+ Project-URL: Repository, https://github.com/josancamon19/mmini-sdk
8
+ Author: use.computer
9
+ Keywords: automation,computer-use,ios,macos,sandbox
10
+ Classifier: Development Status :: 3 - Alpha
11
+ Classifier: Intended Audience :: Developers
12
+ Classifier: Operating System :: OS Independent
13
+ Classifier: Programming Language :: Python :: 3
14
+ Classifier: Programming Language :: Python :: 3.10
15
+ Classifier: Programming Language :: Python :: 3.11
16
+ Classifier: Programming Language :: Python :: 3.12
17
+ Classifier: Programming Language :: Python :: 3.13
18
+ Classifier: Typing :: Typed
19
+ Requires-Python: >=3.10
20
+ Requires-Dist: httpx>=0.27
21
+ Description-Content-Type: text/markdown
22
+
23
+ # use-computer Python SDK
24
+
25
+ Python SDK for the use.computer macOS and iOS Computer Use API.
26
+
27
+ ## Install
28
+
29
+ ```bash
30
+ pip install use-computer
31
+ ```
32
+
33
+ ## Usage
34
+
35
+ ```python
36
+ from use_computer import Computer
37
+
38
+ client = Computer(api_key="mk_live_...")
39
+ sandbox = client.create()
40
+
41
+ # Screenshot
42
+ img = sandbox.screenshot.take_full_screen()
43
+
44
+ # Mouse
45
+ sandbox.mouse.click(500, 400)
46
+ sandbox.mouse.move(100, 200)
47
+ sandbox.mouse.scroll(500, 400, "down", 3)
48
+ sandbox.mouse.drag(100, 100, 300, 300)
49
+
50
+ # Keyboard
51
+ sandbox.keyboard.type("Hello")
52
+ sandbox.keyboard.press("enter")
53
+ sandbox.keyboard.hotkey("command+space")
54
+
55
+ # Display
56
+ info = sandbox.display.get_info()
57
+ windows = sandbox.display.get_windows()
58
+
59
+ # Recording
60
+ recording = sandbox.recording.start(name="my-recording")
61
+ # ... do stuff ...
62
+ sandbox.recording.stop(recording.id)
63
+ sandbox.recording.download(recording.id, "output.mp4")
64
+
65
+ # Cleanup
66
+ sandbox.close()
67
+ client.close()
68
+ ```
69
+
70
+ ## Environment
71
+
72
+ `Computer()` reads these environment variables when explicit values are not passed:
73
+
74
+ ```bash
75
+ USE_COMPUTER_API_KEY=mk_live_...
76
+ USE_COMPUTER_BASE_URL=https://api.use.computer
77
+ ```
78
+
79
+ `USE_COMPUTER_BASE_URL` is optional. The default is `https://api.use.computer`.
80
+
81
+ ## Async
82
+
83
+ ```python
84
+ from use_computer import AsyncComputer
85
+
86
+ client = AsyncComputer(api_key="mk_live_...")
87
+ sandbox = await client.create()
88
+ img = await sandbox.screenshot.take_full_screen()
89
+ await sandbox.close()
90
+ await client.close()
91
+ ```
92
+
93
+ ## Local Gateway
94
+
95
+ ```python
96
+ from use_computer import Computer
97
+
98
+ client = Computer(
99
+ api_key="sk-local",
100
+ base_url="http://localhost:8080",
101
+ )
102
+ ```
103
+
104
+ ## Backward Compatibility
105
+
106
+ The original import path still works:
107
+
108
+ ```python
109
+ from mmini import Mmini
110
+ ```
111
+
112
+ New code should prefer:
113
+
114
+ ```python
115
+ from use_computer import Computer
116
+ ```
117
+
118
+ ## Publishing
119
+
120
+ CI publishes `use-computer` from the `main` branch. The first release is
121
+ `0.0.1`; after that the workflow reads the latest `vX.Y.Z` tag and bumps the
122
+ patch version.
123
+
124
+ For CI, prefer PyPI Trusted Publishing. If using an API token instead, set a
125
+ repo secret named `PYPI_API_TOKEN`; do not commit tokens to `.env`.
126
+
127
+ ## Development
128
+
129
+ ```bash
130
+ uv run --group dev pre-commit install
131
+ ```
132
+
133
+ Pre-commit runs `ruff format`, `ruff check --fix`, and `ty check`.
@@ -0,0 +1,32 @@
1
+ mmini/__init__.py,sha256=6Z7ivHUDYwCMxJXk_C7txP7qYBh4Me6EsIcn-DjCtQ8,1006
2
+ mmini/ax_transpile.py,sha256=pidZiyM3cykYNL9xSeKlxW5qte4KQGiMokFlg5dvSlY,18600
3
+ mmini/client.py,sha256=6Zvn4AVReqiZG7G_HrxKpUtnOPZg-eoRXNpiHPG3CdA,11971
4
+ mmini/display.py,sha256=nWJOr91nmz5xx6ZTh11HAXgVeDhgC3CvdZWG3ABwQCY,1125
5
+ mmini/errors.py,sha256=PpVtvTJsUQuyTwfq0qKn_s4HrKKAKrb4hp70D4Ov6l0,2173
6
+ mmini/models.py,sha256=yShbRgdngN2zyoUrvXfCifoPrSV2FHr3dtaLkZXRNf0,1649
7
+ mmini/parsers.py,sha256=bjdUBoT3uaUY73eDqm4uzFlqFotNZ9PLvYS8SO18Ifc,9368
8
+ mmini/py.typed,sha256=AbpHGcgLb-kRsJGnwFEktk7uzpZOCcBY74-YBdrKVGs,1
9
+ mmini/recording.py,sha256=if3L9rbt3onmg5YM3F6wioLiRzvPgS8QylZAKEmcpcI,3522
10
+ mmini/retry.py,sha256=6owrGNsAUfmoj-4SZlgG_scRDe4kw3flcd3IwXqdCG4,4058
11
+ mmini/sandbox.py,sha256=2dHtIz91oePxdoDfDqlDbmGXRVfesLQ5kGzth6Z-12Q,15168
12
+ mmini/screenshot.py,sha256=8AhbSOA647KqPNB6YhOjLunOiM9c9aYL14hwxnPPin0,2229
13
+ mmini/ios/__init__.py,sha256=Rn60cHhHnpjGBIGkojSgkZbkl8lFJKmMm7lcDnDoX9s,309
14
+ mmini/ios/apps.py,sha256=R6nI7mkv6TY_tSohT642j3Pc8kPnAP4aIoqaFI462hM,2182
15
+ mmini/ios/environment.py,sha256=UCfr4B5A8NRl5gFT4zh75P5rl2Jr0UllT9jp4KtGbR8,1838
16
+ mmini/ios/input.py,sha256=TRYC7TMRO-xenl_k4IYZHyu5jc-Mo9Dzs6AWP-H2qMg,3984
17
+ mmini/macos/__init__.py,sha256=rYsFsjsRTy5Ios8Cf3suBq4sXgu8bAM4LcrIi0YZQxQ,169
18
+ mmini/macos/keyboard.py,sha256=eLOqngkHmoOg8oOskVmkO4d1yPi04Kv4sGXQfrEPvqI,2218
19
+ mmini/macos/mouse.py,sha256=QGUJdOvqysZREYLy__N0DQnJchZW2xRZDs_1g-eHi58,3634
20
+ mmini/tasks/__init__.py,sha256=fLzrLX2JAbqExDUAKNgR6s370maLWxNAWBskajD9RFY,10062
21
+ mmini/tasks/templates/pre_command.sh,sha256=bwNnVohjDOfWoOnPKgNgOaH1RrZdUA-RpBcrRZeytmQ,25
22
+ mmini/tasks/templates/task.toml,sha256=JiWb7gnvKfLpZfisySlH-WsXIX5c7jOuYS8nTHvdbfA,284
23
+ mmini/tasks/templates/test_ios.sh,sha256=n8xVC9nOEztT5bCo0BS3ZDfH_oI5JVSzly7sCQYNr2o,841
24
+ mmini/tasks/templates/test_ios_nograder.sh,sha256=5WwKY2X4SasDDzn9eMR3f1_01d08fUm9nMAot84wq8c,110
25
+ mmini/tasks/templates/test_macos.sh,sha256=WXriAun7h5Udo-8wOx4NUcV7MwVistd8iFul9js9Qmc,264
26
+ mmini/tasks/templates/test_macos_check.sh,sha256=dJEILS8jW4lLVkhH2oBUJZOOaY0MR5M9mo7qb7_mHVw,226
27
+ mmini/tasks/templates/test_macos_nograder.sh,sha256=kZ7LDD6tb9SJmqgVWLZxX2RudRl6xyeWmQvGXWala-0,226
28
+ use_computer/__init__.py,sha256=e8ynfrOySnQBJGn5riiaG06tvqPAxHfkOhYmxwlcAZc,294
29
+ use_computer/py.typed,sha256=AbpHGcgLb-kRsJGnwFEktk7uzpZOCcBY74-YBdrKVGs,1
30
+ use_computer-0.0.1.dist-info/METADATA,sha256=OXfyVvn31rE90V_Ts9PAnRg4MZMZtAubr3KycZlQNTI,3098
31
+ use_computer-0.0.1.dist-info/WHEEL,sha256=QccIxa26bgl1E6uMy58deGWi-0aeIkkangHcxk2kWfw,87
32
+ use_computer-0.0.1.dist-info/RECORD,,
@@ -0,0 +1,4 @@
1
+ Wheel-Version: 1.0
2
+ Generator: hatchling 1.29.0
3
+ Root-Is-Purelib: true
4
+ Tag: py3-none-any