Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

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

SurfaceWhat it isStart here
cdno CLIThe terminal tool for the daily loopQuickstart
cdno-mcp serverAn MCP server so Claude can read and write your vaultConnect 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:

  1. The --vault <PATH> flag (highest priority — overrides everything).
  2. The CUADERNO_VAULT_PATH environment 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_PATH points 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 cdno prompts 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

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" }
    }
  }
}
  • command must resolve on the client’s PATH. If it doesn’t, use the absolute path (e.g. /opt/homebrew/bin/cdno-mcp, or target/release/cdno-mcp for a source build).
  • CUADERNO_VAULT_PATH tells 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 46 tools, grouped by purpose:

  • Context-gathering readsget_orientation, get_project_context, get_portfolio_contents, get_weekly_context, search_notes, read_daily_note, and more.
  • Writesappend_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 lifecyclecreate_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

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

  1. 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-app or a fresh .dmg.

  2. 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 run cdno init first 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_PATH environment 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 cuaderno
    

    When 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. 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.toml in 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_frontmatter may 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_projects under [vault], and the top-level ignore globs — 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 six concrete pillars. Each pillar maps onto one or more note types the tool manages.

The six pillars

  1. A chronological log. A dated, append-only record of what you did and thought — the single source of truth. In Cuaderno this is the daily and weekly journal.
  2. Evidence portfolios. A dossier per important question that accumulates evidence — papers, experiment results, conversation notes — over months and years.
  3. Important questions. Hamming’s discipline, made first-class: name the questions that matter, keep them visible, re-read them often.
  4. Project maps. Lightweight overviews of active work — a current state, next actions, and milestones. Not a Gantt chart.
  5. Stewardships. Small, bounded, perpetual responsibilities (health, finances, a recurring service) — long-haul, low-drama, optionally with habit tracking.
  6. A commitments register. Promises to others (and dated promises to yourself), with deadlines — distinct from a to-do list.

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, commitments get fulfilled or dropped — all first-class, all reversible.
  • 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 orient answers “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

PillarNote type(s)Primary commands
Chronological logdaily, weeklylog, orient, review
Evidence portfoliosportfolio, evidenceportfolio, file
Important questionsquestionquestion, questions
Project mapsproject, actionproject, action
Stewardshipsstewardship, trackingstewardship, track
Commitments registercommitment (+ computed)commit, commitments

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.

TypeLives inMutabilityPurpose
dailyjournal/<year>/daily/Append-onlyOne day’s chronological log
weeklyjournal/<year>/weekly/Append-onlyWeekly review (Wins, Challenges, One Improvement, This Week’s Goal)
monthlyjournal/<year>/monthly/Append-onlyMonthly review (Wins, Themes, Next Month’s Focus) + links to the month’s weeks
projectprojects/ (+ _parked/)MutableProject map: state, next actions, milestones, waiting-on
actionactions/actions/_done/<year>/Mutable while open, then archivedManifest note for an action-as-investigation
portfolioportfolios/<slug>/_index.mdOccasionally editedIndex/summary of an evidence dossier
evidenceportfolios/<slug>/Append-onlyA single piece of evidence (paper, result, note)
stewardshipstewardships/ (flat or folder)Occasionally editedDashboard for a perpetual responsibility
trackingstewardships/<slug>/tracking/Append-onlyOne time-series entry (a gym session, a measurement)
questionquestions/research/ or questions/life/Status transitionsAn important research or life question
commitmentcommitments/commitments/_done/Moves on completionA 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. On fulfilment it moves to commitments/_done/<year>/. 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.md is the identity note of a folder — the portfolio note inside a portfolio folder, the stewardship note 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 expanded stewardships/<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 templates cdno fills when scaffolding a note. The built-ins are written here at init so 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:

  1. Project milestones marked with a hard deadline (--hard).
  2. Stewardship periodic commitments (the recurring lines on a stewardship dashboard).
  3. Standalone commitment notes in commitments/.
  4. 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:

ContextTypical use
workYour main job
side-projectPersonal projects outside work
universityStudies, coursework, a degree
familyFamily responsibilities
householdRunning a home
legalPaperwork, contracts, official matters
personalHealth, 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:

