Metadata-Version: 2.4
Name: openbionews
Version: 0.8.5
Summary: Free, self-hosted tool that turns biopharma primary sources (ClinicalTrials.gov, openFDA, SEC EDGAR) into a daily brief with per-sentence citations. Deterministic by default; bring your own LLM, or none.
Author: OpenBioNews contributors
License: MIT
Project-URL: Homepage, https://fedor-i.github.io/OpenBioNews/
Project-URL: Repository, https://github.com/fedor-i/OpenBioNews
Project-URL: Issues, https://github.com/fedor-i/OpenBioNews/issues
Project-URL: Changelog, https://github.com/fedor-i/OpenBioNews/blob/main/CHANGELOG.md
Keywords: biotech,pharma,fda,clinical-trials,sec-edgar,regulatory,news,rss,digest,citations,self-hosted,ollama
Classifier: Development Status :: 4 - Beta
Classifier: Environment :: Console
Classifier: Intended Audience :: End Users/Desktop
Classifier: Intended Audience :: Healthcare Industry
Classifier: Intended Audience :: Science/Research
Classifier: License :: OSI Approved :: MIT License
Classifier: Operating System :: OS Independent
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3.9
Classifier: Programming Language :: Python :: 3.10
Classifier: Programming Language :: Python :: 3.11
Classifier: Programming Language :: Python :: 3.12
Classifier: Topic :: Scientific/Engineering :: Bio-Informatics
Classifier: Topic :: Internet :: WWW/HTTP :: Indexing/Search
Requires-Python: >=3.9
Description-Content-Type: text/markdown
License-File: LICENSE
Provides-Extra: feeds
Requires-Dist: feedparser>=6.0; extra == "feeds"
Provides-Extra: mcp
Requires-Dist: mcp>=1.2; python_version >= "3.10" and extra == "mcp"
Provides-Extra: ml
Requires-Dist: scikit-learn>=1.3; extra == "ml"
Provides-Extra: dev
Requires-Dist: pytest>=7.0; extra == "dev"
Dynamic: license-file

# OpenBioNews

