mcpField
T
tauri

tauri

github.com/epicenterhq開発4.8k105 回インストール2 週間前に更新
リポジトリ

tauri について

Tauri でデスクトップアプリを開発するためのスキルです。Rust コマンド、IPC、権限、パス、ファイル操作を扱い、セキュリティ境界や生成バインディングの同期、上流資料による検証を重視します。

主な機能

  1. 01Rust コマンドと権限の設計
  2. 02IPC と型付きバインディングの指針
  3. 03セキュリティ設定と CSP
  4. 04クロスプラットフォームのパス処理
  5. 05ファイル操作とネイティブ境界の設計

活用シーン

  1. 01フロントエンドとバックエンド間の Tauri コマンド実装
  2. 02ウィンドウ権限や CSP の調整
  3. 03TypeScript バインディングの生成と確認
  4. 04クロスプラットフォームのファイルパス処理

インストール

コマンドをコピーしてターミナルで実行します。

$npx skills add https://github.com/epicenterhq/epicenter --skill tauri

インストール後、Claude Code で/tauriを入力して読み込まれたか確認します。

同梱ファイルreferences/rust-command-errors.md

詳細

Tauri のパターン

参照リポジトリ

  • Tauri:Rust バックエンドと Web フロントエンドを備えたデスクトップアプリフレームワーク

上流の情報源に基づく確認

Tauri のコマンドの動作、権限、ケイパビリティ、CSP、アセットプロトコル、パス API、プラグインのファイルシステム動作、または IPC のセマンティクスが正確性に影響する場合は、記憶だけに頼らず、ソースに基づいて確認してください。DeepWiki MCP が利用可能であれば、tauri-apps/tauri に絞った質問をしてください。利用できない場合や、リポジトリがインデックス化されていない場合は、上流のソースまたは公式ドキュメントを直接参照してください。DeepWiki は概要を把握するために使い、重要な詳細はコードを変更する前に、ローカルの生成済みバインディング、インストール済みの Rust クレート、TypeScript の型、ソース、または公式ドキュメントで確認してください。

リポジトリ内で確認できるコマンドの命名規則や、アプリ固有のラッパー規約については、DeepWiki で確認する必要はありません。

コマンド、権限、セキュリティ

  • 用途を絞った Rust API を #[tauri::command] で公開し、generate_handler! で登録してください。失敗する可能性のある処理では Result<T, E> を返してください。
  • コマンドの入力は Rust 側で検証してください。TypeScript の呼び出し元は信頼境界ではありません。
  • 現在、Epicenter のケイパビリティではウィンドウラベルごとにコマンドへのアクセスを許可しています。コマンドの登録と、関連するケイパビリティの許可設定に一貫性を持たせてください。提案中の ADR-0402 ではこれらをホスト全体に適用される定数に置き換える予定ですが、まだ実装されていないため、ネイティブ呼び出しを追加する際に前提としてはいけません。
  • CSP、devCsp、アセットプロトコルの設定、convertFileSrc、freezePrototype、リモート IPC は、セキュリティ上慎重に扱うべき設定です。
  • 長期間存続する Rust オブジェクトは、フロントエンドの ResourceId を持つ Tauri リソースにしてください。複雑な長期間存続するオブジェクトをコマンドの応答を通じてシリアライズしてはいけません。
Webview の CSP

app.security.csp: null を決してリリースしないでください(CSP が完全に無効になります)。 最も重要なディレクティブは connect-src です。これを API のオリジンと Tauri の IPC に限定すると、同一オリジンに挿入されたスクリプトが、メモリ内の シークレット(トークン、キー)を攻撃者のホストに流出させるのを防げます。まずは 制限の厳しいポリシーを設定し、アセットプロトコル、wasm、ワーカー、メディア、 開発サーバーのオリジンなど、アプリが実際に使用するソースだけを追加してください。 csp(本番環境)と devCsp(開発用のオーバーライド。tauri dev 実行時に csp を置き換えます)の両方を設定してください。

"security": {
  // Tauri's tauri-codegen hashes every inline <script> in the built
  // frontendDist and injects the hashes, so production script-src does NOT
  // need 'unsafe-inline' (a SvelteKit SPA still boots via its hash).
  "csp": "default-src 'self'; connect-src 'self' ipc: http://ipc.localhost https://api.example.com wss://api.example.com; img-src 'self' data: blob: https:; style-src 'self' 'unsafe-inline'; script-src 'self'; object-src 'none'; base-uri 'self'; frame-ancestors 'none'",
  // Dev loads from the Vite server (not the hashed build), so its inline/HMR
  // scripts ARE unhashed: devCsp must keep 'unsafe-inline' (+ 'unsafe-eval')
  // and add the localhost dev origins.
  "devCsp": "default-src 'self'; connect-src 'self' ipc: http://ipc.localhost http://localhost:5173 ws://localhost:5173 https://api.example.com wss://api.example.com; img-src 'self' data: blob: https:; style-src 'self' 'unsafe-inline'; script-src 'self' 'unsafe-inline' 'unsafe-eval'; object-src 'none'; base-uri 'self'"
}