EnergyMeaning
deepHeavy, uninterrupted focus (real thinking, hard implementation)
mediumModerate focus (routine progress, review)
lightLow 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 collaborators on every project), enforced by cdno 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:

  1. a custom variant template (for tracking, e.g. tracking-gym.md), then
  2. a custom type template (e.g. project.md), then
  3. the built-in variant default, then
  4. 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

# Tick off a finished action (substring match on the bullet):
cdno action complete --project surrogate-model --query "feature set B"

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. 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 dated 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"

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) 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

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"

Inline vs. manifest — when to promote

Use an inline bullet (default)Promote to a manifest note
A discrete next stepAn investigation spanning multiple days
Done in one sittingProduces evidence / artefacts to link
No supporting detail neededNeeds 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):

  1. Project milestones marked --hard.
  2. Stewardship periodic commitments (recurring dashboard lines).
  3. Standalone commitment notes (below).
  4. 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

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"
  • --stewardship can be omitted when there’s exactly one expanded stewardship — Cuaderno defaults to it. With more than one, it’s required.

  • --routine links a reference doc from the stewardship’s routines/ folder into the entry — but only when the resolved template has a routine: field (the gym.md example variant does; the generic default has none, so it no-ops there).

  • --content is optional; leave it empty and fill the entry’s tables in afterward.

  • --at files 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-06
    

    Filing 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.

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:

KindExamplesAggregate
Totalamount spent, pages read, minutes practisedsum
Levelaccount balance, a measurement, a top setlast, or max for a high-water mark
Rate or ratinga score out of ten, perceived difficultymean
Occurrencea call, a visitnone — 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 = 15
  • practice · 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:

  1. cdno status — skim active projects and their top actions.
  2. cdno commitments --weeks 2 — what’s due in the next fortnight?
  3. cdno stewardship list — any habit looking stale?
  4. 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.

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=value flag).

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:

  1. a custom variant file — <type>-<variant>.md (tracking uses this: the variant is the activity slug, e.g. tracking-gym.md), then
  2. a custom type file — <type>.md, then
  3. the built-in variant default, then
  4. 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 init writes 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 is cdno templates eject <type> — e.g. cdno templates eject project writes .cuaderno/templates/project.md matching 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 normalise only reorders frontmatter keys; it won’t add a new section like ## Risks to 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: null above 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 typeAvailable {{placeholders}}
dailydate, heading, weekday, day_name, week
weeklyweek, week_num, year, date_start, date_end
monthlymonth, month_name, year, date_start, date_end, weeks
projecttitle, context, status, created, core_question
actiontitle, slug, project, energy, status, created, due, completed, milestone, criteria, blocker, tags
portfolioquestion, project, created
evidencesource, origin, portfolio, content, created
stewardshipname, context
trackingstewardship, activity, activity_title, routine, content, date, date_long
questionquestion, domain, created, updated
commitmenttitle, context, status, due, project, stewardship, created, completed
inboxbody, created

You can use any subset, in any order, and add as much static Markdown around them as you like.

Daily specifics. weekday and day_name are aliases for the same value — the weekday name (e.g. Sunday) — so use whichever reads better. week is the ISO-week label YYYY-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 example cdno templates vars tracking. See the templates reference.

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=value on the command (repeatable), e.g. cdno project create --title T --context work --var ticket=ABC-123;
  • otherwise, in an interactive TTY, cdno asks (“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 --var can’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.
  • --var only applies to templated notes. cdno file --attach (the attachment stub) and a plain action add (no --note) aren’t templated, so --var is 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 --var over MCP). A [variables.prompt] placeholder in one of those templates fails at creation (an UnresolvedPrompts error) 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_required with a template default and the occasional cdno lint and 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

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.

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:

