@heylemon/lemonade 0.2.3 → 0.2.5
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/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/brave-search/SKILL.md +0 -57
- package/skills/brave-search/content.js +0 -86
- package/skills/brave-search/package.json +0 -14
- package/skills/brave-search/search.js +0 -179
- package/skills/caldav-calendar/SKILL.md +0 -104
- 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/tavily-search/SKILL.md +0 -38
- package/skills/tavily-search/scripts/extract.mjs +0 -59
- package/skills/tavily-search/scripts/search.mjs +0 -101
- package/skills/youtube-watcher/SKILL.md +0 -46
- 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
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)
|
|
@@ -1,255 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env python3
|
|
2
|
-
import argparse
|
|
3
|
-
import base64
|
|
4
|
-
import datetime as dt
|
|
5
|
-
import json
|
|
6
|
-
import os
|
|
7
|
-
import random
|
|
8
|
-
import re
|
|
9
|
-
import sys
|
|
10
|
-
import urllib.error
|
|
11
|
-
import urllib.request
|
|
12
|
-
from pathlib import Path
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
def slugify(text: str) -> str:
|
|
16
|
-
text = text.lower().strip()
|
|
17
|
-
text = re.sub(r"[^a-z0-9]+", "-", text)
|
|
18
|
-
text = re.sub(r"-{2,}", "-", text).strip("-")
|
|
19
|
-
return text or "image"
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
def default_out_dir() -> Path:
|
|
23
|
-
now = dt.datetime.now().strftime("%Y-%m-%d-%H-%M-%S")
|
|
24
|
-
preferred = Path.home() / "Projects" / "tmp"
|
|
25
|
-
base = preferred if preferred.is_dir() else Path("./tmp")
|
|
26
|
-
base.mkdir(parents=True, exist_ok=True)
|
|
27
|
-
return base / f"openai-image-gen-{now}"
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
def pick_prompts(count: int) -> list[str]:
|
|
31
|
-
subjects = [
|
|
32
|
-
"a lobster astronaut",
|
|
33
|
-
"a brutalist lighthouse",
|
|
34
|
-
"a cozy reading nook",
|
|
35
|
-
"a cyberpunk noodle shop",
|
|
36
|
-
"a Vienna street at dusk",
|
|
37
|
-
"a minimalist product photo",
|
|
38
|
-
"a surreal underwater library",
|
|
39
|
-
]
|
|
40
|
-
styles = [
|
|
41
|
-
"ultra-detailed studio photo",
|
|
42
|
-
"35mm film still",
|
|
43
|
-
"isometric illustration",
|
|
44
|
-
"editorial photography",
|
|
45
|
-
"soft watercolor",
|
|
46
|
-
"architectural render",
|
|
47
|
-
"high-contrast monochrome",
|
|
48
|
-
]
|
|
49
|
-
lighting = [
|
|
50
|
-
"golden hour",
|
|
51
|
-
"overcast soft light",
|
|
52
|
-
"neon lighting",
|
|
53
|
-
"dramatic rim light",
|
|
54
|
-
"candlelight",
|
|
55
|
-
"foggy atmosphere",
|
|
56
|
-
]
|
|
57
|
-
prompts: list[str] = []
|
|
58
|
-
for _ in range(count):
|
|
59
|
-
prompts.append(
|
|
60
|
-
f"{random.choice(styles)} of {random.choice(subjects)}, {random.choice(lighting)}"
|
|
61
|
-
)
|
|
62
|
-
return prompts
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
def get_model_defaults(model: str) -> tuple[str, str]:
|
|
66
|
-
"""Return (default_size, default_quality) for the given model."""
|
|
67
|
-
if model == "dall-e-2":
|
|
68
|
-
# quality will be ignored
|
|
69
|
-
return ("1024x1024", "standard")
|
|
70
|
-
elif model == "dall-e-3":
|
|
71
|
-
return ("1024x1024", "standard")
|
|
72
|
-
else:
|
|
73
|
-
# GPT image or future models
|
|
74
|
-
return ("1024x1024", "high")
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
def resolve_api_url_and_key() -> tuple[str, str]:
|
|
78
|
-
"""Resolve the images API base URL and auth token.
|
|
79
|
-
Prefers the Lemonade backend proxy; falls back to direct OpenAI key."""
|
|
80
|
-
backend = (os.environ.get("LEMON_BACKEND_URL") or "").strip()
|
|
81
|
-
gw_token = (os.environ.get("GATEWAY_TOKEN") or "").strip()
|
|
82
|
-
if backend and gw_token:
|
|
83
|
-
return f"{backend}/api/lemonade/proxy/v1/images/generations", gw_token
|
|
84
|
-
api_key = (os.environ.get("OPENAI_API_KEY") or "").strip()
|
|
85
|
-
if api_key:
|
|
86
|
-
return "https://api.openai.com/v1/images/generations", api_key
|
|
87
|
-
return "", ""
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
def request_images(
|
|
91
|
-
api_key: str,
|
|
92
|
-
prompt: str,
|
|
93
|
-
model: str,
|
|
94
|
-
size: str,
|
|
95
|
-
quality: str,
|
|
96
|
-
background: str = "",
|
|
97
|
-
output_format: str = "",
|
|
98
|
-
style: str = "",
|
|
99
|
-
api_url: str = "",
|
|
100
|
-
) -> dict:
|
|
101
|
-
url = api_url or "https://api.openai.com/v1/images/generations"
|
|
102
|
-
args = {
|
|
103
|
-
"model": model,
|
|
104
|
-
"prompt": prompt,
|
|
105
|
-
"size": size,
|
|
106
|
-
"n": 1,
|
|
107
|
-
}
|
|
108
|
-
|
|
109
|
-
# Quality parameter - dall-e-2 doesn't accept this parameter
|
|
110
|
-
if model != "dall-e-2":
|
|
111
|
-
args["quality"] = quality
|
|
112
|
-
|
|
113
|
-
# Note: response_format no longer supported by OpenAI Images API
|
|
114
|
-
# dall-e models now return URLs by default
|
|
115
|
-
|
|
116
|
-
if model.startswith("gpt-image"):
|
|
117
|
-
if background:
|
|
118
|
-
args["background"] = background
|
|
119
|
-
if output_format:
|
|
120
|
-
args["output_format"] = output_format
|
|
121
|
-
|
|
122
|
-
if model == "dall-e-3" and style:
|
|
123
|
-
args["style"] = style
|
|
124
|
-
|
|
125
|
-
body = json.dumps(args).encode("utf-8")
|
|
126
|
-
req = urllib.request.Request(
|
|
127
|
-
url,
|
|
128
|
-
method="POST",
|
|
129
|
-
headers={
|
|
130
|
-
"Authorization": f"Bearer {api_key}",
|
|
131
|
-
"Content-Type": "application/json",
|
|
132
|
-
},
|
|
133
|
-
data=body,
|
|
134
|
-
)
|
|
135
|
-
try:
|
|
136
|
-
with urllib.request.urlopen(req, timeout=300) as resp:
|
|
137
|
-
return json.loads(resp.read().decode("utf-8"))
|
|
138
|
-
except urllib.error.HTTPError as e:
|
|
139
|
-
payload = e.read().decode("utf-8", errors="replace")
|
|
140
|
-
raise RuntimeError(f"OpenAI Images API failed ({e.code}): {payload}") from e
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
def write_gallery(out_dir: Path, items: list[dict]) -> None:
|
|
144
|
-
thumbs = "\n".join(
|
|
145
|
-
[
|
|
146
|
-
f"""
|
|
147
|
-
<figure>
|
|
148
|
-
<a href="{it["file"]}"><img src="{it["file"]}" loading="lazy" /></a>
|
|
149
|
-
<figcaption>{it["prompt"]}</figcaption>
|
|
150
|
-
</figure>
|
|
151
|
-
""".strip()
|
|
152
|
-
for it in items
|
|
153
|
-
]
|
|
154
|
-
)
|
|
155
|
-
html = f"""<!doctype html>
|
|
156
|
-
<meta charset="utf-8" />
|
|
157
|
-
<title>openai-image-gen</title>
|
|
158
|
-
<style>
|
|
159
|
-
:root {{ color-scheme: dark; }}
|
|
160
|
-
body {{ margin: 24px; font: 14px/1.4 ui-sans-serif, system-ui; background: #0b0f14; color: #e8edf2; }}
|
|
161
|
-
h1 {{ font-size: 18px; margin: 0 0 16px; }}
|
|
162
|
-
.grid {{ display: grid; grid-template-columns: repeat(auto-fill, minmax(240px, 1fr)); gap: 16px; }}
|
|
163
|
-
figure {{ margin: 0; padding: 12px; border: 1px solid #1e2a36; border-radius: 14px; background: #0f1620; }}
|
|
164
|
-
img {{ width: 100%; height: auto; border-radius: 10px; display: block; }}
|
|
165
|
-
figcaption {{ margin-top: 10px; color: #b7c2cc; }}
|
|
166
|
-
code {{ color: #9cd1ff; }}
|
|
167
|
-
</style>
|
|
168
|
-
<h1>openai-image-gen</h1>
|
|
169
|
-
<p>Output: <code>{out_dir.as_posix()}</code></p>
|
|
170
|
-
<div class="grid">
|
|
171
|
-
{thumbs}
|
|
172
|
-
</div>
|
|
173
|
-
"""
|
|
174
|
-
(out_dir / "index.html").write_text(html, encoding="utf-8")
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
def main() -> int:
|
|
178
|
-
ap = argparse.ArgumentParser(description="Generate images via OpenAI Images API.")
|
|
179
|
-
ap.add_argument("--prompt", help="Single prompt. If omitted, random prompts are generated.")
|
|
180
|
-
ap.add_argument("--count", type=int, default=8, help="How many images to generate.")
|
|
181
|
-
ap.add_argument("--model", default="gpt-image-1", help="Image model id.")
|
|
182
|
-
ap.add_argument("--size", default="", help="Image size (e.g. 1024x1024, 1536x1024). Defaults based on model if not specified.")
|
|
183
|
-
ap.add_argument("--quality", default="", help="Image quality (e.g. high, standard). Defaults based on model if not specified.")
|
|
184
|
-
ap.add_argument("--background", default="", help="Background transparency (GPT models only): transparent, opaque, or auto.")
|
|
185
|
-
ap.add_argument("--output-format", default="", help="Output format (GPT models only): png, jpeg, or webp.")
|
|
186
|
-
ap.add_argument("--style", default="", help="Image style (dall-e-3 only): vivid or natural.")
|
|
187
|
-
ap.add_argument("--out-dir", default="", help="Output directory (default: ./tmp/openai-image-gen-<ts>).")
|
|
188
|
-
args = ap.parse_args()
|
|
189
|
-
|
|
190
|
-
api_url, api_key = resolve_api_url_and_key()
|
|
191
|
-
if not api_key:
|
|
192
|
-
print("No API credentials available for image generation", file=sys.stderr)
|
|
193
|
-
return 2
|
|
194
|
-
|
|
195
|
-
# Apply model-specific defaults if not specified
|
|
196
|
-
default_size, default_quality = get_model_defaults(args.model)
|
|
197
|
-
size = args.size or default_size
|
|
198
|
-
quality = args.quality or default_quality
|
|
199
|
-
|
|
200
|
-
count = args.count
|
|
201
|
-
if args.model == "dall-e-3" and count > 1:
|
|
202
|
-
print(f"Warning: dall-e-3 only supports generating 1 image at a time. Reducing count from {count} to 1.", file=sys.stderr)
|
|
203
|
-
count = 1
|
|
204
|
-
|
|
205
|
-
out_dir = Path(args.out_dir).expanduser() if args.out_dir else default_out_dir()
|
|
206
|
-
out_dir.mkdir(parents=True, exist_ok=True)
|
|
207
|
-
|
|
208
|
-
prompts = [args.prompt] * count if args.prompt else pick_prompts(count)
|
|
209
|
-
|
|
210
|
-
# Determine file extension based on output format
|
|
211
|
-
if args.model.startswith("gpt-image") and args.output_format:
|
|
212
|
-
file_ext = args.output_format
|
|
213
|
-
else:
|
|
214
|
-
file_ext = "png"
|
|
215
|
-
|
|
216
|
-
items: list[dict] = []
|
|
217
|
-
for idx, prompt in enumerate(prompts, start=1):
|
|
218
|
-
print(f"[{idx}/{len(prompts)}] {prompt}")
|
|
219
|
-
res = request_images(
|
|
220
|
-
api_key,
|
|
221
|
-
prompt,
|
|
222
|
-
args.model,
|
|
223
|
-
size,
|
|
224
|
-
quality,
|
|
225
|
-
args.background,
|
|
226
|
-
args.output_format,
|
|
227
|
-
args.style,
|
|
228
|
-
api_url=api_url,
|
|
229
|
-
)
|
|
230
|
-
data = res.get("data", [{}])[0]
|
|
231
|
-
image_b64 = data.get("b64_json")
|
|
232
|
-
image_url = data.get("url")
|
|
233
|
-
if not image_b64 and not image_url:
|
|
234
|
-
raise RuntimeError(f"Unexpected response: {json.dumps(res)[:400]}")
|
|
235
|
-
|
|
236
|
-
filename = f"{idx:03d}-{slugify(prompt)[:40]}.{file_ext}"
|
|
237
|
-
filepath = out_dir / filename
|
|
238
|
-
if image_b64:
|
|
239
|
-
filepath.write_bytes(base64.b64decode(image_b64))
|
|
240
|
-
else:
|
|
241
|
-
try:
|
|
242
|
-
urllib.request.urlretrieve(image_url, filepath)
|
|
243
|
-
except urllib.error.URLError as e:
|
|
244
|
-
raise RuntimeError(f"Failed to download image from {image_url}: {e}") from e
|
|
245
|
-
|
|
246
|
-
items.append({"prompt": prompt, "file": filename})
|
|
247
|
-
|
|
248
|
-
(out_dir / "prompts.json").write_text(json.dumps(items, indent=2), encoding="utf-8")
|
|
249
|
-
write_gallery(out_dir, items)
|
|
250
|
-
print(f"\nWrote: {(out_dir / 'index.html').as_posix()}")
|
|
251
|
-
return 0
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
if __name__ == "__main__":
|
|
255
|
-
raise SystemExit(main())
|
package/skills/ordercli/SKILL.md
DELETED
|
@@ -1,47 +0,0 @@
|
|
|
1
|
-
---
|
|
2
|
-
name: ordercli
|
|
3
|
-
description: Foodora-only CLI for checking past orders and active order status (Deliveroo WIP).
|
|
4
|
-
homepage: https://ordercli.sh
|
|
5
|
-
metadata: {"lemonade":{"emoji":"🛵","requires":{"bins":["ordercli"]},"install":[{"id":"brew","kind":"brew","formula":"steipete/tap/ordercli","bins":["ordercli"],"label":"Install ordercli (brew)"},{"id":"go","kind":"go","module":"github.com/steipete/ordercli/cmd/ordercli@latest","bins":["ordercli"],"label":"Install ordercli (go)"}]}}
|
|
6
|
-
---
|
|
7
|
-
|
|
8
|
-
# ordercli
|
|
9
|
-
|
|
10
|
-
Use `ordercli` to check past orders and track active order status (Foodora only right now).
|
|
11
|
-
|
|
12
|
-
Quick start (Foodora)
|
|
13
|
-
- `ordercli foodora countries`
|
|
14
|
-
- `ordercli foodora config set --country AT`
|
|
15
|
-
- `ordercli foodora login --email you@example.com --password-stdin`
|
|
16
|
-
- `ordercli foodora orders`
|
|
17
|
-
- `ordercli foodora history --limit 20`
|
|
18
|
-
- `ordercli foodora history show <orderCode>`
|
|
19
|
-
|
|
20
|
-
Orders
|
|
21
|
-
- Active list (arrival/status): `ordercli foodora orders`
|
|
22
|
-
- Watch: `ordercli foodora orders --watch`
|
|
23
|
-
- Active order detail: `ordercli foodora order <orderCode>`
|
|
24
|
-
- History detail JSON: `ordercli foodora history show <orderCode> --json`
|
|
25
|
-
|
|
26
|
-
Reorder (adds to cart)
|
|
27
|
-
- Preview: `ordercli foodora reorder <orderCode>`
|
|
28
|
-
- Confirm: `ordercli foodora reorder <orderCode> --confirm`
|
|
29
|
-
- Address: `ordercli foodora reorder <orderCode> --confirm --address-id <id>`
|
|
30
|
-
|
|
31
|
-
Cloudflare / bot protection
|
|
32
|
-
- Browser login: `ordercli foodora login --email you@example.com --password-stdin --browser`
|
|
33
|
-
- Reuse profile: `--browser-profile "$HOME/Library/Application Support/ordercli/browser-profile"`
|
|
34
|
-
- Import Chrome cookies: `ordercli foodora cookies chrome --profile "Default"`
|
|
35
|
-
|
|
36
|
-
Session import (no password)
|
|
37
|
-
- `ordercli foodora session chrome --url https://www.foodora.at/ --profile "Default"`
|
|
38
|
-
- `ordercli foodora session refresh --client-id android`
|
|
39
|
-
|
|
40
|
-
Deliveroo (WIP, not working yet)
|
|
41
|
-
- Requires `DELIVEROO_BEARER_TOKEN` (optional `DELIVEROO_COOKIE`).
|
|
42
|
-
- `ordercli deliveroo config set --market uk`
|
|
43
|
-
- `ordercli deliveroo history`
|
|
44
|
-
|
|
45
|
-
Notes
|
|
46
|
-
- Use `--config /tmp/ordercli.json` for testing.
|
|
47
|
-
- Confirm before any reorder or cart-changing action.
|
|
@@ -1,38 +0,0 @@
|
|
|
1
|
-
---
|
|
2
|
-
name: spotify-player
|
|
3
|
-
description: Terminal Spotify playback/search via spogo (preferred) or spotify_player.
|
|
4
|
-
homepage: https://www.spotify.com
|
|
5
|
-
metadata: {"lemonade":{"emoji":"🎵","requires":{"anyBins":["spogo","spotify_player"]},"install":[{"id":"brew","kind":"brew","formula":"spogo","tap":"steipete/tap","bins":["spogo"],"label":"Install spogo (brew)"},{"id":"brew","kind":"brew","formula":"spotify_player","bins":["spotify_player"],"label":"Install spotify_player (brew)"}]}}
|
|
6
|
-
---
|
|
7
|
-
|
|
8
|
-
# spogo / spotify_player (SECONDARY — prefer AppleScript)
|
|
9
|
-
|
|
10
|
-
**Default method: AppleScript** (see spotify skill). Use AppleScript to control Spotify unless the user specifically asks for CLI-based control or AppleScript is unavailable.
|
|
11
|
-
|
|
12
|
-
This CLI skill is an **alternative** for users who prefer terminal-based Spotify control.
|
|
13
|
-
|
|
14
|
-
**Never use the browser for Spotify.** Do not open open.spotify.com or use browser tools for playback.
|
|
15
|
-
|
|
16
|
-
Requirements
|
|
17
|
-
- Spotify Premium account.
|
|
18
|
-
- Either `spogo` or `spotify_player` installed.
|
|
19
|
-
|
|
20
|
-
spogo setup
|
|
21
|
-
- Import cookies: `spogo auth import --browser chrome`
|
|
22
|
-
|
|
23
|
-
Common CLI commands
|
|
24
|
-
- Search: `spogo search track "query"`
|
|
25
|
-
- Playback: `spogo play|pause|next|prev`
|
|
26
|
-
- Devices: `spogo device list`, `spogo device set "<name|id>"`
|
|
27
|
-
- Status: `spogo status`
|
|
28
|
-
|
|
29
|
-
spotify_player commands (fallback)
|
|
30
|
-
- Search: `spotify_player search "query"`
|
|
31
|
-
- Playback: `spotify_player playback play|pause|next|previous`
|
|
32
|
-
- Connect device: `spotify_player connect`
|
|
33
|
-
- Like track: `spotify_player like`
|
|
34
|
-
|
|
35
|
-
Notes
|
|
36
|
-
- Config folder: `~/.config/spotify-player` (e.g., `app.toml`).
|
|
37
|
-
- For Spotify Connect integration, set a user `client_id` in config.
|
|
38
|
-
- TUI shortcuts are available via `?` in the app.
|
|
@@ -1,38 +0,0 @@
|
|
|
1
|
-
---
|
|
2
|
-
name: tavily
|
|
3
|
-
description: AI-optimized web search via Tavily API. Returns concise, relevant results for AI agents.
|
|
4
|
-
homepage: https://tavily.com
|
|
5
|
-
metadata: {"lemonade":{"emoji":"🔍","requires":{"bins":["node"],"env":["TAVILY_API_KEY"]},"primaryEnv":"TAVILY_API_KEY"}}
|
|
6
|
-
---
|
|
7
|
-
|
|
8
|
-
# Tavily Search
|
|
9
|
-
|
|
10
|
-
AI-optimized web search using Tavily API. Designed for AI agents - returns clean, relevant content.
|
|
11
|
-
|
|
12
|
-
## Search
|
|
13
|
-
|
|
14
|
-
```bash
|
|
15
|
-
node {baseDir}/scripts/search.mjs "query"
|
|
16
|
-
node {baseDir}/scripts/search.mjs "query" -n 10
|
|
17
|
-
node {baseDir}/scripts/search.mjs "query" --deep
|
|
18
|
-
node {baseDir}/scripts/search.mjs "query" --topic news
|
|
19
|
-
```
|
|
20
|
-
|
|
21
|
-
## Options
|
|
22
|
-
|
|
23
|
-
- `-n <count>`: Number of results (default: 5, max: 20)
|
|
24
|
-
- `--deep`: Use advanced search for deeper research (slower, more comprehensive)
|
|
25
|
-
- `--topic <type>`: Search topic - `general` (default) or `news`
|
|
26
|
-
- `--days <n>`: For news topic, limit to last n days
|
|
27
|
-
|
|
28
|
-
## Extract content from URL
|
|
29
|
-
|
|
30
|
-
```bash
|
|
31
|
-
node {baseDir}/scripts/extract.mjs "https://example.com/article"
|
|
32
|
-
```
|
|
33
|
-
|
|
34
|
-
Notes:
|
|
35
|
-
- Needs `TAVILY_API_KEY` from https://tavily.com
|
|
36
|
-
- Tavily is optimized for AI - returns clean, relevant snippets
|
|
37
|
-
- Use `--deep` for complex research questions
|
|
38
|
-
- Use `--topic news` for current events
|
|
@@ -1,59 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
2
|
-
|
|
3
|
-
function usage() {
|
|
4
|
-
console.error(`Usage: extract.mjs "url1" ["url2" ...]`);
|
|
5
|
-
process.exit(2);
|
|
6
|
-
}
|
|
7
|
-
|
|
8
|
-
const args = process.argv.slice(2);
|
|
9
|
-
if (args.length === 0 || args[0] === "-h" || args[0] === "--help") usage();
|
|
10
|
-
|
|
11
|
-
const urls = args.filter(a => !a.startsWith("-"));
|
|
12
|
-
|
|
13
|
-
if (urls.length === 0) {
|
|
14
|
-
console.error("No URLs provided");
|
|
15
|
-
usage();
|
|
16
|
-
}
|
|
17
|
-
|
|
18
|
-
const apiKey = (process.env.TAVILY_API_KEY ?? "").trim();
|
|
19
|
-
if (!apiKey) {
|
|
20
|
-
console.error("Missing TAVILY_API_KEY");
|
|
21
|
-
process.exit(1);
|
|
22
|
-
}
|
|
23
|
-
|
|
24
|
-
const resp = await fetch("https://api.tavily.com/extract", {
|
|
25
|
-
method: "POST",
|
|
26
|
-
headers: {
|
|
27
|
-
"Content-Type": "application/json",
|
|
28
|
-
},
|
|
29
|
-
body: JSON.stringify({
|
|
30
|
-
api_key: apiKey,
|
|
31
|
-
urls: urls,
|
|
32
|
-
}),
|
|
33
|
-
});
|
|
34
|
-
|
|
35
|
-
if (!resp.ok) {
|
|
36
|
-
const text = await resp.text().catch(() => "");
|
|
37
|
-
throw new Error(`Tavily Extract failed (${resp.status}): ${text}`);
|
|
38
|
-
}
|
|
39
|
-
|
|
40
|
-
const data = await resp.json();
|
|
41
|
-
|
|
42
|
-
const results = data.results ?? [];
|
|
43
|
-
const failed = data.failed_results ?? [];
|
|
44
|
-
|
|
45
|
-
for (const r of results) {
|
|
46
|
-
const url = String(r?.url ?? "").trim();
|
|
47
|
-
const content = String(r?.raw_content ?? "").trim();
|
|
48
|
-
|
|
49
|
-
console.log(`# ${url}\n`);
|
|
50
|
-
console.log(content || "(no content extracted)");
|
|
51
|
-
console.log("\n---\n");
|
|
52
|
-
}
|
|
53
|
-
|
|
54
|
-
if (failed.length > 0) {
|
|
55
|
-
console.log("## Failed URLs\n");
|
|
56
|
-
for (const f of failed) {
|
|
57
|
-
console.log(`- ${f.url}: ${f.error}`);
|
|
58
|
-
}
|
|
59
|
-
}
|