Jovan1666/dsh-commandcode-quota

dsh-commandcode-quota

在 DSH 侧边栏「设置」上方显示 Command Code 套餐额度:5 小时、每周、月度三条窗口的已用百分比与重置倒计时,附 /quota 命令与零依赖命令行工具。API key 从你自己的 provider 路由里读取,无需配置,也不依赖其他插件。

未分類ai-coding-agentclicommand-code

Command Code quota for DeepSeek Harness

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.

What you get

· 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 — and if you just want the sidebar to yourself, switch it off from Settings 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.)

Check your dsh version first

This plugin needs dsh `0.1.5-rc.1` or newer — `>=0.1.5-rc.1`, not `^0.1.5-rc.1`. The caret is the trap: under semver ^0.1.5 means >=0.1.5 <0.2.0, so it would read as *excluding* the 0.2.x line this plugin is running on today. Verified against `0.2.1-rc.3` (the current desktop nightly, 2026-10-26); on anything older than 0.1.5-rc.1 it crashes at startup:

Error: failed to apply loader entry commandcode-quota:
Cannot read properties of undefined (reading 'register')

The ctx.connection.fetch seam it relies on does not exist in older releases. Check before you install:

dsh --version          # needs 0.1.5-rc.1 or newer; 0.2.1-rc.3 is what this was verified on
npm i -g @deepseek-ai/dsh@latest

The desktop client does not put dsh on your PATH; its version is in the app's own About/Settings panel, and the nightly channel currently publishes 0.2.1-rc.3. The npm package trails it slightly (0.2.1-rc.2 at the time of writing), so npm i -g …@latest and the desktop installer can legitimately show different last digits — both satisfy the requirement above.

Note that the plugin's offline checks also pass on an older dsh — they never start dsh. So "the checks are green" does not mean it will work once installed, and the same is true of this README's prose: the numbers in it were read from the version above, not from yours. node scripts/check.mjs prints how many checks it actually ran.

Install

dsh plugin --profile web add github:Jovan1666/dsh-commandcode-quota

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.

Other ways to install, and how to remove it

From a local clone

git clone https://github.com/Jovan1666/dsh-commandcode-quota
dsh plugin --profile web add ./dsh-commandcode-quota

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/.

Desktop client

The Electron desktop client owns its profile: dsh --profile desktop … refuses to run with profile "desktop" is managed exclusively by the Electron application, so the dsh plugin route is unavailable there and the wiring has to be done by hand. There are two shapes, and the app's own profile uses the first one:

Declared in the profile (no loader row). Add the package to $dsh\profiles\desktop\package.json — a link: dependency plus the name in the profile's bundle list — and let the package's own bundle patch register it:

{
  "dependencies": {
    "dsh-commandcode-quota": "link:C:/path/to/dsh-commandcode-quota"
  },
  "dsh": {
    "profile": {
      "bundles": [
        "@deepseek-ai/dsh-base",
        "@deepseek-ai/dsh-web-app",
        "dsh-commandcode-quota"          // ← the registration comes from here
      ]
    }
  }
}

Make sure the folder reaches node_modules as well — the profile resolves the link: through it:

$dsh = if ($env:DSH_HOME) { $env:DSH_HOME } else { "$env:USERPROFILE\.dsh" }
New-Item -ItemType Directory -Force -Path "$dsh\profiles\desktop\node_modules" | Out-Null
New-Item -ItemType Junction `
  -Path "$dsh\profiles\desktop\node_modules\dsh-commandcode-quota" `
  -Target "C:\path\to\dsh-commandcode-quota"

Restart the desktop app. Do not add an `insert` row as well — the bundle layer already registers the loader id, and a second one is the duplicate loader entry id: commandcode-quota failure described above.

Manually, with a loader row — only when the package is *not* in the profile's bundles list. Then nothing else will register it, so the row is required:

# $dsh\profiles\desktop\cordis.patch.yml
- insert:
    - id: commandcode-quota
      name: "dsh-commandcode-quota"

Restart the desktop app. If the plugin does not appear, check the row is there exactly once and that the junction above exists — those are the only two things the loader needs.

Keep whatever you add to that file ASCII-only. The app regenerates the profile on its own: an insert entry added by hand survives, but a non-ASCII comment on it comes back mangled.

Why it appears before you look

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:

  1. All four endpoints are requested at once. whoami used to be awaited on its own — about 590 ms of pure waiting, to learn an org id that personal accounts never report.
  2. The last good report is kept on disk. A cold start answers with it immediately — dimmed, and labelled with how old it is — while a live read runs behind it. The card re-asks 3 seconds later instead of waiting out the usual minute, so the numbers are live by the time you have read them.

How to read the card

