bowenliang123/dsh-context
bowenliang123/dsh-contextDSH 上下文洞察面板:Context 仪表盘 + /context命令 + Context 浏览器,查看 Context的分类组成、内容详情、演进趋势、压缩/注入事件、统计等一站式 Context 全生命周期管理。
在 DSH 侧边栏「设置」上方显示 Command Code 套餐额度:5 小时、每周、月度三条窗口的已用百分比与重置倒计时,附 /quota 命令与零依赖命令行工具。API key 从你自己的 provider 路由里读取,无需配置,也不依赖其他插件。
This plugin lives in the commandcode-usage monorepo (
plugins/dsh). Unlike the other six adapters there, it keeps its own data layer (quota.mjs) rather than sharingcore/cc-usage.mjs— its host/client halves and 244 offline checks are built against that contract. Everything else about it is unchanged.
Your 5-hour, weekly and monthly credit windows, in the sidebar — right above Settings.
No browser tab, no login, no guessing how much of the plan is left.
| Three windows, one glance | 5-hour, weekly and monthly, shortest first — so the tightest limit is where your eye lands |
| Percentage first | The headline rounds to a whole percent, exactly like the Command Code dashboard, so the card and the website never disagree |
| Money where it matters | Used and remaining for the monthly allowance; exact figures for every row on hover |
| Instant, then live | The card is on screen about 2 ms after a restart, and live about a second later |
| Quiet when it should be | No Command Code account? The card does not render at all |
| Bilingual | The card follows your DSH interface language (中文 / English) |
| Per-model allowances | What this plan gives each model, in Settings — the models you configured first |
Everything is read from your own account's data — window count, caps and percentages come from the API, never assumed. GOAT, Pro, Provider, Max and Teams work; a plan that reports no rolling windows simply renders no rows. (The $1 Go tier is the exception — it has no API access, so the card has nothing to render there.)
本插件要求 dsh ^0.1.5-rc.1。 版本不够的话启动会直接崩:
Error: failed to apply loader entry commandcode-quota:
Cannot read properties of undefined (reading 'register')
因为 ctx.connection.fetch 这个 seam 在旧版本里不存在。查一下再装:
dsh --version # 需要 0.1.5-rc.1 或更高
npm i -g @deepseek-ai/dsh@latest
注意:插件的 244 项离线校验跑得过也不需要这个版本——那些校验不启动 dsh。 所以"校验全绿"不代表装上去能用。
git clone https://github.com/Jovan1666/commandcode-usage
dsh plugin --profile web add ./commandcode-usage/plugins/dsh
Then restart dsh web and reload the browser page. That is the whole setup — no configuration file, no API key to paste: if Command Code is already a provider in your DSH settings, the plugin finds it.
The package declares a bundle patch (dsh.bundle.patch → its cordis.patch.yml), so dsh plugin add registers the plugin row for you. Do not also append that row to your profile's cordis.patch.yml. Two layers inserting the same loader id make dsh refuse to start: duplicate loader entry id: commandcode-quota. The row belongs to the manual install below, where no bundle layer exists.
From a local clone
git clone https://github.com/Jovan1666/commandcode-usage
dsh plugin --profile web add ./commandcode-usage/plugins/dsh
Manually, without pnpm — link the folder into the profile's node_modules, then register the row yourself, since nothing else will:
# $DSH_HOME/profiles/web/cordis.patch.yml
- insert:
- id: commandcode-quota
name: "dsh-commandcode-quota"
A freshly created profile ends that file with []. Replace the [] — appending a sequence under it is not valid YAML, and the profile will not load.
# macOS / Linux ($DSH_HOME defaults to $HOME/.dsh)
ln -s "$PWD/dsh-commandcode-quota" "${DSH_HOME:-$HOME/.dsh}/profiles/web/node_modules/dsh-commandcode-quota"
# Windows ($env:DSH_HOME defaults to $env:USERPROFILE\.dsh)
$dsh = if ($env:DSH_HOME) { $env:DSH_HOME } else { "$env:USERPROFILE\.dsh" }
New-Item -ItemType Junction -Path "$dsh\profiles\web\node_modules\dsh-commandcode-quota" -Target "$PWD\dsh-commandcode-quota"
To remove it: dsh plugin --profile web remove dsh-commandcode-quota drops the package and the bundle layer that registers it; a manual install instead deletes the cordis.patch.yml row. Restart dsh web either way. To clear the cached snapshot too, delete $DSH_HOME/dsh-commandcode-quota/.
The card used to wait out a full upstream round trip before drawing anything, and a restarted dsh has nothing cached — which is exactly the "it takes a moment to show up" feeling. Measured against the live API with node preview/latency.mjs:
| First answer after a restart, snapshot on disk | ~2 ms |
| First answer after a restart, no snapshot yet | ~1.4 s |
| The four endpoints requested one after another | ~2.3 s |
| The four endpoints requested together | ~1.2 s |
Two things make the difference:
whoami used to be awaited on its own — about 590 ms of pure waiting, to learn an org id that personal accounts never report.| Window | What it means | On GOAT |
|---|---|---|
| 5 小时 | Rolling burst limit — one long session cannot drain the month | $14 |
| 每周 | Rolling 7-day limit | $35 |
| 月度 | The billing period's credit allowance | $70 |
Each row shows the used percentage (green below 60 %, amber below 85 %, red above), a meter in the same colour, and a reset countdown (59m, 6d9h, 8d1h). Click the card for the monthly allowance in money, the remaining credit, the request count and the token totals. Collapse the sidebar and the card becomes a 36 px badge showing the most constrained window.
used ÷ (used + remaining), read live from the API. preview/e2e-live.mjs asserts that identity against a real account on demand.100% while the exact share is 99.84% — same data, two roundings. The exact figure and the money are one hover away.The sidebar is about 200 px of content width, and a laptop screen makes small type smaller still. So the card answers one question well — how deep am I? — instead of laying out everything the API returns:
projection in its JSON for scripts.The card answers how deep am I. Settings → Call counts answers the other question: what would this plan give me if I only ever called this model?
Every model the plan can call, with requests per 5 hours, per week and per month — the models you configured in DSH first. Above the table sits the plan-level headline (GOAT · $70 · ~75K requests): a different figure on a different basis, labelled so the two are never read as one number.
The figures are the vendor's, not ours. Each plan page publishes a per-model monthly budget, that model's per-token rates and the request shape the estimate assumes; the page itself computes the counts in the browser. The plugin reproduces that arithmetic and rounds exactly like the site (Number(x.toPrecision(3))). tests/catalog.test.mjs pins the outcome against the numbers the vendor's own pages render — 244 rows across four plans, zero mismatches.
It asks only when it has to. The docs pages ignore conditional requests (the server hands out an ETag and then answers 200 to If-None-Match — there is no 304 branch to lean on), so the plugin does the check itself: HEAD for the page's ETag, 0 bytes, and only a real change downloads a body (about 200 KB, ~37 KB compressed). A check runs when you open the section and the local copy is over a day old, or when you press Check for updates; the sidebar card never triggers one. A baseline ships inside the plugin, so a first run — or a machine with no network — still shows figures, labelled baseline, not synced yet.
Where the plan page lists no figure at all, the pricing page's own calculator fills it in: same allowance, same rates, and a request shape derived from the provider exactly the way the site derives it (those rows are tagged derived). A model the vendor gives away reads Free.
Nothing is guessed where the vendor is silent. A model neither page prices reads —, never 0; a plan with no per-model page of its own says so instead of borrowing another tier's numbers (Ultra is shown from Max's table and labelled an inference); a failed check keeps the previous figures and prints how old they are.
/quota commandType /quota in a conversation to print the same report as text:
Command Code · GOAT (active)
5-hour 1.4% used · resets in 3h17m
Weekly 12.8% used · resets in 6d7h
Monthly 99.8% used · $70.11 / $70.23 · $0.11 left · resets in 7d22h
5,000 requests · 100% success · in 210.0M / out 4.20M
It reads the same cached report the card does, so a slash invocation costs no extra upstream requests — and unlike the card, it never answers from a stale snapshot: typing a command means asking for the current numbers. Its text is English; the card is the bilingual surface.
^0.1.5-rc.1. The plugin uses framework seams that are not a stable public API yet; see Compatibility.connection service, which only the web app composes — a headless or CLI profile has nowhere to put it.$1 Go tier includes it; see Getting a Command Code plan.The card reads any Command Code plan that has API access. This plugin was built against GOAT: $10/month, which buys $70 of credits, gated at $14 per 5-hour window and $35 per rolling week.
GOAT is the tier that suits an agent harness. Command Code's own estimate for DeepSeek V4 Flash on that allowance is ~154,000 requests a month (~30,800 per 5-hour window, ~76,900 per week), because flash-tier models bill at roughly $0.15 input / $0.60 output per million tokens with cache reads at $0.003. A coding agent spends its budget on exactly those calls: many small turns, most of the context re-read from cache. Tens of thousands of tool-calling turns a month is ordinary work, and at that price the window caps and the monthly allowance are what run out — not the request count.
Two caveats on those numbers, both from the same page: the request counts assume a typical agent turn of ~800 fresh input tokens, ~50,000 cache-read tokens and 125–200 output tokens, so a run that carries a large repo context drains the allowance faster; and DeepSeek is billed by time of day, with peak hours priced higher (01:00–04:00 and 06:00–10:00 UTC, Mon–Fri). Command Code states that allowances can change at any time — the pricing page is authoritative, and the figures above are what it said on 2026-09-19.
Sign in at commandcode.ai and open Pricing, or Studio → Billing.
Choose GOAT and check out. Card payments run through Stripe; Alipay is supported on the USD-denominated plans and sets up automatic renewal, which is worth knowing before the first invoice. UnionPay is not listed.
In Studio, open API keys → Generate. The key looks like user_…, not sk-….
Hand it to dsh. Settings → Models takes a provider interactively; by file, add a route to $DSH_HOME/settings.yaml:
llm-pi-ai:
providers:
command-code-goat:
apiKeyEnv: COMMAND_CODE_GOAT_API_KEY
api: openai-completions
baseURL: https://api.commandcode.ai/provider/v1
models:
- id: deepseek/deepseek-v4.1-flash
contextWindow: 1000000
input: ["text", "image"]
Keep the key itself out of the file: put it in the environment, or in $DSH_HOME/.credentials.yaml under refs.COMMAND_CODE_GOAT_API_KEY. Nothing else is needed — Credentials is how the card finds this same route, which is why installing the plugin never asks for a key.
$1) has no API access. All four quota endpoints answer 404, which the plugin reports as "this plan has no API access" rather than as an error it can retry.$20 Pro ($80 of credits) is the same shape with more headroom and $100 / $200 Max scale it again. The $15 Provider plan is metered API access with no rolling windows, so the card shows the balance and no window rows.The API key never reaches the browser. The host resolves it in this order and reports which source won:
An explicitly passed key (the CLI's --key).
Discovered from your own DSH configuration — any provider route whose baseURL points at commandcode.ai, read from $DSH_HOME/settings.yaml or a patch layer ($DSH_HOME/cordis.patch.yml, $DSH_HOME/profiles/<name>/cordis.patch.yml). The plugin reads that route's literal apiKey or its apiKeyEnv, then resolves the name through the environment and $DSH_HOME/.credentials.yaml. The provider's host is kept (a staging host or proxy works), but only its origin: the quota endpoints live at the host root, not under the provider's /provider/v1 path.
Both locations matter because the two DSH flavours keep settings in different places: the CLI writes settings.yaml, while the desktop app migrates that file to settings.yaml.imported on first run and writes user settings into the patch layers from then on. Reading only settings.yaml made every desktop install answer configured: false, which renders no card and no error.
Environment variables: COMMANDCODE_API_KEY, COMMAND_CODE_API_KEY, CMD_API_KEY, then any variable whose name contains commandcode.
Those same names inside $DSH_HOME/.credentials.yaml (refs.<NAME>).
~/.commandcode/auth.json, the official command-code CLI's login state.
Step 2 is what makes this work for other people: it follows your provider configuration instead of hardcoding one naming convention.
flowchart LR
A["Sidebar card<br/>(browser half)"] -->|"POST /api/cc-quota/report<br/>same origin, session cookie"| B["Host half<br/>(15 s cache + disk snapshot)"]
B -->|"four read-only GETs, in parallel"| C["Command Code API<br/>/alpha/*"]
| Endpoint | Used for |
|---|---|
/alpha/whoami | Account name, org id |
/alpha/usage/summary | Credits used this period, requests, success rate, tokens |
/alpha/billing/credits | Remaining credits, the 5-hour and weekly windows |
/alpha/billing/subscriptions | Plan id, status, billing-period start and end |
Each endpoint degrades on its own: one failure is recorded in the report's failures, is shown on the card as a muted line, and the rest still render. All four failing raises one error with the most specific code — including "this plan has no API access" when all four answer 404.
The plugin depends on framework seams that are not a stable public API yet. Each is pinned to what dsh 0.1.5-rc.1 actually exposes:
| Seam | Used for |
|---|---|
sidebar.footer.action slot | The seat above Settings, in both sidebar widths |
ctx.slots.register({ name, id, order, inject }, Component) | Contributing the card |
ctx.connection.rpc.call(channel, endpoint, payload, signal) | The browser side of the request |
ctx.connection.fetch.register({ path, methods, requestBody, fetch }) | The host side of the route |
ctx.get('commands') + commands.register({ name, description, handler }) | The optional /quota command |
dsh.client manifest + exports["./client"] | Client-bundle discovery, served at /plugins/<id>/client.js |
Why an exact Fetch route instead of
connection.rpc.handle?rpc.handlemounts its channel throughowner.webServer, whereowneris the Connection service's own context — which never injectswebServer. Calling it from any other plugin throwscannot get property "webServer" without inject, whatever the caller injects.connection.fetch.registeronly writes the route table, works from any plugin fiber, and inherits the shared/apitransport's Host/Origin fence and browser-session cookie.
If a future dsh release changes one of these, the plugin fails loudly at load rather than silently rendering nothing.
The same data layer ships as a zero-dependency read-only CLI — useful on a headless machine, or for checking a second account:
node cli/cli.mjs # render once
node cli/cli.mjs --watch 60 # refresh every 60 seconds
node cli/cli.mjs --json # normalized report for scripts
node cli/cli.mjs --help
Command Code · GOAT(individual-goat) · Jovan1666
key: $DSH_HOME/.credentials.yaml → refs.COMMAND_CODE_GOAT_API_KEY
5 小时 ---------------------------- 1.4%
今天 21:55 重置(2 小时 4 分后)
每周 ####------------------------ 12.8%
09-24 01:51 重置(6 天 6 小时后)
月度额度 ############################ 99.8% · $70.11 / $70.23
剩余 $0.11 · 09-25 17:08 重置(7 天 21 小时后)
本周期 5,000 请求 · 成功率 100% · in 210.0M / out 4.20M tokens
Shown with --ascii, and deliberately so: the default bars are drawn with full-height block glyphs, which sit flush against the text line beside them in many code fonts — in this very README the weekly bar merged with the 5-hour countdown above it. # and - are ordinary glyphs and travel everywhere. There are also no rule lines and no right-aligned columns, so every line stands on its own instead of depending on character-cell widths.
Flags: --json, --watch [seconds], --ascii, --color / --no-color, --base <url>, --timeout <ms> (default 30 s; COMMANDCODE_QUOTA_TIMEOUT_MS overrides it), --key <key>.
The CLI's human-readable output is Chinese; --json is language-neutral and is the interface to script against. It follows the card's presentation rules — money on the monthly allowance only, no pace verdict and no burn-rate forecast (those remain in the JSON).
| Symptom | What it means |
|---|---|
| No card at all | This host has no Command Code provider configured, so the plugin stays invisible by design. Check Settings → Models. |
| The card shows an error | The card says what it can ("cannot reach Command Code", "the API key was rejected"); hover it for the full diagnostic text. |
| The settings section says baseline, not synced yet | Nothing has been fetched on this machine yet — a fresh install, an offline host, or a check that has not run. Press Check for updates; what you are reading is the copy shipped with the plugin, dated by its check time. |
| Check for updates returns instantly and nothing changes | That is the good case: the 0-byte HEAD found the page's ETag unchanged, so no body was downloaded. The status line shows when the last check happened. |
A line under the rows saying N reading(s) unavailable (3 项数据这次没取到) | N of the four upstream readings did not count this time. Each endpoint gets 30 s (COMMANDCODE_QUOTA_TIMEOUT_MS moves that, in ms, 1 s–120 s); past it the reading is dropped rather than shown, and the usual cause is Command Code itself being slow — on 2026-09-30 its own server-timing header reported 14 s for /alpha/usage/summary while /alpha/billing/credits answered in 43 ms. Hover the card to see which endpoint failed; the next poll retries on its own. A missing monthly row on the same card is the same cause, one endpoint over. |
/plugins/dsh-commandcode-quota/client.js returns 404 | The client bundle was not composed. Check that package.json declares dsh.client.platform === "web" and exports["./client"]. |
A change to client.js did nothing | Reload the page — the bundle is read from disk per request. Changes to index.js or quota.mjs need a dsh web restart (Node caches modules). |
| Numbers are dimmed | The host answered with its last snapshot, or a refresh failed. The age is printed underneath, and it corrects itself on the next refresh. |
Everything reads — | The account reported no windows for that plan, or a read is still in flight. |
| The panel is empty after removing Command Code | Intended — the plugin does not show a snapshot for a provider you no longer have. |
/api transport, which is fenced to loopback and requires this process's browser-session cookie.$DSH_HOME/dsh-commandcode-quota/last-report.json. It holds the figures the card shows — never the key, and only a short digest of the key, used to check that the snapshot belongs to the account currently configured. Delete it any time; the plugin recreates it.# 1. React is needed only by the component test and the preview page
mkdir .devdeps && cd .devdeps && npm init -y && npm install react@18 react-dom@18 && cd ..
# 2. Everything at once — 244 checks, one verdict, no network, no real credentials
no