@hashimkarim/usagestat-linux-x64-gnu 2.1.0 → 2.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 (81) hide show
  1. package/native-manifest.json +197 -89
  2. package/package.json +19 -1
  3. package/plugins/abliteration/plugin.json +1 -1
  4. package/plugins/aerostack/plugin.json +1 -1
  5. package/plugins/aixy/plugin.json +1 -1
  6. package/plugins/atlascloud/plugin.json +1 -1
  7. package/plugins/bifrost/plugin.json +1 -1
  8. package/plugins/chatbox/plugin.json +1 -1
  9. package/plugins/claudeplatform/plugin.json +1 -1
  10. package/plugins/codex/plugin.js +11 -6
  11. package/plugins/codex/plugin.json +10 -1
  12. package/plugins/cosmic/plugin.json +1 -1
  13. package/plugins/cursor/plugin.js +18 -6
  14. package/plugins/devpass/plugin.json +1 -1
  15. package/plugins/exa/plugin.json +1 -1
  16. package/plugins/fal/plugin.json +1 -1
  17. package/plugins/flora/plugin.json +1 -1
  18. package/plugins/futureagi/plugin.json +1 -1
  19. package/plugins/gemini-apps/plugin.json +1 -1
  20. package/plugins/gitkraken/plugin.json +1 -1
  21. package/plugins/googledeveloper/LICENSE +21 -0
  22. package/plugins/googledeveloper/icon.svg +3 -0
  23. package/plugins/googledeveloper/plugin.js +204 -0
  24. package/plugins/googledeveloper/plugin.json +25 -0
  25. package/plugins/helmcode/plugin.js +80 -23
  26. package/plugins/helmcode/plugin.json +2 -2
  27. package/plugins/huggingface/plugin.json +1 -1
  28. package/plugins/hyper/plugin.json +1 -1
  29. package/plugins/jetbrains-ai-assistant/LICENSE +22 -0
  30. package/plugins/jetbrains-ai-assistant/plugin.js +502 -16
  31. package/plugins/jetbrains-ai-assistant/plugin.json +27 -4
  32. package/plugins/kimi/plugin.js +16 -1
  33. package/plugins/kimi/plugin.json +46 -6
  34. package/plugins/langdock/plugin.json +1 -1
  35. package/plugins/linkup/plugin.json +1 -1
  36. package/plugins/lithosai/plugin.json +1 -1
  37. package/plugins/llmman/plugin.json +1 -1
  38. package/plugins/munito/plugin.json +1 -1
  39. package/plugins/muse/plugin.json +1 -1
  40. package/plugins/museai/plugin.js +5 -3
  41. package/plugins/museai/plugin.json +2 -2
  42. package/plugins/nous/plugin.json +1 -1
  43. package/plugins/opencode-go/plugin.js +4 -3
  44. package/plugins/opendesign/LICENSE +21 -0
  45. package/plugins/opendesign/icon.svg +4 -0
  46. package/plugins/opendesign/plugin.js +108 -0
  47. package/plugins/opendesign/plugin.json +25 -0
  48. package/plugins/parallel/plugin.json +1 -1
  49. package/plugins/perplexity/LICENSE +22 -0
  50. package/plugins/perplexity/plugin.js +122 -0
  51. package/plugins/perplexity/plugin.json +36 -7
  52. package/plugins/qwencloud/plugin.json +1 -1
  53. package/plugins/raycast/plugin.json +1 -1
  54. package/plugins/replicate/plugin.json +1 -1
  55. package/plugins/sailresearch/plugin.json +1 -1
  56. package/plugins/siliconflow/LICENSE +21 -0
  57. package/plugins/siliconflow/icon.svg +3 -0
  58. package/plugins/siliconflow/plugin.js +87 -0
  59. package/plugins/siliconflow/plugin.json +32 -0
  60. package/plugins/sofya/plugin.json +1 -1
  61. package/plugins/soniox/plugin.json +1 -1
  62. package/plugins/synthetic/plugin.js +12 -0
  63. package/plugins/synthetic/plugin.json +51 -9
  64. package/plugins/tavily/plugin.json +1 -1
  65. package/plugins/tinyapi/plugin.json +1 -1
  66. package/plugins/traecn/plugin.js +10 -11
  67. package/plugins/traecn/plugin.json +2 -2
  68. package/plugins/tripo/LICENSE +21 -0
  69. package/plugins/tripo/icon.svg +4 -0
  70. package/plugins/tripo/plugin.js +60 -0
  71. package/plugins/tripo/plugin.json +24 -0
  72. package/plugins/typesafe/plugin.js +63 -112
  73. package/plugins/typesafe/plugin.json +2 -2
  74. package/plugins/v0/plugin.json +1 -1
  75. package/plugins/vercel/plugin.json +2 -2
  76. package/plugins/workbuddy/plugin.js +103 -17
  77. package/plugins/workbuddy/plugin.json +14 -2
  78. package/plugins/xapi/plugin.json +1 -1
  79. package/plugins/xkiro/plugin.json +1 -1
  80. package/usagestat +0 -0
  81. package/usagestatd +0 -0
