@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.
- package/dist/agents/system-prompt.js +9 -0
- package/dist/build-info.json +3 -3
- package/dist/canvas-host/a2ui/.bundle.hash +1 -1
- package/package.json +1 -1
- package/skills/brave-search/SKILL.md +57 -0
- package/skills/brave-search/content.js +86 -0
- package/skills/brave-search/package.json +14 -0
- package/skills/brave-search/search.js +179 -0
- package/skills/caldav-calendar/SKILL.md +104 -0
- package/skills/frontend-design/SKILL.md +39 -0
- package/skills/image-generation/SKILL.md +23 -84
- package/skills/openai-image-gen/SKILL.md +1 -1
- package/skills/openai-image-gen/scripts/gen.py +18 -3
- package/skills/self-improving-agent/SKILL.md +128 -0
- package/skills/stock-analysis/SKILL.md +131 -0
- package/skills/stock-analysis/scripts/analyze_stock.py +2532 -0
- package/skills/stock-analysis/scripts/dividends.py +365 -0
- package/skills/stock-analysis/scripts/hot_scanner.py +565 -0
- package/skills/stock-analysis/scripts/portfolio.py +528 -0
- package/skills/stock-analysis/scripts/rumor_scanner.py +330 -0
- package/skills/stock-analysis/scripts/watchlist.py +318 -0
- package/skills/tavily-search/SKILL.md +38 -0
- package/skills/tavily-search/scripts/extract.mjs +59 -0
- package/skills/tavily-search/scripts/search.mjs +101 -0
- package/skills/youtube-watcher/SKILL.md +46 -0
- package/skills/youtube-watcher/scripts/get_transcript.py +81 -0
|
@@ -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.",
|
package/dist/build-info.json
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
|
|
1
|
+
dda15b99462cc0b2ef3beeaf3b47c8ead319daf27fd6466820c14e02ea7b21a3
|
package/package.json
CHANGED
|
@@ -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
|
|
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
|
|
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
|
|
17
|
+
import urllib.request
|
|
18
18
|
from datetime import datetime
|
|
19
19
|
|
|
20
|
-
|
|
21
|
-
|
|
20
|
+
backend_url = os.environ.get("LEMON_BACKEND_URL", "")
|
|
21
|
+
gateway_token = os.environ.get("GATEWAY_TOKEN", "")
|
|
22
22
|
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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(
|
|
72
|
-
print(f"
|
|
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
|
-
|
|
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
|
-
- "
|
|
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 |
|
|
108
|
-
|
|
109
|
-
| standard | Good for most uses |
|
|
110
|
-
| hd | More detailed, sharper |
|
|
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
|
-
-
|
|
147
|
-
- Images expire from URL after ~1 hour, so always save locally
|
|
148
|
-
-
|
|
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`
|