• Blog
  • Docs
  • Pricing
  • We’re hiring!
Log inSign up
kamenxrider

kamenxrider

readwise-mastra

Public
Like
readwise-mastra
Home
Code
9
app
1
jobs
1
lib
6
web
3
IMPLEMENTATION_RECAP.md
grounding.md
keyupdates.md
plan.md
tracker.md
Environment variables
6
Branches
1
Pull requests
Remixes
History
Val Town is a collaborative website to build and scale JavaScript apps.
Deploy APIs, crons, & store data – all from the browser, and deployed in milliseconds.
Sign up now
Code
/
plan.md
Code
/
plan.md
Search
…
plan.md

Do this as a Val Town “remixable app template”: one user remixes, pastes their Readwise token in environment variables, and instantly gets the full triage + search + projects experience (and you never touch their token).[1][2] Below is a Townie-ready build spec (files, endpoints, schema, and agent tool contracts) that fits Val Town’s Deno runtime + HTTP triggers + SQLite.[1]

Townie: build plan

Create a new Val Town project with these files/vals (TypeScript everywhere).[1] Use one HTTP val as the single entrypoint that serves both the SPA and the JSON API using routing (Hono is compatible with Val Town’s Request/Response model).[1] Use Val Town SQLite (std/sqlite) as the local index so the UI can query 10k docs fast, while Readwise stays the source of truth.[1]

