2026/2/11

Google Drive の特定のディレクトリをAIと操作するためのアプリを作ってみる

日々のメモ用に Daily Note を使っていて Obsidian で見れるようにしている。
最近はそのノートの編集とかは VSCode の Claude Code Extension で 操作するとかやったりしている。

普段PCで作業しているときは特に問題ないのだけど、たまにPCの前にいないときとか、スマホで Claude アプリで使っている内容をできるだけ共有したいよなぁと。
スマホからPCにつなぐとかすればよいのだけど、、、

Google Drive を利用できるアプリとかはあるのだけど、特定のフォルダだけ制限したかったので、何かしら考えてみる。

この note のディレクトリは Obsidian 経由で GitHub にも連動してるので、結果としては GitHub mobile を使うことで、 Copilot Chat で opusとかつかってノートを編集させれば良さそうだった。

昨日試したときは最初 Copilot Mobile を許可してなくて、それを許可したばかりだったからか、403で何もできずだった。
本日昼過ぎにみたら使えてたので、結果的にはこれでもよかったのかもしれない。

とりあえずは Claude Code で壁打ちをして要件を整理してもらう。

最初は iOS アプリにしてもらおうと思ったのだけど、Webアプリでもいいかな?という感じで、方針転換。


以下、AI作成

VaultChat — Google Drive 上のファイルを AI チャットで操作する Web アプリを作った

TL;DR

Google Drive 上の特定ディレクトリを、どのデバイスから AI チャット経由で読み書きできる Web アプリ VaultChat を開発した。Cloudflare Workers + Hono の BFF 構成で API キーをクライアントに露出させず、Claude の Tool Use でファイル操作を実現している。自分の Obsidian vault を対象にしているが、仕組みとしては Google Drive 上の任意のフォルダに対して動作する。

動機

やりたかったこと

Google Drive 上の特定のディレクトリを、モバイルを含むどのデバイスからでも AI チャットで操作したかった。

自分の場合、そのディレクトリは Obsidian vault (daily notes, リサーチノート, ソース管理) であり、Mac 上では Claude Code を使って「AI がファイルを読み書きし、人間がレビューする」ワークフローが定着していた。この体験をモバイルでも再現したいというのが出発点になる。

既存の手段を試した結果

自分の vault は Git 連携しており、GitHub リポジトリでもある。最初に試したのは GitHub Mobile の Copilot Chat だった。リポジトリのコンテキストを読めるので、理論上はモバイルから vault を操作できる。

しかし意図した使い方ができなかった。自分が求めていたのは「AI がファイルを読み、提案内容を人間がレビューしてから反映する」という Claude Code 的なエージェント体験だった。Copilot Chat はどちらかというとコードを直接書き換える方向で、チャットを通じてファイル内容を確認し、差分を確認してから書き込むというワークフローとは合わなかった。

他のツールも同様に満たせなかった。

  • Obsidian モバイルアプリは AI チャットを搭載していない

  • Claude の iOS アプリはローカルファイルを編集できない

  • ChatGPT 等も外部ストレージとの直接連携は持たない

スマホから自宅 PC に SSH や Tailscale で接続し、Claude Code を直接操作するという手段もある。実際にこのアプローチを取っている人もいるし、技術的には成立する。ただ、PC の常時起動が前提になること、VPN やポートフォワーディングの設定、モバイルでのターミナル操作といったハードルを考えると、自分にとっては「そこまでやるか」という選択肢だった。どちらが正解ということではないが、ブラウザで URL を開くだけで完結する手軽さのほうが自分の用途には合っていた。

最終的に「Claude Code 相当の、AI エージェントがファイルを操作し人間が承認する体験を Web で再現する」ために自作することにした。

後日談: Copilot Chat の改善

VaultChat の開発後、GitHub Mobile の Copilot Chat が改善された。当初は 403 エラーで使えなかったが、時間が経って解決し、リポジトリの内容を読める状態になった。先にこれが安定していたら VaultChat を作らなかった可能性はある。

