Sitemap

Obsidian as a microapp platform — Building ePub Files with Feedly and vibe coding

6 min readAug 20, 2025
Press enter or click to view image in full size
All of my Feedly Sync files

After I returned from a trip to Cancun, I had close to 1800 unread articles in Feedly. I use Feedly for getting all my news in one place: RSS feeds, newsletters, and even Reddit. It’s great to not need to jump around a lot, plus I am able to highlight text and add notes for later. The downside is my news junkie behavior can lead to an overwhelming amount of news.

When I came back, I decided that I needed a new way to pare down that list all the way down to zero (or close to it). I wrote a script on my computer to download all those articles to an ePub file I could download to my phone. I could listen to it as an audiobook via TTS and pick it up when I’m on the subway.

Yet there was still a limitation to this whole thing, which is that it had to run on my PC. If I’m just on my phone, I want a way to download the ePub file directly. If I forget to run the script in the morning, or my PC isn’t working (as was the case recently), this whole system is stuck.

Obsidian and Microapps

I’ve written Obsidian plugins before, and up to this point my approach has been to focus on getting all my data in one place. I download my Feedly annotations to my vault. I sync all of my concerts with date links. It’s been about storing content.

But that’s a limiting perspective. Obsidian plugins are Node.js scripts (with some limitations, as we’ll see) which can do anything that you program. Rather than just plugins, I could write any small program and have it run within Obsidian so that it works whether I’m on my PC or my phone. The program is available everywhere.

This would be more for local programs where I might want it to run in the context of a local device. In the past I’ve published some endpoints in the cloud so that I can host iCal files or RSS feeds and that’s fine if I want something globally available. But I might not always want that, or I may want a better UI or a better UX. In these cases, I just want to have an app.

So I’m starting to think of Obsidian as this universally available microapp platform. The real benefit is that, unlike other platforms, it has access to files and it runs on my phone. This is an inspiring approach and I’ve been thinking of different ideas

But my first idea is moving this ePub generation into Obsidian itself, so I can read it on my phone.

Obsidian Platform Environment

  const Epub = require('epub-gen')
const option = {
title: `Your Evening Discourse for ${new Date().toDateString()}`,
publisher: 'Quillcast',
author: 'Evening Discourse',
// Canonical data type: https://github.com/cyrilis/epub-gen
content: contents
}
const render = new Epub(option, `/mnt/c/Users/handn/Dropbox/Obsidian/FeedlySync-${Date.now()}.epub`)
await render.promise
console.log('done')

While my original script was also written in Node.js, I can’t naively move the code over and run it. Obsidian’s environment does not allow direct calls to the file system. You need to run through its Vault interface. There are a few system-level modules that don’t work and need to be replaced.

Unfortunately, the epub-gen library has a direct dependency on fs. In fact, the function above is the only way to write a file. It abstracts away the file system is a way that seemed helpful but now is quite harmful.

The library was deprecated years ago and there was no way to change that. I had to move over to a new library, nodepub, which had a helpful getFilesForEPUB which returns an object containing all of the data without actually touching the file system. I can then use the JSZip library to bundle all the files into a single blob and write that using the Vault APIs.

Okay, that’s my plan. I already knew how to fetch all the data from Feedly. I didn’t need to rewrite the entire script, just the part where it creates the ePub file. But I haven’t used these libraries before. How much time would it actually take to do all of this?

Vibe Coding

Vibe coding is this new paradigm of giving your product requirements to a large-language model and having it figure out what to do.

I decided to open up Gemini (Flash-2.5) and prompt it to create the necessary code. I thought it would be a good starting point at least and I could modify it afterwards.

So I gave it this prompt:

nodepub getFilesForEPUB output and write that as an epub file in an obsidian plugin

It came up with a fairly long answer with some different sections and then used the Canvas feature to output the entire code sample. I was impressed by how it actually used the correct Obsidian APIs, and I really didn’t know about the APIs. But I copied over the relevant bits and tried to run it.

