img-share スキル
ブラウザのスクリーンショットやローカル画像を、チャット内でクリックで拡大表示できる形式(サムネイル+元画像)に変換し、スマートフォンや他のデバイスからもアクセス可能にします。クロスプラットフォーム(Linux/macOS/WSL/Windowsネイティブ)で利用できます。
使用タイミング(いつトリガーされるか)
- ユーザーが「特定のWebサイトやページのスクリーンショットを撮って共有してほしい」と依頼した場合。
- ユーザーが画像をチャットに貼り付け、それを「スマホでも見られて、クリックで拡大できるようにしたい」と希望した場合。
- ワークスペース内の画像を、チャット内でクリックで拡大表示可能な形で埋め込みたい場合。
依存関係
| 依存 | 用途 | 入手方法 |
|---|---|---|
| web-access スキル | スクリーンショット(CDP駆動ブラウザ) | インストール必須:https://github.com/eze-is/web-access |
| python3/python | オーケストレーター(serve.py) | システム標準 |
| cloudflared | パブリックトンネルのフォールバック | Mac: brew install cloudflared;Win: winget install Cloudflare.cloudflared;Linux: 公式README参照。環境変数 CLOUDFLARED でパス指定可能 |
| ffmpeg(オプション) | 複数画像をグリッドに結合 | システムにインストール(scripts/montage.sh で使用) |
実行手順
-
ホスト到達可能性の判定(必須、最初に実施) —— 画像URLのベースを決定し、パブリックトンネルを別途開く必要があるかどうかを判断します。画像アドレスはホストアドレスに従わなければなりません。動作を優先して、ホストとは異なるオリジンのトンネルを勝手に作成してはいけません。
判定 結論(アクション) ホストがすでにトンネル化/パブリックアクセス可能(ユーザーのスマホがこのアドレスでセッションにアクセスしている)であり、かつホストが静的画像パス(例: /img/*)を公開している画像をホストの静的ディレクトリに配置し、ホストのパブリックアドレスを埋め込む。新しいトンネルは開かない ホストはパブリックアクセス可能だが、静的画像パスを公開していない 静かにフォールバックせず、まずユーザーに「ホストに静的画像マッピングを設定する必要がある」と伝える。ユーザーの同意を得た場合のみ「明示的な独立トンネルフォールバック」を実行する ホストがlocalhostのみ(例: DSH_WEB_URL=http://127.0.0.1:*、パブリック未公開)画像はローカル/ローカルネットワークアドレスのみ。スマホはそもそもこのセッションにアクセスできない。まずユーザーにDSHをパブリックにするよう促すか、ユーザーの同意を得て明示的なフォールバックを実行する。localhost/127.0.0.1アドレスを埋め込んで「スマホで見られます」と主張することを禁止する 検出できない/不明 推測せず、まずユーザーに質問する:「スマホでこのセッションにアクセスする際に使っているアドレスは(パブリックドメインかlocalhostか)?」その後、上表に従って実行する 検出方法:環境変数(
DSH_WEB_URL、DSH_PUBLIC_BASE、DSH_IMG_BASEなど)の読み取り、ホストの静的ルーティング設定の確認。取得できない場合は「ホストが提供していない」とみなし、上表の「ユーザーに質問/通知」フローに従う。静かに別のトンネルを開いてフォールバックしてはいけない。ホスト契約(将来の取り決め、現在のDSHホストは未提供):ホストが
DSH_PUBLIC_BASE/DSH_IMG_BASEまたは同等の静的パス宣言を公開している場合、エージェントは画像を優先的にホストのドメイン下に配置しなければならない。提供されていない場合、存在すると仮定してはいけない。 -
スクリーンショット(必要な場合):web-access スキルを使用し、
/screenshotでページをPNGにレンダリングする。- CDP/Chromiumが制限された環境で起動できない場合(ネットワーク制限/プロセスクラッシュ)、クラウドスクリーンショットにフォールバック:thum.io / mshots / microlink などを使用し、
?wait=N&width=Wパラメータを付ける。返却後、「非白ピクセル比率が閾値以上」「色クラスタ数がN以上」をチェックし、空白でなければ使用可能。
- CDP/Chromiumが制限された環境で起動できない場合(ネットワーク制限/プロセスクラッシュ)、クラウドスクリーンショットにフォールバック:thum.io / mshots / microlink などを使用し、
-
画像保存:ワークスペースに保存(例:
img/x.png、ファイル名はASCII/スペースなし)。 -
共有(手順0の結論に基づき、次の2択):
- ホストパス(手順0でホストが再利用可能と判定された場合):画像をホストが公開している静的ディレクトリに配置し、
https://<ホストドメイン>/img/x.pngを埋め込む。独立したトンネルは開かない。 - 明示的フォールバック(手順0でフォールバックが必要かつユーザーが同意した場合):このファイルのみ共有し、ディレクトリ全体は公開しない。画像サーバー[ループバックのみ]+パブリックトンネルを起動し、エンドツーエンドで検証、PUBLIC_BASEを出力:
bash <スキル>/scripts/setup.sh <img>/x.png # Unix/WSL python <スキル>/scripts/serve.py --share <img>/x.png # Windowsネイティブ
- ホストパス(手順0でホストが再利用可能と判定された場合):画像をホストが公開している静的ディレクトリに配置し、
-
レスポンスに埋め込む(サムネイル=元画像URL、クリックで元画像を全画面表示):
[](https://<ベース>/x.png)ベースは手順0で判定された結果(ホストのパブリックアドレス、またはユーザーの同意を得た明示的トンネルアドレス)でなければならない。localhost/127.0.0.1を埋め込んで「スマホで見られます」と主張することを禁止する。ユーザーに告知:チャット画面にサムネイルが表示され、クリックで元画像が表示される。スマホでも同じパブリックアドレスでアクセス可能(DSHがパブリックMarkdown画像をレンダリングできることを確認済み)。
-
共有完了→トンネル破棄(明示的フォールバックシナリオ、長期公開を避ける):
bash <スキル>/scripts/teardown.sh # 画像サーバー+トンネル停止+クリーンアップ
エラー処理
- ホスト到達可能性が判定できない:推測せず、静かに別のトンネルを開かない。まずユーザーにスマホでセッションにアクセスする際のアドレスを質問する(手順0参照)。
- ホストはパブリックだが静的画像パスがない:ユーザーに静的マッピングの設定を促すか、同意を得て明示的フォールバックを実行し、そのトンネルがホストとは異なるオリジンであることを説明する。
- 依存関係不足:serve.pyが「cloudflared/pythonが不足」と報告した場合、指示に従ってインストールするか
CLOUDFLAREDを設定する。スクリーンショットにweb-accessがない場合、クラウドスクリーンショットにフォールバックするか(上記参照)、ユーザーに画像をアップロードしてもらう。 - ポートが占有されている:serve.pyが自動的に空きポートを選択する。
- トンネルドメインが取得できない(ネットワーク遅延/Cloudflare切断):serve.pyがポーリングでリトライする。それでも失敗した場合、「ネットワークでトンネルを構築できません」と伝え、ローカル/ローカルネットワークのみにフォールバックする(ローカルアドレスを提供し、スマホシナリオでは先にホスト/サービスをパブリックアクセス可能にする必要があることを説明する)。
- エンドツーエンド検証失敗:serve.pyが実際のファイル名をcurlで検証し(200が成功)、失敗した場合は即座に破棄する。「動作しているように見えて実際は404」を避ける。
- ドメインの変動:一時的なトンネルは再起動でドメインが変わるため、埋め込まれたURLは無効になる。ホストパスにはこの問題はない。フォールバックシナリオで長期的な安定性が必要な場合は、名前付きトンネル/固定ドメインを使用する。
環境自己チェック(エージェントが先に実施)
開始前に以下を確認:アウトバウンド接続可能か(パブリックアドレスにcurl)、ブラウザプロセスをspawnできるか(CDP)、ホストがパブリックアクセス可能か(手順0参照)。アウトバウンド/CDPの両方が不可の場合 → クラウドスクリーンショット+より高い権限が必要であることを通知し、ユーザーに「動作しているように見えるが実際には壊れている」画像を提供しない。
セキュリティ(デフォルト動作、注意喚起ではない)
- 指定ファイルのみを共有:serve.pyは共有するファイルをランダムな名前のサブディレクトリにコピーしてからサービスを起動する。img/ディレクトリ全体は絶対に公開しない(そうしないと、トンネルURLからそのディレクトリ内の全ファイルを一覧表示/ダウンロードできてしまう)。ホストの静的ディレクトリでも同様に、共有するファイルのみを配置する。
- 画像サーバーは127.0.0.1のみにバインド(ローカルネットワークから直接接続不可)。パブリックトンネルURLには認証がないため、共有が完了したら即座にteardownする。
- より厳格にする場合:URLにランダムトークンを追加するか、短時間のみ有効なトンネルを使用する。機密性の高い画像は共有しないこと。
- すべての画像をパブリックにしない:「今回共有する1枚/数枚」のみが公開アクセス可能なディレクトリ/トンネルに存在するようにする。
ホストトンネル優先(判定は実行手順の手順0を参照)
- 画像アドレスはホストアドレスに従う:ホストがすでにトンネル化/パブリック化されている場合、画像はホストと同じドメイン(例:静的
/img/*パス)に配置する。ユーザーのスマホでセッションを開けば即座に読み込まれ、ホストとは異なるオリジンの一時トンネルを別途開く必要はなく、また開くべきではない。 - ホストがdsh-trust-proxyなどを介して
/img/静的ルールを公開している場合、画像URLはhttps://<DSHドメイン>/img/x.pngの形式になり、デバイス間で共有でき、複数のトンネルを開く必要がない。 - serve.pyの独立したcloudflaredトンネルは、あくまで移植可能なフォールバック(どのマシンでも実行可能)である。手順0で「ホストがパブリックに再利用できない」と判定され、かつユーザーの同意を得た場合にのみ使用し、その制限(ホストと異なるオリジン、一時トンネルは再起動でドメイン変更)を明確に説明する。
境界
- プロセスは常駐/停止可能であること:serve.pyはPIDを記録し、teardownで使用する。DSHセッション内のサービスはセッションをまたいで動作する必要がある場合がある(管理されたバックグラウンド+teardownによる明示的なクリーンアップを使用)。
- ファイル名:ASCII、スペースなしを使用。スクリプトはファイルの存在をチェックする。日本語やスペースはURLエンコードで処理されるが、避けることを推奨。
- ワークスペースの違い:セッションごとにワークスペースが異なるため、そのセッションのパスに従って共有する。serve.pyはシステムの一時ディレクトリを使用し、ワークスペースに依存しない。