clay-server 5.2.0-beta.9 → 5.2.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 (104) hide show
  1. package/README.md +0 -4
  2. package/lib/clay-product-control-tools.js +1 -0
  3. package/lib/clay-product-control.js +14 -11
  4. package/lib/clay-vendor-usage.js +44 -0
  5. package/lib/github-star-count.js +147 -0
  6. package/lib/notes-checklist.js +43 -1
  7. package/lib/notes.js +2 -1
  8. package/lib/project-session-notes.js +33 -8
  9. package/lib/project-sessions.js +63 -24
  10. package/lib/project-start-protocol.js +29 -0
  11. package/lib/project-vendor-login.js +2 -0
  12. package/lib/project.js +5 -1
  13. package/lib/public/app.js +27 -18
  14. package/lib/public/css/contextual-clay.css +1 -1
  15. package/lib/public/css/filebrowser.css +1 -1
  16. package/lib/public/css/mate-loading-scene.css +21 -0
  17. package/lib/public/css/overlays.css +64 -256
  18. package/lib/public/css/project-mate-creation.css +5 -6
  19. package/lib/public/css/project-start.css +108 -0
  20. package/lib/public/css/projects-hub.css +53 -14
  21. package/lib/public/css/settings-shell.css +103 -0
  22. package/lib/public/css/sidebar.css +7 -0
  23. package/lib/public/css/title-bar.css +83 -0
  24. package/lib/public/index.html +50 -63
  25. package/lib/public/modules/account-transport-settings.js +8 -2
  26. package/lib/public/modules/admin-settings-email.js +7 -2
  27. package/lib/public/modules/admin-settings-invitations.js +2 -2
  28. package/lib/public/modules/admin-settings-projects.js +2 -2
  29. package/lib/public/modules/admin-settings-ui.js +20 -4
  30. package/lib/public/modules/admin-settings-users.js +4 -4
  31. package/lib/public/modules/app-home-hub.js +2 -1
  32. package/lib/public/modules/app-messages.js +10 -2
  33. package/lib/public/modules/app-projects.js +1 -407
  34. package/lib/public/modules/app-rate-limit.js +12 -1
  35. package/lib/public/modules/clay-tool-presentation.js +1 -0
  36. package/lib/public/modules/contextual-clay.js +3 -1
  37. package/lib/public/modules/github-star.js +66 -0
  38. package/lib/public/modules/home-surface-boot.js +0 -1
  39. package/lib/public/modules/mate-loading-scene.js +17 -0
  40. package/lib/public/modules/pane-appearance-bridge.js +59 -0
  41. package/lib/public/modules/profile.js +11 -3
  42. package/lib/public/modules/project-mate-creation-workspace.js +13 -4
  43. package/lib/public/modules/project-session-visibility-settings.js +19 -16
  44. package/lib/public/modules/project-settings-environment.js +12 -1
  45. package/lib/public/modules/project-settings.js +119 -129
  46. package/lib/public/modules/project-start-dialog.js +130 -0
  47. package/lib/public/modules/project-start-folder-state.js +78 -0
  48. package/lib/public/modules/project-start-folder-view.js +190 -0
  49. package/lib/public/modules/project-start-forms.js +66 -0
  50. package/lib/public/modules/project-start-path.js +60 -0
  51. package/lib/public/modules/project-start-state.js +54 -0
  52. package/lib/public/modules/project-start-submit.js +67 -0
  53. package/lib/public/modules/projects-hub-view.js +32 -44
  54. package/lib/public/modules/projects-hub.js +39 -28
  55. package/lib/public/modules/server-settings.js +36 -91
  56. package/lib/public/modules/settings-project-target.js +56 -0
  57. package/lib/public/modules/settings-select.js +1 -1
  58. package/lib/public/modules/settings-shell-registry.js +88 -0
  59. package/lib/public/modules/settings-shell-view.js +104 -0
  60. package/lib/public/modules/settings-shell.js +342 -0
  61. package/lib/public/modules/sidebar-mobile.js +8 -30
  62. package/lib/public/modules/sidebar-projects.js +2 -1
  63. package/lib/public/modules/theme.js +31 -3
  64. package/lib/public/modules/user-settings-integrations.js +2 -1
  65. package/lib/public/modules/user-settings-layout.js +12 -34
  66. package/lib/public/modules/user-settings-profile.js +1 -1
  67. package/lib/public/modules/user-settings.js +18 -85
  68. package/lib/public/modules/vendor-usage-format.js +93 -0
  69. package/lib/public/modules/vendor-usage-list.js +45 -0
  70. package/lib/public/modules/vendor-usage-model.js +215 -0
  71. package/lib/public/modules/vendor-usage-overview.js +323 -0
  72. package/lib/public/style.css +10 -7
  73. package/lib/public/welcome-art/conversation-desk.png +0 -0
  74. package/lib/public/welcome-art/mate-clay.png +0 -0
  75. package/lib/public/welcome-art/project-clay.png +0 -0
  76. package/lib/sdk-bridge.js +6 -1
  77. package/lib/sdk-message-processor.js +9 -1
  78. package/lib/sdk-quota-snapshot.js +20 -2
  79. package/lib/server-github-stars.js +39 -0
  80. package/lib/server-global-ws.js +21 -17
  81. package/lib/server.js +11 -0
  82. package/lib/session-notes-mcp-server.js +26 -4
  83. package/lib/session-notes-writes.js +88 -0
  84. package/lib/session-visibility.js +20 -8
  85. package/lib/sessions.js +12 -2
  86. package/lib/vendor-usage-acp-readers.js +97 -0
  87. package/lib/vendor-usage-capabilities.js +88 -0
  88. package/lib/vendor-usage-http-readers.js +214 -0
  89. package/lib/vendor-usage-metrics.js +47 -0
  90. package/lib/vendor-usage-native-readers.js +106 -0
  91. package/lib/vendor-usage-overview.js +406 -0
  92. package/lib/vendor-usage-service.js +281 -0
  93. package/lib/ws-schema.js +4 -2
  94. package/lib/yoke/adapters/claude.js +12 -0
  95. package/lib/yoke/adapters/codex-query-contract.js +1 -0
  96. package/lib/yoke/adapters/codex.js +45 -12
  97. package/lib/yoke/claude-hook-installer.js +1 -0
  98. package/lib/yoke/codex-dynamic-tool-names.js +80 -0
  99. package/lib/yoke/codex-model-catalog.js +37 -3
  100. package/lib/yoke/muse-services.js +16 -3
  101. package/lib/yoke/providers/claude-model-descriptions.js +22 -2
  102. package/lib/yoke/vendor-registry.js +6 -2
  103. package/package.json +2 -1
  104. package/lib/public/modules/server-settings-navigation.js +0 -39
