A Zettelkasten-style CLI knowledge base with wiki links, heat scoring, and full-text search.
  • Go 99.2%
  • Nix 0.8%
Find a file
ferrumboll f1c0d79283
All checks were successful
ci / go-test (push) Successful in 1m19s
ci / conventional-commits (push) Successful in 1m25s
ci / nix-build (push) Successful in 2m58s
docs-deploy / deploy (push) Successful in 1m18s
docs: add Starlight docs site
2026-04-25 22:22:56 +02:00
.forgejo/workflows docs: add Starlight docs site 2026-04-25 22:22:56 +02:00
.opencode feat(library): replace vaults with markdown libraries 2026-04-25 21:40:53 +02:00
cmd/pkm feat(library): replace vaults with markdown libraries 2026-04-25 21:40:53 +02:00
docs docs: add Starlight docs site 2026-04-25 22:22:56 +02:00
internal feat(library): replace vaults with markdown libraries 2026-04-25 21:40:53 +02:00
scripts/release ci(release): release pipeline 2026-04-25 20:40:01 +02:00
.envrc feat: initial pkm CLI — vault init, capture, save with templates 2026-03-29 11:47:27 +02:00
.gitignore docs: add Starlight docs site 2026-04-25 22:22:56 +02:00
.goreleaser.yml ci(release): release pipeline 2026-04-25 20:40:01 +02:00
AGENTS.md ci(release): release pipeline 2026-04-25 20:40:01 +02:00
DECISIONS.md feat(library): replace vaults with markdown libraries 2026-04-25 21:40:53 +02:00
devenv.lock chore: devenv 2026-04-25 20:25:11 +02:00
devenv.nix docs: add Starlight docs site 2026-04-25 22:22:56 +02:00
devenv.yaml feat: initial pkm CLI — vault init, capture, save with templates 2026-03-29 11:47:27 +02:00
flake.lock feat: flake 2026-03-29 21:32:50 +02:00
flake.nix ci(release): release pipeline 2026-04-25 20:40:01 +02:00
go.mod feat: initial pkm CLI — vault init, capture, save with templates 2026-03-29 11:47:27 +02:00
go.sum feat: initial pkm CLI — vault init, capture, save with templates 2026-03-29 11:47:27 +02:00
LICENSE add MIT license 2026-03-29 21:34:49 +02:00
README.md docs: add Starlight docs site 2026-04-25 22:22:56 +02:00
SCHEMA.md feat(library): replace vaults with markdown libraries 2026-04-25 21:40:53 +02:00
skills-lock.json feat: flake 2026-03-29 21:32:50 +02:00
VERSION feat(library): replace vaults with markdown libraries 2026-04-25 21:40:53 +02:00

pkm

A Zettelkasten-style CLI knowledge base for Markdown libraries with wiki links, heat scoring, and full-text search.

Features

  • Wiki-style [[references]] with backlink tracking and orphan detection
  • Full-text search via SQLite FTS5 with tag, type, and scope filtering
  • Heat scoring — composite relatedness between notes (TF-IDF + tags + links + co-references)
  • Pluggable note types — register custom types with templates and directory mappings
  • Maps of Content — auto-generated index notes grouped by type with [[wiki links]]
  • Dual library scoping — global (~/PKM or PKM_GLOBAL_LIBRARY) and project (git root)
  • Team-friendly — markdown is the source of truth, SQLite index is gitignored and rebuilt with pkm index

Install

Homebrew

brew tap fairlabs/tap https://forgejo.fairlabs.dev/fairlabs/homebrew-tap.git
brew install pkm

Nix

nix profile install git+https://forgejo.fairlabs.dev/ferrumboll/pkm

Or try it without installing:

nix run git+https://forgejo.fairlabs.dev/ferrumboll/pkm -- --help

Build from source

Requires Go 1.21+.

git clone https://forgejo.fairlabs.dev/ferrumboll/pkm.git
cd pkm
go install ./cmd/pkm/

Quick Start

# Initialize a library
pkm init --scope global

# Capture a quick thought
pkm capture "look into event schema changes"

# Save a structured note
pkm save --title "Architecture Decision: Switch to Kafka" --tags "architecture,kafka"

