ターミナルでメモ管理 (Neovim, nb, zeno.zsh)
なぜターミナルでメモを?
ターミナルでメモを取る最大のメリットは カスタマイズ性 と CLIコマンドでの操作 です。
自分好みのキーバインド、検索機能、ワークフローを自由に構築できます。
たとえば以下のようなことが可能です。
- CLIコマンド一発でメモを作成・検索・編集
- 正規表現でメモを検索し、fzfでプレビューしながら選択
- URLを渡すだけでタイトルを自動取得してメモを作成
- Neovimからメモの検索・作成・編集をすべて完結
また、最近ではNeovimで画像を表示できるようになり、CLIでのメモ管理がより実用的になりました。
メモに求めるもの
記録を残す上で最も大切なのは、以下のような「メモを取る動作」を高速に回すことだと思います。
メモを取る動作
- ファイルを作成 → 編集 → 保存
- 検索 → 追記
- 別ファイルへの参照を追加
そしてこの動作を高速に回すためには、以下のような要件が必要だと考えました。
- ディレクトリ管理を意識しない
- どのパスからでもアクセスできる
- ファイル名もタイトルも考えたくない
- 自動保存してGitHubで管理
これらを満たすツールとして、私は nb を選びました。
Obsidian も検討したのですが、CLIコマンドの提供が無さそうだったので、今回は見送りました。
nb
nbはメモを管理するCLIツールです。
以下のように、コマンドでメモを追加/編集/検索などができます。
# メモを追加 (エイリアス: nb a)
nb add
# メモ一覧を表示 (エイリアス: nb ls)
nb list
# メモを編集 (エイリアス: nb e)
nb edit メモ番号
# メモを検索(タイトル・内容両方) (エイリアス: nb q)
nb search "キーワード"
# タイトル・ファイル名のみで検索
nb ls "キーワード"
# 内容のみで検索したい場合はgrep/rgを使用
rg "キーワード" "$(nb notebooks current --path)"
メモは自動保存されるため、保存をサボってしまう人でも安心ですね。
GitHubリポジトリと連携する設定をしておくと、自動でpushまでやってくれます。
nbのインストールはHomebrewで簡単に行えます。
# nbのインストール
brew install xwmx/taps/nb
# 最新版の場合
brew install xwmx/taps/nb --head
nbのnotebook管理
nbのメモはnotebookという単位で管理することができます。
ディレクトリ構造は以下のようになります。
~/.nb/
├── home/ # デフォルトのnotebook
│ ├── .git/
│ ├── 20251031152222.md
│ └── 20251101093045.md
└── work/ # 追加したnotebook
├── .git/
├── 20251105140030.md
└── 20251106183012.md
例えば、普段使いのメモは home 、仕事用のメモは work のように notebook で分けて管理できます。
notebookごとにGitリポジトリを紐づける設定ができるので、普段使いのメモはプライベートリポジトリに、仕事用のメモはローカルのみで管理するといった使い方も可能です。
notebook のディレクトリはコマンドで簡単に作成できます。
コマンドで操作が可能なため、ディレクトリ構造を意識せずにメモを管理できます。
# notebook一覧を表示
nb notebooks
# notebookを作成
nb notebooks add work
# notebookを切り替え
nb use work
# 現在のnotebookを確認
nb notebooks current
# 別のnotebookのメモを操作(notebook名:をつける)
nb ls work:
nb edit work:1
nbの設定
nbで使用するエディタの設定
設定ファイルは~/.nbrcを使用します。
このファイルはnbをインストールと同時に自動生成されます。
内容は以下のようになっています。
#!/usr/bin/env bash
###############################################################################
# .nbrc
#
# Configuration file for `nb`, a command line note-taking, bookmarking,
# and knowledge base application with encryption, search, Git-backed syncing,
# and more in a single portable script.
#
# Edit this file manually or manage settings using the `nb settings`
# subcommand. Configuration options are set as environment variables, eg:
# export NB_ENCRYPTION_TOOL=gpg
#
# https://github.com/xwmx/nb
###############################################################################
エディタを設定するには以下のコマンドを実行します。
# 使用するエディタを設定
nb set editor nvim
すると、~/.nbrc に以下の行が追加されます。
export EDITOR="nvim" # Set by `nb` • Thu Jan 9 12:52:05 JST 2025
このように、コマンドで設定を行うと自動的に設定ファイルに追記してくれます。
もちろん、手動で設定ファイルを編集しても問題ありません。
nbのファイルを管理するディレクトリの設定
各プロジェクトの移動はghqで行なっているため、nbのメモを保存するディレクトリもghqの管理下に置くことにしました。
ghqで使用しているディレクトリの指定は以下のように設定しています。
[ghq]
root = ~/src
このディレクトリ配下であればghq listでnbのリポジトリも表示されるようになります。
ghq getでGitHubからリポジトリをクローンすると、ghqのroot + github.com/自分のユーザー名/ 配下に配置されるため github.com/mozumasu/nb をnb管理ディレクトリに設定します。
nb set nb_dir
# ~/src/github.com/mozumasu/nbを入力してEnter
上記のコマンドを実行すると、 ~/.nbrc に以下の行が追加されます。
export NB_DIR="${NB_DIR:-/Users/mozumasu/src/github.com/mozumasu/nb}" # Set by `nb` • Sat Jan 11 20:09:06 JST 2025
ノートを追加して、実際に ~/src/github.com/mozumasu/nb 配下にnbのディレクトリが追加されるか確認してみましょう。
# ノートを追加
nb notebooks add example
# ノートに対応するディレクトリが作成されているか確認
ls ~/src/github.com/mozumasu/nb
# example/ home/
GitHubリポジトリと連携する
nbのノートブックをGitHub管理して別端末でも利用できるように設定します。
どのノートブックのリポジトリかわかるように、nb-ノートブック名でリモートリポジトリを作成しましょう。
# デフォルトのnotebook (home) を管理するGitHubリポジトリを作成
gh repo create nb-home --private
用意したリモートリポジトリをnbのnotebookに紐づけます。
# 使用するノートブックを指定
nb use home
# 使用するリモートリポジトリの設定
nb remote set git@github.com:mozumasu/nb-home.git
これで、homeノートブックの変更が自動でGitHubリポジトリにpushされるようになります。
リストで表示される絵文字のカスタマイズ
nb ls でノート一覧を表示したときに、ノートの種類ごとに絵文字が表示されます。
この絵文字は設定ファイルでカスタマイズ可能です。