@@ -0,0 +1,214 @@
1
+ // Readers for vendors whose only native usage source is an authenticated HTTP
2
+ // endpoint using the vendor CLI's own stored token.
3
+ //
4
+ // These read exactly one documented credential file and send the token only to
5
+ // the vendor's own HTTPS host: an endpoint override is accepted only when it is
6
+ // https on that exact host (no credentials, no port); anything else is refused
7
+ // BEFORE the credential file is read, so a token is never sent to an unverified
8
+ // origin. Redirects are refused. They never refresh or rewrite the file (the CLI
9
+ // owns the token lifecycle), never log or return the token, and are used only
10
+ // where the credential provably belongs to the requester (see
11
+ // vendor-usage-service.js identity rules). `identity` is a short one-way
12
+ // fingerprint of the credential used, so the service can notice a sign-in
13
+ // change; it cannot be reversed to the token.
14
+ // Request shapes follow Orca@17a2bced src/main/rate-limits/{kimi,grok}-fetcher.ts.
15
+
16
+ var path = require("path");
17
+ var crypto = require("crypto");
18
+ var m = require("./vendor-usage-metrics");
19
+ var failure = require("./vendor-usage-native-readers").failure;
20
+
21
+ var TOKEN_SKEW_SECONDS = 5;
22
+ var TOKEN_SKEW_MS = 5 * 60 * 1000;
23
+
24
+ function parseJson(text, what) {
25
+ try { return JSON.parse(text); } catch (e) { throw failure("parse", what + " could not be read."); }
26
+ }
27
+
28
+ function fingerprint(parts) {
29
+ return crypto.createHash("sha256").update(parts.join("\u0000")).digest("hex").slice(0, 16);
30
+ }
31
+
32
+ // Returns the base URL to use, or throws before any credential is read.
33
+ function trustedBase(override, fallback, host, what) {
34
+ var value = typeof override === "string" ? override.trim() : "";
35
+ if (!value) return fallback;
36
+ var url;
37
+ try { url = new URL(value); } catch (e) { url = null; }
38
+ if (!url || url.protocol !== "https:" || url.hostname !== host || url.username || url.password || url.port) {
39
+ throw failure("untrusted_origin", "The " + what + " endpoint override is not the vendor's own HTTPS host, so the saved sign-in was not used or sent.");
40
+ }
41
+ return (url.origin + url.pathname).replace(/\/$/, "");
42
+ }
43
+
44
+ // A missing file means "not signed in", any other failure is a read error.
45
+ async function readCredentialFile(deps, file, what) {
46
+ var text;
47
+ try { text = await deps.readText(file); } catch (error) {
48
+ if (error && error.code === "ENOENT") throw failure("no_auth", "Not signed in to " + what + ".");
49
+ throw failure("unavailable", "The " + what + " sign-in file could not be read.");
50
+ }
51
+ return parseJson(text, "The " + what + " sign-in file");
52
+ }
53
+
54
+ async function getJson(deps, url, headers, what) {
55
+ var response;
56
+ try { response = await deps.fetchJson(url, headers); } catch (error) {
57
+ throw failure(error && error.name === "TimeoutError" ? "timeout" : "network", what + " usage could not be reached.");
58
+ }
59
+ if (response.status === 401 || response.status === 403) throw failure("unauthorized", what + " rejected the saved sign-in (HTTP " + response.status + ").");
60
+ if (response.status === 429) throw failure("rate_limited", what + " is rate-limiting usage reads.");
61
+ if (response.status < 200 || response.status >= 300) throw failure("server", what + " usage request failed (HTTP " + response.status + ").");
62
+ return response.body && typeof response.body === "object" ? response.body : {};
63
+ }
64
+
65
+ // ---- Kimi Code ------------------------------------------------------------
66
+
67
+ function kimiHome(deps) {
68
+ var override = deps.env.KIMI_CODE_HOME && deps.env.KIMI_CODE_HOME.trim();
69
+ return override || path.join(deps.homedir(), ".kimi-code");
70
+ }
71
+
72
+ function windowMins(window) {
73
+ var duration = m.num(window && window.duration);
74
+ if (duration === null) return null;
75
+ var unit = String((window && window.timeUnit) || "").toUpperCase();
76
+ if (unit.indexOf("MINUTE") !== -1) return duration;
77
+ if (unit.indexOf("HOUR") !== -1) return duration * 60;
78
+ if (unit.indexOf("DAY") !== -1) return duration * 1440;
79
+ if (unit.indexOf("SECOND") !== -1) return Math.round(duration / 60);
80
+ return null;
81
+ }
82
+
83
+ // Counts stay counts: used/limit/remaining are the vendor's own figures.
84
+ function kimiCount(id, label, detail, mins) {
85
+ if (!detail) return null;
86
+ var limit = m.num(detail.limit);
87
+ var used = m.num(detail.used);
88
+ var remaining = m.num(detail.remaining);
89
+ if (limit === null || limit <= 0) return null;
90
+ if (used === null && remaining !== null) used = limit - remaining;
91
+ if (remaining === null && used !== null) remaining = limit - used;
92
+ if (used === null) return null;
93
+ return m.metric("count", id, label, { used: used, limit: limit, remaining: remaining, windowMins: mins, resetsAtMs: m.isoMs(detail.resetTime || detail.resetAt) });
94
+ }
95
+
96
+ function kimiToken(creds) {
97
+ return creds && typeof creds.access_token === "string" ? creds.access_token : "";
98
+ }
99
+
100
+ async function kimi(deps) {
101
+ var base = trustedBase(deps.env.KIMI_CODE_BASE_URL, "https://api.kimi.com/coding/v1", "api.kimi.com", "Kimi Code");
102
+ var creds = await readCredentialFile(deps, path.join(kimiHome(deps), "credentials", "kimi-code.json"), "Kimi Code");
103
+ var token = kimiToken(creds);
104
+ if (!token) throw failure("no_auth", "Not signed in to Kimi Code.");
105
+ var expiresAt = m.num(creds.expires_at);
106
+ if (expiresAt === null || expiresAt - Math.floor(deps.now() / 1000) <= TOKEN_SKEW_SECONDS) {
107
+ throw failure("expired_auth", "The Kimi Code sign-in has expired; run kimi once to refresh it.");
108
+ }
109
+ var body = await getJson(deps, base + "/usages", { Authorization: "Bearer " + token, Accept: "application/json" }, "Kimi Code");
110
+ var metrics = [];
111
+ // The response does not name the period or unit of the top-level block.
112
+ var weekly = kimiCount("kimi:usage", "Usage", body.usage, null);
113
+ if (weekly) metrics.push(weekly);
114
+ (Array.isArray(body.limits) ? body.limits : []).forEach(function (limit, index) {
115
+ var mins = windowMins(limit && limit.window);
116
+ var item = kimiCount("kimi:limit:" + index, mins ? "Rolling window" : "Limit", limit && limit.detail, mins);
117
+ if (item) metrics.push(item);
118
+ });
119
+ return { metrics: metrics, identity: fingerprint(["kimi", token]) };
120
+ }
121
+
122
+ // Fingerprint of the saved sign-in, read synchronously; null when absent,
123
+ // unreadable, or when the endpoint override is untrusted (the credential is not
124
+ // read at all in that case, same as the reader).
125
+ function kimiIdentity(deps) {
126
+ try {
127
+ trustedBase(deps.env.KIMI_CODE_BASE_URL, "https://api.kimi.com/coding/v1", "api.kimi.com", "Kimi Code");
128
+ var token = kimiToken(JSON.parse(deps.readTextSync(path.join(kimiHome(deps), "credentials", "kimi-code.json"))));
129
+ return token ? fingerprint(["kimi", token]) : null;
130
+ } catch (e) { return null; }
131
+ }
132
+
133
+ // ---- Grok Build -----------------------------------------------------------
134
+
135
+ var GROK_ISSUER = "https://auth.x.ai";
136
+
137
+ function grokHome(deps) {
138
+ var override = deps.env.GROK_HOME && deps.env.GROK_HOME.trim();
139
+ return override && path.isAbsolute(override) ? override : path.join(deps.homedir(), ".grok");
140
+ }
141
+
142
+ // Only the default xAI issuer's session is used: a credential from any other
143
+ // issuer is not bound to the billing host, so it is never sent there.
144
+ function pickGrokSession(parsed, now) {
145
+ var expired = null;
146
+ var keys = Object.keys(parsed || {});
147
+ for (var i = 0; i < keys.length; i++) {
148
+ var entry = parsed[keys[i]];
149
+ var preferred = keys[i] === GROK_ISSUER || keys[i].indexOf(GROK_ISSUER + "::") === 0;
150
+ if (!preferred || !entry || typeof entry.key !== "string" || !entry.key) continue;
151
+ var expiresMs = m.isoMs(entry.expires_at);
152
+ var fresh = expiresMs === null || expiresMs - now > TOKEN_SKEW_MS;
153
+ var session = { token: entry.key, userId: typeof entry.user_id === "string" ? entry.user_id : null, fresh: fresh };
154
+ if (fresh) return session;
155
+ expired = expired || session;
156
+ }
157
+ return expired;
158
+ }
159
+
160
+ function grokMoney(value) {
161
+ return m.num(value && value.val);
162
+ }
163
+
164
+ function grokMetrics(config) {
165
+ var out = [];
166
+ var end = m.isoMs(config.currentPeriod && config.currentPeriod.end || config.billingPeriodEnd);
167
+ var start = m.isoMs(config.currentPeriod && config.currentPeriod.start || config.billingPeriodStart);
168
+ // The period length comes from the response's own start and end, never assumed.
169
+ var mins = start !== null && end !== null && end > start ? Math.round((end - start) / 60000) : null;
170
+ // Only a reported percent is shown: an absent field is unknown, not 0%.
171
+ if (m.num(config.creditUsagePercent) !== null) {
172
+ out.push(m.metric("percent", "grok:credits", "Credits", { usedPercent: Math.min(100, Math.max(0, config.creditUsagePercent)), windowMins: mins, resetsAtMs: end }));
173
+ }
174
+ var limit = grokMoney(config.monthlyLimit);
175
+ var used = grokMoney(config.used);
176
+ // The response does not name a currency, so these stay unitless amounts.
177
+ if (limit !== null && limit > 0 && used !== null) {
178
+ out.push(m.metric("amount", "grok:budget", "Included budget", { used: used, limit: limit, windowMins: mins, resetsAtMs: end }));
179
+ }
180
+ return out;
181
+ }
182
+
183
+ function grokBase(deps) {
184
+ return trustedBase(deps.env.GROK_CLI_CHAT_PROXY_BASE_URL, "https://cli-chat-proxy.grok.com/v1", "cli-chat-proxy.grok.com", "Grok");
185
+ }
186
+
187
+ async function grok(deps) {
188
+ var base = grokBase(deps);
189
+ var parsed = await readCredentialFile(deps, path.join(grokHome(deps), "auth.json"), "Grok");
190
+ var session = pickGrokSession(parsed, deps.now());
191
+ if (!session) throw failure("no_auth", "Not signed in to Grok.");
192
+ if (!session.fresh) throw failure("expired_auth", "The Grok sign-in has expired; run grok once to refresh it.");
193
+ var headers = { Authorization: "Bearer " + session.token, "X-XAI-Token-Auth": "xai-grok-cli", Accept: "application/json" };
194
+ if (session.userId) headers["x-userid"] = session.userId;
195
+ var body = await getJson(deps, base + "/billing?format=credits", headers, "Grok");
196
+ var config = body.config || body;
197
+ var metrics = grokMetrics(config);
198
+ if (metrics.length === 0) {
199
+ // The default view can carry a monthly budget the credits view omits.
200
+ var other = await getJson(deps, base + "/billing", headers, "Grok");
201
+ metrics = grokMetrics(other.config || other);
202
+ }
203
+ return { metrics: metrics, planType: typeof config.subscriptionTier === "string" ? config.subscriptionTier : null, identity: fingerprint(["grok", session.token, session.userId || ""]) };
204
+ }
205
+
206
+ function grokIdentity(deps) {
207
+ try {
208
+ grokBase(deps);
209
+ var session = pickGrokSession(JSON.parse(deps.readTextSync(path.join(grokHome(deps), "auth.json"))), deps.now());
210
+ return session ? fingerprint(["grok", session.token, session.userId || ""]) : null;
211
+ } catch (e) { return null; }
212
+ }
213
+
214
+ module.exports = { kimi: kimi, grok: grok, identities: { kimi: kimiIdentity, grok: grokIdentity } };
@@ -0,0 +1,47 @@
1
+ // Provider-neutral usage metric shapes. A metric keeps the unit its vendor
2
+ // reported; nothing is converted to a percentage here or anywhere downstream.
3
+ //
4
+ // percent { usedPercent } native percent used
5
+ // count { used, limit, remaining } requests/messages/tokens
6
+ // credits { balance, used, limit, unlimited } unitless credits (balance and/or used of limit)
7
+ // currency { used, limit, balance, currencyCode } money in a named currency
8
+ // amount { used, limit } numbers whose unit the vendor did not name
9
+ //
10
+ // Every metric may carry windowMins and resetsAtMs (milliseconds). A field the
11
+ // vendor did not report is null, never 0.
12
+
13
+ var UNITS = ["percent", "count", "credits", "currency", "amount"];
14
+
15
+ function num(value) {
16
+ if (typeof value === "string" && value.trim() !== "") value = Number(value);
17
+ return typeof value === "number" && Number.isFinite(value) ? value : null;
18
+ }
19
+
20
+ function resetMs(value) {
21
+ var n = num(value);
22
+ return n !== null && n > 0 ? n : null;
23
+ }
24
+
25
+ function isoMs(value) {
26
+ if (typeof value !== "string" || !value) return null;
27
+ var ms = Date.parse(value);
28
+ return Number.isFinite(ms) ? ms : null;
29
+ }
30
+
31
+ function metric(unit, id, label, fields) {
32
+ var m = { unit: unit, id: String(id), label: String(label), windowMins: null, resetsAtMs: null };
33
+ Object.keys(fields || {}).forEach(function (key) { m[key] = fields[key]; });
34
+ return m;
35
+ }
36
+
37
+ // Validates a metric from any reader before it can reach a client.
38
+ function valid(m) {
39
+ if (!m || UNITS.indexOf(m.unit) === -1 || typeof m.id !== "string") return false;
40
+ if (m.unit === "percent") return num(m.usedPercent) !== null && m.usedPercent >= 0;
41
+ if (m.unit === "count" || m.unit === "amount") return num(m.used) !== null || num(m.remaining) !== null;
42
+ if (m.unit === "credits") return m.unlimited === true || num(m.balance) !== null || num(m.used) !== null;
43
+ if (m.unit === "currency") return typeof m.currencyCode === "string" && m.currencyCode !== "" && (num(m.used) !== null || num(m.balance) !== null);
44
+ return false;
45
+ }
46
+
47
+ module.exports = { UNITS: UNITS, num: num, resetMs: resetMs, isoMs: isoMs, metric: metric, valid: valid };
@@ -0,0 +1,106 @@
1
+ // Readers that ask a vendor runtime Clay already manages (no credential reads).
2
+ // Each takes a request context { adapter, linuxUser } and returns
3
+ // { metrics: [...], accountId?, planType?, observedAtMs? }
4
+ // or throws an Error carrying `failureKind`.
5
+
6
+ var m = require("./vendor-usage-metrics");
7
+
8
+ function failure(kind, message) {
9
+ var error = new Error(message);
10
+ error.failureKind = kind;
11
+ return error;
12
+ }
13
+
14
+ function codexSnapshotMetrics(limitId, snapshot) {
15
+ var out = [];
16
+ var prefix = limitId || "codex";
17
+ var named = snapshot.limitName && limitId && limitId !== "codex" ? String(snapshot.limitName) : "";
18
+ [["primary", "Primary"], ["secondary", "Secondary"]].forEach(function (pair) {
19
+ var window = snapshot[pair[0]];
20
+ if (!window || window.windowDurationMins === 0) return;
21
+ var used = m.num(window.usedPercent);
22
+ if (used === null || used < 0) return;
23
+ out.push(m.metric("percent", prefix + ":" + pair[0], named || pair[1], {
24
+ usedPercent: used,
25
+ windowMins: m.num(window.windowDurationMins) > 0 ? window.windowDurationMins : null,
26
+ resetsAtMs: m.resetMs(window.resetsAt) !== null ? window.resetsAt * 1000 : null,
27
+ }));
28
+ });
29
+ var credits = snapshot.credits;
30
+ if (credits && (credits.unlimited === true || (credits.hasCredits === true && m.num(credits.balance) !== null))) {
31
+ out.push(m.metric("credits", prefix + ":credits", "Credits", { balance: credits.unlimited === true ? null : m.num(credits.balance), unlimited: credits.unlimited === true }));
32
+ }
33
+ return out;
34
+ }
35
+
36
+ // Source: codex app-server `account/rateLimits/read` (GetAccountRateLimitsResponse).
37
+ function codex(context) {
38
+ var adapter = context.adapter;
39
+ if (!adapter || typeof adapter.readAccountRateLimits !== "function") return Promise.reject(failure("unavailable", "The Codex runtime cannot report account usage."));
40
+ return adapter.readAccountRateLimits({ linuxUser: context.linuxUser || undefined }).then(function (reply) {
41
+ if (!reply || typeof reply !== "object") throw failure("parse", "Codex returned no usage.");
42
+ var metrics = [];
43
+ var byId = reply.rateLimitsByLimitId && typeof reply.rateLimitsByLimitId === "object" ? reply.rateLimitsByLimitId : null;
44
+ if (byId) Object.keys(byId).forEach(function (id) { if (byId[id]) metrics = metrics.concat(codexSnapshotMetrics(id, byId[id])); });
45
+ else if (reply.rateLimits) metrics = codexSnapshotMetrics(reply.rateLimits.limitId || "codex", reply.rateLimits);
46
+ var plan = reply.rateLimits && typeof reply.rateLimits.planType === "string" ? reply.rateLimits.planType : null;
47
+ return { metrics: metrics, accountId: typeof reply.accountId === "string" ? reply.accountId : null, planType: plan };
48
+ });
49
+ }
50
+
51
+ // Source: Muse Code `usage/read`; windows carry native used percent and reset ms.
52
+ function muse(context) {
53
+ var adapter = context.adapter;
54
+ if (!adapter || typeof adapter.readUsage !== "function") return Promise.reject(failure("unavailable", "The Muse runtime cannot report usage."));
55
+ return adapter.readUsage({ signal: context.signal }).then(function (usage) {
56
+ if (!usage) throw failure("unavailable", "Muse reported no usage.");
57
+ var metrics = [];
58
+ [["window", "Window"], ["weekly", "Weekly"]].forEach(function (pair) {
59
+ var value = usage[pair[0]];
60
+ if (!value || m.num(value.usedPercent) === null || value.usedPercent < 0) return;
61
+ metrics.push(m.metric("percent", "muse:" + pair[0], pair[1], {
62
+ usedPercent: value.usedPercent,
63
+ windowMins: pair[0] === "weekly" ? 10080 : (m.num(value.windowDurationMins) > 0 ? value.windowDurationMins : null),
64
+ resetsAtMs: m.resetMs(value.resetsAtMs),
65
+ }));
66
+ });
67
+ return { metrics: metrics, observedAtMs: m.num(usage.observedAtMs) };
68
+ });
69
+ }
70
+
71
+ var CLAUDE_WINDOWS = [["five_hour", "5-hour", 300], ["seven_day", "7-day", 10080], ["seven_day_opus", "7-day Opus", 10080], ["seven_day_sonnet", "7-day Sonnet", 10080], ["seven_day_oauth_apps", "7-day OAuth apps", 10080]];
72
+
73
+ // Source: the Claude SDK's account usage call (marked experimental by the SDK),
74
+ // answered by one of the requester's own live sessions, so the account is the
75
+ // one that session authenticates as. Every field is shape-checked; a changed or
76
+ // missing response is "unavailable", never a guess.
77
+ function claude(context) {
78
+ var session = context.session;
79
+ var query = session && session.queryInstance;
80
+ if (!query || typeof query.getAccountUsage !== "function") return Promise.reject(failure("unavailable", "Account usage needs one of your live Claude sessions."));
81
+ return Promise.resolve(query.getAccountUsage({ skipBehaviors: true })).then(function (reply) {
82
+ if (!reply || typeof reply !== "object") throw failure("unavailable", "This Claude runtime cannot report account usage.");
83
+ if (reply.rate_limits_available === false || !reply.rate_limits) throw failure("unavailable", "Plan limits do not apply to this Claude account (for example an API key or a third-party provider).");
84
+ var limits = reply.rate_limits;
85
+ var metrics = [];
86
+ CLAUDE_WINDOWS.forEach(function (spec) {
87
+ var w = limits[spec[0]];
88
+ if (!w || m.num(w.utilization) === null || w.utilization < 0) return;
89
+ metrics.push(m.metric("percent", "claude:" + spec[0], spec[1], { usedPercent: w.utilization, windowMins: spec[2], resetsAtMs: m.isoMs(w.resets_at) }));
90
+ });
91
+ (Array.isArray(limits.model_scoped) ? limits.model_scoped : []).forEach(function (w, index) {
92
+ if (!w || m.num(w.utilization) === null || w.utilization < 0) return;
93
+ metrics.push(m.metric("percent", "claude:model:" + index, typeof w.display_name === "string" && w.display_name ? w.display_name : "Model", { usedPercent: w.utilization, resetsAtMs: m.isoMs(w.resets_at) }));
94
+ });
95
+ var extra = limits.extra_usage;
96
+ if (extra && extra.is_enabled === true && m.num(extra.used_credits) !== null) {
97
+ var shared = { used: extra.used_credits, limit: m.num(extra.monthly_limit), balance: null };
98
+ metrics.push(typeof extra.currency === "string" && extra.currency
99
+ ? m.metric("currency", "claude:extra", "Extra usage", Object.assign({ currencyCode: extra.currency }, shared))
100
+ : m.metric("credits", "claude:extra", "Extra usage", Object.assign({ unlimited: false }, shared)));
101
+ }
102
+ return { metrics: metrics, planType: typeof reply.subscription_type === "string" ? reply.subscription_type : null, sourceSessionId: session.localId };
103
+ });
104
+ }
105
+
106
+ module.exports = { codex: codex, muse: muse, claude: claude, failure: failure };