# CuePaste 技術スタック調査

調査日: 2026-06-07

## 目的

`docs/promptpaste_spec.md` の MVP を実装するために、類似アプリの方式と macOS の公式 API、利用可能なライブラリ、配布上の制約を比較する。

## 類似アプリから確認できる方式

### 1. ランチャー検索型

Raycast の Snippets は、ショートカットで検索画面を開き、候補を選んで前面アプリへ貼り付けられる。CuePaste の「コマンド入力画面を開く -> キーワードを入力する -> 貼り付ける」に最も近い。

特徴:

- 利用者が明示的にランチャーを開く。
- 候補検索やエラー表示を画面内で扱いやすい。
- 常時キー入力を監視する必要がない。
- 元の前面アプリを記録し、ランチャーを閉じてから戻す処理が必要になる。

### 2. 略語自動展開型

Alfred や一般的なテキスト展開アプリは、入力文字列を常時監視し、略語を本文へ置換する。

特徴:

- 操作は速いが、全キーボード入力の監視が必要になる。
- 誤展開、パスワード欄、セキュア入力、入力メソッドとの相互作用を考慮する必要がある。
- CuePaste の MVP 仕様には常時監視が含まれないため、初期実装としては過剰である。

### 3. 既存ランチャー拡張型

Raycast Extension では `Clipboard.paste` や `Action.Paste` を利用できるため、ファイル検索部分だけを実装して短期間で動作検証できる。

特徴:

- ウィンドウ、ショートカット、前面アプリへの貼り付けを Raycast に委譲できる。
- 独立アプリではなく、利用者に Raycast の導入を要求する。
- メニューバー常駐、独自設定画面、独自配布という仕様から外れる。

結論:

- 動作仮説の検証用プロトタイプには使える。
- CuePaste 本体の実装方式には採用しない。

## 技術スタック候補

| 候補 | 長所 | 短所 | 判断 |
| --- | --- | --- | --- |
| Swift + SwiftUI + AppKit | macOS APIへ直接アクセスできる。小さな常駐アプリに適する。配布物を軽くできる | AppKitのウィンドウ、フォーカス、権限処理が必要 | 採用 |
| Raycast Extension + TypeScript | 検索UIと貼り付けを短く実装できる | Raycast必須。独立アプリにならない | 検証用のみ |
| Tauri + Web UI + Rust | Electronより軽量で、グローバルショートカットの公式プラグインがある | 自動貼り付けとmacOS権限処理にはネイティブ実装が残る。技術要素が増える | 不採用 |
| Electron + TypeScript | UI構築が容易。グローバルショートカットAPIがある | Chromium同梱で規模に対して重い。貼り付けには結局macOS固有処理が必要 | 不採用 |
| AppleScript / CLI中心 | 簡易試作が速い | 権限、フォーカス、エラー処理、配布が不安定になりやすい | 本体には不採用 |

## 開発環境

確認した実機:

- MacBook Air 2018
- Intel Core i5
- macOS Sonoma 14.8.7
- 内蔵ストレージ空き容量: 約10GB

CPUとOSの条件ではMVPの開発と実機テストは可能である。ただし、空き容量10GBではXcode 16.2の導入と継続利用はできない。

推奨構成:

- Xcode 16.2
- Swift 6.0コンパイラ
- Swift 5言語モード
- Deployment Target: macOS 13
- 実行対象: My Mac

Appleの対応表では、Xcode 16.2はmacOS Sonoma 14.5以降で動作する。現在の最新XcodeはSonomaに対応しないため、Mac App StoreからではなくApple Developer DownloadsからXcode 16.2を取得する。

Intel Macでも通常のmacOSアプリ開発は可能である。Apple Silicon専用と明記されている機能や予測コード補完は利用できないが、CuePasteのSwiftUI、AppKit、Core Graphicsを使う実装には不要である。

注意点:

- Xcodeの圧縮ファイル、展開時の一時領域、Xcode本体、ビルドデータが必要になる。
- インストール前に内蔵ストレージで最低40GB、安定運用には50GB以上の空き容量を推奨する。
- 2018年モデルでは初回ビルド、Swift Packageの解決、SwiftUI Previewが遅くなる可能性がある。
- CuePasteは小規模なmacOSアプリで、iOS Simulatorも不要なため、性能上の問題は限定的と見込む。
- UI確認はSwiftUI Previewだけに依存せず、`My Mac`で直接実行する。
- 開発とローカル実行は無料のApple Accountで開始できる。Developer ID署名とnotarizationによる配布時にはApple Developer Programへの加入が必要になる。

