@heylemon/lemonade 0.2.1 → 0.2.3

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.
@@ -429,6 +429,15 @@ export function buildAgentSystemPrompt(params) {
429
429
  "- If files must be written, use `mktemp -d` and clean up after (`rm -rf $TMPDIR`).",
430
430
  "- Skill-provided scripts (docx, xlsx, etc.) are fine to use as-is.",
431
431
  "",
432
+ "## User-Facing Communication",
433
+ "NEVER reveal internal implementation details to the user. This includes:",
434
+ "- File paths like `~/.lemonade/`, `lemonade.json`, `.env`, `auth-profiles.json`",
435
+ "- API key names like `OPENAI_API_KEY`, `ANTHROPIC_API_KEY`, `GEMINI_API_KEY`",
436
+ "- Environment variable names, config file locations, or setup instructions",
437
+ "- References to the gateway, proxy, providers, or model routing internals",
438
+ "- Skill names, skill directories, or SKILL.md files",
439
+ 'If a capability is unavailable, say something like "I can\'t do that right now" or suggest an alternative. Never ask the user to configure keys, edit config files, or troubleshoot infrastructure.',
440
+ "",
432
441
  "## Cron / Reminders",
433
442
  "ALWAYS use the `cron` tool (action: add/update/remove/list/run/status) for managing cron jobs and reminders.",
434
443
  "NEVER use `exec` or shell commands to read/write cron files (e.g. ~/.lemonade/cron/jobs.json) directly — the gateway cron service will not detect the change.",
@@ -1,5 +1,5 @@
1
1
  {
2
- "version": "0.2.1",
3
- "commit": "4fb691db7cce1151b7e7b56b76b337a9409ac009",
4
- "builtAt": "2026-02-22T12:34:47.819Z"
2
+ "version": "0.2.3",
3
+ "commit": "a0535878314984aba3836015e906997cadb9bb7f",
4
+ "builtAt": "2026-02-22T15:12:12.410Z"
5
5
  }
@@ -1 +1 @@
1
- 8fcbb831164907bf1103be7d74c1e028f9d54dadb32f21942d50feb871d2fe18
1
+ dda15b99462cc0b2ef3beeaf3b47c8ead319daf27fd6466820c14e02ea7b21a3
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@heylemon/lemonade",
3
- "version": "0.2.1",
3
+ "version": "0.2.3",
4
4
  "description": "AI gateway CLI for Lemon - local AI assistant with integrations",
5
5
  "publishConfig": {
6
6
  "access": "restricted"
@@ -0,0 +1,57 @@
1
+ ---
2
+ name: brave-search
3
+ description: Web search and content extraction via Brave Search API. Use for searching documentation, facts, or any web content. Lightweight, no browser required.
4
+ metadata: {"lemonade":{"emoji":"🦁","requires":{"bins":["node"]}}}
5
+ ---
6
+
7
+ # Brave Search
8
+
9
+ Headless web search and content extraction using Brave Search. No browser required.
10
+
11
+ ## Setup
12
+
13
+ Run once before first use:
14
+
15
+ ```bash
16
+ cd {baseDir}
17
+ npm ci
18
+ ```
19
+
20
+ ## Search
21
+
22
+ ```bash
23
+ node {baseDir}/search.js "query" # Basic search (5 results)
24
+ node {baseDir}/search.js "query" -n 10 # More results
25
+ node {baseDir}/search.js "query" --content # Include page content as markdown
26
+ node {baseDir}/search.js "query" -n 3 --content # Combined
27
+ ```
28
+
29
+ ## Extract Page Content
30
+
31
+ ```bash
32
+ node {baseDir}/content.js https://example.com/article
33
+ ```
34
+
35
+ Fetches a URL and extracts readable content as markdown.
36
+
37
+ ## Output Format
38
+
39
+ ```
40
+ --- Result 1 ---
41
+ Title: Page Title
42
+ Link: https://example.com/page
43
+ Snippet: Description from search results
44
+ Content: (if --content flag used)
45
+ Markdown content extracted from the page...
46
+
47
+ --- Result 2 ---
48
+ ...
49
+ ```
50
+
51
+ ## When to Use
52
+
53
+ - Searching for documentation or API references
54
+ - Looking up facts or current information
55
+ - Fetching content from specific URLs
56
+ - Any task requiring web search without interactive browsing
57
+ ```
@@ -0,0 +1,86 @@
1
+ #!/usr/bin/env node
2
+
3
+ import { Readability } from "@mozilla/readability";
4
+ import { JSDOM } from "jsdom";
5
+ import TurndownService from "turndown";
6
+ import { gfm } from "turndown-plugin-gfm";
7
+
8
+ const url = process.argv[2];
9
+
10
+ if (!url) {
11
+ console.log("Usage: content.js <url>");
12
+ console.log("\nExtracts readable content from a webpage as markdown.");
13
+ console.log("\nExamples:");
14
+ console.log(" content.js https://example.com/article");
15
+ console.log(" content.js https://doc.rust-lang.org/book/ch04-01-what-is-ownership.html");
16
+ process.exit(1);
17
+ }
18
+
19
+ function htmlToMarkdown(html) {
20
+ const turndown = new TurndownService({ headingStyle: "atx", codeBlockStyle: "fenced" });
21
+ turndown.use(gfm);
22
+ turndown.addRule("removeEmptyLinks", {
23
+ filter: (node) => node.nodeName === "A" && !node.textContent?.trim(),
24
+ replacement: () => "",
25
+ });
26
+ return turndown
27
+ .turndown(html)
28
+ .replace(/\[\\?\[\s*\\?\]\]\([^)]*\)/g, "")
29
+ .replace(/ +/g, " ")
30
+ .replace(/\s+,/g, ",")
31
+ .replace(/\s+\./g, ".")
32
+ .replace(/\n{3,}/g, "\n\n")
33
+ .trim();
34
+ }
35
+
36
+ try {
37
+ const response = await fetch(url, {
38
+ headers: {
39
+ "User-Agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36",
40
+ "Accept": "text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8",
41
+ "Accept-Language": "en-US,en;q=0.9",
42
+ },
43
+ signal: AbortSignal.timeout(15000),
44
+ });
45
+
46
+ if (!response.ok) {
47
+ console.error(`HTTP ${response.status}: ${response.statusText}`);
48
+ process.exit(1);
49
+ }
50
+
51
+ const html = await response.text();
52
+ const dom = new JSDOM(html, { url });
53
+ const reader = new Readability(dom.window.document);
54
+ const article = reader.parse();
55
+
56
+ if (article && article.content) {
57
+ if (article.title) {
58
+ console.log(`# ${article.title}\n`);
59
+ }
60
+ console.log(htmlToMarkdown(article.content));
61
+ process.exit(0);
62
+ }
63
+
64
+ // Fallback: try to extract main content
65
+ const fallbackDoc = new JSDOM(html, { url });
66
+ const body = fallbackDoc.window.document;
67
+ body.querySelectorAll("script, style, noscript, nav, header, footer, aside").forEach(el => el.remove());
68
+
69
+ const title = body.querySelector("title")?.textContent?.trim();
70
+ const main = body.querySelector("main, article, [role='main'], .content, #content") || body.body;
71
+
72
+ if (title) {
73
+ console.log(`# ${title}\n`);
74
+ }
75
+
76
+ const text = main?.innerHTML || "";
77
+ if (text.trim().length > 100) {
78
+ console.log(htmlToMarkdown(text));
79
+ } else {
80
+ console.error("Could not extract readable content from this page.");
81
+ process.exit(1);
82
+ }
83
+ } catch (e) {
84
+ console.error(`Error: ${e.message}`);
85
+ process.exit(1);
86
+ }
@@ -0,0 +1,14 @@
1
+ {
2
+ "name": "brave-search",
3
+ "version": "1.0.0",
4
+ "type": "module",
5
+ "description": "Headless web search via Brave Search - no browser required",
6
+ "author": "Mario Zechner",
7
+ "license": "MIT",
8
+ "dependencies": {
9
+ "@mozilla/readability": "^0.6.0",
10
+ "jsdom": "^27.0.1",
11
+ "turndown": "^7.2.2",
12
+ "turndown-plugin-gfm": "^1.0.2"
13
+ }
14
+ }
@@ -0,0 +1,179 @@
1
+ #!/usr/bin/env node
2
+
3
+ import { Readability } from "@mozilla/readability";
4
+ import { JSDOM } from "jsdom";
5
+ import TurndownService from "turndown";
6
+ import { gfm } from "turndown-plugin-gfm";
7
+
8
+ const args = process.argv.slice(2);
9
+
10
+ const contentIndex = args.indexOf("--content");
11
+ const fetchContent = contentIndex !== -1;
12
+ if (fetchContent) args.splice(contentIndex, 1);
13
+
14
+ let numResults = 5;
15
+ const nIndex = args.indexOf("-n");
16
+ if (nIndex !== -1 && args[nIndex + 1]) {
17
+ numResults = parseInt(args[nIndex + 1], 10);
18
+ args.splice(nIndex, 2);
19
+ }
20
+
21
+ const query = args.join(" ");
22
+
23
+ if (!query) {
24
+ console.log("Usage: search.js [-n] [--content]");
25
+ console.log("\nOptions:");
26
+ console.log(" -n Number of results (default: 5)");
27
+ console.log(" --content Fetch readable content as markdown");
28
+ console.log("\nExamples:");
29
+ console.log(' search.js "javascript async await"');
30
+ console.log(' search.js "rust programming" -n 10');
31
+ console.log(' search.js "climate change" --content');
32
+ process.exit(1);
33
+ }
34
+
35
+ async function fetchBraveResults(query, numResults) {
36
+ const url = `https://search.brave.com/search?q=${encodeURIComponent(query)}`;
37
+
38
+ const response = await fetch(url, {
39
+ headers: {
40
+ "User-Agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/142.0.0.0 Safari/537.36",
41
+ "Accept": "text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8",
42
+ "Accept-Language": "en-US,en;q=0.9",
43
+ "sec-ch-ua": '"Chromium";v="142", "Google Chrome";v="142", "Not_A Brand";v="99"',
44
+ "sec-ch-ua-mobile": "?0",
45
+ "sec-ch-ua-platform": '"macOS"',
46
+ "sec-fetch-dest": "document",
47
+ "sec-fetch-mode": "navigate",
48
+ "sec-fetch-site": "none",
49
+ "sec-fetch-user": "?1",
50
+ }
51
+ });
52
+
53
+ if (!response.ok) {
54
+ throw new Error(`HTTP ${response.status}: ${response.statusText}`);
55
+ }
56
+
57
+ const html = await response.text();
58
+ const dom = new JSDOM(html);
59
+ const doc = dom.window.document;
60
+
61
+ const results = [];
62
+
63
+ // Find all search result snippets with data-type="web"
64
+ const snippets = doc.querySelectorAll('div.snippet[data-type="web"]');
65
+
66
+ for (const snippet of snippets) {
67
+ if (results.length >= numResults) break;
68
+
69
+ // Get the main link and title
70
+ const titleLink = snippet.querySelector('a.svelte-14r20fy');
71
+ if (!titleLink) continue;
72
+
73
+ const link = titleLink.getAttribute('href');
74
+ if (!link || link.includes('brave.com')) continue;
75
+
76
+ const titleEl = titleLink.querySelector('.title');
77
+ const title = titleEl?.textContent?.trim() || titleLink.textContent?.trim() || '';
78
+
79
+ // Get the snippet/description
80
+ const descEl = snippet.querySelector('.generic-snippet .content');
81
+ let snippetText = descEl?.textContent?.trim() || '';
82
+ // Remove date prefix if present
83
+ snippetText = snippetText.replace(/^[A-Z][a-z]+ \d+, \d{4} -\s*/, '');
84
+
85
+ if (title && link) {
86
+ results.push({ title, link, snippet: snippetText });
87
+ }
88
+ }
89
+
90
+ return results;
91
+ }
92
+
93
+ function htmlToMarkdown(html) {
94
+ const turndown = new TurndownService({ headingStyle: "atx", codeBlockStyle: "fenced" });
95
+ turndown.use(gfm);
96
+ turndown.addRule("removeEmptyLinks", {
97
+ filter: (node) => node.nodeName === "A" && !node.textContent?.trim(),
98
+ replacement: () => "",
99
+ });
100
+ return turndown
101
+ .turndown(html)
102
+ .replace(/\[\\?\[\s*\\?\]\]\([^)]*\)/g, "")
103
+ .replace(/ +/g, " ")
104
+ .replace(/\s+,/g, ",")
105
+ .replace(/\s+\./g, ".")
106
+ .replace(/\n{3,}/g, "\n\n")
107
+ .trim();
108
+ }
109
+
110
+ async function fetchPageContent(url) {
111
+ try {
112
+ const response = await fetch(url, {
113
+ headers: {
114
+ "User-Agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36",
115
+ "Accept": "text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8",
116
+ },
117
+ signal: AbortSignal.timeout(10000),
118
+ });
119
+
120
+ if (!response.ok) {
121
+ return `(HTTP ${response.status})`;
122
+ }
123
+
124
+ const html = await response.text();
125
+ const dom = new JSDOM(html, { url });
126
+ const reader = new Readability(dom.window.document);
127
+ const article = reader.parse();
128
+
129
+ if (article && article.content) {
130
+ return htmlToMarkdown(article.content).substring(0, 5000);
131
+ }
132
+
133
+ // Fallback: try to get main content
134
+ const fallbackDoc = new JSDOM(html, { url });
135
+ const body = fallbackDoc.window.document;
136
+ body.querySelectorAll("script, style, noscript, nav, header, footer, aside").forEach(el => el.remove());
137
+ const main = body.querySelector("main, article, [role='main'], .content, #content") || body.body;
138
+ const text = main?.textContent || "";
139
+
140
+ if (text.trim().length > 100) {
141
+ return text.trim().substring(0, 5000);
142
+ }
143
+
144
+ return "(Could not extract content)";
145
+ } catch (e) {
146
+ return `(Error: ${e.message})`;
147
+ }
148
+ }
149
+
150
+ // Main
151
+ try {
152
+ const results = await fetchBraveResults(query, numResults);
153
+
154
+ if (results.length === 0) {
155
+ console.error("No results found.");
156
+ process.exit(0);
157
+ }
158
+
159
+ if (fetchContent) {
160
+ for (const result of results) {
161
+ result.content = await fetchPageContent(result.link);
162
+ }
163
+ }
164
+
165
+ for (let i = 0; i < results.length; i++) {
166
+ const r = results[i];
167
+ console.log(`--- Result ${i + 1} ---`);
168
+ console.log(`Title: ${r.title}`);
169
+ console.log(`Link: ${r.link}`);
170
+ console.log(`Snippet: ${r.snippet}`);
171
+ if (r.content) {
172
+ console.log(`Content:\n${r.content}`);
173
+ }
174
+ console.log("");
175
+ }
176
+ } catch (e) {
177
+ console.error(`Error: ${e.message}`);
178
+ process.exit(1);
179
+ }
@@ -0,0 +1,104 @@
1
+ ---
2
+ name: caldav-calendar
3
+ description: Sync and query CalDAV calendars (iCloud, Google, Fastmail, Nextcloud, etc.) using vdirsyncer + khal. Works on Linux.
4
+ metadata: {"lemonade":{"emoji":"📅","os":["linux"],"requires":{"bins":["vdirsyncer","khal"]},"install":[{"id":"apt","kind":"apt","packages":["vdirsyncer","khal"],"bins":["vdirsyncer","khal"],"label":"Install vdirsyncer + khal via apt"}]}}
5
+ ---
6
+
7
+ # CalDAV Calendar (vdirsyncer + khal)
8
+
9
+ **vdirsyncer** syncs CalDAV calendars to local `.ics` files. **khal** reads and writes them.
10
+
11
+ ## Sync First
12
+
13
+ Always sync before querying or after making changes:
14
+ ```bash
15
+ vdirsyncer sync
16
+ ```
17
+
18
+ ## View Events
19
+
20
+ ```bash
21
+ khal list # Today
22
+ khal list today 7d # Next 7 days
23
+ khal list tomorrow # Tomorrow
24
+ khal list 2026-01-15 2026-01-20 # Date range
25
+ khal list -a Work today # Specific calendar
26
+ ```
27
+
28
+ ## Search
29
+
30
+ ```bash
31
+ khal search "meeting"
32
+ khal search "dentist" --format "{start-date} {title}"
33
+ ```
34
+
35
+ ## Create Events
36
+
37
+ ```bash
38
+ khal new 2026-01-15 10:00 11:00 "Meeting title"
39
+ khal new 2026-01-15 "All day event"
40
+ khal new tomorrow 14:00 15:30 "Call" -a Work
41
+ khal new 2026-01-15 10:00 11:00 "With notes" :: Description goes here
42
+ ```
43
+
44
+ After creating, sync to push changes:
45
+ ```bash
46
+ vdirsyncer sync
47
+ ```
48
+
49
+ ## Initial Setup
50
+
51
+ ### 1. Configure vdirsyncer (`~/.config/vdirsyncer/config`)
52
+
53
+ Example for iCloud:
54
+ ```ini
55
+ [general]
56
+ status_path = "~/.local/share/vdirsyncer/status/"
57
+
58
+ [pair icloud_calendar]
59
+ a = "icloud_remote"
60
+ b = "icloud_local"
61
+ collections = ["from a", "from b"]
62
+ conflict_resolution = "a wins"
63
+
64
+ [storage icloud_remote]
65
+ type = "caldav"
66
+ url = "https://caldav.icloud.com/"
67
+ username = "you@icloud.com"
68
+ password.fetch = ["command", "cat", "~/.config/vdirsyncer/icloud_password"]
69
+
70
+ [storage icloud_local]
71
+ type = "filesystem"
72
+ path = "~/.local/share/vdirsyncer/calendars/"
73
+ fileext = ".ics"
74
+ ```
75
+
76
+ Provider URLs:
77
+ - iCloud: `https://caldav.icloud.com/`
78
+ - Google: Use `google_calendar` storage type
79
+ - Fastmail: `https://caldav.fastmail.com/dav/calendars/user/EMAIL/`
80
+ - Nextcloud: `https://YOUR.CLOUD/remote.php/dav/calendars/USERNAME/`
81
+
82
+ ### 2. Configure khal (`~/.config/khal/config`)
83
+
84
+ ```ini
85
+ [calendars]
86
+ [[my_calendars]]
87
+ path = ~/.local/share/vdirsyncer/calendars/*
88
+ type = discover
89
+
90
+ [default]
91
+ default_calendar = Home
92
+ highlight_event_days = True
93
+
94
+ [locale]
95
+ timeformat = %H:%M
96
+ dateformat = %Y-%m-%d
97
+ ```
98
+
99
+ ### 3. Discover and sync
100
+
101
+ ```bash
102
+ vdirsyncer discover
103
+ vdirsyncer sync
104
+ ```
@@ -0,0 +1,39 @@
1
+ ---
2
+ name: frontend-design
3
+ description: Create distinctive, production-grade frontend interfaces with high design quality. Use this skill when the user asks to build web components, pages, or applications. Generates creative, polished code that avoids generic AI aesthetics.
4
+ ---
5
+
6
+ This skill guides creation of distinctive, production-grade frontend interfaces that avoid generic "AI slop" aesthetics. Implement real working code with exceptional attention to aesthetic details and creative choices.
7
+
8
+ The user provides frontend requirements: a component, page, application, or interface to build. They may include context about the purpose, audience, or technical constraints.
9
+
10
+ ## Design Thinking
11
+
12
+ Before coding, understand the context and commit to a BOLD aesthetic direction:
13
+ - **Purpose**: What problem does this interface solve? Who uses it?
14
+ - **Tone**: Pick an extreme: brutally minimal, maximalist chaos, retro-futuristic, organic/natural, luxury/refined, playful/toy-like, editorial/magazine, brutalist/raw, art deco/geometric, soft/pastel, industrial/utilitarian, etc.
15
+ - **Constraints**: Technical requirements (framework, performance, accessibility).
16
+ - **Differentiation**: What makes this UNFORGETTABLE? What's the one thing someone will remember?
17
+
18
+ **CRITICAL**: Choose a clear conceptual direction and execute it with precision. Bold maximalism and refined minimalism both work - the key is intentionality, not intensity.
19
+
20
+ Then implement working code (HTML/CSS/JS, React, Vue, etc.) that is:
21
+ - Production-grade and functional
22
+ - Visually striking and memorable
23
+ - Cohesive with a clear aesthetic point-of-view
24
+ - Meticulously refined in every detail
25
+
26
+ ## Frontend Aesthetics Guidelines
27
+
28
+ Focus on:
29
+ - **Typography**: Choose fonts that are beautiful, unique, and interesting. Avoid generic fonts like Arial and Inter; opt instead for distinctive choices that elevate the frontend's aesthetics; unexpected, characterful font choices. Pair a distinctive display font with a refined body font.
30
+ - **Color & Theme**: Commit to a cohesive aesthetic. Use CSS variables for consistency. Dominant colors with sharp accents outperform timid, evenly-distributed palettes.
31
+ - **Motion**: Use animations for effects and micro-interactions. Prioritize CSS-only solutions for HTML. Focus on high-impact moments: one well-orchestrated page load with staggered reveals creates more delight than scattered micro-interactions.
32
+ - **Spatial Composition**: Unexpected layouts. Asymmetry. Overlap. Diagonal flow. Grid-breaking elements. Generous negative space OR controlled density.
33
+ - **Backgrounds & Visual Details**: Create atmosphere and depth rather than defaulting to solid colors. Add contextual effects and textures that match the overall aesthetic.
34
+
35
+ NEVER use generic AI-generated aesthetics like overused font families (Inter, Roboto, Arial, system fonts), cliched color schemes (particularly purple gradients on white backgrounds), predictable layouts, and cookie-cutter design that lacks context-specific character.
36
+
37
+ Interpret creatively and make unexpected choices that feel genuinely designed for the context. No design should be the same. Vary between light and dark themes, different fonts, different aesthetics.
38
+
39
+ **IMPORTANT**: Match implementation complexity to the aesthetic vision. Maximalist designs need elaborate code with extensive animations and effects. Minimalist or refined designs need restraint, precision, and careful attention to spacing, typography, and subtle details.
@@ -1,60 +1,31 @@
1
1
  ---
