@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.
Files changed (28) hide show
  1. package/dist/agents/system-prompt.js +6 -1
  2. package/dist/build-info.json +3 -3
  3. package/dist/canvas-host/a2ui/.bundle.hash +1 -1
  4. package/package.json +1 -1
  5. package/skills/apple-notes/SKILL.md +0 -50
  6. package/skills/apple-reminders/SKILL.md +0 -67
  7. package/skills/goplaces/SKILL.md +0 -30
  8. package/skills/local-places/SERVER_README.md +0 -101
  9. package/skills/local-places/SKILL.md +0 -91
  10. package/skills/local-places/pyproject.toml +0 -27
  11. package/skills/local-places/src/local_places/__init__.py +0 -2
  12. package/skills/local-places/src/local_places/google_places.py +0 -314
  13. package/skills/local-places/src/local_places/main.py +0 -65
  14. package/skills/local-places/src/local_places/schemas.py +0 -107
  15. package/skills/messages/SKILL.md +0 -125
  16. package/skills/openai-image-gen/SKILL.md +0 -71
  17. package/skills/openai-image-gen/scripts/gen.py +0 -255
  18. package/skills/ordercli/SKILL.md +0 -47
  19. package/skills/spotify-player/SKILL.md +0 -38
  20. package/skills/youtube-watcher/SKILL.md +0 -51
  21. package/skills/youtube-watcher/scripts/get_transcript.py +0 -81
  22. /package/skills/eightctl/{SKILL.md → SKILL.md.disabled} +0 -0
  23. /package/skills/nano-banana-pro/{SKILL.md → SKILL.md.disabled} +0 -0
  24. /package/skills/openai-whisper-api/{SKILL.md → SKILL.md.disabled} +0 -0
  25. /package/skills/openhue/{SKILL.md → SKILL.md.disabled} +0 -0
  26. /package/skills/sag/{SKILL.md → SKILL.md.disabled} +0 -0
  27. /package/skills/sherpa-onnx-tts/{SKILL.md → SKILL.md.disabled} +0 -0
  28. /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
@@ -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)