Obsidian の Canvas でワークフローを定義するプラグイン "Runestone" を作った
Table of Contents
Obsidian 上の Canvas でワークフローを定義・実行できるようにするプラグイン “Runestone” を作った。 記事作成時点のバージョンは v0.2.0。 BRAT 経由でインストールできる。
Runestone とは #
Obsidian には Canvas というキャンバスツールが内蔵されている。 Obsidian 内のノートをノードとして、それらをエッジ(矢印)で接続することができる。
Runestone は、この Canvas に配置した各ノート(Markdown ファイル)をワークフローのノードに見立てて、順番に実行できるようにするというプラグイン。 ノード間の接続は Canvas のエッジ(矢印)で表現し、データは JSON 形式で次のノードへ流れていく。 どのノードが実行されたのかは Canvas 上で視覚的に把握できる。
ざっくり言うと、Obsidian 上で動く n8n1 のようなもの。
ただし、入力UIや外部サービスとの接続などの機能は提供しない。
それらをサポートするとキリがないので、必要最低限の機能しか実装していない方針としている。
必要があれば後述する script や exec ノードで自分で用意すればよい。
プラグイン作成の背景 #
Agent coding により Obsidian プラグイン作成のハードルが大きく下がった。 ちょっとした機能を、低コストでプラグインとして実現できるようになった。
プラグイン自体の作成コストは下がったものの、相対的にプラグインとしての体裁を整えるのが手間になってきた。 「Obsidian 上で何かしらの操作を行う」系の要求は、汎用的なワークフロー実行の仕組みがあれば事足りるだろう、という考えから作ったのが今回の Runestone というプラグイン。
ワークフローの例 #
リポジトリにサンプル vault を用意しているので、実際に動かして試すことができる2。 Obsidian から既存の vault として開いてみてほしい。
サンプルには PARA メソッド3に沿ったノート作成ワークフローが含まれている。
流れとしては、exec ノードでワークフローを開始し、args ノードで設定したパラメータを元に script ノードがダイアログを表示、入力内容に応じて condition ノードが分岐先を決定し、最終的に script ノードがノートを作成する、というもの。
ノードの種類 #
Runestone は Canvas 上にノードを配置し、それらをエッジでつなぐことでワークフローを定義する。
ノードは以下の4種類。
それぞれの frontmatter に runestone.type を指定することで区別する。
| ノード | 役割 | 用途 |
|---|---|---|
| exec | 外部コマンドの実行 | シェルコマンドや Claude Code の呼び出しなど |
| script | JavaScript の実行 | Obsidian API を使ったノート操作やダイアログ表示 |
| condition | 条件分岐 | 返り値に応じて処理の流れを分岐 |
| args | 引数の提供 | script/exec ノードにパラメータを渡す |
すべて通常のノートなので、コードブロック外に実装メモや使い方を書くことができる。
exec #
外部コマンドを実行するノード。
コードブロック(bash)に書いたシェルコマンドが実行され、標準出力が JSON として次のノードに渡される。
前のノードからの入力は {{input[n].name}} で、args ノードの値には {{args.name}} でアクセスできる。
args ノードについては後述。
---
runestone.type: exec
---
```bash
echo '{"message": "hello"}'
```
Claude Code などを呼び出せば、ワークフロー内に AI への指示とその出力を組み込むことができる。
---
runestone.type: exec
---
```bash
claude -p 'What is your name? Answer must be JSON format like: {"name": <answer>}'
```
script #
Obsidian 上で JavaScript を実行するノード。
こちらもコードブロック(javascript)に書いたスクリプトが実行される。
スクリプト中で app(Obsidian API)や obsidian モジュールにアクセス可能。
前のノードからの入力は input[n].name で、args ノードの値には args.name でアクセスできる。
args ノードについては後述。
---
runestone.type: script
---
```javascript
const result = input[0].message.toUpperCase();
return { result };
```
Obsidian の API を使えるので、ノートの作成や編集、モーダルダイアログの表示など、Obsidian 上でできることはだいたいできる。 他のプラグインが提供する API、例えば Templater の API を呼び出せばテンプレートを使ってファイルを作成することもできる。
---
runestone.type: script
---
```javascript
const targetPath = ...;
const file = ...;
const templaterPlugin = app.plugins.plugins["templater-obsidian"];
await templaterPlugin.templater.overwrite_file_commands(file);
return { path: targetPath };
```
condition #
条件分岐を行うノード。 返り値の文字列がエッジのラベルと一致する方向に処理が分岐する。
---
runestone.type: condition
---
```javascript
return input[0].paraType;
```
入力データはそのまま次のノードに渡される(返り値はあくまで分岐の判定にのみ使われる)。 サイクル(ループ)にも対応しており、終了条件を設定すれば繰り返し処理も書ける。
args #
script や exec ノードに引数を提供するノード。
返り値のオブジェクトが args パラメータとして渡される。
---
runestone.type: args
---
```javascript
return {
title: "Enter note title",
placeholder: "My new note",
};
```
args を受け取る script の例:
---
runestone.type: script
---
```javascript
async function dialog(title, placeholder) {
// テキスト入力を促すダイアログ実装
...
return { value: value }
}
return { value: await dialog(args[0].title, args[0].placeholder) };
```
input ではなく args として渡される。
args を使えばひとつの汎用 exec/script ノードを異なる設定で再利用することができる。
Agent Skill 対応 #
ワークフローをイチから手書きするのはそれなりに面倒なので、Claude Code 用の Agent Skill も用意した。 以下のコマンドでインストールできる。
/plugin marketplace add https://github.com/handlename/obsidian-plugin-runestone
/plugin install runestone-workflow
これにより /runestone-workflow というカスタムスラッシュコマンドが使えるようになる。
このコマンドに続けて作りたいワークフローの内容を説明すれば、Canvas ファイルとノード用の Markdown ファイルがまとめて生成される。
細かい調整は手動で行う必要があるかもしれないが、ゼロから書くよりはだいぶ楽。 まずはこの Skill を使って汎用的なノードを整備し、それらを使ってワークフローを組み上げるのがいいだろう。 もちろん、組み上げる部分を Agent 任せにしてしまってもいい。
開発手法 #
今回のプラグイン開発は、いわゆる spec driven development で進めた。 要件定義 → ドメイン用語集の作成 → 設計ドキュメント → 実装計画、という流れで仕様を固め、あとは Claude Code に実装を任せた。
特に、ドメイン用語集を先に作っておくとエージェントに指示を出す際のフレーズが短く正確になるし、実装もブレにくくなる気がしている。 今回の用語集はこれ。 ワークフロー定義で使用できるノードの種類や、使用する Obsidian の機能などを用語として定義している。
Agent coding が一般的になってからは Obsidian プラグインを量産しているが、プラグイン本体以外は毎回同じような設定をすることになるので、テンプレートリポジトリも用意している。
Obsidian 公式のサンプルプラグインリポジトリ から fork したもので、例えば BRAT 対応のリリースを作る GitHub Actions Workflow を始めから用意していたりする。
今後の展望 #
Runestone 自体は入力UIなどの機能を提供しないが、それらを実装した汎用ノードを集めたライブラリを用意するのは面白そうだ。
Canvas の仕様は JSON Canvas として独立しているので、Obsidian に頼らない、単体で動作するアプリケーションとして切り出すこともできるだろう。
おわりに #
Obsidian に Canvas が実装されてからいまいちピンときていなかったが、今回ようやく自分なりの使い道を見いだせた。 本来の使い方とはかけ離れていそうだが、「ノードをエッジでつなぐ」という UI を有効活用できたのではないか。
n8n のような本格的なワークフローエンジンと比べれば機能は限定的だが、Obsidian 内の操作を定義・可視化するには十分だろう。