Sharpen what you know.
Whetstone is a desktop app that turns the
teach skill into an
integrated, two-way learning environment. It wraps Claude Code running the
skill and adds a live bridge, so the AI-generated lessons aren't just things you
read — you can do them, and the teacher responds inside the lesson itself.
A teaching session is a folder on disk (the "workspace") containing a MISSION.md,
lessons, reference docs, learning records, and notes. Whetstone gives that
workspace a home with three panes:
- Left — a sidebar of the workspace's Lessons, References, and the core Workspace docs (Mission / Resources / Notes).
- Centre — the content view: lessons and docs render here (beautiful, Tufte-inspired typography), with back/forward navigation and git commit / push controls.
- Right — your teacher: an agentic chat (powered by Claude Code) that plans the course, writes lessons, and answers questions.
The difference from opening lessons in a browser: lessons are interactive both ways. When you submit an exercise, complete a quiz, or click "explain" inside a lesson, it goes to the teacher; the teacher can render feedback inline, adapt the lesson, record what you've learned, and schedule spaced-repetition reviews.
Grab the latest build for your platform from the
Releases page. Either way you'll also need
Claude Code installed and on your PATH
(claude --version) — Whetstone drives it as the teaching engine.
The released .dmg is not notarized (notarization requires a paid Apple
Developer account). macOS therefore quarantines it on download and shows
"Whetstone is damaged and can't be opened" — misleading wording for "this
developer isn't verified." The app is fine; clear the quarantine flag to run it:
-
Open the
.dmgand drag Whetstone into Applications. -
Remove the quarantine attribute:
xattr -dr com.apple.quarantine /Applications/Whetstone.app
-
Launch Whetstone normally.
(Right-click → Open does not clear the "damaged" variant — the xattr command
is the reliable fix.)
Run the .exe installer (Windows) or the .AppImage (Linux) from the release.
These are also unsigned, so Windows SmartScreen may warn once — choose More
info → Run anyway.
You'll need Claude Code installed and on your PATH
(claude --version), plus Node 20+.
npm install # installs deps incl. the Electron runtime
npm run dev # launches the appOn launch you get a Welcome screen:
- Open a folder… — point Whetstone at any existing teaching-session folder.
- New session — type what you want to learn; Whetstone scaffolds a self-contained folder (starter docs + the teach skill + shared assets) and opens it.
- Recent — reopen a past session; it resumes where you left off.
Then just talk to your teacher in the right pane. Ask it to start a course, work through the lessons it creates in the centre pane, and submit your exercises right in the lesson. Use the header to Commit your progress (each session is its own git repo) and Push it to a remote, the model dropdown to switch models, and the 📁 button to reveal the workspace folder in your file manager.
Sessions are plain folders of Markdown/HTML — fully usable outside the app, and versioned with git.
RENDERER (Vue 3 + Pinia) MAIN PROCESS
sidebar | content iframe | chat Workspace FS (the teaching workspace dir)
↑ IPC ↓ Lesson server (HTTP serve + POST + WS)
Bridge MCP server ◀─ tool calls ─ claude
claude subprocess (teach skill, stream-json)
- The app spawns
claude(stream-json) and is also an MCP server it connects to — that closes the loop. Lesson interactions become synthesized user turns; the agent'slesson_feedback/patch_lesson/schedule_review/record_learningtool calls flow back to the lesson over WebSocket. - Lessons render in a cross-origin iframe served by the local lesson server — on purpose: AI-generated HTML/JS is treated as untrusted and kept away from the app and its IPC bridge.
- The unit of state is the workspace on disk — no database. Per-app state
(recent workspaces) lives in Electron
userData; per-workspace UI state (session id, transcript) in a git-ignored.teach-desktop.json.
npm run dev # run the app (electron-vite)
npm test # Vitest (unit + integration; no real claude, no real ports)
npm run test:watch # watch mode
npm run typecheck # vue-tsc, strict
npm run lint # eslint
npm run build # production bundle (electron-vite → out/)
npm run dist # package a distributable (electron-builder → release/)
npm run dist:dir # unpacked app only (faster; no installer/signing)Layout:
src/
shared/ pure domain — protocol, BridgeCore, stream-json parser, links (+ tests)
main/ Electron main, lesson server, MCP server, claude harness, workspace lifecycle
preload/ typed IPC surface (window.teach)
renderer/ Vue app (Sidebar / content / chat, Pinia stores)
assets/ bridge.js + shared lesson assets (lesson.css, quiz.js) shipped to workspaces
.claude/skills/teach/ the teach skill (the brain), copied into new sessions
ExampleLesson/ a real sample workspace / test fixture
Conventions and gotchas:
- TDD. Domain logic is pure functions in
src/shared/(and pure cores insrc/main) so it tests without Electron, the network, or a liveclaude— those are injected. Tests never spawn a realclaudeor bind real ports for unit work. - Main-process changes require a full
npm run devrestart — the renderer hot-reloads, butsrc/main/src/preload(IPC, routes, the lesson server) do not pick up changes until you restart. - electron-vite config uses
externalizeDepsPlugin()for main/preload, so node deps (e.g.ws) load fromnode_modulesat runtime rather than being bundled. - The teach skill is invoked explicitly (it has
disable-model-invocation: true): the app sends a/teachturn once on session start. - Untrusted lessons: never give the lesson iframe node integration; it reaches the app only through the bridge's POST/WS protocol.
npm run dist produces a distributable in release/ via electron-builder
(per-OS target: dmg / nsis / AppImage); npm run dist:dir produces just the
unpacked app for quick checks. The teach skill (.claude/skills) and shared
assets/ are shipped as extraResources, and the app resolves them from
process.resourcesPath when packaged (and from the repo in dev) — so new-session
scaffolding works in a built app, no git or network required.
The app icon lives in build/ (icon.svg is the source; icon.icns /
icon.png are generated and picked up automatically by electron-builder). To
regenerate after editing the SVG: npx electron scripts/render-icon.cjs rasterizes
it, then rebuild the .icns with sips + iconutil.
Not yet configured: code signing / notarization — set up signing identities before distributing outside your own machine (see https://electron.build/code-signing).
Built on the teach skill by AI Hero —
https://www.aihero.dev/learn-anything-with-my-teach-skill.
