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.
- 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
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
- Node.js >= 22
cd ~/Developer/bwrb
pnpm install
pnpm build
pnpm link --global # Makes 'bwrb' available globallypnpm dev -- new idea # Run without buildingCreate a .bwrb/schema.json in each vault you want to use with bwrb.
# 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 --helpThe schema file is expected at <vault>/.bwrb/schema.json. It defines:
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": { /* ... */ }
}
}
}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 |
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]]")
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 provide reusable defaults and body structure for note creation. They're stored in .bwrb/templates/, organized by type path.
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 template
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 Behavior| 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 |
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})
# 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-reportTemplates 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.
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 quick-
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
The schema structure is defined by schema.schema.json (JSON Schema draft-07). To validate:
./validate_schema.shContributors:
schema.schema.jsonanddocs-site/public/schema.jsonare generated from the Zod source of truth insrc/types/schema.ts— do not edit them by hand. After changing the Zod schema runpnpm schema:genand commit the result. CI (pnpm schema:check, part ofpnpm qa) fails if the committed files are stale.
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 (source of truth)
│ └── tools/
│ └── schema/
│ └── generate-json-schema.ts # Generates the JSON Schema from Zod
├── tests/
│ └── ts/ # TypeScript test suite
├── package.json
├── tsconfig.json
├── vitest.config.ts
├── schema.schema.json # GENERATED JSON Schema (run `pnpm schema:gen`)
└── README.md
Each vault:
my-vault/
└── .bwrb/
└── schema.json # Vault-specific type definitions
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 jsonFind 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")Enable tab completion for commands, types, and paths.
Add to ~/.bashrc:
eval "$(bwrb completion bash)"Add to ~/.zshrc:
eval "$(bwrb completion zsh)"Run once to install:
bwrb completion fish > ~/.config/fish/completions/bwrb.fish- 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.)
pnpm test # Run tests
pnpm test:coverage # Run with coverage report
pnpm typecheck # Type checking
pnpm docs:lint # Verify canonical docs concept linksRun 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.