Skip to content

Commit fe2242f

Browse files
author
DMAir
committed
Initial commit: pubmed-watcher β€” pure Python, zero dependencies
- Source code with CLI - README with documentation - Interactive demo page (GitHub Pages) - .gitignore
0 parents  commit fe2242f

5 files changed

Lines changed: 987 additions & 0 deletions

File tree

β€Ž.gitignoreβ€Ž

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
__pycache__/
2+
*.pyc
3+
.DS_Store
4+
*.bak.json

β€ŽREADME.mdβ€Ž

Lines changed: 159 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,159 @@
1+
# πŸ”¬ PubMed Watcher
2+
3+
Automated alerting for new PubMed papers matching your research interests. Detects **only new papers** by tracking previously seen PMIDs.
4+
5+
Built for cron-based monitoring β€” zero external dependencies, pure Python 3 stdlib.
6+
7+
## Quick Start
8+
9+
```bash
10+
# Run all watched queries, report new papers
11+
python pubmed_watcher.py watch
12+
13+
# First run initializes with 5 default longevity/biotech queries
14+
```
15+
16+
## Commands
17+
18+
### `watch` β€” Check for new papers
19+
20+
```bash
21+
python pubmed_watcher.py watch # Default: last 7 days
22+
python pubmed_watcher.py watch --days 14 # Look back 14 days
23+
python pubmed_watcher.py watch --json # Machine-readable JSON
24+
python pubmed_watcher.py watch --markdown # Telegram-ready markdown
25+
python pubmed_watcher.py watch --days 3 --markdown # Combine flags
26+
```
27+
28+
**How it works:**
29+
1. Runs each configured query against PubMed E-utilities
30+
2. Filters out papers already in `seen.json`
31+
3. Reports only NEW papers
32+
4. Saves newly seen PMIDs to `seen.json`
33+
34+
### `add` β€” Add a watch query
35+
36+
```bash
37+
python pubmed_watcher.py add "CRISPR aging therapy"
38+
python pubmed_watcher.py add "rapamycin longevity clinical trial"
39+
```
40+
41+
### `list` β€” Show all queries
42+
43+
```bash
44+
python pubmed_watcher.py list
45+
```
46+
47+
Output:
48+
```
49+
πŸ“‹ Watched Queries (5)
50+
51+
[1] epigenetic reprogramming aging longevity
52+
Added: 2026-02-17
53+
[2] immunosenescence aging immune system
54+
Added: 2026-02-17
55+
...
56+
```
57+
58+
### `remove` β€” Remove a query by ID
59+
60+
```bash
61+
python pubmed_watcher.py remove 3
62+
```
63+
64+
### `history` β€” Show recent alert runs
65+
66+
```bash
67+
python pubmed_watcher.py history # Last 20 runs
68+
python pubmed_watcher.py history --limit 5 # Last 5 runs
69+
```
70+
71+
### `reset-seen` β€” Clear seen-papers database
72+
73+
```bash
74+
python pubmed_watcher.py reset-seen # Backs up to seen.bak.json first
75+
```
76+
77+
## Output Formats
78+
79+
### Terminal (default)
80+
81+
```
82+
πŸ”” 12 new papers found across 5 queries (last 7 days)
83+
84+
━━━ πŸ”Ž "epigenetic reprogramming aging longevity" β€” 3 new ━━━
85+
86+
πŸ“„ Epigenetic reprogramming reverses age-associated...
87+
πŸ‘€ Zhang W … Bhatt DL
88+
πŸ“° Nature Aging β€’ 2026 Feb
89+
πŸ”— https://pubmed.ncbi.nlm.nih.gov/12345678/
90+
```
91+
92+
### Markdown (`--markdown`)
93+
94+
Telegram-ready formatting with bold titles and inline links.
95+
96+
### JSON (`--json`)
97+
98+
Full structured output for piping to other tools:
99+
100+
```json
101+
[
102+
{
103+
"query": "epigenetic reprogramming aging longevity",
104+
"query_id": 1,
105+
"new_papers": [
106+
{
107+
"pmid": "12345678",
108+
"title": "...",
109+
"authors": ["First Author", "Last Author"],
110+
"journal": "Nature Aging",
111+
"date": "2026 Feb",
112+
"doi": "10.1038/...",
113+
"url": "https://pubmed.ncbi.nlm.nih.gov/12345678/"
114+
}
115+
]
116+
}
117+
]
118+
```
119+
120+
## State Files
121+
122+
| File | Purpose |
123+
|------|---------|
124+
| `config.json` | Watched queries and their IDs |
125+
| `seen.json` | All previously seen PMIDs (prevents re-alerting) |
126+
| `history.json` | Log of past `watch` runs with counts |
127+
128+
All state is stored in the same directory as the script. Safe to version-control `config.json`; the others are ephemeral.
129+
130+
## Default Queries
131+
132+
On first run, these queries are pre-configured:
133+
134+
1. **epigenetic reprogramming aging longevity**
135+
2. **immunosenescence aging immune system**
136+
3. **senolytics senescent cells therapy**
137+
4. **AI drug discovery clinical trial**
138+
5. **biological age clock methylation**
139+
140+
## Cron Setup
141+
142+
```bash
143+
# Run daily at 8 AM, pipe markdown to a notification script
144+
0 8 * * * cd /path/to/pubmed-watcher && python3 pubmed_watcher.py watch --markdown >> /tmp/pubmed-alerts.md 2>&1
145+
```
146+
147+
Or integrate directly with OpenClaw cron for Telegram delivery.
148+
149+
## Technical Details
150+
151+
- **API:** NCBI E-utilities (esearch + efetch)
152+
- **Rate limiting:** 0.5s delay between API calls (NCBI-friendly)
153+
- **Dependencies:** None β€” pure Python 3.9+ stdlib
154+
- **Retry:** 3 attempts with backoff on network errors
155+
- **Date filter:** Uses `edat` (Entrez date) for reliable recency filtering
156+
157+
## License
158+
159+
Internal tool. No license required.

