bwrb
v0.1.8
Published
Schema-driven note management for markdown vaults
Maintainers
Readme
bwrb
Short for bowerbird, pronounced "birb".
Schema-driven note management for markdown vaults.
Pre-release software. bwrb is under active development. The CLI works and is usable, but the schema format and command surface may change before v1.0. See the roadmap for current status.
Documentation policy
- User-facing CLI docs are canonical on the docs site: https://bwrb.dev
- Product rationale/internal notes live in
docs/product/ - Canon policy and routing rules:
docs/product/canonical-docs-policy.md
Overview
bwrb is a CLI tool that creates and edits markdown files based on a hierarchical type schema. It supports:
- Interactive type selection with subtype navigation
- Dynamic frontmatter prompts (select options, text input, vault queries)
- Configurable body sections with various content types
- Edit mode for updating existing files
- List and filter notes by type and frontmatter fields
- Works with any vault via the
--vaultflag
Prerequisites
- Node.js >= 22
Installation
From source (development)
cd ~/Developer/bwrb
pnpm install
pnpm build
pnpm link --global # Makes 'bwrb' available globallyDevelopment mode
pnpm dev -- new idea # Run without buildingSetup
Create a .bwrb/schema.json in each vault you want to use with bwrb.
Usage
# Vault path resolution (in order of precedence):
# 1. --vault=<path> or -v <path> argument
# 2. Find-up: nearest ancestor with .bwrb/schema.json
# 3. BWRB_VAULT environment variable
# 4. Find-down under cwd if not in a vault:
# - 1 candidate => auto-select
# - multiple => numbered picker (TTY) or error requiring --vault
# (non-TTY or --output json)
# Interactive mode - prompts for type selection
bwrb new
bwrb --vault=/path/to/vault new
# Direct creation - specify type
bwrb new objective # Then select subtype (task/milestone/project/goal)
bwrb new idea # Creates idea directly (no subtypes)
# Templates
bwrb new task --template bug-report # Use specific template
bwrb new task --template default # Use default.md template explicitly
bwrb new task --no-template # Skip templates, use schema only
# Edit existing file
bwrb edit path/to/file.md
bwrb edit Objectives/Tasks/My\ Task.md
# List objects by type
bwrb list idea # List all ideas (names only)
bwrb list objective # List all objectives (tasks, milestones, etc.)
bwrb list objective/task # List only tasks
bwrb list objective/milestone # List only milestones
# List output options
bwrb list --output paths idea # Show vault-relative paths
bwrb list --fields=status,priority idea # Show selected frontmatter fields in a table
bwrb list --output paths --fields=status objective # Combine paths + fields
# Open a note by query (or browse all)
bwrb open # Browse all notes with picker
bwrb open "My Note" # Open with system default (default)
bwrb open "My Note" --app editor # Open in $EDITOR
bwrb open "My Note" --app print # Just print the path
bwrb open "Amb" --picker fzf # Use fzf for ambiguous matches
# Generate a wikilink (or browse all)
bwrb search # Browse all notes with picker
bwrb search "My Note" --wikilink # Output: [[My Note]]
bwrb search "My Note" # Output: My Note (name only)
bwrb search "Amb" --output json # JSON output for scripting
# Help
bwrb --help
bwrb list --helpSchema Structure
The schema file is expected at <vault>/.bwrb/schema.json. It defines:
Types
Hierarchical type definitions. Types can have subtypes for nested categorization:
{
"types": {
"objective": {
"subtypes": {
"task": { /* type definition */ },
"milestone": { /* type definition */ }
}
},
"idea": {
"output_dir": "Objectives/Ideas",
"frontmatter": { /* ... */ }
}
}
}Type Definition
Each leaf type requires:
| Field | Required | Description |
|-------|----------|-------------|
| output_dir | Yes | Directory relative to vault root |
| frontmatter | Yes | Field definitions |
| frontmatter_order | No | Array specifying field order |
| body_sections | No | Array of section definitions |
Frontmatter Fields
Fields can be static or prompted:
Static value:
{
"type": { "value": "objective" },
"creation-date": { "value": "$NOW" }
}Special values: $NOW (local datetime, YYYY-MM-DD HH:mm), $TODAY (local date, YYYY-MM-DD)
Select from options:
{
"status": {
"prompt": "select",
"options": ["raw", "backlog", "planned", "in-flight", "settled"],
"default": "raw"
}
}Text input:
{
"deadline": {
"prompt": "text",
"label": "Deadline (YYYY-MM-DD)",
"required": false
}
}Dynamic (vault query):
Query notes of a specific type to populate field options:
{
"milestone": {
"prompt": "relation",
"source": "objective/milestone",
"filter": "status != 'settled' && status != 'ghosted'",
"format": "quoted-wikilink"
}
}source- Type path to query (e.g.,"objective/milestone")filter- Optional expression to filter resultsformat- Output format:plain,wikilink([[value]]),quoted-wikilink("[[value]]")
Body Sections
Define document structure after frontmatter:
{
"body_sections": [
{
"title": "Steps",
"level": 2,
"content_type": "checkboxes",
"prompt": "list",
"prompt_label": "Steps (comma-separated)"
},
{
"title": "Notes",
"level": 2,
"content_type": "paragraphs",
"children": [
{ "title": "Subsection", "level": 3, "content_type": "bullets" }
]
}
]
}Content types: none, paragraphs, bullets, checkboxes
Templates
Templates provide reusable defaults and body structure for note creation. They're stored in .bwrb/templates/, organized by type path.
Template Location
my-vault/
└── .bwrb/
├── schema.json
└── templates/
├── idea/
│ └── default.md # Default template for ideas
└── objective/
└── task/
├── default.md # Default template for tasks
└── bug-report.md # Bug report templateTemplate Format
Templates are markdown files with special frontmatter:
---
type: template
template-for: objective/task
description: Bug report with reproduction steps
defaults:
status: backlog
priority: high
prompt-fields:
- deadline
---
## Description
[Describe the bug]
## Steps to Reproduce
1.
2.
3.
## Expected Behavior
## Actual BehaviorTemplate Properties
| Property | Required | Description |
|----------|----------|-------------|
| type | Yes | Must be template |
| template-for | Yes | Type path (e.g., objective/task) |
| description | No | Human-readable description |
| defaults | No | Default field values (skip prompting) |
| prompt-fields | No | Fields to always prompt for, even with defaults |
| filename-pattern | No | Override default filename |
Template Body
The template body becomes the note body, with variable substitution:
{fieldName}- Replaced with frontmatter value{date}- Today's date (YYYY-MM-DD){date:FORMAT}- Formatted date (e.g.,{date:YYYY-MM})
CLI Usage
# Auto-use default.md if it exists
bwrb new task
# Use specific template
bwrb new task --template bug-report
# Require default template (error if not found)
bwrb new task --template default
# Skip template system
bwrb new task --no-template
# JSON mode with templates
bwrb new task --json '{"name": "Fix bug"}' --template bug-reportTemplate Discovery
Templates use strict matching - only templates in the exact type path directory are considered:
objective/task->.bwrb/templates/objective/task/*.mdidea->.bwrb/templates/idea/*.md
There is no inheritance from parent types.
Template Management
Use the template command to manage templates:
# List all templates
bwrb template list
bwrb template list objective/task # Filter by type
bwrb template list idea default # Show specific template details
# Validate templates against schema
bwrb template validate # All templates
bwrb template validate idea # Templates for specific type
# Create new template interactively
bwrb template new idea
bwrb template new objective/task --name bug-report
# Create template from JSON
bwrb template new idea --name quick --json '{"defaults": {"status": "raw"}}'
# Edit template interactively
bwrb template edit idea default
# Edit template from JSON
bwrb template edit idea default --json '{"defaults": {"priority": "high"}}'
# Delete a template
bwrb template delete idea quickAdding a New Type
Add type definition under
types:{ "types": { "my-type": { "output_dir": "My/Output/Dir", "fields": { "type": { "value": "my-type" }, "status": { "prompt": "select", "options": ["raw", "active", "done"] } }, "field_order": ["type", "status"], "body_sections": [] } } }Validate schema (optional):
./validate_schema.sh
Schema Validation
The schema structure is defined by schema.schema.json (JSON Schema draft-07). To validate:
./validate_schema.shFile Structure
bwrb repo:
bwrb/
├── src/
│ ├── index.ts # CLI entry point
│ ├── commands/
│ │ ├── new.ts # Create new notes
│ │ ├── edit.ts # Edit existing notes
│ │ └── list.ts # List and filter notes
│ ├── lib/
│ │ ├── schema.ts # Schema loading & validation
│ │ ├── frontmatter.ts # Frontmatter parsing & writing
│ │ ├── query.ts # Filter parsing & evaluation
│ │ ├── vault.ts # Vault operations
│ │ └── prompt.ts # Interactive prompts
│ └── types/
│ └── schema.ts # Zod schema definitions
├── tests/
│ └── ts/ # TypeScript test suite
├── package.json
├── tsconfig.json
├── vitest.config.ts
├── schema.schema.json # JSON Schema for validating vault schemas
└── README.mdEach vault:
my-vault/
└── .bwrb/
└── schema.json # Vault-specific type definitionsNavigation Commands
bwrb open [query]
Open a note by name or path query. If no query is provided, shows a picker to browse all notes.
bwrb open # Browse all notes with picker
bwrb open "My Note" # Open with system default (default)
bwrb open "my note" # Case-insensitive
bwrb open "Ideas/My Note" # By path
bwrb open "My Note" --app editor # Open in $VISUAL or $EDITOR
bwrb open "My Note" --app system # Open with system default
bwrb open "My Note" --app print # Just print the resolved pathApp modes:
obsidian- Open in Obsidian via URI schemeeditor- Open in$VISUALor$EDITORsystem- Open with system default handler (default)print- Just print the resolved path
Environment variable: Set BWRB_DEFAULT_APP to change the default app mode:
export BWRB_DEFAULT_APP=editor # Always open in $EDITOR by defaultPicker modes (when query matches multiple files or no query):
--picker auto- Use fzf if available, else numbered select (default)--picker fzf- Force fzf--picker numbered- Force numbered select--picker none- Error on ambiguity (for scripting)
JSON output (implies --picker none):
bwrb open "My Note" --app print --output jsonbwrb search [query]
Find notes and generate wikilinks. If no query is provided, shows a picker to browse all notes. Uses shortest unambiguous form:
- Basename if unique across vault:
[[My Note]] - Full path if ambiguous:
[[Ideas/My Note]]
bwrb search # Browse all notes with picker
bwrb search "My Note" --wikilink # Output: [[My Note]]
bwrb search "My Note" # Output: My Note
bwrb search "Amb" --picker none --output json # Scripting modeNeovim/scripting example:
# Copy wikilink to clipboard (macOS)
bwrb search "My Note" --wikilink | pbcopy
# Use in a Lua script
local link = vim.fn.system("bwrb search 'My Note' --picker none")Shell Completion
Enable tab completion for commands, types, and paths.
Bash
Add to ~/.bashrc:
eval "$(bwrb completion bash)"Zsh
Add to ~/.zshrc:
eval "$(bwrb completion zsh)"Fish
Run once to install:
bwrb completion fish > ~/.config/fish/completions/bwrb.fishWhat Gets Completed
- Commands:
bwrb <TAB>showsnew,edit,list,open, etc. - Options:
bwrb list -<TAB>shows--type,--path,--where, etc. - Types:
bwrb list --type <TAB>shows types from your schema (task, idea, etc.) - Paths:
bwrb list --path <TAB>shows vault directories (Ideas/, Objectives/, etc.)
Running Tests
pnpm test # Run tests
pnpm test:coverage # Run with coverage report
pnpm typecheck # Type checking
pnpm docs:lint # Verify canonical docs concept linksDocs Site
Run docs-site workflows from the repository root:
pnpm docs:install # Install docs-site dependencies
pnpm docs:dev # Start docs dev server
pnpm docs:build # Build docs site
pnpm docs:preview # Preview built docs siteFor docs-site details and deployment notes, see docs-site/README.md.
If you hit local docs contributor setup quirks (ignored build scripts or transient TS diagnostics), see docs-site/README.md#contributor-troubleshooting.
