summarizer
summarize is a Linux-and-macOS command-line summarizer that turns a file or
stdin into a concise summary using a local LLM server you already run
(Ollama, llama.cpp, LM Studio, or any OpenAI-compatible server). It is a Unix
tool: pipe a document in, get a summary on stdout, with long-document chunking,
a trusted/untrusted input boundary, stable JSON output, and diagnostics.
Nothing leaves your machine by default: every request goes to a model server
over loopback (http://localhost:11434). Two opt-ins can produce other
traffic: --check-update (one request to a URL you configure in [update]),
and a non-loopback endpoint, which is refused unless you set
[engine] allow_non_loopback. See docs/security.md.
- MIT-licensed, Python 3.11+, zero runtime dependencies in the core install (PDF and OCR are optional extras). No cloud, no telemetry, no model download, no self-update.
Why not just ollama run?
ollama run prints whatever the model says, with no structure around it.
summarize wraps the same local model with the things a summarizer needs:
documents longer than the context window are chunked and reduced; the document
is wrapped in a fresh random boundary and treated as untrusted data while
--context stays trusted; --format json gives a stable envelope with chunk
provenance; and summarize doctor tells you what is reachable, configured, and
missing. It is one small tool, not a chat client.
Example
Input (docs/examples/cache-incident.md, an original sample), summarized with
the default plain profile on qwen2.5-3b-16k (Ollama 0.35.0):
$ summarize docs/examples/cache-incident.md
# Cache incident review: Stale Prices During Node Replacement
On 14 March, at 02:40, the night-shift on-call engineer replaced a failing cache node in the `pricing` cluster. The node had been experiencing intermittent timeouts for about twenty minutes, and the replacement was treated as routine. During the swap, the pricing API served roughly four percent of requests from a replica that had not yet caught up. This resulted in some customers seeing prices that were up to an hour old for about three and a half minutes. No orders were placed at the stale prices, as the checkout service re-quotes against the primary node before authorization, and the seven orders that arrived during the window were priced from fresh data.
The on-call engineer noticed a rise in stale-read metrics at 02:52 and rolled the replica back at 03:05. Error rates were normal by 03:20, and the stale-read rate returned to its baseline shortly afterwards.
… (excerpt; the full output also covers impact, cause, and follow-up actions)
Output varies from run to run and between models. The --format json envelope
and the full schema are in
docs/readers.md.
Table of contents
- Why not just
ollama run? - Example
- Requirements
- Install
- Quick start (Ollama)
- Common usage
- Supported backends
- Supported inputs
- Configuration
- Profiles
- Safety: trusted vs. untrusted input
- Verifying a summary
- Output and the JSON schema
- Local state
- Commands
- Evaluation is mechanical
- Speed and system load
- Troubleshooting
- When not to use this
- How this differs from other tools
- Documentation
- Contributing, changelog, license
Requirements
- Python 3.11 or newer.
- A local model server with at least one model pulled. The default backend is Ollama; see Quick start.
- A model that fits your machine. A 3B–8B instruct model is plenty. A Q4
model needs roughly its download size of RAM; if it fits in GPU memory it is
fast, and if it spills to CPU it can drop to ~1 token/second.
ollama psshows the CPU/GPU split and the serving window. - Platforms: tested in CI on Linux and macOS (both run the full
suite); WSL is expected to work. The code uses only POSIX APIs (
flock, signals). Native Windows is unsupported.
Install
Not on PyPI yet (planned distribution name: local-terminal-summarizer).
Install from a checkout with
pipx (isolated, puts summarize on your PATH):
git clone https://github.com/adigaiki/local-terminal-summarizer.git
cd local-terminal-summarizer
pipx install . # or: uv tool install .
From a virtualenv instead: python3 -m venv .venv && .venv/bin/python -m pip install -e '.[test]'. Optional extras (the base
install stays dependency-free): pipx install '.[pdf]' for PDF text and
pipx install '.[ocr]' for OCR (which also needs system tesseract and
Poppler). Uninstall with pipx uninstall local-terminal-summarizer.
Releases, packaging, and TestPyPI-first publishing are in docs/installation.md.
Quick start (Ollama)
# 1. Install Ollama (Linux) and start it: https://ollama.com/download
curl -fsSL https://ollama.com/install.sh | sh
# 2. Pull a model that fits your machine.
ollama pull qwen2.5:3b
# 3. Check the whole setup before you trust it.
summarize doctor
summarize doctor is the first thing to run when anything is wrong. It checks
configuration, whether the backend is reachable, whether the configured model
exists, the context window and where it came from, and which optional extras
are installed. A healthy run looks like this (with the custom 16k variant from
the note below configured):
✓ Backend reachable — http://localhost:11434
✓ Model available — configured model 'qwen2.5-3b-16k' is installed
✓ Context window known — 16384 tokens (server)
! OCR — optional, not installed
Everything looks good.
The full output, and what a failure looks like, are in docs/troubleshooting.md.
The window doctor reports is state-dependent. With the model unloaded,
Ollama answers with the model's trained maximum (/api/show); once it is
loaded it serves a smaller window, visible as the CONTEXT column of
ollama ps. A plain ollama pull qwen2.5:3b reports a trained maximum of
32768 while a 4 GiB GPU serves 4096. A normal run warms the model with one
tiny request and re-reads the served window before planning, so it does not
send an over-large prompt; doctor never warms anything and shows a !
"Serving context window" line when it only has the trained maximum.
To give a model a larger served window, create a derived model (verified):
cat > Modelfile <<'EOF'
FROM qwen2.5:3b
PARAMETER num_ctx 16384
EOF
ollama create qwen2.5-3b-16k -f Modelfile
Tested against Ollama 0.35.0. The adapter uses /api/tags (model list),
/api/show (context), /api/ps (serving window of the loaded model), and the
OpenAI-compatible /v1/chat/completions endpoint; doctor reports whichever
of these it can reach.
Common usage
summarize article.md
git diff | summarize --profile code
summarize report.pdf --profile academic
summarize scan.pdf --ocr # OCR a scanned PDF (optional extra)
summarize notes.txt --format json | jq
summarize notes.txt -o summary.md
summarize incident.txt --context glossary.md # glossary.md is trusted, incident.txt stays untrusted
summarize notes.txt --dry-run # plan only; contacts no backend
summarize --stats article.md # timing and token counts on stderr
summarize paper.pdf --verify # flag summary numbers/expansions absent from the source
summarize doctor # environment diagnostics
summarize - reads stdin explicitly. With no input argument and a TTY stdin,
summarize reports an error instead of waiting forever. Omitting --profile
gives the default profile, plain (see Profiles).
One input per invocation. summarize a.md b.md is a usage error; batch
with the shell:
for f in docs/*.md; do summarize "$f" -o "${f%.md}.summary.md"; done
# or: printf '%s\0' docs/*.md | xargs -0 -n1 summarize
Supported backends
There are no cloud providers. A backend is a local adapter:
backend |
Default endpoint | Notes |
|---|---|---|
ollama |
http://localhost:11434 |
native model/context discovery |
openai-compatible (alias openai) |
configured | any local OpenAI Chat Completions server — this covers vLLM, text-generation-webui, and other /v1 servers |
llama.cpp (alias llama-cpp) |
http://localhost:8080 |
llama.cpp server |
lmstudio (alias lm-studio) |
http://localhost:1234 |
LM Studio |
Selecting a backend without setting an endpoint uses its loopback default. Capability negotiation (streaming, structured JSON, reasoning control, context length) is described in docs/backends.md.
Supported inputs
| Input | Reader | Notes |
|---|---|---|
- or no argument |
stdin | bounded; a TTY with no input is an error |
.txt, .log, unknown text extensions |
text | any file whose content is valid text |
.md, .markdown, .mdown, .mkd |
markdown | preserved verbatim; never rendered or fetched |
.py, .rs, .c, .go, .toml, … |
code | passed through unmodified |
.pdf |
PDF (optional extra) | page-aware; scanned PDFs need --ocr |
There is no DOCX/HTML/EPUB reader — convert those to text first. NUL bytes are rejected as binary. Details: docs/readers.md.
Configuration
Configuration is layered (later wins): CLI → SUMMARIZER_* environment →
project ./summarizer.toml (opt-in) → user config → built-in defaults. The
user config lives at $XDG_CONFIG_HOME/summarizer/config.toml or
~/.config/summarizer/config.toml:
[engine]
backend = "ollama"
endpoint = "http://localhost:11434"
model = "qwen2.5:3b"
timeout_seconds = 60
Or override for one run: SUMMARIZER_MODEL=qwen3:8b summarize article.md.
A project ./summarizer.toml is read only when you opt in
(SUMMARIZER_PROJECT_CONFIG=1). Every key, the precedence rules, the
environment variables, and a full example are in
docs/configuration.md and
config.example.toml.
Profiles
Built-in profiles: plain (the default), code, academic, and
meeting. Add or override one by dropping NAME.md into
~/.config/summarizer/prompts/. summarize profiles lists them. See
docs/configuration.md.
Safety: trusted vs. untrusted input
Document text is untrusted: each request wraps it in a fresh random boundary
checked against the content first, and map-reduce interim summaries get the
same treatment. --context FILE is the one input you mark as trusted — it
is kept in a separate labelled section, never mixed with the document.
These are structural guarantees, not a solved problem. See SECURITY.md (threat model and private vulnerability reporting — hence two security docs), docs/design.md, and docs/security.md.
Verifying a summary
--verify reports numbers and ACRONYM (expansion) phrases in the summary
that do not appear in the source: grep, not a judge model, and advisory (exit
status unchanged). It does not catch a real number on the wrong label or
non-numeric fabrication. Details: docs/verify.md.
Output and the JSON schema
Default output is Markdown on stdout. --format json emits a stable envelope
(document, profile, engine, strategy, chunks, summary,
chunk_provenance, duration_seconds; --verify adds verification, and a
detected truncation adds truncated). Schema and example:
docs/readers.md. Provenance
never leaks into plain/Markdown output and never enters trusted prompt
instructions.
Local state
summarize writes nothing by default. Two optional, plain-file stores:
the cache (summarize cache status|path|clear; off by default; derived
summaries only, keyed by content hash + model/profile, under
~/.cache/summarizer, never document text or paths) and sessions
(summarize session start|end|status; pointers only, recorded only when
explicitly selected). See docs/performance.md and
docs/sessions.md.
Commands
| Command | Purpose |
|---|---|
summarize FILE / - |
summarize a file or stdin |
summarize doctor |
installation, configuration, backend, model, and cache diagnostics |
summarize models |
models the configured local backend reports |
summarize profiles |
installed profiles and their prompt identity |
summarize config show|path|validate |
resolved configuration and locations |
summarize cache status|path|clear |
inspect and clear the optional cache (off by default) |
summarize evaluate |
run the local model/backend evaluation corpus |
summarize session start|end|status |
group runs under a named local record |
summarize completions bash|zsh|fish |
shell completion script |
Every option (including --verify, --context, --dry-run, --stats,
--check-update, -o) and the exit status are in docs/cli.md.
Evaluation is mechanical
summarize evaluate applies mechanical checks (valid format/JSON, sentinel
retention, marker absence, strategy, bounds) — not objective quality scores,
and no judge model. Whether summaries are actually good is addressed, in part
and with caveats, in docs/quality.md; the harness is
docs/evaluation.md.
Speed and system load
Latency is dominated by whether the model fits your GPU. Measured on a laptop
with a 4 GiB-VRAM GPU: qwen2.5-3b-16k (GPU-resident) summarized a
14,000-token paper directly in ~21 s; qwen3:8b, which does not fit and spills
to CPU, runs ~1.2 tok/s. Keeping a document in one direct call is usually
faster and more coherent than map-reduce. The CLI is GPU-agnostic; the numbers
and the full picture are in docs/performance.md.
Troubleshooting
connection refused/Backend reachable ✗— start your model server (ollama serve, or the llama.cpp/LM Studio server) and confirm the endpoint insummarize doctor. URL, model, and backend come from config orSUMMARIZER_ENDPOINT/SUMMARIZER_MODEL/SUMMARIZER_BACKEND.the model is not available on the endpoint— pull it (ollama pull qwen2.5:3b) or set[engine] modelto an installed name.summarize modelslists what the backend reports.the backend processed ... prompt tokens ... truncated(or"truncated": true) — the prompt was likely truncated; see docs/troubleshooting.md for the fix and the streaming caveat.
Everything else — PDFs and OCR, timeouts, context overflow, binary files, the no-input error — is in docs/troubleshooting.md.
When not to use this
summarize produces a probabilistic summary from a local model. It is the
wrong tool when being wrong is expensive:
- Where a wrong number or claim matters (legal, medical, financial,
safety-critical).
--verifyis a grep, not fact-checking, and a clean report is not proof; treat--format jsonas untrusted at any automation boundary. - Wide tables on a small model. Table structuring fixes column assignment, not judgment; a 3B model can still garble an 8-column table.
- Documents needing reasoning across the whole text. Map-reduce summarizes
chunks independently, so a term defined in one part and used far away can be
lost; prefer a model whose context holds the document in one call
(
--dry-runshows the plan,--strictrefuses to chunk). - Content you would not send to a local model. The tool does not redact; the model sees the document.
The honest state of summary quality is in docs/quality.md.
How this differs from other tools
Not better — different. summarize is a summarization pipeline with an
explicit trust boundary, strict context budgeting, and validated output;
general-purpose tools may fit better if you need chat, plugins, or cloud
models. Claims below are from each project's own README.
| Tool | What it is | How summarize differs |
|---|---|---|
ollama run |
One prompt against a local model | No chunking, budgeting, trust boundary, or output validation |
llm |
CLI/Python for many models, remote and local (Ollama via a plugin), with SQLite logging, embeddings, schemas and tools | General-purpose; long-document summarization is on you |
fabric |
A library of reusable prompts ("Patterns", including summarize) over many providers, with a REST API |
Prompt-library-first; summarize is pipeline-first and local-only |
Documentation
The README is the orientation; the detail lives in docs/: Installation · Configuration · CLI · Backends · Performance and local state · Readers · Claim verification · Troubleshooting · Sessions · Design and safety · Security and privacy · Evaluation · Summary quality.
Contributing, changelog, license
- Contributing and the repository safety guard: CONTRIBUTING.md.
- Version history: CHANGELOG.md.
- Design notes and deferred work: ROADMAP.md.
- License: MIT — see LICENSE.
Metadata
Release files for local-terminal-summarizer 0.7.0
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | |
|---|---|---|---|
| local_terminal_summarizer-0.7.0.tar.gz | 196.5 kB | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| local_terminal_summarizer-0.7.0-py3-none-any.whl | Python 3 | none | any | Details |
Total release size: 353.2 kB
Release files / local_terminal_summarizer-0.7.0.tar.gz
| Download URL | local_terminal_summarizer-0.7.0.tar.gz |
|---|---|
| Size | 196.5 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
f4a07df7fbb5e198d6946792bb99c11a54184a1030cd86a7f262396ebd677bcd
|
|
BLAKE2b-256 checksum How to use checksums |
84c81b36dd094853e314520650867577d571ee5a0b3bed27e6ab3dfc6f06a192
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
twine/7.0.0 CPython/3.13.15
|
Release files / local_terminal_summarizer-0.7.0-py3-none-any.whl
| Download URL | local_terminal_summarizer-0.7.0-py3-none-any.whl |
|---|---|
| Size | 156.7 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
d1f66f5c3369752bcdb7201a3069f663d512d6e38f22d3ae4b663f3875d5919c
|
|
BLAKE2b-256 checksum How to use checksums |
9608509b260b6a3f9f70b1d2b77573f617c8078452f969151056743502eb9409
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
twine/7.0.0 CPython/3.13.15
|