Official
claude
Claude Code agent provider: runs the Claude Code CLI headless for each account.
The app opens the listing; nothing installs until an agent in your Plugins workspace has read the files and you enable the plugin. In a terminal: cvg install convergence/claude@0.2.0
Permissions in 0.2.0
Files
limits.ts8.1 KB
// Subscription limits (the Rust `limits.rs`).
//
// Two sources say the same thing in different units, so they get two
// decoders. The `rate_limit_event` stream frame reports a 0-1 fraction and
// a unix timestamp in **seconds**; the `get_usage` control response
// reports a 0-100 percentage and an ISO string. Sharing one decoder would
// silently report 1% where 100% was meant.
import type { UsageWindow } from "./types.ts";
import type { UsageLimits, UsageRecovery } from "convergence/protocol";
import { objectOf } from "./wire.ts";
/// Minutes in the windows the CLI names.
function windowMinutes(id: string) {
if (id === "five_hour") return 300;
if (id.startsWith("seven_day")) return 7 * 24 * 60;
return null;
}
/// A readable name for a window id the CLI reports; none for an id it has
/// not documented (an internal code name such as `iguana_necktie`).
function label(id: string) {
switch (id) {
case "five_hour":
return "5 hours";
case "seven_day":
return "7 days";
case "seven_day_opus":
return "7 days (Opus)";
case "seven_day_sonnet":
return "7 days (Sonnet)";
case "seven_day_oauth_apps":
return "7 days (apps)";
case "seven_day_overage_included":
return "7 days (Extra Usage)";
case "overage":
return "Extra Usage";
default:
return null;
}
}
function fromEpochSeconds(value: unknown) {
if (typeof value !== "number" || !Number.isInteger(value) || value <= 0) return null;
return new Date(value * 1000).toISOString();
}
function fromIso(value: unknown) {
if (typeof value !== "string") return null;
const time = Date.parse(value);
return Number.isNaN(time) ? null : new Date(time).toISOString();
}
const number = (value: unknown) => (typeof value === "number" && Number.isFinite(value) ? value : null);
/// A window the CLI reports. One under an undocumented id is left out, as
/// it means nothing to the user, unless it is the one holding them back.
function window(
id: string,
usedPercent: number,
resetsAt: string | null,
blocked: boolean,
name = label(id) ?? (blocked ? "Other limit" : null),
) {
if (name === null) return null;
const out: UsageWindow = { id, label: name, usedPercent: Math.max(0, usedPercent), blocked };
if (resetsAt) out.resetsAt = resetsAt;
const minutes = windowMinutes(id);
if (minutes !== null) out.windowMinutes = minutes;
return out;
}
/// Whether the account may keep working past a rejected window because it
/// pays for extra usage.
function overageAllowed(info: Record<string, unknown>) {
return (
info.overageStatus === "allowed" ||
info.overageStatus === "allowed_warning" ||
info.isUsingOverage === true ||
info.overageInUse === true
);
}
const byId = (a: UsageWindow, b: UsageWindow) => (a.id < b.id ? -1 : a.id > b.id ? 1 : 0);
/// Native status, not utilization, establishes permission. Credits-required
/// rejection needs a billing action and cannot promise a timed reset. Identity
/// is supplied only after reconciling native initialization and auth status.
export function recoveryFromRateLimitInfo(raw: unknown, identity: string | null = null): UsageRecovery {
const info = objectOf(raw);
const credits = info.errorCode === "credits_required";
const allowed = !credits && (info.status === "allowed" || info.status === "allowed_warning" || overageAllowed(info));
const rejected = credits || (info.status === "rejected" && !allowed);
const recovery: UsageRecovery = {
availability: allowed ? "allowed" : rejected ? "blocked" : "unknown",
observedAt: new Date().toISOString(),
source: "claude/rate_limit_event",
reason: credits
? "Included usage requires purchased credits"
: rejected
? "Claude reported a rejected usage window"
: allowed
? "Claude reported allowed usage"
: "Claude did not report usage availability",
};
if (identity) recovery.identity = identity;
if (typeof info.rateLimitType === "string") recovery.scope = info.rateLimitType;
if (rejected && !credits) {
const unified = objectOf(info.unifiedWindows);
const limiting = typeof info.rateLimitType === "string" ? objectOf(unified[info.rateLimitType]) : {};
const reset = fromEpochSeconds(info.resetsAt) ?? fromEpochSeconds(limiting.resetsAt);
// Do not substitute an unrelated window or promise recovery while a
// reported rejected allowance has no reset. Percentages prove no rejection.
const overageReset = info.overageStatus === "rejected" ? fromEpochSeconds(info.overageResetsAt) : reset;
if (reset && overageReset) recovery.resetsAt = reset > overageReset ? reset : overageReset;
}
return recovery;
}
/// Reads a `rate_limit_event` frame's `rate_limit_info`. `unifiedWindows`
/// tracks every window on every observation, while the top-level fields
/// describe only the window that is limiting right now; both are read,
/// because the limiting window may be one `unifiedWindows` does not carry
/// (the per-model weekly buckets).
export function fromRateLimitInfo(raw: unknown, identity: string | null = null): UsageLimits | null {
if (!raw || typeof raw !== "object") return null;
const info = objectOf(raw);
const recovery = recoveryFromRateLimitInfo(raw, identity);
const blocked = recovery.availability === "blocked";
const limiting = typeof info.rateLimitType === "string" ? info.rateLimitType : null;
const windows: UsageWindow[] = [];
const unified = info.unifiedWindows && typeof info.unifiedWindows === "object" ? info.unifiedWindows : {};
for (const [id, rawValues] of Object.entries(unified)) {
const values = objectOf(rawValues);
const used = number(values?.utilization);
if (used === null) continue;
const known = window(id, used * 100, fromEpochSeconds(values.resetsAt), blocked && limiting === id);
if (known) windows.push(known);
}
const used = number(info.utilization);
if (limiting !== null && !windows.some((known) => known.id === limiting) && used !== null) {
const limited = window(limiting, used * 100, fromEpochSeconds(info.resetsAt), blocked);
if (limited) windows.push(limited);
}
windows.sort(byId);
return { windows, recovery };
}
/// Reads the `get_usage` control response. `rate_limits_available` false
/// means the account has no plan limits at all (an API key, Bedrock or
/// Vertex), which is different from "not known yet". Neither response is
/// permission to resume: the endpoint reports meters, not availability.
export function fromUsageResponse(raw: unknown, identity: string | null = null): UsageLimits {
const response = objectOf(raw);
const recovery: UsageRecovery = {
availability: response.rate_limits_available === false ? "unsupported" : "unknown",
observedAt: new Date().toISOString(),
source: "claude/get_usage",
reason:
response.rate_limits_available === false
? "This configuration has no subscription-window reporting"
: "Usage meters do not establish native availability",
};
if (identity) recovery.identity = identity;
const limits = response.rate_limits;
if (!limits || typeof limits !== "object" || Array.isArray(limits)) return { windows: [], recovery };
const windows: UsageWindow[] = [];
for (const [id, rawValues] of Object.entries(limits)) {
const values = objectOf(rawValues);
const used = number(values?.utilization);
if (used === null) continue;
const known = window(id, used, fromIso(values.resets_at), false);
if (known) windows.push(known);
}
const scopedLimits = objectOf(limits).model_scoped;
for (const scoped of Array.isArray(scopedLimits) ? scopedLimits : []) {
const scopedValue = objectOf(scoped);
const name = typeof scopedValue.display_name === "string" ? scopedValue.display_name : null;
const used = number(scopedValue.utilization);
if (name === null || used === null) continue;
const id = `seven_day_${name.toLowerCase().replace(/[^a-z0-9]/g, "_")}`;
windows.push(window(id, used, fromIso(scopedValue.resets_at), false, `7 days (${name})`)!);
}
windows.sort(byId);
// Claude has no credit that reopens a window early; extra usage is a
// spend allowance, not a reset. See NOTES.md.
return { windows, recovery };
}Versions
| Version | Published | Plugin API | Size | Permissions | Status |
|---|---|---|---|---|---|
| 0.2.0latest | Oct 5, 2026 | >=2 <3 | 128.7 KB | 4 permissions | Listed |
No comments yet.