# dsh-usage-meter-harness [English](README.md) | [简体中文](README.zh-CN.md) A real-time usage / cost / balance meter plugin for **DeepSeek Harness (DSH)**. See tokens, spending and real wallet balance right next to the chat input — for the official DeepSeek models **and** any custom model registered in DSH. ![settings](assets/screenshot.png) ## Install > Prerequisite (methods 1 & 2): the DSH CLI itself runs on pnpm — install it once per machine: > `npm install -g pnpm` (or `corepack enable`), then verify with `pnpm --version`. Pick one of the three methods. **Methods 1 and 2 need pnpm** (a one-time machine setup used by the DSH CLI itself): `npm install -g pnpm` or `corepack enable`. ### Method 1 — npm registry via DSH CLI (needs pnpm) ```bash dsh plugin --profile web add --verbose @faith1688/dsh-usage-meter-harness@latest ``` (`--verbose` shows the install progress; drop it if you prefer a quiet install. `@latest` explicitly requests the newest release — always install this way.) ### Method 2 — GitHub via DSH CLI (needs pnpm) ```bash dsh plugin --profile web add --verbose github:faith1688/dsh-usage-meter-harness#main ``` (`--verbose` shows the install progress. `#main` always installs the latest main branch — the newest release.) ### Method 3 — one-line installer, no pnpm (recommended) ```bash npx -y @faith1688/dsh-usage-meter-harness@latest ``` One command: installs into the DSH web profile and registers the bundle (idempotent). (`@latest` explicitly requests the newest release — always install this way.) Prefer not to use npx? The same logic ships as scripts in the repo: Windows (cmd): ```bat curl -fsSL https://raw.githubusercontent.com/faith1688/dsh-usage-meter-harness/main/scripts/install.cmd -o "%TEMP%\um-install.cmd" && "%TEMP%\um-install.cmd" ``` Linux / macOS: ```bash curl -fsSL https://raw.githubusercontent.com/faith1688/dsh-usage-meter-harness/main/scripts/install.sh | sh ``` The script does everything for you: `cd` into the DSH web profile, installs the package with visible progress, and registers the bundle in `dsh.profile.bundles` (idempotent — safe to re-run after upgrades). > Note: Method 3 uses plain `npm` and does **not** do pnpm coordination. If your > profile is managed with pnpm (the default for `dsh plugin`), prefer Method 1. After any method: **restart `dsh web`**. ## Updating Two cases — pick the right one: **Fresh install (never had the plugin), or the `npx` method:** just run the install command; it always fetches the latest release. **Upgrading an existing install (plugin already present):** the profile's `package.json` / `pnpm-lock.yaml` may be pinned to an old version, and a bare `add` can be skipped by pnpm as "already satisfied". **Always ask for the new version explicitly:** ```bash dsh plugin --profile web add @faith1688/dsh-usage-meter-harness@latest ``` or, from inside the profile directory (`~/.dsh/profiles/web`): ```bash pnpm update @faith1688/dsh-usage-meter-harness ``` (You may also pin an exact version, e.g. `...@1.0.28`.) > **Marketplace update failed?** If it reports *"targeted vX but installed vY; the > previous version was restored automatically"*, that is pnpm's **new-release > cooldown** (`minimumReleaseAge` — a freshly published version is skipped for > roughly 24h). Your install is not broken and nothing in your config or data was > touched. **Wait 24 hours** and click update again, or upgrade right away with the > `npx` command below (or add this package name, without a version, to > `minimumReleaseAgeExclude` in your profile's `pnpm-workspace.yaml`). **If your `package.json` binds the plugin via a `file:` path or a pin that `add` won't override** (you see `Already up to date` / `downloaded 0`), use the one-line installer instead — it always requests `@latest`, which rewrites the binding to `^` and upgrades in a single command: ```bash npx -y @faith1688/dsh-usage-meter-harness@latest ``` (Re-running this same command is the permanent update path: it is idempotent and upgrades regardless of whether you currently bind via `file:`, a pin, or `^`.) After updating: **restart `dsh web`** (or reload the browser page). Note that restarting alone never fetches a new version — it only reloads what is already in `node_modules`. **Why this never duplicates the mount entry and never touches your config:** - The mount entry lives **inside the package** (`cordis.patch.yml`, the `dsh.bundle` mechanism). Every release ships its own complete entry; DSH reads it from the installed package at startup — installing a newer package automatically brings the correct entry with it. - Installers only edit the profile's `package.json` (`dsh.profile.bundles`, de-duplicated) and `node_modules`. They **never write** the profile-root `cordis.patch.yml`, so anything you added there yourself (or your other plugin configs) stays untouched. - A duplicate mount entry can only happen if you manually added the same `id` to the profile-root `cordis.patch.yml` yourself — the installers never do that. ## Features ### Conversation usage card (next to the chat input) | Feature | Description | | --- | --- | | Live cost | Session cost in CNY or USD, updated every step | | Token breakdown | Input (miss) / cache hit / cache write / output | | Turn usage panel | Per-turn subtotals with unit prices tagged peak/off-peak | | Token speed | Live tokens/s while streaming; resets cleanly when output stops or tools run | | Cache hit rate | Share of cached tokens for the session | | Account balance | Real DeepSeek wallet balance; local-ledger estimate for other providers | | Budget & remaining | Set a budget, see used / remaining / over-budget | ### Billing engine | Feature | Description | | --- | --- | | 6 billing templates | Basic · Cache hit/miss · Peak/off-peak (DeepSeek official hours) · Cache write+hit · Combined input+output · Batch half price | | Custom price rows | Up to 4 user-defined rows; the popup mirrors your setup verbatim | | Peak/off-peak billing | Beijing-time weekday + hour windows, cross-midnight supported; each request is billed by its start time | | Accurate turn attribution | Each turn is billed to the model that actually ran it — switching models mid-session never re-labels open or finished turns; the turn's currency is fixed at its first billed usage, so USD-priced turns convert correctly | | Per-model pricing | Currency (CNY/USD), unit prices and balance per model | | Shared provider wallet | One balance shared by all models of a provider — single checkbox | | Official price prefill | DeepSeek official models come pre-filled with official prices and the official peak schedule | | Built-in price table | 137 models across 19 vendors bundled; optional LiteLLM-shaped remote price source | | Exchange rate | USD→CNY fetched automatically, refreshed when older than 24 h | | Sync official prices | One click on the settings page fetches the DeepSeek official price page, parses the latest prices and peak/off-peak windows, and writes them as overrides (a global API Key must be set first) — keep official pricing current at any time | | Legacy migration | Old manual initial-balance/top-up settings migrate into provider wallets automatically | ### Settings & UX | Feature | Description | | --- | --- | | Bilingual UI | Language switch at the top-right of the settings page: Follow system / 中文 / English; applies everywhere instantly (popup included) and the choice is remembered. Display only — saved data never changes | | Themes | 20 built-in themes (light first, dark after; default forest-beach-dawn) plus per-theme custom pill & breathing colors — quick switch at the top of the usage popup, full palette on the settings page, saved locally | | In-use lock | While a model is generating, its editor is locked so a running turn keeps consistent prices | | WYSIWYG popup | Usage-card rows are copied verbatim from your template selection | | Non-intrusive | Standard DSH cordis plugin; touches no other plugin and no DSH core files | | Peak/off-peak capsule | The usage capsule turns light-green on off-peak and light-red on peak (with a soft breathing glow while billing), so the active tier is obvious at a glance | | Unsaved / saved state | The model card shows **Unsaved** after you edit and **Saved** after you click Save — no more ambiguous "saving…" | | Current-session dashboard | The popup opens straight into the **Current session** view (total + per-category cost + per-turn ledger); the axis labels use `provider · model` so official vs custom models with the same name are never confused | | DS API Key guard | Non-official models picking "DS API Key" get a centered hint that the option is for DeepSeek models only; a custom model never falls back to the official global key — unset shows "未配置/not configured" | ## Supported models - **DeepSeek official models** (`deepseek-chat`, `deepseek-reasoner`, …): official prices pre-filled; real wallet balance via API Key. - **Any custom model** registered in DSH (OpenAI-compatible providers, Ollama, OpenRouter, …): set unit prices and balance yourself; everything else works the same. ## Screenshots Settings page: ![settings](assets/screenshot.png) Usage popup: ![popup](assets/popup.png) ## Configuration All settings live in the `usage-meter` settings namespace and can be edited directly in the plugin UI: | Key | Type | Default | Description | | --- | --- | --- | --- | | `currency` | string | `CNY` | Display currency | | `budget` | number | – | Session budget; shows "remaining" when set | | `priceSourceUrl` | string | – | LiteLLM-shaped price JSON URL; optional | | `refreshIntervalMs` | number | 4 h | Price / balance / rate refresh interval | | `deepseekApiKey` | secret | – | Only used to query the DeepSeek balance (stored AES-encrypted; never read from the `DEEPSEEK_API_KEY` env var) | ## Compatibility - Node.js ≥ 22. - Peer versions track the supported DSH releases (see `package.json`); updating DSH does not break the plugin, and it never modifies your other plugins. ## License MIT © [faith1688](https://github.com/faith1688) ## Privacy - The plugin makes **no telemetry and no analytics calls**. - Network requests are limited to four optional ones, all initiated by you: querying the **official DeepSeek balance API** with the API key you configure yourself, fetching a public USD→CNY exchange rate, fetching the official pricing page during a one-click price sync, and — only when rule parsing fails — sending that page's content to the DeepSeek API with your global key for extraction. Nothing else leaves your machine. - The source is MIT-licensed and fully readable on GitHub.