Skip to content
mitchfitzsimmonsPublic

About

Convert complex text to plain language (grug speak). CLI, library, and local web UI. Bring your own LLM (Anthropic, OpenAI, or local Ollama / OpenAI-compatible).

Topics

Resources

Stars

0 stars

Watchers

0 watching

Forks

Latest commit

 

History

1 Commit

Folders and files

Repository files navigation

grugnews

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.

Why

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.

How It Works

  1. You hand grug some input — a URL, a chunk of text, a PDF, or anything piped through stdin
  2. grugnews fetches/extracts the text (article-extractor for URLs, pdf-parse for PDFs)
  3. The text + a system prompt encoding the grug voice gets sent to your chosen LLM
  4. You get back the grug-translated version

Built on the Vercel AI SDK, so you can swap providers freely.

Features

  • 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 convert for one-shot translations, supports stdin pipe
  • Library — import { convert } from 'grugnews' for programmatic use
  • Local web UI — grugnews ui spins up a self-contained browser app on localhost
  • No telemetry — it's your text and your API key

Quick Start

# 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 ui

No install? Use npx:

npx grugnews convert "https://example.com/article"
npx grugnews ui

CLI

grugnews convert [input]

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

grugnews ui

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

Providers

grugnews picks the first provider it finds an API key for, in this order:

  1. ANTHROPIC_API_KEY → anthropic (default model: claude-sonnet-4-6)
  2. OPENAI_API_KEY → openai (default model: gpt-5.4-mini)
  3. GRUG_BASE_URL → openai-compatible (default model: llama3.2)

Override explicitly with --provider.

Local models (Ollama, LM Studio, etc.)

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

Library Usage

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',
});

Types

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 };
}

What Makes a Good Grug Translation

The system prompt enforces seven rules borrowed from the original grugbrain.dev voice:

  1. Short sentence. One idea per sentence.
  2. Simple word. "shiney rectangle" not "flagship smartphone".
  3. Grug narrates. First person — "grug think", "grug not sure".
  4. Honest about confusion. If the source is messy, grug says so.
  5. Keep the facts. Numbers, names, dates stay accurate.
  6. Land a closing line. Last sentence is the take.
  7. No jargon laundering. Define technical terms in grug terms first.

Tweak it in src/prompt.ts.

Contributing

git clone https://github.com/mitchfitzsimmons/grugnews.git
cd grugnews
npm install
npm run build
npm link              # makes `grugnews` available globally for testing

The 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

License

MIT

About

Convert complex text to plain language (grug speak). CLI, library, and local web UI. Bring your own LLM (Anthropic, OpenAI, or local Ollama / OpenAI-compatible).

Topics

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Used by

Contributors

Languages