@@ -24,19 +24,17 @@ function _optionalChain(ops) {
24
24
  return value;
25
25
  }
26
26
 
27
- const billingURL = "https://console.typesafe.ai/settings/billing";
28
- const billingOrigin = "https://console.typesafe.ai";
27
+ const billingOverviewURL = "https://console.typesafe.ai/api/billing/overview";
28
+ const overviewFailure =
29
+ "TypeSafe billing overview could not be read. Open https://console.typesafe.ai/billing to check access; if the page opens, report the integration failure.";
29
30
  // Cloudflare blocks the Linux default UA (it contains "curl/"), so send a browser-style one.
30
31
  const userAgent =
31
32
  "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/143.0.0.0 Safari/537.36";
32
- const actionCacheKey = "typesafe.billing.next-action";
33
- const actionCacheTTL = 12 * 60 * 60;
34
- const maximumChunkCount = 60;
35
33
 
36
34
  defineProvider({
37
35
  id: "typesafe",
38
36
  name: "TypeSafe",
39
- endpoints: [billingOrigin],
37
+ endpoints: ["https://console.typesafe.ai"],
40
38
  settings: [],
41
39
  capabilities: ["browser-cookies", "http-status"],
42
40
  cookieDomains: ["console.typesafe.ai", "typesafe.ai"],
@@ -51,9 +49,27 @@ defineProvider({
51
49
  const value = raw && /^\d+(?:\.\d+)?$/.test(raw.trim()) ? Number(raw) : 1;
52
50
  return Number.isFinite(value) ? Math.min(10, value) : 1;
53
51
  };
52
+ // A redirect only means the session is gone when it targets a login/SSO route. The billing overview
53
+ // itself may redirect for benign route moves (for example /settings/billing -> /billing).
54
+ const loginRedirect = (location) => {
55
+ if (typeof location !== "string" || !location.trim()) return false;
56
+ const target = location.trim();
57
+ const loginPath = /(?:^|[/?#])(?:login|sign-?in|signin|auth|oauth|sso)(?:[/?#]|$)/i;
58
+ if (!/^(?:https?:)?\/\//i.test(target)) return loginPath.test(target);
59
+ const hostMatch = /^(?:https?:)?\/\/([^/?#]+)/i.exec(target);
60
+ const hostname = hostMatch ? hostMatch[1].toLowerCase().replace(/:\d+$/, "") : "";
61
+ const knownHost = hostname === "typesafe.ai" || hostname.endsWith(".typesafe.ai");
62
+ return !knownHost || loginPath.test(target);
63
+ };
64
+ // The billing overview API refuses requests that do not look same-origin, even with a valid session.
54
65
  const validate = (response) => {
55
- if (response.status === 401 || response.status === 403 || (response.status >= 300 && response.status < 400))
56
- expired();
66
+ if (response.status === 403 && /cross-origin request refused/i.test(response.bodyText))
67
+ throw ctx.fail.apiFailure(overviewFailure);
68
+ if (response.status === 401 || response.status === 403) expired();
69
+ if (response.status >= 300 && response.status < 400) {
70
+ if (loginRedirect(response.headers["location"])) expired();
71
+ throw ctx.fail.apiFailure(overviewFailure);
72
+ }
57
73
  if (response.status === 429)
58
74
  throw ctx.fail.rateLimited("TypeSafe rate limit reached.", { retryAfterSeconds: retryAfter(response) });
59
75
  if (response.status === 408 || response.status >= 500)
@@ -63,105 +79,40 @@ defineProvider({
63
79
  if (response.status < 200 || response.status >= 300)
64
80
  throw ctx.fail.apiFailure(`TypeSafe returned HTTP ${response.status}.`);
65
81
  };
66
- // Login pages can also arrive directly with HTTP 200, without an HTTP redirect.
67
- const validateBody = (response) => {
68
- validate(response);
69
- if (/\\?"\(auth\)\\?",\{\\?"children\\?":\[\\?"login\\?"/.test(response.bodyText)) expired();
70
- };
71
- const discoverActionID = async (cookie) => {
72
- const page = await ctx.http.get(billingURL, {
73
- headers: { Cookie: cookie, Accept: "text/html", "User-Agent": userAgent },
74
- timeoutSeconds: 6,
75
- });
76
- validateBody(page);
77
- const scriptPattern = /<script\b[^>]*\bsrc=["']([^"']+)["'][^>]*>/gi;
78
- const chunkURLs = [];
79
- let match;
80
- while ((match = scriptPattern.exec(page.bodyText)) && chunkURLs.length < maximumChunkCount) {
81
- const source = match[1];
82
- const normalized = source.startsWith("/") ? `${billingOrigin}${source}` : source;
83
- if (!normalized.startsWith(`${billingOrigin}/`) || !/\.js(?:$|\?)/i.test(normalized)) continue;
84
- if (!chunkURLs.includes(normalized)) chunkURLs.push(normalized);
85
- }
86
- let discoveryFailure;
87
- for (const url of chunkURLs) {
88
- try {
89
- const chunk = await ctx.http.get(url, { headers: { "User-Agent": userAgent }, timeoutSeconds: 2 });
90
- if (chunk.status === 429 || chunk.status === 408 || chunk.status >= 500) validate(chunk);
91
- if (chunk.status < 200 || chunk.status >= 300) continue;
92
- const action = _optionalChain([
93
- /"([0-9a-f]{40,})"[^)]{0,150}"getBillingOverviewResult"/i,
94
- "access",
95
- (_) => _.exec,
96
- "call",
97
- (_2) => _2(chunk.bodyText),
98
- "optionalAccess",
99
- (_3) => _3[1],
100
- ]);
101
- if (action) return action;
102
- } catch (error) {
103
- discoveryFailure ??= error;
104
- }
105
- }
106
- if (discoveryFailure !== undefined) throw discoveryFailure;
107
- throw ctx.fail.parseFailure("TypeSafe billing page format changed: action id not found.");
108
- };
109
82
  const cookie = await ctx.browser.cookieHeader("typesafe.ai");
110
- let actionID = ctx.cache.get(actionCacheKey);
111
- if (!actionID || !/^[0-9a-f]{40,}$/i.test(actionID)) {
112
- actionID = await discoverActionID(cookie);
113
- ctx.cache.set(actionCacheKey, actionID, actionCacheTTL);
114
- }
115
- const post = async (id) =>
116
- ctx.http.post(billingURL, {
117
- headers: {
118
- Cookie: cookie,
119
- Origin: billingOrigin,
120
- "Next-Action": id,
121
- Accept: "text/x-component",
122
- "Content-Type": "application/json",
123
- "User-Agent": userAgent,
124
- },
125
- body: [],
126
- timeoutSeconds: 6,
127
- });
128
- let response = await post(actionID);
129
- if (response.status === 404 && response.headers["x-nextjs-action-not-found"] === "1") {
130
- ctx.cache.set(actionCacheKey, "", 1);
131
- actionID = await discoverActionID(cookie);
132
- ctx.cache.set(actionCacheKey, actionID, actionCacheTTL);
133
- response = await post(actionID);
83
+ const response = await ctx.http.get(billingOverviewURL, {
84
+ headers: {
85
+ Cookie: cookie,
86
+ Accept: "application/json",
87
+ Origin: "https://console.typesafe.ai",
88
+ Referer: "https://console.typesafe.ai/billing",
89
+ "Sec-Fetch-Site": "same-origin",
90
+ "Sec-Fetch-Mode": "cors",
91
+ "Sec-Fetch-Dest": "empty",
92
+ "User-Agent": userAgent,
93
+ },
94
+ timeoutSeconds: 10,
95
+ });
96
+ validate(response);
97
+ // A signed-out console can answer with an HTML login page instead of JSON.
98
+ const body = response.bodyText.trim();
99
+ if (body.startsWith("<")) {
100
+ if (/(?:login|sign[-\s]?in|\(auth\))/i.test(body)) expired();
101
+ throw ctx.fail.apiFailure(overviewFailure);
134
102
  }
135
- validateBody(response);
136
-
137
- let result;
138
- for (const line of response.bodyText.split(/\r?\n/)) {
139
- const separator = line.indexOf(":");
140
- if (separator < 1) continue;
141
- let candidate;
142
- try {
143
- candidate = JSON.parse(line.slice(separator + 1));
144
- } catch (error) {
145
- void error;
146
- continue;
147
- }
148
- const object = record(candidate);
149
- if (object && Object.prototype.hasOwnProperty.call(object, "ok")) {
150
- result = object;
151
- break;
152
- }
103
+ let payload;
104
+ try {
105
+ payload = JSON.parse(body);
106
+ } catch (error) {
107
+ void error;
108
+ throw ctx.fail.parseFailure(overviewFailure);
153
109
  }
154
- if (!result) throw ctx.fail.parseFailure("TypeSafe billing response format changed: missing result.");
155
- if (result.ok !== true) throw ctx.fail.apiFailure("TypeSafe billing request failed.");
156
- const billing = record(
157
- _optionalChain([record, "call", (_4) => _4(result.data), "optionalAccess", (_5) => _5.billing]),
158
- );
159
- if (!billing) throw ctx.fail.parseFailure("TypeSafe billing response format changed: missing billing.");
110
+ const billing = record(_optionalChain([record, "call", (_) => _(payload), "optionalAccess", (_2) => _2.billing]));
111
+ if (!billing) throw ctx.fail.parseFailure(overviewFailure);
160
112
  const number = (value) => (typeof value === "number" && Number.isFinite(value) ? value : undefined);
161
113
  const spent = number(billing.spent);
162
114
  const balance = number(billing.balance);
163
- if (spent === undefined || balance === undefined)
164
- throw ctx.fail.parseFailure("TypeSafe billing response format changed: missing billing numbers.");
115
+ if (spent === undefined || balance === undefined) throw ctx.fail.parseFailure(overviewFailure);
165
116
  const cycleLabel =
166
117
  typeof billing.cycleLabel === "string" && billing.cycleLabel.trim() ? billing.cycleLabel.trim() : undefined;
167
118
  const plan = typeof billing.plan === "string" && billing.plan.trim() ? billing.plan.trim() : undefined;
@@ -171,21 +122,21 @@ defineProvider({
171
122
  : _optionalChain([
172
123
  plan,
173
124
  "optionalAccess",
174
- (_6) => _6.split,
125
+ (_3) => _3.split,
175
126
  "call",
176
- (_7) => _7(/[_-]+/),
127
+ (_4) => _4(/[_-]+/),
177
128
  "access",
178
- (_8) => _8.filter,
129
+ (_5) => _5.filter,
179
130
  "call",
180
- (_9) => _9(Boolean),
131
+ (_6) => _6(Boolean),
181
132
  "access",
182
- (_10) => _10.map,
133
+ (_7) => _7.map,
183
134
  "call",
184
- (_11) => _11((part) => part.charAt(0).toUpperCase() + part.slice(1).toLowerCase()),
135
+ (_8) => _8((part) => part.charAt(0).toUpperCase() + part.slice(1).toLowerCase()),
185
136
  "access",
186
- (_12) => _12.join,
137
+ (_9) => _9.join,
187
138
  "call",
188
- (_13) => _13(" "),
139
+ (_10) => _10(" "),
189
140
  ]);
190
141
  const rows = [{ label: cycleLabel ? `Spent (${cycleLabel})` : "Spent", value: ctx.format.usd(spent) }];
191
142
  if (planLabel) rows.push({ label: "Plan", value: planLabel });
@@ -194,10 +145,10 @@ defineProvider({
194
145
  let creditCount = 0;
195
146
  for (const item of Array.isArray(billing.credits) ? billing.credits : []) {
196
147
  const credit = record(item);
197
- const amount = number(_optionalChain([credit, "optionalAccess", (_14) => _14.amount]));
198
- const remaining = number(_optionalChain([credit, "optionalAccess", (_15) => _15.remaining]));
148
+ const amount = number(_optionalChain([credit, "optionalAccess", (_11) => _11.amount]));
149
+ const remaining = number(_optionalChain([credit, "optionalAccess", (_12) => _12.remaining]));
199
150
  const expiresAt =
200
- typeof _optionalChain([credit, "optionalAccess", (_16) => _16.expiresAt]) === "string"
151
+ typeof _optionalChain([credit, "optionalAccess", (_13) => _13.expiresAt]) === "string"
201
152
  ? credit.expiresAt
202
153
  : undefined;
203
154
  if (amount === undefined || remaining === undefined || remaining <= 0 || !expiresAt) continue;
@@ -17,9 +17,9 @@
17
17
  "setupFields": [],
18
18
  "upstream": {
19
19
  "repository": "https://github.com/steipete/CodexBar",
20
- "revision": "6070b29a1a5d51193a634dc1b0a14a67c39d0ccc",
20
+ "revision": "1f6da9bec44455288ddee5ef6f3617a803d8eb2a",
21
21
  "path": "Sources/CodexBarCore/Resources/Plugins/typesafe.js",
22
- "sha256": "ef710412712c83277acda605517d363a4cea58256e33513477b7b863ccf6dc06",
22
+ "sha256": "da91db172678133393d38836fcb4028342edade8aa7994d2bc5e95290498192f",
23
23
  "license": "MIT"
24
24
  }
25
25
  }
@@ -24,7 +24,7 @@
24
24
  ],
25
25
  "upstream": {
26
26
  "repository": "https://github.com/steipete/CodexBar",
27
- "revision": "6070b29a1a5d51193a634dc1b0a14a67c39d0ccc",
27
+ "revision": "1f6da9bec44455288ddee5ef6f3617a803d8eb2a",
28
28
  "path": "Sources/CodexBarCore/Resources/Plugins/v0.js",
29
29
  "sha256": "5f54a9329dca35d59d76e427c34d4d9db8d3400d0b38c5fcee8899928a900019",
30
30
  "license": "MIT"
@@ -37,7 +37,7 @@
37
37
  ],
38
38
  "upstream": {
39
39
  "repository": "https://github.com/steipete/CodexBar",
40
- "revision": "6070b29a1a5d51193a634dc1b0a14a67c39d0ccc",
40
+ "revision": "1f6da9bec44455288ddee5ef6f3617a803d8eb2a",
41
41
  "path": "Sources/CodexBarCore/Resources/Plugins/vercel.js",
42
42
  "sha256": "27b39f42709e93a2cfbf850171f198069553c99967f9b7ba6d16614862cb084a",
43
43
  "license": "MIT"
@@ -45,7 +45,7 @@
45
45
  "upstreamSources": [
46
46
  {
47
47
  "repository": "https://github.com/steipete/CodexBar",
48
- "revision": "6070b29a1a5d51193a634dc1b0a14a67c39d0ccc",
48
+ "revision": "1f6da9bec44455288ddee5ef6f3617a803d8eb2a",
49
49
  "path": "Sources/CodexBarCore/Resources/Plugins/vercel-budget.js",
50
50
  "sha256": "a65d3affe808517590edc33e4d15b813ba1fa870b4435ef18cb7c7195ab376ad",
51
51
  "license": "MIT"
@@ -17,6 +17,8 @@ defineProvider({
17
17
  { key: "webTimeoutSeconds", title: "Web timeout", type: "plain" },
18
18
  { key: "resetDeadlineMillis", title: "Reset lookup deadline", type: "plain" },
19
19
  { key: "chromeMajorVersion", title: "Chrome major version", type: "plain" },
20
+ { key: "manualUserAgent", title: "User-Agent", type: "plain" },
21
+ { key: "manualUserAgentError", title: "Invalid User-Agent message", type: "plain" },
20
22
  ],
21
23
  capabilities: ["browser-cookies", "http-status"],
22
24
  cookieDomains: ["workbuddy.cn", "www.workbuddy.cn"],
@@ -30,6 +32,16 @@ defineProvider({
30
32
  const domain = "www.workbuddy.cn";
31
33
  const policy = ctx.browser.availability(domain);
32
34
  if (policy === "off") throw ctx.fail.missingCredential("WorkBuddy cookies are disabled.");
35
+ const manualUserAgent = policy === "manual" ? _nullishCoalesce(ctx.settings.get("manualUserAgent"), () => "") : "";
36
+ if (/[^\x20-\x7e]/.test(manualUserAgent)) {
37
+ throw ctx.fail.missingCredential(
38
+ _nullishCoalesce(
39
+ ctx.settings.get("manualUserAgentError"),
40
+ () => "Paste the WorkBuddy User-Agent as a single request header value.",
41
+ ),
42
+ );
43
+ }
44
+ const override = manualUserAgent.trim();
33
45
  const timeoutRaw = Number(_nullishCoalesce(ctx.settings.get("webTimeoutSeconds"), () => "15"));
34
46
  const timeoutSeconds = Number.isFinite(timeoutRaw) ? Math.min(30, Math.max(1, Math.round(timeoutRaw))) : 15;
35
47
  // WorkBuddy binds the session to the browser User-Agent, and Chrome's reduced UA varies only by major version.
@@ -37,8 +49,9 @@ defineProvider({
37
49
  const major = Number(_nullishCoalesce(ctx.settings.get("chromeMajorVersion"), () => ""));
38
50
  const userAgent = (version) =>
39
51
  `Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/${version}.0.0.0 Safari/537.36`;
40
- const userAgents =
52
+ const chromeUserAgents =
41
53
  Number.isSafeInteger(major) && major > 1 ? [userAgent(major), userAgent(major - 1)] : [undefined];
54
+ const userAgents = override ? [override] : chromeUserAgents;
42
55
 
43
56
  const fail = (field) => {
44
57
  throw ctx.fail.parseFailure(`Invalid WorkBuddy billing response: ${field}`);
@@ -105,6 +118,16 @@ defineProvider({
105
118
  // The dashboard refreshes one second after the inclusive end time.
106
119
  return new Date(end.getTime() + 1000);
107
120
  };
121
+ // Spent grants stay in the listing until their cycle ends. The dashboard reports the lapse of the
122
+ // grants that still carry credits, so a grant with nothing left must not bound the reward window.
123
+ const creditsLeft = (value) => {
124
+ try {
125
+ return amount(value, "CycleCapacityRemain");
126
+ } catch (error) {
127
+ void error;
128
+ return undefined;
129
+ }
130
+ };
108
131
  // Package codes the WorkBuddy client sends for its paid and free listings (WorkBuddy 5.6.2).
109
132
  const paidPackageCodes = [
110
133
  "TCACA_code_002_AkiJS3ZHF5",
@@ -130,16 +153,21 @@ defineProvider({
130
153
  "TCACA_code_029_6wCGEWquYy",
131
154
  "TCACA_code_030_BjSt89qTvr",
132
155
  ];
133
- // Reset time is optional: a failed or unexpected package listing must not discard the balance.
134
- const nextReset = async (session, agent) => {
156
+ // Platform reward grants are aggregated under one package code in the summary. Only the free listing
157
+ // carries each grant's own cycle, so the reward expiry has to be read from there.
158
+ const benefitPackageCode = "TCACA_code_007_nzdH5h4Nl0";
159
+ // Reset and reward expiry are optional: a failed or unexpected package listing must not discard the balance.
160
+ const listings = async (session, agent, wantsRewards) => {
135
161
  const now = ctx.date.now().getTime();
136
162
  // Share five seconds across listings, retaining the host's margin for publishing the balance.
137
163
  const hostDeadline = Number(ctx.settings.get("resetDeadlineMillis"));
138
164
  const deadline = Math.min(Date.now() + 5000, hostDeadline > 0 ? hostDeadline : Infinity);
139
- let earliest;
140
- for (const [path, codes] of [
141
- ["/billing/meter/get-user-resource-paid-packages", paidPackageCodes],
142
- ["/billing/meter/get-user-resource-free-packages", freePackageCodes],
165
+ let paidReset;
166
+ let freeReset;
167
+ let rewardExpiry;
168
+ for (const [path, codes, isFree] of [
169
+ ["/billing/meter/get-user-resource-paid-packages", paidPackageCodes, false],
170
+ ["/billing/meter/get-user-resource-free-packages", freePackageCodes, true],
143
171
  ]) {
144
172
  const budget = (deadline - Date.now()) / 1000;
145
173
  if (budget < 1) break;
@@ -152,20 +180,41 @@ defineProvider({
152
180
  { PageNumber: 1, PageSize: 100, PackageCodes: codes, Status: [0, 3] },
153
181
  Math.min(timeoutSeconds, budget),
154
182
  );
155
- if (!Array.isArray(page.Accounts)) continue;
156
- for (const raw of page.Accounts) {
157
- if (raw === null || typeof raw !== "object" || Array.isArray(raw)) continue;
158
- const account = raw;
159
- if (account.CapacityUnit !== "credits") continue;
160
- const end = cycleEnd(account.CycleEndTime);
161
- if (end && end.getTime() > now && (!earliest || end.getTime() < earliest.getTime())) earliest = end;
183
+ if (Array.isArray(page.Accounts)) {
184
+ for (const raw of page.Accounts) {
185
+ if (raw === null || typeof raw !== "object" || Array.isArray(raw)) continue;
186
+ const account = raw;
187
+ if (account.CapacityUnit !== "credits") continue;
188
+ const end = cycleEnd(account.CycleEndTime);
189
+ if (!end || end.getTime() <= now) continue;
190
+ if (isFree) {
191
+ // Reward grants expire piecewise; the earliest grant that still holds credits bounds the window.
192
+ const left = creditsLeft(account.CycleCapacityRemain);
193
+ if (
194
+ account.PackageCode === benefitPackageCode &&
195
+ left !== undefined &&
196
+ left > 0 &&
197
+ (!rewardExpiry || end.getTime() < rewardExpiry.getTime())
198
+ ) {
199
+ rewardExpiry = end;
200
+ }
201
+ // Reward grants expire; they never supply the allowance's fallback reset.
202
+ if (account.PackageCode !== benefitPackageCode && (!freeReset || end.getTime() < freeReset.getTime())) {
203
+ freeReset = end;
204
+ }
205
+ } else if (!paidReset || end.getTime() < paidReset.getTime()) {
206
+ paidReset = end;
207
+ }
208
+ }
162
209
  }
163
210
  } catch (error) {
164
211
  if (error.transportClass === "cancelled") throw error;
165
212
  }
166
- // Paid cycles refill the allowance; free grants may expire earlier.
167
- if (earliest) return earliest;
213
+ // Paid cycles refill the allowance; free grants may expire earlier. The free listing is only
214
+ // worth a request when it answers the reset or bounds the reward window.
215
+ if (!isFree && paidReset && !wantsRewards) break;
168
216
  }
217
+ return { reset: _nullishCoalesce(paidReset, () => freeReset), rewardExpiry };
169
218
  };
170
219
 
171
220
  let rejected = false;
@@ -201,21 +250,58 @@ defineProvider({
201
250
  remaining += amount(item.CycleRemainCapacity, "CycleRemainCapacity");
202
251
  if (item.CycleFrozenCapacity !== undefined) frozen += amount(item.CycleFrozenCapacity, "CycleFrozenCapacity");
203
252
  }
253
+ // Credit groups split the same allowance by origin. Rewards are optional, so an unexpected group
254
+ // shape must not discard the balance the dashboard shows.
255
+ let rewardTotal = 0;
256
+ let rewardRemaining = 0;
257
+ for (const raw of Array.isArray(summary.CreditGroups) ? summary.CreditGroups : []) {
258
+ if (raw === null || typeof raw !== "object" || Array.isArray(raw)) continue;
259
+ const group = raw;
260
+ if (group.Kind !== "benefit" || group.CapacityUnit !== "credits") continue;
261
+ try {
262
+ const groupTotal = amount(group.CycleTotalCapacity, "CycleTotalCapacity");
263
+ const groupRemaining = amount(group.CycleRemainCapacity, "CycleRemainCapacity");
264
+ rewardTotal += groupTotal;
265
+ rewardRemaining += groupRemaining;
266
+ } catch (error) {
267
+ void error;
268
+ }
269
+ }
204
270
  const plan = text(summary.SubscriptionPackageName);
205
271
  // Frozen credits are listed as reported; the meter uses the remaining amount the dashboard shows.
206
272
  const rows = frozen > 0 ? [{ label: "Reserved", value: format(frozen) }] : [];
207
273
  if (total <= 0) {
208
274
  rows.unshift({ label: "Left", value: format(remaining) }, { label: "Total", value: format(total) });
209
275
  }
276
+ const wantsRewards = rewardTotal > 0;
277
+ const cycle = await listings(session, agent, wantsRewards);
278
+ // The window carries the balance; it cannot carry a countdown, because reward credits lapse
279
+ // grant by grant instead of resetting, so the expiry stays a detail row.
280
+ if (wantsRewards && cycle.rewardExpiry) {
281
+ rows.push({ label: "Reward expiry", value: ctx.format.monthDay(cycle.rewardExpiry.getTime()) });
282
+ }
283
+ const extraWindows = wantsRewards
284
+ ? [
285
+ {
286
+ id: "rewards",
287
+ title: "Rewards",
288
+ window: {
289
+ usedPercent: ctx.pct(Math.max(0, rewardTotal - rewardRemaining), rewardTotal),
290
+ resetDescription: `${format(rewardRemaining)} / ${format(rewardTotal)} credits left`,
291
+ },
292
+ },
293
+ ]
294
+ : [];
210
295
  return {
211
296
  primary:
212
297
  total > 0
213
298
  ? {
214
299
  usedPercent: ctx.pct(Math.max(0, total - remaining), total),
215
- resetsAt: await nextReset(session, agent),
300
+ resetsAt: cycle.reset,
216
301
  resetDescription: `${format(remaining)} / ${format(total)} credits left`,
217
302
  }
218
303
  : undefined,
304
+ extraWindows: extraWindows.length ? extraWindows : undefined,
219
305
  details: rows.length ? [{ title: "Credits", rows }] : undefined,
220
306
  identity: plan ? { loginMethod: plan } : undefined,
221
307
  dataConfidence: "exact",
@@ -34,6 +34,18 @@
34
34
  "type": "string",
35
35
  "description": "Provider-specific account or source selection."
36
36
  },
37
+ {
38
+ "key": "manualUserAgent",
39
+ "title": "User-Agent",
40
+ "type": "string",
41
+ "description": "Provider-specific account or source selection."
42
+ },
43
+ {
44
+ "key": "manualUserAgentError",
45
+ "title": "Invalid User-Agent message",
46
+ "type": "string",
47
+ "description": "Provider-specific account or source selection."
48
+ },
37
49
  {
38
50
  "key": "browserUserAgent",
39
51
  "title": "Browser User-Agent",
@@ -43,9 +55,9 @@
43
55
  ],
44
56
  "upstream": {
45
57
  "repository": "https://github.com/steipete/CodexBar",
46
- "revision": "6070b29a1a5d51193a634dc1b0a14a67c39d0ccc",
58
+ "revision": "1f6da9bec44455288ddee5ef6f3617a803d8eb2a",
47
59
  "path": "Sources/CodexBarCore/Resources/Plugins/workbuddy.js",
48
- "sha256": "9da73ec195a174e3ee96e3096a47db0b69295bc52dcfceb9fd9d1a2b8f74b08a",
60
+ "sha256": "4a440637dc7a8ec77592edf5d0fe50181eff9919be08fefa7007bc50476a4301",
49
61
  "license": "MIT"
50
62
  }
51
63
  }
@@ -17,7 +17,7 @@
17
17
  "setupFields": [],
18
18
  "upstream": {
19
19
  "repository": "https://github.com/steipete/CodexBar",
20
- "revision": "6070b29a1a5d51193a634dc1b0a14a67c39d0ccc",
20
+ "revision": "1f6da9bec44455288ddee5ef6f3617a803d8eb2a",
21
21
  "path": "Sources/CodexBarCore/Resources/Plugins/xapi.js",
22
22
  "sha256": "c8aff7628f2a67a8726486d847747dd8121123da6526b2793b4b7b36ec37930f",
23
23
  "license": "MIT"
@@ -16,7 +16,7 @@
16
16
  "setupFields": [],
17
17
  "upstream": {
18
18
  "repository": "https://github.com/steipete/CodexBar",
19
- "revision": "6070b29a1a5d51193a634dc1b0a14a67c39d0ccc",
19
+ "revision": "1f6da9bec44455288ddee5ef6f3617a803d8eb2a",
20
20
  "path": "Sources/CodexBarCore/Resources/Plugins/xkiro.js",
21
21
  "sha256": "03bfcb8e518577ee18dd335e4e1e1f7d629d3e5abdf2216b187b1cba8b683853",
22
22
  "license": "MIT"
package/usagestat CHANGED
Binary file
package/usagestatd CHANGED
Binary file