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