FlagEffect
--vault <PATH>Operate on the vault at PATH. Overrides both discovery and CUADERNO_VAULT_PATH.
--jsonEmit machine-readable JSON instead of a formatted table (see below).
--no-interactiveNever prompt; a missing required argument is an error. Implicit when stdout isn’t a TTY.
-h, --helpPrint help.
-V, --versionPrint 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 flagcdno prompts for it, then asks you to confirm before writing.
  • Non-interactive (piped, redirected, in CI, 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.

JSON output

--json makes any supported verb emit structured output:

  • Read verbs (commitments, questions, status, orient, search, and the list/show verbs of project/portfolio/stewardship, plus action list) emit their listing or detail object.
  • Write verbs (log, capture, file, track, and the create/update verbs of project, 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

CommandWhat it does
initCreate a new vault
logAppend a line to today’s daily note
captureDrop a quick note into the inbox
triageProcess inbox captures
orientMorning orientation
statusActive projects + top actions
weeklyShow the weekly note
monthlyShow the monthly note
commitmentsAggregated deadlines
questionsList active questions
searchFull-text search
reviewGuided weekly/monthly review
projectManage project maps
actionManage next actions
portfolioManage evidence portfolios
fileFile evidence into a portfolio
questionManage question notes
stewardshipManage stewardships
trackFile a tracking entry
commitManage standalone commitments
lintValidate the vault
reindexRebuild the index
normaliseReorder frontmatter
completionsShell-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

ArgumentDescription
[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

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

ArgumentDescription
<MESSAGE>The log message. Quote it if it contains spaces.

Options

FlagDescription
--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-onlylog only ever adds.

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

ArgumentDescription
<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.

capture.

See also

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

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]

Options

FlagDescription
--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'

get_orientation.

See also

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]

Options

Only the global options. With --json, emits the snapshot as structured data.

Examples

cdno status
cdno status --json | jq '.[].slug'

list_projects — the projects view for AI clients.

See also

cdno weekly

Show the weekly review/plan note: Wins, Challenges, One Improvement, and This Week’s Goal.

cdno weekly [OPTIONS]

Options

FlagDescription
--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.

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

FlagDescription
--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.

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

FlagDescription
--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:.

get_commitments.

See also

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'

get_active_questions — which additionally accepts a domain filter.

See also

cdno search

Full-text search across all notes, ranked best-first, with optional filters by note type, date window, and portfolio.

cdno search [OPTIONS] <QUERY>

Arguments

ArgumentDescription
<QUERY>Search text. Matched case-insensitively; terms are ANDed. Quotes and operators are treated as literal words.

Options

FlagDescription
--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'

search_notes.

See also

cdno review

Guided review rituals.

cdno review [OPTIONS] <COMMAND>

Subcommands

SubcommandDescription
weeklyWalk the retrospective sections into this week’s note and set next week’s goal.
monthlyWalk 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

get_weekly_context, read_weekly_note, upsert_weekly_section, read_monthly_note, upsert_monthly_section.

See also

cdno project

Manage project maps: create, update state, add/complete milestones, manage waiting-on items, park/activate, and list/show. Next actions have their own verb, cdno action.

cdno project [OPTIONS] <COMMAND>

Subcommands

SubcommandDescription
createCreate a new project map
stateUpdate the Current State (auto-logs the previous)
parkMove a project to _parked/
activateBring a parked project back (enforces the cap)
listList active projects
showShow one project
milestoneAdd / complete milestones
waitingAdd / 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.

FlagDescription
--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).

FlagDescription
--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 park

Move an active project to projects/_parked/, freeing a slot against the five-project cap.

FlagDescription
--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.

FlagDescription
--slug <SLUG>Parked project slug.
cdno project activate --slug surrogate-model

cdno project list

List active projects with a state snippet. Honours --json.

cdno project list
cdno project list --json | jq '.[].slug'

cdno project show

Show a compact summary of a single project (any status). Takes the slug as a positional argument. Honours --json (emits the project summary object).

cdno project show surrogate-model
cdno project show surrogate-model --json

cdno project milestone

Manage milestones — dated markers of progress. A --hard milestone is a real deadline counted in cdno commitments.

add--slug, --title, --date <YYYY-MM-DD>, --hard done--slug, --query (case-insensitive substring of the milestone title)

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"

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"

