name: karakeep-obsidian-sync description: Full CRUD CLI for Karakeep — standalone bookmarking tool with optional Obsidian vault sync version: 0.5.1 author: Dustin Chadwick metadata: hermes: tags: [karakeep, obsidian, bookmarking, sync, cli, crud]
Karakeep ↔ Obsidian Sync
A CLI tool for Karakeep (self-hosted bookmarking). All CRUD commands work standalone — Obsidian vault sync is an optional add-on.
Author: Dustin "Dusty" Chadwick (@ShaggyD)
CLI Tool: kk
Single-file Python script — zero external dependencies.
Commands
| Command | Description |
|---|---|
kk login <url> <key> |
Connect to a Karakeep instance |
kk logout |
Wipe saved config and state |
kk list [--limit N] |
List recent bookmarks |
kk get <id> |
View bookmark as JSON |
kk add <url> |
Save a URL bookmark |
kk text <content> |
Save a plain text bookmark |
kk note <id> [text] |
View/update a bookmark's note |
kk edit <id> --title= --note= |
Update title, note, archived, favourited |
kk tag <id> <tag>... |
Attach tags |
kk untag <id> <tag>... |
Detach tags |
kk delete <id> |
Delete a bookmark |
kk sync [--enrich] |
Pull new bookmarks → vault; --enrich fetches transcripts/content |
kk push |
Push vault note changes → Karakeep (if configured) |
kk enrich <id> |
Fetch YouTube transcript or article content for a single bookmark |
kk enrich --next |
Enrich the next un-enriched YouTube bookmark (newest first) |
kk enrich --list |
Show YouTube enrichment queue and progress |
kk config [--set key=val] |
Show or update configuration |
None of the CRUD commands (list, get, add, text, note, edit, tag, untag, delete) require a vault. Sync and push only work if vault_path is configured.
Quick Start (standalone — no vault needed)
# Install
cp kk ~/.local/bin/ && chmod +x ~/.local/bin/kk
# Connect
kk login https://hoarder.example.com ak2_your_api_key_here
# Use
kk list
kk add https://example.com/article
kk note <id> "My thoughts on this"
Optional: Obsidian Vault Sync
After login (or later), configure vault sync:
kk config --set vault_path=~/my-vault/003_Resources/Bookmarks
kk config --set daily_path=~/my-vault/002_Journal/daily # optional
kk sync # initial pull
The login command also offers an interactive vault setup prompt.
What sync does
- Creates a
_Index.mdin your bookmark directory with Dataview-powered Recently Added and By Tag tables - Each bookmark becomes a
.mdfile with frontmatter (source, URL, tags, author) - Backlinks point to
[[_Index|Bookmarks]](relative — works from any folder) - Optional: appends a cross-reference to today's daily note if
daily_pathis configured
What push does
Scans your vault for edited ## Notes sections and pushes changes back to the bookmark's note field in Karakeep.
Enrichment
Bookmarks can be enriched with additional content before saving to the vault:
| Type | Enrichment | How |
|---|---|---|
| YouTube links | Full transcript (25K+ chars) | youtube-transcript-api via uv run --with |
| Article links | Readable text extraction | curl + Python's built-in html.parser |
Commands:
kk enrich <id>— enrich a single bookmark and preview the resultkk enrich --next— auto-find the next un-enriched YouTube bookmark (newest first) and add transcript to the vault notekk enrich --list— show progress: total YouTube bookmarks, how many enriched, how many remainingkk sync --enrich— sync new bookmarks and auto-enrich each one
Pacing pattern: For existing bookmarks, use kk enrich --next in a daily cron to process one per day. This avoids hammering the API and gives you a daily artifact to review. Typical pace: 26 bookmarks → ~1 month to catch up.
Enrichment is best-effort. If fetching fails (no uv, no curl, network timeout), the note is still saved with Karakeep's existing content. The cron default is kk sync without --enrich because enrichment adds latency (transcript fetching, page downloads).
Vault path inference
When daily_path is not explicitly set, the tool infers it from vault_path using a PARA-structure heuristic. See references/vault-path-inference.md.
Automation (cron)
# Daily sync + enrichment (both run at midnight, enrich 10 min later)
0 0 * * * /home/user/.local/bin/kk sync
10 0 * * * /home/user/.local/bin/kk enrich --next
Push has no cron by default — editing notes in your vault should be intentional.
Note Format
---
created: 2026-04-30 05:49
source: karakeep
karakeep_id: <full-bookmark-id>
source_url: <original-url>
author: <author>
publisher: <publisher>
tags: [tag1, tag2]
---
No personal tags or conventions are baked in. Notes link back to [[_Index|Bookmarks]] which works from any vault folder structure.
Design Principles for General Distribution
This skill was refactored from a personal tool to a distributable one. Key lessons embedded in the design:
- CLI-first, features second. The core CRUD commands must work without any vault configuration. Sync and push are opt-in.
- No default paths. Never write files to a location the user didn't explicitly configure.
- Interactive onboarding, but skippable.
kk loginasks about vault setup, but you can hit enter to skip and configure later. - Relative backlinks.
[[_Index|Bookmarks]]works from any vault folder — no hardcoded paths. - No personal conventions. Status tags, PARA structure assumptions, and author-specific frontmatter fields are excluded from generated content.
- YouTube Shorts support.
_extract_youtube_idmust handle/shorts/URLs — these URLs don't match/watch,/embed/, or/v/patterns. Without it, Shorts fall through to article enrichment and never get a## Transcriptsection, creating an infinite loop in--next. - Graceful degradation. If uv or curl are missing, enrichment silently skips rather than crashing. The note still gets created.
- Best-effort enrichment. YouTube transcripts via uv (tool installed on-the-fly, no permanent dependency — see
references/uv-adhoc-deps.md). Article text via curl + stdlib HTML parsing. Failures don't block the sync.
Known Pitfalls
- API key in plaintext — stored at
~/.config/kk/config.json. Recommended:chmod 600 ~/.config/kk/config.json - User-Agent block — Karakeep blocks Python's default
urllibUA. The tool setsUser-Agent: kk-cli/0.5 - Full IDs required — use full bookmark IDs from
kk list. Truncated IDs return 404 - Tag format — the API expects
{"tagName": "..."}objects, not strings. Handled internally - Clearing a note:
kk note <id> ""clears a bookmark's note field - Filename collisions — bookmarks without a title/content map to
unknown-untitled.md - Python 3.7+ required (for
pathlibanddatetime.fromisoformat) - Empty DELETE responses — API returns 204 No Content. Tool handles empty body gracefully
--nextvs--listdiscrepancy with orphan bookmarks —kk enrich --nextonly processes vault files that have a non-emptykarakeep_idin frontmatter. Bookmarks created manually in the vault (no Karakeep record) are invisible to--next, so it may report "All enriched" while--liststill shows remaining. Meanwhile,--listonly checks for the## Transcriptheader string — it does not filter bykarakeep_id. If you see a mismatch between the two commands, check missing karakeep_ids and custom header names (e.g.,## Raw Transcriptwon't match## Transcript). Reconcile by either adding a Karakeep entry or tweaking the transcript header in the vault file.- Transcript header matching is strict — enrichment detection checks for the literal string
## Transcript. Variants like## Raw Transcript,## Full Transcript, or## YouTube Transcriptare not recognized as enriched. If a vault file has a transcript under a different heading,--listwill show it as un-enriched and--nextmay loop over it. Keep the header exactly## Transcript.