Journal Companion

unlisted

by 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.

Updated 12d agoMIT
View on GitHub

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.

  1. Build the plugin (see Development), or download main.js and manifest.json from a release.
  2. Copy both files to <your vault>/.obsidian/plugins/journal-companion/.
  3. In Obsidian, open Settings โ†’ Community plugins, turn off Restricted mode, and enable Journal Companion.

Getting an API key

  1. Sign in at console.anthropic.com.
  2. Add credit under Billing. API usage is billed separately from a Claude.ai subscription, and a few dollars lasts a long time.
  3. Open API Keys โ†’ Create Key and copy the key, which starts with sk-ant-. It is shown only once.
  4. In Obsidian, go to Settings โ†’ Journal Companion, paste the key, and click Test connection.

Usage

ActionHow
Get a letter right after writingClick 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 letterRegenerate the reply for the current entry
Preview this week's summary mid-weekGenerate or refresh the weekly summary for the current entry's week
Preview this month's summary mid-monthGenerate or refresh the monthly summary for the current entry's month
See or edit what Claude remembersOpen the long-term memory note
Refresh memory nowUpdate long-term memory from the last 7 days
See your mood trendOpen the mood & energy chart (last 30 days)
Fix a score that feels offEdit mood / energy in the entry's properties, or run Re-score mood and energy for the current entry
File entries into month folders nowOrganize past entries into monthly folders now
Force a catch-upCatch 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.