create_project, update_project_state, park_project, activate_project, list_projects, get_project_context, add_milestone, complete_milestone, add_waiting_on, resolve_waiting_on.

See also

cdno action

Manage a project’s next actions: add (optionally as a manifest note), promote a bullet to a note, complete, and list.

cdno action [OPTIONS] <COMMAND>

Subcommands

SubcommandDescription
addAppend a next action to a project
promotePromote a plain bullet to a wikilinked manifest note
completeMark an action done by substring match
listList 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.

FlagDescription
--project <SLUG>Project slug.
--title <TITLE>Action title.
--energy <ENERGY>deep, medium, or light.
--noteAlso 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.

FlagDescription
--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 complete

Mark a next action completed by case-insensitive substring match. A wikilinked bullet also archives its note to actions/_done/<year>/.

FlagDescription
--project <SLUG>Project slug.
--query <QUERY>Substring of the bullet text.
cdno action complete --project surrogate-model --query "feature set B"

cdno action list

List a project’s open action bullets, with attached-note status (active / blocked / completed) inline when present. Honours --json.

FlagDescription
--project <SLUG>Project slug.
cdno action list --project surrogate-model
cdno action list --project surrogate-model --json

add_action, promote_action, complete_action. (Open actions are also visible via get_project_context.)

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

SubcommandDescription
createCreate a portfolio under portfolios/<slug>/
listList portfolios with evidence counts + staleness
showShow a portfolio’s frontmatter + evidence inventory
linkLink 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.

FlagDescription
--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.

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).

FlagDescription
--portfolio <PORTFOLIO>Portfolio slug.
cdno portfolio show --portfolio sparse-vs-dense-attention-ood
cdno portfolio show --portfolio sparse-vs-dense-attention-ood --json

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.

FlagDescription
--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

create_portfolio, get_portfolio_contents (show), link_portfolio_to_question, link_portfolio_to_project.

See also

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

FlagDescription
--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.
--moveWith --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.

file_to_portfolio.

See also

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

SubcommandDescription
createCreate a new question note
parkSet status to parked
answerSet status to answered
retireSet status to retired
activateSet 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.

FlagDescription
--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).

FlagDescription
--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

create_question, set_question_status. (List via get_active_questions.)

See also

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

SubcommandDescription
createCreate a stewardship (flat, or expanded with --tracking)
listList stewardships with variant, tracking count, staleness
showShow a stewardship’s frontmatter + dashboard excerpt
add-periodicAppend a periodic commitment line

create/add-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.

FlagDescription
--name <NAME>Human-readable name (the slug derives from it).
--context <CONTEXT>Life domain (work, household, personal, …).
--trackingCreate 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.

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).

FlagDescription
--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.

FlagDescription
--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

create_stewardship, get_stewardship_tracking, add_periodic_commitment.

See also

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

ArgumentDescription
<ACTIVITY>Activity slug — anything you track (e.g. gym, swim, reading). Selects tracking-<activity>.md if present, else the generic template.

Options

FlagDescription
--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.

create_tracking_entry.

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

ArgumentDescription
<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:

SourceMeaning
suppliedFilled 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.
configA static [variables] entry in .cuaderno/config.toml, available to any template.
promptA [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

ArgumentDescription
<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

FlagDescription
--allEject 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.
--forceOverwrite 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.

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

ArgumentDescription
<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 or false) is rejected.
  • Type-checked. A value that doesn’t parse as the declared type — or isn’t one of a string field’s values — 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 a key: old → new line 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

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

ArgumentDescription
<TYPE>A config-defined custom type (e.g. person). A built-in type is refused — use its own create command.

Options

FlagDescription
--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

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

SubcommandDescription
createCreate an active commitment at commitments/<slug>.md
doneMark a commitment completed and archive it

Both honour --json ({path, message}, non-interactive).


cdno commit create

Create an active commitment note. Optionally attribute it to a project or stewardship.

FlagDescription
--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.

FlagDescription
--slug <SLUG>Commitment slug.
cdno commit done --slug pay-rent

create_commitment, complete_commitment. (View via get_commitments.)

See also

cdno lint