export NB_INDICATOR_AUDIO="🔉"
export NB_INDICATOR_BOOKMARK="🔖"
export NB_INDICATOR_DOCUMENT="📄"
export NB_INDICATOR_EBOOK="📖"
export NB_INDICATOR_ENCRYPTED="🔒"
export NB_INDICATOR_FOLDER="📂"
export NB_INDICATOR_IMAGE="🌄"
export NB_INDICATOR_PINNED="📌"
export NB_INDICATOR_TODO="✔️ "
export NB_INDICATOR_TODO_DONE="✅"
export NB_INDICATOR_VIDEO="📹"
最終的な~/.nbrcの例
#!/usr/bin/env bash
###############################################################################
# .nbrc
#
# Configuration file for `nb`, a command line note-taking, bookmarking,
# and knowledge base application with encryption, search, Git-backed syncing,
# and more in a single portable script.
#
# Edit this file manually or manage settings using the `nb settings`
# subcommand. Configuration options are set as environment variables, eg:
# export NB_ENCRYPTION_TOOL=gpg
#
# https://github.com/xwmx/nb
###############################################################################
export EDITOR="nvim" # Set by `nb` • Thu Jan 9 12:52:05 JST 2025
export NB_DIR="${NB_DIR:-/Users/mozumasu/src/github.com/mozumasu/nb}" # Set by `nb` • Sat Jan 11 20:09:06 JST 2025
export NB_INDICATOR_AUDIO="🔉"
export NB_INDICATOR_BOOKMARK="🔖"
export NB_INDICATOR_DOCUMENT="📄"
export NB_INDICATOR_EBOOK="📖"
export NB_INDICATOR_ENCRYPTED="🔒"
export NB_INDICATOR_FOLDER="📂"
export NB_INDICATOR_IMAGE="🌄"
export NB_INDICATOR_PINNED="📌"
export NB_INDICATOR_TODO="✔️ "
export NB_INDICATOR_TODO_DONE="✅"
export NB_INDICATOR_VIDEO="📹"
zeno.zshとnbを組みあわせて幸せに
いちいち nb ls でノート番号を確認して nb edit 番号 とするのは面倒です。
Tab補完で、fzfのようにプレビューが出せれば最高ですよね。
それ、zeno.zshでできちゃうんです。

