DEV Community

Stephan Miller
Stephan Miller

Posted on • Originally published at stephanmiller.com on

Creating an Obsidian Plugin with Claude AI

Image description

With this post, I took a break from the Electron project I am building with Claude Code to see how fast I could get a smaller project done. Since I am building a relatively simple Obsidian plugin, I went back to using Claude Desktop with the sequential thinking and desktop commander MCP tools.

And I learned a few more things about using AI to write code. Read on for the complete story

The Story Behind the Project

I’m obsessed with having all my book highlights and notes in one place, and Obsidian seemed like a great place for them. First, I paid for a year’s Readwise subscription, which really didn’t cost too much and did the job well, but after the year, I realized that I really only used it to import my highlights from Kindle and Apple Books and there were free Obsidian plugins to import Kindle highlights.

Then I discovered that Apple Books stored all these highlights and notes in an open though hard to find and hard to understand SQLite database. So I wrote a Python script to import Apple Books annotations, using example from sources like this Calibre plugin repo and used the Obsidian Python Scripter plugin to run it from Obsidian. I only had AI help when I ran into issues for the first script and wrote most of it myself.

Problems with the Original Python Script

The first issue with the Python script I wrote is that the Python Scripter plugin is no longer available. This was not much of a problem though. I just hard-coded the file path of my vault’s book notes folder in the script and it still worked for me.

Only it wasn’t really working the way I thought it was. There were some issues:

  • The annotations were not sorted by the location in the book.
  • The annotations weren’t by chapter (there is still an issue in the current plugin in that I have chapters now, but they are not human readable).
  • There were no configuration options. I just edited the script when I wanted to change something.

Phase 1: Perfecting the Python Script with Claude

Before jumping right in to creating a plugin, I fixed the Python script’s sorting issue first. My reasoning was that if I got stuck in the plugin development process, I would at least have this script and it would work. And once it did, I could have Claude look at it for a working example. And it turns out this was a good move for these exact reasons.

I also thought I could create this plugin with only a Claude Project and no MCP support, but I was wrong about not using MCP. It might have been possible, but MCP tools made things easier. Here was my first message:

I am using this Python file to import highlights and notes from the Mac iBooks app. I don’t think I am rendering them in order from the front of the book to the back in the markdown file. Let me know if I am and if not how to fix it

The Sorting Challenge

Claude initially gave me three sorting options, but none actually sorted by book location. To test the sort, I put highlights in a book with notes that told their order by location and by when I entered them. So I sent this message, along with a dump of the SQLite table that had the annotations:

The first method didn’t seem to work. The second two seemed to sort by date entered or modified instead of by location in the book. Here is the dump of that database table.

The table dump was key, because Claude analyzed the CFI strings that I’d been struggling with and created a sophisticated parser that could extract positional information from cryptic strings like epubcfi(/6/24[c3.xhtml]!/4/188/2/1,:0,:1). And I was done with that until the end, when I had to revisit it multiple times in the plugin version.

Adding Chapter Detection

With sorting fixed, I pushed further:

Now that we have that fixed, is there a way to put chapter headings where they belong in the resulting markdown?

Because I was using a small set of 5 books with highlights, this seemed to work well. Initially, chapters in some books came out like “c3.xhtml” and various other things. But it magically fixed that.

But in the end, it was not magic. And just to let you know, I was using Claude 4, and Claude 4 will use workarounds and not tell you about it. When I tested the script on an account that had 60 books with highlights, none of the chapters in the results markdown were human readable.

I looked into what Claude wrote and it was basically a hack. It was a series of ifs customized to that first set of 5 books, so only they came out right. These are still not human readable in the plugin. I figure I have to use the SQLite results in unison with the ePub file data to make them so, but that’s what new features are for.

new-obsidian-annotations-md-template

Enhanced Features

I wanted to make sure I added everything I could to the Python script I could before moving onto the plugin and added:

  • Citation-ready format
  • More metadata extraction
  • Better error handling

You can find the code for that here.

Phase 2: Converting to an Obsidian Plugin

It took me about an hour to update the Python script. I figured I was on a roll. I had working code, and all Claude had to do was convert it to JavaScript and make it an Obsidian plugin. Famous last words. There is always something and many times it is something stupid.

I had updated the Python script in a Claude chat inside of a Claude project, but had yet added any Project Resources. Now that I was going to work on the plugin, I uploaded these there:

  • The final Python script: So it could reference this instead of reinventing the wheel.
  • The blog post I wrote about the original Python version: I figured it would explain what it was doing and what I was trying to do.
  • The Obsidian sample plugin repository: I am not sure I needed this. Maybe at the beginning, but I eventually realized (duh), that project resources become part of the context, which means your chats get cut off quicker, so I removed it. It never had problems with the plugin API.

And I sent this message:

I have attached a Python script to import apple books annotations. I have also attached the Obsidian plugin sample for reference. I have also attached an article on how an older version of the Python script integrates with Obsidian. Help me create an Obsidian plugin that does the same thing, step by step.

The Development Process

Claude recognized I had sequential thinking MCP and used it right away and came up with a plan.

claude-generate-obsidian-plugin

But I forgot to tell Claude where the project was located, so after that, it spit out all the files in the chat. I quickly corrected that issue by telling it to create all the files in my project itself.

The initial conversion went surprisingly smoothly:

  1. Project structure creation - all necessary TypeScript files
  2. Database access logic - converting Python SQLite to JavaScript
  3. Markdown generation - translating the formatting logic
  4. Settings interface - creating a proper Obsidian settings panel, which I was even worried about at this point.

obsidian-annotation-import-settings

Technical Challenges and Solutions

