Periodically fetches articles from a Raindrop.io collection, extracts their content, summarizes them with AI, and outputs a priority-ranked HTML dashboard.
The name Tsuyu-mi comes from: Raindrop → shizuku (雫, droplet) → tsuyu (露, dew) → tsuyu-mi (露見, "seeing the dew").
Triage your "read later" articles saved in Raindrop — before reading the full text.
- Read now — timely or high-value
- Defer — interesting but not urgent
- Drop — safe to discard
git clone https://github.com/unsolublesugar/tsuyu-mi.git
cd tsuyu-miNote
Python 3.11 or later is required. uv can install Python itself alongside dependencies.
# Using uv (recommended)
uv venv --python 3.11
source .venv/bin/activate
uv pip install -e ".[dev]"
# Using pip
python -m venv .venv
source .venv/bin/activate
pip install -e ".[dev]"This tool requires a Raindrop.io API token and an LLM API key.
- Go to Raindrop.io Integrations
- Click Create new app under "For Developers"
- Enter an app name (e.g.
RaindropSummarizer) and create it - Click the app → Create test token
- Copy the displayed token
- Open Raindrop.io
- Navigate to the target collection (e.g. "Unsorted")
- Check the URL:
https://app.raindrop.io/my/{collection_id}— the numeric part is the collection ID
Obtain an API key from one of the following providers:
Google Gemini (recommended — has a free tier)
- Go to Google AI Studio
- Create API Key → Create API key in new project
- Recommended model:
gemini-3.5-flash-lite
Tip
You may need to link a Google Cloud billing account and enable the Gemini API.
OpenAI
- Go to OpenAI API Keys
- Create new secret key
- Recommended model:
gpt-5.6-luna
Anthropic
- Go to Anthropic Console
- Create Key
- Recommended model:
claude-haiku-4-5
This tool does a 3-line summary plus 4-axis scoring, a few to a dozen articles per run. Expensive reasoning models are unnecessary, so the low-cost tier of each provider is recommended.
| Provider | LLM_MODEL |
Reference price (input / output, per 1M tokens) | Notes |
|---|---|---|---|
gemini |
gemini-3.5-flash-lite |
$0.30 / $2.50 | Recommended. Has a free tier |
openai |
gpt-5.6-luna |
$0.20 / $1.20 | Cheapest tier of the GPT-5.6 family |
anthropic |
claude-haiku-4-5 |
$1.00 / $5.00 | Cheapest Claude tier. Oldest model in the current lineup (retirement not sooner than 2026-10-15); switch to claude-sonnet-5 ($2.00 / $10.00) once a retirement notice is published |
For better triage quality, swap in a higher tier (gemini-3.8-flash / gpt-5.6-terra / claude-sonnet-5). Only LLM_MODEL changes — no code changes needed.
Note
gemini-3.8-flash (released 2026-09-02) is priced at $0.75 / $3.75 through 2026-12-31 and $1.50 / $7.50 from 2027-01-01. Thinking is on by default (thinking_level: medium) and thinking tokens are billed as output, so the actual cost per article is higher than the list price suggests. gemini-3.7-flash remains supported at the same price.
Note
To cut costs further, Gemini also offers gemini-2.5-flash-lite ($0.10 / $0.40). Triage that requires reading full text and judging relatively tends to get coarse there, so validate with the recommended model first.
cp .env.example .envEdit .env with your keys:
RAINDROP_TOKEN=your-raindrop-token
RAINDROP_COLLECTION_ID=your-collection-id
LLM_PROVIDER=gemini
LLM_API_KEY=your-llm-api-key
LLM_MODEL=gemini-3.5-flash-liteWarning
Never commit .env to the repository — it contains secrets.
Add the following to your repository: Settings → Secrets and variables → Actions → Repository secrets.
| Secret name | Value |
|---|---|
RAINDROP_TOKEN |
Raindrop.io API test token |
RAINDROP_COLLECTION_ID |
Target collection ID |
LLM_PROVIDER |
gemini / openai / anthropic |
LLM_API_KEY |
LLM API key |
LLM_MODEL |
Model name (e.g. gemini-3.5-flash-lite) |
# Test Raindrop API connectivity only (no LLM required)
python -m src fetch-only
# Summarize a small batch
MAX_SUMMARIZE_PER_RUN=3 python -m src run
# Full run
python -m src run# Full pipeline (fetch → extract → summarize → generate HTML)
python -m src run
# Dry run — preview target articles without processing
python -m src run --dry-run
# Verbose logging
python -m src run --verbose
# Fetch from Raindrop only
python -m src fetch-only
# Regenerate HTML
python -m src build-html
# Reprocess a specific article
python -m src reprocess --id 123456789
# Retry all failed articles
python -m src reprocess-failedAn article dashboard is generated at docs/index.html. Open it in a browser to review.
- Color-coded by priority (HIGH = red / MEDIUM = yellow / LOW = gray)
- Filter buttons to narrow by priority
- Each article shows a 3-line summary, read-now reason, defer reason, and keywords
- A score (
8/12) sits next to the priority badge; hover it for the per-axis breakdown
Asking the LLM to pick high / medium / low directly skews everything toward high — every article here was bookmarked, so all of them are at least somewhat interesting. Instead the LLM only scores four axes from 0 to 3, and the conversion to a priority happens deterministically in code (src/priority.py).
| Axis | What it measures |
|---|---|
novelty |
Is this a rehash of known material, or first-hand / original information? |
relevance |
How close is the topic to day-to-day engineering work and writing? |
depth |
Does the 3-line summary suffice, or is the full text needed? |
actionability |
Are there concrete steps, settings, or numbers you can use as-is? |
The 0–12 total maps to a priority with these thresholds:
| Priority | Condition |
|---|---|
high |
Total ≥ 10 and depth ≥ 2 |
medium |
Total 8–9 |
low |
Total ≤ 7 |
| Drop candidate | Total ≤ 2 |
The depth condition means an article can be novel and highly relevant yet still land in medium if the summary already covers it.
The medium floor is 8 rather than the theoretical midpoint (6) because of measured behavior: the LLM gravitates to 2 on every axis, so real totals cluster in 5–10 instead of spreading across 0–12 (median 8 over 15 articles, per-axis mean 1.8–2.2). With the midpoint as the boundary, nothing ever landed in low.
To make triage stricter or looser, tune the threshold constants in src/priority.py (HIGH_TOTAL_MIN / MEDIUM_TOTAL_MIN / HIGH_DEPTH_MIN / DROP_TOTAL_MAX) rather than editing the prompt. Changing the prompt's scoring rubric shifts the distribution too, so tune the two together.
Note
Older data without scores, or a response where the LLM failed to emit them, falls back to the priority the LLM returned.
| Environment variable | Description | Default |
|---|---|---|
RAINDROP_TOKEN |
Raindrop.io API test token | (required) |
RAINDROP_COLLECTION_ID |
Target collection ID | (required) |
LLM_PROVIDER |
gemini / openai / anthropic |
gemini |
LLM_API_KEY |
LLM API key | (required) |
LLM_MODEL |
Model name | (required) |
MAX_SUMMARIZE_PER_RUN |
Max articles to summarize per run | 10 |
REQUEST_TIMEOUT_SECONDS |
HTTP request timeout (seconds) | 20 |
USER_AGENT |
HTTP User-Agent header | Tsuyu-mi/0.1 |
OUTPUT_DIR |
HTML output directory | docs |
SITE_URL |
Public site URL used for absolute OGP URLs | https://unsolublesugar.github.io/tsuyu-mi/ |
DATA_DIR |
Data storage directory | data |
STATE_DIR |
State management directory | state |
LOG_LEVEL |
Log level | INFO |
See "4. Configure environment variables → GitHub Actions" above.
Settings → Pages → Source: GitHub Actions
Important
Private repositories require GitHub Pro or higher to use GitHub Pages.
- Automatic: Every 3 days at JST 7:00 (UTC 22:00)
- Manual: Run on demand from the Actions tab via "Run workflow"
Changes are auto-committed and pushed only when new content is generated.
pytest
ruff check src/ tests/
ruff format --check src/ tests/The same checks run in CI (.github/workflows/ci.yml) on every push and pull request.
To run them automatically before each commit, install the pre-commit hooks:
pip install pre-commit
pre-commit installIf you use VS Code, the bundled .vscode/settings.json enables format-on-save with the recommended Ruff extension.
Contributions are welcome! A few things that help keep the project healthy:
- Please make sure
pytestpasses before opening a pull request. PRs are merged once the test suite is green. - When you change or fix behavior, please add or update tests to cover it.
- Please run
ruff check src/ tests/andruff format src/ tests/before pushing —mainis kept lint- and format-clean, and CI will fail otherwise.
The suite includes some regression tests that guard against previously-fixed bugs — for example, tests/unit/test_main_fallback.py covers the fallback summarization path (X/Twitter posts, articles where extraction failed). Please fix the underlying code rather than removing these if they fail.
