Project: AI-powered Readwise Reader inbox triage and organization
Completed: 2026-02-02
Status: ✅ Fully Functional
This is a remixable Val Town app template that lets users:
Users remix this val, paste their API tokens, and instantly get the full experience.
std/sqlite@14-main)readwise-mastra/
├── app/
│ └── http.ts # Main HTTP router (Hono)
├── lib/
│ ├── env.ts # Environment config + validation
│ ├── db.ts # SQLite schema + helpers
│ ├── readwise.ts # Readwise Reader API client
│ ├── sync.ts # Incremental sync runner
│ ├── llm_pollinations.ts # Pollinations wrapper + model discovery
│ └── agent.ts # AI plan generation + application
├── jobs/
│ └── sync/
│ └── interval.ts # 15-min sync + daily model refresh
├── web/
│ ├── index.html # SPA entry point
│ ├── app.js # Frontend JS
│ └── styles.css # Dark theme styles
├── plan.md # Original build spec
├── grounding.md # Pollinations API docs
├── keyupdates.md # Update plan details
├── tracker.md # Implementation progress tracker
└── IMPLEMENTATION_RECAP.md # This file
docs - Readwise documents (id, title, author, site_name, url, category, location, reading_progress, notes, summary, tags, etc.)tags - Tag definitions (key, name, type)doc_tags - Junction table for document-tag relationshipssync_state - Sync watermark + status (single row)views - Saved search viewsprojects - Collections with AI rulesai_plans - Generated organization plansai_jobs - Plan execution logsmodel_cache - Discovered Pollinations models (name, type, tools, reasoning, context_window, pricing)model_config - Selected primary/fallback models + last discovery timestampdocs(location, updated_at) - Inbox sortingdocs(category), docs(site_name) - Filtersdoc_tags(tag_key) - Tag viewsdocs(saved_at) - Date sortingGET /health - Health check (no auth)POST /api/sync/run - Trigger sync nowPOST /api/sync/reset - Reset sync state (full resync)GET /api/stats - Document counts + sync statusGET /api/auth/check - Validate Readwise tokenGET /api/inbox/queue?location=new&limit=50 - List inbox itemsPOST /api/docs/:id/triage - Move/tag/annotate documentGET /api/docs/:id - Get single documentGET /api/search?q=&tag=&category=&location=&from=&to=&progressMin=&progressMax= - Full searchPOST /api/views - Create saved viewGET /api/views - List saved viewsDELETE /api/views/:id - Delete viewPOST /api/projects - Create projectGET /api/projects - List projectsPOST /api/projects/:id/apply - Run AI rule, return planPOST /api/ai/plan - Generate organization planPOST /api/ai/apply - Execute planGET /api/ai/plans - List recent plansGET /api/ai/plans/:id - Get plan detailsGET /api/ai/models (NEW) - List cached models + configPOST /api/ai/discover (NEW) - Trigger model rediscoveryPOST /api/image/generate - Generate image from prompt (stored in blob)GET /api/image/models - List available image modelsPOST /webhooks/readwise - Receive Readwise webhooks (triggers sync)GET /web/:file - Serve SPA assets (index.html, app.js, styles.css)GET /* - SPA fallback (serves index.html)READWISE_TOKEN=your_readwise_token APP_ADMIN_SECRET=your_secret_for_api_auth POLLINATIONS_TOKEN=your_pollinations_sk_token
POLLINATIONS_BASE_URL=https://gen.pollinations.ai POLLINATIONS_CHAT_PATH=/v1/chat/completions POLLINATIONS_PRIMARY_MODEL= # leave empty for auto-discovery READWISE_WEBHOOK_SECRET= # for webhook verification
Old (wrong): Used legacy endpoint https://text.pollinations.ai/openai with hardcoded "openai" model
New (correct):
https://gen.pollinations.ai/v1/chat/completions/v1/models and /text/models| Task Type | Model Used | Why |
|---|---|---|
| Simple triage | gemini | Cheap, fast, tools=true |
| Complex planning | gemini-large | Better reasoning, quality |
| Search/web features | gemini-search | Has google_search enabled |
| Fallback | openai → gemini-large | Broad compatibility |
/api/ai/discover)gemini fails, tries openai, then gemini-large{ text, modelUsed, fallbacksAttempted }generateImage() stores images in Val Town blob storagegemini-search)content_blocks in responses (text, image_url, thinking)updatedAfter watermark to fetch only changed documentssync_state tablejobs/sync/interval.tsPOST /api/sync/runsync_state.updated_after/api/v3/list/?updatedAfter=<watermark>updated_atcomplexTask: true (uses gemini-large){ "items": [ { "docId": "abc123", "patch": { "location": "archive", "tagsAdd": ["read"] }, "rationale": "Not started, over 30 days old" } ] }
{
items: Array<{
docId: string;
patch: {
location?: "new" | "later" | "shortlist" | "archive" | "feed";
tagsAdd?: string[];
tagsRemove?: string[];
notes?: string;
summary?: string;
title?: string;
};
rationale: string;
}>
}
Retry-After header on 429 errorslocalStorageRuns every 15 minutes
Next run: Check Val Town UI for interval status
APP_ADMIN_SECREThttps://kamenxrider--2325eac8ffd511f0b0a842dde27851f2.web.val.run
Set environment variables in Val Town:
READWISE_TOKENAPP_ADMIN_SECRETPOLLINATIONS_TOKENTrigger initial sync:
curl -X POST https://your-val.web.val.run/api/sync/run \ -H "Authorization: Bearer YOUR_ADMIN_SECRET"
Trigger model discovery:
curl -X POST https://your-val.web.val.run/api/ai/discover \ -H "Authorization: Bearer YOUR_ADMIN_SECRET"
Open the UI and sign in with APP_ADMIN_SECRET
GET /api/stats shows last sync time + errorGET /api/ai/models shows cached models + last discoveryREADWISE_TOKEN is validPOLLINATIONS_TOKEN is valid (sk_...)POST /api/ai/discover to refresh cacheAPP_ADMIN_SECRET in localStorageGET /health returns {"ok":true}POST /api/sync/run starts syncGET /api/stats returns doc countsGET /api/inbox/queue?location=new lists docsPOST /api/ai/discover caches modelsGET /api/ai/models shows cached modelsPOST /api/ai/plan with goal returns planPOST /api/ai/apply with planId executes patchesPOST /api/image/generate with prompt returns blob URLapp/http.ts - All HTTP routes (start here)jobs/sync/interval.ts - Background jobslib/sync.ts - Readwise sync algorithmlib/agent.ts - AI plan generation + applicationlib/llm_pollinations.ts - Pollinations API wrapper + discoverylib/readwise.ts - Readwise API clientlib/db.ts - SQLite schema + helperslib/env.ts - Environment configweb/app.js - SPA state + API callsweb/index.html - HTML entry pointweb/styles.css - Dark themeplan.md - Original build specificationgrounding.md - Pollinations API documentationkeyupdates.md - Pollinations integration plantracker.md - Implementation progress (all phases complete)IMPLEMENTATION_RECAP.md - This file✅ All planned features implemented
✅ Pollinations integration complete
✅ Production ready
GET /api/stats to verify sync is workingGET /api/ai/models to verify model discoveryPOST /api/ai/discover to force model refreshapp/http.tslib/db.ts runMigrations()lib/agent.tslib/llm_pollinations.tsweb/app.js and web/styles.cssEnd of Implementation Recap
Last updated: 2026-02-02