この記事で扱うこと
ChatGPTなどのAIからBasic Memory Cloudへ保存したMarkdownを、macOS上のObsidian Vaultへ安全に取り込む方法を扱います。
単に同期コマンドを定期実行するのではなく、次の方針にします。
- 自動化するのはCloudからローカルへの
pullだけ -
pushは内容をレビューしてから手動実行 - 初回実行前に
--dry-runで転送対象を確認 - 競合時は上書きせず停止
- Vaultが見つからない場合は正常終了して次回へ持ち越す
- Cloudだけに残したいファイルがローカルへ入ったら停止
実際に、Cloudへの初回送信、ChatGPTから作成した一時ノートの取り込み、ローカル検索への反映、定期実行の起動まで確認した構成を、固有のパスやアカウント情報を除いて紹介します。
対象読者
- ObsidianのMarkdownをAIから検索・更新したい方
- Basic Memory CloudとChatGPTを接続したものの、ローカルとの同期方法に迷っている方
- AIが更新するノートを、意図しない削除や上書きから守りたい方
前提環境
検証した構成は次のとおりです。
- macOS
- Obsidian
- Basic Memory CLI 0.23系
- Basic Memory Cloud
- ChatGPTのBasic Memory Cloudプラグイン
- 同期対象プロジェクト名(例):
obsidian-notes - 同期先: Obsidian Vault内の専用ディレクトリ
Basic Memory Cloudの接続方法は、公式のCloud Sync GuideとChatGPT連携手順を参照してください。ChatGPT側でプラグインを利用できるかどうかは、プラン、地域、ワークスペース設定、権限によって異なります。
発生した問題
ChatGPTからCloud上のノートを更新できても、普段編集するObsidianへ自動では反映されません。そこで定期同期を考えましたが、無条件にミラー同期すると次のリスクがあります。
- ローカルにないファイルがCloudから削除される
- Cloudとローカルで同じノートを編集し、片方を上書きする
- 外付けディスクなどの同期先が未接続のまま処理する
- Cloud側だけに置きたい管理ファイルをVaultへ取り込む
- 認証情報や未レビューのノートまで自動で送信する
特に注意したのはpush / pullとsyncの違いです。公式ドキュメントでは、pushとpullは追加型で転送先のファイルを削除しません。一方、syncはローカルを正としてCloudを一致させるため、Cloud側の削除を伴う場合があります。
そのため、今回はCloudからObsidianへの追加型pullだけを定期実行し、ローカルからCloudへのpushは自動化しませんでした。
初回同期は必ずdry-runから始める
まず認証状態とプロジェクト名を確認します。
bm cloud status
続いて、実際には転送せず対象だけを表示します。
bm cloud pull --name obsidian-notes --dry-run
bm cloud push --name obsidian-notes --dry-run
初回は次を目視しました。
- 想定したプロジェクトを指定しているか
- 転送対象がレビュー済みMarkdownだけか
- Vault全体やGit管理ファイルが含まれていないか
- 個人情報、顧客情報、認証情報を含むファイルがないか
dry-runの結果に想定外のファイルが1件でもあれば、その場で止めて除外設定を見直します。
Cloud専用ファイルを除外する
今回はCloud上にだけ残したいルートファイルがあったため、~/.basic-memory/.bmignoreへ次のように設定しました。
/index.md
/log.md
先頭の/を付け、プロジェクトのルートにある同名ファイルだけを対象にします。
.bmignoreがグローバル設定の場合、別のBasic Memoryプロジェクトにも影響します。別プロジェクトで同名ファイルを同期したくなったときは、除外方法を見直す必要があります。また、Basic Memory 0.23以降では、ミラー型のsyncで新しい除外設定がCloud側の削除対象に影響する場合があります。変更前に必ずbm cloud sync --name <project> --dry-runで確認してください。
安全確認付きのpullスクリプト
定期実行するラッパースクリプトを作ります。以下では、ユーザー名や実際のVaultの場所を含まないサンプルパスを使っています。
#!/bin/zsh
set -eu
readonly bm_bin="${HOME}/.local/bin/bm"
readonly notes_dir="${HOME}/Documents/Obsidian/BasicMemory"
readonly ignore_file="${HOME}/.basic-memory/.bmignore"
# ノート保存先が使えないときは、次回の実行へ持ち越す
if [[ ! -d "$notes_dir" ]]; then
print -r -- 'BasicMemory folder is unavailable; skipping this run.'
exit 0
fi
# Cloud専用ファイルの除外設定が消えていたら止める
if [[ ! -f "$ignore_file" ]] || \
! /usr/bin/grep -Fqx '/index.md' "$ignore_file" || \
! /usr/bin/grep -Fqx '/log.md' "$ignore_file"; then
print -u2 -r -- 'Cloud-only note exclusions are missing; pull stopped.'
exit 1
fi
# Cloud専用ファイルがローカルに入っていたら、人が確認するまで止める
if [[ -e "$notes_dir/index.md" || -e "$notes_dir/log.md" ]]; then
print -u2 -r -- 'Cloud-only notes are present locally; pull stopped for review.'
exit 1
fi
# 競合時は上書きせず失敗させる
exec "$bm_bin" cloud pull --name obsidian-notes --on-conflict fail
たとえば~/.local/bin/basic-memory-cloud-pull.shとして保存し、実行権限を付けます。
chmod 700 ~/.local/bin/basic-memory-cloud-pull.sh
~/.local/bin/basic-memory-cloud-pull.sh
--on-conflict failは競合を自動解決しません。処理が止まったら、まず次のdry-runで対象を確認します。
bm cloud pull --name obsidian-notes --dry-run
内容を比較したうえで、Cloudを採用する、ローカルを採用する、両方を残す、のどれかを人が選びます。定期ジョブでkeep-cloudやkeep-localを固定すると、意図しない上書きに気づきにくいため避けました。
launchdで定期実行する
macOSではLaunchAgentとして登録します。例として15分間隔にしていますが、ノート更新頻度に合わせて調整してください。
~/Library/LaunchAgents/com.example.basic-memory-cloud-pull.plist:
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN"
"http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>Label</key>
<string>com.example.basic-memory-cloud-pull</string>
<key>ProgramArguments</key>
<array>
<string>/Users/example-user/.local/bin/basic-memory-cloud-pull.sh</string>
</array>
<key>RunAtLoad</key>
<true/>
<key>StartInterval</key>
<integer>900</integer>
<key>StandardOutPath</key>
<string>/Users/example-user/Library/Logs/basic-memory-cloud-pull.out.log</string>
<key>StandardErrorPath</key>
<string>/Users/example-user/Library/Logs/basic-memory-cloud-pull.err.log</string>
</dict>
</plist>
example-userは自分のmacOSユーザー名へ置き換えます。plist内では~を展開できないため、フルパスで指定します。
構文を確認して登録します。
plutil -lint ~/Library/LaunchAgents/com.example.basic-memory-cloud-pull.plist
launchctl bootstrap \
"gui/$(id -u)" \
~/Library/LaunchAgents/com.example.basic-memory-cloud-pull.plist
登録状態とログは次のように確認できます。
launchctl print "gui/$(id -u)/com.example.basic-memory-cloud-pull"
tail -n 50 ~/Library/Logs/basic-memory-cloud-pull.out.log
tail -n 50 ~/Library/Logs/basic-memory-cloud-pull.err.log
すぐに1回動かしたい場合は、次を実行します。
launchctl kickstart -k \
"gui/$(id -u)/com.example.basic-memory-cloud-pull"
Macがスリープ中は実行されません。また、外付けディスク上にVaultがある場合は、ディスクが未接続の回をスキップし、次の実行機会に取り込みます。
動作確認
今回は次の順序で確認しました。
-
pull --dry-runが想定したファイルだけを表示する - ChatGPTから検証用の一時ノートをCloudへ1件作成する
-
pull --dry-runがその1件だけを表示する - 手動pullでObsidianにMarkdownが作成される
- ローカルのBasic Memory検索で同じノートを取得できる
- 検証用ノートをCloudとローカルの両方から削除する
- 再度
pull/pushのdry-runを実行し、差分がないことを確認する - LaunchAgentから1回起動し、終了コードとログを確認する
一時ノートには秘密情報や実データを使わず、「同期確認」のような削除してよい内容だけを入れます。
注意したいのは、追加型pullではCloud側の削除がローカルへ伝播しない点です。検証用ノートを消すときは、Cloudとローカルの両方で削除しました。
うまく動かないときの確認順序
認証エラー
まずCloudの認証状態を確認します。必要なら公式手順に沿ってログインし直します。
bm cloud status
bm cloud logout
bm cloud login
競合で停止する
停止は意図した安全動作です。いきなり競合解決オプションを変えず、dry-runと双方のMarkdownを確認します。
bm cloud pull --name obsidian-notes --dry-run
手動実行は成功するがlaunchdでは失敗する
次を確認します。
- plistのコマンドがフルパスになっているか
-
bmのパスがスクリプト内で明示されているか - Vaultの保存先をLaunchAgentから参照できるか
- 標準エラーのログに権限エラーがないか
- スクリプト更新後、インストール先のコピーも更新したか
設計上のポイント
この構成で重要だったのは、同期そのものより「自動化する範囲を狭くしたこと」です。
- Cloudからの追加型pullだけを自動化する
- ローカルからCloudへの公開範囲は毎回レビューする
- 競合は失敗として人に戻す
- dry-runを初回設定とトラブル対応の起点にする
- Markdownを正本とし、検索インデックスは再生成可能なものとして扱う
- トークン、ログ、SQLite、Git内部ファイルは同期対象に含めない
まとめ
Basic Memory CloudとObsidianの連携では、便利さだけを優先して双方向同期を自動化すると、削除や競合の判断までジョブへ渡してしまいます。
まずは--dry-runで対象を確認し、追加型pull、--on-conflict fail、同期先と除外設定の事前チェックを組み合わせると、安全側に倒した定期取り込みを構成できます。pushはレビュー後に手動実行する運用から始め、問題なく回せることを確認してから自動化範囲を広げるのが安全です。