Validate every indexed note and report frontmatter problems. 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”.

cdno lint [OPTIONS]

Options

FlagDescription
--strictTreat 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 → exit 0.
  • 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)

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.db and 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

FlagDescription
--checkReport 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

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

ArgumentDescription
<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-mcp speaks JSON-RPC over stdio — the client launches it as a subprocess. It opens the vault named by CUADERNO_VAULT_PATH, or, if that’s unset, discovers one from its working directory (the same rule as the CLI).
  • cdno-mcp-server speaks MCP Streamable HTTP for remote clients — see The HTTP server for its flags and security model.

The tool surface

The server advertises 46 tools. This reference groups them by purpose:

GroupPageWhat’s in it
Context-gathering readsContext-gathering toolsOrientation, project/portfolio/weekly context, search, reads, lint, triage list
WritesWrite toolsLog, capture, file evidence, project/action/milestone/waiting edits, commitments, tracking, daily/weekly sections
Creation & lifecycleCreation and lifecycle toolsCreate 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

FlagEnvDefaultPurpose
--vault <path>CUADERNO_VAULT_PATHcwdVault root
--bind <addr>CDNO_MCP_BIND127.0.0.1:8787Listen 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
--smokeoffServe a single echo tool holding no vault handle — prove tunnel/auth infrastructure end-to-end with zero vault exposure
--read-onlyoffAdvertise only the context-gathering read tools; mutating tools are absent from the dispatch table entirely
--reconcile-interval-secs <n>CDNO_MCP_RECONCILE_INTERVAL_SECS300Periodic index reconciliation; 0 disables
--git-checkpoint-interval-secs <n>CDNO_MCP_GIT_CHECKPOINT_INTERVAL_SECS60Commit-if-dirty git sweep of the vault — makes every remote write diffable and revertible. 0 disables; warns and no-ops when the vault isn’t a git repo
--access-team-url <url>CDNO_ACCESS_TEAM_URLCloudflare 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_AUDThe Access application’s AUD tag (expected aud claim). Requires --access-team-url

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 send Accept: 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/DELETE on /mcp return 405.
  • Guardrails: request bodies are capped at 1 MiB and in-flight requests are bounded; the Host header 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.

ToolInputsReturns
get_orientationenergy? (deep|medium|light)Commitments due soon, active projects, lapsed stewardship habits, and a suggested starting point. The MCP form of cdno orient.
get_project_contextproject (slug)A project’s state, next actions, milestones, waiting-on items, and links.
get_portfolio_contentsportfolio (slug)Portfolio metadata plus its evidence inventory.
get_weekly_contextdate? (any day in the week)The weekly note’s sections (Wins, Challenges, One Improvement, This Week’s Goal).
get_monthly_contextdate?Monthly context for a strategic scan.
get_stewardship_trackingstewardship, 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_questionsdomain? (research|life)Active question notes, optionally filtered by domain.
get_commitmentslookahead_weeks? (default 2)The aggregated commitments view; overdue always included.
list_projectsAll projects (active + parked) with summaries.
list_note_typesEvery 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_notedate? (default today)The daily log for a date.
read_weekly_notedate? (default this week)The weekly note for an ISO week.
read_monthly_notedate? (default this month)The monthly note for a calendar month.
search_notesquery, note_type?, from?, to?, portfolio?, limit? (default 20)Ranked full-text hits. The MCP form of cdno search.
lintFrontmatter problems across the vault.
triage_inboxPending 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_notes returns the same hit shape as the CLI: path, note_type, title, snippet, score. See JSON output.
  • 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).

Logging, capture, triage

ToolInputsEffect
append_to_logtextAppend a line to today’s daily note. (cdno log)
capturetextDrop a raw note into inbox/. (cdno capture)
discard_inbox_itemslugClear a triaged capture (slug from triage_inbox).

Evidence

ToolInputsEffect
file_to_portfolioportfolio, 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

