# dsh-api-balance [中文](README.md) | [English](README.en.md) | 日本語 | [偽中国語](README.pcn.md) API 使用量残高プラグイン(DeepSeek Harness)——webui の使用量リング(送信ボタン左のコンテキスト使用量表示)のポップオーバーパネルに「用量 / 残高」タブ切替を提供する。 ## 基本情報 | 項目 | 値 | |------|-----| | タイプ | DSH Host + Client プラグイン(npm パッケージ) | | npm 名 | `@kihara777/dsh-api-balance` | | バージョン | `0.1.0` | | ライセンス | MIT | | データソース | DeepSeek 公式 `GET /user/balance`(API キー認証)+ プラットフォームコンソール使用量 API(プラットフォームセッショントークン認証) | ## 機能 - **用量**:元の内容(コンテキスト占有率と内訳) - **残高**:現在の API キーのアカウント情報(キー末尾、残高可否、通貨別の総残高 / チャージ残高 / 付与残高)、消費明細と使用量チャート——消費明細は同一エリアの水平ページめくり(1 ページ目:当日 / 当月 / 30 日間、2 ページ目:モデル別内訳 + 日別 / 月別チャート)、上部にスマホホーム画面風のページインジケータードット(タップ可、横ドラッグ / スワイプでページ切替)、エリアの高さは現在ページの内容に応じて自動増減し(ページ切替で回収)自身ではスクロールしない(全内容はパネル自身の縦スクロールに依存) - ホスト側 30 秒 TTL キャッシュ;API キーは `credentials` サービス経由で `apiKeyEnv`(デフォルト `DEEPSEEK_API_KEY`)を解決し、プロセス環境変数へフォールバック ### プラットフォームトークン取得(二段構え、全自動優先) - **ローカルブラウザ自動スキャン(デフォルト有効)**:ホストがローカルの Chromium 系ブラウザ(Edge / Chrome / Brave / Chromium / Vivaldi / Opera、全プロファイル)の `Local Storage/leveldb` を読み、LevelDB テーブル構造を精確に解析(footer → index → データブロック → snappy 解凍 → エントリ走査)して `userToken` を取り出し(解析失敗時は生バイトのヒューリスティックへフォールバック)、最初の一致を `$DSH_HOME/api-balance-token`(0600)へ保存する。ローカルブラウザで一度プラットフォームにログインしていれば手動操作なしで取得できる。節流はデフォルト 6 時間に 1 回まで(`browserScanIntervalMs` で設定、`browserScan = false` で無効化)。トークン失効(40003/401)後は次回クエリで即再スキャンする。 - **未ログイン検出とログイン案内**:スキャンがヒットしない場合、パネルに「プラットフォーム未ログイン」プロンプトを自動表示——「ログインへ」で新タブにログインページを開き、ポーリングでトークンを自動取得する。手動入力はプロンプト内の二級オプションのみ(ログインしたくない場合の備え)。接続後はグレー表示の「✓ ログイン済み」ボタンとトークン取得元(本機ブラウザ自動取得 / 手動接続)を表示し、手動更新のたびにトークン未取得時は自動クイックスキャンでログイン状態を確認する——ボタン操作は不要。 ### 界面設定(⚙ 設定 → 界面) - **下部統計バー**:越界内容を横向きスクロール表示しスクロールバーを隠す(既定で有効)。無効にすると省略記号の切り詰め(ホバーで全文表示)に戻る。 - **Enter キー動作**:Enter = 改行、Shift+Enter = 送信(既定で有効;DSH 本来の動作は Enter = 送信)。無効にすると本来の動作に戻る。会話入力欄のみ作用し、他の入力欄には影響しない。 - **モバイルでセッション切替時にキーボードを出さない**:タッチデバイスでサイドバーからセッションを切り替えた際、入力欄の自動フォーカスを阻止し、ソフトキーボードの自動表示を防ぐ。入力欄のタップでは通常どおり入力できる。既定で有効、ここで無効化できる。 - **質問ダイアログのページ全体スクロール**:プロンプトが長い場合、タイトルをオプションと一緒にスクロールさせる(オプション一覧のみのスクロールではなく);操作ボタンとフッターボタンは追従表示を維持し、長いプロンプトがオプションを圧迫しなくなる。既定で有効。 4 設定とも既定で有効、ブラウザの localStorage に永続化される。 ### ピーク課金マーカー DeepSeek の現行ピーク/オフピーク課金ルール(公式価格ページ脚注)は:**ピーク = 月曜〜金曜 北京時間 09:00–12:00、14:00–18:00、それ以外(週末終日を含む)はオフピーク価格**。ピーク時間帯は:用量リング(送信キー左の円形ボタン)、用量ページのコンテキスト進捗バーと各明細カラーチップ、更新/ロードアニメーション、使用量チャートが一括で赤色系表示に——チャート内の各モデルは異なる赤色トーン(赤だが区分可能、凡例も同期)を維持し、チャートタイトル横に赤い「ピーク課金」バッジが表示される(ホバーで時間帯の説明)。赤表示は公式ピーク時間帯に合わせ自動で入り/解除され(30 秒ごとに境界を再検査)、手動更新は不要。ピーク開始と終了の両方で通知を自働再生(パックの `peak` / `peakEnd` セグメント優先、なければ TTS でフォールバック)。「残高」タブクリック(手動更新)の挨拶音声の後にもピーク提示が追加される(パックの `peak` セグメント優先、なければ TTS でフォールバック)。 ### 音声放送 使用量チャートの「日別 / 月別」切替ボタンをクリックすると、対応するビューの音声使用量を放送する(パックセグメント + TTS 数字の連結)。内容は:入力(キャッシュ未ヒット)、キャッシュヒット、出力、金額(通貨)——公式使用量ページの分項基準と一致。放送の言語と音色は DSH 界面言語(zh / en)に追従する。「⚙ 設定 → 音声」タブには: - 自動放送スイッチ(残高が閾値以下の際の通知、30 分レート制限) - TTS バックエンド選択(ブラウザ内蔵 / カスタム TTS API——ホスト経由プロキシで CORS 回避、URL テンプレートのプレースホルダ `{text}` `{lang}` `{rate}`) - 音声パックライブラリ管理(複数 zip のインポート、行クリックで使用パックを切替、複数選択で一括削除;各パックを展開して「音声試聴」——そのパックが対応する全音声を 1 つずつ試聴できる;`$DSH_HOME/api-balance-voicepack/` に保存し全デバイス共有) - 「音声パック管理」サブメニュー内の作成器(ブラウザ録音または音声ファイルのインポート、録音中は可視化フローティングウィンドウとサンプルテキストを表示、言語をまたいだ録音が可能、パッケージ & ダウンロード / コンパイル & 適用) > **カスタム TTS プロキシの SSRF 対策**:ホスト側プロキシは `http(s)` のターゲットのみ受け付け、ループバック / プライベート / リンクローカル / 予約アドレス(DNS 解決結果を含む)を拒否する——`localhost`、`10.x`、`192.168.x`、`169.254.169.254`(クラウドメタデータ)、`*.internal` などはすべて 400 を返す。転送可能なカスタムリクエストヘッダはコンテンツネゴシエーション系(`content-type` / `accept` / `accept-language` / `user-agent`)に限定される。 #### 音声パック形式ガイド 音声パックは **zip アーカイブ**(配布・共有に便利)で、`manifest.json` と音声ファイルを含む。パネルの「⚙ 設定 → 音声」で .zip をインポートすると有効になり、削除するとデフォルトの全文 TTS 放送に戻る。 zip 構造: ``` voice-pack.zip ├── manifest.json └── audio/ ├── dead.mp3 ├── low.mp3 └── … ``` ```json // manifest.json { "format": "dsh-api-balance-voice-pack", "version": 1, "name": "マイパック", "lang": "zh-CN", "segments": { "dead": "audio/dead.mp3", "low": "audio/low.mp3", "peak": "audio/peak.mp3", "peakEnd": "audio/peakEnd.mp3", "today": "audio/today.mp3", "month": "audio/month.mp3", "inLabel": "audio/inLabel.mp3", "outLabel": "audio/outLabel.mp3", "cacheHitLabel": "audio/cacheHitLabel.mp3", "costLabel": "audio/costLabel.mp3", "tokenUnit": "audio/tokenUnit.mp3", "suffix": "audio/suffix.mp3", // 任意:挨拶音声配列(ページ更新時にランダム再生) "greetings": ["audio/greet0.mp3", "audio/greet1.mp3"] } } ``` | セグメント | 用途 | |------|------| | `dead` | 残高不足警告の全文 | | `low` | 低残高警告の全文 | | `peak` | ピーク課金時間帯の提示(挨拶音声の後に追加) | | `peakEnd` | ピーク課金終了の提示(赤表示の自動解除時に再生) | | `today` | 「今日」放送のプレフィックス | | `month` | 「今月」放送のプレフィックス | | `inLabel` | 「入力」ラベル | | `outLabel` | 「出力」ラベル | | `cacheHitLabel` | 「キャッシュヒット」ラベル | | `costLabel` | 「金額」ラベル | | `tokenUnit` | 数字の後の単位(例「トークン」、再利用可) | | `suffix` | 放送の結び | 全セグメントは任意:欠けているセグメントは放送時に TTS でフォールバックする。パネルの表示は公式使用量ページと基準を一致:「入力」はキャッシュ未ヒットのみを計上し、キャッシュヒットは別掲(トークン・金額データは公式 API の日粒度バケットから取得し二次合算しない)。作成器のサンプルテキストはデフォルト TTS のフォールバック文案と一字一句一致させ(録音パックがデフォルト TTS 体験に近づくよう)、動的な数字(トークン数・金額・通貨)は現在の TTS バックエンドが合成し「パックセグメント + TTS 数字」の順で連結する。任意の `greetings` はファイルパスの配列(0–32 個):音声放送が有効な場合、「残高」タブクリック(手動更新)のたびにランダムに 1 つを挨拶/着地音として再生する。挨拶音声がない場合は TTS の挨拶プールからランダムに再生する。 **作成と共有**:「設定 → 音声 → 音声パック管理」→「音声パックを作成」で作成器を開く——まずパック言語(zh-CN / en / ja)を選択(サンプルテキストとマニフェストの `lang` を決定し、言語をまたいだ録音が可能);各セグメントをブラウザのマイクで録音し、挨拶はリストで 1 件ずつ録音(「挨拶を追加」でリスト拡張、✕ でスロット削除、サンプルテキストはデフォルト TTS 挨拶プールに対応);またはローカル音声ファイルをインポートする。録音中は右下に可視化フローティングウィンドウ(レベルメーター + 経過時間 + サンプルテキスト + 停止/破棄)が表示される。完了後「パッケージ & ダウンロード」で共有可能な zip を生成、「コンパイル & 適用」で本機ライブラリへインポートして即適用できる。音声パックがインポート済みの場合、最初の編集時に上書き警告を表示し確認が必要(セッション内で 1 回)。 **制約**:セグメントキー `[A-Za-z0-9_-]{1,32}`;segments ≤ 32 個、greetings ≤ 32 個(zip エントリ合計 ≤ 64)、音声 1 ファイル ≤ 2 MB。音声は mp3 / wav / ogg / webm 推奨、1 セグメント 2 秒以内、22.05/44.1 kHz モノラル。動的部分(残高の数字、トークン数など)はパックに含めず——現在の TTS バックエンド(ブラウザ内蔵またはホスト経由プロキシのカスタム TTS API)がリアルタイム合成し、「パックセグメント + TTS 数字」の順で連結して完全な放送にする。 ## インストール 本プラグインは二つの導入方式に対応する。**NixOS ユーザーは宣言的方式を推奨**(バージョンは Nix が固定し、システム世代とともに更新され、再現可能)。それ以外の DSH ユーザーは `dsh plugin add` を利用できる。**どちらか一方を選ぶこと**——両者は同じ `api-balance` entry id を登録する。 ### 方式 A:宣言的(NixOS モジュール、推奨) ```nix { nixkits.dsh.plugins.packages = [{ package = pkgs.dsh-api-balance; id = "api-balance"; name = "@kihara777/dsh-api-balance"; # config(任意): # apiKeyEnv = "DEEPSEEK_API_KEY"; # credential-ref # baseURL = "https://api.deepseek.com"; # browserScan = true; # ローカルブラウザ自動スキャン # browserScanIntervalMs = 21600000; # スキャン節流(デフォルト 6 時間) }]; } ``` ### 方式 B:`dsh plugin add`(DSH ネイティブ) ```bash dsh plugin --profile web add 'github:Kihara777/NixKits#path:packages/dsh-api-balance' ``` パッケージの `dsh.bundle` が `cordis.patch.yml` を指すため、導入後は profile の layer として有効化される。削除は `dsh plugin --profile web remove @kihara777/dsh-api-balance`。 > **`name` が相対パスである理由**:patch が登録するのはパッケージ名ではなく `./lib/index.js` である。bundle の entry 名が `./` で始まる場合、dsh はそれを**patch と同じディレクトリ**の絶対 `file://` URL にアンカーする。裸のパッケージ名を書くと dsh インストールツリーから解決され、本パッケージはそこに存在しないため `Cannot find package` で失敗する——pnpm はパッケージを dsh ツリーではなく profile ディレクトリへ配置するからである。 > **バージョンの再現性**:方式 B は git 経由で解決され、**`flake.lock` による固定を受けない**。再現可能な環境が必要なら方式 A を使う。 ## 注意 - 残高データは DeepSeek 公式 API、使用量データはプラットフォームコンソール内部 API 由来——認証方式が異なり(API キー / プラットフォームセッショントークン)、いずれかが欠けると該当ビューはエラーではなく未ログイン状態を表示する。 - 自動スキャンは本機ブラウザでログイン済みのトークンのみを読み取り、ブラウザ外のデータは収集しない。トークンファイルは `0600` パーミッションで保存される。 - パネルはページレベルのオーバーレイ(document レベルの fixed portal、会話領域のクリップを受けず横画面/狭幅でも画面外に出ない);高さは「アンカー上方の利用可能スペース」に自動でクランプされる(スマホ横画面でトップバーの遮蔽を回避);横方向の利用可能幅が不足する場合、コンテンツは適応幅を維持しパネルの横スクロールで表示され、縦方向もパネル自身のスクロールバーで表示される。 ### 設定ストレージ層 界面と音声設定(音声アラート、下部統計バー横スクロール、Enter改行 + Shift+Enter送信の交換、モバイルセッション切替時のキーボード抑止、TTS バックエンド)は**ブラウザ localStorage 状態**:ブラウザごとに独立、既定 ON、プラグインの `⚙ 設定` パネルで切り替えると即座に永続化される。これらは DSH host 設定システム(`settings.register` / `settings.yaml`)を**経由しない**ため、`nixkits.dsh.settings` はこれらへの宣言的オーバーライドを**提供しない**——こうした「ブラウザごとの設定」はデバイスごとにパネルで設定する。host 側プラグインパラメータ(`apiKeyEnv` / `baseURL` / `browserScan` / `browserScanIntervalMs`)は引き続き `nixkits.dsh.plugins.packages[].config` で宣言的に設定する。 ### 上流提案とエコシステム - 本プラグインによる host の `StatsLine` セッション統計バーの**横スクロール**最適化(モバイル / 縦向きタッチおよび大フォント環境向けに、省略記号による切り詰めの代わりに行内スクロールで全体を表示)は DeepSeek Harness 上流へ提案済み:GitHub Discussion [deepseek-ai/deepseek-harness #5458](https://github.com/deepseek-ai/deepseek-harness/discussions/5458)。公式 `CONTRIBUTING.md` は現時点で外部 PR を受け付けないため、「Discussion + 準備済みブランチ」の形で公開している。 - PR 準備済みの変更は当リポジトリのフォーク `Kihara777/deepseek-harness` のブランチ `draft/statline-overflow-scroll` に置いてある(local commit `e5ece63`、`packages/client/ui-chat` 配下 3 ファイル — CSS・TSX・コンポーネントテスト 2 件を変更)。アップストリームが外部 PR の受け入れを開始したら正式 PR へ昇格できる。插件側は上流の挙動へ追従後、host の `.root` へのランタイムスタイル注入を撤去できる。 - 本リポジトリには公式の `dsh-plugin` エコシステムトピックを付与してあり、プラグインエコシステム内で発見しやすくしてある。