Convert complex text to plain language. CLI, library, and tiny web UI — powered by your choice of LLM.
grugnews takes any article, PDF, URL, or pasted text and rewrites it in grug speak — short sentences, simple words, honest voice, no jargon. Same facts, less fog.
$ grugnews convert "https://example.com/quarterly-earnings-report"
source: https://example.com/quarterly-earnings-report
title: Q4 Earnings Beat Expectations
via: anthropic · claude-sonnet-4-5
big company make money. more than smart-brain people guess.
they sell more shiney rectangle than last time. number go up 12%.
boss say next year also good but not promise.
grug think: company doing fine. stock probably go up tomorrow.
Two pieces:
- OSS tool (this repo) — the CLI + library + local web UI. Bring your own API key.
- Hosted product (grugnews-platform, coming soon) — curated daily feed, no setup, no key needed.
Inspired by grugbrain.dev — the "grug brained developer" essay that proved cutting jargon makes complex ideas land harder, not softer. News has the same jargon problem on a much bigger surface area: politics, science, finance, sport, tech. Grug just want to know what happen.
- You hand grug some input — a URL, a chunk of text, a PDF, or anything piped through stdin
- grugnews fetches/extracts the text (article-extractor for URLs, pdf-parse for PDFs)
- The text + a system prompt encoding the grug voice gets sent to your chosen LLM
- You get back the grug-translated version
Built on the Vercel AI SDK, so you can swap providers freely.
- Three input modes — paste text, drop a URL, or upload a file (PDF, txt, md, html, csv, json, xml)
- Three provider modes — Anthropic Claude, OpenAI GPT, or any OpenAI-compatible local model (Ollama, LM Studio, llama.cpp, vLLM, OpenRouter, Together, Groq…)
- Auto-detect provider — set whichever API key you have, grugnews picks it up
- CLI —
grugnews convertfor one-shot translations, supports stdin pipe - Library —
import { convert } from 'grugnews'for programmatic use - Local web UI —
grugnews uispins up a self-contained browser app on localhost - No telemetry — it's your text and your API key
# Install globally (or use npx)
npm install -g grugnews
# Set whichever API key you have
export ANTHROPIC_API_KEY=sk-ant-...
# OR
export OPENAI_API_KEY=sk-...
# Convert anything
grugnews convert "https://www.bbc.com/news/some-article"
grugnews convert "paste your article text in quotes"
grugnews convert --file research-paper.pdf
cat article.txt | grugnews convert
# Or open the local web UI
grugnews uiNo install? Use npx:
npx grugnews convert "https://example.com/article"
npx grugnews ui| Option | Default | Description |
|---|---|---|
[input] |
reads stdin | Text or URL to convert. If omitted, reads piped stdin |
--file <path> |
— | Path to a file (PDF, txt, md, html, csv, json, xml) |
--provider <name> |
auto-detect | anthropic, openai, or openai-compatible |
--api-key <key> |
env var | Overrides the provider's env var |
--model <name> |
provider default | Model identifier (e.g. claude-opus-4-5, gpt-4o, llama3.2) |
--base-url <url> |
http://localhost:11434/v1 |
Base URL for openai-compatible providers |
--raw |
false | Print only the grug text — skip the source/provider header |
Starts a local web server with a paste/URL/file UI. Per-request provider/model overrides (stored in browser localStorage).
| Option | Default | Description |
|---|---|---|
-p, --port <number> |
3000 |
Port to listen on |
--provider <name> |
auto-detect | Default provider for requests |
--api-key <key> |
env var | Default API key |
--model <name> |
provider default | Default model |
--base-url <url> |
— | Default base URL for openai-compatible |
grugnews picks the first provider it finds an API key for, in this order:
ANTHROPIC_API_KEY→anthropic(default model:claude-sonnet-4-6)OPENAI_API_KEY→openai(default model:gpt-5.4-mini)GRUG_BASE_URL→openai-compatible(default model:llama3.2)
Override explicitly with --provider.
Anything that exposes an OpenAI-compatible /v1/chat/completions endpoint will work.
# Ollama (default)
ollama pull llama3.2
grugnews convert "..." --provider openai-compatible --model llama3.2
# Or set env vars and forget
export GRUG_BASE_URL=http://localhost:11434/v1
export GRUG_MODEL=llama3.2
grugnews convert "..."
# LM Studio
grugnews convert "..." \
--provider openai-compatible \
--base-url http://localhost:1234/v1 \
--model your-loaded-model
# Hosted OpenAI-compatible (OpenRouter, Together, Groq…)
grugnews convert "..." \
--provider openai-compatible \
--base-url https://openrouter.ai/api/v1 \
--api-key sk-or-... \
--model anthropic/claude-3.5-sonnet| Env var | Used by | Purpose |
|---|---|---|
ANTHROPIC_API_KEY |
anthropic | Claude API key |
OPENAI_API_KEY |
openai | OpenAI API key |
GRUG_BASE_URL |
openai-compatible | Endpoint URL (e.g. http://localhost:11434/v1) |
GRUG_API_KEY |
openai-compatible | Optional key for hosted compatible APIs |
GRUG_MODEL |
openai-compatible | Default model name |
import { convert } from 'grugnews';
// Plain text
const r1 = await convert('The Federal Reserve cut rates by 25 basis points...');
console.log(r1.text);
// URL
const r2 = await convert('https://example.com/article');
console.log(r2.source?.title, r2.text);
// File buffer
import { readFile } from 'node:fs/promises';
const buffer = await readFile('paper.pdf');
const r3 = await convert({ buffer, fileName: 'paper.pdf' });
// Override provider per-call
const r4 = await convert('...', {
provider: 'openai-compatible',
baseURL: 'http://localhost:11434/v1',
model: 'llama3.2',
});interface ConvertOptions {
provider?: 'anthropic' | 'openai' | 'openai-compatible';
apiKey?: string;
model?: string;
maxTokens?: number; // default: 4096
baseURL?: string; // for openai-compatible
}
interface ConvertResult {
text: string; // the grug-ified output
originalText: string; // extracted source text
providerUsed: ProviderName;
modelUsed: string;
source?: { url: string; title?: string; author?: string; publishedDate?: string };
file?: { fileName: string };
}The system prompt enforces seven rules borrowed from the original grugbrain.dev voice:
- Short sentence. One idea per sentence.
- Simple word. "shiney rectangle" not "flagship smartphone".
- Grug narrates. First person — "grug think", "grug not sure".
- Honest about confusion. If the source is messy, grug says so.
- Keep the facts. Numbers, names, dates stay accurate.
- Land a closing line. Last sentence is the take.
- No jargon laundering. Define technical terms in grug terms first.
Tweak it in src/prompt.ts.
git clone https://github.com/mitchfitzsimmons/grugnews.git
cd grugnews
npm install
npm run build
npm link # makes `grugnews` available globally for testingThe codebase is small TypeScript with a clean strategy pattern for providers. Add a new provider by dropping a file into src/providers/ and a case into the factory.
npm run build # tsup, dual CJS/ESM + CLI binary
npm run dev # watch mode
npm run typecheck # tsc --noEmit