Well, it was functional. But let’s just say I was not even halfway done yet. Also, and I am so tired of this, Claude corrected my name everywhere from “Stephan” to “Stephen”.

claude-getting-my-name-wrong

Just like Google always does.

fuck-you-google

No, Google, my name is “Stephan.” But back on track. Here are the issues I ran into after I had a “working” Obsidian plugin.

SQLite Library Issues

A big hurdle was database access. Claude initially tried better-sqlite3 but ran into Electron compatibility issues. We switched to sql.js, a pure JavaScript SQLite implementation, but then faced WebAssembly loading problems.

The solution: Configure sql.js with proper WASM file handling in the Obsidian environment.

Query Formatting Problems

Even after fixing the SQLite library, we had issues with SQL query formatting when passed to the command line. Claude described the approach as “bulletproof” right before it broke, which became a running theme.

Chapter Parsing Regression

One book showed chapters as “Ahr5106 us trade bbp text 2” instead of readable names. This required Claude to revisit the CFI parsing logic and ensure consistency with the Python version. This is still a challenge and I am going to do research before I try to fix this.

The EPUB Metadata Challenge

It was now two hours since I started revising the original Python script. The Python script used ebooklib to extract book covers and enhanced metadata. Finding a JavaScript equivalent proved challenging. And I was a little gun shy by now, so I asked:

Is there anything in JavaScript that will do the same thing the python script did with ebooklib, like get the cover image and other metadata? If you know of something, do not commit code until I say it is working. It works now and I don’t want to commit broken code.

Claude suggested three options, and I chose epub2 for its popularity and feature set. However, it took several rounds of debugging across multiple chat sessions to get ePub parsing working correctly.

Real-World Testing Challenges

Testing on my actual Apple Books account (with 4,711 book) shook some new bugs loose:

Database Schema Variations

The plugin tried to query columns that didn’t exist on my primary account, causing SQLite errors. The Python script handled this gracefully, but the plugin needed updates.

Memory and Buffer Limits

With thousands of books, we hit “maxBuffer length exceeded” errors. Claude implemented chunked processing to handle large datasets.

Annotation Grouping Issues

The plugin kept breaking up annotations that should have been together and duplicating others. This took six rounds of debugging, with Claude repeatedly missing the fact that the Python version worked perfectly.

I finally resorted to extended thinking:

Think as hard as you can about the fact that the Python script is working, and the plugin is not and the only thing happening is SQLite queries and handling the results, so this should be fucking simple.

The Development Timeline

  • Hour 1-2: Python script update, initial plugin creation, and basic functionality
  • Hour 3: Fighting with ePub metadata extraction
  • Hour 4-6: Real-world testing, debugging on large dataset, and configuration tweaking

Total development time: About 6 hours across multiple sessions, with Claude handling the bulk of the coding.

claude-create-obsidian-plugin-results

What I Learned This Time

The Good

  • Rapid prototyping: Claude excels at converting working logic between languages most times, but not understanding that it could use the same SQLite queries in both JavaScript and Python was a pain.
  • Comprehensive solutions: Often suggests improvements you haven’t considered
  • Pattern recognition: Great at handling complex parsing tasks like CFI strings
  • Documentation: Automatically generates thorough README files and comments

The Challenging

  • Context switching: Moving between chat sessions sometimes loses important context
  • Overconfidence: Claims solutions are “bulletproof” right before they break
  • Debugging persistence: Sometimes fixates on wrong solutions instead of reverting to working code
  • Name corrections: Consistently “corrected” my name spelling throughout the project

Tips for Successful Claude Desktop Coding

  1. Use projects for better context persistence
  2. Be specific about what’s working vs. broken
  3. Maintain working versions before attempting fixes
  4. Test incrementally rather than making multiple changes at once
  5. Provide concrete examples when debugging

The Results

I am happy with the results. There is still some more work to do:

  • Not sure what is happening with the covers. The Python version had much more luck finding the covers, but then it used a more extensive library to do it.
  • The chapter names are still in gibberish in general. I think is because it is just using the value in the CFI location string. I am guessing I have to look this up somehow in the ePub file.
  • There seems to be a configuration value for adding the annotation date to each entry, but I haven’t seen one. Not sure what is going on there.

But for now I can use it and when I fix it, just have it overwrite all the old files. Then, once I am happy with it, store a hash of the file in properties or something like that and have overwriting work more intelligently.

obsidian-book-note-example

How to Install the Apple Books Highlight and Note Importer

I do plan on releasing this as a community plugin. I just want to use it a while to see if there are any more bugs I want to fix or features I want to add.

But for now the simplest way to install and test this plugin is with BRAT (Beta Reviewer’s Auto-update Tool):

  1. Install BRAT from the Community Plugins store in Obsidian
  2. Open BRAT settings (Settings → BRAT)
  3. Click “Add Beta plugin”
  4. Enter this repository URL : https://github.com/eristoddle/obsidian-apple-books-import
  5. Click “Add Plugin” - Choose the latest version and BRAT will automatically install and enable it
  6. Auto-updates : BRAT will automatically update the plugin when new versions are released if you choose the latest version from the dropdown.

What’s Next

I took a break from a more complex project for a day to see if I could finish a smaller one. And even though there is still more work to do here, I think it was a success and an excellent test. I will continue to explore new ways of making the “vibe coding” process go smoother. In the couple of days that it took me to write this, I found even more tools to help.

Because I have no end of software ideas I have been collecting, but never got to, because I never had the time. So I will continue to work on my main vibe-coding project while taking a break to test new ways of doing this on smaller projects. My next post should be about the next set of things I learned developing an Electron app with Claude Code. I already have notes on it, but wanted to try this first.

Top comments (0)