Journal Companion
unlistedby Flora Fan
A pen pal who reads your journal and writes back: a warm letter for every entry, plus weekly, monthly, and yearly look-backs.
Journal Companion
A pen pal who reads every page of your journal, and writes back.
You write about your day in Obsidian: the small wins, the late-night worries, the cat who wouldn't get off your keyboard. The next morning there's a letter waiting at the bottom of the page. It's from a friend who actually read what you wrote. They cheer you on, make you laugh, see the thing from an angle you hadn't thought of, and leave you with one question worth sitting with.
Then, week by week, the pages add up:
- ๐ A letter for every entry. Warm, playful, and specific to your day, never generic praise. Each one ends with a reflective question.
- ๐ง A friend who remembers. A memory note keeps track of the people, pets, goals, and worries in your life, and grows a little every week. Letters draw on it, so they understand that "she" is your sister and that you've been thinking about this decision for a month. You can edit the note any time, and anything you pin there is never changed by the AI.
- ๐ Your mood, charted. Each entry gets a mood and energy score (1โ5) and a few feeling words, saved as properties you can correct. A 30-day chart lives in
Summaries/Mood.md, and every weekly and monthly look-back includes its own. - ๐ A tidy shelf. Past entries are filed into a folder per month (
9ๆ-2026,10ๆ-2026, โฆ), with links kept intact. - ๐ Your writing, counted. Every letter opens with how many days you've journaled and how many words you've written in total, plus today's word count, writing time, and speed.
- ๐ A look back every week and every month. How your mood moved, what you got done, the people and worries that kept coming up, and one or two small things to try next.
- โจ A year-in-review. The story of your year, with its hard seasons and bright ones, and a letter to the person you're becoming.
Over time you end up with a book of your own life, and a companion who knows it as well as you do.
Your words stay in your own vault as plain Markdown files. Letters and summaries are written by Claude, in whatever language you journal in: Chinese, English, or a mix of both. If you'd like the letters funnier, gentler, or shorter, just say so in one line in the settings.
> [!ai-reply] ๐ A letter from Claude ยท 2026-09-25 08:12
> ๐ You've journaled for 30 days, 12,345 words in total | Today: 420 words in 14 min (30 words/min)
>
> You went on that run even though you almost talked yourself out of it.
> Honestly, arguing with yourself and winning is its own kind of cardio. ๐โโ๏ธ ...
>
> ๐ค What was the voice that almost kept you home, and what did it want to protect?
How it works
flowchart LR
D[Daily entries<br/>Journal/YYYY-MM-DD*.md] -->|each entry + 2 prior days| R[Letter appended<br/>to the entry]
D -->|7 entries| W[Weekly summary<br/>Summaries/Weekly/2026-W39.md]
D -->|~30 entries + last month's summary| M[Monthly summary<br/>Summaries/Monthly/2026-09.md]
M -->|12 monthly summaries| Y[Yearly review<br/>Summaries/Yearly/2026.md]
Design decisions
Hierarchical summarization. The yearly review reads the 12 monthly summaries instead of 365 raw entries. This keeps each request small and cheap, and it also tends to produce a better result, because the model works from material that has already been distilled rather than a year of day-to-day noise. Monthly summaries read the raw entries, since a month still fits comfortably in context. They also get the previous month's summary so they can describe what changed.
Idempotent catch-up instead of a scheduler. Obsidian isn't always running, so cron-style timing would silently miss periods. Instead, runChecks() runs when Obsidian starts and every 30 minutes after that. Each run asks what should exist and doesn't: past entries with no reply, completed ISO weeks with no weekly file, completed months, completed years. It generates only those. Running it twice does nothing extra, and a week with the laptop closed repairs itself. The order is weeks, then months, then years, because yearly reviews depend on monthly summaries.
Replies never feed back into the model. Letters are tagged with a custom callout type ([!ai-reply]). Before any entry is sent to the API, stripReplies() removes these blocks. This keeps the model from quoting its own earlier letters back to you, and it avoids paying for those tokens again. The logic is pure and unit-tested in tests/utils.test.ts.
Safe writes. Replies are appended with vault.process(), which reads and writes the file atomically, so an edit you're making at the same moment isn't overwritten.
Memory the writer stays in control of. Letters used to see only the previous two days. Now a memory note (Summaries/Memory.md) is built once from every entry, then revised after each weekly summary. The model returns the whole note each time, merging and dropping stale items, so it stays short instead of growing forever. The writer's ๐ Pinned section is split out and reattached by code rather than passed through the model, so the AI can read it but has no way to change it. This is covered by unit tests.
Mood as data, stored where the writer can see it. A small, cheap model scores each entry and returns strict JSON, which is validated (integers 1โ5, at most three feelings) before anything is saved. Scores are written as frontmatter properties through Obsidian's processFrontMatter, so they show up in the Properties panel, can be corrected by hand, and work with Bases and Dataview. Charts are built from those properties locally with no API calls: a Mermaid chart, plus a plain-text table with bar glyphs that still reads well anywhere Mermaid doesn't render.
Measuring writing time without a timer. There's no start/stop button. The plugin listens to editor changes in journal notes and adds up the gaps between keystrokes, ignoring any pause longer than two minutes as time spent away. Time is stored per entry date, so coming back to an entry later adds to the same day. Word counts treat each Chinese character and each English word as one, which matches how people count mixed-language writing.
Right-sized models. Daily letters are short and frequent, so they use a small, fast model (Claude Haiku). Summaries run a few times a month and need more synthesis, so they use a larger model (Claude Sonnet). Both are configurable.
Project structure
src/
main.ts plugin lifecycle, entry discovery, replies, summaries, catch-up loop
claude.ts minimal Anthropic Messages API client (uses Obsidian's requestUrl, which isn't subject to CORS)
prompts.ts system prompts for letters and each summary level
settings.ts settings schema, defaults, and settings tab
utils.ts pure helpers: date parsing, callout formatting and stripping
tests/
utils.test.ts Vitest unit tests for the pure helpers
Installation
This plugin is not in the Obsidian community directory yet, so it has to be installed manually.
- Build the plugin (see Development), or download
main.jsandmanifest.jsonfrom a release. - Copy both files to
<your vault>/.obsidian/plugins/journal-companion/. - In Obsidian, open Settings โ Community plugins, turn off Restricted mode, and enable Journal Companion.
Getting an API key
- Sign in at console.anthropic.com.
- Add credit under Billing. API usage is billed separately from a Claude.ai subscription, and a few dollars lasts a long time.
- Open API Keys โ Create Key and copy the key, which starts with
sk-ant-. It is shown only once. - In Obsidian, go to Settings โ Journal Companion, paste the key, and click Test connection.
Usage
| Action | How |
|---|---|
| Get a letter right after writing | Click the โก button at the top-right of any journal note (or the โก icon in the left ribbon, or run Reply to the current journal entry) |
| Rewrite a letter | Regenerate the reply for the current entry |
| Preview this week's summary mid-week | Generate or refresh the weekly summary for the current entry's week |
| Preview this month's summary mid-month | Generate or refresh the monthly summary for the current entry's month |
| See or edit what Claude remembers | Open the long-term memory note |
| Refresh memory now | Update long-term memory from the last 7 days |
| See your mood trend | Open the mood & energy chart (last 30 days) |
| Fix a score that feels off | Edit mood / energy in the entry's properties, or run Re-score mood and energy for the current entry |
| File entries into month folders now | Organize past entries into monthly folders now |
| Force a catch-up | Catch up now: missing replies and summaries |
By default, entries from before today get their letter automatically the next time Obsidian opens. If you'd like today's entry answered once you stop typing, set Reply to today's entry after N idle minutes (for example, 30).
Entry filenames must start with YYYY-MM-DD. Anything after the date is ignored, so 2026-09-22.md and 2026-09-22 Tue..md both work. Entries can live directly in the journal folder or in any subfolder of it.
With Organize entries into monthly folders on (the default), entries from before today are moved into a month folder whose name you can configure with a Moment.js format, by default M[ๆ]-YYYY โ 9ๆ-2026. To keep folders in order across years, put the year first, e.g. YYYY-MM M[ๆ] โ 2026-09 9ๆ. Today's entry is left in place so Obsidian's "Open today's daily note" still finds it. Moves go through Obsidian's file manager, so links to the entry are updated.
Cost
With default settings, a daily letter costs well under one cent with Claude Haiku, even with two earlier entries included as context. Summaries use the larger model but run only about five times a month. For one daily journal, expect roughly a dollar a month or less. Check current pricing at anthropic.com/pricing.
Privacy
- Entry text is sent to the Anthropic API to generate letters and summaries. Nothing is sent anywhere else.
- Your API key is stored in plain text in
.obsidian/plugins/journal-companion/data.json. If you sync your vault to Git, add that file to.gitignore.
Development
npm install
npm run dev # rebuild on change
npm test # unit tests
npm run build # type-check and produce main.js
To try your changes, symlink or copy the repo into <vault>/.obsidian/plugins/journal-companion/ and reload Obsidian.
Roadmap
- Store the API key in the OS keychain instead of
data.json - Support for other LLM providers behind the same interface
License
MIT
For plugin developers
Search results and similarity scores are powered by semantic analysis of your plugin's README. If your plugin isn't appearing for searches you'd expect, try updating your README to clearly describe your plugin's purpose, features, and use cases.