- Go 99.2%
- Nix 0.8%
| .forgejo/workflows | ||
| .opencode | ||
| cmd/pkm | ||
| docs | ||
| internal | ||
| scripts/release | ||
| .envrc | ||
| .gitignore | ||
| .goreleaser.yml | ||
| AGENTS.md | ||
| DECISIONS.md | ||
| devenv.lock | ||
| devenv.nix | ||
| devenv.yaml | ||
| flake.lock | ||
| flake.nix | ||
| go.mod | ||
| go.sum | ||
| LICENSE | ||
| README.md | ||
| SCHEMA.md | ||
| skills-lock.json | ||
| VERSION | ||
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 (
~/PKMorPKM_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 withPKM_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_TOKENVERCEL_ORG_IDVERCEL_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