Skip to content
Open
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ OpenUsage lives in your menu bar and shows you how much of your AI coding subscr
- [**Antigravity**](docs/providers/antigravity.md) / all models
- [**Claude**](docs/providers/claude.md) / session, weekly, peak/off-peak, extra usage, local token usage (ccusage)
- [**Codex**](docs/providers/codex.md) / session, weekly, reviews, credits
- [**Command Code**](docs/providers/command-code.md) / plan usage, monthly credits
- [**Copilot**](docs/providers/copilot.md) / premium, chat, completions
- [**Cursor**](docs/providers/cursor.md) / credits, total usage, auto usage, API usage, on-demand, CLI auth
- [**Factory / Droid**](docs/providers/factory.md) / standard, premium tokens
Expand Down
84 changes: 84 additions & 0 deletions docs/providers/command-code.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
# Command Code

> Uses the Command Code billing API to track plan credits usage.

## Overview

- **Source of truth:** `https://api.commandcode.ai`
- **Auth discovery:** `~/.commandcode/auth.json`
- **Provider ID:** `command-code`
- **Usage scope:** account-level monthly plan credits

## Detection

The plugin enables when `~/.commandcode/auth.json` exists and contains a non-empty `apiKey`.

If the secrets file is missing, the plugin stays hidden.

## Data Source

OpenUsage calls two Command Code API endpoints:

```
GET https://api.commandcode.ai/alpha/billing/credits
GET https://api.commandcode.ai/alpha/billing/subscriptions
```

Both are authenticated with `Authorization: Bearer <apiKey>`.

### Credits Response

```jsonc
{
"credits": {
"monthlyCredits": 9.9859 // remaining plan credits
}
}
```

### Subscriptions Response

```jsonc
{
"success": true,
"data": {
"planId": "individual-go",
"currentPeriodEnd": "2026-06-05T07:58:40.000Z"
}
}
```

`planId` determines which plan limit applies.

## Limits

OpenUsage uses the current published Command Code plan limits:

- `individual-go`: `$10`
- `individual-pro`: `$30`
- `individual-max`: `$150`
- `individual-ultra`: `$300`
- `teams-pro`: `$40`

Bars show used credits in dollars (plan label) and as a percentage (Monthly Quota), clamped at `100%`.

## Window Rules

- **Period:** subscription billing period (`currentPeriodStart` - `currentPeriodEnd` from the API)
- **Resets at:** the `currentPeriodEnd` timestamp returned by the API

Usage is account-level from the API, not estimated from local history.

## Failure Behavior

| Condition | Behavior |
|---|---|
| Secrets file missing | Plugin hidden |
Comment on lines +74 to +76
| API returns 401/403 | Red error: `Session expired. Re-authenticate in CommandCode.` |
| API returns HTTP error | Red error with status code or detail message |
| Network failure | Red error: `Request failed. Check your connection.` |
| Unexpected response structure | Red error: `Could not parse usage data.` |

## Future Compatibility

