opennote-vault
OpenNotes Vault Management
Overview
OpenNotes manages markdown notes in notebooks with DuckDB-powered SQL queries. Create notes with frontmatter metadata, search by content or links, and execute reusable query views.
Core principle: Notes are markdown files + metadata searchable via SQL queries.
When to Use
Use this skill when you need to:
- Create or manage notebooks of markdown notes
- Add notes with frontmatter metadata (tags, author, status, etc.)
- Search notes by content, metadata, or relationships
- Execute SQL queries against note collections
- Find broken links or orphaned notes
- Create reusable query views
Don't use for:
- Simple file-based note taking (just use mkdir/touch)
- Binary attachments or non-markdown content
- Real-time collaborative editing
- Version control (use git separately)
Quick Reference
Essential Commands
| Task | Command | Example |
|---|---|---|
| Setup | opennotes init |
One-time initialization |
| Create notebook | opennotes notebook create --name <name> |
opennotes notebook create --name "work" |
| Add simple note | opennotes notes add "<title>" |
opennotes notes add "Meeting Notes" |
| Add with metadata | opennotes notes add "<title>" --data k=v |
opennotes notes add "Sprint" --data tag=urgent |
| List notes | opennotes notes list |
Shows all notes in current notebook |
| Search text | opennotes notes search "<query>" |
opennotes notes search "deadline" |
| Boolean query | opennotes notes search query --and <field>=<val> |
opennotes notes search query --and data.tag=meeting |
| SQL query | opennotes notes search --sql "<query>" |
opennotes notes search --sql "SELECT * FROM ..." |
| Execute view | opennotes notes view <name> |
opennotes notes view today |
| List views | opennotes notes view |
Shows all available views |
Feature Detection
Always check --help to determine available features, as version numbers may vary in dev builds.
# Check for metadata support
opennotes notes add --help | grep -- '--data' && echo "Metadata supported"
# Check for boolean query support
opennotes notes search query --help && echo "Boolean queries supported"
Notebook Management
Creating Notebooks
# Create new notebook in current directory
opennotes notebook create --name "project-notes"
# Creates: .opennotes.json in current directory
# Notes are stored in current directory (root: ".")
# To create notebook in a new subdirectory:
mkdir project-notes && cd project-notes
opennotes notebook create --name "project-notes"
# View notebook info
opennotes notebook
Notebook Auto-Discovery
OpenNotes finds notebooks automatically:
- Searches current directory for
.opennotes.json - Walks up parent directories until found
- Falls back to registered notebooks in global config
- Override with
--notebook <path>flag
# Work from anywhere in notebook tree
cd project-notes/2026/january
opennotes notes list # Auto-discovers notebook
# Use specific notebook
opennotes notes list --notebook ~/work-notes
Managing Multiple Notebooks
# List all registered notebooks
opennotes notebook list
# Register existing notebook
opennotes notebook register ~/existing-notes
# Add context path (for auto-discovery)
opennotes notebook add-context /path/to/workspace
Note Creation
Basic Note Creation
# Simple note (filename auto-slugified)
opennotes notes add "My First Note"
# Creates: my-first-note.md
# Title with special characters
opennotes notes add "2026-01-24: Daily Log"
# Creates: 2026-01-24-daily-log.md
# Using deprecated --title flag (still works)
opennotes notes add --title "Meeting Notes"
With Metadata (v0.0.3+)
# Single metadata field
opennotes notes add "Team Standup" --data author="John Doe"
# Multiple fields
opennotes notes add "API Review" \
--data author="Engineering Team" \
--data priority=high \
--data status=draft
# Array values (repeated fields)
opennotes notes add "Sprint Planning" \
--data tag=meeting \
--data tag=urgent \
--data tag=sprint
# Result in frontmatter:
# ---
# author: John Doe
# priority: high
# tag:
# - meeting
# - urgent
# ---
Path Options
# Create in subfolder
opennotes notes add "Project Plan" projects/
# Creates: projects/project-plan.md
# Specify exact filename
opennotes notes add "Changelog" CHANGELOG
# Creates: CHANGELOG.md
# Full path with extension
opennotes notes add "README" docs/README.md
# Creates: docs/README.md
Content Priority
Notes are populated in this order:
- Stdin (piped content)
- Template (
--templateflag) - Default (simple
# Title\n\n)
# From stdin
echo "# Meeting Notes\n\n- Point 1" | opennotes notes add "Meeting"
# From template
opennotes notes add "Standup" --template daily-standup
# Default (just title)
opennotes notes add "Quick Note"
Searching Notes
Text Search (v0.0.2+)
# Search all fields (content, title, filename)
opennotes notes search "deadline"
# Case-insensitive, partial matches
opennotes notes search "meet" # Finds "Meeting", "meet", "meetings"
# No results
opennotes notes search "nonexistent"
# Output: No notes found matching 'nonexistent'
Fuzzy Search (v0.0.3+)
# Typo-tolerant search
opennotes notes search --fuzzy "meetng" # Finds "meeting"
# Ranked by relevance
opennotes notes search --fuzzy "project"
# Title matches ranked higher than body matches
Boolean Queries (v0.0.3+)
# Single condition (AND)
opennotes notes search query --and data.priority=high
# Multiple AND conditions
opennotes notes search query --and data.status=active --and data.priority=high
# OR conditions
opennotes notes search query --or data.priority=high --or data.priority=critical
# NOT conditions
opennotes notes search query --and data.type=task --not data.status=archived
# Path glob patterns
opennotes notes search query --and "path=projects/**/*.md"
# Complex queries (mix AND/OR)
opennotes notes search query \
--and "data.type=task" \
--and "data.status=active" \
--or "data.priority=high" \
--or "data.priority=critical"
Supported Fields:
data.*(any metadata field, use for scalar values)path(file path with glob support)title(note title from frontmatter or filename)links-to(outgoing links)linked-by(incoming links)
Operators:
--and(all must match)--or(any must match)--not(exclude matches)
⚠️ Limitation: Boolean queries work best with scalar metadata values (strings, numbers). For array fields (created with repeated --data flags), use SQL queries instead.
Link Queries (v0.0.3+)
# Find notes linking to specific note
opennotes notes search query --and "links-to=README.md"
# With glob patterns
opennotes notes search query --and "links-to=docs/*.md"
# Find notes linked by specific note
opennotes notes search query --and "linked-by=index.md"
# Combined with other conditions
opennotes notes search query --and "links-to=*.md" --and "data.status=reference"
Querying Array Metadata
Array metadata fields (created with repeated --data flags) are stored correctly in YAML but have limitations in boolean query interface. Use these approaches:
# Text search works for array content
opennotes notes search "meeting" # Finds notes with 'meeting' in any array field
# SQL query for precise array membership
opennotes notes search --sql \
"SELECT * FROM read_markdown('**/*.md')
WHERE content LIKE '%- meeting%'" # Search in YAML array format
# Alternative: Use scalar metadata for queryable fields
# Instead of: opennotes notes add "Note" --data tag=meeting --data tag=important
# Consider: opennotes notes add "Note" --data status=draft --data category=meeting
# Then query with: opennotes notes search query --and data.category=meeting
SQL Queries (v0.0.3+)
# Direct SQL query (uses glob pattern from config)
opennotes notes search --sql "SELECT * FROM read_markdown('**/*.md')"
# With WHERE clause for scalar fields
opennotes notes search --sql \
"SELECT file_path, metadata FROM read_markdown('**/*.md')
WHERE metadata['status'] = 'active'"
# Query array fields (better for tag fields)
opennotes notes search --sql \
"SELECT file_path, metadata FROM read_markdown('**/*.md')
WHERE array_contains(metadata['tag'], 'meeting')"
# Complex aggregations
opennotes notes search --sql \
"WITH tagged AS (
SELECT file_path, metadata['tag'] as tag
FROM read_markdown('**/*.md')
)
SELECT tag, COUNT(*) as count
FROM tagged
WHERE tag IS NOT NULL
GROUP BY tag
ORDER BY count DESC"
Safety:
- Only
SELECTandWITHqueries allowed INSERT,UPDATE,DELETE,DROPblocked- Parameterized queries prevent SQL injection
Pattern Notes:
- Use glob patterns matching your
groups[].globsconfig - Metadata is stored as a struct/object - access fields with
metadata['field_name'] - Array fields use
array_contains()function for membership testing - Date comparisons use ISO 8601 format (e.g.,
'2026-01-20')
Views System (v0.0.3+)
Built-in Views
# List all available views
opennotes notes view
# Execute built-in view
opennotes notes view today # Notes created/updated today
opennotes notes view recent # Last 30 days
opennotes notes view untagged # Notes without tags
opennotes notes view orphans # Notes with no incoming links
opennotes notes view broken-links # Notes with broken links
# Parameterized view
opennotes notes view kanban --param status=in-progress
Custom Views
Define in notebook's .opennotes.json or global ~/.config/opennotes/config.json:
{
"views": {
"active-tasks": {
"description": "All active tasks",
"conditions": [
{"field": "tag", "operator": "=", "value": "task"},
{"field": "status", "operator": "=", "value": "active"}
]
},
"recent-meetings": {
"description": "Meetings in last 7 days",
"conditions": [
{"field": "tag", "operator": "=", "value": "meeting"},
{"field": "created", "operator": ">=", "value": "{{7_days_ago}}"}
]
}
}
}
View Precedence:
- Notebook config (
.opennotes.json) - Global config (
~/.config/opennotes/config.json) - Built-in views
Template Variables
Use in view definitions:
{{today}}- Today's date (YYYY-MM-DD){{yesterday}}- Yesterday's date{{this_week}}- Start of current week{{this_month}}- Start of current month{{7_days_ago}}- Date 7 days ago{{30_days_ago}}- Date 30 days ago
Common Workflows
Daily Note Workflow
# Create today's note
opennotes notes add "$(date +%Y-%m-%d): Daily Log" \
--data type=daily \
--data date="$(date +%Y-%m-%d)"
# View today's notes
opennotes notes view today
# Search recent dailies (SQL for date range)
opennotes notes search --sql "SELECT * FROM read_markdown('.notes/**/*.md') WHERE metadata['type'] = 'daily' AND created >= '2026-01-20'"
Project Management
# Create project epic
opennotes notes add "Q1 2026 Goals" projects/ \
--data tag=epic \
--data status=planning \
--data priority=high
# Create tasks
opennotes notes add "Implement Feature X" tasks/ \
--data tag=task \
--data epic=q1-2026-goals \
--data status=todo
# View active tasks
opennotes notes search query --and "data.tag=task" --and "data.status=todo"
# Kanban board
opennotes notes view kanban --param status=in-progress
Knowledge Base
# Create reference note
opennotes notes add "Git Cheatsheet" reference/ \
--data tag=reference \
--data tag=git \
--data category=tools
# Link analysis
opennotes notes view orphans # Unconnected notes
opennotes notes view broken-links # Fix broken links
# Find related notes
opennotes notes search query --and "data.tag=git"
Common Mistakes
❌ Using --title instead of positional argument
# Don't (deprecated flag)
opennotes notes add --title "My Note"
# Do (positional argument)
opennotes notes add "My Note"
❌ Forgetting flags for boolean queries
# Don't (missing flags)
opennotes notes search query tag=urgent status=active
# Do (use explicit flags and data prefix)
opennotes notes search query --and data.status=active --and data.priority=high
❌ Using boolean queries for array fields
# Don't (arrays not queryable via boolean interface)
opennotes notes search query --and data.tag=meeting # Won't work reliably
# Do (use SQL for array fields)
opennotes notes search --sql "SELECT * FROM read_markdown('**/*.md') WHERE array_contains(metadata['tag'], 'meeting')"
❌ Using --data on old version
# Check version first
opennotes notes add --help | grep -q -- '--data' || echo "Upgrade needed"
# On v0.0.2: Edit frontmatter manually after creation
opennotes notes add "My Note"
vim my-note.md # Add frontmatter manually
❌ Assuming case-sensitive search
# Search is case-INsensitive
opennotes notes search "MEETING" # Finds "meeting", "Meeting", etc.
❌ Forgetting notebook auto-discovery
# Don't (unnecessary --notebook flag)
cd ~/my-notes
opennotes notes list --notebook ~/my-notes
# Do (auto-discovery from current directory)
cd ~/my-notes
opennotes notes list
❌ Looking for notes in .notes/ directory
# Don't (notes are stored in notebook root by default)
ls .notes/ # Directory doesn't exist
# Do (check notebook root)
ls *.md # Notes stored here
Troubleshooting
No notebook found
# Error: No notebook found
# Solution 1: Create notebook
opennotes notebook create --name "my-notes"
# Solution 2: Register existing
opennotes notebook register ~/existing-notes
# Solution 3: Specify explicitly
opennotes notes list --notebook ~/my-notes
Metadata not showing in frontmatter
# Check version supports --data
opennotes notes add --help | grep -- '--data'
# Verify format: field=value (no spaces around =)
opennotes notes add "Note" --data "tag = value" # ❌ Wrong
opennotes notes add "Note" --data "tag=value" # ✅ Correct
# Check created file (notes stored in notebook root by default)
cat note.md
Search returns no results
# Verify notebook has notes
opennotes notes list
# Check search is case-insensitive
opennotes notes search "KEYWORD"
# Try broader search
opennotes notes search "" # List all notes
# Use SQL for debugging
opennotes notes search --sql "SELECT * FROM read_markdown('.notes/**/*.md')"
Configuration Files
Global Config
~/.config/opennotes/config.json:
{
"notebooks": [
{
"name": "work",
"path": "/home/user/work-notes"
}
],
"views": {
"my-custom-view": {
"description": "Custom view",
"conditions": []
}
}
}
Notebook Config
.opennotes.json in notebook root:
{
"root": ".",
"name": "My Notebook",
"contexts": [
"/path/to/workspace"
],
"groups": [
{
"name": "Default",
"globs": [
"**/*.md"
],
"metadata": {}
}
],
"views": {
"project-active": {
"description": "Active project tasks",
"conditions": [
{"field": "tag", "operator": "=", "value": "project"},
{"field": "status", "operator": "=", "value": "active"}
]
}
}
}
Configuration Fields:
root: Base directory for notes (typically"."for notebook root)name: Notebook display namecontexts: Paths for auto-discoverygroups: Array of note groups with glob patterns for discoveryviews: Custom view definitions (optional)
Environment Variables
# Override config path
export OPENNOTES_CONFIG="~/.config/opennotes/custom.json"
# Enable debug logging
export DEBUG=1
export LOG_LEVEL=debug
# In scripts
OPENNOTES_CONFIG=/tmp/test-config.json opennotes notes list
Integration Examples
With Git
# Initialize notebook with git
mkdir wiki && cd wiki
opennotes notebook create --name "wiki"
git init
echo ".DS_Store" > .gitignore
git add .
git commit -m "Initial commit"
# Commit after note creation
opennotes notes add "New Feature Spec" --data status=draft
git add new-feature-spec.md # Notes in notebook root by default
git commit -m "Add feature spec"
With Obsidian
OpenNotes notebooks are compatible with Obsidian:
- Create OpenNotes notebook:
mkdir obsidian-vault && cd obsidian-vault && opennotes notebook create --name "obsidian-vault" - Open in Obsidian: Point to notebook directory
- Use OpenNotes for CLI operations, Obsidian for GUI editing
- Frontmatter metadata is shared between both tools
- Notes are stored in notebook root by default (same directory as
.opennotes.json)
With Scripts
#!/bin/bash
# daily-note.sh - Create daily note with template
DATE=$(date +%Y-%m-%d)
TITLE="$DATE: Daily Log"
opennotes notes add "$TITLE" \
--data type=daily \
--data date="$DATE" \
--template daily-standup
# Open in editor (notes stored in notebook root by default)
$EDITOR *${DATE}*.md
Performance Characteristics
Scalability
- Small notebooks (< 100 notes): Instant operations
- Medium notebooks (100-1000 notes): ~100ms search latency
- Large notebooks (1000+ notes): ~1s search latency (1147 ops/sec tested)
Memory Usage
- Per note: ~4KB memory footprint at scale
- 1000 notes: ~4MB total memory usage
- Search: Memory-efficient streaming queries
Concurrency
- Thread-safe: Safe for concurrent operations
- Connection pooling: DuckDB handles locking
- File operations: Uses atomic writes where possible
Real-World Impact
Use Cases
- Personal Knowledge Base: 500+ reference notes, full-text search, link analysis
- Project Management: Task tracking with metadata, kanban views, status queries
- Meeting Notes: Daily logs, searchable archive, template-based creation
- Documentation: Technical specs, API docs, cross-referenced with link queries
Advantages Over Alternatives
vs Plain Markdown + grep:
- Structured metadata queries (not just text search)
- Link analysis and relationship tracking
- Reusable views with parameters
- SQL query power
vs Obsidian:
- CLI automation and scripting
- SSH/remote access friendly
- Version control friendly (plain text)
- SQL query interface
vs Notion/Roam:
- Local-first, no cloud dependency
- Plain markdown files (portability)
- Git-friendly for version control
- Open source, extensible
Further Reading
- Note Creation Guide:
docs/note-creation-guide.md(471 lines) - Search Guide:
docs/commands/notes-search.md(407 lines) - Views Guide:
docs/views-guide.md(880 lines) - Views API:
docs/views-api.md(805 lines) - Views Examples:
docs/views-examples.md(812 lines)