ただし、両者には明確な違いが残る。

VaultChat の利点:

  • Git 連携が不要。Google Drive のフォルダを指定するだけで使える

  • ファイルの書き込みに対して diff 表示と承認フローがある

  • vault 内の CLAUDE.md でプロンプトをカスタマイズできる

  • Google Drive 上の最新状態を直接参照するため、commit / push を挟まない

GitHub Mobile Copilot Chat の利点:

  • セットアップ不要。リポジトリがあればすぐ使える

  • GitHub のエコシステム (Issues, PR) と統合されている

Git で管理していないファイル群や、commit の手間を省きたい場合は VaultChat のほうが適している。逆に、すでに Git 管理されていて読み取りだけで十分なら Copilot Chat で事足りる場面も多い。

なぜ Web アプリか

当初は iOS ネイティブ (Swift + SwiftUI) を検討したが、Web アプリに方針転換した。理由は3つある。

API キーの安全な管理。 iOS ネイティブでは Claude API キーをクライアント側に持たせるか、別途 BFF を立てる必要がある。Web アプリなら Cloudflare Workers 自体が BFF となり、API キーはサーバー側の環境変数に閉じる。

クロスプラットフォーム対応。 URL を開くだけで PC・iOS・Android すべてから使える。PWA 化すればホーム画面にも追加できる。

成熟したライブラリの活用。 diff 表示には diff2html、Markdown レンダリングには marked.js を使える。iOS で同等の UI をゼロから組む工数と比較して、開発速度に差がある。

アーキテクチャ

BFF (Backend for Frontend) パターンを採用した。ブラウザと外部 API の間に Cloudflare Workers を挟み、認証情報の保護と API 呼び出しの集約を担う。

Browser (Vanilla JS)
  │ fetch / SSE

Cloudflare Workers + Hono (BFF)
  ├── Claude API (Tool Use + Streaming)
  ├── Google Drive API v3
  ├── KV (セッション, 承認データ, キャッシュ)
  └── D1 (会話履歴, 操作ログ)

技術スタック

  • バックエンド: Hono v4 on Cloudflare Workers

  • AI: Claude API (Messages API + Tool Use, ストリーミング SSE)

  • ストレージ: Google Drive API v3

  • 状態管理: Cloudflare KV (セッション・キャッシュ) / D1 SQLite (会話・操作ログ)

  • 認証: Google OAuth 2.0 (Authorization Code + PKCE)

  • フロントエンド: HTML + CSS + Vanilla JavaScript (フレームワークなし)

  • テスト: Vitest (124 unit tests) + Playwright (10 E2E tests)

Hono を選んだ理由

Hono は Cloudflare Workers をファーストクラスターゲットとして開発されたフレームワークで、Workers の制約 (バンドルサイズ制限、V8 Isolate 環境) に最適化されている。

特に streamSSE() ヘルパーが Claude API のストリーミングプロキシに適しており、c.env 経由で KV / D1 に型安全にアクセスできる。バンドルサイズは約 14KB で、Workers Free プランの 1MB 制限に対して十分な余裕がある。

主要機能

Claude Tool Use によるファイル操作

Claude の Tool Use 機能を使い、AI が vault 内のファイルを直接操作する。定義したツールは5つある。

ツール用途ターン制限
read_fileファイル読み取り (最大500行)10回/ターン
list_filesディレクトリ一覧 (最大100件)5回/ターン
search_filesファイル名検索 (最大50件)3回/ターン
write_fileファイル新規作成・上書き3回/ターン
edit_file既存ファイルの部分編集3回/ターン

ツール結果は最大 30,000 文字に切り詰めて Claude に返却する。読み取り系ツールは即座に実行されるが、書き込み系は後述の承認フローを経由する。

SSE ストリーミング

POST /api/chat は Server-Sent Events でレスポンスを返す。Claude の応答テキスト、ツール実行の進捗、承認リクエストなどが同一ストリームで配信される。

