【prompt-selector】
プロンプト管理ツール
「prompt-selector」をTauriで作った

関連記事

1. promt-selectorとは?

生成AIを使う機会が増えると、よく使うプロンプトがどんどん溜まっていきます。
「あのプロンプト、どこに書いたっけ」とメモアプリを漁ったり、Claudeの過去の会話を遡ったりと、地味に面倒でした。
そこで、プロンプトを一覧から選んでコピーするだけの小さなWindowsデスクトップアプリ「prompt-selector」を作りました。

1. promt-selectorとは?
1. promt-selectorとは?

1.1. できること

画面は「リスト+プレビュー」の2ペイン構成です。

1.1. できること

ツールバーに「コピー」「編集」「追加」が並び、「削除」だけ右端に赤で配置されています。
誤操作で削除してしまわないよう、視覚的に分離する意図があります。

1.1. できること

左ペインでプロンプトを選ぶと右ペインにタイトル・タグ・本文・更新日時が表示されます。
「コピー」を押すと画面上部に「コピーしました。
」という帯が出て、リストの選択はそのまま残ります。

追加フォームはモーダルで、タイトル・タグ・本文の3フィールド構成です。
タイトルか本文が空のまま「保存」を押すと、赤字でバリデーションエラーが出ます。
タグはカンマ区切りで任意入力できます。

2. なぜTauriを選んだのか

「ちょっとしたWindowsアプリを作りたい」という用途で、Electronは少し大げさです。
起動時間が遅く、バイナリも数百MB単位になります1
ネイティブGUIフレームワークを使うなら言語の選択から始まるし、WPFやWinFormsはC#の知識が要ります。

Tauri(タウリ)は、このあたりの悩みを解消してくれました。
フロントエンドをTypeScript+HTML+CSSで書き、バックエンドはRustで実装する構成で、Electronのような重さにならないまま、デスクトップアプリをシュッと出せます2

2.1. モジュール構成

Tauriプロジェクトは、フロントエンドとバックエンドが明確に分かれています。

2.1. モジュール構成
役割技術
フロントエンドTypeScript + Vite
バックエンドRust(Tauri v2コマンド)
テストVitest
ビルドnpm run build でMSI / NSIS生成

フロントエンドはブラウザ技術で書けます。
DOMとTypeScriptで画面を作り、クリップボードへのコピーやJSONファイルの読み書きはRust側のTauriコマンドとして実装しています。

// フロントエンドからRustコマンドを呼ぶ
import { invoke } from "@tauri-apps/api/core";

const prompts = await invoke<Prompt[]>("load_prompts");
await invoke("copy_prompt_to_clipboard", { body: prompt.body });Code language: TypeScript (typescript)
// Rustコマンド側
#[tauri::command]
async fn load_prompts(app: AppHandle) -> Result<Vec<Prompt>, String> {
    // ローカルのJSONファイルから読み込む
}Code language: Rust (rust)

フロントエンドで描いた画面は実際のブラウザレンダリングエンジン(WebView2)で表示され、Rustが裏でOSのリソースに触ります3
この分離のおかげで、「TypeScriptでUIを書きつつ、クリップボードやファイルシステムはRustで安全に扱う」という構成が自然に成立します。

ビルドするとMSIインストーラーとNSISインストーラーが両方生成されます4
npm run build 一発で配布可能なWindowsパッケージが出てくるのは、想像以上に楽でした。

3. データモデルとストレージ

プロンプトは次の型で管理しています。

type Prompt = {
  id: string;
  title: string;
  body: string;
  tags: string[];
  createdAt: string;
  updatedAt: string;
};Code language: TypeScript (typescript)

v1ではSQLiteは使わず、ローカルのJSONファイル一本で動かしています。
TauriにはAppDataDir(Windowsなら %APPDATA%\prompt-selector\)にアクセスするAPIがあるので、Rust側でそのパスにJSONを読み書きするだけです5

検索・ソートの複雑さがそこまでなければ、JSONで十分です。
一覧はupdatedAt降順でソートしてあります。