# Index the library (required before search/heat/backlinks)
pkm index

# Search
pkm search "kafka"
pkm search --tags architecture

# Find related notes
pkm heat compute
pkm heat show "Architecture Decision: Switch to Kafka"

# Check backlinks
pkm backlinks "Architecture Decision: Switch to Kafka"

# Generate a Map of Content
pkm moc "Architecture Decisions" --tags architecture

Custom Note Types

The 3 default types (note, moc, fleeting) are seeded on pkm init. Add your own:

# Create a template file
cat > dag-template.md << 'EOF'
---
type: dag
schedule: ""
retry: ""
---

# {title}

## What

## How

## Why

## See Also
EOF

# Register it
pkm type add dag --template dag-template.md --dir Notes/DAGs --scope global

# Use it
pkm save --type dag --title "DAG: user_events_daily" --tags dag,airflow

# Types support inheritance
pkm type add etl-dag --template etl-template.md --dir Notes/ETL --base dag

# List registered types
pkm type list

Commands

Command Description
pkm init --scope global|project|all Initialize library and seed default types
pkm capture "text" Quick capture to Inbox
pkm save --type T --title "X" Save note from type template
pkm index Rebuild SQLite index from markdown files
pkm search "query" Full-text search with filters
pkm backlinks "Title" Find notes referencing a given note
pkm backlinks refs "Title" Show outgoing references from a note
pkm backlinks orphans Find unlinked notes
pkm heat compute Compute pairwise relatedness scores
pkm heat show "Title" Show related notes with score breakdown
pkm moc "Topic" Generate a Map of Content
pkm type add|list|remove|show Manage pluggable note types

Library Structure

library root/
├── Inbox/              # Fleeting captures
├── Notes/              # Permanent notes (subdirs per type)
├── MOCs/               # Maps of Content
└── .pkm/               # Hidden PKM internals
    ├── types/          # Template files for registered note types
    ├── types.json      # Type registry
    └── .index/
        └── pkm.db      # SQLite index (gitignored, rebuilt via pkm index)

Two library locations:

  • Global (~/PKM/, override with PKM_GLOBAL_LIBRARY) — personal, cross-project knowledge
  • Project (git repo root with hidden .pkm/ internals) — repo-specific notes

--scope defaults to project inside a git repo, global otherwise.

Generated filenames preserve readable Markdown names. Unsafe link/path characters are replaced, so DAG: user_events_daily becomes DAG - user_events_daily.md with the original title added as a frontmatter alias.

Compatibility

PKM writes plain Markdown with YAML properties and wikilinks. This keeps libraries usable with editors and PKM tools that understand those conventions, including Obsidian.

Team Workflow

# Commit markdown and type definitions (SQLite DB is gitignored)
git add Inbox/ Notes/ MOCs/ .pkm/types/ .pkm/types.json
git commit -m "add DAG docs"

# After pulling
pkm index   # rebuild the index from markdown

Heat Scoring

Heat is a composite relatedness score (0-100%) between note pairs:

Signal Weight Method
Content similarity 0.40 TF-IDF cosine similarity
Shared tags 0.30 Jaccard similarity
Direct backlinks 0.20 Bidirectional link count
Co-references 0.10 Both link to the same third note

OpenCode Integration

pkm ships with an OpenCode skill and agent for natural language access:

# Install skill + agent globally
cp -r .opencode/skills/pkm ~/.config/opencode/skills/
cp .opencode/agents/pkm.md ~/.config/opencode/agents/

# Then in any OpenCode session:
# "take a note about X"
# "search for notes about kafka"
# "what's related to my architecture decision?"

Docs Site

The docs site lives in docs/ and uses Astro Starlight with Bun:

cd docs
bun install --frozen-lockfile
bun run dev
bun run build

For Vercel, set the root directory to docs, install command to bun install --frozen-lockfile, build command to bun run build, and output directory to dist.

Forgejo deploys docs on master when files under docs/ change. Configure these repository secrets:

  • VERCEL_TOKEN
  • VERCEL_ORG_ID
  • VERCEL_PROJECT_ID

Last Change

0.2.0: Switched to Markdown library roots with hidden .pkm internals, readable filenames, aliases, and extended wikilink resolution.

License

MIT