The public provider identity stays `command-code`. If Command Code later changes billing endpoint paths or response schemas, OpenUsage can update the plugin without changing the provider ID or UI contract.
1 change: 1 addition & 0 deletions plugins/command-code/icon.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
177 changes: 177 additions & 0 deletions plugins/command-code/plugin.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,177 @@
(function () {
var SECRETS_FILE = "~/.commandcode/auth.json";
var SECRETS_KEY = "apiKey";
var CREDITS_URL = "https://api.commandcode.ai/alpha/billing/credits";
var SUBS_URL = "https://api.commandcode.ai/alpha/billing/subscriptions";

var PLAN_LIMITS = {
"individual-go": 10,
"individual-pro": 30,
"individual-max": 150,
"individual-ultra": 300,
"teams-pro": 40,
};

var PLAN_LABELS = {
"individual-go": "Go",
"individual-pro": "Pro",
"individual-max": "Max",
"individual-ultra": "Ultra",
"teams-pro": "Teams Pro",
};

function loadApiKey(ctx) {
if (!ctx.host.fs.exists(SECRETS_FILE)) return null;
try {
var text = ctx.host.fs.readText(SECRETS_FILE);
var parsed = ctx.util.tryParseJson(text);
if (parsed && parsed[SECRETS_KEY]) {
ctx.host.log.info("api key loaded from secrets file");
return parsed[SECRETS_KEY];
}
} catch (e) {
ctx.host.log.warn("secrets file read failed: " + String(e));
}
return null;
}

function fetchCredits(ctx, apiKey) {
return ctx.util.requestJson({
method: "GET",
url: CREDITS_URL,
headers: {
Authorization: "Bearer " + apiKey,
"Content-Type": "application/json",
},
timeoutMs: 15000,
});
}

function fetchSubscriptions(ctx, apiKey) {
return ctx.util.requestJson({
method: "GET",
url: SUBS_URL,
headers: {
Authorization: "Bearer " + apiKey,
"Content-Type": "application/json",
},
timeoutMs: 15000,
});
}

function formatPlanLabel(planId) {
return (
PLAN_LABELS[planId] ||
planId
.split("-")
.map(function (w) {
return w.charAt(0).toUpperCase() + w.slice(1);
})
.join(" ")
);
}

async function probe(ctx) {
var apiKey = loadApiKey(ctx);
if (!apiKey) {
throw "CommandCode not installed. Install CommandCode to get started.";
}
Comment on lines +74 to +78
Comment on lines +75 to +78

var result;
try {
result = fetchCredits(ctx, apiKey);
} catch (e) {
ctx.host.log.error("credits request failed: " + String(e));
throw "Request failed. Check your connection.";
}

var resp = result.resp;
var json = result.json;

if (resp.status === 401 || resp.status === 403) {
throw "Session expired. Re-authenticate in CommandCode.";
}
if (resp.status < 200 || resp.status >= 300) {
var detail = json && json.error && json.error.message ? json.error.message : "";
if (detail) {
ctx.host.log.error("api returned " + resp.status + ": " + detail);
throw detail;
}
ctx.host.log.error("api returned: " + resp.status);
throw "Request failed (HTTP " + resp.status + "). Try again later.";
}

if (!json || !json.credits || typeof json.credits.monthlyCredits !== "number") {
ctx.host.log.error("unexpected credits response structure");
throw "Could not parse usage data.";
}

var remaining = json.credits.monthlyCredits;

var subResult;
try {
subResult = await fetchSubscriptions(ctx, apiKey);
} catch (e) {
ctx.host.log.error("subscription request failed: " + String(e));
throw "Request failed. Check your connection.";
}

var subResp = subResult.resp;
var subJson = subResult.json;

if (subResp.status === 401 || subResp.status === 403) {
throw "Session expired. Re-authenticate in CommandCode.";
}
if (subResp.status < 200 || subResp.status >= 300) {
var detail = subJson && subJson.error && subJson.error.message ? subJson.error.message : "";
if (detail) {
ctx.host.log.error("api returned " + subResp.status + ": " + detail);
throw detail;
}
ctx.host.log.error("api returned: " + subResp.status);
throw "Request failed (HTTP " + subResp.status + "). Try again later.";
}

if (!subJson || !subJson.success || !subJson.data) {
ctx.host.log.error("unexpected subscription response structure");
throw "Could not parse subscription data.";
}

var planId = subJson.data.planId;
var total = PLAN_LIMITS[planId] || 0;
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Emit fallback usage line for unknown plan IDs

When planId is not in PLAN_LIMITS, total becomes 0, the if (planId && total > 0) block is skipped, and the plugin returns lines: []; runtime then converts that into an error line ("no lines returned" in src-tauri/src/plugin_engine/runtime.rs), so users on any new/renamed Command Code plan get a false failure despite successful API responses. Return at least one fallback line (or explicit unsupported-plan error) instead of an empty array.

Useful? React with 👍 / 👎.

var used = Math.max(0, total - remaining);

var resetsAtMs = new Date(subJson.data.currentPeriodEnd).getTime();

var lines = [];
var periodDurationMs =
new Date(subJson.data.currentPeriodEnd).getTime() -
new Date(subJson.data.currentPeriodStart).getTime();
Comment on lines +144 to +149
if (planId && total > 0) {
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Return fallback usage line for unmapped plan IDs

When planId is not present in PLAN_LIMITS, total becomes 0 and this guard skips all output lines, so probe() returns lines: []. In the current runtime, empty lines are converted to an error line ("no lines returned" in src-tauri/src/plugin_engine/runtime.rs), which makes the plugin show an error for valid accounts on newly introduced/unknown plans. This means plan catalog changes on Command Code can silently break usage display until a code update.

Useful? React with 👍 / 👎.

lines.push(
ctx.line.progress({
label: "Monthly Quota",
used: Math.min(100, Math.max(0, Math.round((used / total) * 100))),
limit: 100,
format: { kind: "percent" },
resetsAt: ctx.util.toIso(resetsAtMs),
periodDurationMs,
}),
);
lines.push(
ctx.line.progress({
label: formatPlanLabel(planId),
used: used,
limit: total,
format: { kind: "dollars" },
resetsAt: ctx.util.toIso(resetsAtMs),
periodDurationMs,
}),
);
Comment on lines +145 to +170
Comment on lines +144 to +170
}

return { plan: planId ? formatPlanLabel(planId) : planId, lines: lines };
}

globalThis.__openusage_plugin = { id: "command-code", probe: probe };
})();
21 changes: 21 additions & 0 deletions plugins/command-code/plugin.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
{
"schemaVersion": 1,
"id": "command-code",
"name": "Command Code",
"version": "0.0.1",
"entry": "plugin.js",
"icon": "icon.svg",
"brandColor": "#575757",
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Use Command Code official brand color in manifest

AGENTS.md (repo root) says plugin.json must use the provider’s real brandColor, but this value is set to neutral gray #575757 rather than Command Code’s published brand palette colors, so the provider is misbranded in UI and violates the project’s plugin requirement.

Useful? React with 👍 / 👎.

"links": [
{ "label": "Docs", "url": "https://commandcode.ai/docs" },
{ "label": "Dashboard", "url": "https://commandcode.ai/studio" }
],
"lines": [
{ "type": "progress", "label": "Monthly Quota", "scope": "overview", "primaryOrder": 1 },
{ "type": "progress", "label": "Go", "scope": "overview" },
{ "type": "progress", "label": "Pro", "scope": "overview" },
{ "type": "progress", "label": "Max", "scope": "overview" },
{ "type": "progress", "label": "Ultra", "scope": "overview" },
{ "type": "progress", "label": "Teams Pro", "scope": "overview" }
]
}
Loading
Loading