Repository navigation
Expand file tree
/
Copy pathmilo.py
More file actions
161 lines (138 loc) · 4.94 KB
/
Copy pathmilo.py
File metadata and controls
161 lines (138 loc) · 4.94 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
#!/usr/bin/env python3
"""Milo V0.1.1: screenshot the display, draw a coordinate grid, and locate icons."""
from __future__ import annotations
import argparse
import json
import subprocess
import sys
import tempfile
from pathlib import Path
from PIL import Image
from gemini import MODEL, GeminiError, locate_icon
from rendering import draw_grid, draw_highlight
__version__ = "0.1.1"
def capture_screen() -> Image.Image:
"""Capture the main Mac display using the built-in screencapture tool."""
with tempfile.TemporaryDirectory() as tmp_dir:
screenshot_path = Path(tmp_dir) / "screen.png"
result = subprocess.run(
["screencapture", "-x", "-m", str(screenshot_path)],
capture_output=True,
text=True,
)
if result.returncode != 0 or not screenshot_path.exists():
detail = result.stderr.strip() or "screencapture produced no image."
raise RuntimeError(
"Could not capture the screen. Grant Screen Recording permission "
f"to your terminal in System Settings > Privacy & Security. ({detail})"
)
return Image.open(screenshot_path).convert("RGB")
def parse_args() -> argparse.Namespace:
parser = argparse.ArgumentParser(
description=(
"Capture the main display with a 31x31 coordinate grid, optionally "
"using Gemini to locate an icon."
)
)
parser.add_argument(
"--version",
action="version",
version=f"Milo {__version__}",
)
parser.add_argument(
"target",
nargs="?",
help='Icon to locate, for example: "Safari icon".',
)
parser.add_argument(
"-o",
"--output",
type=Path,
default=Path("milo-grid.png"),
help="Where to save the image (default: milo-grid.png).",
)
parser.add_argument(
"--json",
action="store_true",
help="Print the localization result as JSON.",
)
parser.add_argument(
"--no-open",
action="store_true",
help="Do not open the image after saving it.",
)
return parser.parse_args()
def main() -> int:
args = parse_args()
output = args.output.expanduser().resolve()
output.parent.mkdir(parents=True, exist_ok=True)
try:
image = capture_screen()
except RuntimeError as error:
print(f"Milo: {error}", file=sys.stderr)
return 1
location = None
if args.target:
if not args.json:
print(f'Milo: asking {MODEL} to find "{args.target}"...')
try:
location = locate_icon(image, args.target)
except GeminiError as error:
print(f"Milo: {error}", file=sys.stderr)
draw_grid(image).save(output)
return 1
image = draw_grid(image)
if location is not None and location.found and location.coordinate:
image = draw_highlight(image, location.coordinate)
image.save(output)
if args.json:
result = {
"target": args.target,
"model": MODEL if args.target else None,
"found": location.found if location else None,
"coordinate": location.coordinate if location else None,
"coarse_coordinate": location.coarse_coordinate if location else None,
"refined": location.refined if location else None,
"confidence": location.confidence if location else None,
"description": location.description if location else None,
"reasoning": location.reasoning if location else None,
"center": (
{
"x": location.center[0],
"y": location.center[1],
}
if location and location.center
else None
),
"bounding_box": (
{
"left": location.bounding_box[0],
"top": location.bounding_box[1],
"right": location.bounding_box[2],
"bottom": location.bounding_box[3],
}
if location and location.bounding_box
else None
),
"image": str(output),
}
print(json.dumps(result, indent=2))
elif location is not None:
if location.found and location.coordinate:
description = f" — {location.description}" if location.description else ""
print(
f"Milo: {location.coordinate}{description} "
f"(confidence {location.confidence:.0%})"
)
else:
print(f'Milo: could not find "{args.target}" in the screenshot.')
print(f"Milo: saved {output}")
else:
print(f"Milo: saved {output}")
if not args.no_open:
subprocess.run(["open", str(output)], check=False)
if location is not None and not location.found:
return 2
return 0
if __name__ == "__main__":
raise SystemExit(main())