ToolInputsEffect
update_project_stateproject, new_stateRewrite the Current State (auto-logs the previous).
add_actionproject, title, energy, with_note?, vars?Append a next action; with_note also scaffolds a manifest note (vars applies only then).
promote_actionproject, query, vars?Promote a bullet to a manifest note (substring match).
complete_actionproject, queryComplete an action; archives its note if any.
add_milestoneproject, title, target_date, hard?Add a milestone; hard counts it in commitments.
complete_milestoneproject, queryComplete a milestone (substring match).
add_waiting_onproject, descriptionAdd a waiting-on blocker.
resolve_waiting_onproject, queryResolve a waiting-on item (substring match).

Commitments and tracking

ToolInputsEffect
create_commitmenttitle, due, context, project?, stewardship?, vars?Create a standalone commitment note.
complete_commitmentcommitment (slug)Mark a commitment done and archive it.
create_tracking_entrystewardship, 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

ToolInputsEffect
set_frontmatternote, key, valueSet 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

ToolInputsEffect
upsert_daily_sectionsection (Standup|Intention|Agenda|Meeting), content?, date?, append?Write or append a daily-note section.
upsert_weekly_sectionsection (Wins|Challenges|One Improvement|This Week's Goal), content?, date?, append?Write or append a weekly-note section.
upsert_monthly_sectionsection (Wins|Themes|Next Month's Focus), content?, date?, append?Write or append a monthly-note section.

Notes

  • append? defaults to replacing the section; set it true to 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 optional name -> value map 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

ToolInputsEffect
create_projecttitle, context, core_question?, vars?Create a project (parked if at the active cap). (cdno project create)
create_portfolioquestion, project?, vars?Create an evidence portfolio.
create_questiondomain (research|life), text, vars?Create a question note.
create_stewardshipname, context, expanded?, vars?Create a stewardship; expanded adds a tracking/ folder.
link_portfolio_to_questionportfolio, questionRetrofit a portfolio→question link (backlinks both ways).
link_portfolio_to_projectportfolio, projectRetrofit a portfolio→project link (sets project: and appends to the project’s Links).

Lifecycle

ToolInputsEffect
park_projectprojectMove an active project to _parked/.
activate_projectprojectBring a parked project back (enforces the five-project cap).
set_question_statusquestion, status (active|parked|answered|retired)Transition a question’s status.
add_periodic_commitmentstewardship, title, recurrence, next_dateAppend a periodic commitment to a stewardship dashboard.

Notes

  • context is one of the fixed life domains.
  • recurrence follows the recurrence syntax: daily, weekly, monthly, yearly, or every N months.
  • activate_project enforces the cap — if activating would exceed five active projects, the call fails and the assistant must park one first.
  • vars? is an optional name -> value map 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:

  1. Surfaces context with a read tool — e.g. get_orientation or get_weekly_context.
  2. Talks it through with you, deciding what to do.
  3. 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

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 → [].

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]]"
    }
  ]
}
  • project is null when 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: statusactive/parked/completed, energydeep/medium/light, stewardship variantflat/expanded, contextwork/side-project/household/… So a value is identical whether you read it from cdno --json or over MCP.

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 cdno creates are already well-formed. You mainly need this when hand-authoring or migrating notes — and cdno normalise will 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
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
due: 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

KeyTypeDefaultPurpose
vault.namestring"My Vault"A human label for the vault.
vault.max_active_projectsinteger5The active-project cap.
ignorelist of globs[]Files the index skips. Additive; never deletes. See Ignore globs.
schemas.<type>.extra_requiredlist of strings[]Extra required frontmatter fields for that built-in note type, enforced by cdno lint.
schemas.<type>.fields.<name>tableA 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>tableDeclares 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>tableDeclares 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>stringStatic template variable; resolves in any custom template (per-type values win on name clash).
variables.prompt.<name>stringPrompted 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:

