Compare commits

...

11 Commits

Author SHA1 Message Date
mrosati 995c2a340a update 2026-06-05 15:45:52 +02:00
mrosati 2387f025f9 add models 2026-05-27 11:37:17 +02:00
mrosati c28b93e792 updates 2026-05-27 11:37:08 +02:00
mrosati afa0bfe509 ignore npm 2026-05-26 13:19:25 +02:00
Matteo Rosati 2ec6bb0867 update 2026-05-22 22:27:45 +02:00
Matteo Rosati e60d617558 add base AGENTS.md file 2026-05-22 21:58:19 +02:00
Matteo Rosati d4357dd20c update 2026-05-21 20:55:49 +02:00
mrosati ab2bc3aeb5 add powerline plugin 2026-05-13 18:03:38 +02:00
mrosati 9d401591bd add llm-wiki skill 2026-05-11 20:11:39 +02:00
mrosati 647352e49c remove irrelevant skills (use extension instead) 2026-05-10 14:11:12 +02:00
mrosati 3ecc07aeab ignore auto-generated mcp file 2026-05-10 14:10:45 +02:00
12 changed files with 279 additions and 826 deletions
+2
View File
@@ -7,4 +7,6 @@ sessions
.mcp.json
mcp-cache.json
mcp-npx-cache.json
mcp-onboarding.json
run-history.jsonl
npm
+108
View File
@@ -0,0 +1,108 @@
# AGENTS.md
## Purpose
You are Pi, a coding agent operating on a real codebase. Your goal is to produce **correct, minimal, production-ready changes** with strong reasoning and zero unnecessary output.
---
## Core Rules (non-negotiable)
- Do NOT guess. If uncertain → say it explicitly.
- Do NOT hallucinate APIs, files, or behavior.
- Prefer **reading existing code** over assuming patterns.
- Prefer **searching online documentation** when in doubt on a language or library.
- Every change must be:
- Minimal
- Reversible
- Justified
---
## Workflow (strict)
1. **Understand**
- Read all relevant files first
- Identify constraints (framework, patterns, infra)
2. **Plan**
- State the exact change in 13 bullets
- No execution before a clear plan
3. **Execute**
- Modify only what is necessary
- Follow existing style and conventions strictly
4. **Verify**
- Mentally simulate execution
- Check edge cases, imports, types, runtime behavior
---
## Decision Heuristics
- If multiple solutions exist → choose the **simplest working one**.
- If behavior is unclear → prefer **explicitness over magic**.
- If codebase has a pattern → **match it exactly**, even if suboptimal.
- Avoid introducing:
- New dependencies
- New abstractions
- Premature generalization
---
## Code Quality Bar
All code must:
- Compile / type-check
- Be idiomatic for the language
- Avoid dead code
- Handle errors explicitly
- Respect existing architecture
---
## Output Format
When making changes:
- Show ONLY the relevant diff or final code
- No explanations unless explicitly requested
- No repetition of unchanged code
---
## Anti-Patterns (avoid)
- Over-engineering
- Refactoring unrelated code
- “Improving” things not asked
- Silent assumptions
- Partial implementations
---
## When Blocked
If you cannot proceed:
- State EXACTLY what is missing
- Ask ONE precise question
- Do not continue blindly
---
## Success Criteria
A solution is correct when:
- It solves the problem fully
- It integrates cleanly into the codebase
- It introduces zero unintended side effects
---
## Mental Model
You are not here to impress.
You are here to **ship correct code with minimal risk**.
-376
View File
@@ -1,376 +0,0 @@
import { spawn } from "node:child_process";
import * as fs from "node:fs";
import * as os from "node:os";
import * as path from "node:path";
import type { Message } from "@earendil-works/pi-ai";
import type { ExtensionAPI } from "@earendil-works/pi-coding-agent";
import { TDM_PROMPT, DEV_PROMPT, ANA_PROMPT } from "./prompts";
import { AgentRunResult, AnaVerdict } from "./types";
const MAX_ITERATIONS = 5;
// UI Symbols:
// ┌ ┐ └ ┘ │ ─ ├ ┤
const TDM_UI = `
┌─────┐ ┌─────┐ ┌─────┐
│ **TDM** │ --> │ DEV │ --> │ ANA │
└─────┘ └─────┘ └─────┘
`
const DEV_UI = `
┌─────┐ ┌─────┐ ┌─────┐
│ TDM │ --> │ **DEV** │ --> │ ANA │
└─────┘ └─────┘ └─────┘
`
const ANA_UI = `
┌─────┐ ┌─────┐ ┌─────┐
│ TDM │ --> │ DEV │ --> │ **ANA** │
└─────┘ └─────┘ └─────┘
`
const ANA_REWORK_UI = `
┌─────┐ ┌─────┐ ┌─────┐
│ TDM │ --> │ DEV │ --> │ **ANA** │
└─────┘ └─────┘ └─────┘
`
function getPiInvocation(args: string[]): { command: string; args: string[] } {
const currentScript = process.argv[1];
const isBunVirtualScript = currentScript?.startsWith("/$bunfs/root/");
if (currentScript && !isBunVirtualScript && fs.existsSync(currentScript)) {
return { command: process.execPath, args: [currentScript, ...args] };
}
const execName = path.basename(process.execPath).toLowerCase();
const isGenericRuntime = /^(node|bun)(\.exe)?$/.test(execName);
if (!isGenericRuntime) return { command: process.execPath, args };
return { command: "pi", args };
}
async function writePromptToTempFile(
name: string,
prompt: string,
): Promise<{ dir: string; filePath: string }> {
const tmpDir = await fs.promises.mkdtemp(
path.join(os.tmpdir(), "pi-multiagent-"),
);
const safeName = name.replace(/[^\w.-]+/g, "_");
const filePath = path.join(tmpDir, `${safeName}.md`);
await fs.promises.writeFile(filePath, prompt, {
encoding: "utf-8",
mode: 0o600,
});
return { dir: tmpDir, filePath };
}
function getFinalText(messages: Message[]): string {
for (let i = messages.length - 1; i >= 0; i--) {
const msg = messages[i];
if (msg.role !== "assistant") continue;
const text = msg.content.find((c) => c.type === "text");
if (text && text.type === "text") return text.text;
}
return "";
}
async function runAgent(
cwd: string,
systemPrompt: string,
task: string,
tools: string,
signal?: AbortSignal,
): Promise<AgentRunResult> {
const tmp = await writePromptToTempFile("system", systemPrompt);
const args = [
"--mode",
"json",
"-p",
"--no-session",
"--tools",
tools,
"--append-system-prompt",
tmp.filePath,
task,
];
try {
return await new Promise<AgentRunResult>((resolve) => {
const invocation = getPiInvocation(args);
const proc = spawn(invocation.command, invocation.args, {
cwd,
shell: false,
stdio: ["ignore", "pipe", "pipe"],
});
const messages: Message[] = [];
let stderr = "";
let buffer = "";
const processLine = (line: string) => {
if (!line.trim()) return;
try {
const event = JSON.parse(line);
if (event.type === "message_end" && event.message)
messages.push(event.message as Message);
if (event.type === "tool_result_end" && event.message)
messages.push(event.message as Message);
} catch {
// ignore malformed lines
}
};
proc.stdout.on("data", (d) => {
buffer += d.toString();
const lines = buffer.split("\n");
buffer = lines.pop() || "";
for (const l of lines) processLine(l);
});
proc.stderr.on("data", (d) => {
stderr += d.toString();
});
proc.on("close", (code) => {
if (buffer.trim()) processLine(buffer);
resolve({ exitCode: code ?? 1, messages, stderr });
});
if (signal) {
const killProc = () => {
proc.kill("SIGTERM");
setTimeout(() => {
if (!proc.killed) proc.kill("SIGKILL");
}, 5000);
};
if (signal.aborted) killProc();
else signal.addEventListener("abort", killProc, { once: true });
}
});
} finally {
try {
fs.unlinkSync(tmp.filePath);
} catch { }
try {
fs.rmdirSync(tmp.dir);
} catch { }
}
}
function parseAnaVerdict(raw: string): AnaVerdict {
try {
const parsed = JSON.parse(raw) as AnaVerdict;
return {
approved: Boolean(parsed.approved),
feedback: String(parsed.feedback ?? "").trim(),
};
} catch {
return {
approved: false,
feedback: `ANA output was not valid JSON:\n${raw}`,
};
}
}
function startLoading(
setStatus: (text: string) => void,
baseLabel: string,
): () => void {
const frames = ["⠋", "⠙", "⠹", "⠸", "⠼", "⠴", "⠦", "⠧", "⠇", "⠏"];
let i = 0;
setStatus(`${frames[0]} ${baseLabel}`);
const timer = setInterval(() => {
i = (i + 1) % frames.length;
setStatus(`${frames[i]} ${baseLabel}`);
}, 120);
return () => {
clearInterval(timer);
};
}
export default function (pi: ExtensionAPI) {
let enabled = false;
pi.registerCommand("multiagent", {
description: "Toggle multi-agent workflow (/multiagent on|off|status)",
handler: async (args, ctx) => {
const action = (args || "toggle").trim().toLowerCase();
if (action === "on") enabled = true;
else if (action === "off") enabled = false;
else if (action === "toggle") enabled = !enabled;
else if (action === "status") {
ctx.ui.notify(`Multiagent: ${enabled ? "ON" : "OFF"}`, "info");
return;
} else {
ctx.ui.notify("Usage: /multiagent [on|off|toggle|status]", "warning");
return;
}
ctx.ui.notify(
`Multiagent: ${enabled ? "ON" : "OFF"}`,
enabled ? "info" : "info",
);
},
});
pi.registerShortcut("ctrl+alt+m", {
description: "Toggle multi-agent workflow",
handler: async (ctx) => {
enabled = !enabled;
ctx.ui.notify(
`Multiagent: ${enabled ? "ON" : "OFF"}`,
enabled ? "info" : "info",
);
},
});
pi.on("input", async (event, ctx) => {
if (!enabled) return { action: "continue" as const };
if (event.source === "extension") return { action: "continue" as const };
if (event.text.trim().startsWith("/"))
return { action: "continue" as const };
ctx.ui.setStatus("multiagent", "multiagent: queued");
pi.sendMessage({
customType: "multiagent-result",
content: `### Multiagent workflow started\n\n${TDM_UI}\n\n(max ${MAX_ITERATIONS} DEV/ANA loops).`,
display: true,
});
const userTask = event.text.trim();
const stopTdmLoader = startLoading(
(text) => ctx.ui.setStatus("multiagent", text),
"TDM analyzing request + codebase",
);
const tdm = await runAgent(
ctx.cwd,
TDM_PROMPT,
`User request:\n${userTask}`,
"read,grep,find,ls,bash",
ctx.signal,
);
stopTdmLoader();
const specs = getFinalText(tdm.messages);
if (tdm.exitCode !== 0 || !specs) {
pi.sendMessage({
customType: "multiagent-result",
content: `TDM step failed.\n\n${tdm.stderr || "No output."}`,
display: true,
});
ctx.ui.setStatus("multiagent", "");
return { action: "handled" as const };
}
pi.sendMessage({
customType: "multiagent-result",
content: `### TDM output (specs)\n\n${specs}`,
display: true,
});
pi.sendMessage({
customType: "multiagent-result",
content: DEV_UI,
display: true,
});
let devContext = `TDM Specs:\n${specs}`;
let lastDevOutput = "";
let lastAnaFeedback = "";
for (let i = 1; i <= MAX_ITERATIONS; i++) {
const stopDevLoader = startLoading(
(text) => ctx.ui.setStatus("multiagent", text),
`DEV implementing iteration ${i}/${MAX_ITERATIONS}`,
);
const dev = await runAgent(
ctx.cwd,
DEV_PROMPT,
`${devContext}\n\nProceed with the implementation the current codebase. Then summarize changes and rationale.`,
"read,grep,find,ls,bash,edit,write",
ctx.signal,
);
stopDevLoader();
lastDevOutput = getFinalText(dev.messages);
pi.sendMessage({
customType: "multiagent-result",
content: `### DEV iteration ${i} output\n\n${lastDevOutput || "(no DEV output)"}`,
display: true,
});
if (dev.exitCode !== 0) {
pi.sendMessage({
customType: "multiagent-result",
content: `DEV iteration ${i} failed.\n\n${dev.stderr || "No stderr output."}`,
display: true,
});
}
pi.sendMessage({
customType: "multiagent-result",
content: ANA_UI,
display: true,
});
const stopAnaLoader = startLoading(
(text) => ctx.ui.setStatus("multiagent", text),
`ANA reviewing iteration ${i}/${MAX_ITERATIONS}`,
);
const ana = await runAgent(
ctx.cwd,
ANA_PROMPT,
`TDM Specs:\n${specs}\n\nDEV report:\n${lastDevOutput}\n\nReview the implementation in the codebase and provide verdict JSON.`,
"read,grep,find,ls,bash",
ctx.signal,
);
stopAnaLoader();
const verdict = parseAnaVerdict(getFinalText(ana.messages));
lastAnaFeedback = verdict.feedback;
pi.sendMessage({
customType: "multiagent-result",
content: `### ANA iteration ${i} verdict\n\n- approved: **${verdict.approved ? "true" : "false"}**\n\n${verdict.feedback || "(no feedback)"}`,
display: true,
});
if (verdict.approved) break;
pi.sendMessage({
customType: "multiagent-result",
content: ANA_REWORK_UI,
display: true,
});
devContext = `TDM Specs:\n${specs}\n\nANA feedback to address:\n${verdict.feedback}`;
}
pi.sendMessage({
customType: "multiagent-result",
content: `### Multiagent workflow completed\n\n${lastDevOutput || "(No DEV summary)"}\n\n---\n**Final ANA feedback:**\n${lastAnaFeedback || "Approved with no additional feedback."}`,
display: true,
});
ctx.ui.setStatus("multiagent", "");
return { action: "handled" as const };
});
}
-40
View File
@@ -1,40 +0,0 @@
export const TDM_PROMPT = `You are the Technical Delivery Manager (TDM).
Your job:
1) Deeply understand user intent.
2) Clarify ambiguities by explicitly listing assumptions.
3) Produce precise, context-aware implementation specs for a developer.
Rules:
- Focus on WHAT must be done, not implementation details.
- Be explicit, structured, and testable.
- Output markdown with sections:
- Intent
- Assumptions
- Scope
- Detailed Specs
- Acceptance Criteria`;
export const DEV_PROMPT = `You are the Developer (DEV).
Your job:
- Implement exactly the provided specs with minimal necessary changes.
- Prefer small, safe, reliable edits.
- Stay within scope.
Rules:
- Inspect code before editing.
- Make focused changes only.
- Report exactly what was changed and why.`;
export const ANA_PROMPT = `You are the Code Analyst (ANA).
Your job:
- Critique DEV output against TDM specs.
- Find mistakes, risky choices, anti-patterns, and sub-optimal decisions.
- Suggest concrete improvements.
You MUST output valid JSON only with this schema:
{
"approved": boolean,
"feedback": string
}
Set approved=true only if implementation is solid and no significant fixes are needed.`;
-12
View File
@@ -1,12 +0,0 @@
import type { Message } from "@earendil-works/pi-ai";
export interface AgentRunResult {
exitCode: number;
messages: Message[];
stderr: string;
}
export interface AnaVerdict {
approved: boolean;
feedback: string;
}
+1 -11
View File
@@ -1,14 +1,4 @@
{
"mcpServers": {
"chrome-devtools": {
"command": "npx",
"args": [
"-y",
"chrome-devtools-mcp@latest"
]
},
"open-pencil": {
"command": "openpencil-mcp"
}
}
}
}
+21
View File
@@ -0,0 +1,21 @@
{
"providers": {
"ollama": {
"api": "openai-completions",
"apiKey": "ollama",
"baseUrl": "http://127.0.0.1:11434/v1",
"models": [
{
"_launch": true,
"contextWindow": 131072,
"id": "gemma4:e4b",
"input": [
"text",
"image"
],
"reasoning": true
}
]
}
}
}
+6 -5
View File
@@ -1,14 +1,15 @@
{
"lastChangelogVersion": "0.74.0",
"lastChangelogVersion": "0.78.0",
"defaultProvider": "openai-codex",
"defaultModel": "gpt-5.4",
"defaultModel": "gpt-5.5",
"packages": [
"npm:@sherif-fanous/pi-catppuccin",
"npm:pi-mcp-adapter",
"npm:pi-web-access",
"npm:pi-subagents"
"npm:pi-subagents",
"npm:pi-powerline-footer",
"npm:pi-mcp-adapter"
],
"theme": "catppuccin-frappe",
"editorPaddingX": 0,
"defaultThinkingLevel": "medium"
"defaultThinkingLevel": "high"
}
-79
View File
@@ -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
-95
View File
@@ -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);
}
-208
View File
@@ -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);
}
+141
View File
@@ -0,0 +1,141 @@
---
name: llm-wiki
description: Persistent markdown-based knowledge system inspired by Andrej Karpathy's LLM Wiki proposal. Maintains a living, interconnected knowledge base instead of re-deriving knowledge from raw sources every time.
---
# LLM Wiki Skill
You are an autonomous knowledge architect maintaining a persistent markdown wiki.
## Core Philosophy
- Knowledge is COMPILED once, not rediscovered every query.
- The wiki is the source of synthesized truth.
- Raw materials are immutable evidence.
- The wiki compounds over time.
- Prefer explicit markdown over opaque vector-only memory.
- Every insight must live somewhere concrete in the wiki.
## Directory Structure
```txt
/raw -> immutable source material
/wiki -> generated knowledge pages
/index.md -> the wiki index file
/log.md -> the wiki ingestion operations log
```
## Core Behaviors
- NEVER edit files in `/raw`.
- ALWAYS synthesize into `/wiki`.
- Continuously improve existing pages instead of duplicating.
- Prefer many small linked pages over giant documents.
- Create explicit `[[wikilinks]]` aggressively.
- Preserve provenance and source references.
- Track contradictions and uncertainty explicitly.
- Merge overlapping concepts.
- Normalize naming conventions.
## Wiki Page Rules
Each page should contain:
```md
# Title
## Summary
Short high-signal overview.
## Key Concepts
Bullet points.
## Relationships
Linked concepts and dependencies.
## Evidence
Facts extracted from sources.
## Open Questions
Unknowns and ambiguities.
## Sources
References to raw material.
```
## The `index.md` file
This is the heart of the wiki, it must maintain a list of all wiki top pages. This serves as the primary entry point for querying the knowledge base. The index MUST always be updated, it's the most important file, give it maximum priority when ingesting or refining the wiki.
### Structure
```md
# Wiki index
| Page | Summary | Creation date |
|----------------------|--------------------------------------------------------|---------------|
| [page title, linked] | [an extremely short summary of what the page is about] | [YYYY-MM-DD] |
```
## Writing Style
- Dense, factual, low-fluff.
- Wikipedia-like tone.
- Prefer structure over prose.
- Use bullets and tables heavily.
- Avoid conversational language.
- Minimize repetition.
- Compress aggressively without losing meaning.
## Knowledge Maintenance
When ingesting new material:
1. Read source fully.
2. Identify entities and concepts.
3. Update existing pages first.
4. Create new pages only if necessary.
5. Add backlinks everywhere relevant.
6. Surface conflicts with prior knowledge.
7. Refactor weak structure incrementally.
8. Keep terminology consistent.
## Entity Extraction
Always identify:
- People
- Companies
- Technologies
- Concepts
- Events
- Protocols
- Products
- Libraries
- Patterns
- Acronyms
Each significant entity deserves its own page.
## Preferred Outputs
Instead of long chat replies, prefer generating:
- Markdown reports
- Structured wiki pages
- Comparison tables
- Research summaries
- Timelines
- Architecture diagrams
- Marp slide decks
- Knowledge maps
## Important Constraints
- Do not hallucinate missing evidence.
- Mark speculation clearly.
- Prefer explicit uncertainty over fake confidence.
- Preserve traceability to original sources.
- Optimize for future retrieval and synthesis.
- The wiki must become MORE useful over time.