zcode-acp-server 0.69.4 → 0.71.0

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.
Files changed (60) hide show
  1. package/README.md +1 -1
  2. package/dist/bin/quota.d.ts +1 -1
  3. package/dist/bin/quota.d.ts.map +1 -1
  4. package/dist/bin/quota.js +23 -5
  5. package/dist/bin/quota.js.map +1 -1
  6. package/dist/code-fingerprint.json +2 -2
  7. package/dist/config/user-config.d.ts +8 -0
  8. package/dist/config/user-config.d.ts.map +1 -1
  9. package/dist/config/user-config.js +2 -0
  10. package/dist/config/user-config.js.map +1 -1
  11. package/dist/handlers/account.d.ts +43 -4
  12. package/dist/handlers/account.d.ts.map +1 -1
  13. package/dist/handlers/account.js +79 -6
  14. package/dist/handlers/account.js.map +1 -1
  15. package/dist/index.d.ts.map +1 -1
  16. package/dist/index.js +4 -3
  17. package/dist/index.js.map +1 -1
  18. package/dist/quota/cliproxy/aggregate.d.ts +48 -0
  19. package/dist/quota/cliproxy/aggregate.d.ts.map +1 -0
  20. package/dist/quota/cliproxy/aggregate.js +118 -0
  21. package/dist/quota/cliproxy/aggregate.js.map +1 -0
  22. package/dist/quota/cliproxy/client.d.ts +45 -0
  23. package/dist/quota/cliproxy/client.d.ts.map +1 -0
  24. package/dist/quota/cliproxy/client.js +134 -0
  25. package/dist/quota/cliproxy/client.js.map +1 -0
  26. package/dist/quota/cliproxy/config.d.ts +24 -0
  27. package/dist/quota/cliproxy/config.d.ts.map +1 -0
  28. package/dist/quota/cliproxy/config.js +27 -0
  29. package/dist/quota/cliproxy/config.js.map +1 -0
  30. package/dist/quota/cliproxy/format.d.ts +18 -0
  31. package/dist/quota/cliproxy/format.d.ts.map +1 -0
  32. package/dist/quota/cliproxy/format.js +71 -0
  33. package/dist/quota/cliproxy/format.js.map +1 -0
  34. package/dist/quota/cliproxy/index.d.ts +34 -0
  35. package/dist/quota/cliproxy/index.d.ts.map +1 -0
  36. package/dist/quota/cliproxy/index.js +110 -0
  37. package/dist/quota/cliproxy/index.js.map +1 -0
  38. package/dist/quota/cliproxy/parse.d.ts +46 -0
  39. package/dist/quota/cliproxy/parse.d.ts.map +1 -0
  40. package/dist/quota/cliproxy/parse.js +176 -0
  41. package/dist/quota/cliproxy/parse.js.map +1 -0
  42. package/dist/quota/cliproxy/types.d.ts +82 -0
  43. package/dist/quota/cliproxy/types.d.ts.map +1 -0
  44. package/dist/quota/cliproxy/types.js +33 -0
  45. package/dist/quota/cliproxy/types.js.map +1 -0
  46. package/dist/quota/combined.d.ts +5 -1
  47. package/dist/quota/combined.d.ts.map +1 -1
  48. package/dist/quota/combined.js +24 -6
  49. package/dist/quota/combined.js.map +1 -1
  50. package/dist/quota/format.d.ts +9 -0
  51. package/dist/quota/format.d.ts.map +1 -1
  52. package/dist/quota/format.js +19 -0
  53. package/dist/quota/format.js.map +1 -1
  54. package/dist/quota/live.d.ts.map +1 -1
  55. package/dist/quota/live.js +12 -5
  56. package/dist/quota/live.js.map +1 -1
  57. package/dist/remote/hub-server.d.ts.map +1 -1
  58. package/dist/remote/hub-server.js +7 -3
  59. package/dist/remote/hub-server.js.map +1 -1
  60. package/package.json +1 -1