PatternMatches
CLAUDE.mdthat file at the vault root
**/*.draft.mda .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 reindex prints how many files the globs excluded.
  • The desktop app shows a dismissible notice when the count looks disproportionate — a lone CLAUDE.md stays 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 default at create — a custom template referencing {{<name>}} renders that default (a field with no default renders null), so the value lands in the new note’s frontmatter instead of a literal {{<name>}};
  • cdno lint type-checks the field — a note whose value doesn’t match the declared type (or isn’t one of values) gets a warning;
  • the set_frontmatter setter (cdno frontmatter set, MCP set_frontmatter) writes the field through the index when it is marked settable = true — see the cdno frontmatter reference.
[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 keyTypeDefaultPurpose
type"bool" | "int" | "float" | "string" | "date"(required)The field’s scalar type. An unknown value is a hard load error.
defaultmatching typeA static default value, type-checked at load. Populated at create when a custom template references {{<name>}}. A date is a quoted "YYYY-MM-DD".
requiredboolfalseReserved for create-time enforcement (a later release); parsed now, but inert — it does not yet block creation.
valueslist of stringsAn allowed-value constraint. Valid only on a string field.
settableboolfalseWhether 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_changeboolfalseWhen 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; a date default 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>.md that 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 enum type — model a closed set as a string with values.
  • int and float are distinctint rejects anything with a fractional part, so a currency amount, a measurement or a rate wants float. A float field accepts a whole number too (82 and 82.5 both 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 = true is 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 (dailydate, weeklyweek, monthlymonth); the vault refuses to open. set_frontmatter additionally refuses to write status for every type — even if a vault declares a status field settable = true — so the lifecycle commands stay its sole writers.
  • extra_required still works and is equivalent to an untyped, non-required string field; on a name clash an explicit fields block wins.
  • A malformed field declaration (unknown type, a mistyped key, values on a non-string, a default that 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
KeyWherePurpose
recordsactivityFrontmatter key holding a sequence of flat records. Omit for plain scalars read straight off the entry.
group_byactivityRecord field the series split on — a category, a subject, a person. One series per distinct value.
typemetricbool | int | float | string | date. Optional.
aggregatemetricsum | mean | last | max | min. Defaults to sum.
group_bymetricOverrides the activity’s. "none" collapses across records for an entry-level series.
derivedmetricAn 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.
unitmetricDisplay unit (min, kg, EUR). Carried through to the chart and the MCP series.
labelmetricDisplay name for the series, when the metric’s key is not what you want on a chart (resting_hrResting heart rate).
plotmetricnone | 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 because a-b would 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 plot undeclared (the default) and the desktop will not chart it alongside the result — see below.
  • type must 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:

KindExamplesAggregate
Totalamount spent, pages read, minutes practisedsum
Levelaccount balance, a measurement, a top setlast, max
Rate or ratinga score out of ten, perceived difficultymean

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 blank records/group_by fails 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.
  • window is 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 plot says, so a declared-but-unplotted metric still suppresses its body-table equivalent.
  • plot chooses the mark, and gates whether the desktop draws it. A declared line/column is used as the chart’s mark (an area or scatter resolves 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:

  1. The whole candidate is validated first — the exact check the app runs at open (TOML parse, ignore globs, and the [note_types.*] / [schemas.*] rules). If it would not reopen, the save is refused and the file is left untouched.
  2. 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.
  3. 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

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 / optional frontmatter fields (checked by cdno 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:

  • folder is empty, has surrounding whitespace, escapes the vault (.., absolute, \), or collides with a built-in folder (projects, journal, …) or another custom type’s folder;
  • template is not a bare filename;
  • title_field / date_field names a field that isn’t in required/optional;
  • name shadows a built-in type (project, daily, … — case-insensitive). Built-in names are reserved so a stray type: 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 person lists the {{placeholders}} a person template may reference — its create-path built-ins (title, slug, created, date) plus your declared fields.
  • cdno templates eject person does not apply — a custom type has no built-in template to materialise; author .cuaderno/templates/person.md by hand.
  • cdno search <query> --type person filters results to that type. --type accepts 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.

ValueMeaning
dailyEvery day
weeklyEvery week
monthlyEvery month
every N monthsEvery N months (e.g. every 3 months for quarterly)
yearlyEvery 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.

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.

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-mcp is on the client’s PATH, or use an absolute path in the config command.
  • Set CUADERNO_VAULT_PATH (or rely on working-directory discovery).
  • Restart the client after editing its MCP config.

See Connect to Claude.