A Note-Taking System That Works With AI, Not Against It

AI Productivity Tools

Obsidian, Notion, Roam — there’s no shortage of good note-taking apps. But I wanted something where AI could not just read my notes, but write tooling for them. So I built a system that’s about 50 lines of bash, and AI can extend it trivially.

The Idea

This isn’t anti-Obsidian. Obsidian is great, and it uses markdown too. The difference is what happens when you want a new feature.

In most apps, you either wait for it, find a plugin, or learn the plugin API and build it yourself. In a system that’s just files and scripts, you ask Claude “write me a script that does X” and it works. No plugins to learn, no API to integrate—just scripts that operate on files.

When the whole system is transparent, AI can reason about it completely.

The Structure

Notes are plain markdown files with date-prefixed names:

20251210--project-kickoff.md
20251211--api-design-notes.md

Organized in folders by project or area:

notes/
├── active/
│   ├── client-project/
│   ├── side-project/
│   └── personal/
├── archive/
└── scripts/

Active projects live in active/. When a project wraps up, it moves to archive/. The scripts/ folder holds the tooling.

Dependencies: fzf for fuzzy selection, bat for syntax-highlighted previews, ripgrep for fast search. Standard Unix tools.

Five Scripts I Found Useful

These are the actions I needed. Your list might be different — that’s the point.

nn — New Note

Pick a folder, enter a title, creates a dated markdown file and opens it.

#!/bin/bash
 
NOTES_DIR="/Users/daniel/notes"
ACTIVE_DIR="$NOTES_DIR/active"
PICKER="fzf"
 
folders=$(find "$ACTIVE_DIR" -mindepth 1 -maxdepth 1 -type d -exec basename {} \; | sort)
 
if [ -z "$folders" ]; then
    echo "No folders found in $ACTIVE_DIR"
    exit 1
fi
 
folder=$(echo "$folders" | $PICKER --prompt="Select folder: ")
 
if [ -z "$folder" ]; then
    echo "No folder selected"
    exit 1
fi
 
read -p "Title: " title
 
if [ -z "$title" ]; then
    echo "Title cannot be empty"
    exit 1
fi
 
date_stamp=$(date +%Y%m%d)
slug=$(echo "$title" | tr '[:upper:]' '[:lower:]' | tr ' ' '-' | tr -cd 'a-z0-9-')
filename="${date_stamp}--${slug}.md"
filepath="${ACTIVE_DIR}/${folder}/${filename}"
 
echo "# ${title}" > "$filepath"
echo "" >> "$filepath"
 
echo "Created: $filepath"
${EDITOR:-vim} "$filepath"

nf — Find Note

Fuzzy-find notes by filename with preview. Use -p to pick a specific folder first.

#!/bin/bash
 
NOTES_DIR="/Users/daniel/notes"
ACTIVE_DIR="$NOTES_DIR/active"
PICKER="fzf"
 
pick_folder=false
for arg in "$@"; do
  case $arg in
    -p|--pick) pick_folder=true ;;
  esac
done
 
if $pick_folder; then
  folders=$(find "$ACTIVE_DIR" -mindepth 1 -maxdepth 1 -type d -exec basename {} \; | sort)
 
  if [ -z "$folders" ]; then
    echo "No folders found in $ACTIVE_DIR"
    exit 1
  fi
 
  folder=$(echo "$folders" | $PICKER --prompt="Select folder: ")
 
  if [ -z "$folder" ]; then
    echo "No folder selected"
    exit 1
  fi
 
  search_dir="$ACTIVE_DIR/$folder"
else
  search_dir="$ACTIVE_DIR"
fi
 
notes=$(find "$search_dir" -name "*.md" -type f | sort)
 
if [ -z "$notes" ]; then
  echo "No notes found"
  exit 1
fi
 
filepath=$(echo "$notes" | $PICKER --prompt="Select note: " --preview="bat --color=always --style=plain {}")
 
if [ -z "$filepath" ]; then
  echo "No note selected"
  exit 1
fi
 
${EDITOR:-vim} "$filepath"

ns — Search Content

Full-text search across all notes. Opens at the matching line.

#!/bin/bash
 
NOTES_DIR="/Users/daniel/notes"
ACTIVE_DIR="$NOTES_DIR/active"
PICKER="fzf"
 
