CC Switch

在 CC Switch 中配置 Fast AI,并显示当前 API Key 可见权益的多套餐用量。

适合人群
多客户端用户
你同时用 Claude Code、Codex、OpenCode 等多个工具,想统一管理配置。
Base URL
供应商 Base URL 建议直接填写到 `/v1`。
查询接口
/v1/usage
适合显示当前 API Key 可见范围内的权益、剩余额度和多套餐信息。

官方文档

特点与使用场景

特点
跨平台桌面工具,适合集中管理多个 AI CLI 的 provider、skills、MCP、用量脚本和切换逻辑。
适用场景
你不想频繁手改各家客户端配置文件,或者需要给团队提供统一、可视化的供应商切换入口。

第 1 步:安装 CC Switch

1
macOS
官方 Releases 页面提供 Homebrew 安装方式。
Homebrew
brew tap farion1231/ccswitch
brew install --cask cc-switch
2
Windows / Linux
到 Releases 页面下载对应的 MSI、便携版、.deb.rpm 安装包。
3
验证安装
启动 CC Switch,确认主界面能正常打开并进入 Provider 管理。

第 2 步:先把 Fast AI 供应商配置好

先在 CC Switch 中创建或编辑 Fast AI 供应商,把基础请求配置补齐。

为什么 Base URL 要写到 /v1
下面的脚本会直接拼接 {{baseUrl}}/usage。所以 Base URL 请写成 https://api.fastai.run/v1,不要额外再带结尾 /,否则可能变成双斜杠。
1
填写 Base URL 和 API Key
Base URL 直接填写 Fast AI 的 OpenAI 兼容入口,API Key 使用你在控制台创建的 Key。
供应商配置
Base URL: https://api.fastai.run/v1
API Key: sk-fastai.run-...
2
保存供应商
先保存一次供应商配置,再进入“配置用量查询”。这样脚本测试和正式查询都能拿到同一套 Base URL / API Key。

第 3 步:配置用量查询脚本

在“配置用量查询”中选择“通用模板”,然后粘贴下面这份脚本。

这份脚本会显示什么
它会优先读取 /v1/usage 返回的 all_entitlements,把每个权益都转成 CC Switch 的套餐项;当前绑定、限制类型、到期时间和失效状态也会一起显示。如果没有 all_entitlements,它会回退到顶层兼容字段。
CC Switch 用量脚本
({
  request: {
    url: "{{baseUrl}}/usage",
    method: "GET",
    headers: {
      "Authorization": "Bearer {{apiKey}}",
      "User-Agent": "cc-switch/1.0"
    }
  },
  extractor: function (response) {
    function num(value) {
      return typeof value === "number" && Number.isFinite(value) ? value : undefined;
    }

    function pickLimit(entitlement) {
      if (!entitlement || entitlement.type === "pay_as_you_go") return null;

      const limitedBy = Array.isArray(entitlement.effective_remaining?.limited_by)
        ? entitlement.effective_remaining.limited_by
        : [];
      const first = limitedBy[0];

      if (first === "token_quota") return entitlement.token_quota || null;
      if (first === "rolling_5h") return entitlement.rolling_5h || null;
      if (first === "rolling_7d") return entitlement.rolling_7d || null;
      if (first === "rolling_4w") return entitlement.rolling_4w || null;

      const fallbacks = [
        entitlement.token_quota,
        entitlement.rolling_5h,
        entitlement.rolling_7d,
        entitlement.rolling_4w
      ];

      for (const item of fallbacks) {
        if (item && item.unlimited !== true && typeof item.remaining === "number") {
          return item;
        }
      }

      return null;
    }

    function limitLabel(name) {
      if (name === "balance") return "余额";
      if (name === "token_quota") return "总额度";
      if (name === "rolling_5h") return "5小时窗口";
      if (name === "rolling_7d") return "7天窗口";
      if (name === "rolling_4w") return "4周窗口";
      if (name === "expired") return "已过期";
      if (name === "inactive") return "未激活";
      return name || "";
    }

    function buildExtra(entitlement) {
      const parts = [];
      const limitedBy = Array.isArray(entitlement.effective_remaining?.limited_by)
        ? entitlement.effective_remaining.limited_by
        : [];

      if (entitlement.bound_to_current_key) parts.push("当前绑定");
      if (entitlement.status) parts.push("状态: " + entitlement.status);
      if (limitedBy.length > 0) parts.push("限制: " + limitedBy.map(limitLabel).join(", "));
      if (entitlement.expires_at) parts.push("到期: " + entitlement.expires_at);

      return parts.join(" | ") || undefined;
    }

    const entitlements = Array.isArray(response?.all_entitlements)
      ? response.all_entitlements
      : [];

    if (entitlements.length > 0) {
      return entitlements.map(function (entitlement, index) {
        const effective = entitlement.effective_remaining || {};
        const limit = pickLimit(entitlement);

        const result = {
          planName: entitlement.plan_name || ("权益 " + (index + 1)),
          isValid: entitlement.is_active !== false,
          invalidMessage: entitlement.is_active === false
            ? (entitlement.is_expired ? "权益已过期" : (entitlement.status || "权益不可用"))
            : undefined,
          remaining:
            num(effective.value) ??
            num(limit?.remaining) ??
            num(entitlement.balance?.remaining_usd) ??
            0,
          unit: String(
            effective.unit ||
            limit?.unit ||
            entitlement.quota_unit ||
            response?.unit ||
            response?.quota?.unit ||
            "USD"
          ).toUpperCase(),
          extra: buildExtra(entitlement)
        };

        if (limit && limit.unlimited !== true) {
          result.used = num(limit.used);
          result.total = num(limit.limit);
        } else if (entitlement.type === "pay_as_you_go") {
          const used = num(entitlement.api_usage?.actual_cost);
          if (used !== undefined) result.used = used;
        }

        return result;
      });
    }

    return {
      isValid: response?.is_active ?? response?.isValid ?? true,
      invalidMessage: response?.invalidMessage || response?.invalid_message,
      remaining: response?.remaining ?? response?.quota?.remaining ?? response?.balance ?? 0,
      used: response?.used ?? response?.quota?.used,
      total: response?.total ?? response?.quota?.limit,
      unit: String(response?.unit ?? response?.quota?.unit ?? "USD").toUpperCase(),
      planName: response?.planName ?? response?.plan_name ?? "当前 Key",
      extra: response?.extra
    };
  }
})

第 4 步:测试并确认展示效果

脚本保存后,点击“测试脚本”或等待自动查询,就可以在供应商卡片中看到套餐列表。

  • 只有一个权益时,卡片会显示单条数据。
  • 返回多个权益时,CC Switch 会把它们展开成多套餐列表。
  • 如果返回中包含未激活或已过期权益,脚本会保留它们,并在卡片上标记不可用原因。

常见问题

Unknown balance provider
说明模板类型选错了。请切换到“通用模板”,不要使用旧的 balance 模板。
relative URL without a base
说明脚本测试时没有拿到可用的 Base URL。先确认供应商 Base URL 已填写为完整地址,并先保存供应商配置。
只显示一个套餐
先确认脚本返回的是数组,其次确认 `/v1/usage` 的 `all_entitlements` 中已经包含多个权益。
按量付费没有出现
先确认 `/v1/usage` 的 `all_entitlements` 里是否真的返回了按量付费权益;这版脚本不会再额外过滤它,但上游没返回时也不会凭空显示。
请求地址多了双斜杠
检查供应商 Base URL 末尾是否多写了 `/`。这版脚本是直接拼接 `{{baseUrl}}/usage`,建议固定填写成 `https://api.fastai.run/v1`。