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

  • Provide agents agents.provideMediumAdds agents to the app.Provide the Claude Code agent and pass its tool calls to plugin tools
  • Run named programs processMediumStarts the listed programs.Run the Claude Code CLI (sessions, sign-in, `claude update`), and ask the npm installation that owns it about a newer versionPrograms: claudenpm
  • Read files fs.readMediumReads files in the listed places.Read Claude Code's sessions, settings and skills (in ~/.claude, other accounts' ~/.claude-* folders, the shared skills and the folder you choose in Settings), the project's .claude folder, the administrator's skill policy, and once, what the previous provider keptPlaces: ~/.claude/**~/.claude*/**~/.agents/skills/**the open workspaceits own data folder/Library/Application Support/ClaudeCode/managed-settings.json/etc/claude-code/managed-settings.json${settings.configDir}
  • Environment variables envMediumReads the listed environment variables.Find Claude Code's configuration directory, and the launch settings you set in CLAUDE_* variables (extra arguments, MCP servers, setting sources, appended system prompt, extra folders)Variables: HOMECLAUDE_CONFIG_DIRCLAUDE_EXTRA_ARGSCLAUDE_MCP_CONFIGCLAUDE_STRICT_MCP_CONFIGCLAUDE_SETTING_SOURCESCLAUDE_APPEND_SYSTEM_PROMPTCLAUDE_ADD_DIRS

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

VersionPublishedPlugin APISizePermissionsStatus
0.2.0latestOct 5, 2026>=2 <3128.7 KB4 permissionsListed

Reviews and comments

0 threads · 0 reviews

No comments yet.