nbのノート番号をzeno.zshでTab補完する
zeno.zsh は zsh/fishのプラグインで以下の機能があります。
- スニペット設定
- fzf補完
- コマンド履歴検索
zeno.zshのインストール
シェルのプラグインマネージャーとして、動作が早い sheldon を使用してインストールします。
# 設定ファイルを生成
sheldon init --shell zsh
# Initialize new config file `~/.config/sheldon/plugins.toml`? [y/N] y
# Initialized ~/.config/sheldon/plugins.toml
実行すると以下のようなsheldonの設定ファイルが生成されます。
# `sheldon` configuration file
# ----------------------------
#
# You can modify this file directly or you can use one of the following
# `sheldon` commands which are provided to assist in editing the config file:
#
# - `sheldon add` to add a new plugin to the config file
# - `sheldon edit` to open up the config file in the default editor
# - `sheldon remove` to remove a plugin from the config file
#
# See the documentation for more https://github.com/rossmacarthur/sheldon#readme
shell = "zsh"
[plugins]
# For example:
#
# [plugins.base16]
# github = "chriskempson/base16-shell"
合わせて、以下のコマンドを zshの設定ファイル(~/.zshrc)に追記して、プラグインを読み込むようにします。
eval "$(sheldon source)"
zeno.zshをインストールしたい場合は、以下のようにpluginsセクションに追記します。
[plugins]
+ [plugins.zeno]
+ github = "yuki-yano/zeno.zsh"
+ [plugins.fast-syntax-highlighting]
+ github = "zdharma-continuum/fast-syntax-highlighting"
zeno.zshを使用するためには以下のような設定をzshの設定ファイルに追記する必要があります。
export ZENO_HOME=~/.config/zeno
# git file preview with color
export ZENO_GIT_CAT="bat --color=always"
# git folder preview with color
# export ZENO_GIT_TREE="eza --tree"
if [[ -n $ZENO_LOADED ]]; then
bindkey ' ' zeno-auto-snippet
# if you use zsh's incremental search
# bindkey -M isearch ' ' self-insert
bindkey '^m' zeno-auto-snippet-and-accept-line
bindkey '^i' zeno-completion
bindkey '^xx' zeno-insert-snippet # open snippet picker (fzf) and insert at cursor
bindkey '^x ' zeno-insert-space
bindkey '^x^m' accept-line
bindkey '^x^z' zeno-toggle-auto-snippet
# preprompt bindings
bindkey '^xp' zeno-preprompt
bindkey '^xs' zeno-preprompt-snippet
# Outside ZLE you can run `zeno-preprompt git {{cmd}}` or `zeno-preprompt-snippet foo`
# to set the next prompt prefix; invoking them with an empty argument resets the state.
bindkey '^r' zeno-smart-history-selection # smart history widget
# fallback if completion not matched
# (default: fzf-completion if exists; otherwise expand-or-complete)
# export ZENO_COMPLETION_FALLBACK=expand-or-complete
fi
zeno.zshの設定は、~/.config/zeno/config.yml に記述します。
nbのノート番号をTab補完したい場合はcompletionsセクションに以下のように追加します。
completions:
- name: nb edit
patterns:
- "^nb e( .*)? $"
- "^nb edit( .*)? $"
sourceCommand: "nb ls --no-color | grep -E '^\\[[0-9]+\\]'"
options:
--ansi: true # ← ANSIカラー有効
--prompt: "'nb edit >'"
--preview: "echo {} | sed -E 's/^\\[([0-9]+)\\].*/\\1/' | xargs nb show"
callback: "sed -E 's/^\\[([0-9]+)\\].*/\\1/'"
合わせてサブコマンドのヘルプを確認するための補完を設定するのもおすすめです。
completions:
- name: nb subcommands
patterns:
- ^\s*nb\s*$
- ^\s*nb\s+help\s*$
sourceCommand: nb subcommands
options:
--prompt: "'nb subcommand >'"
スニペットを登録する場合は以下のように追記してください。
completions:
...
+ snippets:
+ - name: Edit Note
+ keyword: nbe
+ snippet: nb edit
+
+ - name: List Note
+ keyword: nbl
+ snippet: nb ls --limit 20
+
+ - name: List All Note
+ keyword: nbla
+ snippet: nb ls --all
+
+ - name: nb search
+ keyword: nbg
+ snippet: rg "{{keyword}}" "$(nb notebooks current --path)"
スニペットは以下のように、space キーで展開できます。

nb用に設定したシェル関数
nba: URLから記事をメモに追加
URLを渡すと記事のタイトルを自動取得してnbにメモを追加する関数です。