フロントエンドでは会話ごとに状態 (メッセージ配列、AbortController、DOM コンテナ) を Map で管理し、会話の切り替え時には DOM コンテナの show/hide で即座に反映する。これにより、ある会話でストリーミング中に別の会話を閲覧・開始できる。

承認フロー付きファイル編集

Claude がファイルの書き込みツールを呼び出すと、以下のフローが走る。

  • BFF 側で diff を生成し、KV に承認データ (diff, checksum, メタデータ) を保存

  • SSE で approval_required イベントをクライアントに送信

  • ブラウザに diff ビューが表示され、ユーザーが承認または却下を選択

  • POST /api/approvals/:id/decision で決定を送信

  • 承認された場合、BFF が md5Checksum を検証してから Google Drive に書き込む

md5Checksum による楽観的ロックを採用しており、承認待ちの間に別のクライアントがファイルを変更していた場合は競合として検出される。

vault 内 CLAUDE.md によるカスタマイズ

vault のルートに CLAUDE.md を置くと、その内容がシステムプロンプトに動的に組み込まれる。Claude Code の CLAUDE.md と同じ発想で、ユーザー固有の指示 (「daily notes は daily/YYYY-MM-DD.md 形式」など) を AI に伝えられる。

セキュリティ設計

個人用ツールではあるが、Google Drive の vault データを扱うため、セキュリティには多層防御のアプローチを取った。

認証・セッション管理

Google OAuth 2.0 の Authorization Code Flow に PKCE を組み合わせている。state パラメータは TTL 5分の使い捨てで、リプレイ攻撃を防ぐ。セッション Cookie は HttpOnly / Secure / SameSite=Lax で設定し、有効期限は7日間とした。

OAuth スコープの段階的昇格

初回ログイン時は drive.readonly スコープのみを要求する。ファイル編集機能を使う場合、ユーザーが明示的にスコープの昇格 (?upgrade=1) を選択する。読み取り専用セッションでは書き込みツールが Claude に公開されないため、AI が書き込みを試行する状況自体が発生しない。

パス検証と読み書き分離

PathValidator が全てのファイルアクセスに対してパスの正規化・検証を行う。

  • 読み取り拒否: .obsidian/, .git/, .github/, .env など

  • 書き込み許可: ユーザーが設定した allowlist のみ (デフォルトは daily/)

  • ディレクトリトラバーサル: .. を含むパスを拒否

Google Drive API のクエリも vaultRootId ベースの in parents 制約をかけており、vault 外のファイルにはアクセスできない設計になっている。

CSRF 対策

Origin 検証ミドルウェアが POST / PUT / DELETE リクエストの Origin ヘッダーを検証し、許可リスト外のオリジンからのリクエストを 403 で拒否する。フロントエンドの Markdown 出力は DOMPurify でサニタイズし、XSS を防止している。

KV キャッシュ戦略

Google Drive API の呼び出し回数を抑えるため、Cloudflare KV にセッション単位のキャッシュを設けた。

対象TTL用途
パス解決結果10分ファイルパス → ID の変換結果
ファイル内容10分read_file の結果
フォルダ ID 一覧5分search_files のスコープ用
CLAUDE.md5分システムプロンプト組み込み用

書き込み操作が発生した場合は該当キャッシュを即座に無効化 (write-through) する。KV Free プランの 1日 1,000 writes 制限に対し、実測では 20〜50 writes/日 で収まっている。

テスト

合計 134 テスト (124 unit + 10 E2E) でカバーしている。

ユニットテスト (Vitest)

テストファイルテスト数対象
path-validator.test.ts26ディレクトリトラバーサル、読み書きポリシー
security-integration.test.ts20セッション認証、Origin Guard、会話分離
tool-executor.test.ts16ターン制限、キャッシュ、エラーハンドリング
diff-engine.test.ts16パッチ生成、編集適用
markdown-sanitize.test.ts15XSS 防止 (script タグ、onerror、javascript: URI)
security.test.ts14CORS、Origin 検証、OAuth state
approval-flow.test.ts11checksum 検証、競合検出
md5-checksum.test.ts6チェックサム計算

