@hashimkarim/usagestat-linux-x64-gnu 2.0.1 → 2.1.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.
- package/native-manifest.json +743 -119
- package/package.json +106 -2
- package/plugins/abliteration/LICENSE +21 -0
- package/plugins/abliteration/icon.svg +5 -0
- package/plugins/abliteration/plugin.js +89 -0
- package/plugins/abliteration/plugin.json +24 -0
- package/plugins/aerostack/LICENSE +21 -0
- package/plugins/aerostack/icon.svg +1 -0
- package/plugins/aerostack/plugin.js +94 -0
- package/plugins/aerostack/plugin.json +24 -0
- package/plugins/aixy/icon.svg +1 -0
- package/plugins/aixy/plugin.js +5 -0
- package/plugins/aixy/plugin.json +9 -1
- package/plugins/antigravity/plugin.js +20 -11
- package/plugins/antigravity-cli/plugin.js +9 -4
- package/plugins/antigravity-ide/plugin.js +8 -5
- package/plugins/atlascloud/plugin.js +5 -0
- package/plugins/atlascloud/plugin.json +2 -1
- package/plugins/bifrost/icon.svg +3 -0
- package/plugins/bifrost/plugin.js +5 -0
- package/plugins/bifrost/plugin.json +9 -1
- package/plugins/chatbox/LICENSE +21 -0
- package/plugins/chatbox/icon.svg +4 -0
- package/plugins/chatbox/plugin.js +208 -0
- package/plugins/chatbox/plugin.json +24 -0
- package/plugins/claude/plugin.js +39 -2
- package/plugins/claudeplatform/LICENSE +21 -0
- package/plugins/claudeplatform/icon.svg +3 -0
- package/plugins/claudeplatform/plugin.js +244 -0
- package/plugins/claudeplatform/plugin.json +25 -0
- package/plugins/codex/plugin.js +3 -2
- package/plugins/codex/plugin.json +1 -1
- package/plugins/cosmic/LICENSE +21 -0
- package/plugins/cosmic/icon.svg +1 -0
- package/plugins/cosmic/plugin.js +134 -0
- package/plugins/cosmic/plugin.json +32 -0
- package/plugins/cursor/plugin.js +33 -8
- package/plugins/cursor-nightly/plugin.json +1 -1
- package/plugins/devpass/icon.svg +3 -0
- package/plugins/devpass/plugin.js +5 -0
- package/plugins/devpass/plugin.json +2 -1
- package/plugins/exa/LICENSE +21 -0
- package/plugins/exa/icon-color.svg +1 -0
- package/plugins/exa/icon.svg +1 -0
- package/plugins/exa/plugin.js +75 -0
- package/plugins/exa/plugin.json +32 -0
- package/plugins/fal/LICENSE +21 -0
- package/plugins/fal/icon-color.svg +1 -0
- package/plugins/fal/icon.svg +1 -0
- package/plugins/fal/plugin.js +74 -0
- package/plugins/fal/plugin.json +24 -0
- package/plugins/flora/LICENSE +21 -0
- package/plugins/flora/icon.svg +4 -0
- package/plugins/flora/plugin.js +93 -0
- package/plugins/flora/plugin.json +32 -0
- package/plugins/futureagi/LICENSE +21 -0
- package/plugins/futureagi/icon.svg +24 -0
- package/plugins/futureagi/plugin.js +139 -0
- package/plugins/futureagi/plugin.json +32 -0
- package/plugins/gemini-apps/LICENSE +1 -2
- package/plugins/gemini-apps/plugin.js +136 -465
- package/plugins/gemini-apps/plugin.json +13 -7
- package/plugins/gitkraken/icon.svg +2 -0
- package/plugins/gitkraken/plugin.js +5 -0
- package/plugins/gitkraken/plugin.json +9 -1
- package/plugins/helmcode/icon.svg +5 -0
- package/plugins/helmcode/plugin.js +49 -41
- package/plugins/helmcode/plugin.json +10 -2
- package/plugins/huggingface/plugin.js +6 -1
- package/plugins/huggingface/plugin.json +4 -3
- package/plugins/hyper/plugin.js +53 -47
- package/plugins/hyper/plugin.json +3 -2
- package/plugins/kimi/plugin.js +39 -14
- package/plugins/langdock/LICENSE +21 -0
- package/plugins/langdock/icon.svg +4 -0
- package/plugins/langdock/plugin.js +106 -0
- package/plugins/langdock/plugin.json +25 -0
- package/plugins/linkup/LICENSE +21 -0
- package/plugins/linkup/icon.svg +10 -0
- package/plugins/linkup/plugin.js +46 -0
- package/plugins/linkup/plugin.json +24 -0
- package/plugins/lithosai/LICENSE +21 -0
- package/plugins/lithosai/icon.svg +1 -0
- package/plugins/lithosai/plugin.js +130 -0
- package/plugins/lithosai/plugin.json +26 -0
- package/plugins/llmman/icon.svg +4 -0
- package/plugins/llmman/plugin.js +5 -0
- package/plugins/llmman/plugin.json +9 -1
- package/plugins/munito/LICENSE +21 -0
- package/plugins/munito/icon.svg +7 -0
- package/plugins/munito/plugin.js +171 -0
- package/plugins/munito/plugin.json +24 -0
- package/plugins/muse/icon.svg +3 -0
- package/plugins/muse/plugin.js +98 -129
- package/plugins/muse/plugin.json +10 -2
- package/plugins/museai/LICENSE +21 -0
- package/plugins/museai/icon.svg +3 -0
- package/plugins/museai/plugin.js +159 -0
- package/plugins/museai/plugin.json +26 -0
- package/plugins/nous/plugin.js +5 -0
- package/plugins/nous/plugin.json +9 -1
- package/plugins/ollama/plugin.js +58 -0
- package/plugins/opencode-go/plugin.js +72 -24
- package/plugins/parallel/LICENSE +21 -0
- package/plugins/parallel/icon.svg +11 -0
- package/plugins/parallel/plugin.js +97 -0
- package/plugins/parallel/plugin.json +25 -0
- package/plugins/qwencloud/LICENSE +21 -0
- package/plugins/qwencloud/plugin.js +197 -0
- package/plugins/qwencloud/plugin.json +17 -0
- package/plugins/raycast/icon.svg +4 -0
- package/plugins/raycast/plugin.js +5 -0
- package/plugins/raycast/plugin.json +9 -1
- package/plugins/replicate/plugin.js +5 -0
- package/plugins/replicate/plugin.json +2 -1
- package/plugins/sailresearch/LICENSE +21 -0
- package/plugins/sailresearch/icon.svg +4 -0
- package/plugins/sailresearch/plugin.js +104 -0
- package/plugins/sailresearch/plugin.json +24 -0
- package/plugins/sofya/LICENSE +21 -0
- package/plugins/sofya/icon.svg +3 -0
- package/plugins/sofya/plugin.js +86 -0
- package/plugins/sofya/plugin.json +24 -0
- package/plugins/soniox/LICENSE +21 -0
- package/plugins/soniox/icon.svg +3 -0
- package/plugins/soniox/plugin.js +103 -0
- package/plugins/soniox/plugin.json +24 -0
- package/plugins/tavily/LICENSE +21 -0
- package/plugins/tavily/icon.svg +3 -0
- package/plugins/tavily/plugin.js +61 -0
- package/plugins/tavily/plugin.json +24 -0
- package/plugins/tinyapi/LICENSE +21 -0
- package/plugins/tinyapi/icon.svg +4 -0
- package/plugins/tinyapi/plugin.js +73 -0
- package/plugins/tinyapi/plugin.json +25 -0
- package/plugins/traecn/LICENSE +21 -0
- package/plugins/traecn/icon.svg +3 -0
- package/plugins/traecn/plugin.js +104 -0
- package/plugins/traecn/plugin.json +25 -0
- package/plugins/typesafe/icon.svg +4 -0
- package/plugins/typesafe/plugin.js +30 -25
- package/plugins/typesafe/plugin.json +3 -2
- package/plugins/v0/plugin.js +5 -0
- package/plugins/v0/plugin.json +9 -1
- package/plugins/vercel/plugin.js +147 -0
- package/plugins/vercel/plugin.json +34 -3
- package/plugins/workbuddy/LICENSE +21 -0
- package/plugins/workbuddy/icon.svg +1 -0
- package/plugins/workbuddy/plugin.js +232 -0
- package/plugins/workbuddy/plugin.json +51 -0
- package/plugins/xai/plugin.js +9 -0
- package/plugins/xapi/LICENSE +21 -0
- package/plugins/xapi/icon.svg +1 -0
- package/plugins/xapi/plugin.js +90 -0
- package/plugins/xapi/plugin.json +25 -0
- package/plugins/xkiro/icon.svg +1 -0
- package/plugins/xkiro/plugin.js +5 -0
- package/plugins/xkiro/plugin.json +2 -1
- package/usagestat +0 -0
- package/usagestatd +0 -0
- package/plugins/cursor-nightly/plugin.js +0 -1416
package/plugins/muse/plugin.js
CHANGED
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
// Generated by tools/vendor-codexbar-providers.cjs; see LICENSE.
|
|
2
2
|
globalThis.__usagestat_bundled_options = {"labels":["Session","Weekly","Additional"],"defaults":{},"autoMode":"api"};
|
|
3
|
+
(function(defineProvider) {
|
|
4
|
+
// BEGIN UPSTREAM Sources/CodexBarCore/Resources/Plugins/muse.js
|
|
3
5
|
function _nullishCoalesce(lhs, rhsFn) {
|
|
4
6
|
if (lhs != null) {
|
|
5
7
|
return lhs;
|
|
@@ -28,27 +30,6 @@ function _optionalChain(ops) {
|
|
|
28
30
|
}
|
|
29
31
|
return value;
|
|
30
32
|
}
|
|
31
|
-
async function _asyncOptionalChain(ops) {
|
|
32
|
-
let lastAccessLHS = undefined;
|
|
33
|
-
let value = ops[0];
|
|
34
|
-
let i = 1;
|
|
35
|
-
while (i < ops.length) {
|
|
36
|
-
const op = ops[i];
|
|
37
|
-
const fn = ops[i + 1];
|
|
38
|
-
i += 2;
|
|
39
|
-
if ((op === "optionalAccess" || op === "optionalCall") && value == null) {
|
|
40
|
-
return undefined;
|
|
41
|
-
}
|
|
42
|
-
if (op === "access" || op === "optionalAccess") {
|
|
43
|
-
lastAccessLHS = value;
|
|
44
|
-
value = await fn(value);
|
|
45
|
-
} else if (op === "call" || op === "optionalCall") {
|
|
46
|
-
value = await fn((...args) => value.call(lastAccessLHS, ...args));
|
|
47
|
-
lastAccessLHS = undefined;
|
|
48
|
-
}
|
|
49
|
-
}
|
|
50
|
-
return value;
|
|
51
|
-
}
|
|
52
33
|
defineProvider({
|
|
53
34
|
id: "muse",
|
|
54
35
|
name: "Muse Code",
|
|
@@ -117,112 +98,113 @@ defineProvider({
|
|
|
117
98
|
throw ctx.fail.permissionDenied("No Muse Code subscription is active on this login.");
|
|
118
99
|
}
|
|
119
100
|
const plan = text(root.subs_tier_name, "subs_tier_name");
|
|
101
|
+
const loginEmail = text(root.user_email, "user_email");
|
|
120
102
|
const rows = [];
|
|
121
103
|
if (plan) rows.push({ label: "Plan", value: plan });
|
|
104
|
+
const details = [{ title: "Muse Code subscription", rows }];
|
|
122
105
|
const snapshot = {
|
|
123
|
-
details
|
|
124
|
-
identity: { email:
|
|
106
|
+
details,
|
|
107
|
+
identity: { email: loginEmail, loginMethod: _nullishCoalesce(plan, () => "Muse login") },
|
|
125
108
|
dataConfidence: "unknown",
|
|
126
109
|
};
|
|
127
110
|
const percentLabel = (value) => `${ctx.format.number(value, { maximumFractionDigits: 0 })}%`;
|
|
128
111
|
|
|
129
|
-
// The browser quota belongs to one dev.meta.ai team, which the user must choose explicitly: a session can
|
|
130
|
-
// see several teams, and list order says nothing about which one holds the CLI login's subscription.
|
|
131
112
|
async function webQuota() {
|
|
132
113
|
if (ctx.browser.availability("dev.meta.ai") === "off") return undefined;
|
|
114
|
+
// The browser session must belong to the same Meta account as the CLI login.
|
|
115
|
+
const expectedEmail = _nullishCoalesce(loginEmail, () => "").toLowerCase();
|
|
116
|
+
if (!expectedEmail) return undefined;
|
|
117
|
+
let requestsLeft = 5;
|
|
118
|
+
// Only a rejected session returns null; other unavailable reads keep the cookie.
|
|
119
|
+
const get = async (session, path, forbiddenRejectsSession = true) => {
|
|
120
|
+
if (requestsLeft-- <= 0) throw new Error("Muse browser request budget exhausted");
|
|
121
|
+
const headers = { Cookie: _nullishCoalesce(session.header, () => ""), "User-Agent": "CodexBar" };
|
|
122
|
+
const response = await ctx.http.get(`https://dev.meta.ai${path}`, { headers, timeoutSeconds: 8 });
|
|
123
|
+
if (response.status === 401 || (response.status === 403 && forbiddenRejectsSession)) {
|
|
124
|
+
ctx.browser.rejectCookie("dev.meta.ai", session);
|
|
125
|
+
return null;
|
|
126
|
+
}
|
|
127
|
+
if (response.status !== 200) return {};
|
|
128
|
+
const value = JSON.parse(response.bodyText);
|
|
129
|
+
return value && typeof value === "object" && !Array.isArray(value) ? value : {};
|
|
130
|
+
};
|
|
133
131
|
try {
|
|
134
|
-
|
|
132
|
+
// dev.meta.ai can report a blank email; the session user's team membership then supplies it. Checking
|
|
133
|
+
// membership costs a request, so such sessions wait until no session matches the login by email.
|
|
134
|
+
const unverified = [];
|
|
135
135
|
for await (const session of ctx.browser.sessions("dev.meta.ai")) {
|
|
136
136
|
if (requestsLeft <= 0) break;
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
const
|
|
140
|
-
|
|
141
|
-
const
|
|
142
|
-
if (
|
|
143
|
-
|
|
144
|
-
ctx.browser.rejectCookie("dev.meta.ai", session);
|
|
145
|
-
return undefined;
|
|
146
|
-
}
|
|
147
|
-
if (response.status !== 200) return undefined;
|
|
148
|
-
const value = JSON.parse(response.bodyText);
|
|
149
|
-
return value && typeof value === "object" && !Array.isArray(value) ? value : undefined;
|
|
150
|
-
};
|
|
151
|
-
// The browser session must belong to the same Meta account as the CLI login.
|
|
152
|
-
const loginEmail = _optionalChain([
|
|
153
|
-
text,
|
|
154
|
-
"call",
|
|
155
|
-
(_3) => _3(root.user_email, "user_email"),
|
|
156
|
-
"optionalAccess",
|
|
157
|
-
(_4) => _4.toLowerCase,
|
|
158
|
-
"call",
|
|
159
|
-
(_5) => _5(),
|
|
160
|
-
]);
|
|
161
|
-
const me = await get("/api/auth/me");
|
|
162
|
-
const webEmail =
|
|
163
|
-
typeof _optionalChain([me, "optionalAccess", (_6) => _6.email]) === "string"
|
|
164
|
-
? me.email.trim().toLowerCase()
|
|
165
|
-
: undefined;
|
|
166
|
-
if (!loginEmail || !webEmail || loginEmail !== webEmail) continue;
|
|
167
|
-
const listed = await _asyncOptionalChain([
|
|
168
|
-
await get("/api/portal/teams"),
|
|
169
|
-
"optionalAccess",
|
|
170
|
-
async (_7) => _7.teams,
|
|
171
|
-
]);
|
|
172
|
-
if (rejected) continue;
|
|
173
|
-
if (!Array.isArray(listed)) return undefined;
|
|
174
|
-
const teams = [];
|
|
175
|
-
for (const entry of listed) {
|
|
176
|
-
const item = entry && typeof entry === "object" ? entry : {};
|
|
177
|
-
const id =
|
|
178
|
-
typeof item.team_id === "string"
|
|
179
|
-
? item.team_id
|
|
180
|
-
: Number.isSafeInteger(item.team_id)
|
|
181
|
-
? String(item.team_id)
|
|
182
|
-
: "";
|
|
183
|
-
if (!/^[0-9]+$/.test(id)) continue;
|
|
184
|
-
const name = typeof item.team_name === "string" && item.team_name.trim() ? item.team_name.trim() : id;
|
|
185
|
-
teams.push({ id, name });
|
|
186
|
-
}
|
|
187
|
-
const selected = _nullishCoalesce(
|
|
188
|
-
_optionalChain([
|
|
189
|
-
ctx,
|
|
190
|
-
"access",
|
|
191
|
-
(_8) => _8.settings,
|
|
192
|
-
"access",
|
|
193
|
-
(_9) => _9.get,
|
|
194
|
-
"call",
|
|
195
|
-
(_10) => _10("MUSE_WEB_TEAM_ID"),
|
|
196
|
-
"optionalAccess",
|
|
197
|
-
(_11) => _11.trim,
|
|
198
|
-
"call",
|
|
199
|
-
(_12) => _12(),
|
|
200
|
-
]),
|
|
201
|
-
() => "",
|
|
202
|
-
);
|
|
203
|
-
const team = teams.find((candidate) => candidate.id === selected);
|
|
204
|
-
if (!selected) return { teams, note: "Choose a browser team in Muse Code settings" };
|
|
205
|
-
if (!team) return { teams, note: "The selected browser team is not visible to this session" };
|
|
206
|
-
const quota = await _asyncOptionalChain([
|
|
207
|
-
await get(`/api/portal/teams/${team.id}/subscription-quota`),
|
|
208
|
-
"optionalAccess",
|
|
209
|
-
async (_13) => _13.subscription_quota,
|
|
210
|
-
]);
|
|
211
|
-
if (rejected) continue;
|
|
212
|
-
if (!quota || typeof quota !== "object")
|
|
213
|
-
return { teams, note: "No subscription quota for the selected team" };
|
|
214
|
-
const record = quota;
|
|
215
|
-
// The team's quota must be for the same plan the CLI login reports.
|
|
216
|
-
if (!plan || typeof record.tier !== "string" || record.tier.trim() !== plan) {
|
|
217
|
-
return { teams, note: "The selected team's plan differs from the Muse login" };
|
|
137
|
+
const me = await get(session, "/api/auth/me");
|
|
138
|
+
if (!me) continue;
|
|
139
|
+
const webEmail = typeof me.email === "string" ? me.email.trim().toLowerCase() : "";
|
|
140
|
+
if (!webEmail) {
|
|
141
|
+
const userID = numericID(me.userId);
|
|
142
|
+
if (userID) unverified.push({ session, userID });
|
|
143
|
+
continue;
|
|
218
144
|
}
|
|
219
|
-
|
|
220
|
-
|
|
145
|
+
if (webEmail !== expectedEmail) continue;
|
|
146
|
+
const result = await teamQuota(session);
|
|
147
|
+
if (result !== "skip") return result;
|
|
148
|
+
}
|
|
149
|
+
for (const { session, userID } of unverified) {
|
|
150
|
+
if (requestsLeft <= 0) break;
|
|
151
|
+
const result = await teamQuota(session, userID);
|
|
152
|
+
if (result !== "skip") return result;
|
|
221
153
|
}
|
|
222
154
|
} catch (error) {
|
|
223
155
|
void error;
|
|
224
156
|
}
|
|
225
157
|
return undefined;
|
|
158
|
+
|
|
159
|
+
// Returns "skip" when the next session should be tried. `userID` asks for a membership check first.
|
|
160
|
+
async function teamQuota(session, userID) {
|
|
161
|
+
const response = await get(session, "/api/portal/teams");
|
|
162
|
+
if (!response) return "skip";
|
|
163
|
+
const listed = response.teams;
|
|
164
|
+
// A session not yet bound to the login must not end the search for a later bound session.
|
|
165
|
+
if (!Array.isArray(listed)) return userID ? "skip" : undefined;
|
|
166
|
+
const teams = [];
|
|
167
|
+
for (const entry of listed) {
|
|
168
|
+
const item = entry && typeof entry === "object" ? entry : {};
|
|
169
|
+
const id = numericID(item.team_id);
|
|
170
|
+
if (!id) continue;
|
|
171
|
+
const name = typeof item.team_name === "string" && item.team_name.trim() ? item.team_name.trim() : id;
|
|
172
|
+
teams.push({ id, name });
|
|
173
|
+
}
|
|
174
|
+
const selected = _nullishCoalesce(ctx.settings.get("MUSE_WEB_TEAM_ID"), () => "").trim();
|
|
175
|
+
const team = teams.find((candidate) => candidate.id === selected);
|
|
176
|
+
if (userID) {
|
|
177
|
+
// Accept the session only if a visible team lists its user with the login's email.
|
|
178
|
+
const probe = _nullishCoalesce(team, () => teams[0]);
|
|
179
|
+
if (!probe) return "skip";
|
|
180
|
+
// Member lists can be forbidden to some roles; that does not mean the session expired.
|
|
181
|
+
const membership = await get(session, `/api/portal/teams/${probe.id}/members`, false);
|
|
182
|
+
if (!membership || !Array.isArray(membership.members)) return "skip";
|
|
183
|
+
const own = membership.members.find((member) => member && numericID(member.user_id) === userID);
|
|
184
|
+
if (
|
|
185
|
+
typeof _optionalChain([own, "optionalAccess", (_3) => _3.email]) !== "string" ||
|
|
186
|
+
own.email.trim().toLowerCase() !== expectedEmail
|
|
187
|
+
)
|
|
188
|
+
return "skip";
|
|
189
|
+
}
|
|
190
|
+
if (!selected) return { teams, note: "Choose a browser team in Muse Code settings" };
|
|
191
|
+
if (!team) return { teams, note: "The selected browser team is not visible to this session" };
|
|
192
|
+
const quotaResponse = await get(session, `/api/portal/teams/${team.id}/subscription-quota`);
|
|
193
|
+
if (!quotaResponse) return "skip";
|
|
194
|
+
const quota = quotaResponse.subscription_quota;
|
|
195
|
+
if (!quota || typeof quota !== "object") return { teams, note: "No subscription quota for the selected team" };
|
|
196
|
+
const record = quota;
|
|
197
|
+
// The team's quota must be for the same plan the CLI login reports.
|
|
198
|
+
if (!plan || typeof record.tier !== "string" || record.tier.trim() !== plan) {
|
|
199
|
+
return { teams, note: "The selected team's plan differs from the Muse login" };
|
|
200
|
+
}
|
|
201
|
+
const parsed = parseWebQuota(record);
|
|
202
|
+
return parsed ? { teams, quota: { team, ...parsed } } : { teams };
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
function numericID(value) {
|
|
206
|
+
const id = typeof value === "string" ? value.trim() : Number.isSafeInteger(value) ? String(value) : "";
|
|
207
|
+
return /^[0-9]+$/.test(id) ? id : undefined;
|
|
226
208
|
}
|
|
227
209
|
function parseWebQuota(quota) {
|
|
228
210
|
// Limits and usage are weighted token counts encoded as decimal strings.
|
|
@@ -267,20 +249,12 @@ defineProvider({
|
|
|
267
249
|
// The login response omits quotas while the 5-hour window is idle, even when the weekly limit has usage.
|
|
268
250
|
// The dev.meta.ai usage page reads the same subscription quota with the browser session.
|
|
269
251
|
const web = await webQuota();
|
|
270
|
-
if (!_optionalChain([web, "optionalAccess", (
|
|
252
|
+
if (!_optionalChain([web, "optionalAccess", (_4) => _4.quota]))
|
|
271
253
|
rows.push({ label: "Quota", value: "Not included in this login response" });
|
|
272
254
|
if (!web) return snapshot;
|
|
273
|
-
const teamRows =
|
|
255
|
+
const teamRows = web.teams.map((team) => ({ label: team.name, value: team.id }));
|
|
274
256
|
if (web.note) teamRows.unshift({ label: "Status", value: web.note });
|
|
275
|
-
|
|
276
|
-
snapshot,
|
|
277
|
-
"access",
|
|
278
|
-
(_15) => _15.details,
|
|
279
|
-
"optionalAccess",
|
|
280
|
-
(_16) => _16.push,
|
|
281
|
-
"call",
|
|
282
|
-
(_17) => _17({ title: "Browser teams", rows: teamRows }),
|
|
283
|
-
]);
|
|
257
|
+
details.push({ title: "Browser teams", rows: teamRows });
|
|
284
258
|
if (!web.quota) return snapshot;
|
|
285
259
|
const { team, primary, secondary } = web.quota;
|
|
286
260
|
const webRows = [
|
|
@@ -288,15 +262,7 @@ defineProvider({
|
|
|
288
262
|
{ label: "5 hours", value: percentLabel(primary.usedPercent) },
|
|
289
263
|
{ label: "Weekly", value: percentLabel(secondary.usedPercent) },
|
|
290
264
|
];
|
|
291
|
-
|
|
292
|
-
snapshot,
|
|
293
|
-
"access",
|
|
294
|
-
(_18) => _18.details,
|
|
295
|
-
"optionalAccess",
|
|
296
|
-
(_19) => _19.push,
|
|
297
|
-
"call",
|
|
298
|
-
(_20) => _20({ title: "Browser team quota (dev.meta.ai)", rows: webRows }),
|
|
299
|
-
]);
|
|
265
|
+
details.push({ title: "Browser team quota (dev.meta.ai)", rows: webRows });
|
|
300
266
|
// Weighted usage reported for a user-selected team, not by the CLI login itself.
|
|
301
267
|
return { usage: { ...snapshot, primary, secondary, dataConfidence: "estimated" }, sourceLabel: "oauth+web" };
|
|
302
268
|
}
|
|
@@ -317,3 +283,6 @@ defineProvider({
|
|
|
317
283
|
};
|
|
318
284
|
},
|
|
319
285
|
});
|
|
286
|
+
|
|
287
|
+
// END UPSTREAM
|
|
288
|
+
})(globalThis.defineProvider);
|
package/plugins/muse/plugin.json
CHANGED
|
@@ -16,11 +16,19 @@
|
|
|
16
16
|
"MUSE_WEB_TEAM_ID",
|
|
17
17
|
"MUSE_COOKIE"
|
|
18
18
|
],
|
|
19
|
+
"setupFields": [
|
|
20
|
+
{
|
|
21
|
+
"key": "MUSE_WEB_TEAM_ID",
|
|
22
|
+
"title": "Browser team ID",
|
|
23
|
+
"type": "string",
|
|
24
|
+
"description": "Provider-specific account or source selection."
|
|
25
|
+
}
|
|
26
|
+
],
|
|
19
27
|
"upstream": {
|
|
20
28
|
"repository": "https://github.com/steipete/CodexBar",
|
|
21
|
-
"revision": "
|
|
29
|
+
"revision": "6070b29a1a5d51193a634dc1b0a14a67c39d0ccc",
|
|
22
30
|
"path": "Sources/CodexBarCore/Resources/Plugins/muse.js",
|
|
23
|
-
"sha256": "
|
|
31
|
+
"sha256": "85b6f9489f4e004d027cca1fe2a0a87884792d0be7aa50ef7b8df3b17902c424",
|
|
24
32
|
"license": "MIT"
|
|
25
33
|
}
|
|
26
34
|
}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Peter Steinberger
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
|
@@ -0,0 +1,3 @@
|
|
|
1
|
+
<svg viewBox="-2.00 -2.04 104.08 104.08" fill="none" xmlns="http://www.w3.org/2000/svg">
|
|
2
|
+
<path fill="currentColor" d="M24.6257 16.1214C28.2365 16.1214 30.8187 18.9408 30.8187 23.3643C30.8185 27.7868 30.1055 31.7765 28.9225 40.2466C28.0743 46.1813 26.62 55.2418 26.0132 59.0088C27.1782 56.7815 29.9074 51.5757 30.3914 50.5982C36.1081 39.061 42.0657 28.7544 43.7826 26.0254C46.0594 22.382 48.062 20.9229 51.1597 20.9229C52.7685 20.923 54.6826 21.7357 55.6315 22.819C56.9178 24.2895 57.1859 26.6602 57.1411 29.244C57.0738 33.2073 56.5599 39.5264 55.8553 46.5698C55.5416 49.7057 54.9365 56.1066 54.5288 60.4004C58.1883 53.8532 64.9609 41.8109 67.6921 37.6058C70.0727 33.9402 72.466 31.5797 75.6836 31.5796C78.4595 31.5796 81.8809 33.5416 81.6772 38.7573C81.3774 46.4556 80.2673 63.2232 79.7607 70.7479C83.1662 65.7913 87.6377 58.4543 89.3148 55.6966C91.0104 52.908 92.75 50.6542 95.3898 50.5778C98.0987 50.4995 99.913 52.341 99.9959 55.1839C100.078 58.0277 98.3835 61.2256 96.1344 64.856C93.6937 68.7957 90.9621 73.0123 87.6383 76.9491C84.3148 80.8856 81.2604 83.8782 76.5991 83.8786C72.4243 83.8786 69.4869 81.1043 69.283 76.0254C69.1768 73.364 69.6804 67.6405 70.0602 63.1673C70.5 57.9875 71.0315 52.9861 71.403 49.6786C67.7337 56.3979 60.0101 70.4676 56.5348 75.9603C53.1343 81.3339 51.4774 83.2269 47.8882 83.2275C44.2977 83.2275 42.7226 80.6416 42.6717 76.7497C42.6142 72.5285 45.7852 47.1821 46.8587 38.737C45.1183 42.4616 40.839 51.6056 38.1551 57.0435C35.5587 62.3055 31.204 71.3978 28.0436 76.6357C25.7668 80.4094 23.2092 83.2273 19.4173 83.2275C15.6252 83.2275 13.4847 80.6784 13.4847 76.5788C13.4847 72.4794 14.6716 65.6492 15.9465 57.1777C16.5839 53.0781 17.3108 48.6133 18.1315 43.8761C18.7324 40.448 19.7764 34.4169 20.4183 30.7129C17.502 35.2539 12.2536 43.4278 10.6567 45.8903C8.88113 48.6291 7.28747 50.626 4.64681 50.6266C1.7329 50.6266 0.000532901 48.3522 0 45.7113C0 43.0703 1.34323 41.0344 3.52376 37.4512C6.32403 32.8465 11.3578 26.57 14.1154 23.2747C17.3656 19.3912 20.6139 16.1219 24.6257 16.1214Z"/>
|
|
3
|
+
</svg>
|
|
@@ -0,0 +1,159 @@
|
|
|
1
|
+
// Generated by tools/vendor-codexbar-providers.cjs; see LICENSE.
|
|
2
|
+
globalThis.__usagestat_bundled_options = {"labels":["Weekly","Additional tokens","Additional"],"defaults":{},"autoMode":"web","maxRequests":160};
|
|
3
|
+
(function(defineProvider) {
|
|
4
|
+
// BEGIN UPSTREAM Sources/CodexBarCore/Resources/Plugins/museai.js
|
|
5
|
+
defineProvider({
|
|
6
|
+
id: "museai",
|
|
7
|
+
name: "Muse (muse.ai)",
|
|
8
|
+
endpoints: ["https://muse.ai"],
|
|
9
|
+
settings: [],
|
|
10
|
+
capabilities: ["browser-cookies", "http-status", "persistent-storage"],
|
|
11
|
+
cookieDomains: ["muse.ai"],
|
|
12
|
+
async fetchUsage(ctx) {
|
|
13
|
+
const origin = "https://muse.ai";
|
|
14
|
+
const userAgent =
|
|
15
|
+
"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/143.0.0.0 Safari/537.36";
|
|
16
|
+
const policy = ctx.browser.availability("muse.ai");
|
|
17
|
+
if (policy === "off") throw ctx.fail.missingCredential("muse.ai cookies are disabled.");
|
|
18
|
+
const signedOut = (r) =>
|
|
19
|
+
r.status === 401 || r.status === 403 || (r.headers["location"] ?? "").startsWith("https://auth.muse.ai/");
|
|
20
|
+
const staleAction = (r) => r.status === 404 && /server action not found/i.test(r.bodyText);
|
|
21
|
+
const discoveryFailure = () =>
|
|
22
|
+
ctx.fail.parseFailure(
|
|
23
|
+
"Could not find a working muse.ai subscription action. muse.ai may have changed. Refresh to retry.",
|
|
24
|
+
);
|
|
25
|
+
const post = (cookie, actionID) =>
|
|
26
|
+
ctx.http.post(`${origin}/`, {
|
|
27
|
+
body: [{ includeAgreement: true }],
|
|
28
|
+
headers: {
|
|
29
|
+
Cookie: cookie,
|
|
30
|
+
Accept: "text/x-component",
|
|
31
|
+
"Next-Action": actionID,
|
|
32
|
+
Origin: origin,
|
|
33
|
+
// muse.ai answers 403 to server actions that do not look like same-origin browser fetches.
|
|
34
|
+
"Sec-Fetch-Site": "same-origin",
|
|
35
|
+
"Sec-Fetch-Mode": "cors",
|
|
36
|
+
"Sec-Fetch-Dest": "empty",
|
|
37
|
+
"User-Agent": userAgent,
|
|
38
|
+
},
|
|
39
|
+
});
|
|
40
|
+
const chunkPaths = (text) => [
|
|
41
|
+
...new Set((text.match(/static\/chunks\/[\w~.-]+\.js/g) ?? []).map((p) => `/_next/${p}`)),
|
|
42
|
+
];
|
|
43
|
+
const fetchChunks = (paths) =>
|
|
44
|
+
Promise.all(
|
|
45
|
+
paths.map((path) =>
|
|
46
|
+
ctx.http
|
|
47
|
+
.get(`${origin}${path}`, { headers: { Accept: "*/*", "User-Agent": userAgent } })
|
|
48
|
+
.then((r) => (r.status === 200 ? r.bodyText : ""))
|
|
49
|
+
.catch(() => ""),
|
|
50
|
+
),
|
|
51
|
+
);
|
|
52
|
+
// The action ID changes on every deploy. The app chunk preloads settings through
|
|
53
|
+
// `e.A(<module>).then(({preloadHatchSettingsData…` and the loader chunk lists that module's files, one of which
|
|
54
|
+
// holds `createServerReference("<id>", …, "fetchSubscriptionAction")`.
|
|
55
|
+
// Returns undefined when muse.ai treats the session as signed out.
|
|
56
|
+
const discover = async (cookie) => {
|
|
57
|
+
const page = await ctx.http.get(`${origin}/`, {
|
|
58
|
+
headers: { Cookie: cookie, Accept: "text/html", "User-Agent": userAgent },
|
|
59
|
+
});
|
|
60
|
+
if (signedOut(page)) return undefined;
|
|
61
|
+
// Fetch page chunks a few at a time and stop once the settings loader entry turns up.
|
|
62
|
+
if (page.status !== 200) throw ctx.fail.apiFailure(`muse.ai returned HTTP ${page.status}.`);
|
|
63
|
+
const paths = chunkPaths(page.bodyText).slice(0, 96);
|
|
64
|
+
let text = "";
|
|
65
|
+
for (let i = 0; i < paths.length; i += 8) {
|
|
66
|
+
text += "\n" + (await fetchChunks(paths.slice(i, i + 8))).join("\n");
|
|
67
|
+
if (text.length > 8 * 1024 * 1024) throw discoveryFailure();
|
|
68
|
+
const module = /\.A\((\d+)\)\.then\(\(\{[^}]*Settings/.exec(text)?.[1];
|
|
69
|
+
const loader =
|
|
70
|
+
module && new RegExp(`[,{\\[]${module},\\w+=>\\{\\w+\\.v\\(\\w+=>Promise\\.all\\(\\[([^\\]]*)`).exec(text);
|
|
71
|
+
if (!loader) continue;
|
|
72
|
+
const settingsPaths = chunkPaths(loader[1]).slice(0, 32);
|
|
73
|
+
for (let j = 0; j < settingsPaths.length; j += 8) {
|
|
74
|
+
for (const body of await fetchChunks(settingsPaths.slice(j, j + 8))) {
|
|
75
|
+
const id = /"([0-9a-f]{40,})",[^"]{0,200}"fetchSubscriptionAction"/.exec(body)?.[1];
|
|
76
|
+
if (id) return id;
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
break;
|
|
80
|
+
}
|
|
81
|
+
throw discoveryFailure();
|
|
82
|
+
};
|
|
83
|
+
|
|
84
|
+
let actionID = ctx.storage.get("actionID");
|
|
85
|
+
const subscription = async (cookie) => {
|
|
86
|
+
const response = actionID ? await post(cookie, actionID) : undefined;
|
|
87
|
+
if (response && !staleAction(response)) {
|
|
88
|
+
return signedOut(response) ? undefined : response;
|
|
89
|
+
}
|
|
90
|
+
ctx.storage.remove("actionID");
|
|
91
|
+
actionID = await discover(cookie);
|
|
92
|
+
if (!actionID) return undefined;
|
|
93
|
+
const fresh = await post(cookie, actionID);
|
|
94
|
+
if (staleAction(fresh)) throw discoveryFailure();
|
|
95
|
+
return signedOut(fresh) ? undefined : fresh;
|
|
96
|
+
};
|
|
97
|
+
|
|
98
|
+
// Try each browser session, rejecting expired ones so a signed-in browser later in the order still works.
|
|
99
|
+
let response;
|
|
100
|
+
let rejected = false;
|
|
101
|
+
for await (const session of ctx.browser.sessions("muse.ai")) {
|
|
102
|
+
response = await subscription(session.header);
|
|
103
|
+
if (response) break;
|
|
104
|
+
rejected = true;
|
|
105
|
+
ctx.browser.rejectCookie("muse.ai", session);
|
|
106
|
+
if (policy === "manual") break;
|
|
107
|
+
}
|
|
108
|
+
if (!response) {
|
|
109
|
+
throw rejected
|
|
110
|
+
? ctx.fail.authenticationExpired("muse.ai session expired. Sign in at muse.ai and refresh.")
|
|
111
|
+
: ctx.fail.missingCredential(
|
|
112
|
+
`No muse.ai session found. Sign in at muse.ai. Supported browsers: ${ctx.browser.supportedBrowsers}. Or set a manual Cookie header.`,
|
|
113
|
+
);
|
|
114
|
+
}
|
|
115
|
+
if (response.status !== 200) throw ctx.fail.apiFailure(`muse.ai returned HTTP ${response.status}.`);
|
|
116
|
+
|
|
117
|
+
// React flight rows look like `1:{...}`; the subscription is the row carrying `success`.
|
|
118
|
+
let sub;
|
|
119
|
+
for (const line of response.bodyText.split("\n")) {
|
|
120
|
+
try {
|
|
121
|
+
const row = JSON.parse(/^[0-9a-f]+:(\{.*\})\s*$/.exec(line)?.[1] ?? "null");
|
|
122
|
+
if (row?.success === true) sub = row.subscription;
|
|
123
|
+
} catch {}
|
|
124
|
+
}
|
|
125
|
+
if (typeof sub?.usage?.percentUsed !== "number") throw ctx.fail.parseFailure("Unexpected muse.ai response.");
|
|
126
|
+
|
|
127
|
+
ctx.storage.set("actionID", actionID);
|
|
128
|
+
const seconds = (value) => (typeof value === "number" ? ctx.date.unixSeconds(value) : undefined);
|
|
129
|
+
const tokensLeft = (label) => /\(([^()]+ tokens left)\)/.exec(label ?? "")?.[1];
|
|
130
|
+
// Top-ups (purchased or from referrals) never expire and sit outside the weekly allowance. muse.ai reports
|
|
131
|
+
// the balance in micro-dollars, so the fallback without its label is a dollar amount.
|
|
132
|
+
const { topupBalance: balance, topupTotal: total } = sub;
|
|
133
|
+
const topup =
|
|
134
|
+
typeof balance === "number" && typeof total === "number" && total > 0
|
|
135
|
+
? {
|
|
136
|
+
label: sub.topupRowLabel ?? "Additional tokens",
|
|
137
|
+
value:
|
|
138
|
+
tokensLeft(sub.topupRowValueLabel) ?? sub.topupRowValueLabel ?? `${ctx.format.usd(balance / 1e6)} left`,
|
|
139
|
+
progress: Math.min(1, Math.max(0, (total - balance) / total)),
|
|
140
|
+
}
|
|
141
|
+
: undefined;
|
|
142
|
+
return {
|
|
143
|
+
primary: {
|
|
144
|
+
usedPercent: Math.min(100, Math.max(0, sub.usage.percentUsed)),
|
|
145
|
+
windowMinutes: 7 * 24 * 60,
|
|
146
|
+
resetsAt: seconds(sub.usage.resetsAt),
|
|
147
|
+
// "3% used (2.8B tokens left)" is the only absolute amount muse.ai reports. Free plans omit it.
|
|
148
|
+
resetDescription: tokensLeft(sub.usageRowValueLabel),
|
|
149
|
+
},
|
|
150
|
+
details: topup ? [{ rows: [topup] }] : undefined,
|
|
151
|
+
subscriptionRenewsAt: seconds(sub.agreement?.currentPeriodEndTime),
|
|
152
|
+
identity: { loginMethod: sub.tier?.name ?? sub.usageRowLabel },
|
|
153
|
+
dataConfidence: "percentOnly",
|
|
154
|
+
};
|
|
155
|
+
},
|
|
156
|
+
});
|
|
157
|
+
|
|
158
|
+
// END UPSTREAM
|
|
159
|
+
})(globalThis.defineProvider);
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
{
|
|
2
|
+
"schemaVersion": 1,
|
|
3
|
+
"id": "museai",
|
|
4
|
+
"name": "Muse (muse.ai)",
|
|
5
|
+
"version": "0.1.0",
|
|
6
|
+
"entry": "plugin.js",
|
|
7
|
+
"enabledByDefault": false,
|
|
8
|
+
"supportedModes": [
|
|
9
|
+
"web"
|
|
10
|
+
],
|
|
11
|
+
"autoMode": "web",
|
|
12
|
+
"usageDashboardUrl": "https://muse.ai",
|
|
13
|
+
"webUrl": "https://muse.ai",
|
|
14
|
+
"envVars": [
|
|
15
|
+
"MUSEAI_COOKIE"
|
|
16
|
+
],
|
|
17
|
+
"setupHelp": "Use a session Cookie header from muse.ai. This provider is separate from Muse Code and does not use its CLI login.",
|
|
18
|
+
"setupFields": [],
|
|
19
|
+
"upstream": {
|
|
20
|
+
"repository": "https://github.com/steipete/CodexBar",
|
|
21
|
+
"revision": "6070b29a1a5d51193a634dc1b0a14a67c39d0ccc",
|
|
22
|
+
"path": "Sources/CodexBarCore/Resources/Plugins/museai.js",
|
|
23
|
+
"sha256": "ab325a1b6cff46bbe347d98340d9212cab732f6b48c0d2ae5c6d955824ac5019",
|
|
24
|
+
"license": "MIT"
|
|
25
|
+
}
|
|
26
|
+
}
|
package/plugins/nous/plugin.js
CHANGED
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
// Generated by tools/vendor-codexbar-providers.cjs; see LICENSE.
|
|
2
2
|
globalThis.__usagestat_bundled_options = {"labels":["Monthly credits","Weekly","Additional"],"defaults":{"PORTAL_URL":"https://portal.nousresearch.com"},"autoMode":"api"};
|
|
3
|
+
(function(defineProvider) {
|
|
4
|
+
// BEGIN UPSTREAM Sources/CodexBarCore/Resources/Plugins/nous.js
|
|
3
5
|
function _nullishCoalesce(lhs, rhsFn) {
|
|
4
6
|
if (lhs != null) {
|
|
5
7
|
return lhs;
|
|
@@ -122,3 +124,6 @@ defineProvider({
|
|
|
122
124
|
};
|
|
123
125
|
},
|
|
124
126
|
});
|
|
127
|
+
|
|
128
|
+
// END UPSTREAM
|
|
129
|
+
})(globalThis.defineProvider);
|
package/plugins/nous/plugin.json
CHANGED
|
@@ -14,9 +14,17 @@
|
|
|
14
14
|
"PORTAL_URL",
|
|
15
15
|
"NOUS_PORTAL_ACCESS_TOKEN"
|
|
16
16
|
],
|
|
17
|
+
"setupFields": [
|
|
18
|
+
{
|
|
19
|
+
"key": "PORTAL_URL",
|
|
20
|
+
"title": "Portal URL",
|
|
21
|
+
"type": "string",
|
|
22
|
+
"description": "Provider-specific account or source selection."
|
|
23
|
+
}
|
|
24
|
+
],
|
|
17
25
|
"upstream": {
|
|
18
26
|
"repository": "https://github.com/steipete/CodexBar",
|
|
19
|
-
"revision": "
|
|
27
|
+
"revision": "6070b29a1a5d51193a634dc1b0a14a67c39d0ccc",
|
|
20
28
|
"path": "Sources/CodexBarCore/Resources/Plugins/nous.js",
|
|
21
29
|
"sha256": "160f5329bf4895a061d3bed74d0476283d3abad47e071fb868d667c0c5cb485e",
|
|
22
30
|
"license": "MIT"
|
package/plugins/ollama/plugin.js
CHANGED
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
var SETTINGS_URL = "https://ollama.com/settings";
|
|
3
3
|
var TAGS_URL = "https://ollama.com/api/tags";
|
|
4
4
|
var VALIDATION_URL = "https://ollama.com/api/web_search";
|
|
5
|
+
var BALANCE_URL = "https://ollama.com/api/balance";
|
|
5
6
|
var SESSION_COOKIE = "__Secure-session";
|
|
6
7
|
var SESSION_MS = 5 * 60 * 60 * 1000;
|
|
7
8
|
var WEEK_MS = 7 * 24 * 60 * 60 * 1000;
|
|
@@ -21,6 +22,7 @@
|
|
|
21
22
|
}
|
|
22
23
|
|
|
23
24
|
function env(ctx, name) {
|
|
25
|
+
if (ctx.provider && ctx.provider.instanceId && ctx.provider.instanceId !== 'ollama') return null;
|
|
24
26
|
try {
|
|
25
27
|
return clean(ctx.host.env.get(name));
|
|
26
28
|
} catch (_) {
|
|
@@ -89,6 +91,13 @@
|
|
|
89
91
|
}
|
|
90
92
|
|
|
91
93
|
function probeApi(ctx, apiKey) {
|
|
94
|
+
var balanceResponse = request(ctx, {method:'GET',url:BALANCE_URL,
|
|
95
|
+
headers:{Authorization:'Bearer '+apiKey,Accept:'application/json'},timeoutMs:15000});
|
|
96
|
+
if (balanceResponse.status !== 404) {
|
|
97
|
+
if (balanceResponse.status === 401 || balanceResponse.status === 403) throw "Ollama API key is invalid or revoked.";
|
|
98
|
+
if (balanceResponse.status !== 200) throw "Ollama balance API returned HTTP " + balanceResponse.status + ".";
|
|
99
|
+
return balanceResult(ctx, parseJson(ctx,balanceResponse.bodyText));
|
|
100
|
+
}
|
|
92
101
|
var validation = request(ctx, {
|
|
93
102
|
method: "POST",
|
|
94
103
|
url: VALIDATION_URL,
|
|
@@ -147,6 +156,55 @@
|
|
|
147
156
|
};
|
|
148
157
|
}
|
|
149
158
|
|
|
159
|
+
function balanceResult(ctx, payload) {
|
|
160
|
+
var data = payload && payload.data && typeof payload.data === 'object' ? payload.data : payload;
|
|
161
|
+
var fail = function() { throw 'Ollama returned an invalid credit balance response.'; };
|
|
162
|
+
if (!data || typeof data !== 'object' || Array.isArray(data)) return fail();
|
|
163
|
+
var amount = function(value) {
|
|
164
|
+
if (typeof value === 'string' && /^-?\d+(?:\.\d+)?$/.test(value.trim())) value = Number(value);
|
|
165
|
+
return typeof value === 'number' && Number.isFinite(value) ? value : fail();
|
|
166
|
+
};
|
|
167
|
+
var date = function(value) {
|
|
168
|
+
if (value == null) return null;
|
|
169
|
+
if (typeof value !== 'string' || !/^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}(?:\.\d+)?(?:Z|[+-]\d{2}:\d{2})$/.test(value)) return fail();
|
|
170
|
+
var parts=value.slice(0,10).split('-').map(Number),ms=Date.parse(value);
|
|
171
|
+
if (!Number.isFinite(ms) || parts[1]<1 || parts[1]>12 || parts[2]<1 || parts[2]>new Date(Date.UTC(parts[0],parts[1],0)).getUTCDate()) return fail();
|
|
172
|
+
return new Date(ms).toISOString();
|
|
173
|
+
};
|
|
174
|
+
var included=data.included,lines=[];
|
|
175
|
+
if (included != null && (typeof included !== 'object' || Array.isArray(included))) return fail();
|
|
176
|
+
if (included && (included.allowance_usd != null || included.balance_usd != null)) {
|
|
177
|
+
var allowance=amount(included.allowance_usd),balance=amount(included.balance_usd);
|
|
178
|
+
if (allowance<0) return fail();
|
|
179
|
+
var used=Math.max(0,allowance-balance),period=included.period;
|
|
180
|
+
if (period!=null && (typeof period!=='object' || Array.isArray(period))) return fail();
|
|
181
|
+
var until=date(period && period.until),from=date(period && period.from);
|
|
182
|
+
if (from && until && Date.parse(until)<=Date.parse(from)) return fail();
|
|
183
|
+
if (allowance>0) lines.push(ctx.line.progress({label:'Monthly credits',used:used,limit:allowance,
|
|
184
|
+
format:{kind:'dollars'},resetsAt:until || undefined,
|
|
185
|
+
periodDurationMs:from && until ? Date.parse(until)-Date.parse(from) : until ? ctx.util.calendarMonthDuration(until) : undefined}));
|
|
186
|
+
else lines.push(ctx.line.text({label:'Monthly allowance',value:'$0.00'}));
|
|
187
|
+
lines.push(ctx.line.text({label:'Monthly remaining',value:'$'+balance.toFixed(2)}));
|
|
188
|
+
}
|
|
189
|
+
var limits=included || data.limits || data;
|
|
190
|
+
for (var spec of [['session','Session',SESSION_MS],['weekly','Weekly',WEEK_MS],['monthly','Monthly',null]]) {
|
|
191
|
+
var window=limits[spec[0]] || limits[spec[0]+'_usage'];
|
|
192
|
+
if (!window) continue;
|
|
193
|
+
var raw=window.used_percent ?? window.usedPercent ?? window.percent ?? window.percentage;
|
|
194
|
+
var percent;
|
|
195
|
+
if (raw!=null) percent=amount(raw);
|
|
196
|
+
else if (window.remaining_percent!=null || window.remainingPercent!=null) percent=100-amount(window.remaining_percent ?? window.remainingPercent);
|
|
197
|
+
else if (window.usage!=null) {percent=amount(window.usage);if(percent<=1) percent*=100;}
|
|
198
|
+
else continue;
|
|
199
|
+
if (percent<0) return fail();
|
|
200
|
+
lines.push(ctx.line.progress({label:spec[1],used:percent,limit:100,format:{kind:'percent'},
|
|
201
|
+
periodDurationMs:spec[2] || undefined,resetsAt:date(window.resets_at || window.resetsAt) || undefined}));
|
|
202
|
+
}
|
|
203
|
+
if (data.purchased!=null) lines.push(ctx.line.text({label:'Purchased credits',value:'$'+amount(data.purchased.balance_usd).toFixed(2)}));
|
|
204
|
+
if (!lines.length) return fail();
|
|
205
|
+
return {displayName:'Ollama',source:'api',plan:typeof data.plan==='string' ? ctx.fmt.planLabel(data.plan) : 'API key',lines:lines};
|
|
206
|
+
}
|
|
207
|
+
|
|
150
208
|
function blockEnd(html, start, currentLabel) {
|
|
151
209
|
var labels = ["Monthly usage", "Session usage", "Hourly usage", "Weekly usage"];
|
|
152
210
|
var end = html.length;
|