remove irrelevant skills (use extension instead)
This commit is contained in:
@@ -1,79 +0,0 @@
|
||||
---
|
||||
name: brave-search
|
||||
description: Web search and content extraction via Brave Search API. Use for searching documentation, facts, or any web content. Lightweight, no browser required.
|
||||
---
|
||||
|
||||
# Brave Search
|
||||
|
||||
Web search and content extraction using the official Brave Search API. No browser required.
|
||||
|
||||
## Setup
|
||||
|
||||
Requires a Brave Search API account with a free subscription. A credit card is required to create the free subscription (you won't be charged).
|
||||
|
||||
1. Create an account at https://api-dashboard.search.brave.com/register
|
||||
2. Create a "Free AI" subscription
|
||||
3. Create an API key for the subscription
|
||||
4. Add to your shell profile (`~/.profile` or `~/.zprofile` for zsh):
|
||||
```bash
|
||||
export BRAVE_API_KEY="your-api-key-here"
|
||||
```
|
||||
5. Install shared dependencies (run once):
|
||||
```bash
|
||||
cd ~/.pi/agent/extensions
|
||||
npm install
|
||||
```
|
||||
|
||||
## Search
|
||||
|
||||
```bash
|
||||
{baseDir}/search.js "query" # Basic search (5 results)
|
||||
{baseDir}/search.js "query" -n 10 # More results (max 20)
|
||||
{baseDir}/search.js "query" --content # Include page content as markdown
|
||||
{baseDir}/search.js "query" --freshness pw # Results from last week
|
||||
{baseDir}/search.js "query" --freshness 2024-01-01to2024-06-30 # Date range
|
||||
{baseDir}/search.js "query" --country DE # Results from Germany
|
||||
{baseDir}/search.js "query" -n 3 --content # Combined options
|
||||
```
|
||||
|
||||
### Options
|
||||
|
||||
- `-n <num>` - Number of results (default: 5, max: 20)
|
||||
- `--content` - Fetch and include page content as markdown
|
||||
- `--country <code>` - Two-letter country code (default: US)
|
||||
- `--freshness <period>` - Filter by time:
|
||||
- `pd` - Past day (24 hours)
|
||||
- `pw` - Past week
|
||||
- `pm` - Past month
|
||||
- `py` - Past year
|
||||
- `YYYY-MM-DDtoYYYY-MM-DD` - Custom date range
|
||||
|
||||
## Extract Page Content
|
||||
|
||||
```bash
|
||||
{baseDir}/content.js https://example.com/article
|
||||
```
|
||||
|
||||
Fetches a URL and extracts readable content as markdown.
|
||||
|
||||
## Output Format
|
||||
|
||||
```
|
||||
--- Result 1 ---
|
||||
Title: Page Title
|
||||
Link: https://example.com/page
|
||||
Age: 2 days ago
|
||||
Snippet: Description from search results
|
||||
Content: (if --content flag used)
|
||||
Markdown content extracted from the page...
|
||||
|
||||
--- Result 2 ---
|
||||
...
|
||||
```
|
||||
|
||||
## When to Use
|
||||
|
||||
- Searching for documentation or API references
|
||||
- Looking up facts or current information
|
||||
- Fetching content from specific URLs
|
||||
- Any task requiring web search without interactive browsing
|
||||
@@ -1,95 +0,0 @@
|
||||
#!/usr/bin/env node
|
||||
|
||||
import path from "node:path";
|
||||
import { fileURLToPath } from "node:url";
|
||||
import { createRequire } from "node:module";
|
||||
|
||||
const require = createRequire(import.meta.url);
|
||||
const baseDir = path.dirname(fileURLToPath(import.meta.url));
|
||||
const sharedNodeModules = path.resolve(baseDir, "../../extensions/node_modules");
|
||||
const resolveFromShared = (pkg) => require.resolve(pkg, { paths: [sharedNodeModules] });
|
||||
|
||||
const { Readability } = require(resolveFromShared("@mozilla/readability"));
|
||||
const { JSDOM } = require(resolveFromShared("jsdom"));
|
||||
const TurndownService = require(resolveFromShared("turndown"));
|
||||
const { gfm } = require(resolveFromShared("turndown-plugin-gfm"));
|
||||
|
||||
const url = process.argv[2];
|
||||
|
||||
if (!url) {
|
||||
console.log("Usage: content.js <url>");
|
||||
console.log("\nExtracts readable content from a webpage as markdown.");
|
||||
console.log("\nExamples:");
|
||||
console.log(" content.js https://example.com/article");
|
||||
console.log(" content.js https://doc.rust-lang.org/book/ch04-01-what-is-ownership.html");
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
function htmlToMarkdown(html) {
|
||||
const turndown = new TurndownService({ headingStyle: "atx", codeBlockStyle: "fenced" });
|
||||
turndown.use(gfm);
|
||||
turndown.addRule("removeEmptyLinks", {
|
||||
filter: (node) => node.nodeName === "A" && !node.textContent?.trim(),
|
||||
replacement: () => "",
|
||||
});
|
||||
return turndown
|
||||
.turndown(html)
|
||||
.replace(/\[\\?\[\s*\\?\]\]\([^)]*\)/g, "")
|
||||
.replace(/ +/g, " ")
|
||||
.replace(/\s+,/g, ",")
|
||||
.replace(/\s+\./g, ".")
|
||||
.replace(/\n{3,}/g, "\n\n")
|
||||
.trim();
|
||||
}
|
||||
|
||||
try {
|
||||
const response = await fetch(url, {
|
||||
headers: {
|
||||
"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",
|
||||
"Accept": "text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8",
|
||||
"Accept-Language": "en-US,en;q=0.9",
|
||||
},
|
||||
signal: AbortSignal.timeout(15000),
|
||||
});
|
||||
|
||||
if (!response.ok) {
|
||||
console.error(`HTTP ${response.status}: ${response.statusText}`);
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
const html = await response.text();
|
||||
const dom = new JSDOM(html, { url });
|
||||
const reader = new Readability(dom.window.document);
|
||||
const article = reader.parse();
|
||||
|
||||
if (article && article.content) {
|
||||
if (article.title) {
|
||||
console.log(`# ${article.title}\n`);
|
||||
}
|
||||
console.log(htmlToMarkdown(article.content));
|
||||
process.exit(0);
|
||||
}
|
||||
|
||||
// Fallback: try to extract main content
|
||||
const fallbackDoc = new JSDOM(html, { url });
|
||||
const body = fallbackDoc.window.document;
|
||||
body.querySelectorAll("script, style, noscript, nav, header, footer, aside").forEach(el => el.remove());
|
||||
|
||||
const title = body.querySelector("title")?.textContent?.trim();
|
||||
const main = body.querySelector("main, article, [role='main'], .content, #content") || body.body;
|
||||
|
||||
if (title) {
|
||||
console.log(`# ${title}\n`);
|
||||
}
|
||||
|
||||
const text = main?.innerHTML || "";
|
||||
if (text.trim().length > 100) {
|
||||
console.log(htmlToMarkdown(text));
|
||||
} else {
|
||||
console.error("Could not extract readable content from this page.");
|
||||
process.exit(1);
|
||||
}
|
||||
} catch (e) {
|
||||
console.error(`Error: ${e.message}`);
|
||||
process.exit(1);
|
||||
}
|
||||
@@ -1,208 +0,0 @@
|
||||
#!/usr/bin/env node
|
||||
|
||||
import path from "node:path";
|
||||
import { fileURLToPath } from "node:url";
|
||||
import { createRequire } from "node:module";
|
||||
|
||||
const require = createRequire(import.meta.url);
|
||||
const baseDir = path.dirname(fileURLToPath(import.meta.url));
|
||||
const sharedNodeModules = path.resolve(baseDir, "../../extensions/node_modules");
|
||||
const resolveFromShared = (pkg) => require.resolve(pkg, { paths: [sharedNodeModules] });
|
||||
|
||||
const { Readability } = require(resolveFromShared("@mozilla/readability"));
|
||||
const { JSDOM } = require(resolveFromShared("jsdom"));
|
||||
const TurndownService = require(resolveFromShared("turndown"));
|
||||
const { gfm } = require(resolveFromShared("turndown-plugin-gfm"));
|
||||
|
||||
const args = process.argv.slice(2);
|
||||
|
||||
const contentIndex = args.indexOf("--content");
|
||||
const fetchContent = contentIndex !== -1;
|
||||
if (fetchContent) args.splice(contentIndex, 1);
|
||||
|
||||
let numResults = 5;
|
||||
const nIndex = args.indexOf("-n");
|
||||
if (nIndex !== -1 && args[nIndex + 1]) {
|
||||
numResults = parseInt(args[nIndex + 1], 10);
|
||||
args.splice(nIndex, 2);
|
||||
}
|
||||
|
||||
// Parse country option
|
||||
let country = "US";
|
||||
const countryIndex = args.indexOf("--country");
|
||||
if (countryIndex !== -1 && args[countryIndex + 1]) {
|
||||
country = args[countryIndex + 1].toUpperCase();
|
||||
args.splice(countryIndex, 2);
|
||||
}
|
||||
|
||||
// Parse freshness option
|
||||
let freshness = null;
|
||||
const freshnessIndex = args.indexOf("--freshness");
|
||||
if (freshnessIndex !== -1 && args[freshnessIndex + 1]) {
|
||||
freshness = args[freshnessIndex + 1];
|
||||
args.splice(freshnessIndex, 2);
|
||||
}
|
||||
|
||||
const query = args.join(" ");
|
||||
|
||||
if (!query) {
|
||||
console.log("Usage: search.js <query> [-n <num>] [--content] [--country <code>] [--freshness <period>]");
|
||||
console.log("\nOptions:");
|
||||
console.log(" -n <num> Number of results (default: 5, max: 20)");
|
||||
console.log(" --content Fetch readable content as markdown");
|
||||
console.log(" --country <code> Country code for results (default: US)");
|
||||
console.log(" --freshness <period> Filter by time: pd (day), pw (week), pm (month), py (year)");
|
||||
console.log("\nEnvironment:");
|
||||
console.log(" BRAVE_API_KEY Required. Your Brave Search API key.");
|
||||
console.log("\nExamples:");
|
||||
console.log(' search.js "javascript async await"');
|
||||
console.log(' search.js "rust programming" -n 10');
|
||||
console.log(' search.js "climate change" --content');
|
||||
console.log(' search.js "news today" --freshness pd');
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
const apiKey = process.env.BRAVE_API_KEY;
|
||||
if (!apiKey) {
|
||||
console.error("Error: BRAVE_API_KEY environment variable is required.");
|
||||
console.error("Get your API key at: https://api-dashboard.search.brave.com/app/keys");
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
async function fetchBraveResults(query, numResults, country, freshness) {
|
||||
const params = new URLSearchParams({
|
||||
q: query,
|
||||
count: Math.min(numResults, 20).toString(),
|
||||
country: country,
|
||||
});
|
||||
|
||||
if (freshness) {
|
||||
params.append("freshness", freshness);
|
||||
}
|
||||
|
||||
const url = `https://api.search.brave.com/res/v1/web/search?${params.toString()}`;
|
||||
|
||||
const response = await fetch(url, {
|
||||
headers: {
|
||||
"Accept": "application/json",
|
||||
"Accept-Encoding": "gzip",
|
||||
"X-Subscription-Token": apiKey,
|
||||
}
|
||||
});
|
||||
|
||||
if (!response.ok) {
|
||||
const errorText = await response.text();
|
||||
throw new Error(`HTTP ${response.status}: ${response.statusText}\n${errorText}`);
|
||||
}
|
||||
|
||||
const data = await response.json();
|
||||
|
||||
const results = [];
|
||||
|
||||
// Extract web results
|
||||
if (data.web && data.web.results) {
|
||||
for (const result of data.web.results) {
|
||||
if (results.length >= numResults) break;
|
||||
|
||||
results.push({
|
||||
title: result.title || "",
|
||||
link: result.url || "",
|
||||
snippet: result.description || "",
|
||||
age: result.age || result.page_age || "",
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
return results;
|
||||
}
|
||||
|
||||
function htmlToMarkdown(html) {
|
||||
const turndown = new TurndownService({ headingStyle: "atx", codeBlockStyle: "fenced" });
|
||||
turndown.use(gfm);
|
||||
turndown.addRule("removeEmptyLinks", {
|
||||
filter: (node) => node.nodeName === "A" && !node.textContent?.trim(),
|
||||
replacement: () => "",
|
||||
});
|
||||
return turndown
|
||||
.turndown(html)
|
||||
.replace(/\[\\?\[\s*\\?\]\]\([^)]*\)/g, "")
|
||||
.replace(/ +/g, " ")
|
||||
.replace(/\s+,/g, ",")
|
||||
.replace(/\s+\./g, ".")
|
||||
.replace(/\n{3,}/g, "\n\n")
|
||||
.trim();
|
||||
}
|
||||
|
||||
async function fetchPageContent(url) {
|
||||
try {
|
||||
const response = await fetch(url, {
|
||||
headers: {
|
||||
"User-Agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36",
|
||||
"Accept": "text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8",
|
||||
},
|
||||
signal: AbortSignal.timeout(10000),
|
||||
});
|
||||
|
||||
if (!response.ok) {
|
||||
return `(HTTP ${response.status})`;
|
||||
}
|
||||
|
||||
const html = await response.text();
|
||||
const dom = new JSDOM(html, { url });
|
||||
const reader = new Readability(dom.window.document);
|
||||
const article = reader.parse();
|
||||
|
||||
if (article && article.content) {
|
||||
return htmlToMarkdown(article.content).substring(0, 5000);
|
||||
}
|
||||
|
||||
// Fallback: try to get main content
|
||||
const fallbackDoc = new JSDOM(html, { url });
|
||||
const body = fallbackDoc.window.document;
|
||||
body.querySelectorAll("script, style, noscript, nav, header, footer, aside").forEach(el => el.remove());
|
||||
const main = body.querySelector("main, article, [role='main'], .content, #content") || body.body;
|
||||
const text = main?.textContent || "";
|
||||
|
||||
if (text.trim().length > 100) {
|
||||
return text.trim().substring(0, 5000);
|
||||
}
|
||||
|
||||
return "(Could not extract content)";
|
||||
} catch (e) {
|
||||
return `(Error: ${e.message})`;
|
||||
}
|
||||
}
|
||||
|
||||
// Main
|
||||
try {
|
||||
const results = await fetchBraveResults(query, numResults, country, freshness);
|
||||
|
||||
if (results.length === 0) {
|
||||
console.error("No results found.");
|
||||
process.exit(0);
|
||||
}
|
||||
|
||||
if (fetchContent) {
|
||||
for (const result of results) {
|
||||
result.content = await fetchPageContent(result.link);
|
||||
}
|
||||
}
|
||||
|
||||
for (let i = 0; i < results.length; i++) {
|
||||
const r = results[i];
|
||||
console.log(`--- Result ${i + 1} ---`);
|
||||
console.log(`Title: ${r.title}`);
|
||||
console.log(`Link: ${r.link}`);
|
||||
if (r.age) {
|
||||
console.log(`Age: ${r.age}`);
|
||||
}
|
||||
console.log(`Snippet: ${r.snippet}`);
|
||||
if (r.content) {
|
||||
console.log(`Content:\n${r.content}`);
|
||||
}
|
||||
console.log("");
|
||||
}
|
||||
} catch (e) {
|
||||
console.error(`Error: ${e.message}`);
|
||||
process.exit(1);
|
||||
}
|
||||
Reference in New Issue
Block a user