[![CI](https://github.com/fedor-i/OpenBioNews/actions/workflows/ci.yml/badge.svg)](https://github.com/fedor-i/OpenBioNews/actions/workflows/ci.yml)
[![License: MIT](https://img.shields.io/badge/License-MIT-blue.svg)](LICENSE)
[![Python 3.9+](https://img.shields.io/badge/python-3.9%2B-blue.svg)](https://www.python.org/)
[![Zero dependencies](https://img.shields.io/badge/dependencies-0-brightgreen.svg)](pyproject.toml)

**A free, self-hosted news digest you run on your own computer.**

OpenBioNews gathers stories from the sources you choose, groups the same story
reported by different outlets into one entry, ranks them by importance, and
writes a clean daily digest — as Markdown, a web page, or plain text, and
optionally straight to your inbox. It ships tuned for **biotech and life
sciences**, but you can point it at any topic.

- 🆓 **Free and open** (MIT). No account, no subscription, no server to rent.
- 🔬 **Primary sources, cited to the sentence.** Track **ClinicalTrials.gov**,
  **FDA** (recalls, approvals, shortages) and **SEC EDGAR** by company, drug or
  condition — and *every sentence* of every brief carries a numbered citation back
  to the exact source record. It's deterministic (verbatim from the record), so
  there's nothing to hallucinate — **no LLM required**. Plus any RSS/Atom feed.
- 🔔 **Tells you what *changed*.** Remembers each trial/filing/record between runs
  and surfaces the diff — a trial gone to *Terminated*, results posted, a recall
  reclassified — so you read the change, not the same snapshot twice.
- 🎯 **Watch by theme, not just disease.** One-tap groups seed your watch list:
  therapeutic areas *and* thematic cohorts like **AI in drug discovery**, **NAM**,
  gene/cell therapy, CRISPR, mRNA, ADCs, radiopharma, GLP-1/obesity. Stack several.
- 🖥️ **Runs everywhere.** Windows, macOS, and Linux — pure Python, no build step.
- 🔌 **Bring your own LLM — or none.** The cited facts are always deterministic;
  an optional local model (Ollama) or OpenAI-compatible API only adds a clearly
  labelled *"Why it matters"* interpretation on top — so the AI never touches the
  record, and there's nothing to hallucinate into it. Runs fully offline.
- 🧩 **Zero dependencies.** The core runs on the Python standard library. If you
  have Python, you can run it.
- 🥇 **Curated, not a firehose.** Stories are de-duplicated across outlets and
  ranked by importance, with a read-time estimate.
- 📬 **Optional email delivery.** Have it mail you the digest every morning via
  any SMTP server.
- 🔒 **Private.** Your feeds and reading habits stay on your machine. With the
  local or no-LLM modes, nothing is sent anywhere.
- 🪄 **One-minute onboarding.** `openbionews setup` walks you through it and
  writes a config customized to you.

> Gathering news into a post is deterministic — grouping and de-duplicating
> stories needs no AI. An LLM is optional and only used to polish the one-line
> summaries, so even a small local model is plenty.

---

## Quick start

You need **Python 3.9 or newer**. Check with `python3 --version` (on Windows,
`py --version`).

Install from PyPI:

```bash
pip install openbionews            # core — pure standard library, no deps
pipx install "openbionews[mcp]"    # optional: + MCP server for AI assistants (Python 3.10+)

openbionews run --demo             # try it offline with bundled sample data
openbionews setup                  # personalize (interactive, ~1 minute)
openbionews run                    # build today's digest
```

That's it — your digest is written to the `digest/` folder as
`digest-YYYY-MM-DD.md` (plus a `latest.md` you can always point to).

Prefer to run from source (or hack on it)? Clone and skip the install:

```bash
git clone https://github.com/fedor-i/OpenBioNews.git
cd OpenBioNews
python3 -m openbionews run --demo
```

### Works on Windows, macOS, and Linux

The tool is pure Python and platform-independent. Command names differ slightly:

| | macOS / Linux | Windows |
| --- | --- | --- |
| Run a module | `python3 -m openbionews …` | `py -m openbionews …` |
| Set an env var (this shell) | `export KEY=value` | `set KEY=value` (cmd) · `$env:KEY="value"` (PowerShell) |
| Schedule it | `cron` | Task Scheduler |

After `pip install .`, the `openbionews` command works the same on every
platform. Output is written as UTF-8 everywhere.

### Optional: install it as a command

```bash
pip install .
openbionews setup      # now available as a plain command
openbionews run
```

---

## Onboarding: `openbionews setup`

The setup wizard is the heart of OpenBioNews. It asks a handful of questions —
each with a sensible default you can accept by pressing **Enter** — and writes a
config file customized to you:

1. **Name your digest** (e.g. *"My Morning Bio Brief"*).
2. **Pick topics.** Choose from built-in bundles (Biotech & Pharma, Regulatory
   (FDA/EMA), Preprints (bioRxiv/medRxiv), Life Science & Research, Health,
   Science, Tech, World) or add your own RSS URLs.
3. **Track primary sources.** Optionally watch **ClinicalTrials.gov**, **FDA**
   recalls/approvals, and **SEC** filings by company, condition, or drug —
   official developments, cited to their source record. Start from a
   **therapeutic-area preset** (oncology, cardiometabolic, rare disease,
   neurology, immunology, infectious disease) to seed the watch list, then
   tweak (see below).
4. **Focus it.** Optionally keep only stories mentioning certain keywords, or
   always drop others.
5. **Choose how summaries are written** — no LLM, local Ollama, or an
   OpenAI-compatible API.
6. **Pick an output format** — Markdown, HTML, text, or an **RSS feed** you can
   subscribe to in any reader.

The result is `openbionews.config.json` in the current folder. Re-run `setup`
any time to change it, or edit the file directly (see
[`config.example.json`](config.example.json)).

---

## Choosing an LLM (or not)

OpenBioNews works three ways. You choose during setup; you can switch any time.

### 1. No LLM (default) — free, instant, offline
Summaries are the article's own opening sentences. No model, no network, no
setup. Great for a fast, private digest.

### 2. Local model via Ollama — free and private
Install [Ollama](https://ollama.com), pull a small model, and OpenBioNews talks
to it on your machine. Nothing leaves your computer.

```bash
ollama pull llama3.2        # or qwen2.5, phi3, gemma2 — small models are fine
# choose "Local model via Ollama" in `openbionews setup`
```

### 3. Any OpenAI-compatible API
Works with OpenAI, OpenRouter, Groq, Together, LM Studio, llama.cpp's server,
vLLM, and more. During setup, give the base URL, model name, and the **name of
an environment variable** that holds your API key. The key is read from the
environment and is **never written to the config file**.

```bash
export OPENAI_API_KEY=sk-...      # or whatever variable you named
openbionews run
```

If a summary call ever fails (rate limit, network blip), that story quietly
falls back to the no-LLM summary — the run never aborts.

---

## Primary sources — traced to the document

Beyond RSS (which is *secondary* trade press), OpenBioNews can read **official
primary records** and trace every development back to its source document.

Nine connectors are built in. You keep **one watch list** (companies,
conditions, drugs, terms) and switch on whichever official sources you want:

- **ClinicalTrials.gov** — trial developments (registration, status, phase),
  cited to the NCT record.
- **openFDA drug recalls** — enforcement reports (reason, Class I/II/III,
  status), cited to the FDA record.
- **openFDA drug approvals** — Drugs@FDA approvals, original and supplemental
  (new indication), cited to the Drugs@FDA page.
- **openFDA drug shortages** — current/resolved shortages (reason, status,
  category), cited to the FDA Drug Shortages database.
- **openFDA drug adverse events (FAERS)** — most-reported reactions per drug
  (spontaneous report counts, not incidence), cited to the openFDA query.
- **openFDA drug labeling** — current FDA label: indications and whether a drug
  carries a boxed warning, cited to the openFDA label record.
- **Federal Register** — FDA guidances, advisory-committee notices and rules,
  cited to the Federal Register document.
- **PubMed** — recent biomedical literature (NCBI E-utilities), cited to the
  PubMed record (and DOI when present).
- **SEC EDGAR** — filings (8-K, 10-K, S-1, 424B, 13D/G, DEF 14A, Form 4…), cited
  to the filing document. SEC asks for a contact User-Agent — set your email in
  `connectors.edgar.user_agent`.

```jsonc
"watchlist": {
  "sponsors": ["Moderna", "Vertex Pharmaceuticals"],
  "conditions": ["cystic fibrosis"],
  "interventions": ["mRNA-1345"],
  "terms": []
},
"connectors": {
  "clinicaltrials":    { "enabled": true, "recent_days": 30 },
  "openfda":           { "enabled": true, "recent_days": 30 },
  "openfda_approvals": { "enabled": true, "recent_days": 90 },
  "openfda_shortages": { "enabled": true, "recent_days": 60, "statuses": ["Current"] },
  "edgar":             { "enabled": true, "recent_days": 30, "forms": ["8-K"],
                         "user_agent": "Your Name your@email.com" }
}
```

The setup wizard builds this for you, and `openbionews doctor` verifies each
source. Every story renders with a tag and a source line:

```
### Study of DrugX in Advanced Solid Tumors
*Recruiting · Phase 2 · Acme Bio · ClinicalTrials.gov · 20 Sep*
A phase 2 study evaluating DrugX…
↳ Source: ClinicalTrials.gov NCT01234567
```

No API keys are required. Because the connectors run on your machine, they can
set the `User-Agent` SEC requires and are not subject to the browser CORS limits
that constrain the hosted web page — this is the reliable path for SEC data.

---

## Hosted web version — filter in your browser, no install

For people who just want to **click and filter**, there's a single-page web app
in [`docs/index.html`](docs/index.html) with a tab for each source, plus a
cross-agency **Insights** digest:

- **Clinical trials** — filter ClinicalTrials.gov by company, condition, drug,
  status, phase, **recency** ("updated within N days"), and
  **industry-sponsored only**.
- **FDA drug recalls** — filter openFDA enforcement reports by firm, product,
  reason, classification (Class I/II/III), status, **report-date recency**, and
  **voluntary vs. FDA-mandated**.
- **FDA drug approvals** — Drugs@FDA approvals (original + supplemental) by
  company or drug, within a chosen window.
- **FDA drug shortages** — current/resolved shortages by drug or company.
- **SEC filings** — full-text search SEC EDGAR by company/ticker/keyword,
  **filing type** (8-K, 10-K, S-1, 424B, 13D/G, DEF 14A, Form 4…), and
  **filed-within** recency; each result links to the filing document. (SEC does
  not reliably allow in-browser cross-origin requests, so when a visitor's
  browser is blocked, this tab falls back to opening the same search on SEC's
  official EDGAR site with the filters applied.)
- **FDA adverse events (FAERS)** — most-reported reactions for a drug (signal,
  not incidence), cited to the openFDA query.
- **FDA labels** — current label indications and boxed-warning status per drug.
- **Federal Register** — FDA guidances, advisory-committee notices and rules.
- **Insights (beta)** — a deterministic, no-LLM cross-agency digest over a time
  window: key stats, emergent themes, a word cloud, rising terms and a
  BM25-ranked source list, every figure counted from the cited records beneath it.

Both query the source **live in the browser**, show a link to each source
record, and support **shareable links** (copy a link that reproduces the exact
filtered view). No server, no backend, no tracking.

**Host it free on GitHub Pages:**

1. Repo → **Settings → Pages → Build and deployment**.
2. **Source: GitHub Actions** (the included workflow deploys `docs/`), *or*
   *Deploy from a branch* → **`main`** / **`/docs`**.
3. Your page goes live at `https://fedor-i.github.io/OpenBioNews/`.

Point a custom domain at it in the same settings page if you like. Because it's
static and calls the public APIs directly, it costs nothing to run and scales to
any number of visitors.

---

## Use it from an AI assistant (MCP)

The core has no LLM and invents nothing — but any assistant that speaks the
[Model Context Protocol](https://modelcontextprotocol.io) (Claude Desktop, Claude
Code, …) can call OpenBioNews as a tool to pull **primary-source** records, each
returned with the exact URL it traces to. The AI gets grounded, citable facts; it
never sees our prose.

**The MCP server needs Python 3.10+** (the `mcp` SDK requires it; the core
pipeline still runs on 3.9). Install it with an isolated installer so the
`openbionews` command lands on your `PATH` — which the MCP config below relies on:

```bash
pipx install "openbionews[mcp]"              # recommended: isolated, command on PATH
# or
uv tool install "openbionews[mcp]"           # same idea, via uv
# or, into an environment you manage yourself:
python3 -m pip install "openbionews[mcp]"    # use python3.10+; see the note below

openbionews mcp                              # speaks MCP over stdio
```

> **On Python 3.9** `pip install "openbionews[mcp]"` can't install the `mcp` SDK,
> so the extra resolves to nothing and you get the core without the server. Use
> Python 3.10+ (`pipx install "openbionews[mcp]" --python python3.12`). A plain
> `pip install --user` also warns that its script directory isn't on `PATH`,
> which breaks the `"command": "openbionews"` config below — prefer `pipx`/`uv`,
> or point `"command"` at `python3` with `"args": ["-m", "openbionews", "mcp"]`.

Ten tools exposed: `search_clinical_trials`, `search_fda_recalls`,
`search_fda_approvals`, `search_fda_shortages`, `search_fda_adverse_events`,
`search_fda_labeling`, `search_federal_register`, `search_pubmed`,
`search_sec_filings`, and `watchlist_digest` (every source at once).

The output is shaped for LLM callers:

- **Lean by default — the citation never drops.** Each record keeps one
  `source: {id, name, url, kind}` object: `source.url` *is* the citation link, always
  present, alongside the record `id` and `date`. Lean only collapses the three
  repeated copies of that link into one — the "every record cited" guarantee holds in
  lean exactly as in verbose. Pass `verbose=true` for the full `citations[]` array and
  raw `meta` (e.g. a PubMed DOI also rides in `source.also`).
- **The numbers travel in `facts`** — enrollment, start/primary-completion dates and
  primary endpoints for trials; reaction counts for FAERS; form/filer/accession for
  SEC; and so on. A model should cite these rather than infer figures. (SEC returns
  the filing *pointer*, not extracted financials — read the cited document for those.)
- **A trustworthy negative** — a successful-but-empty search returns
  `status: "no_current_results"` with the `query_used` and `as_of` date, so "not in
  shortage as of 2026-10-20" is a citable fact, not an inference from a bare `0`.
- **Provenance on every block** — `endpoint`, `retrieved_at`, `as_of`.
- **A relevance signal, and a filter** — each result carries `match: {terms, where}`
  showing which query terms actually hit and where. `search_clinical_trials` and
  `search_pubmed` go further and **drop** strays by default (`require_match=true`):
  a study the registry returns for a different drug, where the query term appears in
  no field, is removed. Set `require_match=false` to see everything and filter
  yourself via `match`.
- **SEC entity scoping** — `search_sec_filings` has two modes. Give `company` (a name,
  ticker, or raw CIK; `query` optional) and it resolves the subject to its CIK and
  returns only that registrant's *own* filings — so a fund's 13F or an NPORT-P that
  merely lists the ticker is excluded (unresolvable names come back as
  `entity_not_found`). Give only `query` for a document full-text search;
  `own_filer_only=true` applies the same entity scoping to a bare `query`.
- **Monitoring + paging** — `since` (YYYY-MM-DD) filters to new records and flags each
  `is_new`; `limit`/`offset` page through results with a `truncated` flag;
  `watchlist_digest` takes `sources=[...]` to query only the agencies you need.
- **Relevance ranking** — every search tool attaches a deterministic score (0–1) to
  each result, reports `ranked_by`, and takes `rank_by` to pick the intent:
  - `recency` (default) — what's new, most recently updated first.
  - `relevance` — lexical **keyword match**, over a wider recent candidate pool. Use it
    to find records containing your terms; use `salience` when you want the important
    ones.
  - `salience` — **what matters**: for trials, an equal blend of enrollment, phase,
    results-posted and maturity (completed/with-results > just-recruiting), with recency
    and keyword match dropped — validated against landmark-trial recall, so a pivotal
    Phase-3/outcomes trial isn't crowded out by a fresher Phase-4. (Recalls weight
    severity; approvals priority; shortages current-vs-resolved.) It searches a **wide
    window and pool**, so a landmark older study can surface instead of being cut by the
    recency window. Sources without a salience signal fall back to `relevance` with a
    `ranking_note`.

  The per-signal breakdown is in `relevance_components` under `verbose`, and
  `watchlist_digest` applies the chosen mode within each source block. This is
  classical scoring — it only orders records that already cite themselves, and invents
  nothing.
- **Semantic ranking (optional `[ml]` extra)** — `rank_by="semantic"` ranks by meaning
  rather than keywords, using TF-IDF + LSA (`pip install "openbionews[ml]"`; classical,
  local, deterministic — no LLM). It surfaces an on-topic record whose wording differs
  from the query. Without the extra it falls back to lexical `relevance` and says so in
  a `ranking_note`. Results report `ranked_by`.

Register it with Claude Desktop (or Claude Code) — add to your MCP config
(`claude_desktop_config.json`).

> **Use an absolute path for `command`.** The desktop app does **not** inherit your
> shell's `PATH`, so a bare `"command": "openbionews"` (or `"uvx"`) usually fails to
> launch. Find the installed binary with `which openbionews` (after `pipx`/`uv tool
> install`) and paste that full path. On macOS it's typically
> `~/.local/bin/openbionews` (pipx) or `~/.local/bin/openbionews` / the uv tools dir.

```json
{
  "mcpServers": {
    "openbionews": {
      "command": "/Users/you/.local/bin/openbionews",
      "args": ["mcp"],
      "env": { "OPENBIONEWS_EDGAR_UA": "Your Name your@email.com" }
    }
  }
}
```

If you'd rather not hard-code a path, point `command` at the Python that has the
package and run it as a module — more portable across machines:

```json
{
  "mcpServers": {
    "openbionews": {
      "command": "/usr/bin/python3",
      "args": ["-m", "openbionews", "mcp"],
      "env": { "OPENBIONEWS_EDGAR_UA": "Your Name your@email.com" }
    }
  }
}
```

**Set `OPENBIONEWS_EDGAR_UA` if you'll use `search_sec_filings`** — SEC rejects EDGAR
requests without a descriptive User-Agent, so skipping it returns a 403 (the tool now
says exactly this and names the variable, rather than surfacing raw 403 HTML). Set it
to `"Your Name your@email.com"`. The other sources don't need it.

Then ask your assistant things like *"any Class I drug recalls for Moderna in the last
90 days?"* and it will call the tool and answer from the cited records.

---

## Commands

| Command | What it does |
| --- | --- |
| `openbionews setup` | Interactive onboarding; writes your config. |
| `openbionews mcp` | Run the MCP server so an AI can call the primary-source connectors. |
| `openbionews run` | Build today's digest and write it to `digest/`. |
| `openbionews run --demo` | Build a digest offline from bundled sample data. |
| `openbionews run --stdout` | Print the digest instead of writing a file. |
| `openbionews run --format html` | Override the output format for one run. |
| `openbionews run --no-summaries` | Headlines and links only. |
| `openbionews run --email` | Also email the digest (uses your email config). |
| `openbionews doctor` | Check your config, feeds, LLM, and email. |
| `openbionews sources` | List the built-in topic bundles and their feeds. |

Use `-c path/to/config.json` to use a config file somewhere other than the
current directory (or set `OPENBIONEWS_CONFIG`).

---

## Run it every morning (automation)

OpenBioNews is a plain command, so any scheduler works.

**macOS / Linux (cron)** — build a digest every weekday at 7am:

```cron
0 7 * * 1-5  cd /path/to/OpenBioNews && /usr/bin/python3 -m openbionews run
```

**Windows (Task Scheduler)** — create a Basic Task that runs
`python -m openbionews run` in the project folder on your schedule.

Point your reader, a static-site folder, or an email script at
`digest/latest.<ext>`. With `format: rss` (or `openbionews run --format rss`),
that's `digest/latest.xml` — a standard RSS feed you can subscribe to in any
feed reader, so a scheduled run keeps your reader up to date automatically.

---

## Email delivery (optional)

Have OpenBioNews mail you the digest every morning. Enable it during
`openbionews setup`, or set the `email` block in your config. It works with any
SMTP server (your own, Gmail with an app password, Fastmail, a work relay…).

```jsonc
"email": {
  "enabled": true,
  "smtp_host": "smtp.gmail.com",
  "smtp_port": 587,
  "use_tls": true,
  "username": "you@gmail.com",
  "password_env": "OPENBIONEWS_SMTP_PASSWORD",  // password read from this env var
  "from_addr": "you@gmail.com",
  "to_addrs": ["you@gmail.com"]
}
```

Set the password in your environment (never in the file) and run:

```bash
export OPENBIONEWS_SMTP_PASSWORD=your-app-password   # Windows: set / $env:
openbionews run --email
```

The email is sent as both a formatted HTML version and a plain-text fallback.

---

## Curation & "why it matters"

OpenBioNews doesn't just dump every headline. Stories are:

1. **De-duplicated** across outlets (the same story from five sites becomes one
   entry that lists all five).
2. **Ranked by importance** — a transparent, deterministic score combining how
   many outlets carried a story, how fresh it is, and whether the headline
   contains high-signal words (approval, acquires, phase, recall…).
3. **Capped** to the top `max_items` (and `max_per_topic`), so you get a
   focused read with a **read-time estimate** in the header.

If you use an LLM backend, turn on **"why it matters"** in setup to add a short
significance note under each summary, generated locally or with your own key. It is
always rendered as a clearly labelled *"Why it matters (AI analysis)"* line, kept
separate from the deterministic, cited facts above it — so interpretation is never
mistaken for a sourced claim.

> **Pick a capable model for this note.** The cited facts never touch an LLM, but the
> optional note does, and a *small* local model will confabulate — in our testing a
> 9B model invented a precise, fake net-loss figure when the numbers weren't in front
> of it. Prefer a strong model (a frontier API, or a larger local model with good
> instruction-following) for "why it matters"; treat a tiny model's note as throwaway.
> If a note ever states a number, trust the cited record, not the note.

---

## How it works

```
feeds ─▶ fetch ─▶ filter ─▶ cluster ─▶ rank ─▶ summarize ─▶ render ─▶ digest
        (RSS/Atom) (age &   (group the  (biggest  (LLM or   (md/html/
                    keywords) same story  first)   no-LLM)   text)
                              across
                              outlets)
```

Everything except the optional summary step is **deterministic**: stories are
grouped by how much their headlines overlap (Jaccard similarity of title
tokens), so results are reproducible and need no AI. See
[`ARCHITECTURE.md`](ARCHITECTURE.md) for a module-by-module tour.

---

## Customizing sources

Run `openbionews sources` to see the built-in bundles. To add your own, either
choose *"Add your own RSS feed"* during setup, or edit the `feeds` list in your
config:

```json
{
  "feeds": [
    { "name": "My Favorite Blog", "url": "https://example.com/feed.xml", "topic": "biotech" }
  ]
}
```

Any RSS 2.0 or Atom feed works. Run `openbionews doctor` to confirm a new feed
is reachable.

---

## Privacy

- **No-LLM and Ollama modes send nothing off your machine.** OpenBioNews only
  fetches the feeds you list.
- **API mode** sends article titles and feed-provided descriptions to the
  endpoint you configured, only to write summaries. Your API key stays in your
  environment, never in the config file.
- No telemetry, ever.

---

## Requirements

- Python 3.9+ (standard library only for the core).
- Optional: [`feedparser`](https://pypi.org/project/feedparser/) for wider feed
  compatibility (`pip install .[feeds]`).
- Optional: [Ollama](https://ollama.com) for a free local model.

---

## Contributing

Contributions of every size are welcome — a typo fix, a new feed, or a whole new
primary-source connector. Start with [`CONTRIBUTING.md`](CONTRIBUTING.md), see
where things are headed in [`ROADMAP.md`](ROADMAP.md), and check the
[good first issues](https://github.com/fedor-i/OpenBioNews/issues?q=is%3Aopen+label%3A%22good+first+issue%22).

The test suite runs with no dependencies (each file prints ok/FAIL per test):

```bash
python3 tests/test_pipeline.py      # core pipeline
python3 tests/test_web_insights.py  # hosted Insights analytics (structure guards)
python3 tests/test_mcp.py           # MCP tool logic
python3 tests/test_mcp_server.py    # MCP server wiring (skips without the mcp extra)
```

CI runs these on Linux, macOS and Windows across Python 3.9–3.12 on every pull
request. Releases publish to PyPI automatically ([`RELEASING.md`](RELEASING.md)).

## License

[MIT](LICENSE). Free to use, modify, and redistribute. This is an independent,
community-oriented, open-source tool.
