@heylemon/lemonade 0.2.3 → 0.2.4

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.
@@ -1,5 +1,5 @@
1
1
  {
2
- "version": "0.2.3",
3
- "commit": "a0535878314984aba3836015e906997cadb9bb7f",
4
- "builtAt": "2026-02-22T15:12:12.410Z"
2
+ "version": "0.2.4",
3
+ "commit": "8eb0340db81a8046301e7ca8d6a7afb19af6f09e",
4
+ "builtAt": "2026-02-22T15:19:53.606Z"
5
5
  }
@@ -1 +1 @@
1
- dda15b99462cc0b2ef3beeaf3b47c8ead319daf27fd6466820c14e02ea7b21a3
1
+ aad422f21104b00c3575f122b2f36572b3d50edc6e9dc94b5c2fb91fd996d4b9
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@heylemon/lemonade",
3
- "version": "0.2.3",
3
+ "version": "0.2.4",
4
4
  "description": "AI gateway CLI for Lemon - local AI assistant with integrations",
5
5
  "publishConfig": {
6
6
  "access": "restricted"
@@ -39,8 +39,13 @@ python3 {baseDir}/scripts/get_transcript.py "https://www.youtube.com/watch?v=VID
39
39
  1. Get the transcript.
40
40
  2. Search the text for keywords or answer the user's question based on the content.
41
41
 
42
+ ## Important
43
+
44
+ **If `lemon-youtube` is available**, prefer using `lemon-youtube transcript <url>` instead — it uses the authenticated YouTube API and is more reliable. Use this skill as a fallback when the YouTube integration is not connected or `lemon-youtube` is unavailable.
45
+
42
46
  ## Notes
43
47
 
44
48
  - Requires `yt-dlp` to be installed and available in the PATH.
45
49
  - Works with videos that have closed captions (CC) or auto-generated subtitles.
46
50
  - If a video has no subtitles, the script will fail with an error message.
51
+ - No API key required — uses yt-dlp to fetch publicly available subtitles.
@@ -1,57 +0,0 @@
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
- ```
@@ -1,86 +0,0 @@
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
- }
@@ -1,14 +0,0 @@
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
- }
@@ -1,179 +0,0 @@
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
- }
@@ -1,104 +0,0 @@
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
- ```
@@ -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
- }
@@ -1,101 +0,0 @@
1
- #!/usr/bin/env node
2
-
3
- function usage() {
4
- console.error(`Usage: search.mjs "query" [-n 5] [--deep] [--topic general|news] [--days 7]`);
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 query = args[0];
12
- let n = 5;
13
- let searchDepth = "basic";
14
- let topic = "general";
15
- let days = null;
16
-
17
- for (let i = 1; i < args.length; i++) {
18
- const a = args[i];
19
- if (a === "-n") {
20
- n = Number.parseInt(args[i + 1] ?? "5", 10);
21
- i++;
22
- continue;
23
- }
24
- if (a === "--deep") {
25
- searchDepth = "advanced";
26
- continue;
27
- }
28
- if (a === "--topic") {
29
- topic = args[i + 1] ?? "general";
30
- i++;
31
- continue;
32
- }
33
- if (a === "--days") {
34
- days = Number.parseInt(args[i + 1] ?? "7", 10);
35
- i++;
36
- continue;
37
- }
38
- console.error(`Unknown arg: ${a}`);
39
- usage();
40
- }
41
-
42
- const apiKey = (process.env.TAVILY_API_KEY ?? "").trim();
43
- if (!apiKey) {
44
- console.error("Missing TAVILY_API_KEY");
45
- process.exit(1);
46
- }
47
-
48
- const body = {
49
- api_key: apiKey,
50
- query: query,
51
- search_depth: searchDepth,
52
- topic: topic,
53
- max_results: Math.max(1, Math.min(n, 20)),
54
- include_answer: true,
55
- include_raw_content: false,
56
- };
57
-
58
- if (topic === "news" && days) {
59
- body.days = days;
60
- }
61
-
62
- const resp = await fetch("https://api.tavily.com/search", {
63
- method: "POST",
64
- headers: {
65
- "Content-Type": "application/json",
66
- },
67
- body: JSON.stringify(body),
68
- });
69
-
70
- if (!resp.ok) {
71
- const text = await resp.text().catch(() => "");
72
- throw new Error(`Tavily Search failed (${resp.status}): ${text}`);
73
- }
74
-
75
- const data = await resp.json();
76
-
77
- // Print AI-generated answer if available
78
- if (data.answer) {
79
- console.log("## Answer\n");
80
- console.log(data.answer);
81
- console.log("\n---\n");
82
- }
83
-
84
- // Print results
85
- const results = (data.results ?? []).slice(0, n);
86
- console.log("## Sources\n");
87
-
88
- for (const r of results) {
89
- const title = String(r?.title ?? "").trim();
90
- const url = String(r?.url ?? "").trim();
91
- const content = String(r?.content ?? "").trim();
92
- const score = r?.score ? ` (relevance: ${(r.score * 100).toFixed(0)}%)` : "";
93
-
94
- if (!title || !url) continue;
95
- console.log(`- **${title}**${score}`);
96
- console.log(` ${url}`);
97
- if (content) {
98
- console.log(` ${content.slice(0, 300)}${content.length > 300 ? "..." : ""}`);
99
- }
100
- console.log();
101
- }