# dsh-key-panel npm では `@moruteaven/dsh-key-panel` · [English](./README.md) · [简体中文](./README.zh-CN.md) · **日本語** [DSH Desktop](https://deepseek.com) 用のシークレット管理プラグインです。API キーを一か所にまとめ、アシスタントのシェルに `$DSH_*` 環境変数として注入し、 設定ページのパネルから「アシスタントに何を許すか」をあなたが決められます。 **解決する問題**:アシスタントが Cloudflare や OpenAI、あるいはあなたの データベースを叩くには資格情報が要ります。チャットに貼れば会話ログに永久に残り、 ファイルで渡せばどこかのディレクトリに紛れます。このプラグインはそれを 1 つの ファイルに収め、環境変数として公開し、**値そのものは会話に入りません**。 ``` アシスタントが Worker をデプロイする → 実行:wrangler deploy --api-token $DSH_CLOUDFLARE_TOKEN → シェルにはある。モデルのコンテキストには無い ``` --- ## 機能 - **設定ページのパネル**。追加・編集・表示・コピー・削除を、他の設定と同じ場所で。 設定ファイルを編集する必要はありません。 - **`$DSH_*` 注入**。各キーはアシスタントのシェル変数になります。変更は次の コマンドから有効——再起動は不要です。 - **3 つのアクセスモード**(あなたが選択、永続化): `読み取り専用`(既定)· `書き込み可` · `編集可`。 - **プラットフォームとアカウント**。多くのプロバイダは 2 つの値(アカウント ID と トークン)を要求し、しかも 1 つのプロバイダに複数アカウントがあるのが普通です。 プラットフォームとアカウントでまとめると、各スロットは `DSH___ID` と `DSH___KEY` になります。まとめは**保存の整理方法**にすぎず、 変数名は平坦なままで、それを読む側も一切変わりません。 - **最近のアクティビティ**。各 shell コマンドが実際に受け取った変数と、アシスタントが任意で申告した目的を記録します。両者は並べて表示され、対応付けはされません。名前とタイムスタンプのみ——**値は決して書き込まれません**。 - **作成元の記録**。あなたが作ったのかアシスタントが作ったのかを保持し、 書き込みで互いを「すり替える」ことはできません。 - **名前スコープ(今回は未公開)**。ホスト側はアシスタントを `DSH_AGENT_*` のような名前に限定できますし、ストアに設定済みのスコープは今も有効です。ただし本リリースではパネル上の入力欄は非表示です。 - **2 段階削除**。削除にはトークン付きの 2 回目の呼び出しが必要です。 - **値はモデルに渡りません**。どのモードでも、値を返すツールは存在しません。 - **冪等・ホットリロード対応**。ポリシーは毎回読み直されます。 ## インストール ``` 設定 → プラグイン → "dsh-key-panel" を検索 → インストール → DSH Desktop を再起動 ``` その後、パネルは **設定 → キー** に表示されます。 その他のチャネル: - **npm** —— `npm i @moruteaven/dsh-key-panel` を実行し、プロファイルの `package.json` にある `dsh.profile.bundles` に裸のパッケージ名 `@moruteaven/dsh-key-panel` を追加します。このリストは**裸のパッケージ名のみ**を 受け付け、`file:` やパス指定は拒否されます(`file:` は `dependencies` では有効です)。 - **ソースから** —— [CONTRIBUTING.md](./CONTRIBUTING.md) を参照。 ## 使い方 ### キーの追加 設定 → キー → **キーを追加**。 | 項目 | 説明 | | --- | --- | | 名前 | `DSH_[A-Z][A-Z0-9_]*` に一致が必要(例:`DSH_CLOUDFLARE_TOKEN`) | | 用途 | 任意。変数の説明としてアシスタントに表示されます | | 値 | シークレット本体。平文保存、以後はマスク表示のみ | あとはアシスタントに伝えるだけです: > worker をデプロイして。トークンは `$DSH_CLOUDFLARE_TOKEN` にある。 ### プラットフォームとアカウント プロバイダが 2 つの値(アカウント ID とトークンなど)を要求し、しかもその アカウントが複数あると、平坦なリストは読みにくくなります。**プラットフォーム追加** と **アカウント追加** でこれらをまとめると、変数名は自動で組み立てられます: | 入力 | 得られる名前 | | --- | --- | | プラットフォーム `CF`、アカウント `WORK` | `DSH_CF_WORK_ID` と `DSH_CF_WORK_KEY` | 確定前にパネルが両方の名前を表示するので、シェルに入るものを事前に確認できます。 識別子に使えるのは大文字・数字・アンダースコアのみです。小文字は**拒否**され、 黙って大文字に変換されることはありません——頼んでいない名前は、打ち直しより 悪いからです。 **値はアカウントの行から直接入力します。** アカウントを作った時点で、2 つの変数名は すでに決まっています——名前は識別子から**導出**されるもので、あなたが選ぶものでは ありません。そこでアカウント行に「**キーを入力**」ボタンがあり、開くポップアップで 2 つの値をまとめて受け取り、そのアカウントに紐づけて保存します。導出された名前は ポップアップに表示されますが、**入力する必要はありません**——それが要点です。 プラグインが計算した名前を、フィールドごとに上部の「キー追加」カードまで運んで 打ち直すのは、入力ではなく転記でした。 各スロットは値を持っているかどうかを表示し、空のままにしておくこともできます。 **キー追加**は未分類のキー用にそのまま残ります——それらには入力元のアカウントが ありません。 プラットフォームとアカウントには**表示名**も付けられます。パネルに出るのはこちらです。 識別子とは意図的に分けています:識別子は変数名に組み込まれ後から変更できませんが、 表示名はいつでも変更できます。パネルには「Cloudflare」と表示しつつ変数名は `DSH_CF_*` のままにしたい、というのはまさにこの 2 つのフィールドの用途です。 そのアカウントが生成する名前がすでに使われている場合——たとえばアカウントを作る前に 手動で `DSH_CF_WORK_KEY` を追加していた場合——パネルは対象のキー名を示して確認を 求めます。そのまま保存すると既存の値が置き換わるため、黙って行われることはありません。 知っておくとよい点が 2 つあります: - **まとめは整理の手段であって、セキュリティ境界ではありません。** 変数はあくまで 平坦な `DSH_*` 名です——シェルに階層がないので、これが要点です。まとめが もたらすのは読みやすいパネルと、副次的に 1 アカウントに揃うスコープです(下記)。 - **プラットフォームやアカウントを削除してもシークレットは消えません。** キーが 残っている間は削除が拒否され、先に片付ける必要があります。強行した場合、キーは **未分類に戻る**だけで、値には一切触れません。 一度もまとめていないキーはそのままです。この機能を使うために何かを整理し直す必要は なく、既存の保存内容もそのまま動きます。 ### 最近のアクティビティ パネルは 2 種類の記録を残し、並べて表示します: | 記録 | 書き手 | 内容 | | --- | --- | --- | | **申告** | アシスタント(`key_panel_intent` の呼び出し) | 目的と、使う予定の名前 | | **コマンドに渡した** | プラグイン自身(shell コマンドの解決時) | そのコマンドが実際に受け取った名前 | 両者は**並べて表示されるだけで、対応付けはされません**。近い位置にある申告と使用が同じ作業のものかもしれませんが、データはそう主張していません。結びつければ、推測を事実として提示することになります。 **「コマンドに渡した」が意味しないこと。** ホストは shell コマンドの実行前に `$DSH_*` 環境全体を解決し、コマンドがそれをどう使ったかは見えません。したがって 1 行が示すのは「その瞬間にスコープにあった」ことだけです。コマンドが読んだことや、何に使ったことは示しません。どの認証情報に依存しているかの目安として扱い、監査証跡とは考えないでください。 実務上の帰結が 2 つあります: - **アシスタントが何も申告しないことがあります。** `key_panel_intent` は任意で、省略にコストはかかりません。意図を尋ねるのは安い場面だけで、必須にはしません——毎回必須の手順はやがて反射になり、情報を運ばなくなるからです。 - **記録はコマンドの経路に入りません。** メモリにバッファしてからまとめて書き出すため、記録がコマンドを遅くすることはありません。ファイルには上限があり(古いものから破棄)、書き込み失敗は無視されます。このファイルの末尾を失っても、失うのは傾向であって秘密ではありません。 記録は鍵ストアの隣、`usage.jsonl` に置かれ、**名前とタイムスタンプだけを保存します。値は決して書き込まれません。** ### アクセスモード | モード | できること | できないこと | | --- | --- | --- | | `読み取り専用`(既定) | キーの使用 | あらゆる変更——**モデル向けツールは一切登録されない** | | `書き込み可` | 追加、自分が作ったキーの上書き | 削除全般、あなたのキーの変更 | | `編集可` | 追加・変更・削除 | ——(削除には確認が要る) | パネルで切り替えれば、**次のツール呼び出しから即座に**反映されます。 **まず「読み取り専用」から。** アシスタント自身に資格情報を追加させたいときだけ 上げてください——後の 2 つはその用途のために存在します。 ### 名前で制限 > **本バージョンのパネルでは未公開です。** スコープ自体は生きています——検証・保存・モデル呼び出しごとの判定はすべて動作し、ストアに設定済みのスコープも引き続き適用されます。隠れているのは**それを設定する入力欄**だけです(`lib/client.js` の `SHOW_SCOPE_UI`)。必要な場合はストアに直接設定してください。フラグを立てれば元に戻ります。 モードを選ぶ前に知っておくべきセキュリティ上の帰結があります:**スコープを設定していない場合、アシスタントが触れられるキーを制限しているのはアクセスモードだけです。** 未設定のスコープは制限なしを意味します。より狭い境界が必要なら、自分で設定してください——現時点ではストアへの直接編集のみです。 スコープはアシスタントが触れる名前を絞り込みます: | 値 | 効果 | | --- | --- | | *(空欄)* | 制限なし | | `DSH_AGENT_*` | その接頭辞のみ | | `DSH_CF_*` | そのプラットフォームのキーのみ | | `DSH_CF_WORK_*` | 特定アカウントの資格情報のみ | ワイルドカードは `*` 一つだけ。**パスも正規表現も表現できません**。 ## 設定項目 | 設定 | 場所 | 既定値 | | --- | --- | --- | | アクセスモード | パネル | `readonly` | | 名前スコープ | ストアのみ(パネル非表示) | 制限なし | | 保存先 | `$DSH_HOME/key-panel/keys.json` | `~/.dsh/key-panel/keys.json` | | アクティビティログ | `$DSH_HOME/key-panel/usage.jsonl` | `~/.dsh/key-panel/usage.jsonl` | ## 保存形式 ```jsonc { "version": 2, "policy": { "accessMode": "readonly", "scopePattern": null }, "platforms": { "CF": { "label": "Cloudflare", "createdAt": 1758428400000 } }, "accounts": { "CF/WORK": { "platform": "CF", "identifier": "WORK", "label": "業務用", "createdAt": 1758428400000 } }, "keys": { "DSH_CF_WORK_TOKEN": { "value": "…", "description": "Cloudflare Workers デプロイトークン", "origin": "operator", // "operator" | "model" "platform": "CF", // 任意の分類フィールド "account": "WORK", "field": "key", // "id" | "key" "createdAt": 1758428400000, "updatedAt": 1758428400000 } } } ``` 書き込みは **一時ファイル → `fsync` → rename** の順です。`fsync` を省くと、 クラッシュ時に「リネーム済みだが空」のファイルが残り、読み戻すと 「全キーが削除された」状態に見えます。 version 1 のファイルはそのまま読み込めます。分類フィールドはすべて任意なので、 プラットフォーム概念より前に書かれたキーは「未分類」として読み戻されます。 **移行手順は不要です。** ### `usage.jsonl` アクティビティの記録は**別ファイル**に、1 行 1 オブジェクトで保存されます: ```jsonc {"t":1758428400000,"kind":"intent","names":["DSH_CF_WORK_TOKEN"],"note":"deploy staging"} {"t":1758428450000,"kind":"use","names":["DSH_CF_WORK_TOKEN"]} ``` | フィールド | 意味 | | --- | --- | | `t` | epoch ミリ秒 | | `kind` | `intent`(アシスタントの申告)または `use`(コマンドに渡した) | | `names` | 対象の名前(重複除去済み)。**値は決して入りません。** | | `note` | `intent` のみ——目的。500 文字で切り詰め | まとめて追記し、直近 1000 件を保持するため無限には増えません。`keys.json` と異なり、**アトミックではなく**、行ごとの fsync も**しません**。強制終了で最終行が切れることがありますが、読み取り側はそれを読み飛ばします。このトレードオフはここでは正しい——このファイルは秘密ではなくシグナルであり、耐久性のために毎コマンドを遅くする価値はありません。 2 種類の記録は別々の主体が別々の時点で書くため、別エントリとして保存し、表示時にタイムスタンプで並べます。プラグインが両者を結合することはありません。 ## セキュリティ **値は平文で保存されます。** これは意図的なトレードオフです。信頼モデル・不変条件・ 非目標を含む詳細は [SECURITY.md](./SECURITY.md) を参照してください。 要点: - あなたの OS ユーザーとして動くものは保存ファイルを読めます。`.env` と同様に扱ってください。 - どのモードでも、アシスタントは値を会話ログに取り込めません。 - アシスタントはモードとスコープを変更できません——操作者専用です。 - 「読み取り専用」では、書き込みツールは**拒否されるのではなく存在しません**。 ## 開発 ```bash npm test # 両スイート —— 427 アサーション npm run test:host # ホスト側:ポリシー、ストア、ツール、ゲートウェイ(298) npm run test:client # クライアントバンドル:契約、スロット、RPC、辞書(129) ``` クライアントテストは実際のフロントエンドと同じ方法で `lib/client.js` を読み込みます ——偽の `window.__ModuleLoader__`、シードモジュールのみ許可する `require`——そして バンドルの評価が**副作用を起こさない**こと、シードモジュールしか要求しないこと、 パネルが呼ぶ全 RPC エンドポイントがホスト側ゲートウェイと一致することを検証します。 **ビルド工程はありません。** ホスト側は純粋な ESM、ブラウザ側は CJS factory 文字列です。 ``` lib/ policy.js アクセスモード、スコープ glob、判定関数 store.js 永続化、作成元の記録、アトミック書き込み tools.js モデル向けツール、削除確認台帳 index.js ホスト側 —— Typert ゲートウェイ、shellEnv 登録 client.js ブラウザバンドル —— 設定パネル ``` このリポジトリが求める規約とテストの実行方法は [CONTRIBUTING.md](./CONTRIBUTING.md) にあります。 ## 互換性 - DSH Desktop 2.0.11+(dsh `0.1.5-rc.1`) - Node 20+ - クライアント側は web プラットフォームのみ ## ライセンス [Apache License 2.0](./LICENSE) · 帰属表示は [NOTICE](./NOTICE) 資格情報を扱うツールであるため、MIT/BSD ではなく Apache-2.0 を選びました。 意味のある条項が 2 つ増えるためです: - **特許許諾**(第 3 条)。MIT と BSD には存在せず、貢献者にも同じ許諾を義務づけます。 - **商標の制限**(第 6 条)。作者名をフォークの推奨に使えなくします。これは BSD-3-Clause の推奨条項と同じ保護です。 それ以外は寛容なままです:商用利用、改変、クローズドソースでの配布も可能です。