3.1. Windowsアプリとして動かすには

リリースビルドしたexeをダブルクリックすると、GUIウィンドウと一緒にコマンドプロンプトの黒い窓も起動します。
Rustのデフォルト設定では、Windowsはコンソールアプリとしてビルドされるためです6

GUIアプリとして動かすには、src-tauri/src/main.rs の先頭に次の1行を追加します。

#![cfg_attr(not(debug_assertions), windows_subsystem = "windows")]Code language: Rust (rust)

これを入れると、リリースビルド時だけコンソールウィンドウが出なくなります7
デバッグビルドでは引き続きコンソールが表示されるため、ログを確認しながら開発できます。

ビルド後に確認する場合は、PowerShellで次のように調べられます。

# PEヘッダのサブシステムを確認(2 = GUI)
[System.Reflection.PortableExecutable.PEReader]::new(
  [System.IO.File]::OpenRead("prompt-selector.exe")
).PEHeaders.PEHeader.SubsystemCode language: PowerShell (powershell)

Tauriのドキュメントにはあまり目立って書かれていない箇所ですが、Windowsアプリとして配布するなら最初から入れておくべき設定です。

4. おわりに

「TypeScriptでGUIを書いて、Rustでファイルとクリップボードを叩く」という構成が、思ったよりずっと素直に動きました。
Tauriの scaffold は npm create tauri-app で数分で立ち上がり、ドメインロジックのテストはVitest、ビルドは npm run build だけで完結します。

Electronを使うほどでもない、でもブラウザアプリにはしたくない、というユーティリティツールにはちょうどよい選択肢です。
ローカルデータだけで完結するため、アカウント不要・サーバー不要で動きます。

プロンプト管理の用途以外にも、クリップボード系・辞書系・スニペット系のミニツールにそのまま応用できる構成です。

  1. ElectronはChromiumエンジンとNode.jsランタイムを丸ごと同梱するため、インストーラーサイズが80〜150MB、アイドル時のメモリ使用量が200〜300MBに達するケースが多い。 – Comparing Electron and Tauri for Desktop Applications
  2. TauriはChromiumを同梱せずOSのネイティブWebViewを使うため、インストーラーサイズは多くの場合10MB未満に収まる。起動時間もElectronが1〜2秒かかるのに対し、Tauriは0.5秒未満で起動することが多い。 – Tauri vs Electron Comparison: Choose the Right Framework
  3. WebView2はMicrosoft Edge/Chromiumベースのレンダリングエンジン。Windows 10 April 2018 Update以降とWindows 11には標準搭載されており、ユーザーが追加インストールする必要がない。TauriのデフォルトはWebView2をOSから取得する設定で、セキュリティパッチはWindowsが管理する。 – Windows Installer – Tauri
  4. MSI形式はWiX Toolset v3を使いWindowsマシン上でしか生成できないが、NSIS形式(-setup.exe)はLinux・macOSからのクロスコンパイルにも対応している。どちらを使うかはtauri.conf.jsonbundle.targetsで指定できる。 – Windows Installer – Tauri
  5. Tauri v2のpath APIではappDataDir()関数でアプリのデータディレクトリを取得でき、WindowsではC:\Users\<ユーザー名>\AppData\Roaming\<アプリ識別子>に解決される。Rust側ではapp_handle.path().app_data_dir()で同じパスを取得できる。 – path API – Tauri
  6. RustのWindowsビルドはデフォルトでリンカーにSUBSYSTEM=CONSOLEを渡す。これはRust RFC 1665で整理されており、GUIアプリにはwindows_subsystem属性でSUBSYSTEM=WINDOWSに切り替える必要がある。 – RFC 1665: windows-subsystem
  7. cfg_attrはRustの条件付きコンパイル属性。not(debug_assertions)はリリースビルド時に真になるため、windows_subsystem = "windows"はリリース時のみ有効になる。デバッグビルドではコンソールが残るので、panicのスタックトレースを確認できる。 – The Rust runtime – Rust Reference