Cuaderno
Cuaderno (cdno) is a command-line vault manager for the Research Logbook Method (RLM) — a
way of running long-horizon knowledge work out of a folder of plain Markdown files. It is built for
researchers and other deep-work practitioners, and is deliberately friendly to ADHD working styles:
it leads with what is there, not what is missing — no guilt counters, no angry overdue badges,
explicit permission to park or drop work.
You drive it from a terminal, and an AI assistant (Claude and other MCP clients) can drive it too, through the bundled MCP server.
What it gives you
- A dated, append-only log as the single source of truth for what you did.
- Projects with a current state, next actions, milestones, and waiting-on items — capped at five active at a time so you can’t overcommit.
- Evidence portfolios: per-question dossiers that accumulate papers, experiment results, and notes over months and years.
- Important questions kept front-and-centre and re-read often.
- Stewardships: small, bounded, long-haul responsibilities (health, finances) with optional habit tracking.
- A commitments register — promises with dates, aggregated from everywhere they live.
- Full-text search, frontmatter linting, and a JSON mode so every read and write verb is scriptable.
Two ways to use it
| Surface | What it is | Start here |
|---|---|---|
cdno CLI | The terminal tool for the daily loop | Quickstart |
cdno-mcp server | An MCP server so Claude can read and write your vault | Connect to Claude |
Both operate on the same Markdown vault — the files on disk are always the source of truth (the SQLite index is just a rebuildable cache).
How this guide is organised
- Getting started — install, create a vault, run the daily loop, and wire up Claude.
- Concepts — the mental model: the method, the twelve note types, the vault layout, and the rules the tool enforces.
- Tutorials — task-oriented walkthroughs of each workflow.
- CLI reference — every command, flag, and example.
- MCP server reference — every tool exposed to AI clients.
- Appendix — JSON shapes, frontmatter fields, the full config file, recurrence syntax, and troubleshooting.
This guide documents the shipped behaviour of Cuaderno. New to it? Read The Research Logbook Method for the why, then jump to the Quickstart for the how.
Installation
Cuaderno ships two binaries:
cdno— the command-line tool for the daily loop.cdno-mcp— the MCP server that lets Claude (and other MCP clients) read and write your vault.
Both are installed together.
Homebrew (macOS and Linux)
The recommended path. Pre-built bottles exist for macOS (Apple Silicon + Intel) and Linux (x86_64 + aarch64).
brew install agustinvalencia/tap/cuaderno
Verify:
cdno --version # -> cdno 0.1.24 (or newer)
cdno-mcp --version
To upgrade later:
brew upgrade cuaderno
From source
Use this on platforms without a bottle, or to track the latest main. You need a
Rust toolchain.
git clone https://github.com/agustinvalencia/cuaderno
cd cuaderno
cargo build --release --bins
# Binaries land at target/release/cdno and target/release/cdno-mcp
Put them on your PATH — for example:
ln -s "$PWD/target/release/cdno" /usr/local/bin/cdno
ln -s "$PWD/target/release/cdno-mcp" /usr/local/bin/cdno-mcp
Shell completions (optional)
cdno can print a completion script for your shell, with vault-aware suggestions (it completes
project, portfolio, and stewardship slugs by re-invoking the binary on TAB):
# zsh — add to your ~/.zshrc:
source <(cdno completions zsh)
Supported shells: bash, zsh, fish, elvish, powershell. See
completions for per-shell setup.
Uninstall
brew uninstall cuaderno # if installed via Homebrew
# or, for a source build, remove the symlinks you created:
rm -f /usr/local/bin/cdno /usr/local/bin/cdno-mcp
Uninstalling the binaries never touches your vault — it’s just Markdown files on disk. Delete the vault directory yourself if you want it gone.
Next step
Create your vault: Initialise a vault.
Initialise a vault
A vault is just a directory of Markdown files with a .cuaderno/ config folder at its root.
Create one with cdno init:
cdno init ~/notebook
cd ~/notebook
This scaffolds the full folder tree, writes a default .cuaderno/config.toml, and drops the
built-in note templates into .cuaderno/templates/ (so you can customise them later). What you get:
~/notebook/
├── journal/ # daily + weekly notes, partitioned by year
│ └── 2026/ # e.g. journal/2026/daily/2026-04-25.md, journal/2026/weekly/2026-W17.md
├── projects/ # project maps (max 5 active)
│ └── _parked/ # inactive projects
├── actions/ # manifest action notes (the heavy form)
│ └── _done/ # completed actions, partitioned by year
├── portfolios/ # evidence dossiers, one folder per question
├── stewardships/ # long-haul responsibilities (flat file or folder)
├── commitments/ # standalone promises with deadlines
│ └── _done/ # fulfilled commitments
├── questions/
│ ├── research/
│ └── life/
├── inbox/ # quick captures awaiting triage
└── .cuaderno/
├── config.toml # vault configuration
└── templates/ # note templates (override the built-ins here)
See Vault structure for what each folder holds.
Running cdno from anywhere
You rarely need to be at the vault root. cdno finds the vault by walking up from your current
directory until it sees a .cuaderno/ folder — so commands work from any subdirectory.
When you’re outside any vault, two fallbacks apply, in order:
- The
--vault <PATH>flag (highest priority — overrides everything). - The
CUADERNO_VAULT_PATHenvironment variable.
# From anywhere, target a specific vault:
cdno --vault ~/notebook log "spotted a bug in the sampler"
# Or set it once for the shell session:
export CUADERNO_VAULT_PATH=~/notebook
cdno log "spotted a bug in the sampler"
If you’re standing inside vault A while
CUADERNO_VAULT_PATHpoints at vault B, the directory you are in wins — writes land in A. The env var is only a fallback for when discovery finds nothing.
Back up your vault
A vault is just Markdown files (the SQLite index in .cuaderno/index.db is a rebuildable cache — see
Business rules). The simplest, most durable backup is version
control: git init the vault and commit as you go, or keep it in a synced folder.
cd ~/notebook
git init && git add . && git commit -m "Initial vault"
# The index is regenerated on demand, so it's safe to ignore:
echo ".cuaderno/index.db" >> .gitignore
Because the Markdown is the source of truth, your history is just your commits — nothing is locked inside a proprietary store.
Next step
Run your first daily loop: Quickstart.
Quickstart: the daily loop
This is Cuaderno in five commands. It assumes you’ve installed cdno and
created a vault. Run these from inside the vault (or with --vault).
# 1. Start a project (capped at 5 active).
cdno project create --title "Surrogate model" --context work
# 2. Give it a next action, tagged by the energy it needs.
cdno action add --project surrogate-model \
--title "Run feature set B on the full mesh" \
--energy deep
# 3. Record a promise with a deadline.
cdno commit create --title "Pay rent" --due 2026-06-01 --context personal
# 4. Each morning: see what's due, your active projects, and a suggested start.
cdno orient --energy deep
# 5. Mark the action done when it's finished (substring match on the bullet).
cdno action complete --project surrogate-model --query "feature set B"
Throughout the day, the two verbs you’ll reach for most:
cdno log "scaled the mesh to 2M cells; runtime 4x, still stable" # append to today's log
cdno capture "check whether Chen 2025 used the same preconditioner" # drop a thought into the inbox
Interactive vs. scripted
Every write command follows the same convention (see CLI overview):
- In a terminal, omit a required flag and
cdnoprompts you for it, then asks you to confirm before writing. - In a script, pipe, or with
--no-interactive, a missing required flag is an error instead — so automation never blocks on a prompt.
# Interactive: cdno asks for the title and context.
cdno project create
# Scripted: everything supplied, no prompts.
cdno project create --title "Surrogate model" --context work --no-interactive
Machine-readable output
Add --json to any read or write verb to get structured output instead of a formatted table.
Read verbs emit their listing/detail; write verbs emit a { "path": ..., "message": ... } result and
run non-interactively. Great for scripts and for piping into jq.
cdno project list --json | jq '.[].slug'
cdno project create --title "Surrogate model" --context work --json
# -> { "path": "projects/surrogate-model.md", "message": "Created projects/surrogate-model.md" }
See JSON output for the full shapes.
Where to go next
- Understand the model: The Research Logbook Method.
- Work through each workflow: Tutorials.
- Let Claude drive: Connect to Claude.
Connect to Claude (MCP)
Cuaderno ships an MCP server, cdno-mcp, that exposes your vault
to AI clients — Claude Desktop, Claude Code, Kiro, Gemini CLI, and anything else that speaks MCP.
The assistant can then read context (your orientation, a project, a portfolio) and make writes
(append to the log, file evidence, complete an action) on your behalf.
It’s the same engine as the CLI — the files on disk stay the source of truth.
Register the server
Add cdno-mcp to your client’s MCP configuration. For Claude Desktop, edit
~/.claude/claude_desktop_config.json (Claude Code: ~/.claude.json or per-project .mcp.json):
{
"mcpServers": {
"cuaderno": {
"command": "cdno-mcp",
"env": { "CUADERNO_VAULT_PATH": "/Users/you/notebook" }
}
}
}
commandmust resolve on the client’sPATH. If it doesn’t, use the absolute path (e.g./opt/homebrew/bin/cdno-mcp, ortarget/release/cdno-mcpfor a source build).CUADERNO_VAULT_PATHtells the server which vault to open. You can omit it; the server then opens whichever vault its working directory belongs to (the same discovery rule as the CLI).
Restart the client. Cuaderno’s tools then appear in the client’s tool list.
What the assistant can do
The server advertises 55 tools, grouped by purpose:
- Context-gathering reads —
get_orientation,get_project_context,get_portfolio_contents,get_weekly_context,search_notes,read_daily_note, and more. - Writes —
append_to_log,file_to_portfolio,update_project_state,add_action,complete_action,create_commitment,create_tracking_entry, the daily/weekly section writers, and others. - Creation and lifecycle —
create_project,create_portfolio,create_question,create_stewardship,park_project,activate_project,set_question_status, and so on.
The full catalogue, with each tool’s inputs and output shape, is in the MCP server reference.
Skills
You can wrap common multi-step flows as Claude skills that call these tools — e.g. a daily
orientation that reads get_orientation and writes your intention with upsert_daily_section. The
repo ships worked examples under
examples/skills/; see
Using with Claude skills.
Next step
Learn the model behind it all: The Research Logbook Method.
Desktop app
Cuaderno ships a macOS desktop app — a lens over the vault, not an editor. It answers “where am I, what’s next, what did I promise?” without a terminal, lets you tick, log, and capture, and hands deep editing off to your editor. The markdown files stay the source of truth; the app is a view that refreshes live when anything else (nvim, the CLI, Claude via MCP) touches the vault.
Apple Silicon only for now (an Intel build can join when there is a taker).
Install
Homebrew cask (recommended)
brew install --cask agustinvalencia/tap/cuaderno-app
xattr -dr com.apple.quarantine /Applications/cuaderno.app
The xattr step matters: the app is ad-hoc signed, not notarized, so Gatekeeper (recent
Homebrew removed the --no-quarantine install flag)
blocks the first launch.
Manual .dmg
Download cuaderno-app-<version>-aarch64-apple-darwin.dmg from the
releases page, copy the app to
/Applications, then strip the quarantine flag (right-click → Open no longer works on macOS 15+):
xattr -dr com.apple.quarantine /Applications/cuaderno.app
(Or use System Settings → Privacy & Security → “Open Anyway” after the first blocked attempt.)
Before first launch: two notes
-
Gatekeeper — covered above: strip the quarantine attribute from a manual install. There is no notarization and no auto-updater yet; upgrades go through
brew upgrade --cask cuaderno-appor a fresh.dmg. -
Vault discovery — on first launch the app asks for your vault folder with a native picker, validates that it is a cuaderno vault (a
.cuaderno/marker — pick the vault root, or runcdno initfirst if you have not created one), and remembers it for next time. If the folder you pick isn’t a vault it re-asks; if you cancel, it explains that it needs a vault and exits.The
CUADERNO_VAULT_PATHenvironment variable remains an override for terminals and dev, same as the CLI and the MCP server. A Finder-launched app inherits no shell environment, so if you want to pin the vault without the picker you can still set it for GUI apps once per login:launchctl setenv CUADERNO_VAULT_PATH "$HOME/Documents/notebook"or for a one-off run from a terminal:
CUADERNO_VAULT_PATH=~/Documents/notebook open -a cuadernoWhen set, the variable wins over the remembered folder (and an invalid override fails loudly, rather than falling back to the picker).
A quick tour
The sidebar is grouped the way the method is: a rhythm, and the two tracks it binds.
Rhythm is the cadence. Today is the day’s own note, with the morning orientation above it: a
Now band naming whatever you started and have not finished (read back from the day’s log, so a
cdno start from the terminal counts too), a one-line log composer, commitments due soon, and an
energy-filtered shortlist of one action per project to pick from. Under the shortlist, Starting
something that isn’t listed? opens a one-line form for work that is on no map yet: it adds the
action to the project you pick and starts it in the same gesture, so it can be ticked off later like
any other. Any quietly lapsed habits sit at the foot. Calendar is a month grid of your journal — days with a daily note are marked, today carries a
ring wherever you have paged to, and clicking a day opens it in a panel beside the grid that reads
read-only and jumps to the previous or next day, the day’s week, or its month. On a narrower window
the grid collapses behind a The month toggle instead of sitting alongside. Weekly is a guided, stop-anywhere five-step review with a labelled stepper and Back/Next; its
wins step hands you the week’s completions and log lines as cards you tick and reshuffle rather than a
blank box to compose in. Monthly is the review at the highest altitude — “am I still pointed at the right questions?” —
stepped like the weekly: questions, portfolio health, the five-slot project allocator, stewardship
trends, the six-week lookahead, and a focus step that writes wins, themes and next month’s focus to
the monthly note. It is the one review that leaves an artefact, which is what gives the monthly
cadence a reason to come back.
Operations is delivery. Projects heads the group and says how many of your slots are taken
(“3 of 5”), with each active project listed beneath it; every one has a full map
(/projects/<slug>) leading with its current state, then the next actions, blockers and milestones,
with backlinks and recent log mentions kept to a few and the rest a click away. Actions is the
cross-project list of next actions: a project rail with counts down the left, and a filter bar of
context and energy chips plus a text filter across the top. Every chip carries its own count, and
one of the energy chips is untagged — most bullets carry no (deep|medium|light) suffix, and
they are work like any other. Filtering says how much it is hiding rather than just showing less. Commitments is everything promised — what someone else is counting on, as
against what you merely decided to do (context-coloured, never red). It reads as a timeline banded
into This week / Next week / by month, or as a month grid with a dot per promise; the
horizon is yours to set, from a fortnight up to everything. Stewardships are the perpetual responsibilities. They never
complete, so the list shows status rather than progress: quietest first, with a count of how many
have gone quiet and a filter down to just those, and freshness read as ink emphasis rather than a
colour. Logging is one click from a row. A stewardship’s detail leads with Log entry in its
header and draws each tracked series as a calm trend, always in the context hue and never as a
target to hit. A metric that declares a plot in
[tracking.<activity>] is drawn the way it says; anything
undeclared falls back to reading the values — counts and volumes (reps, laps, sessions) as columns,
continuous measures (a weight, a pace) as lines.
What you see by default is deliberately bounded. One trend per activity is always on: the activity’s own number when it has one, rather than an arbitrary slice of a grouped metric. Every further series for that activity is denser material — a line per category, a grid of averaged ratings — and appears only when the metrics toggle is on. Nothing is hidden permanently; the dense view is opt-in rather than the default, and beyond six charts the rest sit behind an explicit show all that says how many it is holding back. That is a cap rather than a second filter: a grouped metric grows a chart per new category on its own, so without one the page would expand quietly as your data did. The activity chips narrow further and reset when you navigate away — looking is meant to be temporary, unlike a declaration.
The chart type is changeable in place. Picking one previews immediately and writes nothing; an
explicit Save chart type as default persists it into .cuaderno/config.toml, through the same
validated, conflict-checked save every other config edit uses (see
Editing the config in the app). It is worth knowing that a control on a chart
writes to your vault’s config — that is the point, since the declaration is what an agent reads
too, but it is a durable change rather than a view setting. Choosing None stops the chart being
drawn while the data stays collected and queryable; turning it back on is done from the config
editor, since there will be no chart left here to pick from.
Inquiry is investigation. Questions is the important-questions list that sits above any one project, grouped into research and life, each showing what is pointed at it and each movable between active, parked, answered and retired in place. Portfolios are the evidence dossiers those questions accumulate.
Everywhere: ⌘K opens search-and-jump, ⌘⇧C summons the global capture window from any app (Enter
files to the inbox, ⌘Enter appends to today’s log), and the menu-bar tray keeps Quick capture /
Open / Quit reachable even with every window closed. ⌘[ and ⌘] (or the mouse’s back and forward
side buttons) step backward and forward through your view history, just like a browser.
⌘, opens Settings, which holds everything that configures the app or the vault rather than
living in it: Appearance and Reading, a metrics toggle under General (which governs the denser half of a stewardship’s trend charts, described above), custom CSS under Advanced,
and two full editors — Vault config, which edits .cuaderno/config.toml in a Raw text view and
a structured Form for note types and schemas (every save validated before it touches disk, with a
live reload whenever the config changes underneath), and Templates, the per-note-type template
browser and editor. Both hold real drafts, so Settings will not close over unsaved changes without
asking. See Editing the config in the app for the full walkthrough.
Editing the config in the app
The desktop app’s Vault config editor edits .cuaderno/config.toml without
leaving the app. It reads the file, edits it two ways, validates every change before it touches disk, and
reloads the live vault the moment the config changes — whether the change came from the app or from
your editor.
This page covers using the editor. For what each key means, see Configuration and the Configuration reference.
Open it with ⌘, and pick Vault config from the settings rail. Configuration is not content,
so it lives in Settings rather than in the sidebar beside your notes.
Raw and Form
The editor opens on a Raw / Form toggle:
- Raw is the whole
config.tomlin a text editor. Everything is editable here, byte for byte — it is the escape hatch for anything the Form doesn’t cover. - Form is a structured view of the parts that have a fixed shape: note types, their
schema extensions, and the template variables block. You add, rename, and remove custom
note types and required frontmatter fields with inputs and toggles instead of hand-writing TOML.
Each schema field’s row also exposes its setter behaviour: Settable (whether
set_frontmattermay change the field) and Log changes to daily (auto-log each change to the daily note — available once the field is settable). The Template variables section edits the two variable maps: Static (values available in every template) and Prompted (asked for interactively when a template uses them).
Switch freely between them; both edit the same file. What the Form can’t represent stays in Raw (see What the Form doesn’t edit below).
The never-brick save
A vault whose config.toml fails to parse or validate won’t open — so the editor is built so you
cannot save it into that state from the app.
Every save, from either view, runs the exact validation the app runs when it opens a vault: the TOML is parsed, the ignore globs are compiled, and the type registry is validated. Only if all three pass does the save proceed. A failure is reported inline — with the line and column for a syntax slip — and nothing is written. The file on disk is never the broken version.
The Raw view also has a Check button that dry-runs the same validation without saving, so you can confirm a hand-edit before you commit to it.
Edits are surgical
Saving from the Form does not rewrite the whole file. It applies a targeted edit to just the table you changed, so your comments, key order, and formatting elsewhere survive untouched. Adding a required field rewrites only that one table; renaming a note type removes the old table and writes the new one — nothing else is touched.
Conflict detection
If the file changed on disk between the app reading it and your save — say you edited it in your editor in the meantime — the save is refused rather than silently overwriting the other change. The app tells you the file moved under it; reload to pick up the on-disk version, then reapply your edit.
Live reload
The app watches .cuaderno/config.toml. When it changes on disk — you edited it in nvim, ran a CLI
command, or another tool touched it — the app rebuilds the vault against the new config and
refreshes every view, no restart needed. The status line confirms the reload.
If an external edit leaves the config invalid, the app keeps the last good vault live and shows the validation error instead of the reload, so a bad hand-edit never takes the app down with it — fix the file and the next save reloads cleanly.
What the Form doesn’t edit
The Form covers note types, schema extensions, and template variables. A few things stay Raw-only by design:
max_active_projectsunder[vault], and the top-levelignoreglobs — edit these in Raw.
Everything Raw-only is still covered by the never-brick save and live reload; only the structured inputs are absent.
This is not the only thing that writes the config
A stewardship’s trend charts carry a chart-type picker that persists into
[tracking.<activity>.metrics.<name>], so a control that looks like a view setting makes a durable
change to .cuaderno/config.toml. It stages locally and writes nothing until you use its explicit
save, and that save runs through everything on this page — validation, the compare-and-swap against
what is on disk, and the live reload afterwards. See
the desktop app tour for what the picker does.
The reason it goes through the same gate rather than writing directly is the one this page is about: there is a single way config reaches disk, so an edit made from a chart cannot skip a check an edit made here would have to pass.
Next: the concepts behind these keys in Configuration, or the hands-on Customising templates and frontmatter tutorial.
The Research Logbook Method
The Research Logbook Method (RLM) is the practice Cuaderno implements. It distils habits common to prolific researchers — Faraday’s notebooks, Darwin’s dated entries, Hamming’s “important problems,” Knuth’s and Tao’s working logs — into seven concrete practices across two tracks. Each maps onto one or more note types the tool manages.
Two tracks
Research is two jobs at once. Inquiry is open-ended, driven by questions and evidence. Operations is delivery — deadlines, collaborators, promises. Most systems handle one and force the other into its shape. The RLM keeps them separate and lets them share a substrate: the daily log, the weekly review, and one set of projects that bridges the two.
Track 1 — Inquiry: how you investigate
- A chronological log (Faraday). A single append-only stream of what you did, observed and thought, in the order it happened. Past entries are never edited; a change of mind is a new entry referencing the old one. It removes the “where do I put this?” decision, preserves the reasoning and not just the result, and keeps you honest against hindsight. Unified across contexts — the regularisation weight and the plumber go in the same day.
- Evidence portfolios (Darwin). One folder per active question, accumulating whatever is relevant. The log interleaves many questions; the portfolio aggregates one. Name the folder practically, but phrase the question at the top of its index: “sparse models” names a topic, “does the sparse variant outperform the dense baseline out of distribution?” tells you when you are done. Don’t organise inside — accumulate, and file as things arise, while your reaction is still the most valuable part.
- Important questions (Hamming). The few questions that would genuinely change your situation if answered. Not tasks — questions. They sit above projects: one question may spawn several over time and persists as they finish or get shelved. Two short lists, research and life, reviewed monthly: are these still the right questions?
Track 2 — Operations: how you deliver
A question and a project are not the same thing. A question is open-ended and may branch or turn out to be the wrong question. A project is bounded — a deliverable, a deadline, people counting on you. One question can span several projects; a project can draw on several portfolios; either can exist without the other. Keeping them apart stops deadline pressure polluting open-ended inquiry, and stops open-ended inquiry dissolving operational commitments.
- Project maps (Knuth). A mutable one-pager per piece of finite work — the page you open after a two-week gap. It answers: where was I, what do I believe and what would change my mind, what are my next actions, what is time-sensitive, where is everything. Tasks live here, embedded in the project that gives them meaning. Maximum five active projects, at most three next actions each — fifteen tasks as a ceiling, not a backlog. A project map is not a history (the log is), not a long checkbox list (eighteen unchecked boxes reads as failure), and not a timeline.
- Stewardships. Dashboards for perpetual responsibilities — health, finances, household — that never finish. The critical distinction: projects end, stewardships do not. If health and finances occupy project slots you permanently lose two of five to things that cannot complete. They run in the background with periodic commitments and get a scan at the weekly review, without competing for slots.
- A commitments register. One flat, date-sorted list of everything with a hard external deadline, whichever project or stewardship it came from. This is not a to-do list, and the distinction matters: a to-do is something you decided to do, a commitment is something someone else is counting on. Mixed together, the commitments drown and you break promises you meant to keep.
- Energy-matched scheduling (Tao, Wolfram). Tag work deep, medium or light and match it to your current cognitive state. Forcing deep work when your brain wants something mechanical is not discipline, it is waste — the alternative to deep work on a low day is usually paralysis, not virtue. The exception: if you never have deep days, that is environmental, and the fix is protecting blocks rather than trying harder.
The bridge
The log feeds both tracks: an observation becomes evidence in a portfolio, a realisation that something must be rerun before a deadline becomes a next action on a project map. The weekly review governs both — retrospective and forward scan. Projects reference the portfolios they draw on, and commitments decompose into milestones that surface as next actions at the right time.
The daily orientation is where the tracks meet: check what is urgent, glance at what is important, check your energy, and pick one thing.
Actions, not tasks
The RLM speaks of actions, not “tasks,” on purpose. The default form of an action is a single inline bullet on a project map — not a heavyweight per-item note. You only “promote” an action to its own note when it grows into an investigation spanning multiple days and evidence artefacts. This keeps the friction of capturing the next step near zero. See Actions.
Designed for ADHD working styles
The method is deliberately shaped to be sustainable when executive function is unreliable:
-
Leads with what is there, not what is missing — no guilt engine, no red overdue counts.
-
Permission to park or drop. Projects park, questions retire, actions and milestones drop, commitments get fulfilled or dropped — all first-class, and none of it is a failure state.
“Reversible” is worth stating precisely, because it means less than it might sound like. Parking is genuinely two-way: a parked project activates again, and a retired question does too. Dropping is not — there is no un-drop verb for an action, a milestone or a commitment, and none is planned. It is re-decided rather than undone: if a dropped thing turns out to matter, make it again, and the record honestly shows two decisions rather than pretending the first never happened. That is the same reason a drop is logged as a drop and not as a completion.
What a drop does preserve is the note. A dropped action or commitment keeps its file, its body and its
createddate, archived under_done/<year>/— so the context you wrote survives even though the plan did not. A milestone is only a bullet, with no note behind it, so dropping one leaves just the log entry naming it — including when it had sub-bullets, which is the one place this guarantee does not yet hold (#574). -
Minimal maintenance. If keeping the system running costs more than a few minutes a day outside the weekly review, something is wrong.
-
One obvious next step.
cdno orientanswers “what should I do now?” with a single suggestion, biased to your current energy.
The rhythm
The method runs on a few interlocking loops:
- Daily — orient in the morning, log and act through the day, a light close at the end. See The daily loop.
- Weekly — a short retrospective (wins, challenges, one improvement) and a single goal for next week. See Weekly review.
- Occasional — file evidence as you find it, triage the inbox, prune questions and projects.
How it maps to the tool
| Practice | Note type(s) | Primary commands |
|---|---|---|
| Chronological log | daily, weekly | log, orient, review |
| Evidence portfolios | portfolio, evidence | portfolio, file |
| Important questions | question | question, questions |
| Project maps | project, action | project, action |
| Stewardships | stewardship, tracking | stewardship, track |
| Commitments register | commitment (+ computed) | commit, commitments |
| Energy-matched scheduling | (an energy on actions) | orient --energy, action add --energy |
Read on: Note types.
Note types
Every note in a vault has a type: in its frontmatter. Cuaderno parses that frontmatter into a
typed structure — if it parses, it’s valid. There are twelve note types.
| Type | Lives in | Mutability | Purpose |
|---|---|---|---|
daily | journal/<year>/daily/ | Append-only | One day’s chronological log |
weekly | journal/<year>/weekly/ | Append-only | Weekly review (Wins, Challenges, One Improvement, This Week’s Goal) |
monthly | journal/<year>/monthly/ | Append-only | Monthly review (Wins, Themes, Next Month’s Focus) + links to the month’s weeks |
project | projects/ (+ _parked/) | Mutable | Project map: state, next actions, milestones, waiting-on |
action | actions/ → actions/_done/<year>/ | Mutable while open, then archived | Manifest note for an action-as-investigation |
portfolio | portfolios/<slug>/_index.md | Occasionally edited | Index/summary of an evidence dossier |
evidence | portfolios/<slug>/ | Append-only | A single piece of evidence (paper, result, note) |
stewardship | stewardships/ (flat or folder) | Occasionally edited | Dashboard for a perpetual responsibility |
tracking | stewardships/<slug>/tracking/ | Append-only | One time-series entry (a gym session, a measurement) |
question | questions/research/ or questions/life/ | Status transitions | An important research or life question |
commitment | commitments/ → commitments/_done/ | Moves when it ends, kept or dropped | A standalone dated promise |
Plus the inbox: raw, untyped captures in inbox/ awaiting triage.
The journal: daily, weekly, and monthly
The chronological backbone. Daily notes collect timestamped log lines plus structured sections
(Intention, Agenda, Standup, Meeting). Weekly notes hold the review (Wins, Challenges, One
Improvement) and the week’s single goal. Monthly notes hold the higher-altitude review (Wins,
Themes, Next Month’s Focus) and a ## Weeks block that links the month’s weekly notes rather
than copying them, so the weeks stay the source of truth. All three are append-only — the
historical record only grows.
project — the one mutable map
Projects are the only freely-mutable note type. A project carries a Current State, a list of
next actions (inline bullets by default), milestones, and waiting-on items. When you
update the Current State, the previous state is auto-logged to today’s daily note first, so history
is never lost (see Business rules). At most five projects are active at
once; the rest live parked in projects/_parked/.
action — inline by default, a note when it grows
An action’s default form is a checkbox bullet on its project map. That’s usually all you need. When a
single action becomes an investigation spanning days and artefacts, you promote it to a manifest
action note (heavier: status, energy, criteria, links). Completing an action removes the bullet,
logs it, and — if it had a note — archives that note to actions/_done/<year>/. See
Actions.
portfolio + evidence — dossiers per question
A portfolio is a folder named for a question, with an _index.md (the portfolio note) and a set of
evidence notes filed into it over time. Each evidence note records a source and an origin
(a wikilink to whatever produced it). Evidence is append-only — a portfolio is a growing record. See
Research and evidence.
stewardship + tracking — long-haul responsibilities
A stewardship is a dashboard for something you tend indefinitely. It can be flat (a single
stewardships/<slug>.md) or expanded (a stewardships/<slug>/ folder with _index.md, a
tracking/ subfolder for time-series entries, and a routines/ subfolder for reference docs).
Expanded stewardships accept tracking notes via cdno track. See
Stewardships and tracking.
question — important questions, kept visible
A question note has a domain (research or life) and a status (active, parked, answered,
retired). Questions anchor portfolios and projects (via the core_question link). List the active
ones any time with cdno questions.
commitment — dated promises
A standalone promise with a hard due: date and a context. It moves to
commitments/_done/<year>/ when it ends — stamped completed if it was kept, dropped if it was
cancelled or overtaken. Both are terminal; the frontmatter says which. Standalone commitments are one of four sources feeding the aggregated
commitments view.
These twelve are a closed set with built-in behaviour. For an entity they don’t cover (people,
books, clients), you can declare a schema-only custom note type
in config.toml — a folder, field rules, and a template, with no recompile (see the worked
Tracking people recipe).
For the exact frontmatter fields of each type, see Frontmatter fields. Next: Vault structure.
Vault structure
A vault is a directory tree of Markdown files plus a .cuaderno/ config folder. cdno init creates
the whole layout; here’s what each part holds.
vault/
├── journal/
│ └── 2026/ # partitioned by (ISO) year
│ ├── daily/
│ │ └── 2026-04-25.md # type: daily (append-only)
│ └── weekly/
│ └── 2026-W17.md # type: weekly (append-only)
│
├── projects/
│ ├── surrogate-model.md # type: project (mutable)
│ └── _parked/ # inactive projects (don't count toward the cap)
│ └── bayesian-opt.md
│
├── actions/
│ ├── characterise-sampler.md # type: action (manifest form)
│ └── _done/
│ └── 2026/ # completed actions, partitioned by year
│ └── run-ablation.md
│
├── portfolios/
│ └── sparse-vs-dense-ood/
│ ├── _index.md # type: portfolio
│ ├── 2026-03-15-chen-2025.md # type: evidence (append-only)
│ └── 2026-04-01-ablation-b.md
│
├── stewardships/
│ ├── finances.md # type: stewardship (flat)
│ └── health/ # expanded variant
│ ├── _index.md # type: stewardship
│ ├── tracking/ # type: tracking entries (append-only)
│ │ └── 2026-04-06-gym.md
│ └── routines/ # reference docs, not logs
│ └── upper-body-a.md
│
├── commitments/
│ ├── pay-rent.md # type: commitment
│ └── _done/
│ └── 2026/ # fulfilled commitments
│
├── questions/
│ ├── research/
│ │ └── surrogate-cost.md # type: question (domain: research)
│ └── life/
│ └── apartment-as-home.md # type: question (domain: life)
│
├── inbox/ # raw captures awaiting triage
│
└── .cuaderno/
├── config.toml # vault configuration
├── index.db # SQLite index cache (auto-created, rebuildable)
└── templates/ # note templates (override the built-ins)
Conventions worth knowing
_parked/(projects) and_done/(actions, commitments) prefix folders hold inactive or finished notes. The underscore keeps them sorted out of the way and signals “not the active set.”_done/is partitioned by year so the active folders stay scannable._index.mdis the identity note of a folder — theportfolionote inside a portfolio folder, thestewardshipnote inside an expanded stewardship folder.tracking/inside a stewardship holds time-series entries;routines/holds prescriptive reference documents (a workout plan, a checklist) — those are not logs.- Stewardships have two shapes: a flat
stewardships/<slug>.md, or an expandedstewardships/<slug>/folder. Only expanded ones can hold tracking entries.
.cuaderno/
config.toml— vault settings: the project cap, ignore globs, template behaviour, schema extensions, variables. See Configuration.index.db— a SQLite cache of the vault, used for fast search, linting, and link queries. It is rebuilt automatically when it’s stale (see Business rules); deleting it is safe. The Markdown files are always the source of truth.templates/— the note templatescdnofills when scaffolding a note. The built-ins are written here atinitso you can edit them; pure variable substitution, no logic.
Next: Business rules.
Business rules
Cuaderno enforces a small set of rules that keep the method honest. Knowing them explains why some commands behave the way they do.
Markdown is the source of truth
The files on disk are canonical. The SQLite index in .cuaderno/index.db is a cache — it makes
search, linting, and link lookups fast, but it can always be rebuilt from the Markdown. A stale
index is recoverable; a stale file would be data loss, so the tool never lets the cache override
the files. You can delete index.db at any time, or rebuild it explicitly with
cdno reindex.
Startup reconciliation
On every CLI invocation, MCP session, and app launch, Cuaderno reconciles the index against the filesystem — comparing modification times and content hashes — and quietly repairs anything stale before doing your command. This is why edits you make to notes by hand (in your editor, via sync) are picked up without a manual reindex.
The five-project cap
At most five projects are active at once. Try to create or activate a sixth and the command
stops you — you must park one first. Parked projects live in
projects/_parked/ and don’t count toward the cap. The limit is configurable via config.toml
(see Configuration); five is the default because it’s the point past which
“active” stops meaning anything.
Append-only notes
daily, weekly, evidence, and tracking notes are append-only — Cuaderno only ever grows
them, never overwrites. They are the historical record. (Projects are the deliberate exception; see
next.)
Project state history is preserved
A project’s Current State is the one piece of freely-mutable prose in the vault. To keep history
intact, every time you update it (via cdno project state or the MCP
update_project_state tool) the previous state is auto-logged to today’s daily note before the
new text overwrites it. You get a clean current view and a full audit trail in the journal.
Commitments are aggregated, not stored in one place
The commitments view is computed from four sources, so a promise is counted wherever it naturally lives:
- Project milestones marked with a hard deadline (
--hard). - Stewardship periodic commitments (the recurring lines on a stewardship dashboard).
- Standalone commitment notes in
commitments/. - Action notes carrying a self-imposed
due:that isn’t pinned to a milestone.
cdno commitments merges and sorts all four by date, with overdue
items flagged.
Atomicity and its limits
Each write is captured as a transaction (file writes + index updates) that commits as a batch and
rolls back on failure — while the process is alive. Startup reconciliation catches index staleness
afterward, but a crash midway through a rare multi-file operation can still leave partial state on
disk. In practice this is vanishingly rare; the takeaway is simply that the Markdown is authoritative
and reindex + lint will surface
anything odd.
Next: Contexts and energy.
Contexts and energy
Two small enumerations show up across many commands. Both are fixed vocabularies (not free text), so they stay consistent and filterable.
Contexts — the life domain
A context classifies which part of life a project, stewardship, or commitment belongs to. The set is fixed:
| Context | Typical use |
|---|---|
work | Your main job |
side-project | Personal projects outside work |
university | Studies, coursework, a degree |
family | Family responsibilities |
household | Running a home |
legal | Paperwork, contracts, official matters |
personal | Health, growth, anything else personal |
You set a context when you create a project (--context), stewardship (--context), or commitment
(--context). It groups and colours items in views and lets the system reason about balance across
your life, not just your work.
Contexts are a compile-time set, not configurable — keeping the vocabulary small and shared is the point. (Stewardships accept the same set.)
Energy — the effort a thing needs
An energy level tags how much focus an action demands, so the morning suggestion can match the work to how you actually feel:
| Energy | Meaning |
|---|---|
deep | Heavy, uninterrupted focus (real thinking, hard implementation) |
medium | Moderate focus (routine progress, review) |
light | Low focus (admin, quick wins, tidying) |
You tag an action with --energy when you add it. Then:
# "I have a clear morning" — bias the suggestion toward deep work:
cdno orient --energy deep
# "I'm fried" — surface something light instead:
cdno orient --energy light
cdno orient uses the energy bias to pick which next action to
suggest as your starting point. Matching the task to your state — rather than forcing the hardest
thing first — is part of what makes the daily loop sustainable.
Next: Configuration.
Configuration
Vault behaviour is configured in .cuaderno/config.toml, written for you by cdno init. The
defaults are sensible — you can run for a long time without touching it. This page explains what is
configurable and why; the Configuration reference lists every key.
What you can configure
- The active-project cap. Change the default of five via
[vault] max_active_projects. - Ignore globs. Patterns for files the index should skip —
CLAUDE.md,README.md, scratch notes — so they don’t appear in search, lint, or link checks. Patterns are additive (no negation), matched against vault-relative paths, and never delete anything on disk — they only scope what the index considers. - Templates. Override any built-in note template by adding a file under
.cuaderno/templates/. - Schema extensions. Add vault-specific required frontmatter fields per note type (e.g. require
collaboratorson every project), enforced bycdno lint.
The hands-on walkthrough is Customising templates and frontmatter.
Templates
When cdno scaffolds a note, it fills a template. Templates are pure variable substitution — no
conditionals, no logic. cdno init writes one starter template (.cuaderno/templates/daily.md);
every other type uses its built-in default until you add a file for it. cdno picks the most
specific template that exists:
- a custom variant template (for tracking, e.g.
tracking-gym.md), then - a custom type template (e.g.
project.md), then - the built-in variant default, then
- the built-in type default.
Because templates also define the canonical order of frontmatter keys,
cdno normalise uses them to reorder hand-authored or migrated
notes into a consistent shape.
Variables
Templates use {{placeholder}} markers that cdno fills from the values each note’s creation
command supplies — {{title}}, {{context}}, {{created}}, and so on. The exact set available per
note type, and how to use them in a custom template, is covered in
Customising templates and frontmatter. An unknown
placeholder is left verbatim, so use only the ones a type provides.
Custom templates can also reference static vault variables you set under [variables] in
config.toml (e.g. {{author}}); these resolve on every note type, with per-type values taking
precedence over a config variable of the same name.
For values that change per note, prompted variables under [variables.prompt] are gathered at
creation — from a --var name=value flag, an interactive prompt, or (failing both) a clear error.
The tutorial covers them in full.
Example
[vault]
name = "My Research Vault"
max_active_projects = 5 # the active-project cap
# Skip these from the index entirely (search/lint/links). Never deletes files.
ignore = ["CLAUDE.md", "README.md"]
# Require an extra field on every project note — enforced by `cdno lint`:
[schemas.project]
extra_required = ["collaborators"]
# Static template variables — resolve in any custom template (e.g. {{author}}):
[variables]
author = "A. Researcher"
# Prompted variables — gathered at note creation (--var, prompt, or error):
[variables.prompt]
collaborators = "Who are the collaborators?"
For the complete key-by-key reference, see Configuration reference.
That’s the concepts tour — next, put it to work in the Tutorials.
The daily loop
The core rhythm of Cuaderno is a daily loop: orient → act → log → close. None of it is mandatory or guilt-inducing; it’s a habit that keeps the vault current with almost no overhead.
Morning: orient
Start the day by asking the tool what deserves attention:
cdno orient --energy deep
orient shows commitments due soon, your active projects with their
current state and top next action, and a single suggested starting point — biased toward the
energy you tell it you have. Drop --energy to get a neutral
suggestion; pass --energy light on a low day.
Want just the project snapshot without commitments? Use cdno status.
Through the day: act and log
As you work, two verbs carry most of the weight.
Log what happens — append a timestamped line to today’s journal:
cdno log "scaled the mesh to 2M cells; 4x runtime, still stable"
Capture stray thoughts without breaking flow — they land in the inbox for later triage:
cdno capture "does Chen 2025 use the same preconditioner?"
Make progress on projects as you go:
# Add the next action you just thought of:
cdno action add --project surrogate-model --title "Profile the assembly step" --energy medium
# Record where a project now stands (auto-logs the previous state to today's journal):
cdno project state --slug surrogate-model --text "Mesh scaling works; assembly is the bottleneck"
# File a useful result into the right portfolio:
cdno file --portfolio sparse-vs-dense-ood --source "ablation run B" --origin projects/surrogate-model
# Say what you are starting, so `cdno now` can answer for you later:
cdno action start --project surrogate-model --query "assembly step"
# Tick off a finished action (substring match on the bullet):
cdno action complete --project surrogate-model --query "feature set B"
When you lose the thread, ask what you were doing rather than guessing:
$ cdno now
surrogate-model since 09:30 · 1h 30m
Profile the assembly step (medium)
There is no state behind that — it replays today’s journal, so a start made here, from the desktop,
or by an agent over MCP all count equally, and completing or dropping the action clears it. With
nothing open it says so rather than printing an empty frame. See
cdno now for the full shape, including what a start typed by hand has to
look like.
Evening: close
A light wind-down — note anything for your stewardships and reflect:
# Log a tracked activity under a stewardship:
cdno track gym --stewardship health --content "Upper body; good energy"
# A closing reflection is just another log line:
cdno log "good focus day; pick up assembly profiling tomorrow"
That’s it. The journal now holds a faithful record of the day, your projects reflect reality, and
tomorrow’s orient will pick up where you left off.
Letting Claude run the loop
Each step has an MCP equivalent (get_orientation, append_to_log, add_action,
update_project_state, …), so an assistant can drive the same loop conversationally. See
Connect to Claude and the example
skills.
Next: Managing projects.
Managing projects
A project is a lightweight map of a piece of active work: a current state, next actions,
milestones, and things you’re waiting on. You keep at most five active
at once. All the verbs live under cdno project (plus
cdno action for the next-action list).
Create one
cdno project create --title "Surrogate model" --context work
# -> projects/surrogate-model.md (slug derived from the title)
--context is the life domain. Optionally link the project’s
core question with --question questions/research/surrogate-cost — and if you skip it, or the
question changes later, cdno project core-question --slug surrogate-model --question <target>
sets it afterwards (--clear detaches). If you’re already at five active projects, the new one is
created parked — activate it once you free a slot.
See where things stand
cdno project list # active projects + a state snippet
cdno project show surrogate-model # one project in detail
cdno status # all active projects + their top action
Add --json to any of these for structured output (see JSON output).
Update the current state
The Current State is the project’s one mutable paragraph — “where is this right now?”. Updating it auto-logs the previous state to today’s journal first, so you never lose the trail:
cdno project state --slug surrogate-model \
--text "Mesh scaling works to 2M cells; assembly is now the bottleneck"
Next actions
Actions are the things to do next. By default they’re inline bullets on the project:
cdno action add --project surrogate-model --title "Profile the assembly step" --energy medium
cdno action list --project surrogate-model
cdno action complete --project surrogate-model --query "profile the assembly"
See Actions for the inline-vs-manifest distinction and promotion.
Milestones
Milestones are markers of progress. Mark one --hard to make it a real deadline that shows up
in the aggregated commitments view:
cdno project milestone add --slug surrogate-model --title "Submit to ICML" --date 2026-01-22 --hard
cdno project milestone done --slug surrogate-model --query "submit to icml"
--date is optional. When a milestone is gated by a condition rather than a date, leave it off
rather than inventing an estimate — a made-up date reads back later like a commitment somebody
made:
cdno project milestone add --slug surrogate-model --title "All Round-1 replies received"
An undated milestone records target: TBD, stays out of the commitments view, and completes
exactly like a dated one. --hard needs a real date.
Waiting-on
Track external blockers so they’re visible instead of forgotten:
cdno project waiting add --slug surrogate-model --description "Cluster quota increase from IT"
cdno project waiting resolve --slug surrogate-model --query "cluster quota"
Park and re-activate
Parking is first-class and reversible — it’s how you respect the five-project cap without deleting anything:
cdno project park --slug surrogate-model # -> projects/_parked/, frees a slot
cdno project activate --slug surrogate-model # bring it back (must be under the cap)
Next: Research and evidence.
Research and evidence
This is the knowledge-building half of the method: name the questions that matter, open a portfolio for each, and file evidence into it as you go. Over months a portfolio becomes a dossier you can actually reason from.
Name a question
cdno question create --domain research --text "Does sparse attention beat dense out-of-distribution?"
# -> questions/research/does-sparse-attention-beat-dense-out-of-distribution.md
--domain is research or life. List the active ones any time:
cdno questions # grouped by domain
cdno questions --json | jq .
As a question’s life changes, transition it (each transition is logged to the journal):
cdno question park --slug does-sparse-attention-beat-dense-out-of-distribution
cdno question answer --slug does-sparse-attention-beat-dense-out-of-distribution
cdno question retire --slug does-sparse-attention-beat-dense-out-of-distribution
cdno question activate --slug does-sparse-attention-beat-dense-out-of-distribution
Open a portfolio for it
A portfolio is a folder that accumulates evidence about one question:
cdno portfolio create --question "Sparse vs dense attention OOD"
# -> portfolios/sparse-vs-dense-attention-ood/_index.md
# Optionally tie it to a project when you create it:
cdno portfolio create --question "Sparse vs dense attention OOD" --project projects/surrogate-model
Already have a portfolio and want to link it after the fact? Use the retrofit verb (one of
--question/--project, not both):
cdno portfolio link --portfolio sparse-vs-dense-attention-ood --project projects/surrogate-model
File evidence
Every useful artefact — a paper, an experiment result, a conversation — goes in as an evidence
note. --source is the citation/reference; --origin is a wikilink to whatever produced it:
cdno file --portfolio sparse-vs-dense-attention-ood \
--source "Chen et al. 2025, NeurIPS" \
--origin projects/surrogate-model \
--content "They report a 4x speedup at 95% accuracy on the OOD split."
Attaching a real file
To file a non-Markdown artefact (PDF, image, video), point --attach at it. Cuaderno copies it into
the portfolio and scaffolds a linked evidence stub beside it (here --content is the abstract). Add
--move to move instead of copy:
cdno file --portfolio sparse-vs-dense-attention-ood \
--source "Chen et al. 2025" \
--origin projects/surrogate-model \
--attach ~/Downloads/chen2025.pdf \
--content "Key result: 4x speedup at 95% accuracy."
Review what’s accumulated
cdno portfolio list # all portfolios + evidence counts + staleness
cdno portfolio show --portfolio sparse-vs-dense-attention-ood
cdno search "preconditioner" --portfolio sparse-vs-dense-attention-ood
portfolio list flags staleness — dossiers you haven’t fed in a while — which is a useful prompt
during your monthly scan. Periodically synthesise the findings into the portfolio’s _index.md.
Next: Actions.
Actions
An action is the next concrete thing to do on a project. Cuaderno treats actions as cheap by
default: the normal form is a single bullet on the project map, not a note you have to create and
maintain. You only give an action its own note when it grows into real investigation. All verbs live
under cdno action.
Add a next action
cdno action add --project surrogate-model --title "Profile the assembly step" --energy medium
This appends a checkbox bullet to the project’s next-actions list, tagged with its energy. That’s usually all an action ever is.
List open actions
cdno action list --project surrogate-model
Bullets that have been promoted to notes show their status (active / blocked / completed /
dropped) inline.
--json gives the structured list.
Promote an action to a manifest note
When an action becomes an investigation that spans days and produces evidence, promote it. This
rewrites the bullet as a wikilink and scaffolds an action note (with status, energy, criteria,
links). The match is a case-insensitive substring of the bullet text; energy is inherited:
cdno action promote --project surrogate-model --query "profile the assembly"
You can also create an action already promoted by passing --note to add:
cdno action add --project surrogate-model \
--title "Characterise sample efficiency across mesh sizes" \
--energy deep --note
Start an action
Starting is optional — nothing forces you to declare it — but it is what makes
cdno now able to answer “what am I in the middle of?”, which matters
most on the days you come back from an interruption and cannot remember:
cdno action start --project surrogate-model --query "assembly step"
This logs started [[surrogate-model]] — Profile the assembly step (medium) to today’s journal.
What gets written is the resolved bullet text, not your query, so the later completion logs
matching text and the focus clears by itself.
For work that is on no map yet — the fix you noticed, the errand in front of you — --unplanned
adds the bullet and starts it in one go:
cdno action start --project surrogate-model --unplanned \
--title "Chase the licence renewal" --energy light
That is deliberately a separate flag rather than a fallback when --query matches nothing. A
fallback would turn every typo into a new action, silently.
Two limits worth knowing, both pre-existing and both easy to trip over:
- Do not promote an action between starting it and closing it. Promotion rewrites the bullet,
so the start can no longer be paired with it:
cdno nowkeeps naming the old text for the rest of the day, andcompleteanddropboth match nothing. Close it first, or re-run the start after. - Two open bullets with identical text cannot be told apart by a substring query — neither can
be closed until one is edited. This is not specific to starting;
action addtwice does the same.
Complete an action
Completing matches a bullet by substring, ticks it off, and logs it to today’s journal. If the
action had a manifest note, that note is archived to actions/_done/<year>/ and becomes append-only:
cdno action complete --project surrogate-model --query "feature set B"
Drop an action
Not everything on the list gets done. When an action is superseded, abandoned or reprioritised, drop it rather than completing it:
cdno action drop --project surrogate-model --query "demo proposal" \
--reason "superseded by the demo-planning action"
This matches and archives exactly like complete, but the note is stamped status: dropped with no
completion date, and the journal records action dropped on … instead of action done on ….
The distinction matters more than it looks. The daily log is what the weekly review, the monthly scan and every later verdict read back from — so completing work that was never performed leaves your own vault asserting something untrue, and the only repair is a correction line written by hand. A dropped action also stays out of the completed-actions views, because it carries no completion date.
--reason is optional and worth giving. “Superseded by X” and “no longer wanted” are different
facts, and only one of them tells you to go looking for the replacement.
Inline vs. manifest — when to promote
| Use an inline bullet (default) | Promote to a manifest note |
|---|---|
| A discrete next step | An investigation spanning multiple days |
| Done in one sitting | Produces evidence / artefacts to link |
| No supporting detail needed | Needs success criteria, status, or its own notes |
Keeping the default cheap is deliberate — it means capturing the next step never costs more than a sentence. See The Research Logbook Method for the rationale.
Next: Commitments and deadlines.
Commitments and deadlines
A commitment is a dated promise — to others, or a hard promise to yourself. Cuaderno keeps these distinct from your to-do list and gives you one aggregated view of everything with a deadline, wherever it lives.
The aggregated view
cdno commitments # everything due, sorted by date, overdue flagged
cdno commitments --weeks 6 # look six weeks ahead instead of the default two
cdno commitments is a computed view. It always includes a
30-day overdue look-back on top of the lookahead window, so nothing slips silently into the past. It
draws from four sources (see Business rules):
- Project milestones marked
--hard. - Stewardship periodic commitments (recurring dashboard lines).
- Standalone commitment notes (below).
- Action notes with a self-imposed
due:not tied to a milestone.
Standalone commitments
For a one-off promise that isn’t naturally a project milestone or a recurring stewardship line:
cdno commit create --title "Pay rent" --due 2026-06-01 --context personal
# -> commitments/pay-rent.md
# Optionally attribute it to a project or stewardship:
cdno commit create --title "Review Erik's draft" --due 2026-05-20 --context work --project projects/icml-paper
When it’s fulfilled, mark it done — it’s stamped and moved to commitments/_done/<year>/:
cdno commit done --slug pay-rent
When a promise ends without being kept
Not every promise is kept. The client cancels, the plan changes, the thing is overtaken by events.
Marking it done would be the wrong record — cdno commitments and every weekly and monthly review
read the daily log back, and a completion says the promise was fulfilled:
cdno commit drop --slug quarterly-report --reason "the client cancelled the engagement"
The note is archived beside your completed ones, stamped status: dropped with no completion date,
so it never counts as finished work. --reason is optional; a typo needs no explanation, a
cancellation usually deserves one.
If the date merely moved, reschedule is the
verb you want — it records the move rather than ending the promise.
Nothing is destroyed by a drop: the note keeps its body and its created date. But a drop is
re-decided rather than undone — there is no un-drop. If the promise comes back, make it again, and
the record honestly shows both decisions.
Deadlines that live elsewhere
You often don’t need a standalone note — put the deadline where the work is:
# A hard project milestone shows up in `cdno commitments`:
cdno project milestone add --slug icml-paper --title "Camera-ready" --date 2026-02-01 --hard
# A recurring obligation on a stewardship dashboard:
cdno stewardship add-periodic --stewardship finances --title "File quarterly taxes" \
--every "every 3 months" --next 2026-07-15
Both surface in the same aggregated list, so cdno commitments is the single place to answer “what
have I promised, and when?”.
Next: Stewardships and tracking.
Stewardships and tracking
A stewardship is a small, bounded, perpetual responsibility — your health, your finances, a
service you maintain. Unlike a project it never “finishes”; you just tend it. Stewardships can carry
recurring commitments and, when expanded, time-series tracking. Verbs:
cdno stewardship and cdno track.
Two shapes: flat and expanded
# Flat — a single dashboard file, no tracking. Good for "finances".
cdno stewardship create --name "Finances" --context household
# Expanded — a folder with room for tracking/ and routines/. Use --tracking.
cdno stewardship create --name "Health" --context personal --tracking
A flat stewardship is stewardships/<slug>.md. An expanded one is stewardships/<slug>/ with an
_index.md, a tracking/ subfolder for entries, and a routines/ subfolder for reference docs
(workout plans, checklists — not logs). Only expanded stewardships accept tracking entries.
See what you’re tending
cdno stewardship list # each one's variant, tracking count, staleness badge
cdno stewardship show --slug health
Periodic commitments
Recurring obligations attached to a stewardship show up in the aggregated commitments view:
cdno stewardship add-periodic --stewardship health --title "Dental check-up" \
--every "every 6 months" --next 2026-09-01
--every takes a recurrence: daily, weekly, monthly, yearly,
or every N months.
Tracking entries
For habits and metrics on an expanded stewardship, file a tracking note. The activity is
positional and selects the template — a vault’s .cuaderno/templates/tracking-<activity>.md if you
have one, else a generic fallback. (Ready-made variants live in the repo’s
examples/templates/tracking/; see Customising templates.)
cdno track gym --stewardship health --content "Upper body A; RDL up to 25kg"
cdno track body --stewardship health --content "Weight 78.4kg, resting HR 54"
cdno track swim --stewardship health --content "1500m, 28min"
-
--stewardshipcan be omitted when there’s exactly one expanded stewardship — Cuaderno defaults to it. With more than one, it’s required. -
--routinelinks a reference doc from the stewardship’sroutines/folder into the entry — but only when the resolved template has aroutine:field (thegym.mdexample variant does; the generic default has none, so it no-ops there). -
--contentis optional; leave it empty and fill the entry’s tables in afterward. -
--atfiles the entry for a day that has already passed. Recording lags the event more often than not — a statement reconciled at the weekend, a reading taken this morning and typed up tonight — and without it the entry lands on the wrong day and the trend bends.cdno track body --stewardship health --at 2026-04-06Filing is always journalled to today’s daily log, naming the day the entry describes, so a backfill stays visible in the record rather than quietly appearing in the past. Dates more than 50 years back or a year ahead are refused — that far out is a typo, and a typo would reshape a trend without saying so.
Numbers belong in frontmatter
A number written into the body is prose; a number in frontmatter is data. An agent files them
through the MCP create_tracking_entry tool’s metrics parameter, and each key becomes a
frontmatter key:
---
type: tracking
stewardship: finances
activity: savings
date: 2026-07-25
balance: 12480.50
contributed: 400.00
---
When one entry holds several comparable items — three subjects practised, four categories spent against — write a sequence of flat records rather than one key per item, so the same subject can recur within the entry:
detail:
- { minutes: 25, subject: harmony }
- { minutes: 20, subject: harmony }
- { minutes: 15, subject: sight-reading }
A metric that reports a level rather than a total — a balance, a measurement, the last set of
the day — reduces to the last record in the entry, and “last” means the order the records appear in
the file. If you append them out of order, give every record an at field and they sort by it:
detail:
- { balance: 1200, at: "09:00" }
- { balance: 1240, at: "18:00" }
It is all-or-nothing: if any record lacks an at, or carries one that does not parse (09:00,
9:00, 9:00 AM and their with-seconds forms all do), the file’s own order stands and nothing is
reordered. The key is at rather than time precisely because time is a plausible metric —
a swim split, a lap time — and ordering a record set by one of its own measurements would report
a number that was never the last reading.
Write the colon. at: 18:00 needs no quoting — the colon is what keeps YAML reading it as text —
but at: 1800 is a number and never reaches the time parser at all.
Falling back to file order is the safe answer — ordering a half-stamped set would have to invent
a position for the unstamped records, and whichever position it invented would quietly change
which reading a last metric reports. But the fallback used to be invisible, so a set stamped
morning / evening looked identical to one that sorted correctly. cdno lint now reports an
at it cannot use, and a set where only some records carry one, naming the note and the value.
An entry with no at anywhere is never reported: that is the normal way to write these notes.
Declaring a metric under [schemas.tracking.fields] (see
Configuration) gets it type-checked on the way in — a float for
a measurement or an amount, an int for a count. Anything undeclared is written as given, except
the four keys that identify the note — type, stewardship, activity, date — which a metric
may not name. They are the note’s identity rather than data about it, and the engine owns them:
date is fixed by the filename, and activity is what every reader groups by.
Tracking entries are append-only — they’re your historical record.
Read them back over a window via the MCP get_stewardship_tracking tool, or with
cdno search.
From entries to series
Filing an entry is half the story. The other half — how those numbers turn into a chart, and why a
series sometimes shows a number that means nothing — comes down to two things: which aggregate
each metric declares, and which source, body table or frontmatter, the activity is read from.
Every [tracking.<activity>] key is documented in full in the
Tracking section of the configuration reference; this is
the plain-language version of why those keys exist.
The metric-kind taxonomy
Every number you track is one of four kinds, and the kind decides the aggregate it wants:
| Kind | Examples | Aggregate |
|---|---|---|
| Total | amount spent, pages read, minutes practised | sum |
| Level | account balance, a measurement, a top set | last, or max for a high-water mark |
| Rate or rating | a score out of ten, perceived difficulty | mean |
| Occurrence | a call, a visit | none — declare no metrics; the entry’s existence is the record |
Summing is right for exactly one of these — a total — and wrong for the rest. It’s wrong for a level because it adds successive readings of one quantity instead of reporting the quantity: three statements through a spending-free month, each reading a balance of roughly 12,480, would sum to over 37,000 — a number nothing in the account corresponds to. It’s wrong for a rating because the series grows with how often you record it, not with how the session actually went — log twice a day instead of once and the total doubles for no reason but the logging cadence. An occurrence doesn’t want a number at all: declaring the activity with no metrics is a complete, valid use — the record is “this happened”, not “how much”.
Two worked examples
A scalar activity, a level. The savings activity shown above has no repeated records — every
entry is one reading. balance is a level, so it wants last, not the default sum; contributed
is a total, so the default is already right:
[tracking.savings]
# no `records` — this activity's metrics are scalars read straight off the entry
[tracking.savings.metrics.balance]
aggregate = "last" # a LEVEL — the reading itself, not a running total of readings
unit = "EUR"
[tracking.savings.metrics.contributed]
aggregate = "sum" # a TOTAL — money added since the last entry
unit = "EUR"
Filed against the frontmatter shown earlier (balance: 12480.50, contributed: 400.00), this
produces two series: savings · balance, whose points are each entry’s balance verbatim, and
savings · contributed, whose points are the sum of every contribution recorded on that date.
A record-based activity, two aggregates over the same records. practice splits by subject
and tracks two different kinds of number per session:
[tracking.practice]
records = "detail" # frontmatter key holding the repeated records
group_by = "subject" # one series per distinct subject
[tracking.practice.metrics.minutes]
type = "int"
aggregate = "sum" # a TOTAL — minutes practised
unit = "min"
[tracking.practice.metrics.focus]
aggregate = "mean" # a RATING — sum would grow with how often you log, not with how focused you were
One entry covering two subjects in the same sitting:
---
type: tracking
stewardship: study
activity: practice
date: 2026-07-25
detail:
- { minutes: 25, focus: 8, subject: harmony }
- { minutes: 20, focus: 6, subject: harmony }
- { minutes: 15, focus: 9, subject: sight-reading }
---
Four series come out of it, named <activity> · <group> · <metric>:
practice · harmony · minutes= 45 (25 + 20, summed)practice · harmony · focus= 7 (the mean of 8 and 6)practice · sight-reading · minutes= 15practice · sight-reading · focus= 9
Same records, same date, two different reductions — because minutes and focus are different
kinds of number, not because one config option was chosen for the whole activity.
Gaps, not zeros
A subject you didn’t practise on a given day produces no point on that day, not a zero.
Zero-filling would draw a false line down to the axis — a session that didn’t happen is not the same
as a session scored zero. The same rule means a brand-new subject needs no configuration change: the
first time ear-training shows up in a detail record, its series starts there, on that date, with
every date before it correctly absent rather than zero.
Which source a series comes from
An undeclared activity — no [tracking.<activity>] for it in config.toml — is read from the
first table in the note’s body: one series per column, each column’s numeric cells summed. That’s
right for a rep count and wrong for almost everything else, which is why the declared path above
exists. If you’re still filing through a table, see
Wide, not long
in the example templates — a table column is a series, and a Metric / Value row shape sums
unrelated numbers into one meaningless total.
Declaring an activity moves it to frontmatter: once the declaration yields a series for that activity, its body tables are no longer read — every note of it, not just the ones carrying frontmatter metrics. So the same metric can never appear twice under two disagreeing numbers, one from a table and one from the declared reduction. It also means a half-migrated activity shows only what its frontmatter carries, so migrate an activity’s notes together rather than one at a time.
Next: Weekly review.
Weekly review
The weekly review is the one slightly-longer ritual in the method — a short retrospective plus a single goal for the week ahead. It’s where you celebrate progress, notice what’s stuck, and set direction without micromanaging.
The weekly note
Each ISO week has a weekly note with four sections:
- Wins — what moved (actions completed, evidence filed, projects advanced).
- Challenges — what got in the way.
- One Improvement — a single thing to change next week.
- This Week’s Goal — the week’s anchor: one goal everything else orbits.
View the current week’s note (or any week) any time:
cdno weekly # this ISO week
cdno weekly --date 2026-04-20 # the week containing that date
The guided ritual
cdno review weekly walks you through it. Interactively, it prompts for
each retrospective section and writes them into this week’s note, then asks for next week’s goal
and sets it as the This Week’s Goal of next week’s note — so when the new week starts, its
anchor is already there.
cdno review weekly
Run non-interactively (or with --no-interactive), it reads the current note rather than prompting —
handy for scripts or a quick scan.
A weekly cadence that works
A simple routine, ~15 minutes:
cdno status— skim active projects and their top actions.cdno commitments --weeks 2— what’s due in the next fortnight?cdno stewardship list— any habit looking stale?cdno review weekly— capture Wins / Challenges / One Improvement, set next week’s goal.
The point isn’t a perfect record; it’s a regular moment to look up from the work, acknowledge what you did, and choose one direction. Parking a project or retiring a question here is a good outcome, not a failure.
With Claude
The MCP tools get_weekly_context, read_weekly_note, and upsert_weekly_section let an assistant
run the same review conversationally — reading your week back to you and writing the sections as you
talk. See Connect to Claude.
Next: Inbox and triage.
Inbox and triage
The inbox is a pressure-release valve: capture a thought now, decide what to do with it later. This keeps the daily loop friction-free — you never have to stop and classify something mid-flow.
Capture
cdno capture "does Chen 2025 use the same preconditioner?"
cdno capture "ask IT about the cluster quota"
Each capture becomes a small slug-named note in inbox/. That’s the whole cost — no fields, no
decisions. Capture liberally.
Triage
When you have a moment (a natural fit for the weekly review), process the inbox:
cdno triage
Interactively, triage walks each pending capture and offers, for each one, to:
- keep it as a project action — turn it into a next action on a project, or
- discard it — it served its purpose, or
- skip it — leave it for next time.
Run non-interactively (or with --no-interactive), triage just lists what’s pending — a quick
way to see the backlog without acting on it.
A healthy inbox is empty-ish
The inbox is a transit point, not a filing cabinet. The goal isn’t zero at all times — it’s that nothing important lives only there. Anything worth keeping becomes an action, a piece of evidence, a question, or a log line; the rest gets discarded without ceremony.
With Claude
The MCP tools capture, triage_inbox (lists pending items), and discard_inbox_item let an
assistant capture on your behalf and help you clear the backlog conversationally.
Next: Searching your vault.
Searching your vault
Cuaderno keeps a full-text index of every note, so you can find anything fast — ranked best-first,
with filters by type, date, and portfolio. The command is cdno search.
The index covers each note’s title as well as its body, and a title match counts for ten times a body match. So searching for words you remember from a note’s heading surfaces that note first, even if a dozen other notes mention the same words in passing — there is no separate “search by title” command to remember.
If you already know exactly which note you want, cdno open takes you
straight there by slug, date, or path, without ranking anything.
In a terminal, cdno search also follows its results with a picker: choose a hit and it opens in
your editor. The results print first either way, so piping or redirecting the output is unaffected.
Basic search
cdno search "preconditioner"
cdno search "sparse attention" # multiple words are ANDed
The query is matched case-insensitively and terms are combined with AND. Quotes and operators are treated as literal words, not search syntax. Results come back ranked, best match first.
Filter the results
# Only one note type:
cdno search "ablation" --type evidence
# Within a date window (inclusive):
cdno search "mesh" --from 2026-03-01 --to 2026-03-31
# Only inside one portfolio:
cdno search "speedup" --portfolio sparse-vs-dense-attention-ood
# Cap the number of hits (default 20):
cdno search "todo" --limit 5
Filters combine, so you can scope tightly:
cdno search "preconditioner" --type evidence --portfolio sparse-vs-dense-attention-ood --from 2026-01-01
Scripting with --json
--json returns the ranked hits as a JSON array — each with path, note_type, title, snippet,
and score — ready for jq or another tool:
cdno search "speedup" --json | jq -r '.[].path'
See JSON output for the exact shape.
When search feels stale
Search reads the SQLite index, which is reconciled automatically on every run. If results ever look out of date (e.g. after a bulk external edit), rebuild it explicitly:
cdno reindex
The Markdown files are always the source of truth; the index is just a rebuildable cache (see Business rules).
Next: Customising templates and frontmatter — tailor how notes are scaffolded and which frontmatter fields they require.
For exhaustive detail on any command, see the CLI reference.
Customising templates and frontmatter
When cdno scaffolds a note it fills a template. Every note type has a built-in template, and you
can override any of them per-vault — to change the structure, the default sections, or the frontmatter
fields. You can also require extra frontmatter fields so cdno lint
keeps your notes consistent. This tutorial walks through both, hands-on.
What’s covered here is the shipped behaviour. Both kinds of config variable now resolve in custom templates: static
[variables]and interactive[variables.prompt](gathered from a TTY prompt or a--var name=valueflag).
Where templates live
Templates live in .cuaderno/templates/, one Markdown file per note type (e.g. project.md,
evidence.md). cdno resolves the effective template at creation time:
- a custom variant file —
<type>-<variant>.md(trackinguses this: the variant is the activity slug, e.g.tracking-gym.md), then - a custom type file —
<type>.md, then - the built-in variant default, then
- the built-in type default.
So a custom file in .cuaderno/templates/ always wins over the built-in. No activity-specific
variants ship built-in (tier 3 is empty today) — tracking variants are entirely yours to add;
see Tracking variants below.
cdno initwrites just one starter template —.cuaderno/templates/daily.md. Every other type uses its built-in default until you add a file for it. The quickest way to get an editable copy of a built-in iscdno templates eject <type>— e.g.cdno templates eject projectwrites.cuaderno/templates/project.mdmatching the built-in, ready to edit. You can also just create the file yourself, as the next section shows.
Customise a template
Say you want every project to start with a ## Risks section. Eject the built-in as a starting point:
cdno templates eject project # writes .cuaderno/templates/project.md
That writes the full built-in template. Insert a ## Risks section (you can reference any of the
{{placeholders}} that cdno templates vars project lists) and leave the rest as-is, so
.cuaderno/templates/project.md reads:
---
type: project
context: {{context}}
status: {{status}}
created: {{created}}
core_question: {{core_question}}
---
# {{title}}
## Current State
New project. No work done yet.
## Risks
## Next Actions
- [ ] Define first concrete step (light)
## Waiting On
(nothing yet)
## Milestones
- [ ] First milestone — target: TBD
## Links
- Portfolio: (none yet)
Now create a project:
cdno project create --title "Surrogate model" --context work
The new projects/surrogate-model.md follows your template — including the ## Risks section:
---
type: project
context: work
status: active
created: 2026-06-30
core_question: null
---
# Surrogate model
## Current State
New project. No work done yet.
## Risks
## Next Actions
- [ ] Define first concrete step (light)
## Waiting On
(nothing yet)
## Milestones
- [ ] First milestone — target: TBD
## Links
- Portfolio: (none yet)
cdno templates eject <type> is the recommended way to get an editable base — it always matches the
current built-in. (You could instead hand-write the file, or shape it from a note cdno already
created, but eject saves the guesswork.)
Editing a template only affects notes created afterwards — existing notes are untouched. (And
cdno normaliseonly reorders frontmatter keys; it won’t add a new section like## Risksto old notes.)
Tracking variants
tracking is the one type whose template is chosen per activity. cdno track <activity>
slugifies the activity and looks for .cuaderno/templates/tracking-<activity>.md, falling back to
the generic tracking template when there’s none. So a custom .cuaderno/templates/tracking-gym.md
gives cdno track gym a bespoke layout without touching cdno track swim, and
.cuaderno/templates/tracking.md overrides the generic fallback for everything else.
Only the neutral generic template ships built-in — no activity-specific variants are baked into
the product. Ready-made gym, body, and swim variants (exercise table, body-metrics table,
swim-set table) live in the repo under
examples/templates/tracking/;
copy one to .cuaderno/templates/tracking-<activity>.md to use it, or start your own from it.
Template variables
Templates use {{placeholder}} markers. cdno substitutes the values the note’s creation command
supplies. Two rules to know:
- An omitted optional value renders as
null(e.g.core_question: nullabove when you don’t pass--question). - An unknown placeholder is left verbatim —
{{nope}}stays as the literal text{{nope}}in the note. So a template should only use the placeholders its note type actually provides.
Each type provides these:
| Note type | Available {{placeholders}} |
|---|---|
daily | date, heading, weekday, day_name, week |
weekly | week, week_num, year, date_start, date_end |
monthly | month, month_name, year, date_start, date_end, weeks |
project | title, context, status, created, core_question |
action | title, slug, project, energy, status, created, due, completed, milestone, criteria, blocker, tags |
portfolio | question, project, created |
evidence | source, origin, portfolio, content, created |
stewardship | name, context |
tracking | stewardship, activity, activity_title, routine, content, date, date_long |
question | question, domain, created, updated |
commitment | title, context, status, due, project, stewardship, created, completed |
inbox | body, created |
You can use any subset, in any order, and add as much static Markdown around them as you like.
Daily specifics.
weekdayandday_nameare aliases for the same value — the weekday name (e.g.Sunday) — so use whichever reads better.weekis the ISO-week labelYYYY-Www(e.g.2026-W27), matching the weekly note for that date, so[[{{week}}]]in a daily template links straight to it.
Discover them from the CLI.
cdno templates vars <type>lists exactly this table for a type — the complete set its create path supplies — and folds in any[variables]/[variables.prompt]names your config adds, classified by source. For examplecdno templates vars tracking. See thetemplatesreference.
Static config variables
Beyond the per-type placeholders above, a custom template can reference vault-wide static
variables you define under [variables] in .cuaderno/config.toml. These resolve on every note
type. For example:
# .cuaderno/config.toml
[variables]
author = "A. Researcher"
institution = "University of Examples"
A custom template can then use {{author}} / {{institution}} and they’ll be substituted at
creation. Precedence: a per-type (contextual) placeholder of the same name always wins over a config
variable, so config vars only fill names the note type doesn’t already supply.
Prompted variables
A static variable is the same on every note. When you want a value that changes per note — a ticket
id, a collaborator, a meeting code — declare it under [variables.prompt], where the value is the
prompt message:
# .cuaderno/config.toml
[variables.prompt]
ticket = "Ticket reference?"
Reference it in a custom template like any other placeholder (e.g. ticket: {{ticket}} in the
project frontmatter). When you create a note whose effective template uses a prompted variable, cdno
gets the value one of three ways:
--var name=valueon the command (repeatable), e.g.cdno project create --title T --context work --var ticket=ABC-123;- otherwise, in an interactive TTY,
cdnoasks (“Ticket reference?”) and shows the value in the confirm preview before writing; - otherwise (non-interactive, no
--var) it errors rather than writing a note with a literal{{ticket}}:
Error: missing value for template variable 'ticket' (pass `--var ticket=value`, set a default under
[variables] in .cuaderno/config.toml, or run interactively in a TTY)
--var is available on every note-creating command: project create, question create,
stewardship create, commit create, portfolio create, file, track, action add --note, and
action promote.
A few rules worth knowing:
- A prompted name that also has a static
[variables]default is satisfied by that default — you’re not asked, and it won’t error. (The static default wins by precedence, so--varcan’t override it; remove the default if you want to be prompted.) - A
[variables.prompt]entry whose{{name}}your template doesn’t actually use is ignored. - The same precedence applies: a per-type placeholder of the same name wins over a prompted variable.
--varonly applies to templated notes.cdno file --attach(the attachment stub) and a plainaction add(no--note) aren’t templated, so--varis ignored there.- The implicit-write paths — daily (
log), weekly, and inbox (capture) notes — don’t gather prompted values, and neither do MCP-driven creations (there’s no--varover MCP). A[variables.prompt]placeholder in one of those templates fails at creation (anUnresolvedPromptserror) instead of being asked for; give it a static[variables]default instead.
Frontmatter field order and normalise
Your template also defines the canonical order of frontmatter keys for that type. Notes cdno
creates are already in that order; for hand-authored or migrated notes,
cdno normalise reorders their frontmatter to match the template
(--check reports without writing). So if you reorder the keys in project.md, a later
cdno normalise brings older project notes into line.
Require extra frontmatter fields
Beyond the built-in required fields, you can demand vault-specific ones per type with a
[schemas.<type>] section. For example, to require every project to name an owner, add to
.cuaderno/config.toml:
[schemas.project]
extra_required = ["owner"]
This is enforced by cdno lint, which now errors on any project
missing the field (a missing key, or one whose value is null, fails):
cdno lint
# [error] projects/surrogate-model.md: missing required field `owner` for note type `project`
# Error: found 1 error(s), 0 warning(s)
lint exits non-zero on errors, so this is a good gate to run in a pre-commit hook or CI over a
git-tracked vault.
Satisfy the requirement going forward
Add the field to your template so new notes carry it. Give it a non-null default you can edit per
note (an empty key — owner: — is YAML null and still fails the lint; use a placeholder value):
---
type: project
context: {{context}}
status: {{status}}
created: {{created}}
core_question: {{core_question}}
owner: unassigned
---
New projects are now born with owner: unassigned (edit it as needed) and pass the lint.
Existing notes aren’t changed retroactively — fix them by adding the field, then re-run
cdno lint until it’s clean.
Required fields are about presence, not value: any non-null value satisfies the check. Combine
extra_requiredwith a template default and the occasionalcdno lintand your vault stays uniform without any per-note ceremony.
Give a field a type
extra_required only checks that a key is present. When you want cdno lint to also check the
value — that meds is a boolean, mood is one of a fixed set, since is a real date — declare a
typed field instead, under [schemas.<type>.fields.<name>]:
[schemas.daily.fields.meds]
type = "bool"
default = false
[schemas.daily.fields.mood]
type = "string"
values = ["low", "ok", "good"]
Now a daily note whose meds: isn’t a boolean, or whose mood: isn’t one of the three allowed
values, gets a cdno lint warning. Typed fields are also recognised by the desktop Templates editor,
so a custom template referencing {{meds}} no longer warns that it “renders literally”.
A typed field’s default is populated at create, too: add meds: {{meds}} to your custom
daily.md and every new daily note is scaffolded with meds: false — the declared default — rather
than a literal {{meds}}. A field with no default renders null. (As with any placeholder, the
field only appears in a note if the template references it; and if the note’s create path already
supplies that name, or a [variables] static var does, that value wins over the default.) See
Typed schema fields in the configuration
reference for the full grammar and its limits.
Edit templates in the desktop app
Everything above works from the CLI, but the desktop app also has a Templates editor that does
the same job without a terminal — press ⌘, and pick Templates from the settings rail.
You get a chip for every note type — the built-ins plus any custom types you declared
under [note_types.<name>]. Selecting one names its source in the editor’s header:
- Built-in — the type is using its built-in default (no override yet).
- Custom — a custom override exists in
.cuaderno/templates/. - No template — a custom type that has no template file yet.
The chips themselves flag only the last two, since most types sit on their built-in default.
Select a type to see its effective template in the editor. Edit the text and press Save: the
app writes .cuaderno/templates/<type>.md. For a type currently on the built-in default, that first
save creates the custom override — the same edit-and-save model as cdno templates eject followed
by an edit, but in one step. A custom type showing No template offers Create, which
scaffolds a starter from the type’s declared required fields.
The side panel lists the placeholders you can use, grouped by where their value comes from —
supplied keys the create path fills, a custom type’s own schema fields, and any config
variables or prompted variables. If you type a {{token}} that isn’t in that set, the editor
shows a calm inline notice so you can catch a typo before it renders literally — but it never blocks
you from saving. An edit made outside the app (in your editor, or by another tool) refreshes the
view automatically.
See also
- Configuration — the configurable surface.
- Configuration reference — every
config.tomlkey. - Frontmatter fields — the built-in fields per note type.
normalise,lint.
Tracking people
Cuaderno’s built-in types don’t include a “person” — deliberately, since a person decomposes into the notes you already keep (daily logs, meetings, actions, commitments). But if you want to answer questions like:
- What was my last interaction with Jane?
- When did I ask Jane to do something — and what did she ask me?
…a custom note type plus a linking convention gets you there,
with no bespoke CRM. This recipe is the worked example; the files live in
examples/note-types/person/.
1. Declare the type
Add to .cuaderno/config.toml:
[note_types.person]
folder = "people"
That’s the minimum — a person’s identity is the note title. (Add required/optional fields if you
want enforced frontmatter; see the reference.)
Optionally copy the example person.md
to .cuaderno/templates/person.md for a note with a ## Log section. Without a template, Cuaderno
synthesises a minimal note.
2. Create people
cdno note create person --title "Jane Smith"
This writes people/jane-smith.md. cdno note list person enumerates them; cdno lint keeps them
honest.
3. Link people from your notes
The key habit: whenever a person shows up in a daily log, a meeting section, or an action, reference
them with a [[people/<slug>]] wikilink:
## Logs
- **14:30**: standup — [[people/jane-smith]] asked me to review the sparse-attention draft
Body wikilinks are indexed, so every mention becomes a backlink on the person’s note, and the raw text is full-text searchable.
4. Answer the questions
“What was my last interaction with Jane?” — the reliable answer is the top line of her person
note’s ## Log, which you keep most-recent-first. To scan across the vault, search her slug:
cdno search "people/jane-smith" --type daily --from 2026-06-01
Search ranks by relevance, not date, so bound the window with --from / --to and read the
dates on the hits (daily notes carry their date in the filename) rather than trusting the order.
Drop --type to include meetings and action notes.
“What did Jane ask me — and what did I ask her?” — this is direction, which lives in your
prose, not in structure. Write the log line so the direction is explicit (“Jane asked me…”, “I asked
Jane…”), and either keep a running ## Log in her person note or let search surface the lines. A
Claude skill can read the dated results and summarise who-owes-what; the structure gives it the
material, the prose gives it the direction.
Why a “person” type and not a built-in
A custom person type is schema-only — it gives you a folder, linting, search, and backlinks,
but no bespoke behaviour. That’s exactly right here: people don’t need caps, state machines, or
aggregation; they need to be findable and linkable. If you later want richer per-person structure,
add fields to the config declaration — no code change. See
Custom note types for the full schema.
CLI reference
cdno is the command-line interface to a Cuaderno vault. This section documents every command. For
the why behind them, see Concepts and Tutorials.
cdno [OPTIONS] <COMMAND>
Run cdno --help for the command list, or cdno <command> --help for any command’s flags.
Global options
These apply to every command:
| Flag | Effect |
|---|---|
--vault <PATH> | Operate on the vault at PATH. Overrides both discovery and CUADERNO_VAULT_PATH. |
--json | Emit machine-readable JSON instead of a formatted table (see below). |
--no-interactive | Never prompt; a missing required argument is an error. Implicit unless both stdin and stdout are TTYs. Also turns off interactive reports. |
--color <WHEN> | auto (default), always, or never. See Colour and interactivity. Spelled --colour too. |
-h, --help | Print help. |
-V, --version | Print the version (top level only). |
Finding the vault
When --vault is not given, cdno discovers the vault by walking up from the current directory
until it finds a .cuaderno/ folder. If that finds nothing, it falls back to the
CUADERNO_VAULT_PATH environment variable. Standing inside a vault always wins over the env var.
See Initialise a vault.
Interactive vs. scripted
Write commands follow one convention (the “flags-and-prompts” pattern):
- Interactive terminal, missing a required flag →
cdnoprompts for it, then asks you to confirm before writing. - Non-interactive (either stream not a terminal — piped, redirected,
< /dev/null, or running in CI, which is the usual reason neither is — or--no-interactive) → a missing required flag is an error. Supply every flag and the command runs unattended.
This means the same command serves a human at a prompt and a script with no changes.
Read commands add one thing on top. In a terminal, several listings offer to open one of the rows
they just printed — cdno project list asks which project you want to see, prints it, and asks
again until you press Esc. The listing itself is printed first either way, so piping, --no-interactive,
and --json all behave exactly as they did before. See
Colour and interactivity.
JSON output
--json makes any supported verb emit structured output:
- Read verbs (
commitments,questions,status,now,orient,search,open, and thelist/showverbs ofproject/portfolio/stewardship, plusaction list) emit their listing or detail object. - Write verbs (
log,capture,file,track, and the create/update verbs ofproject,action,portfolio,stewardship,question,commit) emit a{ "path": ..., "message": ... }result, and run non-interactively (so prompts can’t corrupt the JSON on a terminal). - Maintenance / interactive / bootstrap commands (
init,lint,reindex,normalise,triage,review,weekly,monthly) ignore--json.
The CLI’s JSON shapes match the MCP server DTOs. See JSON output for every shape.
The commands
| Command | What it does |
|---|---|
init | Create a new vault |
log | Append a line to today’s daily note |
capture | Drop a quick note into the inbox |
triage | Process inbox captures |
orient | Morning orientation |
status | Active projects + top actions |
now | What you are in the middle of |
open | Resolve a note reference to its path |
weekly | Show the weekly note |
monthly | Show the monthly note |
commitments | Aggregated deadlines |
questions | List active questions |
search | Full-text search over titles and bodies |
review | Guided weekly/monthly review |
project | Manage project maps |
action | Manage next actions |
portfolio | Manage evidence portfolios |
file | File evidence into a portfolio |
question | Manage question notes |
stewardship | Manage stewardships |
track | File a tracking entry |
commit | Manage standalone commitments |
lint | Validate the vault |
reindex | Rebuild the index |
normalise | Reorder frontmatter |
completions | Shell-completion scripts |
cdno init
Create a new vault: the folder tree, a default .cuaderno/config.toml, and a starter daily.md template (every other type uses an in-binary default until you eject one).
cdno init [OPTIONS] [PATH]
Arguments
| Argument | Description |
|---|---|
[PATH] | Target directory. Defaults to the current working directory. |
Options
Only the global options apply. init ignores --json.
Examples
# Create a vault in a new directory:
cdno init ~/notebook
# Initialise the current directory as a vault:
cdno init
It fails if the target already contains a .cuaderno/ directory (it won’t clobber an existing
vault).
See also
- Initialise a vault — what the tree contains and how discovery works.
- Vault structure.
cdno log
Append a log entry to today’s daily note (creating the note if it doesn’t exist yet).
cdno log [OPTIONS] <MESSAGE>
Arguments
| Argument | Description |
|---|---|
<MESSAGE> | The log message. Quote it if it contains spaces. |
Options
| Flag | Description |
|---|---|
--at <TIMESTAMP> | Override the timestamp. Accepts YYYY-MM-DDTHH:MM:SS or YYYY-MM-DDTHH:MM. Defaults to now. |
Plus the global options. With --json, emits a {path, message}
result.
Examples
cdno log "scaled the mesh to 2M cells; 4x runtime, still stable"
# Backdate an entry:
cdno log "forgot to record: fixed the sampler seed" --at 2026-04-24T18:30
# Scripted:
cdno log "nightly run complete" --json
# -> { "path": "journal/2026/daily/2026-04-25.md", "message": "Logged to ..." }
Daily notes are append-only — log only ever adds.
Related MCP tool
append_to_log — the same operation for AI clients.
See also
cdno capture
Drop a quick note into inbox/ with a slug-based filename, to be processed later with
triage.
cdno capture [OPTIONS] <TEXT>
Arguments
| Argument | Description |
|---|---|
<TEXT> | The note text. Quote it if it contains spaces. |
Options
Only the global options. With --json, emits a {path, message}
result.
Examples
cdno capture "does Chen 2025 use the same preconditioner?"
cdno capture "ask IT about the cluster quota" --json
Capture is meant to be frictionless — no fields, no decisions. Classify later during triage.
Related MCP tool
See also
- Inbox and triage.
triage— process what you’ve captured.
cdno triage
Process uncategorised inbox/ captures. For each one, keep it as a project action, discard it, or
skip it.
cdno triage [OPTIONS]
Options
Only the global options. triage ignores --json.
Behaviour
- Interactive — walks each pending capture and prompts you to keep (turn into a project action), discard, or skip.
- Non-interactive (piped or
--no-interactive) — just lists what’s pending, without changing anything.
Examples
# Work through the inbox:
cdno triage
# Just see the backlog (no changes):
cdno triage --no-interactive
Related MCP tools
triage_inbox (lists pending items) and
discard_inbox_item (clears one).
See also
cdno orient
Daily orientation: the commitments due soon, your active projects, a single suggested starting point, and any stewardship habits that have lapsed.
A habit counts as lapsed when its line in a stewardship dashboard’s ## Active Habits section
declares it so — a status starting with “lapsed” after the em-dash, e.g.
- Swimming 1x/week — lapsed since March. The dashboard is the source of truth (updated
during reviews); orientation only surfaces what it says, without judgement.
cdno orient [OPTIONS]
In a terminal this then offers to open one of the projects it just listed, printing what cdno project show would and asking again until you press Esc. Piped output, --no-interactive, and
--json skip the prompt. See Colour and interactivity.
Options
| Flag | Description |
|---|---|
--energy <ENERGY> | Bias the suggested starting point toward this energy level: deep, medium, or light. |
Plus the global options. With --json, emits the orientation as a
structured object.
Examples
cdno orient # neutral suggestion
cdno orient --energy deep # bias toward a heavy-focus action
cdno orient --energy light # bias toward something quick on a low day
cdno orient --json | jq '.suggested_start'
Related MCP tool
See also
- The daily loop.
status— projects only, without the commitments digest.
cdno status
A quick snapshot: your active projects and each one’s top next action. Lighter than
orient — no commitments digest, no suggestion.
cdno status [OPTIONS]
In a terminal this then offers to open one of the projects it just listed, printing what cdno project show would and asking again until you press Esc. Piped output, --no-interactive, and
--json skip the prompt. See Colour and interactivity.
Options
Only the global options. With --json, emits the snapshot as
structured data.
Examples
cdno status
cdno status --json | jq '.[].slug'
Related MCP tool
list_projects — the projects view for AI clients.
See also
orient— the fuller morning view.- Managing projects.
cdno now
What you are in the middle of: the most recent action started and not yet closed.
cdno now [OPTIONS]
There is no state behind this. It replays today’s ## Logs, so a start made from
cdno action start, from an agent over MCP, or typed into the daily
note by hand all count — and a completion or a drop clears it. Nothing to keep in sync, and
nothing to go stale.
One verb breaks the pairing: cdno action promote rewrites the
bullet it matched, so promoting between a start and its close leaves cdno now naming the old text
for the rest of the day, and action complete and action drop then match nothing.
A line you type yourself has to match the shape the writers emit:
- **09:30**: started [[surrogate-model]] — Draft the methods section (deep)
Both halves are required. The - **HH:MM**: stamp is what makes the line a log entry at all, and
the separator is an em dash (U+2014), not a hyphen. The parser requires that exact codepoint, so
that ordinary prose beginning “started something” is never mistaken for a focus. That strictness
means a line missing the stamp, or typed with -, is simply not picked up here — but
cdno lint reports it, naming the line and the likely cause, so a near-miss does not stay
invisible.
$ cdno now
surrogate-model since 09:30 · 1h 30m
Draft the methods section (deep)
With nothing open it says so rather than printing an empty frame:
$ cdno now
Nothing started yet. `cdno orient` suggests one thing to begin.
--json emits {project, action, started}, all three null when nothing is open — so a caller can
test one field without first branching on the shape of the document.
The action field is the bullet text exactly as logged, energy suffix and all. That is the same
string cdno action complete matches, which is why the pairing
works.
Related MCP tools
current_focus — the same read, for an agent.
See also
action start— what puts something here.orient— what to begin when nothing is open.
cdno open
Open a note in your editor. Where cdno search answers “where did I write about
this”, open answers “take me to the note I mean” — it is addressing, not searching.
cdno open [OPTIONS] [REFERENCE]
Run it with no reference and it offers a picker over every note, most-recently-edited first.
Arguments
| Argument | Description |
|---|---|
[REFERENCE] | The note to resolve. Omit it and pass --list to see every note. Tab-completion offers your vault’s slugs. |
A reference can be any of:
| Form | Example | Resolves to |
|---|---|---|
| Bare slug | surrogate-model | the note with that slug, whatever its type |
| Type-scoped slug | project:surrogate-model | that slug within one note type |
| Calendar word | today, yesterday, tomorrow | the daily note for that day |
| Date | 2026-08-21 | that day’s daily note |
| ISO week | 2026-W34 | that week’s weekly note |
| Month | 2026-08 | that month’s monthly note |
| Vault-relative path | journal/2026/daily/2026-08-21.md | that file |
| Absolute path inside the vault | /home/you/vault/projects/foo.md | that file |
Portfolios and expanded stewardships are addressed by their folder name, not the literal
_index: cdno open surrogate-model reaches portfolios/surrogate-model/_index.md.
The calendar words always mean the journal. A note genuinely named today stays reachable as
<type>:today.
Options
| Flag | Description |
|---|---|
--path | Print the note’s absolute path instead of opening it. |
--list | Print every note as path<TAB>title<TAB>type, for piping to a fuzzy finder. Opens nothing and takes no reference. |
--editor <COMMAND> | Editor command template for this invocation. Outranks $CUADERNO_EDITOR, $VISUAL, and $EDITOR. |
Plus the global options. With --json, a resolved reference emits
{"path": …} and --list emits an array of {path, title, type}.
When stdout is not a terminal, cdno open prints the path instead of launching anything. So
cdno open today | cat, a script, --no-interactive, and --json all behave the same way, and no
editor is ever started into a pipe.
Choosing the editor
First one set wins:
--editor <COMMAND> | this invocation only |
$CUADERNO_EDITOR | your shell |
$VISUAL, then $EDITOR | your shell |
| (nothing set) | the operating system’s default handler for .md |
export CUADERNO_EDITOR='code -g {path}'
{path} marks where the note’s path goes. Leave it out and the path is appended, so a bare
nvim works. Quoting is honoured, so a program name may contain spaces:
export CUADERNO_EDITOR='"/Applications/Sublime Text.app/Contents/SharedSupport/bin/subl" -w {path}'
A value whose first word contains :// is handed to the operating system instead of executed,
with the path percent-encoded — obsidian://open?path={path}.
cdno waits for the editor to exit. It does not try to guess whether your editor is a terminal
or a GUI one — code -w blocks and code does not, and a guess would be wrong in a way you
could not override. Waiting is correct for every terminal editor and harmless for a GUI one that
returns immediately. If the editor exits non-zero, cdno open exits with the same code, the way
git commit treats an abandoned edit.
There is no per-vault editor setting, on purpose
You might expect .cuaderno/config.toml to carry this — a research vault that opens in Obsidian,
a code vault that opens in your editor. It deliberately does not.
A vault is a git repository, and --vault exists so cdno can be pointed at one you did not
create. A setting that names a program to run cannot live in data that gets cloned and synced:
opening a note in someone else’s vault would run their choice of program on your machine. It is
the same reason git does not honour every setting from a repository you just cloned.
Restricting such a setting to a bare binary name would not help. sh is a binary name, and
sh <the note> executes the note’s own contents — which, in a vault you cloned, the author also
wrote. The fix is that the setting comes from your shell rather than from the data.
If you work across vaults that want different editors, set CUADERNO_EDITOR in a per-directory
shell hook (direnv, or your shell’s chpwd), which keeps the decision on your machine.
Archived notes
Opening a note under actions/_done/ prints a warning first: its text was frozen when it was
archived, and cdno lint reports an edit to the existing text as an error. Appending
to it is fine. The warning does not stop you — markdown remains the source of truth.
When a reference does not resolve
Two cases, and they say different things:
- Nothing matched. The error names the closest few notes by their type-scoped form — so a mistyped slug tells you what you probably meant rather than dumping the vault.
- The slug matched more than one note.
cdno opennever guesses, because opening the wrong note is a mistake you only discover after typing into it. The error names each type-scoped form instead, and any of them resolves. This happens when a stewardship and a portfolio share a name, for instance —stewardships/gym.mdandportfolios/gym/_index.mdare bothgym.
A reference that looks like a path never falls back to fuzzy matching: a typo there means “no such file”, not a near-miss opened on your behalf.
Examples
cdno open # pick from every note
cdno open today # today's daily note
cdno open surrogate-model # by slug
cdno open project:surrogate-model # when one slug is used by two types
cdno open 2026-W34 # that week's weekly note
cdno open --path today # print the path, open nothing
cdno open --list | head # every note, tab-separated
cdno open today --json | jq -r .path
cdno open notes --editor 'code -g {path}'
With a fuzzy finder
cdno open has its own picker, so you do not need fzf at all. If you would rather use yours —
your keybindings, your preview window — --list gives it candidates. Because --list emits
vault-relative paths and open accepts absolute ones, the round-trip works from any directory,
with no need to cd into the vault first:
cdno open "$(cdno open --list | fzf --with-nth=2.. --delimiter='\t' | cut -f1)"
Worth a shell function:
cdo() {
local pick
pick=$(cdno open --list | fzf --with-nth=2.. --delimiter='\t' | cut -f1) || return
[ -n "$pick" ] && cdno open "$pick"
}
This beats running fzf over the vault directory directly, because the candidates carry each
note’s title rather than only its filename — filenames here are slugs, so a note titled
“Surrogate model” would otherwise never match the words you remember. The listing also respects
the vault’s ignore globs.
See also
search— full-text search when you do not know which note you want.- Searching your vault.
cdno weekly
Show the weekly review/plan note: Wins, Challenges, One Improvement, and This Week’s Goal.
cdno weekly [OPTIONS]
Options
| Flag | Description |
|---|---|
--date <DATE> | Any day in the target ISO week (YYYY-MM-DD). Defaults to this week. |
Plus the global options. weekly ignores --json.
Examples
cdno weekly # this ISO week
cdno weekly --date 2026-04-20 # the week containing 20 Apr 2026
To write the weekly sections rather than just view them, use the guided
review weekly.
Related MCP tools
read_weekly_note and get_weekly_context.
See also
cdno monthly
Show the monthly review note: Wins, Themes, Next Month’s Focus, and the month’s linked weeks.
cdno monthly [OPTIONS]
Options
| Flag | Description |
|---|---|
--date <DATE> | Any day in the target calendar month (YYYY-MM-DD). Defaults to this month. |
Plus the global options. monthly ignores --json.
Examples
cdno monthly # this calendar month
cdno monthly --date 2026-04-20 # the month containing 20 Apr 2026
To write the monthly sections rather than just view them, use the guided
review monthly.
Related MCP tools
read_monthly_note and get_monthly_context.
See also
cdno commitments
List aggregated commitments across the vault — project hard milestones, standalone commitment notes, stewardship periodic commitments, and self-imposed action-note deadlines — sorted by date, with overdue items flagged.
cdno commitments [OPTIONS]
Options
| Flag | Description |
|---|---|
--weeks <WEEKS> | Lookahead window, in weeks. Default 2. A standing 30-day overdue look-back always applies on top. |
Plus the global options. With --json, emits the list as structured
data.
Examples
cdno commitments # next 2 weeks + anything overdue
cdno commitments --weeks 6 # next 6 weeks
cdno commitments --json | jq '.[] | select(.overdue)'
How it’s computed
This is a derived view, not a single file. It merges four sources (see
Business rules):
project milestones marked --hard, stewardship periodic commitments, standalone
commit notes, and action notes with a self-imposed due:.
Related MCP tool
See also
- Commitments and deadlines.
commit— create a standalone commitment.
cdno questions
List active questions grouped by domain (research, life) — the orientation surface against the
question system. For lifecycle changes (park / answer / retire / activate), use
cdno question.
cdno questions [OPTIONS]
Options
Only the global options. With --json, emits the list as structured
data.
Examples
cdno questions
cdno questions --json | jq '.[].slug'
Related MCP tool
get_active_questions — which additionally accepts a domain filter.
See also
- Research and evidence.
question— create questions and transition their status.
cdno search
Full-text search across all notes, ranked best-first, with optional filters by note type, date window, and portfolio.
Titles are searched too, and weighted ten times a body match. A note whose title contains
your words ranks above one that merely mentions them, so you do not need a separate command to
look one up by name. When you already know which note you want and can name its slug or date,
cdno open resolves it directly instead of ranking.
cdno search [OPTIONS] <QUERY>
Arguments
| Argument | Description |
|---|---|
<QUERY> | Search text. Matched case-insensitively; terms are ANDed. Quotes and operators are treated as literal words. |
Options
| Flag | Description |
|---|---|
--type <TYPE> | Restrict to one note type (e.g. daily, project, evidence, or a config-defined custom type). A name that is neither built-in nor a registered custom type errors with the valid set; tab-completion offers your vault’s types. |
--from <FROM> | Inclusive earliest note date (YYYY-MM-DD). |
--to <TO> | Inclusive latest note date (YYYY-MM-DD). |
--portfolio <PORTFOLIO> | Restrict to notes in this portfolio. |
--limit <LIMIT> | Maximum results. Default 20. |
Plus the global options. With --json, emits an array of hits, each
with path, note_type, title, snippet, and score.
Examples
cdno search "preconditioner"
cdno search "sparse attention" --type evidence
cdno search "mesh" --from 2026-03-01 --to 2026-03-31 --limit 5
cdno search "speedup" --portfolio sparse-vs-dense-attention-ood --json | jq -r '.[].path'
Related MCP tool
See also
- Searching your vault.
reindex— rebuild the index if results look stale.
cdno review
Guided review rituals.
cdno review [OPTIONS] <COMMAND>
Subcommands
| Subcommand | Description |
|---|---|
weekly | Walk the retrospective sections into this week’s note and set next week’s goal. |
monthly | Walk the monthly sections into this month’s note. |
review ignores --json.
cdno review weekly
Walk the retrospective sections (Wins, Challenges, One Improvement) into this week’s note, then set next week’s goal as the This Week’s Goal of next week’s note. When run non-interactively, it reads the current note instead of prompting.
cdno review weekly [OPTIONS]
Options
Only the global options.
Examples
cdno review weekly # interactive: prompts for each section, sets next week's goal
cdno review weekly --no-interactive # read the current weekly note without prompting
cdno review monthly
Walk the monthly sections (Wins, Themes, Next Month’s Focus) into this month’s note. Unlike the weekly ritual there is no cross-note carry-forward — every section lands in the same month’s note. When run non-interactively, it reads the current note instead of prompting.
cdno review monthly [OPTIONS]
Options
Only the global options.
Examples
cdno review monthly # interactive: prompts for each section
cdno review monthly --no-interactive # read the current monthly note without prompting
Related MCP tools
get_weekly_context, read_weekly_note,
upsert_weekly_section, read_monthly_note,
upsert_monthly_section.
See also
- Weekly review.
- Note types.
weekly/monthly— just view the notes.
cdno project
Manage project maps: create, update state, set the core question, add/complete/drop milestones, manage
waiting-on items, park/activate, and list/show. Next actions have their own verb, cdno action.
cdno project [OPTIONS] <COMMAND>
Subcommands
| Subcommand | Description |
|---|---|
create | Create a new project map |
state | Update the Current State (auto-logs the previous) |
core-question | Set or clear the core question (auto-logs the previous) |
park | Move a project to _parked/ |
activate | Bring a parked project back (enforces the cap) |
list | List active projects |
show | Show one project |
milestone | Add / complete / drop milestones |
waiting | Add / resolve waiting-on items |
Write subcommands honour --json (a {path, message} result, run non-interactively); list/show
emit their data under --json.
cdno project create
Create a new project map. Created parked if you’re already at the active cap.
| Flag | Description |
|---|---|
--title <TITLE> | Project title (the slug derives from it). |
--context <CONTEXT> | Life domain: work, side-project, university, family, household, legal, personal. |
--question <QUESTION> | Vault-relative core-question wikilink target (e.g. questions/research/foo). Optional. |
--var <NAME=VALUE> | Value for a custom template’s prompted variable ([variables.prompt]). Repeatable. See Prompted variables. |
cdno project create --title "Surrogate model" --context work
cdno project create --title "Thesis" --context university --var ticket=ABC-123
cdno project create --title "Thesis" --context university --question questions/research/surrogate-cost
cdno project state
Update the Current State section. The previous state is auto-logged to today’s daily note first (see Business rules).
| Flag | Description |
|---|---|
--slug <SLUG> | Project slug. |
--text <TEXT> | The new state text. |
cdno project state --slug surrogate-model --text "Mesh scaling works; assembly is the bottleneck"
cdno project core-question
Set or clear the project’s core question after creation, auto-logging the previous value to today’s
daily note in the same was: / now: shape state uses.
core-question — --slug, and either --question <target> or --clear
cdno project core-question --slug surrogate-model --question questions/research/does-it-scale
cdno project core-question --slug surrogate-model --clear
--question takes the bare wikilink target, the same form
create --question takes — questions/research/foo, not [[…]], which is
rejected rather than double-wrapped.
Passing neither flag non-interactively is an error, not a silent detach: dropping a project’s question is a decision and has to be asked for.
cdno project park
Move an active project to projects/_parked/, freeing a slot against the five-project cap.
| Flag | Description |
|---|---|
--slug <SLUG> | Project slug. |
cdno project park --slug surrogate-model
cdno project activate
Bring a parked project back. Fails if it would exceed the active cap — park another first.
| Flag | Description |
|---|---|
--slug <SLUG> | Parked project slug. |
cdno project activate --slug surrogate-model
cdno project list
List active projects with a state snippet. Each project renders as a card — a coloured bar keyed to
its context, the slug as a title, and the state wrapped underneath. Honours --json.
In a terminal this then offers to open one of the projects it just listed, printing the same thing
cdno project show would and asking again until you press Esc. Piped output, --no-interactive, and
--json skip the prompt. See Colour and interactivity.
cdno project list
cdno project list --json | jq '.[].slug'
cdno project list --no-interactive # listing only, never a prompt
cdno project show
Show a compact summary of a single project (any status). The slug is an optional positional: omit it
in a terminal and cdno offers a picker covering active and parked projects. Honours --json
(emits the project summary object).
cdno project show surrogate-model
cdno project show # pick from a list
cdno project show surrogate-model --json
cdno project milestone
Manage milestones — markers of progress. A --hard milestone is a real deadline counted in
cdno commitments.
add — --slug, --title, --date <YYYY-MM-DD> (optional), --hard
done — --slug, --query (case-insensitive substring of the milestone title)
drop — --slug, --query, --reason (optional)
cdno project milestone add --slug surrogate-model --title "Submit to ICML" --date 2026-01-22 --hard
cdno project milestone add --slug surrogate-model --title "All Round-1 replies received"
cdno project milestone done --slug surrogate-model --query "submit to icml"
cdno project milestone drop --slug surrogate-model --query "book the venue" --reason "the funder withdrew"
Use drop rather than done when the milestone is not going to happen — superseded, mis-typed, or
overtaken by events. done ticks the bullet and writes milestone done on ... to the daily log,
asserting a milestone that was met; drop removes the bullet and writes milestone dropped on ...
instead, so a later reader can tell a plan that changed from a plan that was kept. --reason is
optional and never prompted for: a correction is simply a drop with no reason. Only open - [ ]
bullets are matched — a completed milestone is a record of what happened, not a plan to revise.
--date is optional. Some milestones are gated by a condition rather than a date — “all Round-1
replies received” on a correspondence-driven project — and omitting the flag records the milestone
as target: TBD rather than making you invent an estimate. An undated milestone does not appear
in cdno commitments, which is the point: a date you made up reads later like a
commitment somebody made. It completes with done exactly like a dated one.
Interactively, the calendar is offered behind a yes/no so the undated case is reachable without knowing the flag can be omitted.
--hard requires --date: a hard deadline with no date is rejected rather than quietly downgraded
to a soft target.
cdno project waiting
Track external blockers.
add — --slug, --description
resolve — --slug, --query (substring of the item)
cdno project waiting add --slug surrogate-model --description "Cluster quota from IT"
cdno project waiting resolve --slug surrogate-model --query "cluster quota"
Related MCP tools
create_project, update_project_state,
park_project,
activate_project, list_projects,
get_project_context, add_milestone,
complete_milestone, set_core_question,
add_waiting_on,
resolve_waiting_on.
See also
- Managing projects.
action— the next-action list.
cdno action
Manage a project’s next actions: add (optionally as a manifest note), promote a bullet to a note, complete, drop, and list.
cdno action [OPTIONS] <COMMAND>
Subcommands
| Subcommand | Description |
|---|---|
add | Append a next action to a project |
start | Log that work on an action is starting |
promote | Promote a plain bullet to a wikilinked manifest note |
complete | Mark an action done by substring match |
drop | Close an action without recording it as done |
list | List a project’s open actions |
Write subcommands honour --json ({path, message}, non-interactive); list emits its data under
--json.
cdno action add
Append a next action to a project. --note also scaffolds a manifest note and wikilinks the bullet.
| Flag | Description |
|---|---|
--project <SLUG> | Project slug. |
--title <TITLE> | Action title. |
--energy <ENERGY> | deep, medium, or light. |
--note | Also create a manifest note alongside the bullet and wikilink it. |
--var <NAME=VALUE> | Value for a custom action-note template’s prompted variable ([variables.prompt]). Repeatable. Only applies with --note (a plain bullet isn’t templated). See Prompted variables. |
cdno action add --project surrogate-model --title "Profile the assembly step" --energy medium
cdno action add --project surrogate-model --title "Characterise sample efficiency" --energy deep --note
cdno action promote
Promote an existing plain bullet to a wikilinked manifest note. Substring-matches the bullet text; energy is inherited.
| Flag | Description |
|---|---|
--project <SLUG> | Project slug. |
--query <QUERY> | Case-insensitive substring of the bullet text. |
--var <NAME=VALUE> | Value for a custom action-note template’s prompted variable ([variables.prompt]). Repeatable. Promotion scaffolds an action note, so it gathers the same prompts as add --note. See Prompted variables. |
cdno action promote --project surrogate-model --query "profile the assembly"
cdno action start
Log that work on an action is starting: writes started [[slug]] — <bullet> to today’s daily note,
which is what cdno now reads back.
The action must already be on the map. A start names a bullet, so that the later completion logs
matching text and the focus clears — a start naming nothing could never be closed. What gets logged
is the resolved bullet text, not your query, so --query "draft methods" logs
Draft methods (deep).
cdno action promote is the one thing that breaks the pairing: it
rewrites the bullet it matched, so promoting between a start and its close strands the focus for
the rest of the day — cdno now keeps naming the old text and both complete and drop
then match nothing. Close the action before promoting it, or re-run the start afterwards.
| Flag | Description |
|---|---|
--project <SLUG> | Project slug. |
--query <QUERY> | Substring of an existing bullet. Conflicts with --unplanned. |
--unplanned | Start work that is on no map yet: adds the bullet, then starts it. |
--title <TEXT> | Title for the new bullet. Requires --unplanned. |
--energy <LEVEL> | deep, medium or light. Requires --unplanned. |
An ambiguous --query is a question rather than a dead end: in a terminal you get a picker over the
candidates, and non-interactively they are listed one per line. The same holds for
complete, drop and
promote, which resolve through the same matcher.
One case no picker can settle: two open bullets whose text differs only in case, or not at all. The domain’s whole-bullet tiebreak compares case-insensitively, so it sees two exact matches, declines, and picking either re-ambiguates. Edit one of the bullets to tell them apart.
# start something already planned
cdno action start --project surrogate-model --query "feature set B"
# start something that was never planned — adds the bullet and starts it
cdno action start --project surrogate-model --unplanned \
--title "Fix the CI badge" --energy light
--unplanned is deliberately explicit rather than a fallback when --query matches nothing: a
fallback would turn every typo into a new action, silently. --title and --energy require it, so
passing them alone is a parse error naming --unplanned rather than a start on some other bullet.
cdno action complete
Mark a next action completed by case-insensitive substring match. A wikilinked bullet also archives
its note to actions/_done/<year>/.
| Flag | Description |
|---|---|
--project <SLUG> | Project slug. |
--query <QUERY> | Substring of the bullet text. |
cdno action complete --project surrogate-model --query "feature set B"
cdno action drop
Close a next action without recording it as done — for work that was superseded, abandoned or
reprioritised. Matches the bullet exactly as complete does; a wikilinked bullet also archives its
note to actions/_done/<year>/, stamped status: dropped with no completion date.
| Flag | Description |
|---|---|
--project <SLUG> | Project slug. |
--query <QUERY> | Substring of the bullet text. |
--reason <TEXT> | Optional: why it was dropped. |
cdno action drop --project surrogate-model --query "demo proposal" \
--reason "superseded by the demo-planning action"
Use this rather than complete whenever the work was not actually performed. complete writes
action done on [[…]] into the daily log, which is the record the weekly review, the monthly scan
and every later verdict read back from — so completing something that never happened leaves the
vault asserting work nobody did, and the only repair is a correction line written by hand.
--reason is optional but worth giving: “superseded by X” and “no longer wanted” are different
facts, and only one of them tells a later reader to go looking for the replacement. It is recorded
on a continuation line under the log entry.
A dropped action does not appear in the completed-actions views that the weekly and monthly reviews build, because it carries no completion date.
cdno action list
List a project’s open action bullets, with attached-note status (active / blocked / completed / dropped) inline
when present. Honours --json.
| Flag | Description |
|---|---|
--project <SLUG> | Project slug. |
cdno action list --project surrogate-model
cdno action list --project surrogate-model --json
Related MCP tools
add_action, promote_action,
start_action, start_unplanned_action,
complete_action, drop_action. (Open actions are also visible via
get_project_context; what is currently started via
current_focus.)
See also
cdno portfolio
Manage evidence portfolios: create, list, show, and link to a question or project. Filing evidence
into a portfolio is the separate cdno file verb.
cdno portfolio [OPTIONS] <COMMAND>
Subcommands
| Subcommand | Description |
|---|---|
create | Create a portfolio under portfolios/<slug>/ |
list | List portfolios with evidence counts + staleness |
show | Show a portfolio’s frontmatter + evidence inventory |
link | Link an existing portfolio to a question or project |
create/link honour --json ({path, message}); list/show emit their data under --json.
cdno portfolio create
Create a new portfolio. The slug derives from the question.
| Flag | Description |
|---|---|
--question <QUESTION> | The question this dossier accumulates evidence for. |
--project <PROJECT> | Optional wikilink to a project to associate it with. |
--var <NAME=VALUE> | Value for a custom template’s prompted variable ([variables.prompt]). Repeatable. See Prompted variables. |
cdno portfolio create --question "Sparse vs dense attention OOD"
cdno portfolio create --question "Sparse vs dense attention OOD" --project projects/surrogate-model
cdno portfolio list
List every portfolio with its evidence count and staleness. Honours --json.
In a terminal this then offers to open one of the portfolios it just listed, printing what cdno portfolio show would and asking again until you press Esc. Piped output, --no-interactive, and
--json skip the prompt. See Colour and interactivity.
cdno portfolio list
cdno portfolio list --json | jq '.[] | {slug, evidence_count}'
cdno portfolio show
Show a portfolio’s frontmatter and its evidence inventory. Honours --json (a detail object with the
evidence list).
| Flag | Description |
|---|---|
--portfolio <PORTFOLIO> | Portfolio slug. |
cdno portfolio show --portfolio sparse-vs-dense-attention-ood
cdno portfolio show --portfolio sparse-vs-dense-attention-ood --json
cdno portfolio link
Link an existing portfolio to an existing question or project (the retrofit path — pass exactly
one of --question/--project). Backlinks are set on both sides.
| Flag | Description |
|---|---|
--portfolio <PORTFOLIO> | Portfolio slug. |
--question <QUESTION> | Question to link (mutually exclusive with --project). |
--project <PROJECT> | Project wikilink to link (mutually exclusive with --question). |
cdno portfolio link --portfolio sparse-vs-dense-attention-ood --project projects/surrogate-model
cdno portfolio link --portfolio sparse-vs-dense-attention-ood --question questions/research/surrogate-cost
Related MCP tools
create_portfolio, get_portfolio_contents
(show), link_portfolio_to_question,
link_portfolio_to_project.
See also
- Research and evidence.
file— add evidence to a portfolio.
cdno file
File a piece of evidence into a portfolio. Without --attach it writes a plain Markdown evidence
note; with --attach it copies a non-Markdown artefact (PDF, image, video) into the portfolio and
scaffolds a linked evidence stub beside it.
cdno file [OPTIONS]
Options
| Flag | Description |
|---|---|
--portfolio <PORTFOLIO> | Portfolio slug. (Prompted/fuzzy-picked if omitted interactively.) |
--source <SOURCE> | Citation, experiment id, conversation reference, … |
--origin <ORIGIN> | Bare wikilink target to whatever produced this evidence (e.g. projects/foo); the CLI wraps it into [[...]]. |
--content <CONTENT> | Inline body. For a plain note it’s the content; with --attach it’s the abstract. Optional; defaults to empty. |
--attach <ATTACH> | Path to a non-Markdown artefact. Copied into portfolios/<slug>/<evidence-slug>/ with a stub that links to it. |
--move | With --attach, remove the source file after a successful copy (move instead of copy). |
--var <NAME=VALUE> | Value for a custom evidence template’s prompted variable ([variables.prompt]). Repeatable. Ignored with --attach (attachment stubs aren’t templated). See Prompted variables. |
Plus the global options. With --json, emits a {path, message}
result and runs non-interactively.
Examples
# A plain prose evidence note:
cdno file --portfolio sparse-vs-dense-attention-ood \
--source "Chen et al. 2025, NeurIPS" \
--origin projects/surrogate-model \
--content "4x speedup at 95% accuracy on the OOD split."
# Attach a PDF (copied into the portfolio; --content is the abstract):
cdno file --portfolio sparse-vs-dense-attention-ood \
--source "Chen et al. 2025" --origin projects/surrogate-model \
--attach ~/Downloads/chen2025.pdf --content "Key result: 4x speedup."
# Move the artefact in instead of copying:
cdno file --portfolio sparse-vs-dense-attention-ood --source "fig 3" \
--origin projects/surrogate-model --attach ./fig3.png --move
Evidence notes are append-only.
Related MCP tool
See also
- Research and evidence.
portfolio— create and inspect portfolios.
cdno question
Manage question notes: create one, then transition its status. Each status transition is logged to
today’s daily note. To list active questions, use cdno questions.
cdno question [OPTIONS] <COMMAND>
Subcommands
| Subcommand | Description |
|---|---|
create | Create a new question note |
park | Set status to parked |
answer | Set status to answered |
retire | Set status to retired |
activate | Set status to active |
All honour --json ({path, message}, non-interactive).
cdno question create
Create a new question under questions/<domain>/<slug>.md. The slug derives from the text.
| Flag | Description |
|---|---|
--domain <DOMAIN> | research or life. |
--text <TEXT> | The question text (becomes the body H1). |
--var <NAME=VALUE> | Value for a custom template’s prompted variable ([variables.prompt]). Repeatable. See Prompted variables. |
cdno question create --domain research --text "Does sparse attention beat dense OOD?"
Status transitions
park, answer, retire, and activate each take a --slug. The interactive picker offers only
eligible questions for that transition (e.g. you can only park an active question).
| Flag | Description |
|---|---|
--slug <SLUG> | Question slug. |
cdno question park --slug does-sparse-attention-beat-dense-ood
cdno question answer --slug does-sparse-attention-beat-dense-ood
cdno question retire --slug does-sparse-attention-beat-dense-ood
cdno question activate --slug does-sparse-attention-beat-dense-ood
Related MCP tools
create_question,
set_question_status. (List via
get_active_questions.)
See also
- Research and evidence.
questions— list active questions.
cdno stewardship
Manage stewardship dashboards: create (flat or expanded), list, show, and append a periodic
commitment line. Filing a tracking entry is the separate cdno track verb.
cdno stewardship [OPTIONS] <COMMAND>
Subcommands
| Subcommand | Description |
|---|---|
create | Create a stewardship (flat, or expanded with --tracking) |
list | List stewardships with variant, tracking count, staleness |
show | Show a stewardship’s frontmatter + dashboard excerpt |
add-periodic | Append a periodic commitment line |
complete-periodic | Complete one occurrence, rolling next: forward |
create/add-periodic/complete-periodic honour --json ({path, message}); list/show emit their data under
--json.
cdno stewardship create
Create a stewardship dashboard. --tracking makes it expanded (a stewardships/<slug>/ folder
with room for tracking/ and routines/); without it, the dashboard is a single flat file.
| Flag | Description |
|---|---|
--name <NAME> | Human-readable name (the slug derives from it). |
--context <CONTEXT> | Life domain (work, household, personal, …). |
--tracking | Create the expanded variant with a tracking/ folder. |
--var <NAME=VALUE> | Value for a custom template’s prompted variable ([variables.prompt]). Repeatable. See Prompted variables. |
cdno stewardship create --name "Finances" --context household # flat
cdno stewardship create --name "Health" --context personal --tracking # expanded
cdno stewardship list
List every stewardship with its variant, tracking count, and staleness badge. Honours --json.
In a terminal this then offers to open one of the stewardships it just listed, printing what cdno stewardship show would and asking again until you press Esc. Piped output, --no-interactive, and
--json skip the prompt. See Colour and interactivity.
cdno stewardship list
cdno stewardship list --json | jq '.[] | {slug, variant}'
cdno stewardship show
Show a stewardship’s frontmatter and an excerpt of the dashboard body. Honours --json (a detail
object including variant and body_markdown).
| Flag | Description |
|---|---|
--slug <SLUG> | Stewardship slug. |
cdno stewardship show --slug health
cdno stewardship add-periodic
Append a periodic commitment line to the dashboard’s ## Periodic Commitments section. The line
becomes a row in the aggregated cdno commitments view.
| Flag | Description |
|---|---|
--stewardship <SLUG> | Stewardship slug. |
--title <TITLE> | Commitment title (e.g. “Dental check-up”). |
--every <RECURRENCE> | Recurrence: daily, weekly, monthly, yearly, or every N months. See Recurrence syntax. |
--next <YYYY-MM-DD> | Next due date. |
cdno stewardship add-periodic --stewardship health --title "Dental check-up" \
--every "every 6 months" --next 2026-09-01
cdno stewardship complete-periodic
Complete one occurrence of a periodic commitment, rolling its next: date forward by that line’s
own recurrence. Named to pair with add-periodic: it completes an occurrence, not the
stewardship, which is perpetual and never completes.
| Flag | Description |
|---|---|
--stewardship <SLUG> | Stewardship slug. |
--title <SUBSTRING> | Case-insensitive substring of the commitment’s title. |
--at <YYYY-MM-DD> | Date the work was actually done. Defaults to today. |
cdno stewardship complete-periodic --stewardship health --title "dental"
cdno stewardship complete-periodic --stewardship health --title "dental" --at 2026-08-25
Before this verb there was no way to mark a periodic commitment done: it is a bullet, not a note, so
cdno commit done has nothing to act on, and the reminder kept firing until the file
was hand-edited — the one edit the vault asks you not to make.
The next date is computed from the due date, never from --at. A check-up every 6 months, done
a week early each time, would creep a week earlier every cycle if the schedule followed the work.
Anchored to the due date, a run of early completions leaves the schedule where it was. A late
completion advances until next: is in the future, so one neglected commitment comes back on
schedule rather than several reminders deep.
One case the calendar imposes: a monthly commitment on the 31st has no 31st to land on in February, so it clamps to the 28th and keeps the 28th thereafter — the bullet records the next date, not the day the schedule nominally wants.
The entry records the completion and where the schedule moved to:
- **09:30**: periodic done on [[health]] — Dental check-up
was: 2026-09-01
now: 2027-03-01
A line whose recurrence the parser cannot read is refused rather than guessed — see
Recurrence syntax for the accepted forms. Such a line is otherwise unaffected:
it still appears in cdno commitments and lint still accepts it.
Related MCP tools
create_stewardship,
get_stewardship_tracking,
add_periodic_commitment,
complete_periodic.
See also
- Stewardships and tracking.
track— file a tracking entry.
cdno track
File a tracking note under an expanded stewardship. The activity is positional and selects the
template: a vault’s .cuaderno/templates/tracking-<activity>.md if present, else the generic
built-in. No activity-specific templates ship built-in — ready-made gym/body/swim variants are
in examples/templates/tracking/.
cdno track [OPTIONS] <ACTIVITY>
Arguments
| Argument | Description |
|---|---|
<ACTIVITY> | Activity slug — anything you track (e.g. gym, swim, reading). Selects tracking-<activity>.md if present, else the generic template. |
Options
| Flag | Description |
|---|---|
--stewardship <STEWARDSHIP> | Stewardship slug. Defaults to the only expanded stewardship if there’s exactly one; otherwise required. |
--routine <ROUTINE> | Bare slug of a routine doc — wrapped into [[stewardships/<slug>/routines/<routine>]] and substituted into the template’s routine: field. Only takes effect when the resolved template has a routine: field; the generic default has none, so it silently no-ops there. |
--content <CONTENT> | Inline body. Optional; defaults to empty so you can fill in tables afterward. |
--at <WHEN> | File the entry at a past (or near-future) day rather than today: YYYY-MM-DD, or a full YYYY-MM-DDTHH:MM[:SS] as cdno log --at takes (only the date is kept). For a session recorded after the fact — a statement reconciled days later, a reading taken this morning. Only the date is kept; the entry lands on that day and the daily-log line still goes into today’s note, naming the day it describes. Bounded to 50 years back and 1 year ahead: an unbounded date makes history writable, and a mistyped year would silently reshape a trend. |
--var <NAME=VALUE> | Value for a custom tracking template’s prompted variable ([variables.prompt]). Repeatable. Prompts come from the activity’s template (e.g. tracking-gym) when one exists. See Prompted variables. |
Plus the global options. With --json, emits a {path, message}
result and runs non-interactively.
Examples
cdno track gym --stewardship health --content "Upper body A; RDL up to 25kg"
cdno track body --stewardship health --content "Weight 78.4kg, resting HR 54"
cdno track swim --stewardship health --content "1500m, 28min"
# --routine needs a template with a routine: field (the example gym.md has one):
cdno track gym --routine upper-body-a
# With one expanded stewardship, --stewardship can be omitted:
cdno track gym --content "Legs day"
Tracking entries are append-only and only land in expanded
stewardships (those created with --tracking).
Until a vault has any tracking template, cdno track prints a one-line hint (on stderr) pointing
at examples/templates/tracking/
for a structured layout. It goes quiet once you author a template, and is suppressed under --json.
Merging a day
A second cdno track for the same activity and date merges into the first rather than
erroring: --content is appended to the entry’s ## Notes, and any metrics are folded into its
frontmatter. Recording a day in two passes — a morning and an evening session, spending logged as
it happens — is ordinary rather than exceptional.
Merging is not blind concatenation. A record carrying a stable id replaces the record with
that id, so re-applying the same payload is idempotent; a record without one appends, so
re-running an import that omits ids double-counts every summed metric. Scalars are
last-write-wins: there is no array to key on, and the later reading is what a level means — but
sending a plain value for a key that already holds records is refused, since that would discard
the day’s entries on a note type that only grows.
Related MCP tool
See also
cdno templates
Inspect note templates. Use it before writing a custom template in
.cuaderno/templates/ to see which {{placeholders}} a note type supports —
unknown placeholders render verbatim, so this is how you learn the valid set
without reading the source.
cdno templates vars <type>
List the {{placeholders}} a note type’s template supports.
cdno templates vars [OPTIONS] <TYPE>
Arguments
| Argument | Description |
|---|---|
<TYPE> | Note type: project, action, question, portfolio, evidence, stewardship, tracking, commitment, daily, weekly, inbox, or a config-defined custom type. |
Takes only the global options.
Sources
Each placeholder is classified by where its value comes from:
| Source | Meaning |
|---|---|
supplied | Filled automatically by the note type’s create command. This is the type’s complete create-path key set — including body placeholders and keys the default template happens not to reference (e.g. daily’s weekday, tracking’s routine) — so it matches the per-type table in Customising templates and frontmatter exactly. |
config | A static [variables] entry in .cuaderno/config.toml, available to any template. |
prompt | A [variables.prompt] entry — a value must be provided at creation (via --var name=value, the MCP vars parameter, or interactively). The prompt message is shown. |
A config or prompt name that collides with a supplied key is omitted: the
supplied value shadows it, so it would never take effect.
With --json, emits an array of { name, source } objects (prompt entries
also carry message).
Examples
cdno templates vars project
cdno templates vars tracking
cdno templates vars question --json | jq -r '.[].name'
cdno templates eject <type>
Copy a built-in template into .cuaderno/templates/<type>.md as an editable
starting point. Note types use an in-binary default until you add a file for
them (only daily is seeded on cdno init); this materialises one so you can
customise it (add sections, reorder frontmatter, reference {{placeholders}}
from templates vars) without hand-reconstructing it from the source tree.
cdno templates eject [OPTIONS] <TYPE>
Arguments
| Argument | Description |
|---|---|
<TYPE> | Built-in note type to eject. Omit when using --all. A config-defined custom type has no built-in template to eject (author .cuaderno/templates/<type>.md by hand), so it is refused here — unlike templates vars, which does accept custom types. |
Options
| Flag | Description |
|---|---|
--all | Eject every built-in template into .cuaderno/templates/ at once. Types that already have a template file are skipped (a summary reports which), unless --force. Mutually exclusive with <TYPE> — pass one or the other. |
--force | Overwrite existing custom template(s). Without it, an existing file is left untouched (and single-type eject errors; --all skips it). |
Plus the global options. With --json, single-type
eject emits the { path, message } write result; --all emits an object with
written and skipped arrays (note-type names).
Only base note-type templates eject — no <type>-<variant> template ships
built-in. To create a tracking activity variant, copy one from
examples/templates/tracking/
to .cuaderno/templates/tracking-<activity>.md instead.
Examples
cdno templates eject project # → .cuaderno/templates/project.md
cdno templates eject tracking # → the generic tracking template
cdno templates eject project --force # overwrite an earlier customisation
cdno templates eject --all # eject all built-ins, skip customised
cdno templates eject --all --force # eject all, overwriting everything
The written file is exactly the built-in default, so a note created straight after ejecting is byte-identical to before — customise from there.
Related
- Customising templates and frontmatter — how to write a custom template and use
[variables]/[variables.prompt].
cdno frontmatter
Set typed frontmatter fields on a note through the index. Every other write
surface (cdno log, section upserts) is body-oriented; this is the one that
writes a frontmatter field, so a flag like the daily meds: true can be toggled
without a hand-edit that would desync .cuaderno/index.db.
The field must be declared in .cuaderno/config.toml under
[schemas.<type>.fields.<key>] and marked settable = true — the write is
driven entirely by that spec (see the configuration
reference).
cdno frontmatter set <note> <key> <value>
Set a declared, settable field to a new value.
cdno frontmatter set [OPTIONS] <NOTE> <KEY> <VALUE>
Arguments
| Argument | Description |
|---|---|
<NOTE> | The note to edit: today, a YYYY-MM-DD date (both resolve to the daily note), or a vault-relative note path (e.g. projects/foo.md). |
<KEY> | The frontmatter field to set. Must be declared settable = true under [schemas.<type>.fields.<key>]. |
<VALUE> | The new value, as a string. It is coerced to the field’s declared type (bool/int/float/string/date) and checked against any values allowed-set. |
Takes only the global options. With --json,
emits the { path, message } write result.
Rules
- Declared + settable, default-deny. An undeclared key is rejected; a
declared field without
settable = true(absent orfalse) is rejected. - Type-checked. A value that doesn’t parse as the declared type — or isn’t
one of a
stringfield’svalues— is rejected and nothing is written. - Reserved keys are blocked.
type,status, and a calendar type’s period key (date/week/month) are engine-owned regardless of config — use the lifecycle commands (cdno project park/activate,cdno question set-status, …) for those, so their auto-logging and index invariants are never bypassed. - No-op on no change. Setting a field to the value it already holds writes nothing and logs nothing.
- Optional auto-log. When the field declares
log_on_change = true, a real change stamps akey: old → newline into today’s daily note in the same commit. - Strict-exists (v1). The key must already be present in the note’s
frontmatter (the daily flags exist via the template default). A missing key
errors rather than being appended; ordered-insert is a planned follow-up.
Slug resolution for projects/questions is likewise a follow-up — v1 resolves
today/dates and explicit note paths.
Examples
cdno frontmatter set today meds true # toggle today's daily meds flag
cdno frontmatter set today workout true # if log_on_change, also logs it
cdno frontmatter set 2026-07-09 closed true # a specific day's daily note
cdno frontmatter set projects/surrogate.md phase review
Related
- Configuration reference — declaring
[schemas.<type>.fields], includingsettableandlog_on_change. - Frontmatter fields — the built-in per-type frontmatter shapes.
cdno note
Create and list notes of a config-defined custom type (declared under
[note_types.<name>] in .cuaderno/config.toml). Built-in types have their own verbs
(cdno project create, cdno question create, …); this is the generic surface for custom types.
cdno note create <type>
Create a note of custom type <type>, written to <folder>/<slug(title)>.md.
cdno note create [OPTIONS] <TYPE> --title <TITLE>
Arguments
| Argument | Description |
|---|---|
<TYPE> | A config-defined custom type (e.g. person). A built-in type is refused — use its own create command. |
Options
| Flag | Description |
|---|---|
--title <TITLE> | Required. The note’s title; its slug becomes the filename. |
--field <NAME=VALUE> | A frontmatter field, repeatable. Each key must be a declared required/optional field of the type; every required field must be supplied. |
--var <NAME=VALUE> | A value for the type’s template prompted variable, repeatable. |
Plus the global options. With --json, emits a {path, message}
result. If the type ships no template (.cuaderno/templates/<type>.md), a minimal note is
synthesised from the declared fields plus a # <title> heading.
cdno note list <type>
List every note of custom type <type>, by path.
cdno note list <TYPE>
Examples
cdno note create person --title "Ada Lovelace" --field name=Ada --field role=advisor
cdno note list person
Related
- Custom note types — declaring a type and the full feature.
cdno commit
Manage standalone commitments — dated promises kept as their own notes. To view all commitments
(from every source), use cdno commitments.
cdno commit [OPTIONS] <COMMAND>
Subcommands
| Subcommand | Description |
|---|---|
create | Create an active commitment at commitments/<slug>.md |
done | Mark a commitment completed and archive it |
drop | End a commitment that was not kept |
reschedule | Move an active commitment’s due date |
All four honour --json ({path, message}, non-interactive).
cdno commit create
Create an active commitment note. Optionally attribute it to a project or stewardship.
| Flag | Description |
|---|---|
--title <TITLE> | Commitment title (the slug derives from it). |
--due <YYYY-MM-DD> | Deadline. |
--context <CONTEXT> | Life domain (work, personal, …). |
--project <SLUG> | Optional associated project. |
--stewardship <SLUG> | Optional associated stewardship. |
--var <NAME=VALUE> | Value for a custom template’s prompted variable ([variables.prompt]). Repeatable. See Prompted variables. |
cdno commit create --title "Pay rent" --due 2026-06-01 --context personal
cdno commit create --title "Review Erik's draft" --due 2026-05-20 --context work --project projects/icml-paper
cdno commit done
Mark a commitment completed: stamps status and completed, and moves the note to
commitments/_done/<year>/<slug>.md.
| Flag | Description |
|---|---|
--slug <SLUG> | Commitment slug. |
cdno commit done --slug pay-rent
cdno commit drop
End an active commitment that was not kept — cancelled, superseded, or overtaken by events.
| Flag | Description |
|---|---|
--slug <SLUG> | Slug of the active commitment. |
--reason <TEXT> | Why it ended. Optional, and never prompted for. |
cdno commit drop --slug quarterly-report
cdno commit drop --slug quarterly-report --reason "the client cancelled the engagement"
Use this rather than done when the promise was not fulfilled. done writes
commitment completed [[slug]] into the daily log, which every weekly and monthly review reads back
from, so using it for a cancelled promise makes the vault assert something nobody did. Deleting the
note is not the alternative either: that destroys the record that the promise was ever made, and
desyncs the index on the way out.
The note is archived to commitments/_done/<year>/ like a completion, stamped status: dropped
with completed cleared — so it never appears as completed work. The log entry says what happened,
with the reason on its own line:
- **16:30**: commitment dropped on [[quarterly-report]] — Quarterly report
reason: the client cancelled the engagement
A dropped commitment can afterwards be neither completed nor rescheduled. Nothing is destroyed — the
note keeps its body and its created date — but a drop is re-decided rather than undone: if the
promise comes back, make it again, and the record shows both decisions.
cdno commit reschedule
Move an active commitment’s due date, recording the move in today’s daily note.
| Flag | Description |
|---|---|
--slug <SLUG> | Slug of the active commitment. |
--due <YYYY-MM-DD> | The new due date. Must differ from the current one. |
cdno commit reschedule --slug quarterly-report --due 2026-06-15
Commitments slip, and that is normal rather than an error state. Use this instead of deleting and
recreating the note: recreating destroys the body — the notes on who was chased and what was
promised — and resets created, the one field that shows how long something has been slipping.
The log entry carries both dates:
- **09:30**: commitment rescheduled on [[quarterly-report]] — Quarterly report
was: 2026-06-01
now: 2026-06-15
That is what makes repeated slippage visible instead of silently rewritten: a commitment moving once is a checkpoint, a commitment moving for the fourth time is a signal. Moving a date earlier is allowed; moving it to the date it already carries is refused, since the entry would assert a slip that never happened.
Related MCP tools
create_commitment, complete_commitment,
drop_commitment, reschedule_commitment. (View via
get_commitments.)
See also
- Commitments and deadlines.
commitments— the aggregated view.
cdno lint
Validate every indexed note and report what is wrong with it — frontmatter, links, attachment
pairing, and lines the canonical parsers silently skip. Errors fail the command; warnings (such as
broken wikilinks) are non-fatal unless --strict is given.
A wikilink or embed that points at an attachment — a pasted image, a filed PDF — is not a broken
link: attachments are never indexed, but the target is resolved against the filesystem (relative to
the linking note, then to the vault root) before a link is called broken. Only a target that matches
nothing at all is reported, and a missing ![[embed]] reads as a missing file rather than a link
that “resolves to no note”.
It also reports lines that were plainly meant to be structured but will never be read as such,
because the parsers that consume them skip what they cannot parse rather than complaining. That
covers malformed ## Active Habits and ## Periodic Commitments bullets on a stewardship
dashboard, and — in a daily note’s ## Logs — a start or close marker that
cdno now will not see:
[warning] journal/2026/daily/2026-09-15.md: log line `- **09:30**: started [[alpha]] - Draft methods (deep)` reads as a `started` marker but `cdno now` will not see it -- found an ASCII hyphen (-) where an em-dash (—) separates the slug from the action
The shape has to be - **HH:MM**: started [[slug]] — text, and every part of it matters: the -
bullet and the stamp are what make the line a log entry at all, and the separator must be a real em
dash (U+2014). A stamp that was attempted and mangled — - 09:20: unbolded, - **25:99**: out of
range, - **09:40** with no colon — is caught too, not only one that is missing outright. The check is
deliberately narrow — the marker has to open the entry and be followed immediately by [[ — so
ordinary prose in ## Logs, including a sentence that merely mentions starting something or names a
note mid-sentence, is never flagged.
cdno lint [OPTIONS]
Options
| Flag | Description |
|---|---|
--strict | Treat warnings as failures too (exit non-zero on any issue). |
Plus the global options. lint ignores --json.
Exit status
- Clean, or warnings only without
--strict→ exit0. - Any error (e.g. unknown note type, invalid frontmatter) → non-zero.
- With
--strict, any warning also → non-zero. Useful in CI to keep a vault pristine.
Examples
cdno lint # report issues; fail only on errors
cdno lint --strict # fail on warnings too (e.g. broken links)
Related MCP tool
lint.
See also
cdno reindex
Rebuild the SQLite index from scratch off the Markdown source of truth. The recovery path for a corrupt or stale index.
cdno reindex [OPTIONS]
Options
Only the global options. reindex ignores --json.
When you need it
Almost never — Cuaderno reconciles the index automatically on every run (see
Business rules). Reach for reindex when:
- search or link results look wrong after a large external edit or a sync conflict, or
- you deleted
.cuaderno/index.dband want to rebuild it eagerly rather than on next use.
Because the Markdown files are authoritative, a full rebuild is always safe.
Examples
cdno reindex
See also
cdno normalise
Reorder note frontmatter into the canonical key order of each note’s template (a custom
.cuaderno/templates/ override if present, else the built-in). Notes cdno creates are already
canonical; this fixes hand-authored or migrated notes.
cdno normalise [OPTIONS]
Options
| Flag | Description |
|---|---|
--check | Report out-of-order notes without rewriting them. Exits non-zero if any are out of order. |
Plus the global options. normalise ignores --json.
Examples
cdno normalise # rewrite notes into canonical frontmatter order
cdno normalise --check # report only; non-zero exit if anything is out of order (CI-friendly)
normalise only reorders existing keys — it never changes values or adds/removes fields. The
canonical order is whatever the matching template
defines.
See also
- Configuration — how templates define field order.
lint.
cdno completions
Print a shell-completion script. Source it in your shell’s rc file. The script wires vault-aware
dynamic suggestions — --project, --portfolio, --stewardship, --slug, etc. are completed by
re-invoking the binary when you press TAB.
cdno completions [OPTIONS] <SHELL>
Arguments
| Argument | Description |
|---|---|
<SHELL> | Target shell. One of bash, zsh, fish, elvish, powershell. |
Options
Only the global options.
Setup per shell
# zsh — in ~/.zshrc:
source <(cdno completions zsh)
# bash — in ~/.bashrc:
source <(cdno completions bash)
# fish — write it into the completions dir:
cdno completions fish > ~/.config/fish/completions/cdno.fish
# elvish / powershell: emit the script and source it per that shell's convention.
cdno completions powershell
After reloading your shell, TAB completes commands, flags, and live vault values (project slugs, portfolio slugs, …).
See also
MCP server reference
cdno-mcp is a Model Context Protocol server that exposes your
vault to AI clients (Claude Desktop, Claude Code, Kiro, Gemini CLI, …). It runs the same domain
engine as the CLI, so anything the assistant does goes through the same rules and lands in the same
Markdown files. To wire it up, see Connect to Claude.
Transport and vault selection
Two binaries serve the same tool catalogue:
cdno-mcpspeaks JSON-RPC over stdio — the client launches it as a subprocess. It opens the vault named byCUADERNO_VAULT_PATH, or, if that’s unset, discovers one from its working directory (the same rule as the CLI).cdno-mcp-serverspeaks MCP Streamable HTTP for remote clients — see The HTTP server for its flags and security model.
The tool surface
The server advertises 55 tools. This reference groups them by purpose:
| Group | Page | What’s in it |
|---|---|---|
| Context-gathering reads | Context-gathering tools | Orientation, project/portfolio/weekly context, search, reads, lint, triage list |
| Writes | Write tools | Log, capture, file evidence, project/action/milestone/waiting edits, commitments, tracking, daily/weekly sections |
| Creation & lifecycle | Creation and lifecycle tools | Create projects/portfolios/questions/stewardships, link portfolios, park/activate, status transitions |
Every tool returns typed JSON; the shapes mirror the CLI’s --json output, so a
client gets the same structures whichever surface it uses.
Conventions
- Slugs, not paths. Tools take slugs (
surrogate-model), matching the CLI. - Substring matching for completing actions/milestones and resolving waiting-on items, exactly as on the CLI.
- The same rules apply. The five-project cap, append-only notes, auto-logged project-state history, and commitments aggregation all hold — the MCP server is not a back door around them.
Building skills on top
Multi-step rituals (a morning orientation, a guided weekly review) are best wrapped as Claude skills that call these tools in sequence. See Using with Claude skills.
The HTTP server
cdno-mcp-server serves the same tool catalogue as the stdio cdno-mcp, over the MCP
Streamable HTTP transport, for clients that reach your vault remotely — most notably Claude’s
custom-connector infrastructure, which connects from Anthropic’s cloud for every surface
(web, desktop, mobile).
cdno-mcp-server --vault ~/vault # listens on 127.0.0.1:8787, endpoint /mcp
Security model — read this first
The binary issues no OAuth of its own, on purpose. Static bearer tokens are not spec-legal
for remote MCP connectors; real deployments terminate OAuth 2.1 at an identity-aware proxy
(for example Cloudflare Access with Managed OAuth in front of a Cloudflare Tunnel). The server’s
own contribution is origin-side validation of the identity assertion the proxy injects
(Cf-Access-Jwt-Assertion): RS256 against the team’s JWKS, strict issuer/audience/expiry, fail
closed — configure it with CDNO_ACCESS_TEAM_URL and CDNO_ACCESS_AUD. If the JWKS cannot be
fetched at startup, the server refuses to start rather than serve unauthenticated.
Without that configuration, cdno-mcp-server refuses to bind anything but loopback.
Be precise about what that guarantees: the process only accepts connections arriving on its own
loopback interface. It cannot detect a tunnel or SSH forward that bridges the port outward —
never bridge this port without the authenticating proxy in front. The server logs a warning
at startup to the same effect whenever it serves real vault data unauthenticated. Configuring
the JWT validation is exactly what lifts the non-loopback restriction (e.g. binding 0.0.0.0
inside a container).
Flags
| Flag | Env | Default | Purpose |
|---|---|---|---|
--vault <path> | CUADERNO_VAULT_PATH | cwd | Vault root |
--bind <addr> | CDNO_MCP_BIND | 127.0.0.1:8787 | Listen address (non-loopback refused until #302) |
--allowed-host <host> | CDNO_MCP_ALLOWED_HOSTS (comma-separated) | — | Extra Host header values to accept on top of the loopback defaults (DNS-rebinding protection). A public deployment adds its hostname |
--smoke | — | off | Serve a single echo tool holding no vault handle — prove tunnel/auth infrastructure end-to-end with zero vault exposure |
--read-only | — | off | Advertise only the context-gathering read tools; mutating tools are absent from the dispatch table entirely |
--reconcile-interval-secs <n> | CDNO_MCP_RECONCILE_INTERVAL_SECS | 300 | Periodic index reconciliation; 0 disables |
--git-checkpoint-interval-secs <n> | CDNO_MCP_GIT_CHECKPOINT_INTERVAL_SECS | 60 | How often the git sweep runs. 0 disables it; warns and no-ops when the vault isn’t a git repo |
--git-checkpoint-mode <mode> | CDNO_MCP_GIT_CHECKPOINT_MODE | commit | What the sweep does with a dirty tree: commit here, or nudge-only — see The recovery trail |
--sync-nudge | CDNO_MCP_SYNC_NUDGE | off | Touch a sentinel file after every verified write so an external sync agent reacts at once instead of on its own timer — see Pairing with a sync agent |
--sync-nudge-path <path> | CDNO_MCP_SYNC_NUDGE_PATH | <vault>/.git/cdno-sync.nudge | Where that sentinel lives. Setting it does not by itself enable nudging |
--access-team-url <url> | CDNO_ACCESS_TEAM_URL | — | Cloudflare Access team URL (JWT issuer + JWKS host). Requires --access-aud; activates origin JWT validation and lifts the loopback-only restriction |
--access-aud <tag> | CDNO_ACCESS_AUD | — | The Access application’s AUD tag (expected aud claim). Requires --access-team-url |
The recovery trail
Exposing write tools remotely means anything a confused or prompt-injected session does lands in
your vault. The damage limit is that every mutation ends up in a git commit you can diff and
revert. The sweep is what provides it: on an interval it takes the vault write lock, and if the tree
is dirty it commits everything as cdno-mcp checkpoint (N path(s)). It is a sweep rather than a
per-write hook, so out-of-band edits — the CLI, your editor, a sync tool — join the trail too.
What must hold is that something commits. Which actor does is configurable:
| Mode | Who commits | How |
|---|---|---|
| interval (default) | this server | --git-checkpoint-interval-secs <n>, default 60 |
| nudge-only | an external sync agent | --git-checkpoint-mode nudge-only |
| disabled | nobody | --git-checkpoint-interval-secs 0 |
Reach for nudge-only when an agent already owns the repository’s history. Per-minute checkpoint commits would fight it: two git actors in one working tree, and the agent’s coalesced, unit-of-thought commits buried under machine noise. In this mode the sweep still runs and still takes the lock, but it commits nothing — on a dirty tree it touches the sync-nudge sentinel and leaves the change exactly where it found it, unstaged, for the agent to pick up.
cdno-mcp-server --vault /srv/vault --git-checkpoint-mode nudge-only
The sentinel path is the one --sync-nudge-path sets, so the sweep and the per-write nudge always
agree on it. You do not need --sync-nudge as well: that flag governs the per-write signal, and
the two are useful together (writes nudge immediately; the sweep catches anything that arrived out
of band) but independent.
Both non-default modes hand the trail to somebody else, and the server says so at startup —
nudge-only logs a warning naming the sentinel and stating that this process commits nothing, and
0 warns that nothing in the process commits at all. If no agent is running, either is equivalent
to having no recovery trail.
Only cdno-mcp-server sweeps. The stdio binary has no checkpoint loop and none of these flags.
When the sweep stops
The sweep’s job is to be the thing you can fall back on, so it must never fail quietly. Two ways it can stop, both loud:
-
A tick that runs and finds trouble — a non-zero
git, the vault write lock busy, someone else’s paused merge or rebase — logs and retries on the next tick. Only agitthat cannot be executed at all, five times running, disables the loop, with an error saying so. -
A tick that never runs at all. The sweep does its work on a pool of worker threads, and if the process cannot get one, the tick is queued and the loop waits. Nothing is committed and, until this was fixed, nothing was logged either — the endpoint stayed up and tool calls kept answering, so nothing outside could tell. A tick that overruns three sweep intervals (never less than 30 seconds) now logs:
ERROR git checkpoint sweep STALLED: a tick has not completed, no further tick can start, and so NOTHING in this process is committing — writes are no longer being recorded. ...It repeats while the tick stays stuck, and logs a recovery line if it completes. Alert on it: the two realistic causes are a process out of threads (see Thread budget) and a wedged
gitinvocation, and the process cannot tell them apart from inside — but either way the vault has stopped being recorded.
Thread budget and pids_limit
A container sized with pids_limit counts every OS thread this process holds, not just processes.
The server bounds itself so that number is knowable, and logs it at startup:
INFO runtime thread budget workers=8 max_blocking_threads=16 max_os_threads=25
max_os_threads is the ceiling: one main thread, one async worker per CPU the process can see, and
a fixed pool of 16 for the blocking work (vault reads and writes, the reconciliation pass, the
checkpoint sweep). Size pids_limit above that number with room for whatever else shares the
container — a healthcheck that shells out needs to fork too, and a container that cannot fork
reports unhealthy and refuses docker exec while the server itself carries on serving.
Note that workers follows the CPUs the process can see, which on Linux is CPU affinity, not a
cgroup CPU quota: a small container on a large host still gets a worker per host core. Read the
number off the log line rather than assuming it.
Pairing with a sync agent
A common shape is two clones of the vault repository — an always-on host running this server, and a laptop — with an external agent on the host owning the commit-and-push loop. That agent normally polls: it wakes on its own timer, sees a dirty tree, and commits. A write that landed a second after the last poll waits out the whole interval.
--sync-nudge closes that gap. After every write the server has verified,
it rewrites a sentinel file, changing both its modification time and its contents. The agent watches
that one path — launchd WatchPaths, inotifywait, fswatch, whatever it already uses — and acts
immediately.
cdno-mcp-server --vault /srv/vault --sync-nudge
# → touches /srv/vault/.git/cdno-sync.nudge after each verified write
The contract is deliberately narrow:
- Off by default. A deployment with no agent has nobody to signal.
- One-way. The server writes the sentinel and never reads it, so an agent that is absent, stopped, or slow costs nothing but latency.
- Only after a verified write. A write that failed, or that could not be read back, leaves the sentinel alone — an agent woken by writes that did not happen learns to ignore the signal.
- Never fatal. A sentinel that cannot be written is logged and skipped; the write still succeeds.
- Never content. The file holds a unix timestamp and nothing else. It names no note.
It lives under .git/ on purpose: git will not track it, and no tool that mirrors the working tree
will carry it, so the signal cannot leak into the vault or across machines. --sync-nudge-path
moves it if your agent needs it elsewhere; parent directories are never created.
Only cdno-mcp-server has this. The stdio binary is a local session with no agent on the other side
of it, and offers no such flag.
Index freshness
Unlike a stdio session, this process is long-running while other writers — the CLI, editors, sync
tools — mutate the Markdown underneath it. Markdown is the source of truth and the index is a
cache, so the server re-runs the reconciliation pass on the configured interval as the correctness
backstop. Out-of-band edits become visible to search_notes and the context tools within one
interval at most.
Timezone — set TZ on the host
The server timestamps everything it writes — log lines, daily entries, tracking entries — with the
process’s local time (chrono::Local::now()). A container or host with no zoneinfo database and
no TZ set makes chrono fall back silently to UTC, so remote writes land hours behind the wall
clock with no error. Any deployment must therefore ship a zoneinfo DB (tzdata on Alpine, already
present on most distros) and set TZ, e.g. TZ=Europe/Stockholm.
At startup the server logs the offset it resolved, next to the “vault opened” line:
INFO local time zone resolved (server timestamps use process-local time) local_offset=+02:00 sample_local_now=2026-07-06T14:30:00+02:00
Check this line after deploying: a local_offset=+00:00 you didn’t intend is the tell-tale of a
missing tzdata/TZ. (A host legitimately in UTC is fine — the line is factual, not a warning.)
Transport details
- Endpoint:
POST /mcp. Clients must sendAccept: application/json, text/event-stream(the Streamable HTTP spec requires both). - Stateless JSON mode: every request is self-contained; responses are plain
application/json(no SSE streams, no session ids).GET/DELETEon/mcpreturn405. - Guardrails: request bodies are capped at 1 MiB and in-flight requests are bounded; the
Hostheader is validated against the allowlist (403 otherwise).
Context-gathering tools
Read-only tools an assistant uses to understand your vault before acting. None of these mutate anything. Inputs marked optional may be omitted.
| Tool | Inputs | Returns |
|---|---|---|
get_orientation | energy? (deep|medium|light) | Commitments due soon, active projects, lapsed stewardship habits, and a suggested starting point. The MCP form of cdno orient. |
get_project_context | project (slug) | A project’s state, next actions, milestones, waiting-on items, and links. |
get_portfolio_contents | portfolio (slug) | Portfolio metadata plus its evidence inventory. |
get_weekly_context | date? (any day in the week) | The weekly note’s sections (Wins, Challenges, One Improvement, This Week’s Goal), plus the week’s logs, completed_actions and project state changes. |
get_monthly_context | date? | Monthly context for a strategic scan, including the past 30 days’ completed_actions as wins patterns. |
get_stewardship_tracking | stewardship, activity, period? (e.g. 30d, 6m) | Tracking entries for a stewardship/activity over a window, plus the activity’s declared contract in spec (record key, group field, and each metric’s type, unit, aggregate, and derived expression when it is computed rather than recorded — write that metric’s operands, never a field of its own name) when the vault declares one under [tracking.<activity>]. spec is null for an undeclared activity. Also returns series: this activity’s numeric trends, one per (group, metric) it declares, each point already reduced by that metric’s own aggregate, scoped and windowed by the same activity and period as the entries. |
get_active_questions | domain? (research|life) | Active question notes, optionally filtered by domain. |
get_commitments | lookahead_weeks? (default 2) | The aggregated commitments view; overdue always included. |
current_focus | — | The action started and not yet closed, or null. Replayed from today’s ## Logs, so a start made from the CLI counts too, as does one written by hand in the log’s full shape (- **HH:MM**: started [[slug]] — text — stamp and em dash U+2014 both required; see cdno now); a completion or a drop clears it, but a promote_action in between strands it. The MCP form of cdno now. |
list_projects | — | All projects (active + parked) with summaries. |
list_note_types | — | Every note type — the built-ins plus any config-defined [note_types.*] custom type — with its folder, required/optional fields, typed [schemas.*] field specs, template, and supplied placeholders. Call before create_custom_note to discover a vault’s custom types. |
read_daily_note | date? (default today) | The daily log for a date. |
read_weekly_note | date? (default this week) | The weekly note for an ISO week. |
read_monthly_note | date? (default this month) | The monthly note for a calendar month. |
search_notes | query, note_type?, from?, to?, portfolio?, limit? (default 20) | Ranked full-text hits. The MCP form of cdno search. |
lint | — | Vault-wide problems: frontmatter, broken wikilinks, attachment pairing, and lines the canonical parsers silently skip — malformed stewardship-dashboard bullets and daily-log focus markers cdno now will not read back. |
triage_inbox | — | Pending inbox captures awaiting triage. |
Notes
- Dates are
YYYY-MM-DD. Week-scoped tools accept any day within the target ISO week; month-scoped tools accept any day within the target calendar month. search_notesreturns the same hit shape as the CLI:path,note_type,title,snippet,score. See JSON output.completed_actions(weekly and monthly) covers both forms an action takes. One with its own note carriesslugandpathalongsidesource: "note"; an inline bullet — the default form, and so the common case — carriessource: "bullet"with both null. A client must expect null there. A completion that has both a note and a log line is listed once, and dropped actions never appear. The list is read from the daily notes directly rather than from the cappedlogsfield, so a completion early in a busy week is not lost to that cap.- These pair naturally with the write tools: read context, propose an action, then write it.
Write tools
Tools that mutate the vault. Each returns a result describing what was written. The same business rules as the CLI apply (append-only notes, auto-logged project state, the project cap).
Every write is verified
A tool result that only said “success” could not be told apart from a write that silently never landed — which over a remote connection is exactly how a lost note goes unnoticed. So every write tool re-reads its target before answering, and the result carries the evidence:
{
"path": "journal/2026/daily/2026-08-26.md",
"message": "Logged to journal/2026/daily/2026-08-26.md",
"verification": {
"verified": "content",
"bytes_written": 412,
"content_hash": "84bc0919e867576f",
"appended_tail": "- **09:14**: baseline sweep finished\n"
}
}
| Field | Meaning |
|---|---|
verified | content — the file was re-read; or removed, for discard_inbox_item, where the check is that the file is gone |
bytes_written | Size of the file on disk after the write. 0 for a removal |
content_hash | The note’s content hash (below) — null for a removal |
appended_tail | For append-shaped writes (append_to_log, start_action), the tail of the section the text went into — see below. null elsewhere |
If the target cannot be read back, the tool returns an error rather than a success. The wording says the write is unverified, not failed: it may still have landed, so the right response is to re-read the note, not to blindly repeat the write.
The appended tail
appended_tail is scoped to the section the write targeted, not to the last bytes of the file.
append_to_log and start_action both write into ## Logs, so that is the section you get back.
(start_unplanned_action also logs, but it rewrites the project map in the same commit and so
verifies as a whole-file rewrite — its appended_tail is null.)
The distinction matters because ## Logs is not always last. Cuaderno pins the effective daily
template’s last ## section to the bottom of the note — for the built-in template that is ## Logs,
but a custom .cuaderno/templates/daily.md ending in, say, ## Reflection keeps Reflection last and
leaves the log line in the middle of the file. Reading the end of the file would then show you
Reflection’s text while claiming to be the line that landed.
If the section cannot be located — an unparseable note, a ## Logs heading that is missing or
duplicated — the field is null. The write is still verified by bytes_written and content_hash;
only the extra evidence is withheld, because a window over the wrong bytes is worse than no window.
The content hash
content_hash is the same non-cryptographic xxh3-64 fingerprint (16 lowercase hex characters) the
index uses for change detection, so a client can recompute it over a note it has read and compare.
Two uses in practice:
- confirm a note is byte-identical to the one the server saw;
- notice a no-op.
update_project_statewith a state that already matches deliberately writes nothing and still reports success — an unchangedcontent_hashacross two calls is how you tell.
It is a change detector, not tamper evidence: it does not defend against someone who also chooses the content.
Logging, capture, triage
| Tool | Inputs | Effect |
|---|---|---|
append_to_log | text | Append a line to today’s daily note. (cdno log) |
capture | text | Drop a raw note into inbox/. (cdno capture) |
discard_inbox_item | slug | Clear a triaged capture (slug from triage_inbox). |
Evidence
| Tool | Inputs | Effect |
|---|---|---|
file_to_portfolio | portfolio, source, origin, content?, attach?, vars? | File evidence into a portfolio; attach is a server-side path to a non-Markdown artefact (vars is ignored on the attach path). (cdno file) |
Projects, actions, milestones, waiting-on
| Tool | Inputs | Effect |
|---|---|---|
update_project_state | project, new_state | Rewrite the Current State (auto-logs the previous). |
set_core_question | project, core_question?, clear? | Set the project’s core question (bare questions/<domain>/<slug> target, not [[…]]); clear: true detaches. Auto-logs the previous value. |
add_action | project, title, energy, with_note?, vars? | Append a next action; with_note also scaffolds a manifest note (vars applies only then). |
promote_action | project, query, vars? | Promote a bullet to a manifest note (substring match). |
start_action | project, query | Log that work on an existing bullet is starting. Logs the resolved bullet text, so the later close pairs with it. Errors when query matches nothing — it will not create the action. |
start_unplanned_action | project, title, energy | Add the bullet and start it, in one commit, for work that was on no map. Separate from start_action on purpose: a fallback would turn a typo into a new action. |
complete_action | project, query | Complete an action; archives its note if any. |
drop_action | project, query, reason? | Close an action without recording it as done (superseded, abandoned, reprioritised); archives its note as status: dropped. |
add_milestone | project, title, target_date?, hard? | Add a milestone; hard counts it in commitments and requires target_date. Omit target_date for a condition-gated milestone (target: TBD), which stays out of commitments. |
complete_milestone | project, query | Complete a milestone (substring match). |
drop_milestone | project, query, reason? | Remove a milestone without recording it as met (superseded, mis-typed, not happening); logs milestone dropped on. Completed bullets are never matched. |
add_waiting_on | project, description | Add a waiting-on blocker. |
resolve_waiting_on | project, query | Resolve a waiting-on item (substring match). |
Commitments and tracking
| Tool | Inputs | Effect |
|---|---|---|
create_commitment | title, due, context, project?, stewardship?, vars? | Create a standalone commitment note. |
complete_commitment | commitment (slug) | Mark a commitment done and archive it. |
drop_commitment | commitment, reason? | End a commitment that was not kept; stamps status: dropped, clears completed, archives it. Never appears as completed work. |
reschedule_commitment | commitment, due | Move an active commitment’s due date, logging both the old and new dates. Refuses an unchanged date. |
complete_periodic | stewardship, title, at? | Complete one occurrence of a stewardship’s periodic commitment, rolling next: forward by that line’s recurrence. Anchored to the due date, so completing early never drags the schedule earlier. |
create_tracking_entry | stewardship, activity, routine?, content?, vars?, metrics?, date? | File a tracking note under an expanded stewardship. metrics is a JSON object merged into the entry’s frontmatter — a scalar per reading ({"balance": 1240.5}), or an array of flat records when one entry holds several comparable items ({"detail": [{"subject": "harmony", "minutes": 25}]}); a scalar whose key is declared under [schemas.tracking.fields] is type-checked, and a key naming the note’s identity (type, stewardship, activity, date) is refused. date files the entry for a past day (bounded to 50 years back, 1 year ahead). A second call for the same (activity, date) merges into the first: content appended, metrics folded in. Records carrying a stable id replace the record with that id; records without one append, so re-sending a payload without ids double-counts summed metrics. Either way the write is journalled to today’s daily log. |
Frontmatter
| Tool | Inputs | Effect |
|---|---|---|
set_frontmatter | note, key, value | Set a declared, settable = true typed frontmatter field through the index (no desync). note is today, a YYYY-MM-DD date, or a vault-relative path. Engine-owned keys (type, status, a period key) are rejected; the value is type-checked; log_on_change fields stamp a daily-log line. (cdno frontmatter set) |
Daily, weekly, and monthly sections
| Tool | Inputs | Effect |
|---|---|---|
upsert_daily_section | section (Standup|Intention|Agenda|Meeting), content?, date?, append? | Write or append a daily-note section. |
upsert_weekly_section | section (Wins|Challenges|One Improvement|This Week's Goal), content?, date?, append? | Write or append a weekly-note section. |
upsert_monthly_section | section (Wins|Themes|Next Month's Focus), content?, date?, append? | Write or append a monthly-note section. |
Notes
append?defaults to replacing the section; set ittrueto append instead.- Dates are
YYYY-MM-DD; week-scoped tools accept any day in the target week, and month-scoped tools accept any day in the target month. vars?is an optionalname -> valuemap supplying values for a custom template’s[variables.prompt]placeholders — the MCP analogue of the CLI’s repeatable--var name=value. Omitting a required prompted variable fails with an “unresolved prompts” error. See Creation and lifecycle tools for the full list of templated tools that accept it.- See also: Creation and lifecycle tools, JSON output.
Creation and lifecycle tools
Tools that create new notes or move existing ones through their lifecycle.
Creation
| Tool | Inputs | Effect |
|---|---|---|
create_project | title, context, core_question?, vars? | Create a project (parked if at the active cap). (cdno project create) |
create_portfolio | question, project?, vars? | Create an evidence portfolio. |
create_question | domain (research|life), text, vars? | Create a question note. |
create_stewardship | name, context, expanded?, vars? | Create a stewardship; expanded adds a tracking/ folder. |
create_custom_note | type_name, title, fields?, vars? | Create a note of a config-defined custom type ([note_types.<name>]); built-in types have their own dedicated create tools, on this page and under Write tools. fields is a name → value map of the type’s declared frontmatter fields — every required one must be present, and each key must be declared. Call list_note_types first to discover a vault’s types and their fields. (cdno note create) |
link_portfolio_to_question | portfolio, question | Retrofit a portfolio→question link (backlinks both ways). |
link_portfolio_to_project | portfolio, project | Retrofit a portfolio→project link (sets project: and appends to the project’s Links). |
Lifecycle
| Tool | Inputs | Effect |
|---|---|---|
park_project | project | Move an active project to _parked/. |
activate_project | project | Bring a parked project back (enforces the five-project cap). |
set_question_status | question, status (active|parked|answered|retired) | Transition a question’s status. |
add_periodic_commitment | stewardship, title, recurrence, next_date | Append a periodic commitment to a stewardship dashboard. |
Notes
contextis one of the fixed life domains.recurrencefollows the recurrence syntax:daily,weekly,monthly,yearly, orevery N months.activate_projectenforces the cap — if activating would exceed five active projects, the call fails and the assistant must park one first.vars?is an optionalname -> valuemap supplying values for a custom template’s[variables.prompt]placeholders — the MCP analogue of the CLI’s repeatable--var name=value. Supply an entry for each prompted variable the note’s template uses that has no static[variables]default; otherwise creation fails with an “unresolved prompts” error (MCP has no interactive prompt to fall back on).- See also: Write tools, Context-gathering tools.
Using with Claude skills
The MCP tools are building blocks. A skill composes them into a repeatable ritual the assistant can run on request — “do my daily orientation,” “walk me through the weekly review.” Cuaderno ships worked examples you can adapt.
Where the examples live
The repo’s examples/skills/
directory contains two reference skills:
quick-capture— capture a thought into the inbox with minimal friction.daily-orientation— read your orientation and help set the day’s intention.
Each is a directory with a SKILL.md (YAML frontmatter + a Markdown body of steps), plus shared
references/. The README there documents the authoring pattern.
The pattern
A skill typically:
- Surfaces context with a read tool — e.g.
get_orientationorget_weekly_context. - Talks it through with you, deciding what to do.
- Writes with the matching tool — e.g.
append_to_log,upsert_daily_section,update_project_state.
For example, a daily orientation skill calls get_orientation, presents the commitments and the
suggested start, asks for your intention, and writes it with
upsert_daily_section(section="Intention", ...).
Graceful degradation
Because each step maps to a discrete tool, a skill can degrade gracefully — if a write tool isn’t available or you decline it, the read half still gives you the briefing. The example skills show how to bind steps to tools and reference shared material.
Build your own
Start from an example, swap in the tools your ritual needs (see reads, writes, creation & lifecycle), and keep each step mapped to one tool so the flow stays inspectable. Then install it like any other Claude skill.
See also
- Connect to Claude — register the server.
- MCP server reference — the full tool surface.
JSON output
Adding --json to a supported verb swaps the formatted table for machine-readable JSON. These shapes
match the MCP server DTOs, so you get the same structures from the CLI and from an
AI client. (Which verbs support --json is covered in the CLI overview.)
Write verbs → a result object
Every write verb emits the same small object and runs non-interactively:
{
"path": "projects/surrogate-model.md",
"message": "Created projects/surrogate-model.md"
}
path is the vault-relative file written or updated; message is the human-readable line. For the
two-file cases (e.g. action add --note), path is the file the verb considers primary.
search --json → an array of hits
Ranked best-first; each hit:
[
{
"path": "portfolios/sparse-vs-dense-attention-ood/2026-03-15-chen-2025.md",
"note_type": "evidence",
"title": "Chen et al. 2025",
"snippet": "...4x speedup at 95% accuracy on the OOD split...",
"score": 1.7
}
]
A lower score ranks earlier (best match first). No matches → [].
open --json → the resolved path
cdno open today --json
{ "path": "/home/you/vault/journal/2026/daily/2026-08-21.md" }
Absolute, so the value composes from any directory. A reference that resolves to nothing — or to more than one note — is an error, not an empty result.
open --list --json → an array of candidates
Every note in the vault, most-recently-modified first. The plain-text form of the same listing is
tab-separated for piping into a fuzzy finder; --json is for programmatic consumers.
[
{
"path": "projects/surrogate-model.md",
"title": "Surrogate model",
"type": "project"
}
]
title is the note’s body H1, and is null for a note that has none.
list verbs → arrays of summaries
cdno project list --json
[
{
"slug": "surrogate-model",
"status": "active",
"state_snippet": "Mesh scaling works; assembly is the bottleneck",
"top_action": { "text": "Profile the assembly step", "energy": "medium" }
}
]
portfolio list→[{ "slug", "question", "evidence_count", "last_updated", "staleness_days" }]stewardship list→[{ "slug", "name", "context", "variant", "tracking_count" }]action list→[{ "text", "energy", "attached": { "slug", "status" } | null }]
show verbs → a detail object
cdno project show surrogate-model --json # same shape as a project-list element
cdno portfolio show --portfolio sparse-vs-dense-attention-ood --json
{
"slug": "sparse-vs-dense-attention-ood",
"question": "Sparse vs dense attention OOD",
"created": "2026-03-01",
"project": "[[projects/surrogate-model]]",
"evidence": [
{
"path": "portfolios/sparse-vs-dense-attention-ood/2026-03-15-chen-2025.md",
"created": "2026-03-15",
"source": "Chen et al. 2025",
"origin": "[[projects/surrogate-model]]"
}
]
}
projectisnullwhen the portfolio isn’t linked to one.- An evidence entry gains a
"kind"field (e.g."pdf") only for attachment stubs; it’s omitted for plain prose notes. stewardship show→{ "slug", "name", "context", "variant", "body_markdown" }.
Casing
Enumerations serialise in their canonical lowercase/kebab form, matching the MCP DTOs:
status → active/parked/completed, energy → deep/medium/light, stewardship variant →
flat/expanded, context → work/side-project/household/… So a value is identical whether you
read it from cdno --json or over MCP.
Colour and interactivity
cdno’s human-readable output is coloured, and several read commands offer to open one of the rows
they just printed. Both behaviours are for people at a terminal, and both switch themselves off the
moment the output is going somewhere else.
Cards
Listings whose items carry prose — project list, portfolio list, stewardship list, questions,
search, and the active-projects section of orient — render as cards: a coloured bar down the left
of every line an item owns, the identifier as a title, a badge aligned into a shared column, and the
text wrapped underneath.
3 active projects
▎ surrogate side-project
▎ Six contributors settled; scope is fixed to the solver rather than the
▎ mesher, and the validation plan is agreed.
▎ next: Draft the validation plan
▎ mesh work
▎ Coarse-mesh run validated end to end; two workstreams in flight.
▎ next: Profile the assembly step
What the bar’s colour means depends on the listing, and it is always the thing that listing exists to
surface: context for project list and orient, staleness for portfolio list and
stewardship list (amber once nothing has been filed for a month), and domain for questions.
search colours every hit alike — relevance is already the ordering.
Commands whose rows are genuinely tabular keep their tables — status, commitments, orient’s
commitments section, and a portfolio’s evidence list all have short, aligned fields where a column
beats a card. show commands keep their plain line shape too: a bar earns its space by marking where
one item ends and the next begins, and a detail view has only one item.
note list is untouched. It prints one bare path per line because it exists to be piped into other
tools, and a header or a gutter would break that.
Colour
| Setting | Effect |
|---|---|
--color auto | Default. Colour only when stdout is a terminal. |
--color always | Colour even when redirected — for cdno project list --color always | less -R. |
--color never | Never colour. |
--colour is accepted as a spelling of the same flag.
Under auto, three environment variables are consulted, in this order:
NO_COLOR(set and non-empty) turns colour off.CLICOLOR=0turns colour off.CLICOLOR_FORCE(set and non-empty) turns colour on even when redirected.
NO_COLOR deliberately outranks CLICOLOR_FORCE: NO_COLOR is something you export once as a
preference about your own terminal, while CLICOLOR_FORCE is usually set by a harness that has no
standing to override it. An explicit --color outranks all three.
--json output is never coloured, whatever any of the above says. Scripts can pass --color always
safely.
Plain output
cdno open is deliberately outside all of the above. It prints one absolute path, and --list
prints one tab-separated row per note — no cards, no colour, no alignment, whatever the terminal
is. Both are written to be consumed by another program ($(…), fzf, cut), and a colour escape
or a padded column would corrupt them. The tab is load-bearing: it is what fzf --delimiter='\t'
splits on.
Interactive reports
In a terminal, project list, portfolio list, stewardship list, orient, status, and
search follow their output with a picker:
? Inspect a project
❯ surrogate (side-project)
mesh (work)
garden (family)
[↑↓ to move, enter to inspect, Esc to leave]
Choosing a row prints exactly what the matching show command would print, then asks again. Esc or
Ctrl-C leaves, with exit status 0.
search is the exception, and deliberately: choosing a hit opens it in your editor and the
command ends there, rather than returning to the list. Once an editor has the file, coming back to
the search results is not what anyone wants. cdno open’s own picker behaves the
same way.
The listing is always printed first, so the prompt only ever adds to what you would have seen. It is skipped entirely when:
- either stdin or stdout is not a terminal — piped, redirected,
< /dev/null, a background job, or running under CI, which is the usual reason neither end is a terminal, --no-interactiveis passed,--jsonis passed, or- the terminal is narrower than 20 columns, which is too narrow to draw a picker in.
Both streams matter: the listing is written to stdout but the picker reads stdin, so a caller with a terminal on only one end is not offered the prompt. There is no separate CI detection — a CI job has no terminal, and that is what actually decides it.
That makes the same command safe for a person, a shell pipeline, and an AI agent without changing anything about how it is invoked.
Width
On a terminal, output is laid out to the terminal’s width as measured when the command runs. Text with
nowhere to break — a long URL, a long slug, or Thai and Lao, which need dictionary-based word
segmentation cdno does not yet do — runs past the edge rather than being cut, on the grounds that a
path you can copy beats a path that fits. cdno
prints once and exits, so resizing afterwards does not reflow anything already on screen — run the
command again. Everywhere else — piped, redirected, or when the terminal reports no usable size (a
pty opened without one reports zero columns) — it lays out to a fixed 100 columns, so captured output
is deterministic and diffable.
Text from your notes is sanitised before it is laid out — in listings and in show views alike, and
in card titles as well as bodies. Tabs and carriage returns become spaces; other control characters,
including escape sequences, are replaced. A note is data, and without this a stray escape in a note
could repaint the terminal, move the cursor, or draw over the card’s own gutter. The raw markdown is
of course untouched, and cdno note still prints paths verbatim.
Frontmatter fields
Every note begins with a YAML frontmatter block. Cuaderno parses it into a typed structure — if it
parses, it’s valid (see Business rules). This page lists the fields
per note type. ? marks an optional field.
Notes that
cdnocreates are already well-formed. You mainly need this when hand-authoring or migrating notes — andcdno normalisewill reorder keys to the canonical order for you.
daily
type: daily
date: 2026-04-25
tags: [] # auto-populated
weekly
type: weekly
week: 2026-W17
date_start: 2026-04-20
date_end: 2026-04-26
monthly
type: monthly
month: 2026-04
date_start: 2026-04-01
date_end: 2026-04-30
project
type: project
context: work # work | side-project | university | family | household | legal | personal
status: active # active | parked
created: 2026-04-25
core_question?: "[[questions/research/surrogate-cost]]"
action
type: action
status: active # active | completed | blocked | dropped
project: surrogate-model
energy: deep # deep | medium | light
milestone?: "[[...]]"
due?: 2026-05-10
criteria?: "Definition of done"
blocker?: "What it's waiting on"
created: 2026-04-25
completed?: 2026-05-01
tags?: []
portfolio
type: portfolio
question: "Sparse vs dense attention OOD"
created: 2026-03-01
project?: "[[projects/surrogate-model]]"
evidence
type: evidence
created: 2026-03-15
source: "Chen et al. 2025"
portfolio: sparse-vs-dense-attention-ood
origin: "[[projects/surrogate-model]]"
kind?: pdf # only for attachment stubs (pdf | image | video | …)
stewardship
type: stewardship
context: personal # one of the life-domain contexts
tracking
type: tracking
stewardship: health
activity: gym
date: 2026-04-06
duration_min?: 60
routine?: "[[stewardships/health/routines/upper-body-a]]"
question
type: question
domain: research # research | life
status: active # active | parked | answered | retired
created: 2026-04-25
updated?: 2026-05-01
commitment
type: commitment
status: active # active | completed | dropped
due: 2026-06-01
created: 2026-04-25
completed?: 2026-06-01
context: personal
project?: icml-paper
stewardship?: finances
Extending schemas
You can require additional fields per type via config.toml ([schemas.<type>] extra_required) —
see Configuration reference. Required built-in fields are always enforced on top.
Configuration reference
The vault’s settings live in .cuaderno/config.toml, written by cdno init. Every key is optional —
defaults are applied when omitted. For the conceptual tour, see
Configuration.
Full example
[vault]
name = "My Research Vault"
max_active_projects = 5 # the active-project cap
# Glob patterns excluded from the index (search, lint, link checks).
# Additive only — no "!" negation. Matched against vault-relative paths.
# NEVER deletes files; only scopes what the index considers.
ignore = ["CLAUDE.md", "README.md"]
# Per-type extra required frontmatter fields. Built-in required fields
# are always enforced; these add vault-specific requirements (cdno lint).
[schemas.project]
extra_required = ["collaborators"]
[schemas.evidence]
extra_required = []
# Typed frontmatter fields for a built-in type. Recognised by the desktop
# Templates editor and type-checked by `cdno lint`.
[schemas.daily.fields.meds]
type = "bool" # bool | int | float | string | date
default = false # static, type-checked against `type`
[schemas.daily.fields.mood]
type = "string"
values = ["low", "ok", "good"] # allowed values (a string constraint)
default = "ok"
# How an activity's tracked numbers are read back. Each metric declares how
# it collapses to one point per date; without a declaration the activity's
# series come from its body table, with each column summed.
[tracking.practice]
records = "detail" # frontmatter key holding repeated records
group_by = "subject" # one series per distinct value
[tracking.practice.metrics.minutes]
aggregate = "sum" # a TOTAL
unit = "min"
[tracking.practice.metrics.focus]
aggregate = "mean" # a RATING; sum would grow with how often you log
# Static template variables — resolve in any custom template ({{author}}).
[variables]
author = "A. Researcher"
# Prompted variables — gathered at note creation (--var, prompt, or error).
[variables.prompt]
collaborators = "Who are the collaborators?"
Keys
| Key | Type | Default | Purpose |
|---|---|---|---|
vault.name | string | "My Vault" | A human label for the vault. |
vault.max_active_projects | integer | 5 | The active-project cap. |
ignore | list of globs | [] | Files the index skips. Additive; never deletes. See Ignore globs. |
schemas.<type>.extra_required | list of strings | [] | Extra required frontmatter fields for that built-in note type, enforced by cdno lint. |
schemas.<type>.fields.<name> | table | — | A typed frontmatter field for a built-in note type (type, default, required, values, settable, log_on_change). Recognised by the Templates editor, type-checked by cdno lint, and (when settable) writable via cdno frontmatter set. See Typed schema fields. |
tracking.<activity> | table | — | Declares how an activity’s tracked numbers are read back (records, group_by, and a metrics.<name> table per metric). Without one, the activity’s series come from its body table with each column summed. See Tracking. |
note_types.<name> | table | — | Declares a config-defined custom note type (folder, required/optional fields, template, …) — a schema-only type for entities the built-ins don’t cover. See Custom note types. |
variables.<name> | string | — | Static template variable; resolves in any custom template (per-type values win on name clash). |
variables.prompt.<name> | string | — | Prompted template variable; the value is the prompt text. Gathered at creation from --var name=value, an interactive prompt, or a static [variables] default; errors if none supplies it. |
Ignore globs
ignore lists files that live in the vault directory but are not notes — repo scaffolding like
CLAUDE.md or README.md. They are excluded from the index, and therefore from search, lint and
backlinks as well. The files are never touched on disk.
Patterns are matched against each file’s vault-relative path:
| Pattern | Matches |
|---|---|
CLAUDE.md | that file at the vault root |
**/*.draft.md | a .draft.md at any depth |
folder/*/** | everything one or more levels below folder/<anything>/ |
folder/*/*/** | everything two or more levels below folder/<anything>/ |
The last two are the trap worth knowing. * stays inside one path segment but ** is recursive, so
portfolios/*/** does not mean “the level below a portfolio” — it matches the portfolio’s own notes
as well as anything nested under them. A glob written that way excludes every note in the folder it
was meant to tidy, and because an unindexed note is also unsearchable and unlinkable, the symptom
looks like a broken view rather than a misconfigured vault.
Two things guard against that:
cdno reindexprints how many files the globs excluded.- The desktop app shows a dismissible notice when the count looks disproportionate — a lone
CLAUDE.mdstays silent, a glob swallowing a large share of the vault does not.
If notes go missing, clear the pattern and run cdno reindex: every row comes back.
Note that attachment artefacts filed into a portfolio need no ignore entry — they are excluded
automatically, by location. See vault structure.
Typed schema fields
[schemas.<type>.fields.<name>] declares a typed frontmatter field on a built-in note type. It
is the richer sibling of extra_required: instead of just a name, each field carries a type (and
optionally a default and an allowed-value set). Four things consume it today:
- the desktop Templates editor recognises the field, so a custom template referencing
{{<name>}}no longer warns “renders literally”; - note creation populates the field’s
defaultat create — a custom template referencing{{<name>}}renders that default (a field with no default rendersnull), so the value lands in the new note’s frontmatter instead of a literal{{<name>}}; cdno linttype-checks the field — a note whose value doesn’t match the declared type (or isn’t one ofvalues) gets a warning;- the
set_frontmattersetter (cdno frontmatter set, MCPset_frontmatter) writes the field through the index when it is markedsettable = true— see thecdno frontmatterreference.
[schemas.daily.fields.meds]
type = "bool" # bool | int | float | string | date
default = false # optional; static, type-checked against `type`
settable = true # optional; allow `set_frontmatter` to write it (default false)
log_on_change = true # optional; stamp a daily-log line when it changes
[schemas.daily.fields.mood]
type = "string"
values = ["low", "ok", "good"] # optional; allowed values (only valid on a string)
default = "ok"
required = false # optional; default false
| Field key | Type | Default | Purpose |
|---|---|---|---|
type | "bool" | "int" | "float" | "string" | "date" | (required) | The field’s scalar type. An unknown value is a hard load error. |
default | matching type | — | A static default value, type-checked at load. Populated at create when a custom template references {{<name>}}. A date is a quoted "YYYY-MM-DD". |
required | bool | false | Reserved for create-time enforcement (a later release); parsed now, but inert — it does not yet block creation. |
values | list of strings | — | An allowed-value constraint. Valid only on a string field. |
settable | bool | false | Whether set_frontmatter (cdno frontmatter set, MCP set_frontmatter) may write this field. Default-deny: absent or false means not settable. Never overrides an engine-owned key (type, status, a period key) — those stay blocked regardless. |
log_on_change | bool | false | When a settable field’s value actually changes, stamp a key: old → new line into today’s daily note in the same commit. |
Notes and limits:
- Defaults are static — there is no
"today"token; adatedefault is a literal calendar date. - A field only lands in frontmatter if a custom template references it — rendering substitutes
the
{{<name>}}tokens a template contains; it never adds a frontmatter line. The shipped built-in templates can’t reference vault-specific fields, so populate a declared field by adding a custom.cuaderno/templates/<type>.mdthat references{{<name>}}. - A create-path value wins over a declared default — if the note type’s create path already
supplies a value for that name (an engine-supplied placeholder), that value takes precedence and
the declared default does not apply. Likewise a
[variables]static var of the same name wins over a schema default. - A
[variables.prompt]name is owned by the prompt — if a field name is also a prompted variable, its value is collected via the prompt (from--var, an interactive prompt, or a static default), and the schema default is not used. This ensures a supplied answer is never discarded. - No
enumtype — model a closed set as astringwithvalues. intandfloatare distinct —intrejects anything with a fractional part, so a currency amount, a measurement or a rate wantsfloat. Afloatfield accepts a whole number too (82and82.5both validate), because a round reading is written without a decimal point. A non-finite default (nan,inf) is a load error — those values have no meaning as data.- List fields are reserved but not yet implemented — a
list = trueis a load error today. - Engine-owned keys are protected — you can’t declare a field named
type, or a calendar type’s own period key (daily→date,weekly→week,monthly→month); the vault refuses to open.set_frontmatteradditionally refuses to writestatusfor every type — even if a vault declares astatusfieldsettable = true— so the lifecycle commands stay its sole writers. extra_requiredstill works and is equivalent to an untyped, non-requiredstringfield; on a name clash an explicitfieldsblock wins.- A malformed field declaration (unknown
type, a mistyped key,valueson a non-string, adefaultthat doesn’t type-check) fails at vault-open, like every other config error.
Tracking
[tracking.<activity>] declares how an activity’s numbers are read back. Without one, a tracking
note’s series come from the first table in its body, with each column summed — right for a rep
sheet, wrong for a balance or a rating. Declaring an activity moves it to frontmatter, where each
metric says how it collapses.
[tracking.practice]
records = "detail" # frontmatter key holding repeated records
group_by = "subject" # one series per distinct value of this field
[tracking.practice.metrics.minutes]
type = "int"
aggregate = "sum" # a TOTAL
unit = "min"
plot = "column"
[tracking.practice.metrics.focus]
aggregate = "mean" # a RATING - a sum would grow with how often you log
| Key | Where | Purpose |
|---|---|---|
records | activity | Frontmatter key holding a sequence of flat records. Omit for plain scalars read straight off the entry. |
group_by | activity | Record field the series split on — a category, a subject, a person. One series per distinct value. |
at | record | Not a config key but the per-record field that orders a set, so last reads the reading you meant. HH:MM, HH:MM:SS, or a 12-hour time with a meridiem. Needs no quoting — the colon keeps it text — but a colon-less at: 1800 is a number and never reaches the parser. All-or-nothing: cdno lint reports a value it cannot use, or a set only partly stamped. |
type | metric | bool | int | float | string | date. Optional. |
aggregate | metric | sum | mean | last | max | min. Defaults to sum. |
group_by | metric | Overrides the activity’s. "none" collapses across records for an entry-level series. |
derived | metric | An expression computing this metric from sibling fields, e.g. "km * rate_per_km". Evaluated per record, before aggregation. Declare type on it and the vault refuses to open. |
unit | metric | Display unit (min, kg, EUR). Carried through to the chart and the MCP series. |
label | metric | Display name for the series, when the metric’s key is not what you want on a chart (resting_hr → Resting heart rate). |
plot | metric | none | line | column | area | scatter. Defaults to none. Chooses the mark the chart draws, and whether the desktop draws it at all — see the note below. |
Derived metrics
Some tracked quantities are products of others — a cost from a rate and a distance, a load from a weight and a count. Rather than making whoever writes the entry pre-compute them, declare the expression:
[tracking.commute.metrics.km]
aggregate = "sum"
[tracking.commute.metrics.rate_per_km]
aggregate = "last"
[tracking.commute.metrics.cost]
derived = "km * rate_per_km"
aggregate = "sum"
unit = "EUR"
It is evaluated per record, then aggregated — not derived from the aggregates. With two trips
of 10 km at 0.50 and 20 km at 0.25, cost is 10.00; deriving from the totals would give a
different, wrong number the moment the rate varies.
The grammar is deliberately tiny — one binary operation, nothing else:
expr := operand OP operand
operand := field-name | number
OP := '+' | '-' | '*'
- No
/. Division is the one operator that manufactures NaN and infinity, and those must never reach an aggregate. Multiply by the reciprocal, or pre-compute the ratio. - No parentheses, calls, chaining or recursion, and no deriving from another derived metric.
- Field names are letters, digits and
_, not starting with a digit. Hyphens are excluded becausea-bwould be indistinguishable from a subtraction. - Numbers are plain decimal — digits, an optional leading
-, an optional.. Exponent notation (1e-3) is not supported; write the value out in full. - Every operand must name a metric the same activity declares. A typo is a vault-open error
naming the field, rather than a silently empty chart. The cost of that requirement: an operand
that exists only to be multiplied — a rate, say — still becomes a metric of its own. Leave its
plotundeclared (the default) and the desktop will not chart it alongside the result — see below. typemust be omitted — the output is numeric by construction, so declaring one can only contradict it.- A record missing an operand contributes nothing — a gap, on the same rule as a plain metric. So does a result that is not finite.
Choosing the aggregate is the whole point, and it follows from what the number is:
| Kind | Examples | Aggregate |
|---|---|---|
| Total | amount spent, pages read, minutes practised | sum |
| Level | account balance, a measurement, a top set | last, max |
| Rate or rating | a score out of ten, perceived difficulty | mean |
Notes and limits:
- An absent
[tracking]section is not an error — an undeclared activity keeps being served from its body table, so nothing forces a migration. - Declaring is checked at vault-open, not at first chart render: an unknown
aggregate, a mistyped key, or a blankrecords/group_byfails when the vault is opened, naming the key. - An activity may declare no metrics at all. That is a complete use — recording that something happened, with nothing to aggregate.
windowis reserved for a future time-reduction axis (month-over-month deltas, rollups) and is a load error today, so adding the behaviour later is not a breaking change.- A declared activity’s body table is no longer read once its frontmatter yields a series, so
the same metric can never appear twice under two disagreeing numbers. This rule is unconditional
— it keys on the frontmatter derivation’s full produced set, not on what any individual metric’s
plotsays, so a declared-but-unplotted metric still suppresses its body-table equivalent. plotchooses the mark, and gates whether the desktop draws it. A declaredline/columnis used as the chart’s mark (anareaorscatterresolves to the closest of the two the chart draws).plot = "none"— the default for a declared metric that names no mark — is still emitted and still queryable over MCP, but the desktop leaves it out of the chart pane. Declaring an activity is an explicit act, and is allowed to change what is drawn: its frontmatter series replace its body-table ones (the rule above, unaffected by this), and only the metrics that opt into a mark are charted.
Templates
Templates live in .cuaderno/templates/ and are pure variable substitution. cdno init writes one
starter (daily.md); other types use their built-in default until you add a file. cdno selects the
most specific template that exists: a custom variant (e.g. tracking-gym.md), then a custom type
(e.g. project.md), then the built-in variant default, then the built-in type default. Template
field order is the canonical order
cdno normalise enforces.
The per-type placeholders that resolve at creation, with a worked example, are in Customising templates and frontmatter. An unknown placeholder is left verbatim in the note.
Static [variables] resolve in any custom template (e.g. {{author}}). Prompted
[variables.prompt] are gathered at creation (via --var name=value, an interactive prompt, or a
static default) — see the
tutorial.
Editing from the desktop app
You can edit .cuaderno/config.toml directly from the desktop app’s Config view, without
hand-editing the file. It offers a Raw text editor and a structured Form for note types and
schema extensions; Check dry-runs the same validation the app runs when it opens a vault.
Saving is gated so an edit — from either view — can never leave the vault unopenable:
- The whole candidate is validated first — the exact check the app runs at open (TOML parse,
ignoreglobs, and the[note_types.*]/[schemas.*]rules). If it would not reopen, the save is refused and the file is left untouched. - A content-hash compare-and-swap then guards against a concurrent hand-edit: if the file changed on disk since the editor read it, the save is refused with a “changed on disk — reload” notice rather than overwriting the newer version.
- The vault is then reloaded live, so the edit applies with no restart. A Raw save writes the
buffer verbatim; a Form save applies a surgical edit to just the table it changed — either
way comments, key order, and the
[variables]block are preserved.
The full walkthrough of the Config view is in Editing the config in the app.
See also
- Customising templates and frontmatter — the tutorial.
- Configuration — the conceptual overview.
- Frontmatter fields — what
extra_requiredextends. - Custom note types — the
[note_types.*]table in full.
Custom note types
Cuaderno ships twelve built-in note types (see Note types). If you need
an entity the built-ins don’t cover — people, books, clients, recipes — you can declare your own
custom note type under [note_types.<name>] in .cuaderno/config.toml. No recompile, no plugin.
What a custom type is (and is not)
A custom type is schema-only. It gives you:
- a folder its notes live in,
- enforced
required/optionalfrontmatter fields (checked bycdno lint), - an optional template,
- canonical frontmatter ordering (
cdno normalise), - full participation in indexing, full-text search, backlinks, and
cdno note.
It does not get bespoke behaviour. The 5-project cap, project state history, commitment
aggregation, tracking streams, and action lifecycle belong to specific built-in types and are not
available to custom types. A custom type is therefore invisible to cdno orient, the project cap,
and the commitments view — by design. If you need that behaviour, use (or extend) a built-in type.
Declaring a type
[note_types.person]
folder = "people" # required — vault-relative, must not be a built-in folder
required = ["name"] # fields that must be present and non-null (lint errors otherwise)
optional = ["role", "org"] # fields that may be present; part of the canonical order
template = "person.md" # optional; defaults to "<name>.md" under .cuaderno/templates/
append_only = false # optional; accepted now, lint enforcement is a later addition
title_field = "name" # optional; which frontmatter field holds the display title (default: the H1)
date_field = "met_on" # optional; which field carries the note's date (for date-filtered search)
Validation runs at vault-open, so a malformed declaration fails fast. Cuaderno rejects a type whose:
folderis empty, has surrounding whitespace, escapes the vault (.., absolute,\), or collides with a built-in folder (projects,journal, …) or another custom type’s folder;templateis not a bare filename;title_field/date_fieldnames a field that isn’t inrequired/optional;- name shadows a built-in type (
project,daily, … — case-insensitive). Built-in names are reserved so a straytype:typo can’t silently mint a type.
Creating notes
cdno note create person --title "Ada Lovelace" --field name=Ada --field role=advisor
cdno note list person
--field name=value is repeatable; each key must be a declared required/optional field, and
every required field must be supplied. --var name=value supplies a template’s
prompted variables.
The note is written to <folder>/<slug(title)>.md. If the type has a template
(.cuaderno/templates/person.md), it is rendered; otherwise Cuaderno synthesises a minimal note
— a frontmatter block of your fields plus a # <title> heading — so a type works before you author
its template. (Field values are always emitted as strings, so a value with a colon, #, or newline
round-trips safely; author a template if you need richer frontmatter shapes.)
From an MCP client, the equivalent tool is create_custom_note ({ type_name, title, fields, vars }).
Discovering placeholders and searching
cdno templates vars personlists the{{placeholders}}apersontemplate may reference — its create-path built-ins (title,slug,created,date) plus your declared fields.cdno templates eject persondoes not apply — a custom type has no built-in template to materialise; author.cuaderno/templates/person.mdby hand.cdno search <query> --type personfilters results to that type.--typeaccepts any built-in or custom name; a name that is neither errors with the valid set. Shell completion offers your vault’s types.
Relationship to [schemas.*]
[note_types.*] defines a new type; [schemas.<builtin>] extends a built-in with
extra_required fields. They are separate tables with separate purposes — a name under
[note_types] may not be a built-in, and [schemas.<custom>] has no effect (a custom type’s
required fields come from its own required list).
A worked example
Tracking people walks a person type end to end — declaring it,
creating people, and linking them from your notes to answer “what was my last interaction with X?”.
Recurrence syntax
Periodic commitments on a stewardship (--every on
cdno stewardship add-periodic, or recurrence on the MCP
add_periodic_commitment tool) use a small canonical vocabulary.
| Value | Meaning |
|---|---|
daily | Every day |
weekly | Every week |
monthly | Every month |
every N months | Every N months (e.g. every 3 months for quarterly) |
yearly | Every year |
Quote any value containing a space on the command line.
Examples
cdno stewardship add-periodic --stewardship health --title "Dental check-up" --every "every 6 months" --next 2026-09-01
cdno stewardship add-periodic --stewardship finances --title "File quarterly taxes" --every "every 3 months" --next 2026-07-15
cdno stewardship add-periodic --stewardship health --title "Annual physical" --every yearly --next 2026-11-01
Each line becomes a row in the dashboard’s ## Periodic Commitments section and feeds the aggregated
cdno commitments view, advancing to its next occurrence as dates pass.
See also
Troubleshooting
Common situations and how to resolve them.
“not inside a Cuaderno vault”
A command can’t find a vault. Cuaderno discovers one by walking up from the current directory for a
.cuaderno/ folder, then falls back to CUADERNO_VAULT_PATH. Fix one of:
cd ~/notebook # run from inside the vault, or
cdno --vault ~/notebook <command> # point at it explicitly, or
export CUADERNO_VAULT_PATH=~/notebook
See Initialise a vault.
Search or links look out of date
The index is a cache; it’s reconciled automatically each run, but a large external edit or a sync conflict can occasionally confuse it. Rebuild it:
cdno reindex
The Markdown files are always authoritative, so a rebuild is safe. See Business rules.
lint is failing
Run it to see the specifics:
cdno lint # errors fail; warnings are listed but non-fatal
cdno lint --strict # warnings fail too
Errors are usually an unknown type: or invalid frontmatter; warnings are typically broken
wikilinks. Fix the reported file, or run cdno normalise if the issue is field
ordering. See Frontmatter fields.
cdno now says nothing is started, but I started something
Two causes, and cdno lint tells them apart.
If you wrote the log line by hand, it is almost certainly not in the shape the readers accept. It has to be exactly:
- **09:30**: started [[surrogate-model]] — Draft the methods section (deep)
The - bullet and the **HH:MM**: stamp are what make it a log entry at all, and the separator
must be a real em dash (U+2014), not a hyphen. A near-miss is skipped in silence by design — prose
beginning “started something” must never register as a focus — so cdno lint reports it instead,
naming the line and the likely cause. Run it and fix what it points at.
Otherwise, check the date. The focus is read from today’s daily note only, so a start logged yesterday and never closed does not carry over.
cdno now names the wrong action
You probably ran cdno action promote between starting the
action and closing it. Promotion rewrites the bullet, so the start can no longer be paired with it:
cdno now keeps naming the old text for the rest of the day, and action complete and action drop
both match nothing. Close an action before promoting it, or re-run the start afterwards.
A prompt appears when I wanted automation (or vice versa)
Write commands prompt for missing required flags only in an interactive terminal. In scripts, pipes, or CI they error instead. Force non-interactive behaviour explicitly:
cdno project create --title "X" --context work --no-interactive
Conversely, if a command errors about a missing flag when you expected a prompt, your stdout probably isn’t a TTY (it’s piped or redirected). See CLI overview.
Can’t create a sixth project
That’s the five-project cap. Park an active project first:
cdno project park --slug some-active-project
cdno project activate --slug the-one-you-want
(New projects created while at the cap are created parked rather than rejected.)
--json output won’t parse
--json is only honoured by read verbs and the write verbs that emit a result; maintenance and
interactive commands (init, lint, reindex, normalise, triage, review, weekly) ignore
it. Under --json, write verbs run non-interactively, so there are no prompts mixed into the output.
See JSON output.
Claude doesn’t see the tools
For the MCP server (cdno-mcp):
- Make sure
cdno-mcpis on the client’sPATH, or use an absolute path in the configcommand. - Set
CUADERNO_VAULT_PATH(or rely on working-directory discovery). - Restart the client after editing its MCP config.
See Connect to Claude.