# プラグイン貢献ガイド Voyager のプラグインシステムは、宣言的なプラグインを優先します。`plugin.json` でプラグイン情報と DOM 操作を記述し、CSS で見た目を調整します。プラグイン自体はリモート JavaScript を実行せず、Voyager 内蔵のプラグインエンジンがマニフェストとスタイルを解釈します。 これにより、プラグインのレビューと保守がしやすくなります。プラグインを貢献したい場合は、まずこの方式から始めてください。 ## 自分だけで使う場合: ローカルプラグイン 自分のブラウザだけで使うプラグインであれば、この手順は必要ありません。ローカルプラグインとして作成し、ポップアップからインポートしてください。Issue も PR もリポジトリのチェックアウトも不要です。[独自のプラグインを作る](./write-a-plugin.md) では、3 つの作成方法を説明しています。`create-voyager-plugin` のチェッカーと、コーディングエージェント向けのスキルも含まれます。 ## 推奨フロー 1. そのアイデアがプラグイン向きか確認します。読み取り幅、レイアウト修正、テーマ調整、ページ要素の非表示やマーク、簡単なサイト適配はよい候補です。 2. Voyager 本体リポジトリで先に Issue を作成し、解決する問題、対象サイト、既存プラグインとの差分を説明します。メンテナーの明示的な承認を待ってから実装と PR 作成を始めてください。 3. `plugin.json` にメタデータ、対象サイト、設定、貢献内容を書きます。 4. スタイルは同じディレクトリの `style.css` に置き、`contributes.styles` から参照します。 5. ローカルでテストし、PR にテストページ、スクリーンショット、または短い録画を添えてください。公式 catalog に入れるかどうかは成熟度を見て判断します。 ## ディレクトリ構成 公式のバンドルプラグインは `src/features/plugins/catalog/` の下にあり、プラグイン対応サイトごとに 1 ディレクトリです。 ``` src/features/plugins/catalog/ marketplace.json ドキュメントのプラグインストアが読む索引 sites//site.json データとしてのサイトアダプタ sites//plugins//plugin.json 宣言的プラグイン sites//plugins//style.css そのスタイル sites//plugins//README.md 何をどう直すかの説明 ``` 検出は自動です。`catalog/sites/index.ts` が `import.meta.glob` で各 `site.json` と各 `plugin.json` を見つけるため、サイトやプラグインの追加はファイルの追加だけで済み、書き換えるべき対応表はありません。 `marketplace.json` はその対応表ではなく、ドキュメントのプラグインストアが読む索引です。テストが検出結果との一致を保証しているので、新しいプラグインはここにも 1 件追加します。`source` は catalog からの相対パスで、たとえば `sites/deepseek/plugins/reading-width/plugin.json` です。ただし `sites/gemini/` と `sites/aistudio/` の下のプラグインは例外で、Voyager に同梱されるため `marketplace.json` には載せません。 プラグインの `matches` は、それが置かれているサイトの `matches` の内側に収まっていなければなりません。はみ出したプラグインはビルドを失敗させます。 ## 意味的セレクタキー `site.json` は固定語彙の意味的キーをサイト固有の CSS セレクタに対応づけます。語彙は `src/features/plugins/sites/semanticKeys.ts` で定義されており、`site.json` はこのリストのキーしか使えません。未知のキーは拒否されます。 - `userTurn`: ユーザーメッセージのコンテナ。 - `assistantTurn`: アシスタントメッセージのコンテナ。 - `thinkingBlock`: アシスタント応答内の思考部分。 - `codeBlock`: 描画済みのコードブロック。 - `composer`: ユーザーが入力するプロンプト欄。 - `sidebar`: 会話一覧またはナビゲーションレール。 - `sidePanel`: artifacts や canvas などの副次パネル。 - `headerActions`: 会話画面右上の操作エリア。 - `scrollContainer`: 会話をスクロールさせる要素。 - `composerRoot`: プロンプト欄とその操作ボタンをまとめたコンテナ。 - `sendButton`: プロンプトを送信するボタン。入力があるときだけ表示されるサイトもあります。 サイトは実際に提供できるキーだけを宣言します。プラグインはセレクタを直接書かず、DOM 操作の `target` に `{ "kind": "semantic", "key": "userTurn" }` と書いてキーを参照するので、サイトの改修は `site.json` 1 ファイルの修正で済みます。 `conversationIdPattern` はセレクタではなく `site.json` の別フィールドです。URL パスに対する正規表現で、最初のキャプチャグループが会話 ID になります。たとえば `^/chat/([^/?#]+)` です。 ## 新しいプラットフォームを追加する 新しいチャットサイトを追加するには、2 つの別々の層があります。 1. **サイトアダプタ** `catalog/sites//site.json`。Voyager に、そのウェブサイトの認識方法と、ユーザーのターン、アシスタントのターン、入力欄、サイドバー、テーマのマーカーを見つける方法を教えます。 2. `catalog/sites//plugins//` の下にある **プラグイン**。そのウェブサイト上で、読み取り幅やレンダリングの修正など、ユーザーが直面する 1 つの問題を解決します。 `site.json` には、`id`(ディレクトリ名と同じ)、`label`、`matches`、`selectors`、`theme`、`brandColor`、`capabilities` と、任意の `conversationIdPattern` および `turnKeyAttributes` が含まれます。`turnKeyAttributes` は、ページが再描画されたときにユーザーのメッセージの識別子を保つ属性の名前を指定します。送信の追跡(フォルダの Activity やタイムラインのタイムスタンプで使われます)は、この属性と `composer`、`userTurn`、`conversationIdPattern` を設定したサイトでのみ動作します。`src/features/plugins/sites/siteAdapterData.ts` の `validateSiteAdapterData` がこれを検証し、`bun run catalog:build` は公開の前にすべてのサイトとプラグインを検証します。 `site.json` はサイトアダプタそのものをデータとして書いたものです。現在のプラグイン対応サイトは ChatGPT、Claude、DeepSeek です。Gemini と AI Studio は Voyager のネイティブ画面で、こちらも専用の `site.json`(`catalog/sites/gemini/`、`catalog/sites/aistudio/`)を持ちますが、拡張機能に同梱され、リモートカタログには公開されません。`sites/adapters/gemini.ts`、`aistudio.ts`、`claude.ts`、`chatgpt.ts`、`deepseek.ts` はいずれも `site.json` を読むだけの 1 行のシェルです。TypeScript ではなく JSON を編集してください。 公開されるホスト別カタログはサイトデータも運ぶため、セレクタ修正は拡張機能をリリースしなくてもユーザーに届きます。ただし、まったく新しいサイトには、やはり拡張機能の中に含める必要があります。ホスト権限とコンテンツスクリプトの登録が、パッケージに組み込まれているためです。 DeepSeek のような新しいプラットフォームを追加する場合は、アダプタの作業をプラットフォームとの契約に絞ってください。`site.json`、必要なセレクタのテスト、そして対応内容を説明するための最低限のドキュメントを、1 つの完全な変更にまとめます。サイトのレジストリは、ファイル自体から新しいアダプタを読み込むので、登録リストを編集する必要はありません。プラグインごとに新しいサイトを追加しないでください。 アダプタが承認されたあとは、後続のプラグインを、独立した焦点の絞られた変更として提出できます。複数の変更が互いに依存している場合は、スタックされた PR の連鎖が役立ちます。後続のブランチは直前のブランチを基にし、各 PR には、そのPR独自の増分の機能だけが表示されます。それでも連鎖全体で同じスコープのルールに従ってください。PR ごとに、明確なユーザー目標を 1 つだけ持ち、必要なテストと後片付けを含めます。 サイトの対応に、モデル API との連携は必要ありません。新しいプラットフォームのために、静的なコンテンツスクリプトや必須のホスト権限を追加しないでください。プラグインだけのサイトには、既存のオプション権限と動的な登録の仕組みを使ってください。 オプション権限と動的な登録の仕組みは、ブラウザのバージョンに依存します。Chrome と Edge は、この拡張機能が使っている仕組みに対応しています。Firefox はオプションのホスト権限に バージョン 128 以降を必要とし、Safari は動的なコンテンツスクリプトの登録にバージョン 16.4 以降を必要とします。対応しているより古いブラウザでは、必要な API が永続的な許可を提供できないため、カスタムサイトでプラグインを有効にできなくても、それは正しい動作です。メンテナーの承認なしに、広い静的なホスト権限を追加して回避しないでください。 ## プラグインの粒度 プラグインは「ユーザーが解決したい問題」で区切るべきで、プラットフォームごとに機械的に分割する必要はありません。 カタログのプラグインは 1 つのサイトフォルダに置かれ、すべての `matches` はそのフォルダの `site.json` の範囲内でなければなりません(超えると `bun run plugin:check` が失敗します)。そのため 1 つのプラグインで Claude と ChatGPT の両方をカバーすることはできません。読み取り幅、ページ送り、コードブロックのレイアウトのように複数のプラットフォームに合う機能は、サイトフォルダごとに 1 つずつプラグインを作り(例:`sites/claude/plugins/reading-width` と `sites/chatgpt/plugins/reading-width`)、名前・設定・説明をそろえてください。 一方、プラットフォームごとに設定、DOM ロジック、ユーザー向け文言が大きく違う場合は、分けたほうが明確です。「全部入り」にするために無関係な機能を詰め込まないでください。1 つのプラグインは 1 つの明確な問題を解くのが理想です。 簡単な判断基準: - 同じユーザー目標、同じ設定、違うのはセレクタだけ:サイトフォルダごとに 1 つずつ作り、名前と設定をそろえる。 - 同じテーマだが各プラットフォームの体験が大きく違う:分割してもよいが、名前と説明に関連性を持たせる。 - 機能目標が違う:統合しない。 ## 重複プラグインを避ける 提出前にプラグインマーケットと既存の公式プラグインを確認してください。すでによいプラグインがある場合は、似たものを新しく作るより、そのプラグインを改善する PR を優先してください。 重複プラグインが受け入れられるのは、次のような明確な改善がある場合です。 - 既存プラグインが対応していない重要なプラットフォームをサポートする。 - 既存プラグインで長く解決できていない互換性問題を修正する。 - パフォーマンス、アクセシビリティ、保守性が明確に向上している。 - 名前や細かなスタイル変更だけではなく、十分に異なる有用な体験を提供する。 これによりマーケットが整理され、ユーザーも選びやすくなります。 ## 最小例 空のディレクトリから始める必要はありません。`bun run plugin:new --site ` を実行すると `catalog/sites//plugins//` が作られ、`plugin.json`、`style.css`、`README.md` が入り、`marketplace.json` にも対応するエントリが追記されます。生成されるのは出発点であって承認ではありません。埋めていくマニフェストはこの形です。 ```json { "$schema": "https://voyager.nagi.fun/plugin.schema.json", "id": "your-name.example-plugin", "name": "Example Plugin", "version": "1.0.0", "description": "A short description of what this plugin improves.", "author": "your-name", "category": "readability", "license": "MIT", "engine": ">=1.0.0", "tier": "declarative", "matches": ["https://claude.ai/*"], "contributes": { "styles": [{ "file": "style.css" }], "domOps": [ { "op": "addClass", "target": "body", "className": "gv-plugin-example" } ] } } ``` `$schema` の行は任意で、エディタのためだけのものです。マニフェストの JSON Schema を指すので、書きながら検証と補完が効きます。`site.json` 用は `https://voyager.nagi.fun/site.schema.json` にあります。 `style.css` は通常の CSS として書けますが、プラグインのスタイルは自分の `gv-plugin-*` クラス配下に閉じることを推奨します。 ```css .gv-plugin-example .some-target { max-width: 880px; } ``` ## Manifest の注意点 - `id` は `your-name.reading-width` のように、作者プレフィックスまたは逆ドメイン風の名前にして衝突を避けます。 - `matches` はできるだけ狭くし、本当に必要なサイトだけに一致させます。 - `matches` に複数のホストを書けるのは、そのサイトの `site.json` がすべてをカバーしている場合だけです(例:chatgpt.com と chat.openai.com)。別のサイトに対応するには、そのサイトのフォルダにプラグインを追加してください。 - `category` は `render-fix`、`theme`、`layout`、`readability`、`productivity`、`integration`、`other` を推奨します。 - 必要なプラグインエンジンのバージョンを `engine` に明記します。公式プラグインを参考にできます。 - `i18n` には中国語、英語、その他よく使われる言語の名前、説明、設定文言をできるだけ追加してください。 ## CSS とリソース制限 宣言的プラグインは信頼されていない入力として検証されるため、リソースは自己完結にしてください。 - `@import` を使わない。 - リモート画像、外部フォント、リモート CSS を参照しない。 - 通常の CSS、カスタムプロパティ、Voyager が提供する設定値の置換は使用できます。 - クラス名には `gv-plugin-` プレフィックスを使い、ホストサイトや Voyager 本体のスタイルを汚染しないようにします。 設定が必要な場合は、まず数値設定から始めるのがおすすめです。たとえば読み取り幅プラグインは、設定値を CSS 変数に書き込み、その変数を CSS 側で使えます。 ## DOM 操作の範囲 現在の宣言的プラグインは次の操作をサポートします。 - `addClass`:対象要素にクラスを追加する。 - `setAttribute`:属性を設定する。 - `setStyle`:インラインスタイルまたは CSS 変数を設定する。値の末尾に `!important` を付けると、その優先度で適用されます。古い Voyager はこの値を捨てるため、プラグインがこれに依存する場合は `engine` を `>=1.9.0` にしてください。`margin` などのショートハンドはロングハンドごとに追跡されるので、あるプラグインをオフにしても別のプラグインの `margin-top` が消えることはありません。 - `hide`:対象要素を非表示にする。 対象は CSS セレクタ、または上のリストにある意味的キーを `{ "kind": "semantic", "key": "userTurn" }` と書いて指定できます。意味的キーはより安定しますが、対象サイトのアダプタがそのキーを宣言している必要があります。 宣言的操作は、取り消し可能で、何度実行しても安全である必要があります。一度きりのページ状態に依存せず、DOM が永遠に変わらないとも仮定しないでください。 ### プリミティブ CSS と取り消し可能な DOM 変更だけでは表現できない挙動もあります。プリミティブは Voyager に同梱されるファーストパーティのコードで、manifest は `native` 操作から名前で呼び出せます。 ```json { "engine": ">=1.3.0", "requires": { "handlers": ["formulaCopy"] }, "contributes": { "domOps": [{ "op": "native", "handler": "formulaCopy", "params": {} }] } } ``` manifest はプリミティブを選んで設定するだけで、ロジックは持ちません。`params` は実行前にそのプリミティブ自身が検証します。 プリミティブを使うプラグインには 2 つのルールがあります。 - プラグインがマッチするすべてのサイトが、そのプリミティブが読むセマンティックキーを宣言していること(たとえば `mermaid` は `assistantTurn` と `codeBlock` を読みます)。`requires.handlers` は省略できます。`native` 操作がすでにそのプリミティブを示しているためです。 - `engine` は、そのプリミティブが最初に入ったエンジンバージョン以上にすること。`formulaCopy` はエンジン 1.3.0 で入ったので、使うプラグインは `">=1.3.0"` と書きます。これより低いと `bun run catalog:build` が失敗します。古い Voyager では、更新を促す代わりに handler が見つからないと報告されてしまうためです。 プリミティブは増えるだけです。新しいパラメータは必ず任意で、互換性を壊す変更は新しい名前で追加されます。 挙動が変わったプラグインは 1 行の `changelog` を付けることもできます。popup がバージョンの隣に表示します。翻訳は `name` や `description` と並べて `i18n..changelog` に書きます。 ## 通常プラグインに向かない場合 JavaScript の実行、リクエストの遮断、Voyager 内部データの読み書き、複雑な実行時ロジックが必要な機能は、通常の宣言的プラグインには向きません。 その場合はまず Issue を開いて要件を説明してください。本当に内蔵能力が必要な場合は、Formula Copy のような builtin/native プラグインとして Voyager 本体に実装することを検討します。 ## PR 前チェック `bun run plugin:check <プラグインディレクトリ>` を実行し、その出力を PR に貼ってください。マニフェストと CSS、`matches` がサイトの範囲を超えていないか、プリミティブ、意味的セレクタキー、10 言語の翻訳、README まで、問題を一度にすべて報告します。CI も同じチェックをすべての内蔵プラグインに対して実行します。 - プラグインはデフォルトで無効で、ユーザーが自分で有効化する。 - ほぼ同じ既存プラグインがないか確認した。ある場合は既存プラグインの改善を優先した。 - 対象サイトのライトテーマとダークテーマでテストした。 - `matches` が無関係なサイトを含んでいない。 - リモートリソースを参照していない。 - プラグインディレクトリに `plugin.json`、必要な CSS ファイル、短い README がある。 - 公式プラグインなら、ディレクトリが `catalog/sites//plugins//` にあり、`matches` がサイトの `matches` の内側で、`catalog/marketplace.json` に登録されている(Gemini と AI Studio のプラグインは Voyager に同梱されるため除く)。 - PR でテストページ、スクリーンショットまたは録画、影響を受けるページ領域を説明している。 シンプルで、控えめで、取り消し可能にしてください。1 つの明確な問題を解くプラグインほど、マージと保守がしやすくなります。