pick_folder=false
for arg in "$@"; do
  case $arg in
    -p|--pick) pick_folder=true ;;
  esac
done
 
if $pick_folder; then
  folders=$(find "$ACTIVE_DIR" -mindepth 1 -maxdepth 1 -type d -exec basename {} \; | sort)
 
  if [ -z "$folders" ]; then
    echo "No folders found in $ACTIVE_DIR"
    exit 1
  fi
 
  folder=$(echo "$folders" | $PICKER --prompt="Select folder: ")
 
  if [ -z "$folder" ]; then
    echo "No folder selected"
    exit 1
  fi
 
  search_dir="$ACTIVE_DIR/$folder"
else
  search_dir="$ACTIVE_DIR"
fi
 
result=$(rg --line-number --color=always . "$search_dir" | $PICKER --ansi --prompt="Search content: " --preview="bat --color=always --style=plain --highlight-line {2} {1}" --delimiter=: --preview-window=+{2}-10)
 
if [ -z "$result" ]; then
  echo "No match selected"
  exit 1
fi
 
filepath=$(echo "$result" | cut -d: -f1)
line=$(echo "$result" | cut -d: -f2)
 
${EDITOR:-vim} "+$line" "$filepath"

np — New Project

Create a new folder in active/.

#!/bin/bash
 
NOTES_DIR="/Users/daniel/notes"
ACTIVE_DIR="$NOTES_DIR/active"
 
read -p "Folder name: " folder
 
if [ -z "$folder" ]; then
  echo "Folder name cannot be empty"
  exit 1
fi
 
slug=$(echo "$folder" | tr '[:upper:]' '[:lower:]' | tr ' ' '-' | tr -cd 'a-z0-9-')
 
mkdir -p "$ACTIVE_DIR/$slug"
 
echo "Created: $ACTIVE_DIR/$slug"

na — Archive Project

Move a folder from active/ to archive/.

#!/bin/bash
 
NOTES_DIR="/Users/daniel/notes"
ACTIVE_DIR="$NOTES_DIR/active"
ARCHIVE_DIR="$NOTES_DIR/archive"
PICKER="fzf"
 
folders=$(find "$ACTIVE_DIR" -mindepth 1 -maxdepth 1 -type d -exec basename {} \; | sort)
 
if [ -z "$folders" ]; then
  echo "No folders found in $ACTIVE_DIR"
  exit 1
fi
 
folder=$(echo "$folders" | $PICKER --prompt="Select folder to archive: ")
 
if [ -z "$folder" ]; then
  echo "No folder selected"
  exit 1
fi
 
mkdir -p "$ARCHIVE_DIR"
 
mv "$ACTIVE_DIR/$folder" "$ARCHIVE_DIR/"
 
echo "Archived: $folder"

I also have Neovim commands that mirror these :NoteNew, :NoteFind, :NoteSearch, and so on. AI generated those too.

AI Can Write the Rest

This is the key part. When you need a new feature, you don’t search for plugins or learn an API. You describe what you want.

“Write me a script that generates a weekly summary of notes created in the last 7 days.”

“Write me a script that finds notes containing a specific phrase and lists them with context.”

“Write me a script that exports a project folder to a single markdown file for sharing.”

Claude can see the folder structure, understand the naming convention, and write a working script in seconds. No abstractions to navigate, no plugin architecture to satisfy. Just files and bash.

You don’t need to anticipate every feature. When you need something, ask.

Tradeoffs

There are real downsides:

  • No native mobile app. A simple markdown viewer / editor like 1Writer is enough for me though.
  • No sync built-in. I use git. Syncthing would also work. But you have to set it up.
  • No graph views. I never found these useful, but if you did — AI could generate one.
  • Requires terminal comfort. If fzf and vim aren’t in your vocabulary, there’s friction.

Why This Works

The system isn’t better because it’s minimal. It’s better because it’s transparent.

AI works best when it can see and modify the full system. No hidden state, no proprietary formats, no plugin APIs to reverse-engineer. Just files and scripts that do exactly what they say.

Five scripts was enough for me. Yours might be different. That’s the point.

Imprint

This website is created and run by

Daniel Benner

Zur Deutschen Einheit 2

81929 München

Germany

hello(a)danielbenner.de