@heylemon/lemonade 0.2.4 → 0.2.6
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/dist/agents/system-prompt.js +6 -1
- package/dist/build-info.json +3 -3
- package/dist/canvas-host/a2ui/.bundle.hash +1 -1
- package/package.json +1 -1
- package/skills/apple-notes/SKILL.md +0 -50
- package/skills/apple-reminders/SKILL.md +0 -67
- package/skills/goplaces/SKILL.md +0 -30
- package/skills/local-places/SERVER_README.md +0 -101
- package/skills/local-places/SKILL.md +0 -91
- package/skills/local-places/pyproject.toml +0 -27
- package/skills/local-places/src/local_places/__init__.py +0 -2
- package/skills/local-places/src/local_places/google_places.py +0 -314
- package/skills/local-places/src/local_places/main.py +0 -65
- package/skills/local-places/src/local_places/schemas.py +0 -107
- package/skills/messages/SKILL.md +0 -125
- package/skills/openai-image-gen/SKILL.md +0 -71
- package/skills/openai-image-gen/scripts/gen.py +0 -255
- package/skills/ordercli/SKILL.md +0 -47
- package/skills/spotify-player/SKILL.md +0 -38
- package/skills/youtube-watcher/SKILL.md +0 -51
- package/skills/youtube-watcher/scripts/get_transcript.py +0 -81
- /package/skills/eightctl/{SKILL.md → SKILL.md.disabled} +0 -0
- /package/skills/nano-banana-pro/{SKILL.md → SKILL.md.disabled} +0 -0
- /package/skills/openai-whisper-api/{SKILL.md → SKILL.md.disabled} +0 -0
- /package/skills/openhue/{SKILL.md → SKILL.md.disabled} +0 -0
- /package/skills/sag/{SKILL.md → SKILL.md.disabled} +0 -0
- /package/skills/sherpa-onnx-tts/{SKILL.md → SKILL.md.disabled} +0 -0
- /package/skills/sonoscli/{SKILL.md → SKILL.md.disabled} +0 -0
|
@@ -1,314 +0,0 @@
|
|
|
1
|
-
from __future__ import annotations
|
|
2
|
-
|
|
3
|
-
import logging
|
|
4
|
-
import os
|
|
5
|
-
from typing import Any
|
|
6
|
-
|
|
7
|
-
import httpx
|
|
8
|
-
from fastapi import HTTPException
|
|
9
|
-
|
|
10
|
-
from local_places.schemas import (
|
|
11
|
-
LatLng,
|
|
12
|
-
LocationResolveRequest,
|
|
13
|
-
LocationResolveResponse,
|
|
14
|
-
PlaceDetails,
|
|
15
|
-
PlaceSummary,
|
|
16
|
-
ResolvedLocation,
|
|
17
|
-
SearchRequest,
|
|
18
|
-
SearchResponse,
|
|
19
|
-
)
|
|
20
|
-
|
|
21
|
-
GOOGLE_PLACES_BASE_URL = os.getenv(
|
|
22
|
-
"GOOGLE_PLACES_BASE_URL", "https://places.googleapis.com/v1"
|
|
23
|
-
)
|
|
24
|
-
logger = logging.getLogger("local_places.google_places")
|
|
25
|
-
|
|
26
|
-
_PRICE_LEVEL_TO_ENUM = {
|
|
27
|
-
0: "PRICE_LEVEL_FREE",
|
|
28
|
-
1: "PRICE_LEVEL_INEXPENSIVE",
|
|
29
|
-
2: "PRICE_LEVEL_MODERATE",
|
|
30
|
-
3: "PRICE_LEVEL_EXPENSIVE",
|
|
31
|
-
4: "PRICE_LEVEL_VERY_EXPENSIVE",
|
|
32
|
-
}
|
|
33
|
-
_ENUM_TO_PRICE_LEVEL = {value: key for key, value in _PRICE_LEVEL_TO_ENUM.items()}
|
|
34
|
-
|
|
35
|
-
_SEARCH_FIELD_MASK = (
|
|
36
|
-
"places.id,"
|
|
37
|
-
"places.displayName,"
|
|
38
|
-
"places.formattedAddress,"
|
|
39
|
-
"places.location,"
|
|
40
|
-
"places.rating,"
|
|
41
|
-
"places.priceLevel,"
|
|
42
|
-
"places.types,"
|
|
43
|
-
"places.currentOpeningHours,"
|
|
44
|
-
"nextPageToken"
|
|
45
|
-
)
|
|
46
|
-
|
|
47
|
-
_DETAILS_FIELD_MASK = (
|
|
48
|
-
"id,"
|
|
49
|
-
"displayName,"
|
|
50
|
-
"formattedAddress,"
|
|
51
|
-
"location,"
|
|
52
|
-
"rating,"
|
|
53
|
-
"priceLevel,"
|
|
54
|
-
"types,"
|
|
55
|
-
"regularOpeningHours,"
|
|
56
|
-
"currentOpeningHours,"
|
|
57
|
-
"nationalPhoneNumber,"
|
|
58
|
-
"websiteUri"
|
|
59
|
-
)
|
|
60
|
-
|
|
61
|
-
_RESOLVE_FIELD_MASK = (
|
|
62
|
-
"places.id,"
|
|
63
|
-
"places.displayName,"
|
|
64
|
-
"places.formattedAddress,"
|
|
65
|
-
"places.location,"
|
|
66
|
-
"places.types"
|
|
67
|
-
)
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
class _GoogleResponse:
|
|
71
|
-
def __init__(self, response: httpx.Response):
|
|
72
|
-
self.status_code = response.status_code
|
|
73
|
-
self._response = response
|
|
74
|
-
|
|
75
|
-
def json(self) -> dict[str, Any]:
|
|
76
|
-
return self._response.json()
|
|
77
|
-
|
|
78
|
-
@property
|
|
79
|
-
def text(self) -> str:
|
|
80
|
-
return self._response.text
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
def _api_headers(field_mask: str) -> dict[str, str]:
|
|
84
|
-
api_key = os.getenv("GOOGLE_PLACES_API_KEY")
|
|
85
|
-
if not api_key:
|
|
86
|
-
raise HTTPException(
|
|
87
|
-
status_code=500,
|
|
88
|
-
detail="GOOGLE_PLACES_API_KEY is not set.",
|
|
89
|
-
)
|
|
90
|
-
return {
|
|
91
|
-
"Content-Type": "application/json",
|
|
92
|
-
"X-Goog-Api-Key": api_key,
|
|
93
|
-
"X-Goog-FieldMask": field_mask,
|
|
94
|
-
}
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
def _request(
|
|
98
|
-
method: str, url: str, payload: dict[str, Any] | None, field_mask: str
|
|
99
|
-
) -> _GoogleResponse:
|
|
100
|
-
try:
|
|
101
|
-
with httpx.Client(timeout=10.0) as client:
|
|
102
|
-
response = client.request(
|
|
103
|
-
method=method,
|
|
104
|
-
url=url,
|
|
105
|
-
headers=_api_headers(field_mask),
|
|
106
|
-
json=payload,
|
|
107
|
-
)
|
|
108
|
-
except httpx.HTTPError as exc:
|
|
109
|
-
raise HTTPException(status_code=502, detail="Google Places API unavailable.") from exc
|
|
110
|
-
|
|
111
|
-
return _GoogleResponse(response)
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
def _build_text_query(request: SearchRequest) -> str:
|
|
115
|
-
keyword = request.filters.keyword if request.filters else None
|
|
116
|
-
if keyword:
|
|
117
|
-
return f"{request.query} {keyword}".strip()
|
|
118
|
-
return request.query
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
def _build_search_body(request: SearchRequest) -> dict[str, Any]:
|
|
122
|
-
body: dict[str, Any] = {
|
|
123
|
-
"textQuery": _build_text_query(request),
|
|
124
|
-
"pageSize": request.limit,
|
|
125
|
-
}
|
|
126
|
-
|
|
127
|
-
if request.page_token:
|
|
128
|
-
body["pageToken"] = request.page_token
|
|
129
|
-
|
|
130
|
-
if request.location_bias:
|
|
131
|
-
body["locationBias"] = {
|
|
132
|
-
"circle": {
|
|
133
|
-
"center": {
|
|
134
|
-
"latitude": request.location_bias.lat,
|
|
135
|
-
"longitude": request.location_bias.lng,
|
|
136
|
-
},
|
|
137
|
-
"radius": request.location_bias.radius_m,
|
|
138
|
-
}
|
|
139
|
-
}
|
|
140
|
-
|
|
141
|
-
if request.filters:
|
|
142
|
-
filters = request.filters
|
|
143
|
-
if filters.types:
|
|
144
|
-
body["includedType"] = filters.types[0]
|
|
145
|
-
if filters.open_now is not None:
|
|
146
|
-
body["openNow"] = filters.open_now
|
|
147
|
-
if filters.min_rating is not None:
|
|
148
|
-
body["minRating"] = filters.min_rating
|
|
149
|
-
if filters.price_levels:
|
|
150
|
-
body["priceLevels"] = [
|
|
151
|
-
_PRICE_LEVEL_TO_ENUM[level] for level in filters.price_levels
|
|
152
|
-
]
|
|
153
|
-
|
|
154
|
-
return body
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
def _parse_lat_lng(raw: dict[str, Any] | None) -> LatLng | None:
|
|
158
|
-
if not raw:
|
|
159
|
-
return None
|
|
160
|
-
latitude = raw.get("latitude")
|
|
161
|
-
longitude = raw.get("longitude")
|
|
162
|
-
if latitude is None or longitude is None:
|
|
163
|
-
return None
|
|
164
|
-
return LatLng(lat=latitude, lng=longitude)
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
def _parse_display_name(raw: dict[str, Any] | None) -> str | None:
|
|
168
|
-
if not raw:
|
|
169
|
-
return None
|
|
170
|
-
return raw.get("text")
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
def _parse_open_now(raw: dict[str, Any] | None) -> bool | None:
|
|
174
|
-
if not raw:
|
|
175
|
-
return None
|
|
176
|
-
return raw.get("openNow")
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
def _parse_hours(raw: dict[str, Any] | None) -> list[str] | None:
|
|
180
|
-
if not raw:
|
|
181
|
-
return None
|
|
182
|
-
return raw.get("weekdayDescriptions")
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
def _parse_price_level(raw: str | None) -> int | None:
|
|
186
|
-
if not raw:
|
|
187
|
-
return None
|
|
188
|
-
return _ENUM_TO_PRICE_LEVEL.get(raw)
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
def search_places(request: SearchRequest) -> SearchResponse:
|
|
192
|
-
url = f"{GOOGLE_PLACES_BASE_URL}/places:searchText"
|
|
193
|
-
response = _request("POST", url, _build_search_body(request), _SEARCH_FIELD_MASK)
|
|
194
|
-
|
|
195
|
-
if response.status_code >= 400:
|
|
196
|
-
logger.error(
|
|
197
|
-
"Google Places API error %s. response=%s",
|
|
198
|
-
response.status_code,
|
|
199
|
-
response.text,
|
|
200
|
-
)
|
|
201
|
-
raise HTTPException(
|
|
202
|
-
status_code=502,
|
|
203
|
-
detail=f"Google Places API error ({response.status_code}).",
|
|
204
|
-
)
|
|
205
|
-
|
|
206
|
-
try:
|
|
207
|
-
payload = response.json()
|
|
208
|
-
except ValueError as exc:
|
|
209
|
-
logger.error(
|
|
210
|
-
"Google Places API returned invalid JSON. response=%s",
|
|
211
|
-
response.text,
|
|
212
|
-
)
|
|
213
|
-
raise HTTPException(status_code=502, detail="Invalid Google response.") from exc
|
|
214
|
-
|
|
215
|
-
places = payload.get("places", [])
|
|
216
|
-
results = []
|
|
217
|
-
for place in places:
|
|
218
|
-
results.append(
|
|
219
|
-
PlaceSummary(
|
|
220
|
-
place_id=place.get("id", ""),
|
|
221
|
-
name=_parse_display_name(place.get("displayName")),
|
|
222
|
-
address=place.get("formattedAddress"),
|
|
223
|
-
location=_parse_lat_lng(place.get("location")),
|
|
224
|
-
rating=place.get("rating"),
|
|
225
|
-
price_level=_parse_price_level(place.get("priceLevel")),
|
|
226
|
-
types=place.get("types"),
|
|
227
|
-
open_now=_parse_open_now(place.get("currentOpeningHours")),
|
|
228
|
-
)
|
|
229
|
-
)
|
|
230
|
-
|
|
231
|
-
return SearchResponse(
|
|
232
|
-
results=results,
|
|
233
|
-
next_page_token=payload.get("nextPageToken"),
|
|
234
|
-
)
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
def get_place_details(place_id: str) -> PlaceDetails:
|
|
238
|
-
url = f"{GOOGLE_PLACES_BASE_URL}/places/{place_id}"
|
|
239
|
-
response = _request("GET", url, None, _DETAILS_FIELD_MASK)
|
|
240
|
-
|
|
241
|
-
if response.status_code >= 400:
|
|
242
|
-
logger.error(
|
|
243
|
-
"Google Places API error %s. response=%s",
|
|
244
|
-
response.status_code,
|
|
245
|
-
response.text,
|
|
246
|
-
)
|
|
247
|
-
raise HTTPException(
|
|
248
|
-
status_code=502,
|
|
249
|
-
detail=f"Google Places API error ({response.status_code}).",
|
|
250
|
-
)
|
|
251
|
-
|
|
252
|
-
try:
|
|
253
|
-
payload = response.json()
|
|
254
|
-
except ValueError as exc:
|
|
255
|
-
logger.error(
|
|
256
|
-
"Google Places API returned invalid JSON. response=%s",
|
|
257
|
-
response.text,
|
|
258
|
-
)
|
|
259
|
-
raise HTTPException(status_code=502, detail="Invalid Google response.") from exc
|
|
260
|
-
|
|
261
|
-
return PlaceDetails(
|
|
262
|
-
place_id=payload.get("id", place_id),
|
|
263
|
-
name=_parse_display_name(payload.get("displayName")),
|
|
264
|
-
address=payload.get("formattedAddress"),
|
|
265
|
-
location=_parse_lat_lng(payload.get("location")),
|
|
266
|
-
rating=payload.get("rating"),
|
|
267
|
-
price_level=_parse_price_level(payload.get("priceLevel")),
|
|
268
|
-
types=payload.get("types"),
|
|
269
|
-
phone=payload.get("nationalPhoneNumber"),
|
|
270
|
-
website=payload.get("websiteUri"),
|
|
271
|
-
hours=_parse_hours(payload.get("regularOpeningHours")),
|
|
272
|
-
open_now=_parse_open_now(payload.get("currentOpeningHours")),
|
|
273
|
-
)
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
def resolve_locations(request: LocationResolveRequest) -> LocationResolveResponse:
|
|
277
|
-
url = f"{GOOGLE_PLACES_BASE_URL}/places:searchText"
|
|
278
|
-
body = {"textQuery": request.location_text, "pageSize": request.limit}
|
|
279
|
-
response = _request("POST", url, body, _RESOLVE_FIELD_MASK)
|
|
280
|
-
|
|
281
|
-
if response.status_code >= 400:
|
|
282
|
-
logger.error(
|
|
283
|
-
"Google Places API error %s. response=%s",
|
|
284
|
-
response.status_code,
|
|
285
|
-
response.text,
|
|
286
|
-
)
|
|
287
|
-
raise HTTPException(
|
|
288
|
-
status_code=502,
|
|
289
|
-
detail=f"Google Places API error ({response.status_code}).",
|
|
290
|
-
)
|
|
291
|
-
|
|
292
|
-
try:
|
|
293
|
-
payload = response.json()
|
|
294
|
-
except ValueError as exc:
|
|
295
|
-
logger.error(
|
|
296
|
-
"Google Places API returned invalid JSON. response=%s",
|
|
297
|
-
response.text,
|
|
298
|
-
)
|
|
299
|
-
raise HTTPException(status_code=502, detail="Invalid Google response.") from exc
|
|
300
|
-
|
|
301
|
-
places = payload.get("places", [])
|
|
302
|
-
results = []
|
|
303
|
-
for place in places:
|
|
304
|
-
results.append(
|
|
305
|
-
ResolvedLocation(
|
|
306
|
-
place_id=place.get("id", ""),
|
|
307
|
-
name=_parse_display_name(place.get("displayName")),
|
|
308
|
-
address=place.get("formattedAddress"),
|
|
309
|
-
location=_parse_lat_lng(place.get("location")),
|
|
310
|
-
types=place.get("types"),
|
|
311
|
-
)
|
|
312
|
-
)
|
|
313
|
-
|
|
314
|
-
return LocationResolveResponse(results=results)
|
|
@@ -1,65 +0,0 @@
|
|
|
1
|
-
import logging
|
|
2
|
-
import os
|
|
3
|
-
|
|
4
|
-
from fastapi import FastAPI, Request
|
|
5
|
-
from fastapi.encoders import jsonable_encoder
|
|
6
|
-
from fastapi.exceptions import RequestValidationError
|
|
7
|
-
from fastapi.responses import JSONResponse
|
|
8
|
-
|
|
9
|
-
from local_places.google_places import get_place_details, resolve_locations, search_places
|
|
10
|
-
from local_places.schemas import (
|
|
11
|
-
LocationResolveRequest,
|
|
12
|
-
LocationResolveResponse,
|
|
13
|
-
PlaceDetails,
|
|
14
|
-
SearchRequest,
|
|
15
|
-
SearchResponse,
|
|
16
|
-
)
|
|
17
|
-
|
|
18
|
-
app = FastAPI(
|
|
19
|
-
title="My API",
|
|
20
|
-
servers=[{"url": os.getenv("OPENAPI_SERVER_URL", "http://maxims-macbook-air:8000")}],
|
|
21
|
-
)
|
|
22
|
-
logger = logging.getLogger("local_places.validation")
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
@app.get("/ping")
|
|
26
|
-
def ping() -> dict[str, str]:
|
|
27
|
-
return {"message": "pong"}
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
@app.exception_handler(RequestValidationError)
|
|
31
|
-
async def validation_exception_handler(
|
|
32
|
-
request: Request, exc: RequestValidationError
|
|
33
|
-
) -> JSONResponse:
|
|
34
|
-
logger.error(
|
|
35
|
-
"Validation error on %s %s. body=%s errors=%s",
|
|
36
|
-
request.method,
|
|
37
|
-
request.url.path,
|
|
38
|
-
exc.body,
|
|
39
|
-
exc.errors(),
|
|
40
|
-
)
|
|
41
|
-
return JSONResponse(
|
|
42
|
-
status_code=422,
|
|
43
|
-
content=jsonable_encoder({"detail": exc.errors()}),
|
|
44
|
-
)
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
@app.post("/places/search", response_model=SearchResponse)
|
|
48
|
-
def places_search(request: SearchRequest) -> SearchResponse:
|
|
49
|
-
return search_places(request)
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
@app.get("/places/{place_id}", response_model=PlaceDetails)
|
|
53
|
-
def places_details(place_id: str) -> PlaceDetails:
|
|
54
|
-
return get_place_details(place_id)
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
@app.post("/locations/resolve", response_model=LocationResolveResponse)
|
|
58
|
-
def locations_resolve(request: LocationResolveRequest) -> LocationResolveResponse:
|
|
59
|
-
return resolve_locations(request)
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
if __name__ == "__main__":
|
|
63
|
-
import uvicorn
|
|
64
|
-
|
|
65
|
-
uvicorn.run("local_places.main:app", host="0.0.0.0", port=8000)
|
|
@@ -1,107 +0,0 @@
|
|
|
1
|
-
from __future__ import annotations
|
|
2
|
-
|
|
3
|
-
from pydantic import BaseModel, Field, field_validator
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
class LatLng(BaseModel):
|
|
7
|
-
lat: float = Field(ge=-90, le=90)
|
|
8
|
-
lng: float = Field(ge=-180, le=180)
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
class LocationBias(BaseModel):
|
|
12
|
-
lat: float = Field(ge=-90, le=90)
|
|
13
|
-
lng: float = Field(ge=-180, le=180)
|
|
14
|
-
radius_m: float = Field(gt=0)
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
class Filters(BaseModel):
|
|
18
|
-
types: list[str] | None = None
|
|
19
|
-
open_now: bool | None = None
|
|
20
|
-
min_rating: float | None = Field(default=None, ge=0, le=5)
|
|
21
|
-
price_levels: list[int] | None = None
|
|
22
|
-
keyword: str | None = Field(default=None, min_length=1)
|
|
23
|
-
|
|
24
|
-
@field_validator("types")
|
|
25
|
-
@classmethod
|
|
26
|
-
def validate_types(cls, value: list[str] | None) -> list[str] | None:
|
|
27
|
-
if value is None:
|
|
28
|
-
return value
|
|
29
|
-
if len(value) > 1:
|
|
30
|
-
raise ValueError(
|
|
31
|
-
"Only one type is supported. Use query/keyword for additional filtering."
|
|
32
|
-
)
|
|
33
|
-
return value
|
|
34
|
-
|
|
35
|
-
@field_validator("price_levels")
|
|
36
|
-
@classmethod
|
|
37
|
-
def validate_price_levels(cls, value: list[int] | None) -> list[int] | None:
|
|
38
|
-
if value is None:
|
|
39
|
-
return value
|
|
40
|
-
invalid = [level for level in value if level not in range(0, 5)]
|
|
41
|
-
if invalid:
|
|
42
|
-
raise ValueError("price_levels must be integers between 0 and 4.")
|
|
43
|
-
return value
|
|
44
|
-
|
|
45
|
-
@field_validator("min_rating")
|
|
46
|
-
@classmethod
|
|
47
|
-
def validate_min_rating(cls, value: float | None) -> float | None:
|
|
48
|
-
if value is None:
|
|
49
|
-
return value
|
|
50
|
-
if (value * 2) % 1 != 0:
|
|
51
|
-
raise ValueError("min_rating must be in 0.5 increments.")
|
|
52
|
-
return value
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
class SearchRequest(BaseModel):
|
|
56
|
-
query: str = Field(min_length=1)
|
|
57
|
-
location_bias: LocationBias | None = None
|
|
58
|
-
filters: Filters | None = None
|
|
59
|
-
limit: int = Field(default=10, ge=1, le=20)
|
|
60
|
-
page_token: str | None = None
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
class PlaceSummary(BaseModel):
|
|
64
|
-
place_id: str
|
|
65
|
-
name: str | None = None
|
|
66
|
-
address: str | None = None
|
|
67
|
-
location: LatLng | None = None
|
|
68
|
-
rating: float | None = None
|
|
69
|
-
price_level: int | None = None
|
|
70
|
-
types: list[str] | None = None
|
|
71
|
-
open_now: bool | None = None
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
class SearchResponse(BaseModel):
|
|
75
|
-
results: list[PlaceSummary]
|
|
76
|
-
next_page_token: str | None = None
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
class LocationResolveRequest(BaseModel):
|
|
80
|
-
location_text: str = Field(min_length=1)
|
|
81
|
-
limit: int = Field(default=5, ge=1, le=10)
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
class ResolvedLocation(BaseModel):
|
|
85
|
-
place_id: str
|
|
86
|
-
name: str | None = None
|
|
87
|
-
address: str | None = None
|
|
88
|
-
location: LatLng | None = None
|
|
89
|
-
types: list[str] | None = None
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
class LocationResolveResponse(BaseModel):
|
|
93
|
-
results: list[ResolvedLocation]
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
class PlaceDetails(BaseModel):
|
|
97
|
-
place_id: str
|
|
98
|
-
name: str | None = None
|
|
99
|
-
address: str | None = None
|
|
100
|
-
location: LatLng | None = None
|
|
101
|
-
rating: float | None = None
|
|
102
|
-
price_level: int | None = None
|
|
103
|
-
types: list[str] | None = None
|
|
104
|
-
phone: str | None = None
|
|
105
|
-
website: str | None = None
|
|
106
|
-
hours: list[str] | None = None
|
|
107
|
-
open_now: bool | None = None
|
package/skills/messages/SKILL.md
DELETED
|
@@ -1,125 +0,0 @@
|
|
|
1
|
-
---
|
|
2
|
-
name: messages
|
|
3
|
-
description: Send iMessages and SMS via the Messages app. Read recent messages. Triggers on: iMessage, text, send message, SMS, message someone
|
|
4
|
-
---
|
|
5
|
-
|
|
6
|
-
# Messages (iMessage/SMS)
|
|
7
|
-
|
|
8
|
-
## CRITICAL: Use AppleScript, Not Browser
|
|
9
|
-
|
|
10
|
-
Messages is a **native macOS app**. Do NOT use browser tools.
|
|
11
|
-
|
|
12
|
-
## Quick Reference
|
|
13
|
-
|
|
14
|
-
| Action | AppleScript |
|
|
15
|
-
|--------|-------------|
|
|
16
|
-
| Send message | See below - requires buddy/participant |
|
|
17
|
-
| Open Messages | `tell application "Messages" to activate` |
|
|
18
|
-
|
|
19
|
-
## Sending Messages
|
|
20
|
-
|
|
21
|
-
### Method 1: Using Phone Number or Email
|
|
22
|
-
|
|
23
|
-
```applescript
|
|
24
|
-
tell application "Messages"
|
|
25
|
-
set targetService to 1st service whose service type = iMessage
|
|
26
|
-
set targetBuddy to buddy "+1234567890" of targetService
|
|
27
|
-
send "Hello!" to targetBuddy
|
|
28
|
-
end tell
|
|
29
|
-
```
|
|
30
|
-
|
|
31
|
-
### Method 2: Using Existing Conversation
|
|
32
|
-
|
|
33
|
-
```applescript
|
|
34
|
-
tell application "Messages"
|
|
35
|
-
set targetChat to chat "Contact Name"
|
|
36
|
-
send "Hello!" to targetChat
|
|
37
|
-
end tell
|
|
38
|
-
```
|
|
39
|
-
|
|
40
|
-
### Method 3: Via System Events (More Reliable)
|
|
41
|
-
|
|
42
|
-
```applescript
|
|
43
|
-
tell application "Messages"
|
|
44
|
-
activate
|
|
45
|
-
end tell
|
|
46
|
-
|
|
47
|
-
tell application "System Events"
|
|
48
|
-
tell process "Messages"
|
|
49
|
-
-- Start new conversation
|
|
50
|
-
keystroke "n" using command down
|
|
51
|
-
delay 0.3
|
|
52
|
-
|
|
53
|
-
-- Enter recipient
|
|
54
|
-
keystroke "+1234567890"
|
|
55
|
-
delay 0.3
|
|
56
|
-
keystroke return
|
|
57
|
-
delay 0.3
|
|
58
|
-
|
|
59
|
-
-- Type message
|
|
60
|
-
keystroke "Hello, this is a test message!"
|
|
61
|
-
delay 0.2
|
|
62
|
-
|
|
63
|
-
-- Send
|
|
64
|
-
keystroke return
|
|
65
|
-
end tell
|
|
66
|
-
end tell
|
|
67
|
-
```
|
|
68
|
-
|
|
69
|
-
## Common Tasks
|
|
70
|
-
|
|
71
|
-
### "Send a message to John saying I'll be late"
|
|
72
|
-
|
|
73
|
-
```applescript
|
|
74
|
-
tell application "Messages"
|
|
75
|
-
activate
|
|
76
|
-
set targetService to 1st service whose service type = iMessage
|
|
77
|
-
-- Replace with actual contact
|
|
78
|
-
set targetBuddy to buddy "john@email.com" of targetService
|
|
79
|
-
send "I'll be late" to targetBuddy
|
|
80
|
-
end tell
|
|
81
|
-
```
|
|
82
|
-
|
|
83
|
-
### "Open Messages"
|
|
84
|
-
|
|
85
|
-
```applescript
|
|
86
|
-
tell application "Messages" to activate
|
|
87
|
-
```
|
|
88
|
-
|
|
89
|
-
### "Read my latest message"
|
|
90
|
-
|
|
91
|
-
```applescript
|
|
92
|
-
tell application "Messages"
|
|
93
|
-
set latestChat to chat 1
|
|
94
|
-
set lastMessage to last message of latestChat
|
|
95
|
-
return text of lastMessage
|
|
96
|
-
end tell
|
|
97
|
-
```
|
|
98
|
-
|
|
99
|
-
## Important Notes
|
|
100
|
-
|
|
101
|
-
1. **Contact Format**: Use phone number (+1234567890) or email (user@icloud.com)
|
|
102
|
-
2. **Confirmation Required**: Always confirm before sending messages
|
|
103
|
-
3. **Privacy**: Never read or send messages without explicit user consent
|
|
104
|
-
4. **iMessage vs SMS**: The script auto-detects based on contact's Apple ID status
|
|
105
|
-
|
|
106
|
-
## Error Handling
|
|
107
|
-
|
|
108
|
-
If buddy not found:
|
|
109
|
-
|
|
110
|
-
```applescript
|
|
111
|
-
try
|
|
112
|
-
tell application "Messages"
|
|
113
|
-
set targetService to 1st service whose service type = iMessage
|
|
114
|
-
set targetBuddy to buddy "+1234567890" of targetService
|
|
115
|
-
send "Message" to targetBuddy
|
|
116
|
-
end tell
|
|
117
|
-
on error errMsg
|
|
118
|
-
-- Fallback: Try SMS service
|
|
119
|
-
tell application "Messages"
|
|
120
|
-
set targetService to 1st service whose service type = SMS
|
|
121
|
-
set targetBuddy to buddy "+1234567890" of targetService
|
|
122
|
-
send "Message" to targetBuddy
|
|
123
|
-
end tell
|
|
124
|
-
end try
|
|
125
|
-
```
|
|
@@ -1,71 +0,0 @@
|
|
|
1
|
-
---
|
|
2
|
-
name: openai-image-gen
|
|
3
|
-
description: Batch-generate images via OpenAI Images API. Random prompt sampler + `index.html` gallery.
|
|
4
|
-
homepage: https://platform.openai.com/docs/api-reference/images
|
|
5
|
-
metadata: {"lemonade":{"emoji":"🖼️","requires":{"bins":["python3"]},"install":[{"id":"python-brew","kind":"brew","formula":"python","bins":["python3"],"label":"Install Python (brew)"}]}}
|
|
6
|
-
---
|
|
7
|
-
|
|
8
|
-
# OpenAI Image Gen
|
|
9
|
-
|
|
10
|
-
Generate a handful of “random but structured” prompts and render them via the OpenAI Images API.
|
|
11
|
-
|
|
12
|
-
## Run
|
|
13
|
-
|
|
14
|
-
```bash
|
|
15
|
-
python3 {baseDir}/scripts/gen.py
|
|
16
|
-
open ~/Projects/tmp/openai-image-gen-*/index.html # if ~/Projects/tmp exists; else ./tmp/...
|
|
17
|
-
```
|
|
18
|
-
|
|
19
|
-
Useful flags:
|
|
20
|
-
|
|
21
|
-
```bash
|
|
22
|
-
# GPT image models with various options
|
|
23
|
-
python3 {baseDir}/scripts/gen.py --count 16 --model gpt-image-1
|
|
24
|
-
python3 {baseDir}/scripts/gen.py --prompt "ultra-detailed studio photo of a lobster astronaut" --count 4
|
|
25
|
-
python3 {baseDir}/scripts/gen.py --size 1536x1024 --quality high --out-dir ./out/images
|
|
26
|
-
python3 {baseDir}/scripts/gen.py --model gpt-image-1.5 --background transparent --output-format webp
|
|
27
|
-
|
|
28
|
-
# DALL-E 3 (note: count is automatically limited to 1)
|
|
29
|
-
python3 {baseDir}/scripts/gen.py --model dall-e-3 --quality hd --size 1792x1024 --style vivid
|
|
30
|
-
python3 {baseDir}/scripts/gen.py --model dall-e-3 --style natural --prompt "serene mountain landscape"
|
|
31
|
-
|
|
32
|
-
# DALL-E 2
|
|
33
|
-
python3 {baseDir}/scripts/gen.py --model dall-e-2 --size 512x512 --count 4
|
|
34
|
-
```
|
|
35
|
-
|
|
36
|
-
## Model-Specific Parameters
|
|
37
|
-
|
|
38
|
-
Different models support different parameter values. The script automatically selects appropriate defaults based on the model.
|
|
39
|
-
|
|
40
|
-
### Size
|
|
41
|
-
|
|
42
|
-
- **GPT image models** (`gpt-image-1`, `gpt-image-1-mini`, `gpt-image-1.5`): `1024x1024`, `1536x1024` (landscape), `1024x1536` (portrait), or `auto`
|
|
43
|
-
- Default: `1024x1024`
|
|
44
|
-
- **dall-e-3**: `1024x1024`, `1792x1024`, or `1024x1792`
|
|
45
|
-
- Default: `1024x1024`
|
|
46
|
-
- **dall-e-2**: `256x256`, `512x512`, or `1024x1024`
|
|
47
|
-
- Default: `1024x1024`
|
|
48
|
-
|
|
49
|
-
### Quality
|
|
50
|
-
|
|
51
|
-
- **GPT image models**: `auto`, `high`, `medium`, or `low`
|
|
52
|
-
- Default: `high`
|
|
53
|
-
- **dall-e-3**: `hd` or `standard`
|
|
54
|
-
- Default: `standard`
|
|
55
|
-
- **dall-e-2**: `standard` only
|
|
56
|
-
- Default: `standard`
|
|
57
|
-
|
|
58
|
-
### Other Notable Differences
|
|
59
|
-
|
|
60
|
-
- **dall-e-3** only supports generating 1 image at a time (`n=1`). The script automatically limits count to 1 when using this model.
|
|
61
|
-
- **GPT image models** support additional parameters:
|
|
62
|
-
- `--background`: `transparent`, `opaque`, or `auto` (default)
|
|
63
|
-
- `--output-format`: `png` (default), `jpeg`, or `webp`
|
|
64
|
-
- Note: `stream` and `moderation` are available via API but not yet implemented in this script
|
|
65
|
-
- **dall-e-3** has a `--style` parameter: `vivid` (hyper-real, dramatic) or `natural` (more natural looking)
|
|
66
|
-
|
|
67
|
-
## Output
|
|
68
|
-
|
|
69
|
-
- `*.png`, `*.jpeg`, or `*.webp` images (output format depends on model + `--output-format`)
|
|
70
|
-
- `prompts.json` (prompt → file mapping)
|
|
71
|
-
- `index.html` (thumbnail gallery)
|