@vibe-cafe/vibe-usage 0.13.0 → 0.13.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +1 -0
- package/package.json +1 -1
- package/src/quotas/providers/opencode-go.js +4 -1
- package/src/quotas/schema.js +20 -0
package/README.md
CHANGED
|
@@ -62,6 +62,7 @@ npx @vibe-cafe/vibe-usage help --all # Full help (plain `help` shows the short
|
|
|
62
62
|
|
|
63
63
|
The versioned `quota` JSON contract is designed for local desktop clients. `discover` only checks ordinary app, config-directory, and executable presence signals: it does not open credentials or use the network. `fetch` invokes only products explicitly named with `--product`, and one provider failure does not prevent results for the others.
|
|
64
64
|
|
|
65
|
+
- A quota result may carry a machine-readable `emptyReason` (`limitReached` / `noWindow` / `notEntitled` / `sessionWithoutPlanLimits`) alongside the human `message`; it is additive within schema v1, so clients that do not know it ignore it, and it says *why* there was no window instead of leaving the caller to parse prose. OpenCode Go sets `notEntitled` for a key without the Go subscription.
|
|
65
66
|
- **Kimi Code** reads the official OAuth file from `$KIMI_CODE_HOME/credentials/kimi-code.json` (otherwise `~/.kimi-code/credentials/kimi-code.json`) and falls back to the legacy `$KIMI_SHARE_DIR` / `~/.kimi` location — the first login that exists wins. It calls `https://api.kimi.com/coding/v1/usages` and supports both the current 2.x response and the legacy quota response. When the short-lived access token is close to expiry, it uses Kimi's standard OAuth refresh flow and atomically rotates the official credential with owner-only permissions. Refreshes are serialized across Vibe Usage processes and re-check the file before writing so a concurrent Kimi CLI refresh wins safely.
|
|
66
67
|
- **ZCode / GLM Coding Plan** accepts only a caller-supplied regional key: `BIGMODEL_API_KEY` calls the domestic `https://open.bigmodel.cn/api/monitor/usage/quota/limit`, while the existing `Z_AI_API_KEY` keeps using `https://api.z.ai/api/monitor/usage/quota/limit`. If both are present, the explicitly named BigModel key wins. It does not read ZCode's private OAuth state.
|
|
67
68
|
- **Grok** reads at most the final 2 MiB of the official CLI's ordinary `$GROK_HOME/logs/unified.jsonl` (default `~/.grok/logs/unified.jsonl`). It accepts only the structured `billing: fetched credits config` event and projects the current utilization, period bounds, subscription tier, and event timestamp. It performs no network request, reads no credentials, and never returns or retains other log fields.
|
package/package.json
CHANGED
|
@@ -184,10 +184,13 @@ export async function fetchOpenCodeGoQuota({
|
|
|
184
184
|
}), token);
|
|
185
185
|
}
|
|
186
186
|
if (response.status === 403) {
|
|
187
|
-
// A valid OpenCode key without an active Go subscription.
|
|
187
|
+
// A valid OpenCode key without an active Go subscription. The reason
|
|
188
|
+
// travels machine-readably so a client can say "not subscribed" instead
|
|
189
|
+
// of showing a neutral empty state.
|
|
188
190
|
return attachCacheScope(quotaResult({
|
|
189
191
|
id: PRODUCT_ID,
|
|
190
192
|
status: 'no_data',
|
|
193
|
+
emptyReason: 'notEntitled',
|
|
191
194
|
message: 'OpenCode Go subscription is not active',
|
|
192
195
|
fetchedAt: now,
|
|
193
196
|
}), token);
|
package/src/quotas/schema.js
CHANGED
|
@@ -15,6 +15,19 @@ export const FETCHABLE_QUOTA_PRODUCT_IDS = Object.freeze([
|
|
|
15
15
|
'opencode-go',
|
|
16
16
|
]);
|
|
17
17
|
|
|
18
|
+
/**
|
|
19
|
+
* Machine-readable "why there was no window" values, shared with the desktop
|
|
20
|
+
* clients' `EmptyReason` (macOS `RateLimit.swift`) and additive within schema
|
|
21
|
+
* v1: a client that does not know the field ignores it, and a client that does
|
|
22
|
+
* can render the reason instead of a neutral empty state.
|
|
23
|
+
*/
|
|
24
|
+
export const QUOTA_EMPTY_REASONS = Object.freeze([
|
|
25
|
+
'limitReached',
|
|
26
|
+
'noWindow',
|
|
27
|
+
'notEntitled',
|
|
28
|
+
'sessionWithoutPlanLimits',
|
|
29
|
+
]);
|
|
30
|
+
|
|
18
31
|
const FETCH_STATUSES = new Set([
|
|
19
32
|
'ok',
|
|
20
33
|
'no_data',
|
|
@@ -129,6 +142,7 @@ export function quotaResult({
|
|
|
129
142
|
dataAsOf = fetchedAt,
|
|
130
143
|
message,
|
|
131
144
|
source = 'live',
|
|
145
|
+
emptyReason,
|
|
132
146
|
}) {
|
|
133
147
|
if (!FETCHABLE_QUOTA_PRODUCT_IDS.includes(id)) {
|
|
134
148
|
throw new TypeError(`unsupported quota product: ${id}`);
|
|
@@ -147,6 +161,12 @@ export function quotaResult({
|
|
|
147
161
|
if (normalizedDataAsOf) result.dataAsOf = normalizedDataAsOf;
|
|
148
162
|
if (typeof planLabel === 'string' && planLabel.trim()) result.planLabel = planLabel.trim();
|
|
149
163
|
if (typeof message === 'string' && message.trim()) result.message = message.trim();
|
|
164
|
+
if (emptyReason !== undefined && emptyReason !== null) {
|
|
165
|
+
if (!QUOTA_EMPTY_REASONS.includes(emptyReason)) {
|
|
166
|
+
throw new TypeError(`invalid quota emptyReason: ${emptyReason}`);
|
|
167
|
+
}
|
|
168
|
+
result.emptyReason = emptyReason;
|
|
169
|
+
}
|
|
150
170
|
return result;
|
|
151
171
|
}
|
|
152
172
|
|