@@ -0,0 +1,118 @@
1
+ /**
2
+ * Cross-account aggregation for the CLIProxy card.
3
+ *
4
+ * Each antigravity subscription has its own quota pools; CLIProxy rotates
5
+ * requests across them, so the actionable numbers are POOL-WIDE: total used
6
+ * over total capacity (the average of the per-account used fractions — the
7
+ * sum divided by the number of subscriptions reporting that family+window),
8
+ * plus the earliest reset among the contributing buckets (when capacity
9
+ * starts coming back). The per-account detail stays in
10
+ * {@link ./types.js CpAccountQuota} for ACP consumers.
11
+ */
12
+ /**
13
+ * Aggregate every account's families into pool-wide rows. Families are keyed
14
+ * by name (accounts report the same family set); a window reported by only
15
+ * some accounts still divides by the reporting count — a subscription that
16
+ * reports nothing for a family contributes neither usage nor capacity.
17
+ */
18
+ export function aggregateCpFamilies(accounts) {
19
+ // family name → window → {usedSum, count, resetAt?}
20
+ const families = new Map();
21
+ for (const account of accounts) {
22
+ for (const model of account.models) {
23
+ let windows = families.get(model.name);
24
+ if (!windows) {
25
+ windows = new Map();
26
+ families.set(model.name, windows);
27
+ }
28
+ for (const w of model.windows) {
29
+ const agg = windows.get(w.window) ?? { usedSum: 0, count: 0 };
30
+ agg.usedSum += w.used;
31
+ agg.count += 1;
32
+ if (w.resetAt !== undefined && (agg.resetAt === undefined || w.resetAt < agg.resetAt)) {
33
+ agg.resetAt = w.resetAt;
34
+ }
35
+ windows.set(w.window, agg);
36
+ }
37
+ }
38
+ }
39
+ const out = [];
40
+ for (const [name, windows] of families) {
41
+ const rows = [];
42
+ for (const [window, agg] of windows) {
43
+ rows.push({
44
+ window,
45
+ used: Math.min(1, agg.usedSum / agg.count),
46
+ ...(agg.resetAt !== undefined && { resetAt: agg.resetAt }),
47
+ });
48
+ }
49
+ // Tightest window first; ties keep the 5h-then-week display order.
50
+ rows.sort((a, b) => b.used - a.used || windowOrder(a.window) - windowOrder(b.window));
51
+ out.push({ name, windows: rows });
52
+ }
53
+ // Most-used family leads the card.
54
+ return out.sort((a, b) => maxUsed(b) - maxUsed(a));
55
+ }
56
+ function windowOrder(window) {
57
+ return window === "5h" ? 0 : 1;
58
+ }
59
+ function maxUsed(family) {
60
+ return family.windows.length > 0 ? family.windows[0].used : 0;
61
+ }
62
+ /**
63
+ * The pool-wide binding row across ALL families: the single (window, used,
64
+ * earliest reset) the dock and the ACP `combined` field report. The window is
65
+ * whichever is fullest (max aggregated used over every family×window); the
66
+ * reset is the earliest among that window's family rows — capacity starts
67
+ * returning then even though the pool refills gradually.
68
+ */
69
+ export function bindingCpWindow(families) {
70
+ let binding = null;
71
+ for (const family of families) {
72
+ for (const w of family.windows) {
73
+ if (!binding || w.used > binding.used)
74
+ binding = { ...w };
75
+ }
76
+ }
77
+ if (!binding)
78
+ return null;
79
+ // Earliest reset across every family reporting the SAME window.
80
+ let earliest;
81
+ for (const family of families) {
82
+ for (const w of family.windows) {
83
+ if (w.window === binding.window && w.resetAt !== undefined) {
84
+ earliest = earliest === undefined ? w.resetAt : Math.min(earliest, w.resetAt);
85
+ }
86
+ }
87
+ }
88
+ return { ...binding, ...(earliest !== undefined && { resetAt: earliest }) };
89
+ }
90
+ /**
91
+ * Per-window pool-wide rows for compact consumers (ACP `combined`): for each
92
+ * window, the fullest family's aggregated used plus that window's earliest
93
+ * reset. Families are separate pools, so a per-window sum would double-count
94
+ * capacity; the fullest family is the binding constraint for that window.
95
+ */
96
+ export function combinedCpWindows(families) {
97
+ const rows = new Map();
98
+ const earliest = {};
99
+ for (const family of families) {
100
+ for (const w of family.windows) {
101
+ if (w.resetAt !== undefined) {
102
+ earliest[w.window] =
103
+ earliest[w.window] === undefined ? w.resetAt : Math.min(earliest[w.window], w.resetAt);
104
+ }
105
+ const prev = rows.get(w.window);
106
+ if (!prev || w.used > prev.used)
107
+ rows.set(w.window, { ...w });
108
+ }
109
+ }
110
+ const out = [...rows.values()].map((w) => ({
111
+ window: w.window,
112
+ used: w.used,
113
+ ...(earliest[w.window] !== undefined && { resetAt: earliest[w.window] }),
114
+ }));
115
+ out.sort((a, b) => b.used - a.used || windowOrder(a.window) - windowOrder(b.window));
116
+ return out;
117
+ }
118
+ //# sourceMappingURL=aggregate.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"aggregate.js","sourceRoot":"","sources":["../../../src/quota/cliproxy/aggregate.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,45 @@
1
+ /**
2
+ * CLIProxyAPI management HTTP client.
3
+ *
4
+ * Two calls, both Bearer-authed with the management key:
5
+ * - credentials listing (v8: `/v8/management/credentials`; the v0 servers
6
+ * that still exist answer `/v0/management/auth-files` — probed once, the
7
+ * working spelling cached for the process);
8
+ * - `api-call` — the credential-scoped upstream relay the management web UI
9
+ * itself uses for Gemini quota.
10
+ *
11
+ * No retries beyond the URL-candidate ladder: a remote quota card must fail
12
+ * fast into `unavailable`, not hang the combined view.
13
+ */
14
+ import type { CpResponse } from "./types.js";
15
+ /**
16
+ * Upstream quota endpoints CPAMC itself tries, in order
17
+ * (Cli-Proxy-API-Management-Center `constants.ts`): the primary internal
18
+ * quota API first, sandbox and bare variants as fallbacks.
19
+ */
20
+ export declare const QUOTA_SUMMARY_URLS: readonly ["https://daily-cloudcode-pa.googleapis.com/v1internal:retrieveUserQuotaSummary", "https://daily-cloudcode-pa.sandbox.googleapis.com/v1internal:retrieveUserQuotaSummary", "https://cloudcode-pa.googleapis.com/v1internal:retrieveUserQuotaSummary"];
21
+ /** Hook: forget the cached route spellings (tests, and watch-mode ticks so a
22
+ * server upgrade mid-watch re-probes instead of sticking to a stale route). */
23
+ export declare function resetRouteCache(): void;
24
+ /**
25
+ * List the proxy's credential entries. 401/403 surfaces to the caller as
26
+ * `auth_error`; other statuses as `unavailable`.
27
+ */
28
+ export declare function fetchCredentials(base: string, key: string, fetchImpl?: typeof globalThis.fetch): Promise<CpResponse>;
29
+ /**
30
+ * Relay `retrieveUserQuotaSummary` for one account. `$TOKEN$` is substituted
31
+ * by the proxy with that credential's OAuth token — the key never sees the
32
+ * token, and we never see it either (it stays server-side).
33
+ *
34
+ * The upstream URL ladder walks on the UPSTREAM status carried inside the
35
+ * relay envelope (CPAMC's semantics): the relay itself answers HTTP 200 with
36
+ * the upstream result embedded, so a 403/404 upstream means this URL variant
37
+ * is not the one this account's client is recognized on — the next variant
38
+ * may still answer. Only an upstream 2xx satisfies the ladder.
39
+ *
40
+ * @returns the relay response (its `body` is the upstream JSON as a STRING),
41
+ * or null when every URL candidate failed (relay/network error, or
42
+ * no variant produced an upstream 2xx).
43
+ */
44
+ export declare function fetchQuotaSummary(base: string, key: string, authIndex: string, projectId: string | undefined, fetchImpl?: typeof globalThis.fetch): Promise<CpResponse | null>;
45
+ //# sourceMappingURL=client.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"client.d.ts","sourceRoot":"","sources":["../../../src/quota/cliproxy/client.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AAGH,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,YAAY,CAAC;AAkB7C;;;;GAIG;AACH,eAAO,MAAM,kBAAkB,gQAIrB,CAAC;AAMX;gFACgF;AAChF,wBAAgB,eAAe,IAAI,IAAI,CAGtC;AA4CD;;;GAGG;AACH,wBAAsB,gBAAgB,CACpC,IAAI,EAAE,MAAM,EACZ,GAAG,EAAE,MAAM,EACX,SAAS,GAAE,OAAO,UAAU,CAAC,KAAwB,GACpD,OAAO,CAAC,UAAU,CAAC,CAWrB;AAED;;;;;;;;;;;;;;GAcG;AACH,wBAAsB,iBAAiB,CACrC,IAAI,EAAE,MAAM,EACZ,GAAG,EAAE,MAAM,EACX,SAAS,EAAE,MAAM,EACjB,SAAS,EAAE,MAAM,GAAG,SAAS,EAC7B,SAAS,GAAE,OAAO,UAAU,CAAC,KAAwB,GACpD,OAAO,CAAC,UAAU,GAAG,IAAI,CAAC,CAoC5B"}
@@ -0,0 +1,134 @@
1
+ /**
2
+ * CLIProxyAPI management HTTP client.
3
+ *
4
+ * Two calls, both Bearer-authed with the management key:
5
+ * - credentials listing (v8: `/v8/management/credentials`; the v0 servers
6
+ * that still exist answer `/v0/management/auth-files` — probed once, the
7
+ * working spelling cached for the process);
8
+ * - `api-call` — the credential-scoped upstream relay the management web UI
9
+ * itself uses for Gemini quota.
10
+ *
11
+ * No retries beyond the URL-candidate ladder: a remote quota card must fail
12
+ * fast into `unavailable`, not hang the combined view.
13
+ */
14
+ import { relayUpstreamStatus } from "./parse.js";
15
+ const TIMEOUT_MS = 10_000;
16
+ /** Both route spellings, tried in order; the winner is cached per process. */
17
+ const CREDENTIALS_ROUTES = ["/v8/management/credentials", "/v0/management/auth-files"];
18
+ // The v8 relay lives under `/requests/` (official docs/management-api-v8.md);
19
+ // a top-level `/v8/management/api-call` does not exist and answers 404.
20
+ const API_CALL_ROUTES = ["/v8/management/requests/api-call", "/v0/management/api-call"];
21
+ /**
22
+ * Antigravity accounts' client identity (CPAMC `constants.ts`
23
+ * ANTIGRAVITY_USER_AGENT). The quota endpoint gates on it: without the
24
+ * antigravity CLI UA, Google answers 403 "You do not have a valid license of
25
+ * this product" even for a perfectly healthy account (verified live 2026-10).
26
+ */
27
+ const ANTIGRAVITY_USER_AGENT = "antigravity/cli/1.0.13 (aidev_client; os_type=darwin; arch=arm64)";
28
+ /**
29
+ * Upstream quota endpoints CPAMC itself tries, in order
30
+ * (Cli-Proxy-API-Management-Center `constants.ts`): the primary internal
31
+ * quota API first, sandbox and bare variants as fallbacks.
32
+ */
33
+ export const QUOTA_SUMMARY_URLS = [
34
+ "https://daily-cloudcode-pa.googleapis.com/v1internal:retrieveUserQuotaSummary",
35
+ "https://daily-cloudcode-pa.sandbox.googleapis.com/v1internal:retrieveUserQuotaSummary",
36
+ "https://cloudcode-pa.googleapis.com/v1internal:retrieveUserQuotaSummary",
37
+ ];
38
+ /** Route-spelling memory (set on first success; 404 keeps probing). */
39
+ let credentialsRoute = null;
40
+ let apiCallRoute = null;
41
+ /** Hook: forget the cached route spellings (tests, and watch-mode ticks so a
42
+ * server upgrade mid-watch re-probes instead of sticking to a stale route). */
43
+ export function resetRouteCache() {
44
+ credentialsRoute = null;
45
+ apiCallRoute = null;
46
+ }
47
+ function authHeaders(key) {
48
+ return {
49
+ Authorization: `Bearer ${key}`,
50
+ "X-Management-Key": key,
51
+ Accept: "application/json",
52
+ };
53
+ }
54
+ async function fetchOnce(url, key, init, fetchImpl) {
55
+ const resp = await fetchImpl(url, {
56
+ ...init,
57
+ headers: { ...authHeaders(key), ...init.headers },
58
+ signal: AbortSignal.timeout(TIMEOUT_MS),
59
+ });
60
+ return { status: resp.status, text: await resp.text() };
61
+ }
62
+ /** Probe the route spellings until one answers non-404 (cached afterwards). */
63
+ async function fetchRouted(base, key, routes, cached, init, fetchImpl) {
64
+ if (cached) {
65
+ return { route: cached, resp: await fetchOnce(`${base}${cached}`, key, init, fetchImpl) };
66
+ }
67
+ let last = { status: 404, text: "" };
68
+ for (const route of routes) {
69
+ last = await fetchOnce(`${base}${route}`, key, init, fetchImpl);
70
+ if (last.status !== 404)
71
+ return { route, resp: last };
72
+ }
73
+ return { route: null, resp: last };
74
+ }
75
+ /**
76
+ * List the proxy's credential entries. 401/403 surfaces to the caller as
77
+ * `auth_error`; other statuses as `unavailable`.
78
+ */
79
+ export async function fetchCredentials(base, key, fetchImpl = globalThis.fetch) {
80
+ const { route, resp } = await fetchRouted(base, key, CREDENTIALS_ROUTES, credentialsRoute, { method: "GET" }, fetchImpl);
81
+ if (route)
82
+ credentialsRoute = route;
83
+ return resp;
84
+ }
85
+ /**
86
+ * Relay `retrieveUserQuotaSummary` for one account. `$TOKEN$` is substituted
87
+ * by the proxy with that credential's OAuth token — the key never sees the
88
+ * token, and we never see it either (it stays server-side).
89
+ *
90
+ * The upstream URL ladder walks on the UPSTREAM status carried inside the
91
+ * relay envelope (CPAMC's semantics): the relay itself answers HTTP 200 with
92
+ * the upstream result embedded, so a 403/404 upstream means this URL variant
93
+ * is not the one this account's client is recognized on — the next variant
94
+ * may still answer. Only an upstream 2xx satisfies the ladder.
95
+ *
96
+ * @returns the relay response (its `body` is the upstream JSON as a STRING),
97
+ * or null when every URL candidate failed (relay/network error, or
98
+ * no variant produced an upstream 2xx).
99
+ */
100
+ export async function fetchQuotaSummary(base, key, authIndex, projectId, fetchImpl = globalThis.fetch) {
101
+ for (const url of QUOTA_SUMMARY_URLS) {
102
+ let resp = null;
103
+ try {
104
+ const routed = await fetchRouted(base, key, API_CALL_ROUTES, apiCallRoute, {
105
+ method: "POST",
106
+ headers: { "Content-Type": "application/json" },
107
+ body: JSON.stringify({
108
+ auth_index: authIndex,
109
+ method: "POST",
110
+ url,
111
+ header: {
112
+ Authorization: "Bearer $TOKEN$",
113
+ "Content-Type": "application/json",
114
+ "User-Agent": ANTIGRAVITY_USER_AGENT,
115
+ },
116
+ data: JSON.stringify(projectId !== undefined ? { project: projectId } : {}),
117
+ }),
118
+ }, fetchImpl);
119
+ if (routed.route)
120
+ apiCallRoute = routed.route;
121
+ resp = routed.resp;
122
+ }
123
+ catch {
124
+ resp = null; // network/timeout on this variant — try the next
125
+ }
126
+ if (resp?.status !== 200)
127
+ continue;
128
+ const upstream = relayUpstreamStatus(resp.text);
129
+ if (upstream >= 200 && upstream < 300)
130
+ return resp;
131
+ }
132
+ return null;
133
+ }
134
+ //# sourceMappingURL=client.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"client.js","sourceRoot":"","sources":["../../../src/quota/cliproxy/client.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,24 @@
1
+ /**
2
+ * CLIProxyAPI credential discovery — the same convention as the Ollama card:
3
+ * config file first (`~/.config/zcode-acp/config.json` `quota.cliproxyUrl` +
4
+ * `quota.cliproxyManagementKey`), env vars as a full fallback
5
+ * (`CLIPROXY_URL` / `CLIPROXY_MANAGEMENT_KEY`).
6
+ *
7
+ * A missing URL or key yields `not_configured` (the section is silently
8
+ * skipped in `all` mode). An `http://` URL to a non-loopback host is refused
9
+ * at query time — see {@link loadCpConfig}.
10
+ */
11
+ export declare const ENV_URL = "CLIPROXY_URL";
12
+ export declare const ENV_KEY = "CLIPROXY_MANAGEMENT_KEY";
13
+ export interface CpConfig {
14
+ /** Base URL, no trailing slash/path (e.g. `https://cp.example.com:8317`). */
15
+ url: string;
16
+ /** Management key (`remote-management.secret-key`). */
17
+ key: string;
18
+ }
19
+ /**
20
+ * Resolve the endpoint + key. Blank strings count as absent. The URL gets a
21
+ * trailing-slash trim so `https://host/` still works.
22
+ */
23
+ export declare function loadCpConfig(): CpConfig | null;
24
+ //# sourceMappingURL=config.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"config.d.ts","sourceRoot":"","sources":["../../../src/quota/cliproxy/config.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AAMH,eAAO,MAAM,OAAO,iBAAiB,CAAC;AACtC,eAAO,MAAM,OAAO,4BAA4B,CAAC;AAEjD,MAAM,WAAW,QAAQ;IACvB,6EAA6E;IAC7E,GAAG,EAAE,MAAM,CAAC;IACZ,uDAAuD;IACvD,GAAG,EAAE,MAAM,CAAC;CACb;AAED;;;GAGG;AACH,wBAAgB,YAAY,IAAI,QAAQ,GAAG,IAAI,CAM9C"}
@@ -0,0 +1,27 @@
1
+ /**
2
+ * CLIProxyAPI credential discovery — the same convention as the Ollama card:
3
+ * config file first (`~/.config/zcode-acp/config.json` `quota.cliproxyUrl` +
4
+ * `quota.cliproxyManagementKey`), env vars as a full fallback
5
+ * (`CLIPROXY_URL` / `CLIPROXY_MANAGEMENT_KEY`).
6
+ *
7
+ * A missing URL or key yields `not_configured` (the section is silently
8
+ * skipped in `all` mode). An `http://` URL to a non-loopback host is refused
9
+ * at query time — see {@link loadCpConfig}.
10
+ */
11
+ import process from "node:process";
12
+ import { loadUserConfig } from "../../config/user-config.js";
13
+ export const ENV_URL = "CLIPROXY_URL";
14
+ export const ENV_KEY = "CLIPROXY_MANAGEMENT_KEY";
15
+ /**
16
+ * Resolve the endpoint + key. Blank strings count as absent. The URL gets a
17
+ * trailing-slash trim so `https://host/` still works.
18
+ */
19
+ export function loadCpConfig() {
20
+ const quota = loadUserConfig().quota;
21
+ const url = (quota?.cliproxyUrl ?? process.env[ENV_URL] ?? "").trim().replace(/\/+$/, "");
22
+ const key = (quota?.cliproxyManagementKey ?? process.env[ENV_KEY] ?? "").trim();
23
+ if (!url || !key)
24
+ return null;
25
+ return { url, key };
26
+ }
27
+ //# sourceMappingURL=config.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"config.js","sourceRoot":"","sources":["../../../src/quota/cliproxy/config.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AAEH,OAAO,OAAO,MAAM,cAAc,CAAC;AAEnC,OAAO,EAAE,cAAc,EAAE,MAAM,6BAA6B,CAAC;AAE7D,MAAM,CAAC,MAAM,OAAO,GAAG,cAAc,CAAC;AACtC,MAAM,CAAC,MAAM,OAAO,GAAG,yBAAyB,CAAC;AASjD;;;GAGG;AACH,MAAM,UAAU,YAAY;IAC1B,MAAM,KAAK,GAAG,cAAc,EAAE,CAAC,KAAK,CAAC;IACrC,MAAM,GAAG,GAAG,CAAC,KAAK,EAAE,WAAW,IAAI,OAAO,CAAC,GAAG,CAAC,OAAO,CAAC,IAAI,EAAE,CAAC,CAAC,IAAI,EAAE,CAAC,OAAO,CAAC,MAAM,EAAE,EAAE,CAAC,CAAC;IAC1F,MAAM,GAAG,GAAG,CAAC,KAAK,EAAE,qBAAqB,IAAI,OAAO,CAAC,GAAG,CAAC,OAAO,CAAC,IAAI,EAAE,CAAC,CAAC,IAAI,EAAE,CAAC;IAChF,IAAI,CAAC,GAAG,IAAI,CAAC,GAAG;QAAE,OAAO,IAAI,CAAC;IAC9B,OAAO,EAAE,GAAG,EAAE,GAAG,EAAE,CAAC;AACtB,CAAC"}
@@ -0,0 +1,18 @@
1
+ /**
2
+ * CLIProxyAPI quota rendering — pool-wide rows: every Gemini subscription's
3
+ * usage aggregated per model family and window (`5h` / `Week`), each row
4
+ * stamped with that window's EARLIEST reset across accounts (when capacity
5
+ * starts coming back). Same bar style as the other sections so the combined
6
+ * card reads as one piece. Non-Gemini providers the proxy routes are
7
+ * summarised in a single trailing count line.
8
+ */
9
+ import type { CpQueryResult } from "./types.js";
10
+ export declare function formatCpSection(result: CpQueryResult, color?: boolean,
11
+ /** Count of non-Gemini accounts routed by the proxy, for the tail line. */
12
+ otherAccounts?: number,
13
+ /** Clock for the cached-age stamp (tests inject; default real time). */
14
+ now?: () => number): {
15
+ header: string;
16
+ body: string[];
17
+ };
18
+ //# sourceMappingURL=format.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"format.d.ts","sourceRoot":"","sources":["../../../src/quota/cliproxy/format.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAMH,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,YAAY,CAAC;AAahD,wBAAgB,eAAe,CAC7B,MAAM,EAAE,aAAa,EACrB,KAAK,UAAQ;AACb,2EAA2E;AAC3E,aAAa,SAAI;AACjB,wEAAwE;AACxE,GAAG,GAAE,MAAM,MAAyB,GACnC;IAAE,MAAM,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,MAAM,EAAE,CAAA;CAAE,CA2CpC"}
@@ -0,0 +1,71 @@
1
+ /**
2
+ * CLIProxyAPI quota rendering — pool-wide rows: every Gemini subscription's
3
+ * usage aggregated per model family and window (`5h` / `Week`), each row
4
+ * stamped with that window's EARLIEST reset across accounts (when capacity
5
+ * starts coming back). Same bar style as the other sections so the combined
6
+ * card reads as one piece. Non-Gemini providers the proxy routes are
7
+ * summarised in a single trailing count line.
8
+ */
9
+ import { aggregateCpFamilies } from "./aggregate.js";
10
+ import { pickOverlay, renderColorBar } from "../color.js";
11
+ import { formatResetTime, renderBar } from "../format.js";
12
+ import { roundTenth } from "../rounding.js";
13
+ /** `3m`-style age for the cached-stale header stamp. */
14
+ function formatAge(ms) {
15
+ const minutes = Math.floor(ms / 60_000);
16
+ if (minutes >= 60)
17
+ return `${Math.floor(minutes / 60)}h${minutes % 60 || ""}`;
18
+ return `${minutes}m`;
19
+ }
20
+ function windowLabel(window) {
21
+ return window === "5h" ? "5h" : "Week";
22
+ }
23
+ export function formatCpSection(result, color = false,
24
+ /** Count of non-Gemini accounts routed by the proxy, for the tail line. */
25
+ otherAccounts = 0,
26
+ /** Clock for the cached-age stamp (tests inject; default real time). */
27
+ now = () => Date.now()) {
28
+ // The section's 5-minute server-side-of-the-card TTL means values can lag
29
+ // the rest of the card by minutes (see ../index.js TTL_MS) — stamp the age
30
+ // so a motionless number reads as cached, not broken.
31
+ const ageMs = result.kind === "success" ? now() - result.fetchedAt : 0;
32
+ const header = ageMs > 90_000 ? `CLIProxy · ${formatAge(ageMs)} old` : "CLIProxy";
33
+ if (result.kind !== "success") {
34
+ const msg = result.kind === "not_configured"
35
+ ? "not configured (set quota.cliproxyUrl + quota.cliproxyManagementKey)"
36
+ : result.kind === "auth_error"
37
+ ? "auth failed — check the management key"
38
+ : "unavailable";
39
+ return { header, body: [msg] };
40
+ }
41
+ if (result.accounts.length === 0) {
42
+ return { header, body: ["no Gemini accounts on this proxy"] };
43
+ }
44
+ const body = [];
45
+ for (const family of aggregateCpFamilies(result.accounts)) {
46
+ const label = family.name.slice(0, 22);
47
+ for (const [index, w] of family.windows.entries()) {
48
+ const pct = roundTenth(w.used * 100);
49
+ // Continuation rows (2nd window, 2nd family…) indent under the label.
50
+ const name = index === 0 ? label.padEnd(22) : " ".repeat(22);
51
+ const window = windowLabel(w.window).padEnd(5);
52
+ const reset = formatResetTime(w.resetAt);
53
+ const trailing = reset ? ` · ${reset}` : "";
54
+ if (color) {
55
+ const bar = renderColorBar(pct, { overlay: pickOverlay({ usedPercent: pct }) });
56
+ body.push(`${name}${window}${bar}${trailing}`);
57
+ }
58
+ else {
59
+ body.push(`${name}${window}${renderBar(pct)} ${String(pct).padStart(2)}%${trailing}`);
60
+ }
61
+ }
62
+ }
63
+ if (body.length === 0) {
64
+ return { header, body: ["no quota buckets reported"] };
65
+ }
66
+ if (otherAccounts > 0) {
67
+ body.push(` + ${otherAccounts} non-Gemini account${otherAccounts > 1 ? "s" : ""}`);
68
+ }
69
+ return { header, body };
70
+ }
71
+ //# sourceMappingURL=format.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"format.js","sourceRoot":"","sources":["../../../src/quota/cliproxy/format.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,34 @@
1
+ /**
2
+ * CLIProxyAPI quota orchestration — the entry used by `zcode-acp quota cp`
3
+ * and the combined card.
4
+ *
5
+ * Flow: config (file → env) → plain-HTTP guard → cache → credentials listing
6
+ * → per-Gemini-account quota relay (parallel across accounts, three upstream
7
+ * URL variants in order) → parse → cache. Account-level failures degrade to
8
+ * that account's omission; a failed listing degrades the whole section.
9
+ *
10
+ * Never throws — the CLI always produces output.
11
+ */
12
+ import type { CpQueryResult } from "./types.js";
13
+ /** Result carrying the non-Gemini account count for the renderer's tail. */
14
+ export interface CpQueryOutcome {
15
+ result: CpQueryResult;
16
+ /** Credential entries whose provider is not Gemini (codex/claude/…). */
17
+ otherCount: number;
18
+ }
19
+ /**
20
+ * Drop the cached outcome. NOT wired into the CLI watch tick on purpose —
21
+ * clearing it per tick would restore the 30s poll this TTL exists to prevent.
22
+ * Tests use it to force a fresh query.
23
+ */
24
+ export declare function clearCpCache(): void;
25
+ /**
26
+ * Query the remote CLIProxyAPI. Returns `{result, otherCount}` so the caller
27
+ * can render the codex/claude tail without a second listing.
28
+ */
29
+ export declare function queryCpUsage(): Promise<CpQueryOutcome>;
30
+ export { formatCpSection } from "./format.js";
31
+ export { loadCpConfig, ENV_KEY, ENV_URL } from "./config.js";
32
+ export { ANTIGRAVITY_PROVIDER, parseCredentialEntries, parseQuotaSummary } from "./parse.js";
33
+ export type { CpAccountQuota, CpModelQuota, CpQueryResult, CpWindow, CpWindowQuota } from "./types.js";
34
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/quota/cliproxy/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AAYH,OAAO,KAAK,EAAkB,aAAa,EAAE,MAAM,YAAY,CAAC;AAEhE,4EAA4E;AAC5E,MAAM,WAAW,cAAc;IAC7B,MAAM,EAAE,aAAa,CAAC;IACtB,wEAAwE;IACxE,UAAU,EAAE,MAAM,CAAC;CACpB;AAeD;;;;GAIG;AACH,wBAAgB,YAAY,IAAI,IAAI,CAEnC;AAsBD;;;GAGG;AACH,wBAAsB,YAAY,IAAI,OAAO,CAAC,cAAc,CAAC,CAkD5D;AAED,OAAO,EAAE,eAAe,EAAE,MAAM,aAAa,CAAC;AAC9C,OAAO,EAAE,YAAY,EAAE,OAAO,EAAE,OAAO,EAAE,MAAM,aAAa,CAAC;AAC7D,OAAO,EAAE,oBAAoB,EAAE,sBAAsB,EAAE,iBAAiB,EAAE,MAAM,YAAY,CAAC;AAC7F,YAAY,EAAE,cAAc,EAAE,YAAY,EAAE,aAAa,EAAE,QAAQ,EAAE,aAAa,EAAE,MAAM,YAAY,CAAC"}
@@ -0,0 +1,110 @@
1
+ /**
2
+ * CLIProxyAPI quota orchestration — the entry used by `zcode-acp quota cp`
3
+ * and the combined card.
4
+ *
5
+ * Flow: config (file → env) → plain-HTTP guard → cache → credentials listing
6
+ * → per-Gemini-account quota relay (parallel across accounts, three upstream
7
+ * URL variants in order) → parse → cache. Account-level failures degrade to
8
+ * that account's omission; a failed listing degrades the whole section.
9
+ *
10
+ * Never throws — the CLI always produces output.
11
+ */
12
+ import { log } from "../../utils.js";
13
+ import { fetchCredentials, fetchQuotaSummary } from "./client.js";
14
+ import { loadCpConfig } from "./config.js";
15
+ import { ANTIGRAVITY_PROVIDER, parseCredentialEntries, parseQuotaSummary, relayUpstreamBody, relayUpstreamStatus, } from "./parse.js";
16
+ /**
17
+ * Cache TTL — deliberately MUCH longer than the other providers (10s): the
18
+ * Gemini quota summary rides `v1internal:retrieveUserQuotaSummary`, a Google
19
+ * INTERNAL endpoint reached with the account's own OAuth token, so every
20
+ * refresh is charged to that Google account. High-frequency polling risks
21
+ * rate-limiting or account-level risk flags, and quota buckets move at
22
+ * minute-scale anyway. This TTL is the rate guard NO MATTER how often the
23
+ * caller polls (watch mode's 30s ticks included — they must not clear this
24
+ * cache; see `clearCpCache`).
25
+ */
26
+ const TTL_MS = 5 * 60_000;
27
+ let cached = null;
28
+ /**
29
+ * Drop the cached outcome. NOT wired into the CLI watch tick on purpose —
30
+ * clearing it per tick would restore the 30s poll this TTL exists to prevent.
31
+ * Tests use it to force a fresh query.
32
+ */
33
+ export function clearCpCache() {
34
+ cached = null;
35
+ }
36
+ async function queryOneAccount(base, key, entry) {
37
+ try {
38
+ const relay = await fetchQuotaSummary(base, key, entry.authIndex, entry.projectId);
39
+ if (!relay)
40
+ return null;
41
+ if (relayUpstreamStatus(relay.text) !== 200)
42
+ return null;
43
+ const body = relayUpstreamBody(relay.text);
44
+ if (body === undefined)
45
+ return null;
46
+ const models = parseQuotaSummary(body);
47
+ if (models.length === 0)
48
+ return null;
49
+ return { label: entry.label, provider: ANTIGRAVITY_PROVIDER, models };
50
+ }
51
+ catch (e) {
52
+ log(`cliproxy: account ${entry.authIndex} quota relay failed (${String(e)})`);
53
+ return null;
54
+ }
55
+ }
56
+ /**
57
+ * Query the remote CLIProxyAPI. Returns `{result, otherCount}` so the caller
58
+ * can render the codex/claude tail without a second listing.
59
+ */
60
+ export async function queryCpUsage() {
61
+ if (cached && Date.now() - cached.at < TTL_MS) {
62
+ log("cliproxy: serving cached result");
63
+ return cached.outcome;
64
+ }
65
+ const cfg = loadCpConfig();
66
+ if (!cfg)
67
+ return { result: { kind: "not_configured" }, otherCount: 0 };
68
+ // The management key relays credential-bearing upstream calls — refuse a
69
+ // plaintext connection to anything but loopback (an SSH tunnel is the
70
+ // recommended way to reach a plain-HTTP instance).
71
+ if (cfg.url.startsWith("http://") && !/^http:\/\/(127\.|localhost|\[::1\])/.test(cfg.url)) {
72
+ log("cliproxy: refusing plain-HTTP management URL to a non-loopback host (use https or an SSH tunnel)");
73
+ return { result: { kind: "unavailable" }, otherCount: 0 };
74
+ }
75
+ let outcome;
76
+ try {
77
+ const resp = await fetchCredentials(cfg.url, cfg.key);
78
+ if (resp.status === 401 || resp.status === 403) {
79
+ outcome = { result: { kind: "auth_error" }, otherCount: 0 };
80
+ }
81
+ else if (resp.status !== 200) {
82
+ log(`cliproxy: unexpected status ${resp.status}`);
83
+ outcome = { result: { kind: "unavailable" }, otherCount: 0 };
84
+ }
85
+ else {
86
+ const entries = parseCredentialEntries(resp.text);
87
+ const gemini = entries.filter((e) => e.provider === ANTIGRAVITY_PROVIDER);
88
+ const accounts = (await Promise.all(gemini.map((e) => queryOneAccount(cfg.url, cfg.key, e)))).filter((a) => a !== null);
89
+ // Every Gemini account failing its relay reads as "unreachable quota",
90
+ // not as an empty success — with accounts present but none answering,
91
+ // the honest row is an error line.
92
+ outcome = {
93
+ result: gemini.length > 0 && accounts.length === 0
94
+ ? { kind: "unavailable" }
95
+ : { kind: "success", accounts, fetchedAt: Date.now() },
96
+ otherCount: entries.length - gemini.length,
97
+ };
98
+ }
99
+ }
100
+ catch (e) {
101
+ log(`cliproxy: fetch failed (${e instanceof Error ? e.message : String(e)})`);
102
+ outcome = { result: { kind: "unavailable" }, otherCount: 0 };
103
+ }
104
+ cached = { outcome, at: Date.now() };
105
+ return outcome;
106
+ }
107
+ export { formatCpSection } from "./format.js";
108
+ export { loadCpConfig, ENV_KEY, ENV_URL } from "./config.js";
109
+ export { ANTIGRAVITY_PROVIDER, parseCredentialEntries, parseQuotaSummary } from "./parse.js";
110
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../../src/quota/cliproxy/index.ts"],"names":[],"mappings":"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"}