# dsh-token-monitor

็ฎ€ไฝ“ไธญๆ–‡ | English

> ๐Ÿค– **AI-generated disclaimer**: This project was generated with AI assistance and is provided for learning and technical research only โ€” no commercial warranty or support is implied. A [DeepSeek Harness](https://github.com/deepseek-ai/deepseek-harness) plugin that puts a **"ไปŠๆ—ฅ็”จ้‡ / today's usage" card at the sidebar foot** (next to the Settings gear) showing today's token total in real time (e.g. "ไปŠๆ—ฅ็”จ้‡๏ผš8888ไธ‡"); **clicking the card pops the full stats window** โ€” today / 7-day / 30-day token totals, a 30-day per-model stacked usage chart, a 30-day per-model usage ranking (fixed window), and conversation counts. No settings page involved: one click and you're in. ![Today usage card](screenshot.png) ## Features - ๐Ÿš€ **One-click access** โ€” the sidebar "ไปŠๆ—ฅ็”จ้‡" card shows today's token total live on one line ("ไปŠๆ—ฅ็”จ้‡๏ผš8888ไธ‡"; refreshes every 30 s; the 56 px rail keeps only the compact value); clicking opens the stats window (close with Esc / โœ• / clicking the mask; background scroll is locked while open). - ๐Ÿ“Š **30-day model usage** โ€” a 30-day stacked bar chart: each bar is stacked per model by color (the day's heaviest model at the bottom; colors fixed by the 30-day rank). The chart area has a full grey dot-grid background (exactly two dot columns per day, 13 px row pitch, per `temp/model-usage.png`), with bars drawn on top of the dots; the axis shows only the weekly Monday baseline dates; hover/click a day to pin the breakdown card (date / total / per-model swatch rows, styled after `temp/tooltip.png`) โ€” no highlight/dim effects and no label above the bar. - ๐Ÿ† **Model usage ranking** โ€” fixed 30-day window: top-4 models in a **2ร—2** grid (rank number on its own line / model + token total / provider + right-aligned usage share โ€” no swatch, no growth or unit labels); cards scale slightly on hover. - ๐Ÿ’ฌ **Conversation stats** โ€” top-level conversations opened today / 7 days / 30 days (subagent sessions excluded), with the corresponding model request counts. - ๐Ÿ”„ **Auto refresh** โ€” 30 s polling (with an idempotent re-scan that self-corrects the numbers) plus manual "ๅˆทๆ–ฐ / Refresh" and "ๅ›žๅกซๅކๅฒ / Backfill" buttons. - ๐Ÿ’พ **Persistence** โ€” buckets are written to `$DSH_HOME/plugins/token-monitor/data.json` and survive restarts (181-day daily buckets; per-model breakdown inside each bucket). - ๐Ÿ•˜ **Historical backfill** โ€” session logs are the single source of truth: on startup, on the "ๅ›žๅกซๅކๅฒ / Backfill" button, and on every 30 s poll the plugin folds the whole in-window corpus into a fresh bucket map and swaps it in atomically, so usage from *before* the plugin was installed is included too, repeated runs never double-count, and stale buckets self-heal. - ๐ŸŽจ **DSH-native styling** โ€” light theme, 1 px hairline cards, 8โ€“10 px radii, no shadows; colors use `--dsw-alias-*` theme variables so it follows DSH's dark theme as well. ## Install From this repository: ```bash dsh plugin --profile web add github:zhangzheng25/dsh-token-monitor ``` Or from a local checkout: ```bash dsh plugin --profile web add E:\path\to\dsh-token-monitor ``` `dsh plugin` forwards to pnpm inside the profile directory and reconciles `dsh.profile.bundles`; the package's `dsh.bundle.patch` (`cordis.patch.yml`) inserts the plugin row into the host composition, and `dsh.client.platform: "web"` makes the web shell serve `client/bundle.js`. **Restart DSH**: the "ไปŠๆ—ฅ็”จ้‡ / today's usage" card appears at the sidebar foot (next to the Settings gear); click it to pop the stats window. ## How it works ``` โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ Host (Node.js) โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” โ”‚ src/index.js โ”‚ โ”‚ โ€ข backfill() โ† session logs are the SINGLE source โ”‚ โ”‚ of truth (idempotent full rebuild: sessionQuery is โ”‚ โ”‚ live-preferred โ€” in-memory sessions and persisted logs both โ”‚ โ”‚ count; each run folds every in-window assistant/message โ”‚ โ”‚ usage event into a FRESH bucket map and swaps it in โ”‚ โ”‚ atomically, so repeated runs never double-count; runs on โ”‚ โ”‚ startup / "ๅ›žๅกซๅކๅฒ" button / 30 s polls) โ”‚ โ”‚ (input / output / cache read / cache write / reasoning, โ”‚ โ”‚ split by day AND model via message.source; 181-day window) โ”‚ โ”‚ โ€ข buildSessionStats() โ† top-level conversation counts โ”‚ โ”‚ (delegationDepth === 0), 20 s cache โ”‚ โ”‚ โ€ข persist() โ† debounced JSON write (schema v3, โ”‚ โ”‚ per-day AND per-model buckets, 181 days kept) to โ”‚ โ”‚ $DSH_HOME/plugins/token-monitor/data.json โ”‚ โ”‚ โ€ข webServer.register('/token-monitor/today') โ† light today read โ”‚ โ”‚ (no rebuild; polled by the sidebar card every 30 s) โ”‚ โ”‚ โ€ข webServer.register('/token-monitor/snapshot') โ† HTTP route โ”‚ โ”‚ consumed by the browser half (static-bundle pattern; serves โ”‚ โ”‚ modelRank aggregates + modelDaily30) โ”‚ โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ โ”‚ fetch('/token-monitor/today' | '/token-monitor/snapshot') โ–ผ โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ Client (browser) โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” โ”‚ client/bundle.js โ€” hand-built web bundle following the โ”‚ โ”‚ client-modules protocol (window.__ModuleLoader__.load) โ”‚ โ”‚ โ€ข slots.inject('sidebar.footer.action') โ†’ "ไปŠๆ—ฅ็”จ้‡" card โ”‚ โ”‚ (click pops a fixed modal hosting the full dashboard; no โ”‚ โ”‚ settings.section registration anymore) โ”‚ โ”‚ โ€ข metric cards + conversation cards + 30-day stacked chart + โ”‚ โ”‚ model usage ranking โ”‚ โ”‚ โ€ข card polls /token-monitor/today every 30 s; popup fetches the โ”‚ โ”‚ snapshot on open and polls every 30 s, refresh / backfill โ”‚ โ”‚ buttons โ”‚ โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ ``` ## Data sources - **Single source of truth = session logs**: `assistant/message` usage events in session logs, attributed via the event's `message.source` (`provider` / `model`), read through `ctx.sessionQuery` (zstd decoding handled internally; sessions are not woken). `sessionQuery` is live-preferred โ€” in-memory active sessions and persisted logs are both readable, so in-flight calls are counted too and no `llm/stream` hook is needed (older versions ran waterfall live capture *and* log backfill at once; the overlap counted every call twice and inflated the numbers โ€” v3 removed the live path and rebuilds idempotently instead). - **Conversations**: `sessionQuery.listSessions()` headers (`cwd`, `createdAt`, `delegationDepth`); only top-level sessions (`delegationDepth === 0`) count as conversations. ## Development ```bash node --check src/index.js # host half node --check client/bundle.js # client bundle ``` The client bundle is written by hand to match the `client-modules` bundle protocol โ€” no build step required. ## License [MIT](./LICENSE)