Files to create

  • app/http.ts (HTTP val): routes /api/*, /webhooks/readwise, and serves SPA assets for everything else.[1]
  • lib/env.ts: env var loading + validation (Readwise token, AI provider key, admin secret).[1]
  • lib/readwise.ts: Readwise client: list/paginate, update, delete, tags list, auth check.[2]
  • lib/db.ts: sqlite helpers (migrations, row mapping, indexes).[1]
  • lib/sync.ts: incremental sync runner (pagination + updatedAfter watermark).[2]
  • lib/agent.ts: Mastra agent + tool definitions + structured “plan” schema.[3]
  • jobs/sync/interval.ts (Cron/Interval val): calls runSync() every N minutes.[2]
  • web/index.html, web/app.js, web/styles.css (static frontend files) served by Val Town file serving utilities or SPA fallback.[1]

Environment variables (per-user remix)

  • READWISE_TOKEN (required): used as Authorization: Token XXX for Readwise Reader API calls.[2]
  • OPENAI_API_KEY (or your chosen provider key) (required for AI features).[1]
  • APP_ADMIN_SECRET (required): simple shared secret for protecting your endpoints (since Val Town URLs are public-by-URL).[1]

API endpoints (contract)

Implement these HTTP routes in app/http.ts (all JSON, all require Authorization: Bearer <APP_ADMIN_SECRET> except /health).[1]

Core

  • GET /health → { ok: true } (no auth, used for webhook “test endpoint”).[4]
  • POST /api/sync/run → triggers sync now; returns { started: true }.[2]
  • GET /api/stats → counts by location/category/tag + sync status from SQLite.[1]

Inbox Zero

  • GET /api/inbox/queue?location=new|later&limit=50 → list items from local SQLite. [2][1]
  • POST /api/docs/:id/triage with { location, tagsAdd?, tagsRemove?, notes? } → patches Readwise via PATCH /api/v3/update/<id>/ then updates local SQLite.[2]

Search + saved views

  • GET /api/search?q=&tag=&category=&location=&from=&to=&progressMin=&progressMax=&limit=&cursor= → SQL query against local index with pagination.[1]
  • POST /api/views with { name, filterJson } → stores a saved view.[1]
  • GET /api/views → list saved views.[1]

Projects (collections + automation)

  • POST /api/projects with { name, rulePrompt, tagKey } → creates a project that maps to a tag + an AI rule.[3][1]
  • POST /api/projects/:id/apply → runs AI rule over a candidate set and returns a plan (no writes yet).[3]

AI endpoints

  • POST /api/ai/plan with { goal, scope } → returns a structured plan: a list of proposed patches (moves/tags/notes/title/summary).[3][2]
  • POST /api/ai/apply with { planId } → executes patches with rate-limit-aware batching and logs results.[2]

Readwise webhooks

  • POST /webhooks/readwise → accepts Readwise webhook JSON and verifies authenticity using the webhook secret value included in the payload.[4]
  • Subscribe users to Reader event types like reader.any_document.created and reader.document.tags_updated so your index updates in near-real-time.[4]

SQLite schema (fast 10k UX)

Use SQLite tables that mirror Readwise docs, plus app-specific metadata.[1]

Tables

  • docs: id TEXT PRIMARY KEY, title TEXT, author TEXT, site_name TEXT, source_url TEXT, url TEXT, category TEXT, location TEXT, word_count INTEGER, reading_progress REAL, created_at TEXT, updated_at TEXT, published_date TEXT, notes TEXT, summary TEXT, last_moved_at TEXT, saved_at TEXT.[2]
  • tags: key TEXT PRIMARY KEY, name TEXT, type TEXT, created INTEGER (from webhook payload tag objects when present).[4]
  • doc_tags: doc_id TEXT, tag_key TEXT, PRIMARY KEY (doc_id, tag_key).[1]
  • views: id TEXT PRIMARY KEY, name TEXT, filter_json TEXT.[1]
  • projects: id TEXT PRIMARY KEY, name TEXT, tag_key TEXT, rule_prompt TEXT, created_at TEXT.[1]
  • sync_state: single row with updated_after TEXT, last_sync_started_at TEXT, last_sync_finished_at TEXT, last_error TEXT.[2]
  • ai_plans: id TEXT PRIMARY KEY, created_at TEXT, goal TEXT, scope_json TEXT, plan_json TEXT, status TEXT.[3]
  • ai_jobs: id TEXT PRIMARY KEY, plan_id TEXT, started_at TEXT, finished_at TEXT, applied_count INTEGER, error TEXT.[1]

Indexes

  • Index docs(location, updated_at) for inbox sorting.[1]
  • Index docs(category) and docs(site_name) for filters.[1]
  • Index doc_tags(tag_key) for fast “tag view” pages.[1]

Mastra agent tool contract (safe + predictable)

Implement the agent as “read-only by default” and force all writes to go through one validator tool that only allows fields Readwise supports on update.[3][2] Use Agent.generate() with maxSteps > 1 so the agent can search, inspect, propose, and refine without looping forever.[5][3] Return structured output for the plan so your UI can show a diff and your apply step can be deterministic.[3]

Tools (the only ones the agent can call)

  • searchIndex(args) → queries SQLite and returns doc ids + thin metadata.[1]
  • getDoc(args) → returns full local doc row + current tags.[1]
  • proposePatch(args) → (optional) internal helper; or skip and rely on structured output.[3]
  • validatePatch(args) → rejects anything outside Readwise update fields (title, author, summary, published_date, image_url, seen, location, category, tags, notes).[2]
  • applyPatch(args) → calls Readwise PATCH https://readwise.io/api/v3/update/<document_id>/ then updates SQLite.[2]
  • rateLimitSleep(args) → used when Readwise returns 429 (respect Retry-After when present).[2]

Plan schema (structured output)

  • Plan { items: Array<{ docId: string, patch: { location?, tagsAdd?, tagsRemove?, notes?, summary?, title? }, rationale: string }> } (no direct HTML/markdown injection; keep it plain text).[3]

“Create the file tree above, implement schema + sync first, then implement /api/inbox/queue + /api/docs/:id/triage, then add the Mastra plan/apply endpoints, then build the SPA UI.”

Citations: [1] llms-full-valtown.txt
[2] Reader API
[3] LangGraph overview - Docs by LangChain
[4] LangGraph
[5] Using Agents | Agents | Mastra Docs
[6] Reference: Agent.generate()
[7] [DOCS] How to use maxSteps and what does it do #2930
[8] llms.txt
[9] maxSteps on agent tool calling - Mastra
[10] File I/O
[11] Reference: Agent.network()
[12] Readwise API/Access token
[13] std
[14] Why use Mastra?
[15] Readwise API
[16] History | sqlite
[17] Reference: Agents API | Client SDK
[18] GitHub - Scarvy/readwise-reader-cli: Use Readwise Reader 📖 in the Command-line (CLI) 💻

FeaturesVersion controlCode intelligenceCLIMCP
Use cases
TeamsAI agentsSlackGTM
DocsShowcaseTemplatesNewestTrendingAPI examplesNPM packages
PricingNewsletterBlogAboutCareers
We’re hiring!
Brandhi@val.townStatus
X (Twitter)
Discord community
GitHub discussions
YouTube channel
Bluesky
Open Source Pledge
Terms of usePrivacy policyAbuse contact
© 2026 Val Town, Inc.