ChatSpooler v2.0:機能を
削って設計を作り直した

ChatSpoolerを設計し直しました。

ChatSpoolerは、生成AIに複数のプロンプトを順番に送信するためのmacOSアプリです。

最初のバージョンは機能としては動いていました。送信方法は複数選べて、スケジュールも組めて、ログも保存できました。

関連記事

1. Spoolerとしての仕様

仕様を整理しました。
削除した機能は、スケジュール実行、ログ保存、複数の送信方式、履歴管理、フローティングウィンドウです。

役割は一つだけ。プロンプトをキューに積み、選択した生成AIアプリに順番に送信する。
それ以外の責務は持たせません。

印刷では、ジョブをためて順番に処理します。
ChatSpoolerも同じです。

送信対象はChatGPTやClaudeなどのmacOSアプリです。
送信方法は一つに固定しました。アプリを前面に出し、クリップボードに貼り付けて、Cmd+Enterで送信します。

プロンプトの追加方法は二つです。
フォルダから.txt.mdファイルを読み込む方法と、タイトルと本文を手入力する方法です。

送信後の待機時間は、タイトル末尾の数字で指定します。
「設計案 10」と書けば、送信後に10秒待機します。AIの返事を待つための時間です。

2. 技術的な変更点

2.1. UIフレームワークの変更(PyQt6)

TkinterからPyQt6に移行しました1

QSettingsを使えば設定の永続化が簡単になります2
JSONファイルを自前で管理する必要がなくなりました。

2.2. ファイル構成

5つのファイルに分割しました。

  • main.pyはエントリポイントです。環境チェックとアプリ起動だけを担当します。
  • models.pyはデータ構造を定義します。キューに積むアイテムの情報と、ステータス(待機中、送信中、完了、失敗)を表す列挙型です。
  • system.pyはmacOSの操作を担当します。アプリのフォーカス移動、クリップボード操作、キーストローク送信、通知表示などです。
  • core.pyはビジネスロジックです。キューの管理と、送信ワーカースレッドの制御を行います。
  • ui.pyはユーザーインターフェースです。PyQt6でウィンドウを構築し、ユーザーの操作をコアに伝えます。

2.3. 作業を中断させない工夫

このツールの目的は、作業中にAIへ指示を出すことです。
指示を出すたびに作業が中断されては困ります。

そこで、送信完了後は元のアプリにフォーカスを戻すようにしました。
クリップボードも元の内容に復元します。
ユーザーはすぐに自分の作業に戻れます。

次の送信の3秒前には、macOSの通知センターで知らせます。
「3秒後に送信します」という通知が出るので、心の準備ができます。

2.4. Send Nowボタン

「Add」ボタンの隣に「Send Now」ボタンを追加しました。
プロンプトをキューに追加して、即座に送信を開始します。
AddとStartを1アクションで行うショートカットです。

本文入力欄でCmd+Enterを押しても同じ動作になります。
チャットアプリと同じ操作感です。

2.5. アクセシビリティ権限

macOSでキーストロークを送信するには、アクセシビリティ権限が必要です3

権限がない状態で送信しようとすると、エラーになります。
そこで権限エラーを検出したら、ダイアログを表示して「システム設定を開く」ボタンを用意しました。
ユーザーを該当の設定画面に誘導できます。

ビルドしたアプリを使う場合、ChatSpooler.app自体に許可が必要です。
Pythonやターミナルの許可は引き継がれません。

アプリを再登録する

ビルドし直すたびに、アクセシビリティのリストから一度削除して再追加する必要があります4。macOSはアプリの署名を見ているため、再ビルドすると別のアプリ扱いになります。

手順:

  1. システム設定 → プライバシーとセキュリティ → アクセシビリティ
  2. ChatSpoolerを選択して「−」で削除
  3. 「+」で dist/ChatSpooler.app を追加

署名の問題

未署名のアプリはGatekeeperにブロックされることがあります5

以下を試してください:

# 隔離属性を削除
xattr -cr dist/ChatSpooler.app

# アドホック署名(開発用)
codesign --force --deep --sign - dist/ChatSpooler.app
Code language: PHP (php)

デバッグ方法

ターミナルからアプリを起動すると、エラーが見えます:

dist/ChatSpooler.app/Contents/MacOS/ChatSpooler

3. 課題と限界

ダークモード対応は完全ではありません。ボタンやステータス表示の色は調整しましたが、システム全体の配色に追従する実装にはなっていません。

送信方法が1種類に固定されているため、Cmd+Enter以外の方法でメッセージ送信するサービスには対応できません。

キューが空の状態で待機時間が発生しないようにしましたが、キューに1件だけ残っている状態で送信した場合、その判定タイミングによっては意図しない待機が発生する可能性があります。

  1. PyQt6はQt6フレームワークのPythonバインディングで、TkinterよりもGUI機能が豊富です。特に設定管理やシグナル・スロット機構など、大規模アプリケーションに適した機能を提供します。 – PythonによるGUI開発: Tkinter, PyQt, wxPythonの特徴と選び方
  2. QSettingsはプラットフォーム非依存の設定管理クラスで、Windowsではレジストリ、macOSではplistファイル、Linuxでは設定ファイルに自動的に保存されます。プログラマはプラットフォームごとの違いを意識せずに設定を永続化できます。 – QSettings Class | Qt Core
  3. macOSでは、他のアプリケーションを制御したりキーボード・マウスイベントを送信したりするアプリケーションは、セキュリティ保護のため「システム設定 > プライバシーとセキュリティ > アクセシビリティ」で明示的に許可する必要があります。 – アクセシビリティアプリにMacへのアクセスを許可する
  4. macOSはアプリケーションの署名やバイナリのハッシュ値で識別するため、再ビルドすると別のアプリケーションとして扱われます。そのため、開発中は再ビルドごとにアクセシビリティ権限を再設定する必要があります。 – macOSでアクセシビリティアクセス権限を利用するアプリケーション開発の知見
  5. macOSのGatekeeperは、App Store以外からダウンロードされたアプリや未署名のアプリを実行時にブロックします。開発中のアプリは xattr -cr コマンドで隔離属性を削除するか、codesign でアドホック署名を行うことで回避できます。 – Mac – すべてのアプリケーションの実行を許可する方法