容量確保の選択肢:

1. 内蔵ストレージを整理し、最低40GB以上を空ける。
2. 50GB以上の空きがある外付けSSDを用意し、Xcode本体とプロジェクトを置く。
   - HDDや低速USBメモリではビルドが大幅に遅くなるため、SSDを使う。
   - DerivedDataなど一部のデータは標準設定では内蔵ストレージにも作られるため、内蔵側にも余裕が必要である。

AppleのCommand Line ToolsにはmacOS SDKとSwiftツールチェーンが含まれるが、`xcodebuild`は含まれない。CuePasteではXcodeプロジェクト、アプリ署名、entitlements、実機実行を扱うため、Command Line Toolsだけで進める構成は採用しない。

## 推奨スタック

### アプリ本体

- Swift
- SwiftUI
- AppKit
- Core Graphics
- ApplicationServices
- Swift Package Manager

### UI

- アプリ入口と設定画面は SwiftUI を使う。
- メニューバー常駐には `MenuBarExtra` を使う。
- 中央に表示してキーボードフォーカスを受けるコマンド画面は `NSPanel` を使い、内容を SwiftUI で構築する。
- `MenuBarExtra` のポップオーバーをコマンド画面の代用にはしない。表示位置がメニューバー基準になり、仕様の「画面中央付近」と合わないためである。

### グローバルショートカット

- `sindresorhus/KeyboardShortcuts` を Swift Package Manager で導入する案を第一候補とする。
- ショートカット記録UI、`UserDefaults` への保存、競合警告を利用できる。
- ライブラリは App Sandbox と Mac App Store に対応しているが、自動ペースト部分の配布制約とは分けて判断する。
- 初期値 `⌘ + :` はキーボード配列によって `⌘ + Shift + ;` として扱われる可能性があるため、実機で確認する。

### ファイルアクセス

- `FileManager` と `URL` を使い、文字列でパスを連結しない。
- 検索は再帰走査ではなく、入力キーワードから候補URLを直接組み立てる。
- 候補は `{keyword}.md`、次に `{keyword}.txt` の2件だけとする。
- UTF-8 の `String(contentsOf:encoding:)` で読み込む。
- 設定画面では `NSOpenPanel` または SwiftUI の `fileImporter` でフォルダを選択する。
- サンドボックスを採用する場合のみ、読み取り専用の security-scoped bookmark を保存する。

### クリップボードと自動ペースト

- クリップボード書き込みは `NSPasteboard.general` を使う。
- コマンド画面を開く直前に `NSWorkspace.shared.frontmostApplication` を保存する。
- 実行時は、ファイル読込成功後にのみクリップボードを書き換える。
- パネルを閉じ、保存したアプリへフォーカスを戻してから、Core Graphics の `CGEvent` で `⌘V` のキーイベントを送る。
- アクセシビリティ信頼状態は `AXIsProcessTrustedWithOptions` で確認する。
- アプリ復帰とキー送信の間隔を固定値のフォールバック群で調整せず、まずアプリのアクティブ化通知などで処理順を確定する。

### 状態保存

- 初期段階は `UserDefaults` / `@AppStorage` を使用する。
- 保存対象はショートカット、プロンプトフォルダ参照、権限未許可時にコピーするかどうかの設定に限定する。
- データベースは使用しない。

### テスト

- Swift Testing または XCTest のうち、Xcodeプロジェクト作成時に標準で扱いやすい方を採用する。
- ファイル検索と読込はUIやmacOS権限から分離し、ユニットテストする。
- `NSPasteboard`、アプリ切替、`CGEvent` は薄い境界にまとめ、実機手動テストを併用する。
- 少なくとも `.md` 優先、`.txt` フォールバック、サブフォルダ除外、未検出、読込失敗、パストラバーサルを検証する。

## 配布方式

### MVP

MVPは、GitHub Actionsで作成したIntel macOS向けの未署名DMGを配布する。

方針:

- GitHub ActionsのIntel macOS runnerでビルドする。
- Intel Mac向けのアプリとしてビルドする。
- `hdiutil` などmacOS標準ツールで `.app` をDMG化する。
- コード署名と公証はMVPでは行わない。
- 配布物はGitHub Releasesから取得できるようにする。

未署名DMGは、初回起動時にmacOSのGatekeeperによる警告が表示される可能性がある。これはMVPの配布簡略化として受け入れ、署名と公証は初期リリース後の改善対象とする。