ルール:connect-src には必ず ipc: http://ipc.localhost を含めてください。含めないと invoke() が動作しません。アセットプロトコルが実際に有効な場合 (convertFileSrc)に限り、asset: / http://asset.localhost を指定してください。 実際に tauri dev を実行し、リリースビルドも作成して、必ずスモークテストを行ってください。 その際は Webview のコンソールを確認し、CSP 違反がないことを確かめてください。

型付き IPC と生成されたバインディング

Tauri アプリで tauri-specta を使用する場合は、Rust のコマンドレジストリ、生成された TypeScript バインディング、アプリが所有するコマンドサーフェスを同期させてください。コマンドで TypeScript が使用するエラーを変更するとき、または生成された Result が IPC を通じてどのように渡されるかを選択するときは、Rust コマンドのエラーを参照してください。

  • すべての型付きコマンドを tauri_specta::collect_commands! ビルダーに登録します。
  • フロントエンドがリッスンするイベントのペイロードは、そのイベント型を返すコマンドがない場合でも、tauri_specta::collect_events! に登録します。
  • tauri-specta v2 RC のイベントでは、イベント型に #[tauri_specta(event_name = "...")] を使用します。インストール済みのマクロドキュメントまたはローカルのマクロソースでその形式が存在すると確認できない限り、#[tauri_specta::event(...)] を作らないでください。
  • lib.rs がビルダー用にイベントとコマンドのペイロード型をインポートする場合は、それらを所有する Rust モジュールから再エクスポートします。
  • bindings.gen.ts は派生出力として扱います。コマンド、エラー、または生成モードを変更した場合は、再生成し、両方の利用側を確認します。コマンドの変更が Rust のコンパイル形式を修正するだけで、公開 IPC コントラクトを変更しない場合は、生成ファイルに広範な変更が生じないようにします。
  • tauri::ipc::Response をそのまま返すコマンドは、本文が specta::Type ではないため、specta では生成できません。これらは別の tauri::generate_handler! ルートを通してマウントし、小規模な手書きの TypeScript ラッパーを用意します。

IPC の変更を検証するには、通常、両側での確認が必要です。

cargo check --manifest-path apps/epicenter/src-tauri/Cargo.toml
cargo test --manifest-path apps/epicenter/src-tauri/Cargo.toml export_types

バインディングの生成によって無関係なセクションが書き換えられた場合は、コミットする前に差分を確認してください。

コンテキストの検出

パス API を選ぶ前に、実行コンテキストを特定してください。

コンテキスト場所適切な API
Tauri フロントエンドapps/*/src/**/*.ts、apps/*/src/**/*.svelte@tauri-apps/api/path
Node.js/Bun バックエンドpackages/**/*.ts、CLI ツールNode.js の path モジュール

ルール:コードがブラウザー(Tauri WebView)で実行される場合は、Tauri のパス API を使用してください。Node.js/Bun で実行される場合は、Node.js の path モジュールを使用してください。

@tauri-apps/api/path で利用できる関数

パス操作
関数用途例
join(...paths)プラットフォームの区切り文字でパス要素を結合しますawait join(baseDir, 'workspaces', id)
dirname(path)親ディレクトリを取得しますawait dirname('/foo/bar/file.txt') → /foo/bar
basename(path, ext?)ファイル名を取得します。拡張子は任意で除外できますawait basename('/foo/bar.txt', '.txt') → bar
extname(path)ファイル拡張子を取得しますawait extname('file.txt') → .txt
normalize(path).. と . の要素を解決しますawait normalize('/foo/bar/../baz') → /foo/baz
resolve(...paths)絶対パスに解決しますawait resolve('relative', 'path')
isAbsolute(path)パスが絶対パスかどうかを確認しますawait isAbsolute('/foo') → true
プラットフォーム定数
関数用途戻り値
sep()プラットフォームのパス区切り文字Windows では \、POSIX では /
delimiter()プラットフォームのパス区切り文字Windows では ;、POSIX では :

Tauri v2 では、sep() と delimiter() は同期処理です。ディレクトリやパスを操作するヘルパー関数のほとんどは、バックエンドを呼び出すため非同期処理です。

