@hashimkarim/usagestat-linux-x64-gnu 2.0.0-alpha.3 → 2.0.0-alpha.4
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 +731 -65
- package/package.json +112 -1
- package/plugins/UPSTREAM-LICENSES.md +6 -2
- package/plugins/_provider-icons/LICENSE +21 -0
- package/plugins/_provider-icons/NOTICE +12 -0
- package/plugins/_provider-icons/licenses/CodexBar-MIT.txt +21 -0
- package/plugins/_provider-icons/licenses/LobeHub-MIT.txt +21 -0
- package/plugins/_provider-icons/licenses/UsageStat-Bar-MIT.txt +21 -0
- package/plugins/_provider-icons/manifest.json +1219 -0
- package/plugins/_provider-icons/provenance.json +269 -0
- package/plugins/_provider-icons/source.json +4 -0
- package/plugins/abacus-ai/icon-color.svg +18 -0
- package/plugins/aixy/LICENSE +21 -0
- package/plugins/aixy/plugin.js +261 -0
- package/plugins/aixy/plugin.json +24 -0
- package/plugins/antigravity/plugin.js +136 -46
- package/plugins/antigravity-cli/icon-color.svg +1 -0
- package/plugins/antigravity-cli/icon.svg +1 -3
- package/plugins/antigravity-cli/plugin.js +11 -15
- package/plugins/antigravity-ide/icon-color.svg +1 -0
- package/plugins/antigravity-ide/icon.svg +1 -3
- package/plugins/atlascloud/LICENSE +21 -0
- package/plugins/atlascloud/icon-color.svg +1 -0
- package/plugins/atlascloud/icon.svg +1 -0
- package/plugins/atlascloud/plugin.js +49 -0
- package/plugins/atlascloud/plugin.json +23 -0
- package/plugins/augment/icon-color.svg +14 -0
- package/plugins/bifrost/LICENSE +21 -0
- package/plugins/bifrost/plugin.js +361 -0
- package/plugins/bifrost/plugin.json +23 -0
- package/plugins/claude/plugin.js +302 -152
- package/plugins/claude/plugin.json +1 -1
- package/plugins/clinepass/plugin.js +19 -3
- package/plugins/codebuff/icon-color.svg +92 -0
- package/plugins/coderabbit/plugin.js +25 -0
- package/plugins/coderabbit/plugin.json +12 -0
- package/plugins/codex/plugin.js +1 -0
- package/plugins/command-code/icon-color.svg +3 -0
- package/plugins/command-code/plugin.js +7 -3
- package/plugins/copilot/icon.svg +1 -1
- package/plugins/crof/icon-color.svg +3 -0
- package/plugins/crofai/icon-color.svg +3 -0
- package/plugins/crofai/icon.svg +2 -15
- package/plugins/cursor/icon-color.svg +1 -0
- package/plugins/deepinfra/icon-color.svg +1 -0
- package/plugins/deepinfra/icon.svg +1 -4
- package/plugins/deepseek/plugin.js +150 -59
- package/plugins/deepseek/plugin.json +3 -1
- package/plugins/devin/plugin.js +3 -0
- package/plugins/devpass/LICENSE +21 -0
- package/plugins/devpass/plugin.js +115 -0
- package/plugins/devpass/plugin.json +23 -0
- package/plugins/elevenlabs/icon-color.svg +4 -0
- package/plugins/factory/icon-color.svg +1 -0
- package/plugins/fireworks-ai/icon-color.svg +1 -0
- package/plugins/fireworks-ai/icon.svg +1 -6
- package/plugins/gemini-apps/LICENSE +22 -0
- package/plugins/gemini-apps/icon-color.svg +1 -0
- package/plugins/gemini-apps/icon.svg +1 -0
- package/plugins/gemini-apps/plugin.js +470 -0
- package/plugins/gemini-apps/plugin.json +19 -0
- package/plugins/gitkraken/LICENSE +21 -0
- package/plugins/gitkraken/plugin.js +153 -0
- package/plugins/gitkraken/plugin.json +24 -0
- package/plugins/grok/icon-color.svg +1 -0
- package/plugins/groqcloud/icon-color.svg +1 -0
- package/plugins/helmcode/LICENSE +21 -0
- package/plugins/helmcode/plugin.js +188 -0
- package/plugins/helmcode/plugin.json +24 -0
- package/plugins/huggingface/LICENSE +21 -0
- package/plugins/huggingface/icon-color.svg +1 -0
- package/plugins/huggingface/icon.svg +1 -0
- package/plugins/huggingface/plugin.js +293 -0
- package/plugins/huggingface/plugin.json +25 -0
- package/plugins/hyper/LICENSE +21 -0
- package/plugins/hyper/plugin.js +131 -0
- package/plugins/hyper/plugin.json +26 -0
- package/plugins/kilo/icon-color.svg +1 -0
- package/plugins/litellm/plugin.js +54 -3
- package/plugins/llmman/LICENSE +21 -0
- package/plugins/llmman/plugin.js +133 -0
- package/plugins/llmman/plugin.json +23 -0
- package/plugins/longcat/icon-color.svg +1 -0
- package/plugins/longcat/icon.svg +1 -4
- package/plugins/manus/icon-color.svg +6 -0
- package/plugins/mimo/icon-color.svg +4 -0
- package/plugins/mimo/icon.svg +4 -1
- package/plugins/mistral/plugin.js +110 -5
- package/plugins/moonshot/icon-color.svg +1 -0
- package/plugins/muse/LICENSE +21 -0
- package/plugins/muse/plugin.js +319 -0
- package/plugins/muse/plugin.json +26 -0
- package/plugins/nous/LICENSE +21 -0
- package/plugins/nous/icon-color.svg +1 -0
- package/plugins/nous/icon.svg +1 -0
- package/plugins/nous/plugin.js +124 -0
- package/plugins/nous/plugin.json +24 -0
- package/plugins/ollama/icon-color.svg +1 -0
- package/plugins/openai-api/icon-color.svg +1 -0
- package/plugins/opencode/icon-color.svg +1 -0
- package/plugins/opencode-go/icon-color.svg +1 -0
- package/plugins/opencode-go/plugin.js +30 -12
- package/plugins/openrouter/icon-color.svg +1 -0
- package/plugins/pi/plugin.js +113 -0
- package/plugins/pi/plugin.json +11 -0
- package/plugins/qwencloud/icon-color.svg +1 -0
- package/plugins/qwencloud/icon.svg +1 -4
- package/plugins/raycast/LICENSE +21 -0
- package/plugins/raycast/plugin.js +174 -0
- package/plugins/raycast/plugin.json +24 -0
- package/plugins/replicate/LICENSE +21 -0
- package/plugins/replicate/icon-color.svg +1 -0
- package/plugins/replicate/icon.svg +1 -0
- package/plugins/replicate/plugin.js +165 -0
- package/plugins/replicate/plugin.json +24 -0
- package/plugins/synthetic/icon-color.svg +14 -0
- package/plugins/typesafe/LICENSE +21 -0
- package/plugins/typesafe/plugin.js +225 -0
- package/plugins/typesafe/plugin.json +24 -0
- package/plugins/v0/LICENSE +21 -0
- package/plugins/v0/icon-color.svg +1 -0
- package/plugins/v0/icon.svg +1 -0
- package/plugins/v0/plugin.js +214 -0
- package/plugins/v0/plugin.json +24 -0
- package/plugins/venice/plugin.js +50 -1
- package/plugins/venice/plugin.json +2 -1
- package/plugins/vercel/LICENSE +21 -0
- package/plugins/vercel/icon-color.svg +1 -0
- package/plugins/vercel/icon.svg +1 -0
- package/plugins/vercel/plugin.js +55 -0
- package/plugins/vercel/plugin.json +23 -0
- package/plugins/windsurf/icon-color.svg +1 -0
- package/plugins/xai/icon-color.svg +1 -0
- package/plugins/xai/icon.svg +1 -7
- package/plugins/xkiro/LICENSE +21 -0
- package/plugins/xkiro/plugin.js +99 -0
- package/plugins/xkiro/plugin.json +23 -0
- package/plugins/zai/icon-color.svg +1 -0
- package/plugins/zenmux/icon-color.svg +1 -0
- package/plugins/zenmux/icon.svg +1 -3
- package/usagestat +0 -0
- package/usagestatd +0 -0
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
// Generated by tools/vendor-codexbar-providers.cjs; see LICENSE.
|
|
2
|
+
globalThis.__usagestat_bundled_options = {"labels":["Plan credits","Premium weekly","Additional"],"defaults":{},"autoMode":"api"};
|
|
3
|
+
function _nullishCoalesce(lhs, rhsFn) {
|
|
4
|
+
if (lhs != null) {
|
|
5
|
+
return lhs;
|
|
6
|
+
} else {
|
|
7
|
+
return rhsFn();
|
|
8
|
+
}
|
|
9
|
+
}
|
|
10
|
+
function _optionalChain(ops) {
|
|
11
|
+
let lastAccessLHS = undefined;
|
|
12
|
+
let value = ops[0];
|
|
13
|
+
let i = 1;
|
|
14
|
+
while (i < ops.length) {
|
|
15
|
+
const op = ops[i];
|
|
16
|
+
const fn = ops[i + 1];
|
|
17
|
+
i += 2;
|
|
18
|
+
if ((op === "optionalAccess" || op === "optionalCall") && value == null) {
|
|
19
|
+
return undefined;
|
|
20
|
+
}
|
|
21
|
+
if (op === "access" || op === "optionalAccess") {
|
|
22
|
+
lastAccessLHS = value;
|
|
23
|
+
value = fn(value);
|
|
24
|
+
} else if (op === "call" || op === "optionalCall") {
|
|
25
|
+
value = fn((...args) => value.call(lastAccessLHS, ...args));
|
|
26
|
+
lastAccessLHS = undefined;
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
return value;
|
|
30
|
+
}
|
|
31
|
+
defineProvider({
|
|
32
|
+
id: "devpass",
|
|
33
|
+
name: "DevPass",
|
|
34
|
+
endpoints: ["https://api.llmgateway.io"],
|
|
35
|
+
auth: { type: "bearer", secret: "DEVPASS_API_KEY" },
|
|
36
|
+
settings: [{ key: "DEVPASS_API_KEY", title: "DevPass API key", type: "secure" }],
|
|
37
|
+
capabilities: ["http-status"],
|
|
38
|
+
async fetchUsage(ctx) {
|
|
39
|
+
const response = await ctx.http.get("https://api.llmgateway.io/v1/key");
|
|
40
|
+
if (response.status === 401) throw ctx.fail.authenticationExpired("DevPass API key was rejected or is inactive.");
|
|
41
|
+
if (response.status === 403) throw ctx.fail.permissionDenied("DevPass requires a regular gateway API key.");
|
|
42
|
+
if (response.status === 429) {
|
|
43
|
+
const delay = Number(_nullishCoalesce(response.headers["retry-after"], () => 1));
|
|
44
|
+
throw ctx.fail.rateLimited("DevPass usage requests are rate limited.", {
|
|
45
|
+
retryAfterSeconds: Number.isFinite(delay) && delay >= 0 ? Math.min(delay, 10) : 1,
|
|
46
|
+
});
|
|
47
|
+
}
|
|
48
|
+
if (response.status >= 500) throw ctx.fail.providerUnavailable("DevPass usage is temporarily unavailable.");
|
|
49
|
+
if (response.status !== 200) throw ctx.fail.apiFailure(`DevPass returned HTTP ${response.status}.`);
|
|
50
|
+
function fail() {
|
|
51
|
+
throw ctx.fail.parseFailure("DevPass returned an unrecognized usage response.");
|
|
52
|
+
}
|
|
53
|
+
let body;
|
|
54
|
+
try {
|
|
55
|
+
body = JSON.parse(response.bodyText);
|
|
56
|
+
} catch (error) {
|
|
57
|
+
void error;
|
|
58
|
+
return fail();
|
|
59
|
+
}
|
|
60
|
+
const data = _optionalChain([body, "optionalAccess", (_) => _.data]);
|
|
61
|
+
if (!data || !["none", "lite", "pro", "max"].includes(data.devPlan)) return fail();
|
|
62
|
+
function money(key) {
|
|
63
|
+
const value = data[key];
|
|
64
|
+
if (typeof value !== "string" || !/^\d+(?:\.\d+)?$/.test(value)) return fail();
|
|
65
|
+
const number = Number(value);
|
|
66
|
+
if (!Number.isFinite(number)) return fail();
|
|
67
|
+
return number;
|
|
68
|
+
}
|
|
69
|
+
const keyUsed = money("usage");
|
|
70
|
+
const keyLimit = data.limit === null ? null : money("limit");
|
|
71
|
+
const keyRows = [{ label: "All-time key usage", value: ctx.format.usd(keyUsed) }];
|
|
72
|
+
if (keyLimit !== null) keyRows.push({ label: "Key spending limit", value: ctx.format.usd(keyLimit) });
|
|
73
|
+
if (data.devPlan === "none") {
|
|
74
|
+
return {
|
|
75
|
+
details: [{ title: "API key (all time)", rows: keyRows }],
|
|
76
|
+
identity: { loginMethod: "Pay as you go" },
|
|
77
|
+
dataConfidence: "exact",
|
|
78
|
+
};
|
|
79
|
+
}
|
|
80
|
+
const used = money("devPlanCreditsUsed");
|
|
81
|
+
const limit = money("devPlanCreditsLimit");
|
|
82
|
+
const remaining = money("devPlanCreditsRemaining");
|
|
83
|
+
const weeklyUsed = money("devPlanPremiumCreditsUsed");
|
|
84
|
+
const weeklyLimit = money("devPlanPremiumWeeklyLimit");
|
|
85
|
+
const reset = data.devPlanPremiumWeekResetsAt;
|
|
86
|
+
let resetsAt = null;
|
|
87
|
+
if (reset !== null) {
|
|
88
|
+
if (
|
|
89
|
+
typeof reset !== "string" ||
|
|
90
|
+
!/^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}(?:\.\d+)?(?:Z|[+-]\d{2}:\d{2})$/.test(reset)
|
|
91
|
+
)
|
|
92
|
+
return fail();
|
|
93
|
+
resetsAt = new Date(reset);
|
|
94
|
+
if (!Number.isFinite(resetsAt.getTime())) return fail();
|
|
95
|
+
}
|
|
96
|
+
return {
|
|
97
|
+
primary: limit > 0 ? { usedPercent: ctx.pct(used, limit) } : null,
|
|
98
|
+
secondary:
|
|
99
|
+
weeklyLimit > 0 ? { usedPercent: ctx.pct(weeklyUsed, weeklyLimit), windowMinutes: 10080, resetsAt } : null,
|
|
100
|
+
details: [
|
|
101
|
+
{
|
|
102
|
+
title: "DevPass credits",
|
|
103
|
+
rows: [
|
|
104
|
+
{ label: "Cycle used", value: `${ctx.format.usd(used)} / ${ctx.format.usd(limit)}` },
|
|
105
|
+
{ label: "Cycle remaining", value: ctx.format.usd(remaining) },
|
|
106
|
+
{ label: "Premium weekly", value: `${ctx.format.usd(weeklyUsed)} / ${ctx.format.usd(weeklyLimit)}` },
|
|
107
|
+
],
|
|
108
|
+
},
|
|
109
|
+
{ title: "API key (all time)", rows: keyRows },
|
|
110
|
+
],
|
|
111
|
+
identity: { loginMethod: `DevPass ${data.devPlan.charAt(0).toUpperCase()}${data.devPlan.slice(1)}` },
|
|
112
|
+
dataConfidence: "exact",
|
|
113
|
+
};
|
|
114
|
+
},
|
|
115
|
+
});
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
{
|
|
2
|
+
"schemaVersion": 1,
|
|
3
|
+
"id": "devpass",
|
|
4
|
+
"name": "DevPass",
|
|
5
|
+
"version": "0.1.0",
|
|
6
|
+
"entry": "plugin.js",
|
|
7
|
+
"enabledByDefault": false,
|
|
8
|
+
"supportedModes": [
|
|
9
|
+
"api"
|
|
10
|
+
],
|
|
11
|
+
"autoMode": "api",
|
|
12
|
+
"usageDashboardUrl": "https://devpass.llmgateway.io/dashboard",
|
|
13
|
+
"envVars": [
|
|
14
|
+
"DEVPASS_API_KEY"
|
|
15
|
+
],
|
|
16
|
+
"upstream": {
|
|
17
|
+
"repository": "https://github.com/steipete/CodexBar",
|
|
18
|
+
"revision": "78ba5a1e65e6859da59a21b799df95ab91bc0b89",
|
|
19
|
+
"path": "Sources/CodexBarCore/Resources/Plugins/devpass.js",
|
|
20
|
+
"sha256": "6fdfab17f543fc88f28b90f76fbe6edc6db3f4d3c7fa657a899c573d363a0886",
|
|
21
|
+
"license": "MIT"
|
|
22
|
+
}
|
|
23
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 67 65" id="mobile-nav-logo" class="h-auto w-10 md:hidden"><path fill="currentColor" d="M47.75 11.15a.867.867 0 0 1-.671-.806.84.84 0 0 1 .067-.362c1.688-4.007 2.433-7.213 1.23-8.555-3.183-3.56-15.952 3.52-20.024 5.919a.9.9 0 0 1-1.273-.41c-1.711-3.998-3.51-6.78-5.334-6.9-4.833-.323-8.73 13.49-9.87 17.992a.85.85 0 0 1-.459.563.9.9 0 0 1-.737.027c-4.109-1.647-7.398-2.373-8.773-1.2-3.651 3.104 3.609 15.557 6.068 19.528a.85.85 0 0 1-.11 1.031.9.9 0 0 1-.31.21C3.455 39.856.604 41.61.478 43.389c-.329 4.713 13.834 8.513 18.452 9.625q.186.046.337.163a.87.87 0 0 1 .332.642.84.84 0 0 1-.067.362c-1.688 4.007-2.433 7.214-1.23 8.555 3.183 3.561 15.954-3.519 20.025-5.917a.9.9 0 0 1 1.058.107.9.9 0 0 1 .215.302c1.711 3.997 3.509 6.779 5.334 6.9 4.833.322 8.73-13.49 9.868-17.993a.85.85 0 0 1 .168-.33.88.88 0 0 1 .659-.324.9.9 0 0 1 .371.066c4.109 1.647 7.397 2.372 8.773 1.2 3.651-3.105-3.61-15.559-6.07-19.53a.85.85 0 0 1 .111-1.03.9.9 0 0 1 .31-.21c4.1-1.67 6.952-3.424 7.075-5.203.331-4.713-13.833-8.513-18.45-9.623m-5.546-4.518c.93 1.624-3.858 12.446-7.42 20.015a.7.7 0 0 1-.28.303.71.71 0 0 1-.796-.059.7.7 0 0 1-.23-.341c-1.439-4.921-3.082-10.704-4.841-15.612a.84.84 0 0 1 .01-.594.87.87 0 0 1 .401-.446c4.392-2.34 11.908-5.446 13.156-3.266m-21.048 1.34c1.833.507 6.294 11.46 9.264 19.268a.67.67 0 0 1-.2.754.71.71 0 0 1-.794.08c-4.589-2.485-9.94-5.444-14.743-7.702a.87.87 0 0 1-.422-.427.84.84 0 0 1-.04-.591c1.414-4.679 4.471-12.063 6.935-11.383M7.243 23.433c1.664-.906 12.762 3.763 20.522 7.235.13.058.239.154.311.274a.67.67 0 0 1-.06.776.7.7 0 0 1-.35.225c-5.045 1.403-10.976 3.006-16.01 4.721a.9.9 0 0 1-.607-.01.88.88 0 0 1-.456-.391c-2.395-4.284-5.586-11.613-3.35-12.83M8.617 43.96c.519-1.788 11.752-6.14 19.758-9.035a.72.72 0 0 1 .773.195.67.67 0 0 1 .081.774c-2.548 4.475-5.582 9.694-7.898 14.377a.87.87 0 0 1-.437.413.9.9 0 0 1-.607.039c-4.797-1.37-12.37-4.36-11.67-6.763m15.855 13.568c-.93-1.623 3.859-12.446 7.42-20.014a.7.7 0 0 1 .28-.303.715.715 0 0 1 .796.059.7.7 0 0 1 .23.34c1.439 4.92 3.083 10.705 4.841 15.613a.84.84 0 0 1-.01.593.87.87 0 0 1-.402.445c-4.391 2.335-11.908 5.447-13.15 3.267zm21.049-1.34c-1.836-.506-6.297-11.461-9.266-19.269a.67.67 0 0 1 .2-.755.71.71 0 0 1 .795-.078c4.587 2.484 9.94 5.445 14.742 7.703.189.088.339.24.423.426a.84.84 0 0 1 .039.592c-1.413 4.686-4.47 12.063-6.933 11.381m13.912-15.462c-1.665.907-12.762-3.763-20.523-7.236a.7.7 0 0 1-.311-.273.67.67 0 0 1 .06-.777.7.7 0 0 1 .35-.225c5.046-1.402 10.975-3.005 16.009-4.72a.9.9 0 0 1 .609.01.88.88 0 0 1 .457.392c2.393 4.282 5.584 11.613 3.349 12.829M58.06 20.2c-.521 1.79-11.753 6.14-19.759 9.036a.72.72 0 0 1-.774-.195.67.67 0 0 1-.08-.776c2.547-4.474 5.581-9.694 7.897-14.377a.87.87 0 0 1 .437-.412.9.9 0 0 1 .607-.038c4.797 1.377 12.37 4.359 11.672 6.762"></path></svg>
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
<svg height="1em" style="flex:none;line-height:1" viewBox="0 0 24 24" width="1em" xmlns="http://www.w3.org/2000/svg"><title>Fireworks</title><path clip-rule="evenodd" d="M14.8 5l-2.801 6.795L9.195 5H7.397l3.072 7.428a1.64 1.64 0 003.038.002L16.598 5H14.8zm1.196 10.352l5.124-5.244-.699-1.669-5.596 5.739a1.664 1.664 0 00-.343 1.807 1.642 1.642 0 001.516 1.012L16 17l8-.02-.699-1.669-7.303.041h-.002zM2.88 10.104l.699-1.669 5.596 5.739c.468.479.603 1.189.343 1.807a1.643 1.643 0 01-1.516 1.012l-8-.018-.002.002.699-1.669 7.303.042-5.122-5.246z" fill="#5019C5" fill-rule="evenodd"></path></svg>
|
|
@@ -1,6 +1 @@
|
|
|
1
|
-
<svg viewBox="0 0
|
|
2
|
-
<path
|
|
3
|
-
d="M314.333 110.167L255.98 251.729l-58.416-141.562h-37.459l64 154.75c5.23 12.854 17.771 21.312 31.646 21.312s26.417-8.437 31.646-21.27l64.396-154.792h-37.459zm24.917 215.666L446 216.583l-14.562-34.77-116.584 119.562c-9.708 9.958-12.541 24.833-7.146 37.646 5.292 12.73 17.792 21.083 31.584 21.083l.042.063L506 359.75l-14.562-34.77-152.146.853h-.042zM66 216.5l14.563-34.77 116.583 119.562a34.592 34.592 0 017.146 37.646C199 351.667 186.5 360.02 172.708 360.02l-166.666-.375-.042.042 14.563-34.771 152.145.875L66 216.5z"
|
|
4
|
-
fill="currentColor"
|
|
5
|
-
/>
|
|
6
|
-
</svg>
|
|
1
|
+
<svg fill="currentColor" fill-rule="evenodd" height="1em" style="flex:none;line-height:1" viewBox="0 0 24 24" width="1em" xmlns="http://www.w3.org/2000/svg"><title>Fireworks</title><path clip-rule="evenodd" d="M14.8 5l-2.801 6.795L9.195 5H7.397l3.072 7.428a1.64 1.64 0 003.038.002L16.598 5H14.8zm1.196 10.352l5.124-5.244-.699-1.669-5.596 5.739a1.664 1.664 0 00-.343 1.807 1.642 1.642 0 001.516 1.012L16 17l8-.02-.699-1.669-7.303.041h-.002zM2.88 10.104l.699-1.669 5.596 5.739c.468.479.603 1.189.343 1.807a1.643 1.643 0 01-1.516 1.012l-8-.018-.002.002.699-1.669 7.303.042-5.122-5.246z"></path></svg>
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Robin Ebers (original macOS creator)
|
|
4
|
+
Copyright (c) 2026 barramee kottanawadee
|
|
5
|
+
|
|
6
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
7
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
8
|
+
in the Software without restriction, including without limitation the rights
|
|
9
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
10
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
11
|
+
furnished to do so, subject to the following conditions:
|
|
12
|
+
|
|
13
|
+
The above copyright notice and this permission notice shall be included in all
|
|
14
|
+
copies or substantial portions of the Software.
|
|
15
|
+
|
|
16
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
17
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
18
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
19
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
20
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
21
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
22
|
+
SOFTWARE.
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
<svg height="1em" style="flex:none;line-height:1" viewBox="0 0 24 24" width="1em" xmlns="http://www.w3.org/2000/svg"><title>Gemini</title><path d="M20.616 10.835a14.147 14.147 0 01-4.45-3.001 14.111 14.111 0 01-3.678-6.452.503.503 0 00-.975 0 14.134 14.134 0 01-3.679 6.452 14.155 14.155 0 01-4.45 3.001c-.65.28-1.318.505-2.002.678a.502.502 0 000 .975c.684.172 1.35.397 2.002.677a14.147 14.147 0 014.45 3.001 14.112 14.112 0 013.679 6.453.502.502 0 00.975 0c.172-.685.397-1.351.677-2.003a14.145 14.145 0 013.001-4.45 14.113 14.113 0 016.453-3.678.503.503 0 000-.975 13.245 13.245 0 01-2.003-.678z" fill="#3186FF"></path><path d="M20.616 10.835a14.147 14.147 0 01-4.45-3.001 14.111 14.111 0 01-3.678-6.452.503.503 0 00-.975 0 14.134 14.134 0 01-3.679 6.452 14.155 14.155 0 01-4.45 3.001c-.65.28-1.318.505-2.002.678a.502.502 0 000 .975c.684.172 1.35.397 2.002.677a14.147 14.147 0 014.45 3.001 14.112 14.112 0 013.679 6.453.502.502 0 00.975 0c.172-.685.397-1.351.677-2.003a14.145 14.145 0 013.001-4.45 14.113 14.113 0 016.453-3.678.503.503 0 000-.975 13.245 13.245 0 01-2.003-.678z" fill="url(#lobe-icons-gemini-0-_R_0_)"></path><path d="M20.616 10.835a14.147 14.147 0 01-4.45-3.001 14.111 14.111 0 01-3.678-6.452.503.503 0 00-.975 0 14.134 14.134 0 01-3.679 6.452 14.155 14.155 0 01-4.45 3.001c-.65.28-1.318.505-2.002.678a.502.502 0 000 .975c.684.172 1.35.397 2.002.677a14.147 14.147 0 014.45 3.001 14.112 14.112 0 013.679 6.453.502.502 0 00.975 0c.172-.685.397-1.351.677-2.003a14.145 14.145 0 013.001-4.45 14.113 14.113 0 016.453-3.678.503.503 0 000-.975 13.245 13.245 0 01-2.003-.678z" fill="url(#lobe-icons-gemini-1-_R_0_)"></path><path d="M20.616 10.835a14.147 14.147 0 01-4.45-3.001 14.111 14.111 0 01-3.678-6.452.503.503 0 00-.975 0 14.134 14.134 0 01-3.679 6.452 14.155 14.155 0 01-4.45 3.001c-.65.28-1.318.505-2.002.678a.502.502 0 000 .975c.684.172 1.35.397 2.002.677a14.147 14.147 0 014.45 3.001 14.112 14.112 0 013.679 6.453.502.502 0 00.975 0c.172-.685.397-1.351.677-2.003a14.145 14.145 0 013.001-4.45 14.113 14.113 0 016.453-3.678.503.503 0 000-.975 13.245 13.245 0 01-2.003-.678z" fill="url(#lobe-icons-gemini-2-_R_0_)"></path><defs><linearGradient gradientUnits="userSpaceOnUse" id="lobe-icons-gemini-0-_R_0_" x1="7" x2="11" y1="15.5" y2="12"><stop stop-color="#08B962"></stop><stop offset="1" stop-color="#08B962" stop-opacity="0"></stop></linearGradient><linearGradient gradientUnits="userSpaceOnUse" id="lobe-icons-gemini-1-_R_0_" x1="8" x2="11.5" y1="5.5" y2="11"><stop stop-color="#F94543"></stop><stop offset="1" stop-color="#F94543" stop-opacity="0"></stop></linearGradient><linearGradient gradientUnits="userSpaceOnUse" id="lobe-icons-gemini-2-_R_0_" x1="3.5" x2="17.5" y1="13.5" y2="12"><stop stop-color="#FABC12"></stop><stop offset=".46" stop-color="#FABC12" stop-opacity="0"></stop></linearGradient></defs></svg>
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
<svg fill="currentColor" fill-rule="evenodd" height="1em" style="flex:none;line-height:1" viewBox="0 0 24 24" width="1em" xmlns="http://www.w3.org/2000/svg"><title>Gemini</title><path d="M20.616 10.835a14.147 14.147 0 01-4.45-3.001 14.111 14.111 0 01-3.678-6.452.503.503 0 00-.975 0 14.134 14.134 0 01-3.679 6.452 14.155 14.155 0 01-4.45 3.001c-.65.28-1.318.505-2.002.678a.502.502 0 000 .975c.684.172 1.35.397 2.002.677a14.147 14.147 0 014.45 3.001 14.112 14.112 0 013.679 6.453.502.502 0 00.975 0c.172-.685.397-1.351.677-2.003a14.145 14.145 0 013.001-4.45 14.113 14.113 0 016.453-3.678.503.503 0 000-.975 13.245 13.245 0 01-2.003-.678z"></path></svg>
|
|
@@ -0,0 +1,470 @@
|
|
|
1
|
+
(function () {
|
|
2
|
+
const APP_URL = "https://gemini.google.com/app"
|
|
3
|
+
const USAGE_URL = "https://gemini.google.com/usage"
|
|
4
|
+
const BATCH_URL = "https://gemini.google.com/_/BardChatUi/data/batchexecute"
|
|
5
|
+
const ORIGIN = "https://gemini.google.com"
|
|
6
|
+
const RPC_USAGE = "jSf9Qc"
|
|
7
|
+
const RPC_PLAN = "sJBwce"
|
|
8
|
+
const CURRENT_MS = 5 * 60 * 60 * 1000
|
|
9
|
+
const WEEKLY_MS = 7 * 24 * 60 * 60 * 1000
|
|
10
|
+
const BRAND = "#3186FF"
|
|
11
|
+
const COOKIE_HOSTS = [".google.com", "google.com", ".gemini.google.com", "gemini.google.com"]
|
|
12
|
+
const COOKIE_NAMES = [
|
|
13
|
+
"SID",
|
|
14
|
+
"HSID",
|
|
15
|
+
"SSID",
|
|
16
|
+
"APISID",
|
|
17
|
+
"SAPISID",
|
|
18
|
+
"__Secure-1PSID",
|
|
19
|
+
"__Secure-1PSIDTS",
|
|
20
|
+
"__Secure-3PSID",
|
|
21
|
+
"__Secure-1PAPISID",
|
|
22
|
+
]
|
|
23
|
+
|
|
24
|
+
function readString(value) {
|
|
25
|
+
if (typeof value !== "string") return null
|
|
26
|
+
const trimmed = value.trim()
|
|
27
|
+
return trimmed ? trimmed : null
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
function getEnv(ctx, name) {
|
|
31
|
+
try {
|
|
32
|
+
return readString(ctx.host.env.get(name))
|
|
33
|
+
} catch (e) {
|
|
34
|
+
ctx.host.log.warn("env read failed for " + name + ": " + String(e))
|
|
35
|
+
return null
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
function parseCookieHeader(raw) {
|
|
40
|
+
const text = readString(raw)
|
|
41
|
+
if (!text) return null
|
|
42
|
+
const header = text.replace(/^Cookie:\s*/i, "")
|
|
43
|
+
const map = {}
|
|
44
|
+
const parts = header.split(";")
|
|
45
|
+
for (let i = 0; i < parts.length; i += 1) {
|
|
46
|
+
const part = parts[i].trim()
|
|
47
|
+
const eq = part.indexOf("=")
|
|
48
|
+
if (eq === -1) continue
|
|
49
|
+
const name = part.slice(0, eq).trim()
|
|
50
|
+
const value = part.slice(eq + 1).trim()
|
|
51
|
+
if (name && value) map[name] = value
|
|
52
|
+
}
|
|
53
|
+
return Object.keys(map).length > 0 ? map : null
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
function hasGeminiSession(map) {
|
|
57
|
+
if (!map || typeof map !== "object") return false
|
|
58
|
+
const hasPsid = readString(map["__Secure-1PSID"])
|
|
59
|
+
const hasSapisid = readString(map.SAPISID) || readString(map["__Secure-1PAPISID"])
|
|
60
|
+
return !!(hasPsid && hasSapisid)
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
function readChromeCookies(ctx) {
|
|
64
|
+
if (!ctx.host.chromiumCookies || typeof ctx.host.chromiumCookies.read !== "function") {
|
|
65
|
+
return null
|
|
66
|
+
}
|
|
67
|
+
try {
|
|
68
|
+
const map = ctx.host.chromiumCookies.read({ hosts: COOKIE_HOSTS, names: COOKIE_NAMES })
|
|
69
|
+
if (!hasGeminiSession(map)) return null
|
|
70
|
+
return map
|
|
71
|
+
} catch (e) {
|
|
72
|
+
ctx.host.log.warn("chromium cookie read failed: " + String(e))
|
|
73
|
+
return null
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
function loadCookies(ctx) {
|
|
78
|
+
const provider = ctx.provider || {}
|
|
79
|
+
if (Object.prototype.hasOwnProperty.call(provider, "cookieHeader")) {
|
|
80
|
+
const configured = parseCookieHeader(provider.cookieHeader)
|
|
81
|
+
return configured ? { map: configured, source: "Configured cookie" } : null
|
|
82
|
+
}
|
|
83
|
+
if (provider.instanceId && provider.instanceId !== "gemini-apps") return null
|
|
84
|
+
const explicit = parseCookieHeader(getEnv(ctx, "GEMINI_COOKIE"))
|
|
85
|
+
if (explicit) return { map: explicit, source: "GEMINI_COOKIE" }
|
|
86
|
+
const chrome = readChromeCookies(ctx)
|
|
87
|
+
if (chrome) return { map: chrome, source: "Chrome" }
|
|
88
|
+
|
|
89
|
+
const envMap = parseCookieHeader(getEnv(ctx, "GEMINI_COOKIE"))
|
|
90
|
+
if (envMap) return { map: envMap, source: "GEMINI_COOKIE" }
|
|
91
|
+
|
|
92
|
+
return null
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
function headerSafe(value) {
|
|
96
|
+
const text = readString(value)
|
|
97
|
+
if (!text) return null
|
|
98
|
+
for (let i = 0; i < text.length; i += 1) {
|
|
99
|
+
const code = text.charCodeAt(i)
|
|
100
|
+
if (code < 32 || code > 126) return null
|
|
101
|
+
}
|
|
102
|
+
return text
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
function cookieHeader(map) {
|
|
106
|
+
const parts = []
|
|
107
|
+
for (let i = 0; i < COOKIE_NAMES.length; i += 1) {
|
|
108
|
+
const name = COOKIE_NAMES[i]
|
|
109
|
+
const value = headerSafe(map[name])
|
|
110
|
+
if (value) parts.push(name + "=" + value)
|
|
111
|
+
}
|
|
112
|
+
const keys = Object.keys(map)
|
|
113
|
+
for (let i = 0; i < keys.length; i += 1) {
|
|
114
|
+
const name = keys[i]
|
|
115
|
+
if (COOKIE_NAMES.indexOf(name) !== -1) continue
|
|
116
|
+
const value = headerSafe(map[name])
|
|
117
|
+
if (value) parts.push(name + "=" + value)
|
|
118
|
+
}
|
|
119
|
+
return parts.join("; ")
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
function sapisidHash(ctx, map) {
|
|
123
|
+
const sapisid = readString(map.SAPISID) || readString(map["__Secure-1PAPISID"])
|
|
124
|
+
if (!sapisid) return null
|
|
125
|
+
if (!ctx.host.crypto || typeof ctx.host.crypto.sha1Hex !== "function") {
|
|
126
|
+
ctx.host.log.warn("sha1Hex missing; SAPISIDHASH skipped")
|
|
127
|
+
return null
|
|
128
|
+
}
|
|
129
|
+
const ts = Math.floor(Date.parse(ctx.nowIso || "") / 1000)
|
|
130
|
+
const timestamp = Number.isFinite(ts) && ts > 0 ? ts : Math.floor(Date.now() / 1000)
|
|
131
|
+
const digest = ctx.host.crypto.sha1Hex(timestamp + " " + sapisid + " " + ORIGIN)
|
|
132
|
+
return "SAPISIDHASH " + timestamp + "_" + digest
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
function geminiHeaders(map, extra) {
|
|
136
|
+
const headers = {
|
|
137
|
+
Cookie: cookieHeader(map),
|
|
138
|
+
Origin: ORIGIN,
|
|
139
|
+
Referer: USAGE_URL,
|
|
140
|
+
"X-Same-Domain": "1",
|
|
141
|
+
"User-Agent":
|
|
142
|
+
"Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/144.0.0.0 Safari/537.36",
|
|
143
|
+
}
|
|
144
|
+
if (extra) {
|
|
145
|
+
const keys = Object.keys(extra)
|
|
146
|
+
for (let i = 0; i < keys.length; i += 1) headers[keys[i]] = extra[keys[i]]
|
|
147
|
+
}
|
|
148
|
+
return headers
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
function extractMeta(html) {
|
|
152
|
+
function field(name) {
|
|
153
|
+
const re = new RegExp('"' + name + '"\\s*:\\s*"([^"]+)"')
|
|
154
|
+
const m = re.exec(html)
|
|
155
|
+
if (m && m[1]) return m[1]
|
|
156
|
+
const re2 = new RegExp("'" + name + "'\\s*:\\s*'([^']+)'")
|
|
157
|
+
const m2 = re2.exec(html)
|
|
158
|
+
return m2 && m2[1] ? m2[1] : null
|
|
159
|
+
}
|
|
160
|
+
return {
|
|
161
|
+
at: field("SNlM0e"),
|
|
162
|
+
bl: field("cfb2h"),
|
|
163
|
+
sid: field("FdrFJe"),
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
function looksLikeLogin(html, status) {
|
|
168
|
+
if (status === 302 || status === 303 || status === 307 || status === 308) return true
|
|
169
|
+
const text = String(html || "")
|
|
170
|
+
if (/accounts\.google\.com\/(?:ServiceLogin|v3\/signin)/i.test(text) && !/"SNlM0e"/.test(text)) {
|
|
171
|
+
return true
|
|
172
|
+
}
|
|
173
|
+
return false
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
function parsePlan(text) {
|
|
177
|
+
const s = String(text || "")
|
|
178
|
+
if (/\bUltra\b/i.test(s)) return "Ultra"
|
|
179
|
+
if (/\bPRO\b/.test(s) || /\bPro\b/.test(s)) return "Pro"
|
|
180
|
+
if (/\bFree\b/i.test(s)) return "Free"
|
|
181
|
+
return null
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
function tryParseJson(ctx, text) {
|
|
185
|
+
return ctx.util.tryParseJson(text)
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
function parseBatchexecuteFrames(ctx, text) {
|
|
189
|
+
let s = String(text || "").replace(/^\)\]\}'\s*/, "")
|
|
190
|
+
const frames = []
|
|
191
|
+
let pos = 0
|
|
192
|
+
while (pos < s.length) {
|
|
193
|
+
while (pos < s.length && /\s/.test(s.charAt(pos))) pos += 1
|
|
194
|
+
if (pos >= s.length) break
|
|
195
|
+
if (s.charAt(pos) === "[") {
|
|
196
|
+
const parsed = tryParseJson(ctx, s.slice(pos))
|
|
197
|
+
if (parsed) frames.push(parsed)
|
|
198
|
+
break
|
|
199
|
+
}
|
|
200
|
+
const nl = s.indexOf("\n", pos)
|
|
201
|
+
if (nl < 0) break
|
|
202
|
+
const n = Number(s.slice(pos, nl).trim())
|
|
203
|
+
if (!Number.isFinite(n) || n <= 0) {
|
|
204
|
+
pos = nl + 1
|
|
205
|
+
continue
|
|
206
|
+
}
|
|
207
|
+
const frame = s.slice(nl + 1, nl + 1 + n)
|
|
208
|
+
const parsed = tryParseJson(ctx, frame)
|
|
209
|
+
if (parsed) frames.push(parsed)
|
|
210
|
+
pos = nl + 1 + n
|
|
211
|
+
}
|
|
212
|
+
return frames
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
function extractRpcPayload(ctx, text, rpcId) {
|
|
216
|
+
const marker = '"' + rpcId + '",'
|
|
217
|
+
const idx = String(text || "").indexOf(marker)
|
|
218
|
+
if (idx === -1) return findWrbPayload(ctx, parseBatchexecuteFrames(ctx, text), rpcId)
|
|
219
|
+
let p = idx + marker.length
|
|
220
|
+
while (p < text.length && /\s/.test(text.charAt(p))) p += 1
|
|
221
|
+
if (text.charAt(p) !== '"') {
|
|
222
|
+
return findWrbPayload(ctx, parseBatchexecuteFrames(ctx, text), rpcId)
|
|
223
|
+
}
|
|
224
|
+
let i = p + 1
|
|
225
|
+
while (i < text.length) {
|
|
226
|
+
const ch = text.charAt(i)
|
|
227
|
+
if (ch === "\\") {
|
|
228
|
+
i += 2
|
|
229
|
+
continue
|
|
230
|
+
}
|
|
231
|
+
if (ch === '"') {
|
|
232
|
+
const sliced = text.slice(p, i + 1)
|
|
233
|
+
const inner = tryParseJson(ctx, sliced)
|
|
234
|
+
if (typeof inner === "string") return tryParseJson(ctx, inner) || inner
|
|
235
|
+
return inner
|
|
236
|
+
}
|
|
237
|
+
i += 1
|
|
238
|
+
}
|
|
239
|
+
return findWrbPayload(ctx, parseBatchexecuteFrames(ctx, text), rpcId)
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
function findWrbPayload(ctx, frames, rpcId) {
|
|
243
|
+
function walk(node) {
|
|
244
|
+
if (!node) return null
|
|
245
|
+
if (Array.isArray(node)) {
|
|
246
|
+
if (node[0] === "wrb.fr" && node[1] === rpcId) {
|
|
247
|
+
const payload = node[2]
|
|
248
|
+
if (typeof payload === "string") return tryParseJson(ctx, payload) || payload
|
|
249
|
+
return payload
|
|
250
|
+
}
|
|
251
|
+
for (let i = 0; i < node.length; i += 1) {
|
|
252
|
+
const found = walk(node[i])
|
|
253
|
+
if (found != null) return found
|
|
254
|
+
}
|
|
255
|
+
}
|
|
256
|
+
return null
|
|
257
|
+
}
|
|
258
|
+
for (let i = 0; i < frames.length; i += 1) {
|
|
259
|
+
const found = walk(frames[i])
|
|
260
|
+
if (found != null) return found
|
|
261
|
+
}
|
|
262
|
+
return null
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
function resetUnix(row) {
|
|
266
|
+
if (!row || !Array.isArray(row[3])) return null
|
|
267
|
+
const first = row[3][0]
|
|
268
|
+
if (Array.isArray(first)) {
|
|
269
|
+
const n = Number(first[0])
|
|
270
|
+
return Number.isFinite(n) && n > 0 ? n : null
|
|
271
|
+
}
|
|
272
|
+
const n = Number(first)
|
|
273
|
+
return Number.isFinite(n) && n > 0 ? n : null
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
function parseUsagePayload(payload) {
|
|
277
|
+
let rows = null
|
|
278
|
+
if (Array.isArray(payload) && Array.isArray(payload[1])) rows = payload[1]
|
|
279
|
+
else if (Array.isArray(payload) && payload.length && Array.isArray(payload[0])) rows = payload
|
|
280
|
+
if (!rows) return null
|
|
281
|
+
|
|
282
|
+
let current = null
|
|
283
|
+
let weekly = null
|
|
284
|
+
for (let i = 0; i < rows.length; i += 1) {
|
|
285
|
+
const row = rows[i]
|
|
286
|
+
if (!Array.isArray(row) || row.length < 3) continue
|
|
287
|
+
if (typeof row[1] !== "number" || row[1] < 0 || row[1] > 1) continue
|
|
288
|
+
const frac = row[1]
|
|
289
|
+
const type = Number(row[2])
|
|
290
|
+
if (!Number.isFinite(frac) || !Number.isFinite(type)) continue
|
|
291
|
+
const used = Math.max(0, Math.min(100, Math.round(frac * 100)))
|
|
292
|
+
const bucket = { used: used, resetsAtUnix: resetUnix(row) }
|
|
293
|
+
if (type === 1) current = bucket
|
|
294
|
+
else if (type === 2) weekly = bucket
|
|
295
|
+
}
|
|
296
|
+
if (!current || !weekly) return null
|
|
297
|
+
return { current: current, weekly: weekly }
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
function encodeForm(fields) {
|
|
301
|
+
const parts = []
|
|
302
|
+
const keys = Object.keys(fields)
|
|
303
|
+
for (let i = 0; i < keys.length; i += 1) {
|
|
304
|
+
const key = keys[i]
|
|
305
|
+
if (fields[key] == null) continue
|
|
306
|
+
parts.push(encodeURIComponent(key) + "=" + encodeURIComponent(String(fields[key])))
|
|
307
|
+
}
|
|
308
|
+
return parts.join("&")
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
function batchExecute(ctx, cookies, meta, rpcId, innerPayload) {
|
|
312
|
+
const params = [
|
|
313
|
+
"rpcids=" + encodeURIComponent(rpcId),
|
|
314
|
+
"rt=c",
|
|
315
|
+
"source-path=" + encodeURIComponent("/usage"),
|
|
316
|
+
]
|
|
317
|
+
if (meta.bl) params.push("bl=" + encodeURIComponent(meta.bl))
|
|
318
|
+
if (meta.sid) params.push("f.sid=" + encodeURIComponent(meta.sid))
|
|
319
|
+
params.push("_reqid=" + String(100000 + Math.floor(Math.random() * 900000)))
|
|
320
|
+
|
|
321
|
+
const headers = geminiHeaders(cookies.map, {
|
|
322
|
+
"Content-Type": "application/x-www-form-urlencoded;charset=utf-8",
|
|
323
|
+
})
|
|
324
|
+
const auth = sapisidHash(ctx, cookies.map)
|
|
325
|
+
if (auth) headers.Authorization = auth
|
|
326
|
+
|
|
327
|
+
const form = encodeForm({
|
|
328
|
+
"f.req": JSON.stringify([[[rpcId, innerPayload, null, "generic"]]]),
|
|
329
|
+
at: meta.at,
|
|
330
|
+
})
|
|
331
|
+
|
|
332
|
+
let resp
|
|
333
|
+
try {
|
|
334
|
+
resp = ctx.util.request({
|
|
335
|
+
method: "POST",
|
|
336
|
+
url: BATCH_URL + "?" + params.join("&"),
|
|
337
|
+
headers: headers,
|
|
338
|
+
bodyText: form,
|
|
339
|
+
timeoutMs: 15000,
|
|
340
|
+
http1Only: true,
|
|
341
|
+
})
|
|
342
|
+
} catch (e) {
|
|
343
|
+
ctx.host.log.warn("batchexecute failed (" + rpcId + "): " + String(e))
|
|
344
|
+
return null
|
|
345
|
+
}
|
|
346
|
+
if (looksLikeLogin(resp.bodyText, resp.status)) {
|
|
347
|
+
throw "Gemini Apps session expired. Log into gemini.google.com in Chrome."
|
|
348
|
+
}
|
|
349
|
+
if (resp.status < 200 || resp.status >= 300) {
|
|
350
|
+
ctx.host.log.warn("batchexecute HTTP " + resp.status + " for " + rpcId)
|
|
351
|
+
return null
|
|
352
|
+
}
|
|
353
|
+
return extractRpcPayload(ctx, resp.bodyText, rpcId)
|
|
354
|
+
}
|
|
355
|
+
|
|
356
|
+
function fetchHtml(ctx, cookies, url) {
|
|
357
|
+
const headers = geminiHeaders(cookies.map, { Accept: "text/html" })
|
|
358
|
+
const auth = sapisidHash(ctx, cookies.map)
|
|
359
|
+
if (auth) headers.Authorization = auth
|
|
360
|
+
let resp
|
|
361
|
+
try {
|
|
362
|
+
resp = ctx.util.request({
|
|
363
|
+
method: "GET",
|
|
364
|
+
url: url,
|
|
365
|
+
headers: headers,
|
|
366
|
+
timeoutMs: 15000,
|
|
367
|
+
http1Only: true,
|
|
368
|
+
})
|
|
369
|
+
} catch (e) {
|
|
370
|
+
ctx.host.log.warn("html request failed (" + url + "): " + String(e))
|
|
371
|
+
throw "Could not reach gemini.google.com. Check your connection."
|
|
372
|
+
}
|
|
373
|
+
if (looksLikeLogin(resp.bodyText, resp.status)) {
|
|
374
|
+
throw "Gemini Apps session expired. Log into gemini.google.com in Chrome."
|
|
375
|
+
}
|
|
376
|
+
if (ctx.util.isAuthStatus(resp.status)) {
|
|
377
|
+
throw "Gemini Apps session expired. Log into gemini.google.com in Chrome."
|
|
378
|
+
}
|
|
379
|
+
if (resp.status < 200 || resp.status >= 300) {
|
|
380
|
+
throw "Gemini Apps page failed (HTTP " + resp.status + ")."
|
|
381
|
+
}
|
|
382
|
+
return resp.bodyText
|
|
383
|
+
}
|
|
384
|
+
|
|
385
|
+
function fetchUsagePage(ctx, cookies) {
|
|
386
|
+
let html = fetchHtml(ctx, cookies, APP_URL)
|
|
387
|
+
let meta = extractMeta(html)
|
|
388
|
+
if (!meta.at) {
|
|
389
|
+
ctx.host.log.info("SNlM0e missing on /app; trying /usage")
|
|
390
|
+
html = fetchHtml(ctx, cookies, USAGE_URL)
|
|
391
|
+
meta = extractMeta(html)
|
|
392
|
+
}
|
|
393
|
+
ctx.host.log.info(
|
|
394
|
+
"htmlChars=" +
|
|
395
|
+
String(html.length) +
|
|
396
|
+
" hasAt=" +
|
|
397
|
+
(meta.at ? "yes" : "no") +
|
|
398
|
+
" hasBl=" +
|
|
399
|
+
(meta.bl ? "yes" : "no"),
|
|
400
|
+
)
|
|
401
|
+
if (!meta.at) {
|
|
402
|
+
throw "Could not read Gemini Apps session token. Log into gemini.google.com in Chrome."
|
|
403
|
+
}
|
|
404
|
+
return { html: html, meta: meta }
|
|
405
|
+
}
|
|
406
|
+
|
|
407
|
+
function probe(ctx) {
|
|
408
|
+
const cookies = loadCookies(ctx)
|
|
409
|
+
if (!cookies) {
|
|
410
|
+
throw "Gemini Apps auth missing. Log into gemini.google.com in Chrome."
|
|
411
|
+
}
|
|
412
|
+
ctx.host.log.info("cookie names=" + Object.keys(cookies.map).sort().join(","))
|
|
413
|
+
if (!hasGeminiSession(cookies.map)) {
|
|
414
|
+
throw "Gemini Apps auth missing. Log into gemini.google.com in Chrome."
|
|
415
|
+
}
|
|
416
|
+
|
|
417
|
+
const page = fetchUsagePage(ctx, cookies)
|
|
418
|
+
const payload = batchExecute(ctx, cookies, page.meta, RPC_USAGE, "[]")
|
|
419
|
+
const usage = parseUsagePayload(payload)
|
|
420
|
+
if (!usage) throw "Could not parse Gemini Apps usage."
|
|
421
|
+
|
|
422
|
+
let plan = null
|
|
423
|
+
try {
|
|
424
|
+
const planPayload = batchExecute(ctx, cookies, page.meta, RPC_PLAN, "[]")
|
|
425
|
+
if (planPayload != null) {
|
|
426
|
+
plan = parsePlan(JSON.stringify(planPayload))
|
|
427
|
+
}
|
|
428
|
+
} catch (e) {
|
|
429
|
+
ctx.host.log.warn("plan RPC skipped: " + String(e))
|
|
430
|
+
}
|
|
431
|
+
|
|
432
|
+
const currentResetsAt = usage.current.resetsAtUnix
|
|
433
|
+
? ctx.util.toIso(usage.current.resetsAtUnix)
|
|
434
|
+
: undefined
|
|
435
|
+
const weeklyResetsAt = usage.weekly.resetsAtUnix
|
|
436
|
+
? ctx.util.toIso(usage.weekly.resetsAtUnix)
|
|
437
|
+
: undefined
|
|
438
|
+
|
|
439
|
+
const lines = [
|
|
440
|
+
ctx.line.progress({
|
|
441
|
+
label: "Current",
|
|
442
|
+
used: usage.current.used,
|
|
443
|
+
limit: 100,
|
|
444
|
+
format: { kind: "percent" },
|
|
445
|
+
resetsAt: currentResetsAt,
|
|
446
|
+
periodDurationMs: CURRENT_MS,
|
|
447
|
+
color: BRAND,
|
|
448
|
+
}),
|
|
449
|
+
ctx.line.progress({
|
|
450
|
+
label: "Weekly",
|
|
451
|
+
used: usage.weekly.used,
|
|
452
|
+
limit: 100,
|
|
453
|
+
format: { kind: "percent" },
|
|
454
|
+
resetsAt: weeklyResetsAt,
|
|
455
|
+
periodDurationMs: WEEKLY_MS,
|
|
456
|
+
color: BRAND,
|
|
457
|
+
}),
|
|
458
|
+
ctx.line.text({ label: "Source", value: cookies.source }),
|
|
459
|
+
]
|
|
460
|
+
|
|
461
|
+
const result = { lines: lines, source: "web" }
|
|
462
|
+
if (plan) result.plan = ctx.fmt.planLabel(plan)
|
|
463
|
+
return result
|
|
464
|
+
}
|
|
465
|
+
|
|
466
|
+
globalThis.__openusage_plugin = {
|
|
467
|
+
id: "gemini-apps",
|
|
468
|
+
probe: probe,
|
|
469
|
+
}
|
|
470
|
+
})()
|