2
2
  name: image-generation
3
- description: Generate images using DALL-E 3. Create artwork, illustrations, photos, designs. Triggers on: generate image, create picture, make art, draw, illustration, design image
3
+ description: Generate images using DALL-E 3 or GPT Image models. Create artwork, illustrations, photos, designs. Triggers on: generate image, create picture, make art, draw, illustration, design image
4
4
  ---
5
5
 
6
6
  # Image Generation Skill
7
7
 
8
- Generate images using OpenAI's DALL-E 3 API.
8
+ Generate images using OpenAI's image generation API, routed through the backend proxy.
9
9
 
10
10
  ## CRITICAL: How to Generate Images
11
11
 
12
- Use the `exec` tool to run a Python script that calls the OpenAI API:
12
+ Use the `exec` tool to run a Python script. The script routes through the Lemonade backend proxy — no local API key needed.
13
13
 
14
14
  ```python
15
15
  import openai
16
16
  import os
17
- import base64
17
+ import urllib.request
18
18
  from datetime import datetime
19
19
 
20
- # Get API key from environment
21
- client = openai.OpenAI(api_key=os.environ.get("OPENAI_API_KEY"))
20
+ backend_url = os.environ.get("LEMON_BACKEND_URL", "")
21
+ gateway_token = os.environ.get("GATEWAY_TOKEN", "")
22
22
 
23
- # Generate image
24
- response = client.images.generate(
25
- model="dall-e-3",
26
- prompt="YOUR PROMPT HERE",
27
- size="1024x1024", # or 1792x1024, 1024x1792
28
- quality="standard", # or "hd" for high quality
29
- n=1
23
+ client = openai.OpenAI(
24
+ api_key=gateway_token,
25
+ base_url=f"{backend_url}/api/lemonade/proxy/v1",
30
26
  )
31
27
 
32
- # Get the URL
33
- image_url = response.data[0].url
34
- print(f"Image generated: {image_url}")
35
-
36
- # Optionally save to desktop
37
- import urllib.request
38
- desktop = os.path.expanduser("~/Desktop")
39
- filename = f"generated_image_{datetime.now().strftime('%Y%m%d_%H%M%S')}.png"
40
- filepath = os.path.join(desktop, filename)
41
- urllib.request.urlretrieve(image_url, filepath)
42
- print(f"Saved to: {filepath}")
43
- ```
44
-
45
- ## Quick Template
46
-
47
- For image generation requests:
48
-
49
- ```python
50
- import openai
51
- import os
52
- import urllib.request
53
- from datetime import datetime
54
-
55
- client = openai.OpenAI(api_key=os.environ.get("OPENAI_API_KEY"))
56
-
57
- prompt = """DETAILED PROMPT HERE"""
28
+ prompt = """YOUR PROMPT HERE"""
58
29
 
59
30
  response = client.images.generate(
60
31
  model="dall-e-3",
@@ -64,29 +35,22 @@ response = client.images.generate(
64
35
  n=1
65
36
  )
66
37
 
67
- # Save to desktop
38
+ image_url = response.data[0].url
68
39
  desktop = os.path.expanduser("~/Desktop")
69
40
  filename = f"generated_{datetime.now().strftime('%H%M%S')}.png"
70
41
  filepath = os.path.join(desktop, filename)
71
- urllib.request.urlretrieve(response.data[0].url, filepath)
72
- print(f"Image saved to Desktop: {filename}")
42
+ urllib.request.urlretrieve(image_url, filepath)
43
+ print(f"Image saved to Desktop: {filename}")
73
44
  ```