ベースディレクトリ
機能用途
appLocalDataDir()アプリのローカルデータディレクトリ
appDataDir()アプリのローミングデータディレクトリ
appConfigDir()アプリの設定ディレクトリ
appCacheDir()アプリのキャッシュディレクトリ
appLogDir()アプリのログディレクトリ
tempDir()システムの一時ディレクトリ
resourceDir()アプリのリソースディレクトリ
resolveResource(path)リソースを基準にパスを解決

パターン

パスの構築(正しい方法)
import { appLocalDataDir, dirname, join } from '@tauri-apps/api/path';

// Join path segments; handles platform separators automatically
const baseDir = await appLocalDataDir();
const filePath = await join(baseDir, 'workspaces', workspaceId, 'data.json');

// Get parent directory; cleaner than manual slicing
const parentDir = await dirname(filePath);
await mkdir(parentDir, { recursive: true });
パスのログ出力(例外)

人が読みやすいログ出力では、ファイルシステム操作には使用しないため、/ をハードコードしても問題ありません。

// OK for logging; consistent cross-platform log output
const logPath = pathSegments.join('/');
console.log(`[Persistence] Loading from ${logPath}`);

アンチパターン

禁止:文字列の手動連結
// BAD: Hardcoded separator breaks on Windows
const filePath = baseDir + '/' + 'workspaces' + '/' + id;

// BAD: Template literal with hardcoded separator
const filePath = `${baseDir}/workspaces/${id}`;

// GOOD: Use join()
const filePath = await join(baseDir, 'workspaces', id);
禁止:親ディレクトリの手動抽出
// BAD: Manual slicing is error-prone
const parentSegments = pathSegments.slice(0, -1);
const parentDir = await join(baseDir, ...parentSegments);

// GOOD: Use dirname()
const parentDir = await dirname(filePath);
禁止:ファイルシステム操作での区切り文字のハードコード
// BAD: Windows uses backslashes
const configPath = appDir + '/config.json';

// GOOD: Platform-agnostic
const configPath = await join(appDir, 'config.json');
禁止:パス形式の決めつけ
// BAD: Splitting on '/' fails on Windows paths
const parts = filePath.split('/');

// GOOD: Use dirname/basename for extraction
const dir = await dirname(filePath);
const file = await basename(filePath);

インポートパターン

常に @tauri-apps/api/path からインポートしてください。

import {
	appLocalDataDir,
	dirname,
	join,
	basename,
	extname,
	normalize,
	resolve,
	sep,
} from '@tauri-apps/api/path';

非同期処理に関する注意

Tauri のパスヘルパーの多くは、IPC 経由で Rust バックエンドと通信するため、非同期です。ヘルパーが Promise を返すと決めつける前に、インストールされている TypeScript の型定義を必ず確認してください。appLocalDataDir()、join()、dirname() などのディレクトリヘルパーやパス操作ヘルパーは非同期です。一方、sep() や delimiter() などの単純な定数は、Tauri v2 では同期処理です。

const baseDir = await appLocalDataDir();
const filePath = await join(baseDir, 'file.txt');
const parent = await dirname(filePath);
const separator = sep();

ファイルシステム操作

ファイル操作には、Tauri のパス API と組み合わせて @tauri-apps/plugin-fs を使用してください。

import { appLocalDataDir, dirname, join } from '@tauri-apps/api/path';
import { mkdir, readFile, writeFile } from '@tauri-apps/plugin-fs';

async function saveData(segments: string[], data: Uint8Array) {
	const baseDir = await appLocalDataDir();
	const filePath = await join(baseDir, ...segments);

	// Ensure parent directory exists
	const parentDir = await dirname(filePath);
	await mkdir(parentDir, { recursive: true });

	await writeFile(filePath, data);
}

ネイティブ側の所有権境界

ネイティブ側がデータを所有する場合は、フロントエンドが制御するパスよりも、アプリが所有する識別子を優先してください。

  • Rust が録音ディレクトリを所有する場合、録音操作には録音 ID を渡してください。
  • モデルの選択ではパスを渡せますが、Rust 側で正規化し、許可されたアプリデータモデルのディレクトリ外の値は拒否してください。
  • Markdown のエクスポート、ダウンロード、一時ファイルには、広範なファイルシステム権限ではなく、アプリが所有するディレクトリをルートとする目的に特化したコマンドを使用してください。
  • ケイパビリティ、プラグイン、またはコマンドを削除する場合は、それについて記載している古いドキュメントや UI も削除してください。提案中の ADR-0402 では、ケイパビリティの検証を 1 つのスキーマ比較に統合する予定です。現在のテストでは、引き続き個別のウィンドウ権限を検証しています。

フロントエンドはユーザーの意図を記憶できます。ファイルシステムの境界を強制するのは Rust です。

コメント