Window · What it means · On GOAT 5-hour · Rolling burst limit — one long session cannot drain the month · $14 Weekly · Rolling 7-day limit · $35 Monthly · 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. The chip is only the duration, in the UI language, so it does not push the percentage out of a 200 px row: 59分 / 3时05分 / 6天9时 in Chinese, 59m / 3h 05m / 6d 9h in English. “Resets in” and the clock time stay on the hover. 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 which window is most constrained, and that window’s percentage.

Reading the numbers

  • Percentages are used ÷ (used + remaining), read live from the API. preview/e2e-live.mjs asserts that identity against a real account on demand.
  • The headline rounds to a whole percent, the same way the dashboard does. That is why the website can say 100% while the exact share is 99.84% — same data, two roundings. The exact figure and the money are one hover away.
  • The monthly cap is the sum of two figures from two different endpoints. Across a billing-period rollover or a plan change those two can describe different periods, and the sum would look plausible while being wrong by tens of percent. The plan's nominal allowance is the sanity check; when a read fails it, the host reports no percentage at all and the card says why. The next refresh corrects it.

What the card deliberately leaves out

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:

  • Money only for the monthly allowance. The 5-hour and weekly windows are pass/fail gates, not budgets; their dollar rows told a user nothing they could act on. Hover still shows exact figures.
  • No pace verdict, no burn-rate forecast. "Over pace" cannot be acted on by someone who has work to do, and a projected exhaustion date assumes a constant burn rate that credit usage never has. The host still exposes projection in its JSON for scripts.
  • Nothing silent. A row that disappears because its endpoint failed says so; a failure with nothing to fall back on says what went wrong in one readable line, with the full diagnostic text on hover.

Per-model allowances, in Settings

The card answers *how deep am I*. Settings → Quota & card answers the other question: *what would this plan give me if I only ever called this model?* — and holds the switch that decides whether the card exists at all.

Use another model and want the sidebar back? The first panel in that section has a switch: Show the quota card in the sidebar. Turn it off and the card (and its collapsed rail badge) disappears from the bottom-left immediately — no reload, no restart — and the choice is remembered across restarts. Turn it back on whenever you want the numbers again. Nothing about the account changes; it is a display preference, stored in the plugin's own directory ($DSH_HOME/dsh-commandcode-quota/settings.json) and never sent anywhere.

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 — every model row of all four plan pages, plus the pricing page's availability and plan-level tables — and the offline baseline stores the inputs those rows were computed from, so the arithmetic can be re-checked without the network. (This paragraph used to quote a row count. It went stale twice, so it no longer quotes one: run node scripts/verify.mjs and read the catalog suite's own numbers instead.)

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*.

Opening the section never waits for that check. The section paints from local data immediately, the check runs behind the answer, and the page says *checking the official data in the background* until the fresh numbers land — which they do on their own, because the section comes back for the result instead of leaving you to press the button again. Measured with a simulated 300 ms round trip, serving the panel went from 1.6 s to 0.32 s, and a card poll from 22 KB to 1.06 KB (the catalog used to ride along on every answer; the card never reads it). The check's own HTTP is two round trips instead of four — both page probes go out together — and a check that finds nothing new costs two 0-byte probes. If a check leaves the local copy unchanged, the next one waits a few seconds (and a failed day-long check is not retried on every mount), so a panel left open cannot turn into a request loop.

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.

The /quota command

Type /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.

Requirements

  • DeepSeek Harness >=0.1.5-rc.1 — verified on 0.2.1-rc.3. The plugin uses framework seams that are not a stable public API yet; see Compatibility.
  • The web profile. The card mounts into the browser sidebar through the connection service, which only the web app composes — a headless or CLI profile has nowhere to put it.
  • A Command Code account with API access. Every plan except the $1 Go tier includes it; see Getting a Command Code plan.
  • Node.js 18+ — only for the optional CLI and the development scripts.

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.

Subscribing

  1. Sign in at commandcode.ai and open Pricing, or Studio → Billing.
  2. 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.
  3. In Studio, open API keys → Generate. The key looks like user_…, not sk-….
  4. 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.

Before you subscribe

  • Go (`$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.
  • The 5-hour and weekly windows start at your first request, not at a calendar boundary, and switching plans clears both of them. The card shows the reset times the API reports instead of computing them from a period start.
  • One account per person. The terms forbid sharing, reselling, or rotating keys across accounts, and a violation puts every account involved at risk of a permanent ban.
  • Other tiers, same card. $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.

Credentials

The API key never reaches the browser. The host resolves it in this order and reports which source won:

  1. An explicitly passed key (the CLI's --key).
  2. Discovered from your own `$DSH_HOME/settings.yaml` — any provider route whose baseURL points at commandcode.ai. 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.
  3. Environment variables: COMMANDCODE_API_KEY, COMMAND_CODE_API_KEY, CMD_API_KEY, then any variable whose name contains commandcode.
  4. Those same names inside $DSH_HOME/.credentials.yaml (refs.).
  5. ~/.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.

How it works — endpoints, seams, and the one that bit us