74
45
 
75
46
  ## Prompt Best Practices
76
47
 
77
48
  ### Be Specific
78
- "A cat"
79
- ✅ "A fluffy orange tabby cat sitting on a windowsill, soft morning light, photorealistic style"
49
+ - "A fluffy orange tabby cat sitting on a windowsill, soft morning light, photorealistic style"
80
50
 
81
51
  ### Include Style
82
- - "photorealistic"
83
- - "digital art"
84
- - "watercolor painting"
85
- - "3D render"
86
- - "pencil sketch"
87
- - "oil painting"
88
- - "anime style"
89
- - "minimalist illustration"
52
+ - "photorealistic", "digital art", "watercolor painting", "3D render"
53
+ - "pencil sketch", "oil painting", "anime style", "minimalist illustration"
90
54
 
91
55
  ### Include Details
92
56
  - Lighting: "golden hour", "studio lighting", "dramatic shadows"
@@ -104,34 +68,10 @@ print(f"✅ Image saved to Desktop: {filename}")
104
68
 
105
69
  ## Quality Options
106
70
 
107
- | Quality | Description | Cost |
108
- |---------|-------------|------|
109
- | standard | Good for most uses | Lower |
110
- | hd | More detailed, sharper | Higher |
111
-
112
- ## Common Requests
113
-
114
- ### "Generate a logo for [company]"
115
- ```python
116
- prompt = "Modern minimalist logo for [COMPANY NAME], clean design, professional, vector style, white background, simple geometric shapes"
117
- ```
118
-
119
- ### "Create a profile picture"
120
- ```python
121
- prompt = "Professional headshot style portrait, [DESCRIPTION], studio lighting, neutral background, photorealistic"
122
- ```
123
-
124
- ### "Make wallpaper for my desktop"
125
- ```python
126
- # Use landscape size
127
- size = "1792x1024"
128
- prompt = "[SCENE DESCRIPTION], cinematic, 4K quality, desktop wallpaper"
129
- ```
130
-
131
- ### "Draw an illustration of [X]"
132
- ```python
133
- prompt = "Illustration of [X], digital art style, vibrant colors, detailed, artistic"
134
- ```
71
+ | Quality | Description |
72
+ |---------|-------------|
73
+ | standard | Good for most uses |
74
+ | hd | More detailed, sharper |
135
75
 
136
76
  ## Workflow
137
77
 
@@ -143,7 +83,6 @@ prompt = "Illustration of [X], digital art style, vibrant colors, detailed, arti
143
83
 
144
84
  ## Important Notes
145
85
 
146
- - DALL-E 3 costs ~$0.04-0.08 per image
147
- - Images expire from URL after ~1 hour, so always save locally
148
- - The `OPENAI_API_KEY` environment variable is already configured
149
- - Always save to Desktop for easy access
86
+ - Always save generated images to `~/Desktop/` for easy access
87
+ - Images expire from the URL after ~1 hour, so always save locally
88
+ - Always install openai package first if needed: `source ~/.lemonade/.venv/bin/activate && pip install -q openai`