# nb add article - Add a note with article title and URL
# Usage: nba <url> - Auto-fetch title from URL
# nba <title> <url> - Use specified title
function nba() {
if [ $# -lt 1 ]; then
echo "Usage: nba <url> # Auto-fetch title"
echo " nba <title> <url> # Manual title"
return 1
fi
local title=""
local url=""
if [ $# -eq 1 ]; then
url="$1"
echo "Fetching title from: $url"
title=$(curl -sL --max-redirs 3 --max-time 5 --compressed "$url" | head -c 512 | perl -0777 -ne 'print $1 if /<title[^>]*>([^<]+)<\/title>/i')
title=$(echo "$title" | perl -pe 's/^\s+|\s+$//g; s/\s+/ /g')
if [ -z "$title" ]; then
echo "Error: Could not fetch title from URL"
return 1
fi
echo "Title: $title"
else
title="$1"
url="$2"
fi
local content="# ${title}
参照: [${title}](${url})"
nb add --filename "${title}.md" --content "$content"
echo "Note created: [${title}](${url})"
}
nbq: 検索結果をfzfで選択して編集
nb searchの検索結果をfzfでプレビューしながら選択し、そのまま編集できる関数です。

# nb query - Search notes and select with fzf preview
# Usage: nbq <search query>
function nbq() {
if [ -z "$1" ]; then
echo "Usage: nbq <search query>"
return 1
fi
local query="$*"
local results=$(nb q "$query" --no-color 2>/dev/null | grep -E '^\[[0-9]+\]')
if [ -z "$results" ]; then
echo "No results found for: $query"
return 1
fi
export _NBQ_QUERY="$query"
local selected=$(echo "$results" | fzf --ansi \
--preview 'note_id=$(echo {} | sed -E "s/^\[([0-9]+)\].*/\1/")
echo "=== Note [$note_id] ==="
echo ""
nb show "$note_id" | head -5
echo ""
echo "=== Matching lines ==="
echo ""
nb show "$note_id" | grep -i --color=always -C 2 "$_NBQ_QUERY" | head -30' \
--preview-window=right:60%:wrap \
--header "Search: $query")
unset _NBQ_QUERY
if [ -n "$selected" ]; then
local note_id=$(echo "$selected" | sed -E 's/^\[([0-9]+)\].*/\1/')
nb edit "$note_id"
fi
}
nbのリンク参照
別のページにリンクを貼る場合は、以下のように[[]]を使用します。
[[ページタイトル]]
nbで画像を管理する
画像のインポートは nb import 画像のパス で行います。
画像をターミナル上でペーストするとパスが入るため、nb import と入力したあとにペーストするだけで画像をnbで管理することができます。
私はRaycastのクリップボード履歴からペーストして使っています。
# 画像パスはRaycastのクリップボード履歴からペースト
nb import ../../../../../Documents/screenshot/スクリーンショット%202025-11-06%208.48.40.png
ファイル名を変更したい場合は nb rename ノート番号 新しいファイル名 を使用します。
# 番号を指定してファイル名を変更
$ nb rename 13 wezterm-doc.png
Moving: [13] wezterm-doc
To: wezterm-doc.png
Proceed? [y/N] y
Moved to: [13] 🌄 wezterm-doc.png
以下のようにimport時にファイル名を指定することも可能です。
# 画像のインポート時にファイル名を指定する
nb import 画像のパス 画像ファイル名
ノートで画像のリンクを貼る場合は以下のように記述します。
現在のパスを示す ./ は不要なので注意しましょう 。

Todo機能
nb には 以下の2つのようなタスク管理の機能があります。
- task: ノート機能はなく、markdownのチェックボックスのみ
- todo: ノート機能にチェックボックスチェックボックスの機能が組みあわさったもの. タスクも追加できる
taskは本文中の - [ ] を nb tasksコマンドで表示して管理することができます。
しかし、記事の下書きなどで - [ ] を使用するため私は task は使用せず、 todo のみ使用しています。
todoは nb todo addコマンドで追加でき、拡張子 .todo.md という拡張子でファイルが追加されます。
# todo
nb todo add "やること"
# 完了にする
nb todo do タスクID
# 未完了にする
nb todo undo タスクID
todoの一覧表示では以下のコマンドを使用します。
# すべてのタスク一覧
nb todos
# 完了タスク
nb todos closed
# 未完了タスク
nb todo open
特に、やること/やらないことの管理のために、通常のノートをタスクに切り替えるコマンドを使用しています。
やることは大抵ノートに概要を書くので、そのノートをtodoに変換して管理しています。
# ノート7 ("demo.md") をtodo "demo.todo.md" に変換
nb rename 7 --to-todo
# todoをノートに変換
nb rename 7 --to-note
Neovim
Neovimをメモ編集用のエディタとして使用している理由は以下の通りです。
- 最も効率よくテキストするためのキーバインド
- 見た目もキーバインドもカスタマイズ可能
- 外部コマンドが実行できる
- 普段使用しているプラグインをそのまま使える
nb用に設定したNeovimの設定を紹介していきます。
バッファタイトルの設定
nbのファイル名は自動でタイムスタンプになるため、ファイル名だけでは内容がわかりません。

タブやバッファラインにファイル名ではなく1行目のタイトルを表示できます。
まず、nbのヘルパー関数を定義します。
local M = {}
-- nbコマンドのプレフィックス
local NB_CMD = "NB_EDITOR=: NO_COLOR=1 nb"
-- nbのノートディレクトリパスを取得
function M.get_nb_dir()
-- nbのディレクトリパスに合わせて変更してください
return vim.fn.expand("~/.nb")
end
-- nbコマンドを実行
function M.run_cmd(args)
local cmd = NB_CMD .. " " .. args
local output = vim.fn.systemlist(cmd)
if vim.v.shell_error ~= 0 then
return nil
end
return output
end
-- リスト行をパースして構造化データを返す
-- 例: "[1] 🌄 image.png" -> { note_id = "1", name = "image.png", is_image = true }
-- 例: "[2] ノートタイトル" -> { note_id = "2", name = "ノートタイトル", is_image = false }
function M.parse_list_item(line)
local note_id = line:match("^%[(.-)%]")
if not note_id then
return nil
end
local is_image = line:match("🌄") ~= nil
local name
if is_image then
name = line:match("%[%d+%]%s*🌄%s*(.+)$")
else
name = line:match("%[%d+%]%s*(.+)$")
end
if not name then
return nil
end
return {
note_id = note_id,
name = vim.trim(name),
is_image = is_image,
text = line,
}
end
-- パース済みアイテム一覧を取得
function M.list_items()
local output = M.run_cmd("list --no-color")
if not output then
return nil
end
local items = {}
for _, line in ipairs(output) do
local item = M.parse_list_item(line)
if item then
table.insert(items, item)
end
end
return items
end
-- nbノートのタイトルを取得する関数(bufferline用)
function M.get_title(filepath)
local nb_dir = M.get_nb_dir()
if not filepath:match("^" .. nb_dir) then
return nil
end
local file = io.open(filepath, "r")
if not file then
return nil
end
local first_line = file:read("*l")
file:close()
if first_line then
return first_line:match("^#%s+(.+)")
end
return nil
end
-- ノートIDからファイルパスを取得
function M.get_note_path(note_id)
local output = M.run_cmd("show --path " .. note_id)
if output and output[1] then
return vim.trim(output[1])
end
return ""
end
return M
LazyVimではタブの表示に bufferline.nvim がデフォルトで使われているので、以下のように設定を拡張します。
return {
"akinsho/bufferline.nvim",
opts = function(_, opts)
local nb = require("config.nb")
opts.options = opts.options or {}
opts.options.name_formatter = function(buf)
local title = nb.get_title(buf.path)
return title or buf.name
end
end,
}
この設定により、nbのノートを開いたときに1行目のタイトルがバッファラインに表示されるようになります。

bufferlineにnbのタイトルを表示
検索の設定
ノートのタイトルや、ノートの内容をgrep検索して開きたいときに便利なのがファジーファインダー系のプラグインです。
LazyVimでは snacks.nvim がデフォルトで使用されているので、これを活用します。
検索でもファイル名ではなく、メモのタイトルで検索できるようにしています。

snacks.nvimでnbのノートを検索する
以下のファイルを作成すると、snacks.nvimでnbのノートを検索できるようになります。
-- snacks.nvimでノートをタイトル一覧から検索して開く
local function pick_notes()
local nb = require("config.nb")
local Snacks = require("snacks")
local items = nb.list_items()
if not items or #items == 0 then
vim.notify("No notes found", vim.log.levels.WARN)
return
end
Snacks.picker({
title = "nb Notes",
items = items,
format = function(item)
return { { item.text } }
end,
preview = function(ctx)
local item = ctx.item
if not item.file then
item.file = nb.get_note_path(item.note_id)
end
return Snacks.picker.preview.file(ctx)
end,
confirm = function(picker, item)
picker:close()
if item then
vim.cmd.edit(nb.get_note_path(item.note_id))
end
end,
})
end
-- snacks.nvimでノートの内容をgrep検索
local function grep_notes()
local nb = require("config.nb")
local Snacks = require("snacks")
Snacks.picker.grep({
dirs = { nb.get_nb_dir() },
})
end
return {
"folke/snacks.nvim",
keys = {
{ "<leader>np", pick_notes, desc = "nb picker" },
{ "<leader>ng", grep_notes, desc = "nb grep" },
},
}
この設定で以下のキーマップが使えます:
-
<leader>np- ノートのタイトル一覧から検索して開く -
<leader>ng- ノートの内容をgrep検索して開く

タイトル検索でノートを開く

grep検索でノートを開く
削除機能の追加
picker内でノートを削除できるようにします。
config/nb.lua に削除用の関数を追加します(return M の前に追加)。
+ -- ノートを削除
+ function M.delete_note(note_id)
+ local output = M.run_cmd("delete --force " .. note_id)
+ return output ~= nil
+ end
return M
plugins/nb.lua の pick_notes にも削除アクションとキーバインドを追加します。
Snacks.picker({
title = "nb Notes",
items = items,
-- ... 省略 ...
confirm = function(picker, item)
picker:close()
if item then
vim.cmd.edit(nb.get_note_path(item.note_id))
end
end,
+ actions = {
+ delete_note = function(picker)
+ local item = picker:current()
+ if item then
+ vim.ui.select({ "Yes", "No" }, {
+ prompt = "Delete: " .. item.name .. "?",
+ }, function(choice)
+ if choice == "Yes" then
+ if nb.delete_note(item.note_id) then
+ vim.notify("Deleted: " .. item.name, vim.log.levels.INFO)
+ picker:close()
+ pick_notes()
+ else
+ vim.notify("Failed to delete", vim.log.levels.ERROR)
+ end
+ end
+ end)
+ end
+ end,
+ },
+ win = {
+ input = {
+ keys = {
+ ["<C-d>"] = { "delete_note", mode = { "n", "i" }, desc = "Delete note" },
+ },
+ },
+ },
})
これで picker 内で <C-d> を押すと、確認ダイアログが表示され、選択中のノートを削除できます。
ノートを追加する設定
Neovimからnbのノートを追加できるようにします。
config/nb.lua にノート追加用の関数を追加します(return M の前に追加)。
+ -- ノートを追加してIDを返す
+ function M.add_note(title)
+ local timestamp = os.date("%Y%m%d%H%M%S")
+ local note_title = title and title ~= "" and title or os.date("%Y-%m-%d %H:%M:%S")
+ local escaped_title = note_title:gsub('"', '\\"')
+ local args = string.format('add --no-color --filename "%s.md" --title "%s"', timestamp, escaped_title)
+
+ local output = M.run_cmd(args)
+ if not output then
+ return nil
+ end
+
+ -- 追加されたノートのIDを取得
+ for _, line in ipairs(output) do
+ local note_id = line:match("%[(%d+)%]")
+ if note_id then
+ return note_id
+ end
+ end
+ return nil
+ end
return M
plugins/nb.lua にもノートを追加する関数とキーマップを追加します。
+ -- ノートを追加して開く
+ local function add_note()
+ local nb = require("config.nb")
+ vim.ui.input({ prompt = "Note title (empty for timestamp): " }, function(title)
+ local note_id = nb.add_note(title)
+ if note_id then
+ local path = nb.get_note_path(note_id)
+ if path and path ~= "" then
+ vim.cmd.edit(path)
+ end
+ else
+ vim.notify("Failed to add note", vim.log.levels.ERROR)
+ end
+ end)
+ end
return {
"folke/snacks.nvim",
keys = {
+ { "<leader>na", add_note, desc = "nb add" },
{ "<leader>np", pick_notes, desc = "nb picker" },
{ "<leader>ng", grep_notes, desc = "nb grep" },
},
}
これで <leader>na でノートを追加して開けるようになります。

NbのノートをNeovimから追加する
画像をインポートする設定
Neovimから画像をnbにインポートし、マークダウンリンクを挿入できるようにします。
config/nb.lua に画像インポート用の関数を追加します(return M の前に追加)。
+ -- 画像をnbにインポートする
+ function M.import_image(image_path, new_filename)
+ if not image_path or image_path == "" then
+ return nil, "No path provided"
+ end
+
+ -- 前後の空白とクォートを除去してパスを展開
+ local cleaned_path = image_path:gsub("^%s*['\"]?", ""):gsub("['\"]?%s*$", "")
+ local expanded_path = vim.fn.expand(cleaned_path)
+
+ -- ファイルが存在するか確認
+ if vim.fn.filereadable(expanded_path) == 0 then
+ return nil, "File not found: " .. expanded_path
+ end
+
+ -- 新しいファイル名が指定されていれば追加
+ local final_filename
+ if new_filename and new_filename ~= "" then
+ -- 拡張子がなければ元の拡張子を追加
+ if not new_filename:match("%.%w+$") then
+ local ext = vim.fn.fnamemodify(expanded_path, ":e")
+ new_filename = new_filename .. "." .. ext
+ end
+ final_filename = new_filename
+ else
+ final_filename = vim.fn.fnamemodify(expanded_path, ":t")
+ end
+
+ -- コマンドを構築して実行
+ local escaped_path = vim.fn.shellescape(expanded_path)
+ local args = "import --no-color " .. escaped_path
+ if new_filename and new_filename ~= "" then
+ args = args .. " " .. vim.fn.shellescape(new_filename)
+ end
+
+ local output = M.run_cmd(args)
+ if not output then
+ return nil, "Import failed"
+ end
+
+ -- インポートされたファイルのIDを取得
+ for _, line in ipairs(output) do
+ local note_id = line:match("%[(%d+)%]")
+ if note_id then
+ return note_id, final_filename
+ end
+ end
+ return nil, "Could not parse import result"
+ end
return M
plugins/nb.lua にも画像インポート関数とキーマップを追加します。
+ -- 画像をインポートしてマークダウンリンクを挿入
+ local function import_image()
+ local nb = require("config.nb")
+ vim.ui.input({ prompt = "Image path: ", completion = "file" }, function(image_path)
+ if not image_path or image_path == "" then
+ return
+ end
+
+ -- 新しいファイル名を入力(空ならそのまま)
+ vim.ui.input({ prompt = "New filename (empty to keep original): " }, function(new_filename)
+ local note_id, result = nb.import_image(image_path, new_filename)
+ if note_id then
+ local filename = result
+ local link = string.format("", filename, filename)
+ vim.api.nvim_put({ link }, "c", true, true)
+ vim.notify("Imported: " .. filename, vim.log.levels.INFO)
+ else
+ vim.notify(result or "Failed to import image", vim.log.levels.ERROR)
+ end
+ end)
+ end)
+ end
return {
"folke/snacks.nvim",
keys = {
{ "<leader>na", add_note, desc = "nb add" },
+ { "<leader>ni", import_image, desc = "nb import image" },
{ "<leader>np", pick_notes, desc = "nb picker" },
{ "<leader>ng", grep_notes, desc = "nb grep" },
},
}
これで <leader>ni で画像をインポートできるようになります。
- 画像パスを入力(ペースト)
- 新しいファイル名を入力(空Enterで元の名前を使用)
画像がnbにインポートされ、カーソル位置にマークダウンの画像リンクが挿入されます。
リンクを挿入する設定
snacks.nvimのpickerを使って、nbの画像やノートを選択してリンクを挿入できるようにします。
plugins/nb.lua にリンク挿入の関数とキーマップを追加します。
+ -- リンクを挿入
+ local function link_item()
+ local nb = require("config.nb")
+ local Snacks = require("snacks")
+ local items = nb.list_items()
+
+ if not items or #items == 0 then
+ vim.notify("No items found", vim.log.levels.WARN)
+ return
+ end
+
+ Snacks.picker({
+ title = "nb Link",
+ items = items,
+ format = function(item)
+ return { { item.text } }
+ end,
+ preview = function(ctx)
+ local item = ctx.item
+ if not item.file then
+ item.file = nb.get_note_path(item.note_id)
+ end
+ return Snacks.picker.preview.file(ctx)
+ end,
+ confirm = function(picker, item)
+ picker:close()
+ if item then
+ local link
+ if item.is_image then
+ link = string.format("", item.name, item.name)
+ else
+ link = string.format("[[%s]]", item.name)
+ end
+ vim.api.nvim_put({ link }, "c", true, true)
+ end
+ end,
+ })
+ end
return {
"folke/snacks.nvim",
keys = {
{ "<leader>na", add_note, desc = "nb add" },
{ "<leader>ni", import_image, desc = "nb import image" },
+ { "<leader>nl", link_item, desc = "nb link" },
{ "<leader>np", pick_notes, desc = "nb picker" },
{ "<leader>ng", grep_notes, desc = "nb grep" },
},
}
これで <leader>nl でプレビュー付きのpickerが表示されます。
画像(🌄マーク付き)を選択すると  形式、ノートを選択すると [[title]] 形式で挿入されます。
Claude Codeとの連携
nbのメモはローカルにマークダウンファイルとして保存されるため、Claude Codeとの相性も良いです。
ログ用のノートを作成し、スラッシュコマンドでセッションの内容を追記したり要約したりしています。
# ログ用ノートを作成
nb notebooks add log
合わせてHooksでフォーマットの設定を行うと、Claude Codeで編集した内容が自動で整形されるため便利です。
以下の例では、 rumdl を使用してマークダウンファイルをフォーマットしています。
{
"hooks": {
"PostToolUse": [{
"matcher": "Write|Edit|MultiEdit",
"hooks": [{
"type": "command",
"command": "jq -r '.tool_input.file_path | select(endswith(\".md\"))' | xargs -r rumdl fmt"
}]
}]
}
}
rumdlの紹介
インストール
brew install rumdl
使い方
# 現在のディレクトリ内の Markdown ファイルを Lint する
rumdl check .
# ファイル形式をチェック(修正不可能な違反が残っていても成功時は終了コード 0 を返す)
rumdl fmt .
# 自動修正できない違反を報告する(違反が残っている場合は終了コード1を返す)
rumdl check --fix .
# デフォルトの設定ファイルを作成する
rumdl init
設定ファイルは次の場所に配置できます。
- プロジェクトディレクトリまたは親ディレクトリ内に .rumdl.toml または rumdl.toml
- .config/rumdl.toml (config-dir 規約に準拠)
既存の.markdownlint.json や .markdownlint.yaml を使用することもできるため、rumdlを導入する際に既存の設定を活かすことも可能です。
# rumdl 設定ファイル
# グローバル設定オプション
[global]
# 無効にするルールのリスト(必要に応じてコメント解除して修正)
# disable = ["MD013", "MD033"]
# 排他的に有効にするルールのリスト(指定した場合、これらのルールのみが実行されます)
# enable = ["MD001", "MD003", "MD004"]
# リント対象に含めるファイル/ディレクトリパターンのリスト(指定した場合、これらのみがリントされます)
# include = [
# "docs/*.md",
# "src/**/*.md",
# "README.md"
# ]
# リント対象から除外するファイル/ディレクトリパターンのリスト
exclude = [
# 除外する一般的なディレクトリ
".git",
".github",
"node_modules",
"vendor",
"dist",
"build",
# 特定のファイルまたはパターン
"CHANGELOG.md",
"LICENSE.md",
]
# ディレクトリをスキャンする際に .gitignore ファイルを尊重する(デフォルト: true)
respect-gitignore = true
# Markdown フレーバー/方言(有効にするにはコメント解除)
# オプション: mkdocs, gfm, commonmark
# flavor = "mkdocs"
# ルール固有の設定(必要に応じてコメント解除して修正)
# [MD003]
# style = "atx" # 見出しスタイル (atx, atx_closed, setext)
# [MD004]
# style = "asterisk" # 順序なしリストスタイル (asterisk, plus, dash, consistent)
# [MD007]
# indent = 4 # 順序なしリストのインデント
# [MD013]
# line-length = 100 # 行の長さ
# code-blocks = false # コードブロックを行の長さチェックから除外
# tables = false # テーブルを行の長さチェックから除外
# headings = true # 見出しを行の長さチェックに含める
# [MD044]
# names = ["rumdl", "Markdown", "GitHub"] # 正しく大文字小文字を使用すべき固有名詞
# code-blocks = false # コードブロックで固有名詞をチェックする(デフォルト: false、コードブロックをスキップ)
スラッシュコマンドは以下のものを使用しています。
- https://github.com/mozumasu/dotfiles/blob/main/.config/claude/commands/nb-log.md
- https://github.com/mozumasu/dotfiles/blob/main/.config/claude/commands/nb.md
Claude Codeがアクセスできるように /add-dir コマンドか、claude/setting.json でnbのノートディレクトリを追加しておきましょう。
{
"permissions": {
"additionalDirectories": [
"~/src/github.com/mozumasu/nb", # ここにnbのノートディレクトリを追加
"~/src/github.com/mozumasu/zenn/articles"
]
},
}
おわりに
この記事では、nb・Neovim・zeno.zshを組み合わせたターミナルでのメモ管理環境を紹介しました。
- nb: CLIでメモの作成・検索・編集ができる
-
シェル関数:
nbaでURL からタイトルを自動取得、nbqでfzfプレビュー付き検索 - Neovim連携: snacks.nvimでメモの検索・追加をエディタ内で完結
- zeno.zsh: fzf補完でノート選択を快適に
ターミナルでのメモ管理は、最初の設定に少し手間がかかりますが、一度構築してしまえば自分だけのワークフローを実現できます。
この記事が、みなさんのメモ環境構築の参考になれば幸いです。
Discussion
On WSL2, this setup helps open images using msedge.exe instead of Linux browsers.