⚠️ Preview README — this version describes the upcomingv2.0.0(TypeScript / Fastify backend). The code currently onmainis still the legacy Python build. This README ships ahead of the backend rewrite via PR — feature mentions and install instructions will only apply once the backend rewrite lands onmain.
Papyrus is a minimalist, keyboard-driven, AI-agent-powered spaced-repetition (SRS) review engine, designed for high-intensity memory drilling.
"Simplicity is the ultimate sophistication."
- 🚀 Flow-state interaction — fully keyboard-driven; never touch the mouse during a review session.
- 🧠 Proven scheduling — built-in SM-2 spaced-repetition algorithm tunes the next review interval per card.
- 🤖 AI agent — OpenAI / Anthropic / Ollama, with tool-call approval flow (manual or auto-approve) and call history.
- 📝 Notes + Obsidian import — bring your existing vault, navigate via folder tree, tags, and a relation graph.
- 🕘 Version history & rollback — every note/card edit auto-saves a content-hashed version; rollback creates a forward version (no destructive history).
- 🔐 Encrypted API keys at rest — AES-GCM with a per-install master key, salt, and auth tag.
- 🛡️ Hardened defaults — auth token required for write APIs, SSRF guard for AI base URLs, rate limiting on the public surface, path-traversal protection (
dev+inocontainment). - ♿ Accessibility — WCAG 2.1 AA across the app, with AAA-grade contrast options, screen-reader optimization, and reduced-motion modes.
- 🌐 Modern stack — Node.js + TypeScript (Fastify) backend, React 19 + Vite + Arco Design frontend, Electron 41 shell.
- 📦 Local-first — your data never leaves the machine unless you point an AI provider at the cloud.
Pre-built installers are published on the Releases page.
| Platform | Architecture | Formats |
|---|---|---|
| Windows | x64 | NSIS installer (.exe), Portable (.exe) |
| macOS | arm64 | DMG (.dmg), ZIP (.zip) |
| Linux | x64 | AppImage, DEB (.deb), TAR.GZ |
⚠️ v2.0.0-betais a beta. The data schema is stable, but the UI and APIs may still evolve beforev2.0.0.
| Key | Action | Effect |
|---|---|---|
| Space | Reveal answer | Unrolls the scroll, showing the answer side |
| 1 | Forgot | Mark unfamiliar; the card returns soon |
| 2 | Hazy | Mark uncertain; the card returns later today |
| 3 | Aced | Memory rock-solid; interval doubles linearly |
| Tab | Navigate | Move focus between interactive elements |
| Ctrl + K | Search | Open the global search palette |
- Node.js 24+
- npm 11+
# postinstall cascades into frontend/ and backend/
npm install# Concurrently runs backend (Fastify, tsx watch) + frontend (Vite)
npm run dev
# Or, with the Electron shell on top
npm run electron:dev- Frontend: http://localhost:5173
- Backend API: http://127.0.0.1:8000
- Health check: http://127.0.0.1:8000/api/health
npm run electron:build # current platform
npm run electron:build:win # Windows only
npm run electron:build:mac # macOS only
npm run electron:build:linux # Linux onlyInstallers are emitted to dist-electron/.
Prepare a UTF-8 .txt file in this format:
Question or scenario A === Trigger or answer A
Question or scenario B === Trigger or answer B
Cards within a group are separated by
===. Leave a blank line between groups for readability.
Open the in-app ⚙️ Settings sidebar and add an API key:
- OpenAI — get a key at https://platform.openai.com/api-keys
- Anthropic — get a key at https://console.anthropic.com/
- Ollama — runs locally, no API key needed
# Install: https://ollama.ai ollama pull llama2
API keys are persisted encrypted at rest (AES-GCM with a per-install master key).
- Agent mode — the model can invoke tools (add / edit / delete cards, search notes, …) under a tool-call approval flow.
- Chat mode — pure conversation, no side effects.
- Temperature — 0 to 2, higher is more random.
- Max tokens — cap on response length.
- Tool approval —
manual(queue and review) orauto(allow-list driven).
Papyrus aims to be usable by everyone:
- Keyboard navigation — full Tab traversal across every page.
- Screen readers — semantic ARIA labels, live regions for state changes.
- Contrast — AAA-grade palettes available under Settings → Accessibility.
- Motion — reduced-motion mode honors OS preference.
Papyrus/
├── backend/ # Node.js + TypeScript (Fastify)
│ └── src/
│ ├── api/ # Fastify routes & server entry (server.ts)
│ ├── core/ # Cards, notes, SM-2, versioning, crypto
│ ├── db/ # JSON persistence + migrations
│ ├── ai/ # Provider abstraction, tool manager, LLM cache
│ ├── mcp/ # MCP REST endpoints (notes / vault CRUD)
│ ├── integrations/ # Obsidian import, file watcher (chokidar)
│ └── utils/ # Shared utilities
├── frontend/ # React 19 + TypeScript (Vite)
│ └── src/
│ ├── StartPage/ # Home (recent notes, review queue, solar terms)
│ ├── ScrollPage/ # Flashcard study (the "scroll")
│ ├── NotesPage/ # Notes management & graph view
│ ├── SettingsPage/ # Settings, AI config, accessibility
│ └── ChartsPage/ # Stats & progress charts
├── electron/ # Main process + preload (Electron 41)
├── scripts/ # build-electron.js, extract-changelog.js
├── e2e/ # Playwright E2E tests
└── docs/ # Project documentation
- Backend — Node.js 24, TypeScript 5, Fastify 5, Jest
- Frontend — React 19, TypeScript 5, Vite, Arco Design, Tailwind CSS
- Desktop — Electron 41 + electron-builder
- Algorithm — SM-2 spaced repetition
- Storage — local JSON files, content-hashed versions
- CI/CD — GitHub Actions matrix (Windows x64, macOS arm64, Linux x64)
cd backend
npm run dev # tsx watch — hot-reload Fastify
npm run build # compile TypeScript to dist/
npm run typecheck # tsc --noEmit
npm test # Jest unit + integration
npm start # run compiled dist/api/server.jsThe backend listens on 127.0.0.1:8000 by default; override with PAPYRUS_PORT.
cd frontend
npm run dev # Vite dev server (http://localhost:5173)
npm run build # production build to dist/
npm run typecheck # TypeScript type-checking# 1. Move [Unreleased] entries in CHANGELOG.md under the new version
# 2. (optional) preview the section that will land in the GitHub Release
node scripts/extract-changelog.js v2.0.0
# 3. Commit
git add CHANGELOG.md
git commit -m "chore: release v2.0.0"
# 4. Tag & push — GitHub Actions builds all three platforms,
# extracts the matching CHANGELOG section, and uploads installers.
git tag v2.0.0
git push origin main --tagsBy default, user data lives under paths.dataDir (defaults to $HOME/PapyrusData, override with PAPYRUS_DATA_DIR):
ai_config.json— provider, model, encrypted API keysPapyrusdata.json— cards & SM-2 review statenotes.json— notes~/.papyrus/auth.token— token required for write APIs (generated on first run)
- API costs — OpenAI and Anthropic bill per use; set provider-side budgets.
- Local models — Ollama is free but needs decent hardware.
- Network — cloud providers need a stable connection.
- Privacy — local models stay local; cloud providers see the prompts you send.
- Concurrency — JSON-file storage is single-writer; don't run multiple instances against the same data dir.
- Quick Start — 5-minute onboarding
- Accessibility settings
- Version info
- Changelog — synced into GitHub Releases
- Project structure
- Environment requirements
- Accessibility development
- UI design tokens
- REST API reference
- Extension development and extension template
- Electron packaging
- Dev environment
Honestly, the birth of this project wasn't all that grand or visionary.
Why did I make it? One day I was drilling some AI material and downloaded Anki to memorize the key points. Right after install, the thing wanted to check for updates, the updates wanted to talk to a server, and I just could not get a connection through.
Gemini was sitting right there. So I told it to write me one. We tinkered, I patched, and I started using it.
I never planned to publish it. So why did I?
Yesterday I logged into a long-dormant account to give my girlfriend a star, and GitHub flagged me as a bot and wiped my activity. I was furious. I'm using AI; I'm not AI. To prove I'm not a bot, I cleaned up what I had on hand and put it up here.
End of small story. It's already the sixth day of the lunar new year, and dawn is creeping up. A grumbling student, sighing at three in the morning. Such is life.
There's a phrase I love: Veritas vos liberabit — "the truth shall set you free." May we all share in it. This is my first open-source project; please be kind.
Consider this my belated lunar new-year greeting. May the new year find you wielding the blade of knowledge — slicing through the void of night, charging straight into the dawn.
Papyrus — make learning smarter, make memory scientific.