It required a little bit of massaging. For one, the use of JSZip was incorrect. It stuck every file into the main directory, causing file corruption. Instead, files need to be placed into correct folders for it to meet the file specification.

So this output was wrong:

 // 3. Initialize JSZip
const zip = new JSZip();

// 4. Add mimetype first and uncompressed
const mimetypeFile = filesForEpub.find(file => file.name === 'mimetype');
if (mimetypeFile) {
zip.file(mimetypeFile.name, mimetypeFile.data, { compression: 'STORE' });
}

// 5. Add all other files
for (const file of filesForEpub) {
if (file.name !== 'mimetype') {
zip.file(file.name, file.data);
}
}

// 6. Generate the final EPUB Buffer
const epubBuffer = await zip.generateAsync({ type: 'nodebuffer', mimeType: 'application/epub+zip' });

And after spending some time fixing it, the right code looked like:


// 3. Initialize JSZip
const zip = new JSZip();

// 4. Add mimetype first and uncompressed
console.log(files)
const mimetypeFile = files.find((file: any) => file.name === 'mimetype');
if (mimetypeFile) {
zip.file(mimetypeFile.name, mimetypeFile.data, { compression: 'STORE' });
}

// 5. Add all other files
const folders: Record<string, any> = {
'META-INF': zip.folder('META-INF'),
'OEBPF': zip.folder('OEBPF'),
}
folders['OEBPF/css'] = folders['OEBPF'].folder('css')
folders['OEBPF/content'] = folders['OEBPF'].folder('content')
folders['OEBPF/images'] = folders['OEBPF'].folder('images')

for (const file of files) {
if (file.name !== 'mimetype') {
if (file.folder !== '') {
console.log(' ', file.folder, file.name, file.content.length)
folders[file.folder].file(file.name, file.content);
} else {
console.log(' ', file.name, file.content.length)
zip.file(`${file.folder}/${file.name}`, file.content);
}
}
}

// 6. Generate the final EPUB Buffer
const arrayBuffer = await zip.generateAsync({ type: 'nodebuffer', mimeType: 'application/epub+zip' });

Once I got this to work on my PC, I then needed to make sure that it would also work on my phone. It quietly failed. In fact, the plugin wouldn’t even load. You can debug Obsidian plugins on your phone through Chrome’s remote inspector which helped me discover the reason.

When you’re using the nodepub library, it requires you provide a path to a cover image. This cover isn’t required for ePub but it is for the library. Once you provide a path, it uses the fs module to read it. This is not allowed on mobile. Unfortunately the nodepub library is also deprecated, so the author won’t fix it.

So I had to fork the library to remove this requirement. And I had to write custom implementations of methods in the path module because that’s also not available on mobile. Finally, last-minute debugging revealed that the nodebuffer type in the generateAsync method would fail on a phone. It needed to be replaced with arraybuffer.

After a few hours of debugging, I released a fully working v1.2.5 of my Feedly for Obsidian plugin which now has a generate ePub command I can call on my phone so that I can always fetch the latest articles whenever I want from any device.

Conclusion

Does vibe coding work?

I think it was a useful rubber duck and I did ask follow-ups as I worked my way through this new feature. Its ability to understand codebases was adequate. But it definitely was insufficient to actually get the feature complete. Because this feature required external tools like an ePub reader, I do wonder how it could’ve actually been able to debug this if left completely autonomously.

I like how Feedly gives me control. It’s not anti-AI. I have created a few AI topic feeds on subjects I want to learn more about. But at the same time, I do like being able to subscribe to a full blog or website and see everything and choose what I want to read.

Now I can get back to reading the news and think up an AI agent which might be able to build valid ePub files in the future.

--

--

Nick Felker
Nick Felker

Written by Nick Felker

Social Media Expert -- Rowan University 2017 -- IoT & Assistant @ Google

No responses yet