β€Žconfig.jsonβ€Ž

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
{
2+
"queries": [
3+
{
4+
"id": 1,
5+
"query": "epigenetic reprogramming aging longevity",
6+
"added": "2026-02-17T10:04:28.475406+00:00Z"
7+
},
8+
{
9+
"id": 2,
10+
"query": "immunosenescence aging immune system",
11+
"added": "2026-02-17T10:04:28.475406+00:00Z"
12+
},
13+
{
14+
"id": 3,
15+
"query": "senolytics senescent cells therapy",
16+
"added": "2026-02-17T10:04:28.475406+00:00Z"
17+
},
18+
{
19+
"id": 4,
20+
"query": "AI drug discovery clinical trial",
21+
"added": "2026-02-17T10:04:28.475406+00:00Z"
22+
},
23+
{
24+
"id": 5,
25+
"query": "biological age clock methylation",
26+
"added": "2026-02-17T10:04:28.475406+00:00Z"
27+
}
28+
],
29+
"next_id": 7
30+
}

β€Žindex.htmlβ€Ž

Lines changed: 177 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,177 @@
1+
<!DOCTYPE html>
2+
<html lang="en">
3+
<head>
4+
<meta charset="UTF-8">
5+
<meta name="viewport" content="width=device-width, initial-scale=1.0">
6+
<title>pubmed-watcher β€” Automated PubMed Paper Alerts</title>
7+
<meta name="description" content="Automated alerting for new PubMed papers. Delta detection β€” only alerts on unseen papers. Built for cron.">
8+
<link rel="icon" href="data:image/svg+xml,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 100 100'><text y='.9em' font-size='90'>πŸ”¬</text></svg>">
9+
<style>
10+
*,*::before,*::after{margin:0;padding:0;box-sizing:border-box}
11+
:root{--bg:#06080c;--bg2:#0c1018;--bg3:#0f1520;--bg4:#141c28;--accent:#00e5bf;--accent-dim:#00e5bf60;--accent-glow:#00e5bf20;--text:#e2e8f0;--text2:#8892a4;--text3:#4a5568;--border:#1a2332;--red:#ff5f57;--yellow:#febc2e;--green:#28c840;--mono:'SF Mono','Fira Code','JetBrains Mono','Consolas',monospace;--sans:'Inter',-apple-system,BlinkMacSystemFont,system-ui,sans-serif}
12+
html{scroll-behavior:smooth;-webkit-font-smoothing:antialiased}
13+
body{font-family:var(--sans);background:var(--bg);color:var(--text);line-height:1.6;overflow-x:hidden}
14+
::selection{background:var(--accent);color:var(--bg)}
15+
a{color:var(--accent);text-decoration:none}a:hover{text-decoration:underline}
16+
.container{max-width:960px;margin:0 auto;padding:0 clamp(1rem,4vw,2rem)}
17+
.topbar{padding:1rem 0;border-bottom:1px solid var(--border);position:sticky;top:0;z-index:100;background:rgba(6,8,12,.92);backdrop-filter:blur(16px)}
18+
.topbar-inner{display:flex;align-items:center;justify-content:space-between;max-width:960px;margin:0 auto;padding:0 clamp(1rem,4vw,2rem)}
19+
.topbar a{font-family:var(--mono);font-size:.8rem;color:var(--text2)}.topbar a:hover{color:var(--accent);text-decoration:none}
20+
.topbar .logo{font-size:1rem;font-weight:700;color:var(--accent)}
21+
.hero{padding:4rem 0 3rem;text-align:center}
22+
.hero h1{font-size:clamp(2rem,6vw,3.5rem);font-weight:800;letter-spacing:-.03em;margin-bottom:.5rem}
23+
.hero h1 span{background:linear-gradient(135deg,var(--accent),#00b4d8);-webkit-background-clip:text;background-clip:text;-webkit-text-fill-color:transparent}
24+
.hero .sub{font-family:var(--mono);font-size:.85rem;color:var(--text2);margin-bottom:1.5rem}
25+
.hero p{color:var(--text2);max-width:600px;margin:0 auto 2rem;font-size:.95rem}
26+
.badge{display:inline-block;font-family:var(--mono);font-size:.7rem;padding:.25rem .75rem;border:1px solid var(--border);border-radius:100px;color:var(--text3);margin-bottom:1rem}
27+
.section{padding:3rem 0}.section-title{font-family:var(--mono);font-size:.75rem;color:var(--accent);letter-spacing:.1em;text-transform:uppercase;margin-bottom:1.5rem}
28+
.terminal{background:var(--bg);border:1px solid var(--border);border-radius:10px;overflow:hidden;margin-bottom:2rem}
29+
.terminal-bar{display:flex;align-items:center;gap:.75rem;padding:.5rem .75rem;background:var(--bg4);border-bottom:1px solid var(--border)}
30+
.terminal-dots{display:flex;gap:5px}.terminal-dots i{width:8px;height:8px;border-radius:50%;display:block}
31+
.terminal-dots i:nth-child(1){background:var(--red)}.terminal-dots i:nth-child(2){background:var(--yellow)}.terminal-dots i:nth-child(3){background:var(--green)}
32+
.terminal-title{font-family:var(--mono);font-size:.7rem;color:var(--text3)}
33+
.terminal-body{padding:1rem;font-family:var(--mono);font-size:.72rem;line-height:1.6;color:var(--text2);overflow-x:auto;white-space:pre}
34+
.info-grid{display:grid;grid-template-columns:repeat(auto-fit,minmax(280px,1fr));gap:1.5rem;margin-bottom:2rem}
35+
.info-card{background:var(--bg3);border:1px solid var(--border);border-radius:10px;padding:1.25rem}
36+
.info-card h4{font-family:var(--mono);font-size:.85rem;margin-bottom:.75rem}
37+
.info-card p,.info-card li{font-size:.82rem;color:var(--text2);line-height:1.6}
38+
.info-card ul{padding-left:1.25rem}.info-card li{margin-bottom:.25rem}
39+
.flow-steps{display:flex;gap:0;margin-bottom:2rem;flex-wrap:wrap}
40+
.flow-step{flex:1;min-width:150px;background:var(--bg3);border:1px solid var(--border);padding:1rem;text-align:center;position:relative}
41+
.flow-step:first-child{border-radius:8px 0 0 8px}
42+
.flow-step:last-child{border-radius:0 8px 8px 0}
43+
.flow-step .step-num{font-family:var(--mono);font-size:.65rem;color:var(--accent);margin-bottom:.25rem}
44+
.flow-step .step-icon{font-size:1.5rem;margin-bottom:.25rem}
45+
.flow-step .step-text{font-family:var(--mono);font-size:.7rem;color:var(--text2)}
46+
.flow-step::after{content:'β†’';position:absolute;right:-8px;top:50%;transform:translateY(-50%);color:var(--accent);font-family:var(--mono);z-index:1}
47+
.flow-step:last-child::after{content:''}
48+
.query-list{list-style:none;padding:0}
49+
.query-item{display:flex;align-items:center;gap:.75rem;padding:.75rem 1rem;background:var(--bg3);border:1px solid var(--border);border-radius:6px;margin-bottom:.5rem;font-family:var(--mono);font-size:.8rem}
50+
.query-num{color:var(--accent);font-weight:700;width:24px}
51+
.query-text{color:var(--text2)}
52+
.footer{padding:2rem 0;border-top:1px solid var(--border);text-align:center;margin-top:2rem}
53+
.footer p{font-family:var(--mono);font-size:.7rem;color:var(--text3)}
54+
</style>
55+
</head>
56+
<body>
57+
<div class="topbar"><div class="topbar-inner">
58+
<a href="https://qclawq.github.io" class="logo">← Q's Build Lab</a>
59+
<a href="https://github.com/QclawQ/pubmed-watcher" target="_blank">github β†—</a>
60+
</div></div>
61+
62+
<div class="container">
63+
<div class="hero">
64+
<div class="badge">pure python Β· zero dependencies Β· 617 loc</div>
65+
<h1>πŸ”¬ <span>pubmed-watcher</span></h1>
66+
<div class="sub">Automated PubMed Paper Alerts</div>
67+
<p>Watch PubMed queries and get alerted only about <strong>new</strong> papers. Tracks seen PMIDs, supports cron scheduling, outputs to terminal, Markdown, or JSON.</p>
68+
</div>
69+
70+
<div class="section">
71+
<div class="section-title">// how it works</div>
72+
<div class="flow-steps">
73+
<div class="flow-step"><div class="step-num">01</div><div class="step-icon">πŸ”Ž</div><div class="step-text">Query PubMed E-utilities</div></div>
74+
<div class="flow-step"><div class="step-num">02</div><div class="step-icon">πŸ”„</div><div class="step-text">Filter against seen.json</div></div>
75+
<div class="flow-step"><div class="step-num">03</div><div class="step-icon">πŸ“₯</div><div class="step-text">Fetch new paper details</div></div>
76+
<div class="flow-step"><div class="step-num">04</div><div class="step-icon">πŸ””</div><div class="step-text">Alert only NEW papers</div></div>
77+
</div>
78+
</div>
79+
80+
<div class="section">
81+
<div class="section-title">// default queries</div>
82+
<ul class="query-list">
83+
<li class="query-item"><span class="query-num">[1]</span><span class="query-text">epigenetic reprogramming aging longevity</span></li>
84+
<li class="query-item"><span class="query-num">[2]</span><span class="query-text">immunosenescence aging immune system</span></li>
85+
<li class="query-item"><span class="query-num">[3]</span><span class="query-text">senolytics senescent cells therapy</span></li>
86+
<li class="query-item"><span class="query-num">[4]</span><span class="query-text">AI drug discovery clinical trial</span></li>
87+
<li class="query-item"><span class="query-num">[5]</span><span class="query-text">biological age clock methylation</span></li>
88+
</ul>
89+
</div>
90+
91+
<div class="section">
92+
<div class="section-title">// demo output</div>
93+
<div class="terminal">
94+
<div class="terminal-bar"><span class="terminal-dots"><i></i><i></i><i></i></span><span class="terminal-title">$ python3 pubmed_watcher.py watch</span></div>
95+
<div class="terminal-body">πŸ”” 12 new papers found across 5 queries (last 7 days)
96+
97+
━━━ πŸ”Ž "epigenetic reprogramming aging longevity" β€” 3 new ━━━
98+
99+
πŸ“„ Epigenetic reprogramming reverses age-associated
100+
decline in neural stem cell function
101+
πŸ‘€ Zhang W … Bhatt DL
102+
πŸ“° Nature Aging β€’ 2026 Feb
103+
πŸ”— https://pubmed.ncbi.nlm.nih.gov/39847201/
104+
105+
πŸ“„ In vivo partial reprogramming extends lifespan
106+
in progeroid mice via cellular rejuvenation
107+
πŸ‘€ Ocampo A … Izpisua Belmonte JC
108+
πŸ“° Cell β€’ 2026 Feb 10
109+
πŸ”— https://pubmed.ncbi.nlm.nih.gov/39847202/
110+
111+
━━━ πŸ”Ž "senolytics senescent cells therapy" β€” 4 new ━━━
112+
113+
πŸ“„ Dasatinib plus quercetin reduces senescent
114+
cell burden in human adipose tissue
115+
πŸ‘€ Kirkland JL … Tchkonia T
116+
πŸ“° Nature Medicine β€’ 2026 Feb
117+
πŸ”— https://pubmed.ncbi.nlm.nih.gov/39847203/
118+
119+
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
120+
πŸ“Š Total: 12 new | 4/5 queries had results</div>
121+
</div>
122+
123+
<div class="terminal">
124+
<div class="terminal-bar"><span class="terminal-dots"><i></i><i></i><i></i></span><span class="terminal-title">$ python3 pubmed_watcher.py history</span></div>
125+
<div class="terminal-body">πŸ“œ Alert History (last 5 runs)
126+
127+
πŸ”” 2026-02-17 08:00 UTC β€” 12 new papers (5 queries, 7d window)
128+
β€’ epigenetic reprogramming aging longevity: 3 new
129+
β€’ senolytics senescent cells therapy: 4 new
130+
β€’ AI drug discovery clinical trial: 3 new
131+
β€’ biological age clock methylation: 2 new
132+
πŸ”” 2026-02-16 08:00 UTC β€” 8 new papers (5 queries, 7d window)
133+
βœ… 2026-02-15 08:00 UTC β€” 0 new papers (5 queries, 7d window)
134+
πŸ”” 2026-02-14 08:00 UTC β€” 5 new papers (5 queries, 7d window)</div>
135+
</div>
136+
</div>
137+
138+
<div class="section">
139+
<div class="section-title">// features</div>
140+
<div class="info-grid">
141+
<div class="info-card">
142+
<h4>πŸ”„ Delta Detection</h4>
143+
<p>Tracks every PMID in <code>seen.json</code>. You only get alerted once per paper, even across multiple queries. Perfect for daily cron jobs.</p>
144+
</div>
145+
<div class="info-card">
146+
<h4>πŸ“€ Output Formats</h4>
147+
<ul>
148+
<li><code>--terminal</code> β€” Pretty CLI output</li>
149+
<li><code>--markdown</code> β€” Telegram-ready</li>
150+
<li><code>--json</code> β€” Machine-readable</li>
151+
</ul>
152+
</div>
153+
<div class="info-card">
154+
<h4>⏰ Cron Ready</h4>
155+
<p style="font-family:var(--mono);font-size:.72rem">0 8 * * * python3 pubmed_watcher.py watch --markdown >> /tmp/alerts.md</p>
156+
<p style="margin-top:.5rem">Run daily, pipe to Telegram, Slack, or email.</p>
157+
</div>
158+
<div class="info-card">
159+
<h4>πŸ’» Commands</h4>
160+
<ul>
161+
<li><code>watch</code> β€” Check all queries</li>
162+
<li><code>add "query"</code> β€” Add new query</li>
163+
<li><code>list</code> β€” Show queries</li>
164+
<li><code>remove &lt;id&gt;</code> β€” Remove query</li>
165+
<li><code>history</code> β€” Past runs</li>
166+
<li><code>reset-seen</code> β€” Clear DB</li>
167+
</ul>
168+
</div>
169+
</div>
170+
</div>
171+
</div>
172+
173+
<footer class="footer"><div class="container">
174+
<p>pubmed-watcher Β· <a href="https://github.com/QclawQ/pubmed-watcher">source</a> Β· <a href="https://qclawq.github.io">Q's Build Lab</a> Β· NCBI E-utilities</p>
175+
</div></footer>
176+
</body>
177+
</html>

0 commit comments

Comments
Β (0)