Zettel
The zettel subsystem handles Zettelkasten note management — reading, formatting, and syncing notes with external systems like Jira.
Core
Domain entities, use cases, and repository interfaces for Zettelkasten notes.
PrintZettelUseCase
- class buvis.pybase.zettel.application.use_cases.print_zettel_use_case.PrintZettelUseCase(formatter: ZettelFormatter)
Bases:
objectUse case for printing formatted Zettel data.
This class encapsulates the logic for formatting and printing Zettel data using a provided ZettelFormatter.
- execute(zettel_data: ZettelData) str
Execute the use case by formatting the given Zettel data.
Format the provided Zettel data using the configured formatter and return the result.
- Parameters:
zettel_data – The Zettel data to format and print.
- Returns:
The formatted Zettel data.
ReadZettelUseCase
- class buvis.pybase.zettel.application.use_cases.read_zettel_use_case.ReadZettelUseCase(repository: ZettelReader)
Bases:
objectRead zettel from repository by location and downcast it to zettel.
This class is responsible for taking location of a zettel within a ZettelReader, and downcasting it using ZettelFactory service to specific zettel according to zettel type.
- Parameters:
repository – An instance of a class that implements the ZettelReader interface, used to data access in persistence layer.
- execute(repository_location: str) Zettel
Execute the use case of reading from repository by location and downcasting to zettel.
This method takes repository location, attempts to retrieve the corresponding zettel from the repository, downcasts it into a zettel, and returns it. It handles potential exceptions during the retrieval process.
- Parameters:
repository_location – Unique identifier of location within repository containing zettel data.
- Raises:
ZettelRepositoryZettelNotFoundError – If the zettel is not found in the repository.
- Returns:
A Zettel object created from the retrieved zettel.
QueryZettelsUseCase
UpdateZettelUseCase
DeleteZettelUseCase
Templates
Templates define how bim create builds new zettels. Each template is a
Python module in src/lib/buvis/pybase/zettel/domain/templates/. Drop a
module there and discover_templates() finds it automatically — no
registration needed.
Built-in templates: note (minimal, no questions) and project (asks for
dev type, creates project dir).
Writing a template
A template is a class with three attributes/methods and a name class
variable:
# src/lib/buvis/pybase/zettel/domain/templates/meeting.py
from __future__ import annotations
from pathlib import Path
from typing import Any
from buvis.pybase.zettel.domain.templates import Hook, Question
from buvis.pybase.zettel.domain.value_objects.zettel_data import ZettelData
class MeetingTemplate:
name = "meeting"
def questions(self) -> list[Question]:
return [
Question(
key="attendees",
prompt="Attendees (comma-separated)",
),
Question(
key="location",
prompt="Location",
choices=["office", "remote", "hybrid"],
default="remote",
required=True,
),
]
def build_data(self, answers: dict[str, Any]) -> ZettelData:
data = ZettelData()
data.metadata["type"] = "meeting"
data.metadata["title"] = answers.get("title", "")
data.metadata["location"] = answers.get("location", "remote")
data.metadata["attendees"] = answers.get("attendees", "")
tags_raw = answers.get("tags", "")
if tags_raw:
data.metadata["tags"] = [
t.strip() for t in tags_raw.split(",") if t.strip()
]
title = data.metadata["title"]
data.sections = [
(f"# {title}", ""),
("## Agenda", ""),
("## Notes", ""),
("## Action items", ""),
]
return data
def hooks(self) -> list[Hook]:
return []
That’s it. Save the file, and bim create -t meeting --title "Standup"
works.
YAML templates
You can also define templates as YAML files without writing Python. Place
.yaml files in your config directory under templates/:
$BUVIS_CONFIG_DIR/templates/(highest priority)~/.config/buvis/templates/~/.buvis/templates/
A YAML template with the same name as a Python template overrides it.
Higher-priority config dirs override lower-priority ones.
Basic example
# ~/.config/buvis/templates/meeting.yaml
name: meeting
questions:
- key: attendees
prompt: "Who attended?"
required: true
- key: location
prompt: "Location"
default: "online"
choices: ["online", "room-a", "room-b"]
metadata:
type: meeting
attendees: "{attendees}"
location: "{location}"
sections:
- heading: "# {title}"
body: ""
- heading: "## Agenda"
body: ""
- heading: "## Notes"
body: ""
- heading: "## Action items"
body: ""
Usage:
bim create -t meeting --title "Sprint review" -a attendees="Alice, Bob" -a location="room-a"
Field resolution
Three resolution modes for metadata and section values:
String substitution — {key} is replaced with the answer value. Missing
keys become empty strings:
metadata:
attendees: "{attendees}"
note: "Created by {author}" # empty string if author not provided
Python eval — a dict with an eval key runs arbitrary Python. All
answers are available as local variables:
metadata:
tag_count:
eval: "len(tags) if tags else 0"
slug:
eval: "title.lower().replace(' ', '-')"
due:
eval: "datetime.datetime.now() + datetime.timedelta(days=7)"
Passthrough — non-string, non-dict values (int, bool, list) are kept as-is:
metadata:
completed: false
priority: 3
Extending Python templates
YAML templates can extend existing Python (or YAML) templates with extends.
This lets you reuse base logic while customizing fields:
# ~/.config/buvis/templates/standup.yaml
name: standup
extends: note
questions:
- key: sprint
prompt: "Sprint"
metadata:
type: standup
sprint: "{sprint}"
sections:
- heading: "# {title}"
body: ""
- heading: "## Done"
body: ""
- heading: "## Doing"
body: ""
- heading: "## Blocked"
body: ""
Inheritance rules:
Questions: base questions appear first, YAML questions appended after
Metadata: base metadata applied first, YAML keys overwrite or add
Sections: if YAML defines
sections, they replace base sections entirely; otherwise base sections are keptHooks: inherited from base (YAML can’t define hooks)
Single-level extends only (no chaining
standup→note→ something else)
Example extending project to inherit its directory-creation hook:
name: research-project
extends: project
questions:
- key: hypothesis
prompt: "Research hypothesis"
metadata:
type: project
dev-type: spike
hypothesis: "{hypothesis}"
Template protocol
Member |
Purpose |
|---|---|
|
String used in |
|
Returns |
|
Receives a dict with |
|
Returns |
Question fields
Field |
Type |
Description |
|---|---|---|
|
|
Answer key passed to |
|
|
Display text in TUI / help text. |
|
|
Pre-filled value. Used as fallback in scripted mode when |
|
|
If set, renders a select widget in TUI and validates input in scripted mode. |
|
|
If |
Hook signature
def my_hook(data: ZettelData, zettelkasten_path: Path) -> None:
# data.metadata has the final zettel metadata (id, title, etc.)
# zettelkasten_path is the resolved path to the zettelkasten dir
...
Wrap it in a Hook dataclass:
Hook(name="my_hook", fn=my_hook)
CLI usage
# TUI (interactive wizard)
bim create
# TUI with template pre-selected
bim create -t project
# Scripted (non-interactive, requires both -t and --title)
bim create -t note --title "My note" --tags "foo,bar"
# With template-specific answers
bim create -t project --title "Auth refactor" -a dev_type=spike
CreateZettelUseCase
Integrations
Jira Assemblers
- class buvis.pybase.zettel.integrations.jira.assemblers.project_zettel_jira_issue.ProjectZettelJiraIssueDTOAssembler(defaults: dict[str, Any] | None = None)
Bases:
object- to_dto(source: ProjectZettel) JiraIssueDTO