E2E テスト (Playwright)

ログイン画面の表示、OAuth リンクの検証、未認証時の API 拒否、Origin Guard の動作、静的アセットの配信など、基本的なスモークテストを 10 ケース用意した。

CI/CD

GitHub Actions で main ブランチへの push 時に以下を実行する。

  • TypeScript 型チェック (tsc)

  • ユニットテスト (vitest)

  • セキュリティ監査 (npm audit)

  • Cloudflare Workers へのデプロイ (wrangler deploy)

開発中に得た知見

requestSubmit() と IME の相性

フォーム送信を JavaScript から行う場合、form.dispatchEvent(new Event('submit')) ではなく form.requestSubmit() を使う。前者は cancelable でないイベントを生成し、Firefox で NetworkError を引き起こす。また IME の変換確定 Enter と送信の Enter が競合する問題は、compositionend イベントのハンドリングで解決した。

Miniflare のタイムゾーン問題

Cloudflare Workers のローカル開発環境 (Miniflare/workerd) では toLocaleString() によるタイムゾーン変換が期待通り動作しない。daily notes のファイル名にユーザーのローカル日時を反映するため、クライアント側で getFullYear() / getMonth() / getDate() 等から日時文字列を組み立て、リクエストの localDatetime フィールドとして送信する方式に変更した。

diff パッケージの trailing newline

npm の diff パッケージの structuredPatch は、末尾に改行がないテキストを「改行が欠落している」として差分に含める。テストフィクスチャでは文字列の末尾に必ず \n を付けることで、意図しない diff の発生を防いだ。

Hono の app.request() シグネチャ

テストで app.request() を使う場合、第2引数が RequestInit、第3引数が Env となる。app.request(req, undefined, env) のように明示的に渡す必要がある。

DriveApiError の instanceof

テストで DriveApiErrorinstanceof チェックが失敗する場合がある。モック側で new DriveApiError() ではなく plain object を投げると instanceof が false になるため、実際のクラスインスタンスを生成する必要がある。

現在の状態と今後

完了済み

  • Phase 1 (読み取り専用 MVP): OAuth、チャット、ファイル読み取り、検索、会話履歴

  • Phase 2 (ファイル編集): 書き込みツール、diff 表示、承認フロー、楽観的ロック

  • 本番化: 自動タイトル生成、KV キャッシュ、エラーハンドリング、E2E テスト、CI/CD

  • UI 改善: モダンカラーパレット、モバイル対応、サイドバーのストリーミングインジケータ

今後の予定

  • ダークモード対応

  • 色味の微調整

  • 会話のエクスポート機能 (Markdown / JSON)

リポジトリ構成

vaultchat/
├── src/
│   ├── index.ts              # Hono エントリポイント
│   ├── routes/               # 認証、チャット、承認、会話、設定
│   ├── services/             # Claude API、Google Drive、ツール実行、diff 生成
│   ├── middleware/            # セッション検証、Origin Guard
│   └── config/               # システムプロンプト
├── public/
│   ├── index.html            # SPA シェル
│   ├── css/style.css         # スタイル (約 1,050 行)
│   └── js/                   # チャット、会話、承認、Markdown、設定
├── test/                     # Vitest ユニットテスト (124 テスト)
├── e2e/                      # Playwright E2E テスト (10 テスト)
├── migrations/               # D1 マイグレーション SQL
└── .github/workflows/ci.yml  # CI/CD

バックエンドの依存パッケージは honodiff の2つだけ。フロントエンドは CDN から marked.js、DOMPurify、diff2html を読み込んでいる。フレームワークを使わず Vanilla JS で組んだことで、バンドルサイズと依存関係の管理をシンプルに保てた。