### 将来候補

Mac App Storeではなく、Developer ID署名とnotarizationを使った直接配布を将来候補とする。

理由:

- Mac App Store では App Sandbox が必須である。
- Apple は App Sandbox と互換性がない活動として、支援アプリでのアクセシビリティAPI利用などを挙げている。
- 自動 `⌘V` は CuePaste の中心機能であり、審査やサンドボックス制約を前提にすると成立性が不安定になる。
- 直接配布でも署名と notarization により Gatekeeper に対応できる。

これはAppleの資料からの設計上の推論であり、個別の審査結果を保証するものではない。将来Mac App Store対応を検討する場合は、「コピーのみで自動ペーストしないモード」を別製品要件として再検討する。

## 推奨する初期構成

```text
CuePaste/
  App/
  Domain/
    PromptResolver.swift
    PromptReader.swift
  Services/
    ClipboardService.swift
    PasteService.swift
    AccessibilityPermissionService.swift
    PromptFolderStore.swift
  UI/
    CommandPanel/
    Settings/
  CuePasteTests/
```

責務:

- `PromptResolver`: キーワードから `.md` / `.txt` の候補を安全に解決する。
- `PromptReader`: UTF-8テキストを読み、読込エラーを分類する。
- `ClipboardService`: 読込成功後のクリップボード更新だけを担当する。
- `PasteService`: 対象アプリの記録、復帰、`⌘V` 送信を担当する。
- `AccessibilityPermissionService`: 権限確認と案内を担当する。
- `PromptFolderStore`: フォルダ設定と、必要な場合のブックマークを担当する。

MVPではプロトコルやレイヤーを増やしすぎず、OS依存処理と純粋な検索処理をテスト可能な境界で分ける。

## 実装前に確認する事項

1. 最低対応macOSを `13` 以降とするか。
   - `MenuBarExtra` をそのまま使えるため、現時点では `macOS 13+` を推奨する。
2. GitHub ActionsでIntel macOS向けの未署名DMGを作るworkflowを用意する。
3. 初回起動時にショートカット `⌘ + :` を登録するか、利用者に選択させるか。
   - `KeyboardShortcuts` は公開アプリで初期ショートカットを強制しないことを推奨している。
   - 仕様を優先するなら初期値を設定し、競合時に変更を促す。
4. アクセシビリティ未許可時に、本文をクリップボードへコピーする初期設定を有効にするか。
5. プロンプトフォルダ未作成時にアプリが作成するか、選択を求めるか。

## 参照

- [Raycast Snippets](https://manual.raycast.com/snippets/how-to-import-snippets)
- [Raycast Clipboard API](https://developers.raycast.com/api-reference/clipboard)
- [Alfred Snippets and Text Expansion](https://www.alfredapp.com/help/features/snippets/)
- [Apple: MenuBarExtra](https://developer.apple.com/documentation/swiftui/menubarextra)
- [Apple: NSPanel](https://developer.apple.com/documentation/appkit/nspanel)
- [Apple: NSPasteboard](https://developer.apple.com/documentation/appkit/nspasteboard)
- [Apple: NSWorkspace](https://developer.apple.com/documentation/appkit/nsworkspace)
- [Apple: CGEvent](https://developer.apple.com/documentation/coregraphics/cgevent)
- [Apple: AXIsProcessTrustedWithOptions](https://developer.apple.com/documentation/applicationservices/1459186-axisprocesstrustedwithoptions)
- [Apple: Accessing files from the macOS App Sandbox](https://developer.apple.com/documentation/security/accessing-files-from-the-macos-app-sandbox)
- [Apple: Protecting user data with App Sandbox](https://developer.apple.com/documentation/security/protecting-user-data-with-app-sandbox)
- [Apple: Security and direct distribution](https://developer.apple.com/security/)
- [Apple: Xcode support matrix](https://developer.apple.com/support/xcode/)
- [Apple: Installing the command-line tools](https://developer.apple.com/documentation/xcode/installing-the-command-line-tools)
- [Apple: Developer ID](https://developer.apple.com/support/developer-id/)
- [KeyboardShortcuts](https://github.com/sindresorhus/KeyboardShortcuts)
- [Tauri Global Shortcut](https://v2.tauri.app/plugin/global-shortcut/)
- [Electron globalShortcut](https://www.electronjs.org/docs/latest/api/global-shortcut)
